Skip to content

Commit 64c1771

Browse files
nickajacks1gaby
andauthored
🔥 Feature: Add Req and Res API (#2894)
* 🔥 feat: add Req and Res interfaces Split the existing Ctx API into two separate APIs for Requests and Responses. There are two goals to this change: 1. Reduce cognitive load by making it more obvious whether a Ctx method interacts with the request or the response. 2. Increase API parity with Express. * fix(req,res): several issues * Sprinkle in calls to Req() and Res() to a few unit tests * Fix improper initialization caught by ^ * Add a few missing methods * docs: organize Ctx methods by request and response * feat(req,res): sync more missed methods --------- Co-authored-by: Juan Calderon-Perez <[email protected]>
1 parent 8e54c8f commit 64c1771

File tree

9 files changed

+1501
-1097
lines changed

9 files changed

+1501
-1097
lines changed

ctx.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ type DefaultCtx struct {
5454
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
5555
bind *Bind // Default bind reference
5656
redirect *Redirect // Default redirect reference
57+
req *DefaultReq // Default request api reference
58+
res *DefaultRes // Default response api reference
5759
values [maxParams]string // Route parameter values
5860
viewBindMap sync.Map // Default view map to bind template engine
5961
method string // HTTP method
@@ -1463,6 +1465,18 @@ func (c *DefaultCtx) renderExtensions(bind any) {
14631465
}
14641466
}
14651467

1468+
// Req returns a convenience type whose API is limited to operations
1469+
// on the incoming request.
1470+
func (c *DefaultCtx) Req() Req {
1471+
return c.req
1472+
}
1473+
1474+
// Res returns a convenience type whose API is limited to operations
1475+
// on the outgoing response.
1476+
func (c *DefaultCtx) Res() Res {
1477+
return c.res
1478+
}
1479+
14661480
// Route returns the matched Route struct.
14671481
func (c *DefaultCtx) Route() *Route {
14681482
if c.route == nil {

ctx_interface.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,14 @@ type CustomCtx interface {
3232

3333
func NewDefaultCtx(app *App) *DefaultCtx {
3434
// return ctx
35-
return &DefaultCtx{
35+
ctx := &DefaultCtx{
3636
// Set app reference
3737
app: app,
3838
}
39+
ctx.req = &DefaultReq{ctx: ctx}
40+
ctx.res = &DefaultRes{ctx: ctx}
41+
42+
return ctx
3943
}
4044

4145
func (app *App) newCtx() Ctx {

ctx_interface_gen.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ctx_test.go

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func Test_Ctx_Accepts(t *testing.T) {
4646

4747
c.Request().Header.Set(HeaderAccept, "text/html,application/xhtml+xml,application/xml;q=0.9")
4848
require.Equal(t, "", c.Accepts(""))
49-
require.Equal(t, "", c.Accepts())
49+
require.Equal(t, "", c.Req().Accepts())
5050
require.Equal(t, ".xml", c.Accepts(".xml"))
5151
require.Equal(t, "", c.Accepts(".john"))
5252
require.Equal(t, "application/xhtml+xml", c.Accepts("application/xml", "application/xml+rss", "application/yaml", "application/xhtml+xml"), "must use client-preferred mime type")
@@ -57,13 +57,13 @@ func Test_Ctx_Accepts(t *testing.T) {
5757
c.Request().Header.Set(HeaderAccept, "text/*, application/json")
5858
require.Equal(t, "html", c.Accepts("html"))
5959
require.Equal(t, "text/html", c.Accepts("text/html"))
60-
require.Equal(t, "json", c.Accepts("json", "text"))
60+
require.Equal(t, "json", c.Req().Accepts("json", "text"))
6161
require.Equal(t, "application/json", c.Accepts("application/json"))
6262
require.Equal(t, "", c.Accepts("image/png"))
6363
require.Equal(t, "", c.Accepts("png"))
6464

6565
c.Request().Header.Set(HeaderAccept, "text/html, application/json")
66-
require.Equal(t, "text/*", c.Accepts("text/*"))
66+
require.Equal(t, "text/*", c.Req().Accepts("text/*"))
6767

6868
c.Request().Header.Set(HeaderAccept, "*/*")
6969
require.Equal(t, "html", c.Accepts("html"))
@@ -968,46 +968,46 @@ func Test_Ctx_Cookie(t *testing.T) {
968968
Expires: expire,
969969
// SameSite: CookieSameSiteStrictMode, // default is "lax"
970970
}
971-
c.Cookie(cookie)
971+
c.Res().Cookie(cookie)
972972
expect := "username=john; expires=" + httpdate + "; path=/; SameSite=Lax"
973-
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
973+
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
974974

975975
expect = "username=john; expires=" + httpdate + "; path=/"
976976
cookie.SameSite = CookieSameSiteDisabled
977-
c.Cookie(cookie)
978-
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
977+
c.Res().Cookie(cookie)
978+
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
979979

980980
expect = "username=john; expires=" + httpdate + "; path=/; SameSite=Strict"
981981
cookie.SameSite = CookieSameSiteStrictMode
982-
c.Cookie(cookie)
983-
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
982+
c.Res().Cookie(cookie)
983+
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
984984

985985
expect = "username=john; expires=" + httpdate + "; path=/; secure; SameSite=None"
986986
cookie.Secure = true
987987
cookie.SameSite = CookieSameSiteNoneMode
988-
c.Cookie(cookie)
989-
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
988+
c.Res().Cookie(cookie)
989+
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
990990

991991
expect = "username=john; path=/; secure; SameSite=None"
992992
// should remove expires and max-age headers
993993
cookie.SessionOnly = true
994994
cookie.Expires = expire
995995
cookie.MaxAge = 10000
996-
c.Cookie(cookie)
997-
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
996+
c.Res().Cookie(cookie)
997+
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
998998

999999
expect = "username=john; path=/; secure; SameSite=None"
10001000
// should remove expires and max-age headers when no expire and no MaxAge (default time)
10011001
cookie.SessionOnly = false
10021002
cookie.Expires = time.Time{}
10031003
cookie.MaxAge = 0
1004-
c.Cookie(cookie)
1005-
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
1004+
c.Res().Cookie(cookie)
1005+
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
10061006

10071007
expect = "username=john; path=/; secure; SameSite=None; Partitioned"
10081008
cookie.Partitioned = true
1009-
c.Cookie(cookie)
1010-
require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
1009+
c.Res().Cookie(cookie)
1010+
require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
10111011
}
10121012

10131013
// go test -v -run=^$ -bench=Benchmark_Ctx_Cookie -benchmem -count=4
@@ -1033,8 +1033,8 @@ func Test_Ctx_Cookies(t *testing.T) {
10331033
c := app.AcquireCtx(&fasthttp.RequestCtx{})
10341034

10351035
c.Request().Header.Set("Cookie", "john=doe")
1036-
require.Equal(t, "doe", c.Cookies("john"))
1037-
require.Equal(t, "default", c.Cookies("unknown", "default"))
1036+
require.Equal(t, "doe", c.Req().Cookies("john"))
1037+
require.Equal(t, "default", c.Req().Cookies("unknown", "default"))
10381038
}
10391039

10401040
// go test -run Test_Ctx_Format
@@ -1058,13 +1058,13 @@ func Test_Ctx_Format(t *testing.T) {
10581058
}
10591059

10601060
c.Request().Header.Set(HeaderAccept, `text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7`)
1061-
err := c.Format(formatHandlers("application/xhtml+xml", "application/xml", "foo/bar")...)
1061+
err := c.Res().Format(formatHandlers("application/xhtml+xml", "application/xml", "foo/bar")...)
10621062
require.Equal(t, "application/xhtml+xml", accepted)
10631063
require.Equal(t, "application/xhtml+xml", c.GetRespHeader(HeaderContentType))
10641064
require.NoError(t, err)
10651065
require.NotEqual(t, StatusNotAcceptable, c.Response().StatusCode())
10661066

1067-
err = c.Format(formatHandlers("foo/bar;a=b")...)
1067+
err = c.Res().Format(formatHandlers("foo/bar;a=b")...)
10681068
require.Equal(t, "foo/bar;a=b", accepted)
10691069
require.Equal(t, "foo/bar;a=b", c.GetRespHeader(HeaderContentType))
10701070
require.NoError(t, err)
@@ -1165,7 +1165,7 @@ func Test_Ctx_AutoFormat(t *testing.T) {
11651165
require.Equal(t, "Hello, World!", string(c.Response().Body()))
11661166

11671167
c.Request().Header.Set(HeaderAccept, MIMETextHTML)
1168-
err = c.AutoFormat("Hello, World!")
1168+
err = c.Res().AutoFormat("Hello, World!")
11691169
require.NoError(t, err)
11701170
require.Equal(t, "<p>Hello, World!</p>", string(c.Response().Body()))
11711171

@@ -1175,7 +1175,7 @@ func Test_Ctx_AutoFormat(t *testing.T) {
11751175
require.Equal(t, `"Hello, World!"`, string(c.Response().Body()))
11761176

11771177
c.Request().Header.Set(HeaderAccept, MIMETextPlain)
1178-
err = c.AutoFormat(complex(1, 1))
1178+
err = c.Res().AutoFormat(complex(1, 1))
11791179
require.NoError(t, err)
11801180
require.Equal(t, "(1+1i)", string(c.Response().Body()))
11811181

@@ -2939,7 +2939,7 @@ func Test_Ctx_SaveFile(t *testing.T) {
29392939
app := New()
29402940

29412941
app.Post("/test", func(c Ctx) error {
2942-
fh, err := c.FormFile("file")
2942+
fh, err := c.Req().FormFile("file")
29432943
require.NoError(t, err)
29442944

29452945
tempFile, err := os.CreateTemp(os.TempDir(), "test-")
@@ -3075,7 +3075,7 @@ func Test_Ctx_ClearCookie(t *testing.T) {
30753075
c := app.AcquireCtx(&fasthttp.RequestCtx{})
30763076

30773077
c.Request().Header.Set(HeaderCookie, "john=doe")
3078-
c.ClearCookie("john")
3078+
c.Res().ClearCookie("john")
30793079
require.True(t, strings.HasPrefix(string(c.Response().Header.Peek(HeaderSetCookie)), "john=; expires="))
30803080

30813081
c.Request().Header.Set(HeaderCookie, "test1=dummy")
@@ -3104,7 +3104,7 @@ func Test_Ctx_Download(t *testing.T) {
31043104
require.Equal(t, expect, c.Response().Body())
31053105
require.Equal(t, `attachment; filename="Awesome+File%21"`, string(c.Response().Header.Peek(HeaderContentDisposition)))
31063106

3107-
require.NoError(t, c.Download("ctx.go"))
3107+
require.NoError(t, c.Res().Download("ctx.go"))
31083108
require.Equal(t, `attachment; filename="ctx.go"`, string(c.Response().Header.Peek(HeaderContentDisposition)))
31093109
}
31103110

@@ -3136,7 +3136,7 @@ func Test_Ctx_SendFile(t *testing.T) {
31363136

31373137
// test with custom error code
31383138
c = app.AcquireCtx(&fasthttp.RequestCtx{})
3139-
err = c.Status(StatusInternalServerError).SendFile("ctx.go")
3139+
err = c.Res().Status(StatusInternalServerError).SendFile("ctx.go")
31403140
// check expectation
31413141
require.NoError(t, err)
31423142
require.Equal(t, expectFileContent, c.Response().Body())
@@ -3161,7 +3161,7 @@ func Test_Ctx_SendFile_ContentType(t *testing.T) {
31613161

31623162
// 1) simple case
31633163
c := app.AcquireCtx(&fasthttp.RequestCtx{})
3164-
err := c.SendFile("./.github/testdata/fs/img/fiber.png")
3164+
err := c.Res().SendFile("./.github/testdata/fs/img/fiber.png")
31653165
// check expectation
31663166
require.NoError(t, err)
31673167
require.Equal(t, StatusOK, c.Response().StatusCode())
@@ -3782,7 +3782,7 @@ func Test_Ctx_JSONP(t *testing.T) {
37823782
require.Equal(t, `callback({"Age":20,"Name":"Grame"});`, string(c.Response().Body()))
37833783
require.Equal(t, "text/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type")))
37843784

3785-
err = c.JSONP(Map{
3785+
err = c.Res().JSONP(Map{
37863786
"Name": "Grame",
37873787
"Age": 20,
37883788
}, "john")
@@ -4006,7 +4006,7 @@ func Test_Ctx_Render(t *testing.T) {
40064006
err = c.Render("./.github/testdata/template-non-exists.html", nil)
40074007
require.Error(t, err)
40084008

4009-
err = c.Render("./.github/testdata/template-invalid.html", nil)
4009+
err = c.Res().Render("./.github/testdata/template-invalid.html", nil)
40104010
require.Error(t, err)
40114011
}
40124012

@@ -4907,7 +4907,7 @@ func Test_Ctx_Queries(t *testing.T) {
49074907

49084908
c.Request().URI().SetQueryString("tags=apple,orange,banana&filters[tags]=apple,orange,banana&filters[category][name]=fruits&filters.tags=apple,orange,banana&filters.category.name=fruits")
49094909

4910-
queries = c.Queries()
4910+
queries = c.Req().Queries()
49114911
require.Equal(t, "apple,orange,banana", queries["tags"])
49124912
require.Equal(t, "apple,orange,banana", queries["filters[tags]"])
49134913
require.Equal(t, "fruits", queries["filters[category][name]"])
@@ -5055,7 +5055,7 @@ func Test_Ctx_IsFromLocal_X_Forwarded(t *testing.T) {
50555055
c := app.AcquireCtx(&fasthttp.RequestCtx{})
50565056
c.Request().Header.Set(HeaderXForwardedFor, "93.46.8.90")
50575057

5058-
require.False(t, c.IsFromLocal())
5058+
require.False(t, c.Req().IsFromLocal())
50595059
}
50605060
}
50615061

@@ -5088,8 +5088,8 @@ func Test_Ctx_IsFromLocal_RemoteAddr(t *testing.T) {
50885088
fastCtx := &fasthttp.RequestCtx{}
50895089
fastCtx.SetRemoteAddr(localIPv6)
50905090
c := app.AcquireCtx(fastCtx)
5091-
require.Equal(t, "::1", c.IP())
5092-
require.True(t, c.IsFromLocal())
5091+
require.Equal(t, "::1", c.Req().IP())
5092+
require.True(t, c.Req().IsFromLocal())
50935093
}
50945094
// Test for the case fasthttp remoteAddr is set to "0:0:0:0:0:0:0:1".
50955095
{

0 commit comments

Comments
 (0)