From 322da9ca24aa7ed0f19625431b030d1e8504fbc1 Mon Sep 17 00:00:00 2001 From: wanghaolong613 Date: Wed, 19 Nov 2025 16:20:21 +0800 Subject: [PATCH] feat(context): support chaining --- context.go | 26 +++++++++++++++++--------- context_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/context.go b/context.go index 112f0ee0..15277a70 100644 --- a/context.go +++ b/context.go @@ -280,7 +280,7 @@ func (c *Context) Error(err error) *Error { // Set is used to store a new key/value pair exclusively for this context. // It also lazy initializes c.Keys if it was not used previously. -func (c *Context) Set(key any, value any) { +func (c *Context) Set(key any, value any) *Context { c.mu.Lock() defer c.mu.Unlock() if c.Keys == nil { @@ -288,6 +288,7 @@ func (c *Context) Set(key any, value any) { } c.Keys[key] = value + return c } // Get returns the value for the given key, ie: (value, true). @@ -506,8 +507,9 @@ func (c *Context) Param(key string) string { // Example Route: "/user/:id" // AddParam("id", 1) // Result: "/user/1" -func (c *Context) AddParam(key, value string) { +func (c *Context) AddParam(key, value string) *Context { c.Params = append(c.Params, Param{Key: key, Value: value}) + return c } // Query returns the keyed url query value if it exists, @@ -1052,19 +1054,21 @@ func bodyAllowedForStatus(status int) bool { } // Status sets the HTTP response code. -func (c *Context) Status(code int) { +func (c *Context) Status(code int) *Context { c.Writer.WriteHeader(code) + return c } // Header is an intelligent shortcut for c.Writer.Header().Set(key, value). // It writes a header in the response. // If value == "", this method removes the header `c.Writer.Header().Del(key)` -func (c *Context) Header(key, value string) { +func (c *Context) Header(key, value string) *Context { if value == "" { c.Writer.Header().Del(key) - return + return c } c.Writer.Header().Set(key, value) + return c } // GetHeader returns value from request headers. @@ -1081,14 +1085,15 @@ func (c *Context) GetRawData() ([]byte, error) { } // SetSameSite with cookie -func (c *Context) SetSameSite(samesite http.SameSite) { +func (c *Context) SetSameSite(samesite http.SameSite) *Context { c.sameSite = samesite + return c } // SetCookie adds a Set-Cookie header to the ResponseWriter's headers. // The provided cookie must have a valid Name. Invalid cookies may be // silently dropped. -func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) { +func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) *Context { if path == "" { path = "/" } @@ -1102,12 +1107,13 @@ func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, Secure: secure, HttpOnly: httpOnly, }) + return c } // SetCookieData adds a Set-Cookie header to the ResponseWriter's headers. // It accepts a pointer to http.Cookie structure for more flexibility in setting cookie attributes. // The provided cookie must have a valid Name. Invalid cookies may be silently dropped. -func (c *Context) SetCookieData(cookie *http.Cookie) { +func (c *Context) SetCookieData(cookie *http.Cookie) *Context { if cookie.Path == "" { cookie.Path = "/" } @@ -1115,6 +1121,7 @@ func (c *Context) SetCookieData(cookie *http.Cookie) { cookie.SameSite = c.sameSite } http.SetCookie(c.Writer, cookie) + return c } // Cookie returns the named cookie provided in the request or @@ -1394,8 +1401,9 @@ func (c *Context) NegotiateFormat(offered ...string) string { } // SetAccepted sets Accept header data. -func (c *Context) SetAccepted(formats ...string) { +func (c *Context) SetAccepted(formats ...string) *Context { c.Accepted = formats + return c } /************************************/ diff --git a/context_test.go b/context_test.go index 126646fc..c4657ee7 100644 --- a/context_test.go +++ b/context_test.go @@ -3677,3 +3677,46 @@ func BenchmarkGetMapFromFormData(b *testing.B) { }) } } + +func TestContextChaining(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + // Basic cookie settings + cookie := &http.Cookie{ + Name: "name", + Value: "gin", + MaxAge: 1, + Path: "/", + Domain: "localhost", + Secure: true, + HttpOnly: true, + } + + c.Set("foo", "bar"). + AddParam("id", "1"). + SetSameSite(http.SameSiteLaxMode). + SetCookie("user", "gin", 1, "/", "localhost", true, true). + SetCookieData(cookie). + Header("Content-Type", "text/plain"). + Header("X-Custom", "value"). + SetAccepted(MIMEJSON, MIMEXML). + Status(200) + + value, err := c.Get("foo") + assert.Equal(t, "bar", value) + assert.True(t, err) + + v, ok := c.Params.Get("id") + assert.True(t, ok) + assert.Equal(t, "1", v) + + assert.Equal(t, []string{"user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure; SameSite=Lax", "name=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure"}, c.Writer.Header().Values("Set-Cookie")) + + assert.Equal(t, "text/plain", c.Writer.Header().Get("Content-Type")) + assert.Equal(t, "value", c.Writer.Header().Get("X-Custom")) + + assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) //nolint:testifylint + assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML, MIMEHTML)) + assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) //nolint:testifylint + + assert.Equal(t, 200, c.Writer.Status()) +}