From 3319038418656a268c889393cb2dd4224c4469ec Mon Sep 17 00:00:00 2001 From: eduardo-ax Date: Sun, 20 Apr 2025 13:01:03 -0300 Subject: [PATCH 001/156] fix(readme): fix broken link to English documentation (#4222) Co-authored-by: Eduardo Alexandre --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa8ada4e..9f548cc0 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ See the [API documentation on go.dev](https://pkg.go.dev/github.com/gin-gonic/gi The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in several languages: -- [English](https://gin-gonic.com/docs/) +- [English](https://gin-gonic.com/en/docs/) - [简体中文](https://gin-gonic.com/zh-cn/docs/) - [繁體中文](https://gin-gonic.com/zh-tw/docs/) - [日本語](https://gin-gonic.com/ja/docs/) From 56fccc39ec5cbe30e39fa34e67371219354f14cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 00:01:35 +0800 Subject: [PATCH 002/156] chore(deps): bump golang.org/x/net from 0.37.0 to 0.38.0 (#4221) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.37.0 to 0.38.0. - [Commits](https://github.com/golang/net/compare/v0.37.0...v0.38.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.38.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1223267c..1756a917 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/quic-go/quic-go v0.48.2 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.37.0 + golang.org/x/net v0.38.0 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 2e1c30ad..71fc142a 100644 --- a/go.sum +++ b/go.sum @@ -90,8 +90,8 @@ golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJ golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From afa0c31d97e1b112ccfe7652957f7d8514580c72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 00:02:02 +0800 Subject: [PATCH 003/156] chore(deps): bump github.com/gin-contrib/sse from 0.1.0 to 1.1.0 (#4216) Bumps [github.com/gin-contrib/sse](https://github.com/gin-contrib/sse) from 0.1.0 to 1.1.0. - [Release notes](https://github.com/gin-contrib/sse/releases) - [Changelog](https://github.com/gin-contrib/sse/blob/master/.goreleaser.yaml) - [Commits](https://github.com/gin-contrib/sse/compare/v0.1.0...v1.1.0) --- updated-dependencies: - dependency-name: github.com/gin-contrib/sse dependency-version: 1.1.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 1756a917..b993ca53 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,14 @@ go 1.23.0 require ( github.com/bytedance/sonic v1.13.1 - github.com/gin-contrib/sse v0.1.0 + github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.22.1 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.2.2 github.com/quic-go/quic-go v0.48.2 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.38.0 google.golang.org/protobuf v1.34.1 diff --git a/go.sum b/go.sum index 71fc142a..c107dc03 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -74,8 +74,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= From 0eb99493c28b09cee339061b0d8a11c9a4f31399 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Mon, 21 Apr 2025 00:05:34 +0800 Subject: [PATCH 004/156] perf: optimize AsciiJSON.Render method by using fmt.Appendf and reusing temp buffer (#4175) per: use bytesconv.BytesToString(ret) instead of string(str) Co-authored-by: 1911860538 --- render/json.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/render/json.go b/render/json.go index fc8dea45..a6b54dc3 100644 --- a/render/json.go +++ b/render/json.go @@ -151,7 +151,7 @@ func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { } // Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType. -func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { +func (r AsciiJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) ret, err := json.Marshal(r.Data) if err != nil { @@ -159,12 +159,15 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { } var buffer bytes.Buffer + escapeBuf := make([]byte, 0, 6) // Preallocate 6 bytes for Unicode escape sequences + for _, r := range bytesconv.BytesToString(ret) { - cvt := string(r) if r >= 128 { - cvt = fmt.Sprintf("\\u%04x", int64(r)) + escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x", r) // Reuse escapeBuf + buffer.Write(escapeBuf) + } else { + buffer.WriteByte(byte(r)) } - buffer.WriteString(cvt) } _, err = w.Write(buffer.Bytes()) From 71496abe6836462e2ed70436b7d72ea2a3585417 Mon Sep 17 00:00:00 2001 From: sunshineplan Date: Mon, 21 Apr 2025 00:11:16 +0800 Subject: [PATCH 005/156] feat(fs): Implement loading HTML from http.FileSystem (#4053) * Implement loading HTML from http.FileSystem * Add OnlyHTMLFS test * Move OnlyHTMLFS to internal and add test --- docs/doc.md | 8 ++- gin.go | 14 ++++++ ginS/gins.go | 5 ++ gin_test.go | 109 +++++++++++++++++++++++++++++++++++++++++ internal/fs/fs.go | 22 +++++++++ internal/fs/fs_test.go | 49 ++++++++++++++++++ render/html.go | 18 +++++-- render/render_test.go | 51 ++++++++++++++----- 8 files changed, 258 insertions(+), 18 deletions(-) create mode 100644 internal/fs/fs.go create mode 100644 internal/fs/fs_test.go diff --git a/docs/doc.md b/docs/doc.md index cd651390..ce466652 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -1393,13 +1393,19 @@ func main() { ### HTML rendering -Using LoadHTMLGlob() or LoadHTMLFiles() +Using LoadHTMLGlob() or LoadHTMLFiles() or LoadHTMLFS() ```go +//go:embed templates/* +var templates embed.FS + func main() { router := gin.Default() router.LoadHTMLGlob("templates/*") //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") + //router.LoadHTMLFS(http.Dir("templates"), "template1.html", "template2.html") + //or + //router.LoadHTMLFS(http.FS(templates), "templates/template1.html", "templates/template2.html") router.GET("/index", func(c *gin.Context) { c.HTML(http.StatusOK, "index.tmpl", gin.H{ "title": "Main website", diff --git a/gin.go b/gin.go index 0761c14d..f9813e1d 100644 --- a/gin.go +++ b/gin.go @@ -16,6 +16,7 @@ import ( "sync" "github.com/gin-gonic/gin/internal/bytesconv" + filesystem "github.com/gin-gonic/gin/internal/fs" "github.com/gin-gonic/gin/render" "github.com/quic-go/quic-go/http3" @@ -285,6 +286,19 @@ func (engine *Engine) LoadHTMLFiles(files ...string) { engine.SetHTMLTemplate(templ) } +// LoadHTMLFS loads an http.FileSystem and a slice of patterns +// and associates the result with HTML renderer. +func (engine *Engine) LoadHTMLFS(fs http.FileSystem, patterns ...string) { + if IsDebugging() { + engine.HTMLRender = render.HTMLDebug{FileSystem: fs, Patterns: patterns, FuncMap: engine.FuncMap, Delims: engine.delims} + return + } + + templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFS( + filesystem.FileSystem{FileSystem: fs}, patterns...)) + engine.SetHTMLTemplate(templ) +} + // SetHTMLTemplate associate a template with HTML renderer. func (engine *Engine) SetHTMLTemplate(templ *template.Template) { if len(engine.trees) > 0 { diff --git a/ginS/gins.go b/ginS/gins.go index 1dcb1919..3e6a92eb 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -32,6 +32,11 @@ func LoadHTMLFiles(files ...string) { engine().LoadHTMLFiles(files...) } +// LoadHTMLFS is a wrapper for Engine.LoadHTMLFS. +func LoadHTMLFS(fs http.FileSystem, patterns ...string) { + engine().LoadHTMLFS(fs, patterns...) +} + // SetHTMLTemplate is a wrapper for Engine.SetHTMLTemplate. func SetHTMLTemplate(templ *template.Template) { engine().SetHTMLTemplate(templ) diff --git a/gin_test.go b/gin_test.go index 850ae09b..a80b690e 100644 --- a/gin_test.go +++ b/gin_test.go @@ -325,6 +325,115 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { assert.Equal(t, "Date: 2017/07/01", string(resp)) } +var tmplFS = http.Dir("testdata/template") + +func TestLoadHTMLFSTestMode(t *testing.T) { + ts := setupHTMLFiles( + t, + TestMode, + false, + func(router *Engine) { + router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + if err != nil { + t.Error(err) + } + + resp, _ := io.ReadAll(res.Body) + assert.Equal(t, "

Hello world

", string(resp)) +} + +func TestLoadHTMLFSDebugMode(t *testing.T) { + ts := setupHTMLFiles( + t, + DebugMode, + false, + func(router *Engine) { + router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + if err != nil { + t.Error(err) + } + + resp, _ := io.ReadAll(res.Body) + assert.Equal(t, "

Hello world

", string(resp)) +} + +func TestLoadHTMLFSReleaseMode(t *testing.T) { + ts := setupHTMLFiles( + t, + ReleaseMode, + false, + func(router *Engine) { + router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + if err != nil { + t.Error(err) + } + + resp, _ := io.ReadAll(res.Body) + assert.Equal(t, "

Hello world

", string(resp)) +} + +func TestLoadHTMLFSUsingTLS(t *testing.T) { + ts := setupHTMLFiles( + t, + TestMode, + true, + func(router *Engine) { + router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl") + }, + ) + defer ts.Close() + + // Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + client := &http.Client{Transport: tr} + res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) + if err != nil { + t.Error(err) + } + + resp, _ := io.ReadAll(res.Body) + assert.Equal(t, "

Hello world

", string(resp)) +} + +func TestLoadHTMLFSFuncMap(t *testing.T) { + ts := setupHTMLFiles( + t, + TestMode, + false, + func(router *Engine) { + router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) + if err != nil { + t.Error(err) + } + + resp, _ := io.ReadAll(res.Body) + assert.Equal(t, "Date: 2017/07/01", string(resp)) +} + func TestAddRoute(t *testing.T) { router := New() router.addRoute(http.MethodGet, "/", HandlersChain{func(_ *Context) {}}) diff --git a/internal/fs/fs.go b/internal/fs/fs.go new file mode 100644 index 00000000..524ac08b --- /dev/null +++ b/internal/fs/fs.go @@ -0,0 +1,22 @@ +package fs + +import ( + "io/fs" + "net/http" +) + +// FileSystem implements an [fs.FS]. +type FileSystem struct { + http.FileSystem +} + +// Open passes `Open` to the upstream implementation and return an [fs.File]. +func (o FileSystem) Open(name string) (fs.File, error) { + f, err := o.FileSystem.Open(name) + + if err != nil { + return nil, err + } + + return fs.File(f), nil +} diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go new file mode 100644 index 00000000..113e92b6 --- /dev/null +++ b/internal/fs/fs_test.go @@ -0,0 +1,49 @@ +package fs + +import ( + "errors" + "net/http" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type mockFileSystem struct { + open func(name string) (http.File, error) +} + +func (m *mockFileSystem) Open(name string) (http.File, error) { + return m.open(name) +} + +func TesFileSystem_Open(t *testing.T) { + var testFile *os.File + mockFS := &mockFileSystem{ + open: func(name string) (http.File, error) { + return testFile, nil + }, + } + fs := &FileSystem{mockFS} + + file, err := fs.Open("foo") + + require.NoError(t, err) + assert.Equal(t, testFile, file) +} + +func TestFileSystem_Open_err(t *testing.T) { + testError := errors.New("mock") + mockFS := &mockFileSystem{ + open: func(_ string) (http.File, error) { + return nil, testError + }, + } + fs := &FileSystem{mockFS} + + file, err := fs.Open("foo") + + require.ErrorIs(t, err, testError) + assert.Nil(t, file) +} diff --git a/render/html.go b/render/html.go index c308408d..f5e7455a 100644 --- a/render/html.go +++ b/render/html.go @@ -7,6 +7,8 @@ package render import ( "html/template" "net/http" + + "github.com/gin-gonic/gin/internal/fs" ) // Delims represents a set of Left and Right delimiters for HTML template rendering. @@ -31,10 +33,12 @@ type HTMLProduction struct { // HTMLDebug contains template delims and pattern and function with file list. type HTMLDebug struct { - Files []string - Glob string - Delims Delims - FuncMap template.FuncMap + Files []string + Glob string + FileSystem http.FileSystem + Patterns []string + Delims Delims + FuncMap template.FuncMap } // HTML contains template reference and its name with given interface object. @@ -73,7 +77,11 @@ func (r HTMLDebug) loadTemplate() *template.Template { if r.Glob != "" { return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob)) } - panic("the HTML debug render was created without files or glob pattern") + if r.FileSystem != nil && len(r.Patterns) > 0 { + return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFS( + fs.FileSystem{FileSystem: r.FileSystem}, r.Patterns...)) + } + panic("the HTML debug render was created without files or glob pattern or file system with patterns") } // Render (HTML) executes template and writes its result with custom ContentType for response. diff --git a/render/render_test.go b/render/render_test.go index ad633b00..4dd2a3af 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -489,10 +489,12 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) { func TestRenderHTMLDebugFiles(t *testing.T) { w := httptest.NewRecorder() htmlRender := HTMLDebug{ - Files: []string{"../testdata/template/hello.tmpl"}, - Glob: "", - Delims: Delims{Left: "{[{", Right: "}]}"}, - FuncMap: nil, + Files: []string{"../testdata/template/hello.tmpl"}, + Glob: "", + FileSystem: nil, + Patterns: nil, + Delims: Delims{Left: "{[{", Right: "}]}"}, + FuncMap: nil, } instance := htmlRender.Instance("hello.tmpl", map[string]any{ "name": "thinkerou", @@ -508,10 +510,33 @@ func TestRenderHTMLDebugFiles(t *testing.T) { func TestRenderHTMLDebugGlob(t *testing.T) { w := httptest.NewRecorder() htmlRender := HTMLDebug{ - Files: nil, - Glob: "../testdata/template/hello*", - Delims: Delims{Left: "{[{", Right: "}]}"}, - FuncMap: nil, + Files: nil, + Glob: "../testdata/template/hello*", + FileSystem: nil, + Patterns: nil, + Delims: Delims{Left: "{[{", Right: "}]}"}, + FuncMap: nil, + } + instance := htmlRender.Instance("hello.tmpl", map[string]any{ + "name": "thinkerou", + }) + + err := instance.Render(w) + + require.NoError(t, err) + assert.Equal(t, "

Hello thinkerou

", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) +} + +func TestRenderHTMLDebugFS(t *testing.T) { + w := httptest.NewRecorder() + htmlRender := HTMLDebug{ + Files: nil, + Glob: "", + FileSystem: http.Dir("../testdata/template"), + Patterns: []string{"hello.tmpl"}, + Delims: Delims{Left: "{[{", Right: "}]}"}, + FuncMap: nil, } instance := htmlRender.Instance("hello.tmpl", map[string]any{ "name": "thinkerou", @@ -526,10 +551,12 @@ func TestRenderHTMLDebugGlob(t *testing.T) { func TestRenderHTMLDebugPanics(t *testing.T) { htmlRender := HTMLDebug{ - Files: nil, - Glob: "", - Delims: Delims{"{{", "}}"}, - FuncMap: nil, + Files: nil, + Glob: "", + FileSystem: nil, + Patterns: nil, + Delims: Delims{"{{", "}}"}, + FuncMap: nil, } assert.Panics(t, func() { htmlRender.Instance("", nil) }) } From 255af882db4baf0ed6209f1a5471f1663c5d0060 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 00:14:45 +0800 Subject: [PATCH 006/156] chore(deps): bump github.com/go-playground/validator/v10 (#4208) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.22.1 to 10.26.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.22.1...v10.26.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-version: 10.26.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index b993ca53..397db4a4 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.0 require ( github.com/bytedance/sonic v1.13.1 github.com/gin-contrib/sse v1.1.0 - github.com/go-playground/validator/v10 v10.22.1 + github.com/go-playground/validator/v10 v10.26.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 @@ -22,7 +22,7 @@ require ( github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/cloudwego/base64x v0.1.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect diff --git a/go.sum b/go.sum index c107dc03..d2fc985e 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= @@ -24,8 +24,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= -github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= From bb824731032856460aa3ffc23bd90e11bf7d5199 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 00:15:51 +0800 Subject: [PATCH 007/156] chore(deps): bump github.com/quic-go/quic-go from 0.48.2 to 0.50.1 (#4197) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.48.2 to 0.50.1. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Changelog](https://github.com/quic-go/quic-go/blob/master/Changelog.md) - [Commits](https://github.com/quic-go/quic-go/compare/v0.48.2...v0.50.1) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++------------ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 397db4a4..3a7e1ba6 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.2.2 - github.com/quic-go/quic-go v0.48.2 + github.com/quic-go/quic-go v0.51.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.38.0 @@ -35,12 +35,12 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - go.uber.org/mock v0.4.0 // indirect + go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/crypto v0.36.0 // indirect - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect - golang.org/x/mod v0.17.0 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/tools v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index d2fc985e..5a7f2adf 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= -github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= +github.com/quic-go/quic-go v0.51.0 h1:K8exxe9zXxeRKxaXxi/GpUqYiTrtdiWP8bo1KFya6Wc= +github.com/quic-go/quic-go v0.51.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -81,16 +81,14 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= @@ -101,10 +99,8 @@ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 67c9d4ee110e9adfe33063ef847dba56717c148a Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Mon, 21 Apr 2025 22:05:28 +0800 Subject: [PATCH 008/156] refactor: replace magic number 128 with unicode.MaxASCII in AsciiJSON Render (#4224) Co-authored-by: huangzw --- render/json.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/render/json.go b/render/json.go index a6b54dc3..23923c44 100644 --- a/render/json.go +++ b/render/json.go @@ -9,6 +9,7 @@ import ( "fmt" "html/template" "net/http" + "unicode" "github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/internal/json" @@ -162,7 +163,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) error { escapeBuf := make([]byte, 0, 6) // Preallocate 6 bytes for Unicode escape sequences for _, r := range bytesconv.BytesToString(ret) { - if r >= 128 { + if r > unicode.MaxASCII { escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x", r) // Reuse escapeBuf buffer.Write(escapeBuf) } else { From 7a1b655074c313f9491c83bb8ea164cdc4a9afe9 Mon Sep 17 00:00:00 2001 From: Yash Date: Sun, 11 May 2025 20:04:09 +0530 Subject: [PATCH 009/156] fix: sonic on arm64 (#4234) --- .github/workflows/gin.yml | 2 +- docs/doc.md | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- internal/json/json.go | 2 +- internal/json/sonic.go | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 1062252a..54185a0a 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -38,7 +38,7 @@ jobs: [ "", "-tags nomsgpack", - '--ldflags="-checklinkname=0" -tags "sonic avx"', + '--ldflags="-checklinkname=0" -tags sonic', "-tags go_json", "-race", ] diff --git a/docs/doc.md b/docs/doc.md index ce466652..9b7b1ec9 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -84,10 +84,10 @@ go build -tags=jsoniter . go build -tags=go_json . ``` -[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu supports avx instruction.) +[sonic](https://github.com/bytedance/sonic) ```sh -$ go build -tags="sonic avx" . +$ go build -tags=sonic . ``` ### Build without `MsgPack` rendering feature diff --git a/go.mod b/go.mod index 3a7e1ba6..15475588 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.23.0 require ( - github.com/bytedance/sonic v1.13.1 + github.com/bytedance/sonic v1.13.2 github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.26.0 github.com/goccy/go-json v0.10.2 diff --git a/go.sum b/go.sum index 5a7f2adf..58aea722 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g= -github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= +github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= +github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= diff --git a/internal/json/json.go b/internal/json/json.go index c7ee83eb..26817786 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -//go:build !jsoniter && !go_json && !(sonic && avx && (linux || windows || darwin) && amd64) +//go:build !jsoniter && !go_json && !(sonic && (linux || windows || darwin)) package json diff --git a/internal/json/sonic.go b/internal/json/sonic.go index 529e16d0..8cd88049 100644 --- a/internal/json/sonic.go +++ b/internal/json/sonic.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -//go:build sonic && avx && (linux || windows || darwin) && amd64 +//go:build sonic && (linux || windows || darwin) package json From 4714c2a9a39f0877ccb38089894263f052025a6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 22:34:39 +0800 Subject: [PATCH 010/156] chore(deps): bump google.golang.org/protobuf from 1.34.1 to 1.36.6 (#4198) Bumps google.golang.org/protobuf from 1.34.1 to 1.36.6. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 15475588..5aa6b36e 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.38.0 - google.golang.org/protobuf v1.34.1 + google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 58aea722..75b73d8c 100644 --- a/go.sum +++ b/go.sum @@ -101,8 +101,8 @@ golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From cf32d2dcf8c7534f59727c5e213e45f2412c593a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 22:35:03 +0800 Subject: [PATCH 011/156] chore(deps): bump github.com/pelletier/go-toml/v2 from 2.2.2 to 2.2.4 (#4212) Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.2.2 to 2.2.4. - [Release notes](https://github.com/pelletier/go-toml/releases) - [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml) - [Commits](https://github.com/pelletier/go-toml/compare/v2.2.2...v2.2.4) --- updated-dependencies: - dependency-name: github.com/pelletier/go-toml/v2 dependency-version: 2.2.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 5aa6b36e..90014fb5 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 - github.com/pelletier/go-toml/v2 v2.2.2 + github.com/pelletier/go-toml/v2 v2.2.4 github.com/quic-go/quic-go v0.51.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 diff --git a/go.sum b/go.sum index 75b73d8c..f8d0534b 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= @@ -66,15 +66,12 @@ github.com/quic-go/quic-go v0.51.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= From b38c59de7fef67400a1c98efeae700a689c45783 Mon Sep 17 00:00:00 2001 From: Orkhan Alikhanov Date: Sun, 11 May 2025 18:38:33 +0400 Subject: [PATCH 012/156] fix(errors): change Unwrap method receiver to value type (#4232) --- errors.go | 2 +- errors_test.go | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/errors.go b/errors.go index 06b53c28..b0d70a94 100644 --- a/errors.go +++ b/errors.go @@ -91,7 +91,7 @@ func (msg *Error) IsType(flags ErrorType) bool { } // Unwrap returns the wrapped error, to allow interoperability with errors.Is(), errors.As() and errors.Unwrap() -func (msg *Error) Unwrap() error { +func (msg Error) Unwrap() error { return msg.Err } diff --git a/errors_test.go b/errors_test.go index 72a36992..91ae9c92 100644 --- a/errors_test.go +++ b/errors_test.go @@ -126,4 +126,15 @@ func TestErrorUnwrap(t *testing.T) { require.ErrorIs(t, err, innerErr) var testErr TestErr require.ErrorAs(t, err, &testErr) + + // Test non-pointer usage of gin.Error + errNonPointer := Error{ + Err: innerErr, + Type: ErrorTypeAny, + } + wrappedErr := fmt.Errorf("wrapped: %w", errNonPointer) + // Check that 'errors.Is()' and 'errors.As()' behave as expected for non-pointer usage + require.ErrorIs(t, wrappedErr, innerErr) + var testErrNonPointer TestErr + require.ErrorAs(t, wrappedErr, &testErrNonPointer) } From ef68fa032c0e6ce637db56e89ec734c0de0a9f5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 22:42:01 +0800 Subject: [PATCH 013/156] chore(deps): bump golang.org/x/net from 0.38.0 to 0.40.0 (#4229) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.38.0 to 0.40.0. - [Commits](https://github.com/golang/net/compare/v0.38.0...v0.40.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.40.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 90014fb5..0a5c626d 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/quic-go/quic-go v0.51.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.38.0 + golang.org/x/net v0.40.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 ) @@ -37,10 +37,10 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.36.0 // indirect + golang.org/x/crypto v0.38.0 // indirect golang.org/x/mod v0.18.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect golang.org/x/tools v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index f8d0534b..b047363b 100644 --- a/go.sum +++ b/go.sum @@ -82,20 +82,20 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= From da67cc1b988ed6498eccaecb989467e87b555dfd Mon Sep 17 00:00:00 2001 From: Siddhesh Mhadnak <10049286+sid-maddy@users.noreply.github.com> Date: Tue, 20 May 2025 15:46:21 +0530 Subject: [PATCH 014/156] test: fix lint failures (#4244) --- gin_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gin_test.go b/gin_test.go index a80b690e..250269e5 100644 --- a/gin_test.go +++ b/gin_test.go @@ -338,7 +338,7 @@ func TestLoadHTMLFSTestMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -358,7 +358,7 @@ func TestLoadHTMLFSDebugMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -378,7 +378,7 @@ func TestLoadHTMLFSReleaseMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -405,7 +405,7 @@ func TestLoadHTMLFSUsingTLS(t *testing.T) { }, } client := &http.Client{Transport: tr} - res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := client.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -425,7 +425,7 @@ func TestLoadHTMLFSFuncMap(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) + res, err := http.Get(ts.URL + "/raw") if err != nil { t.Error(err) } @@ -847,7 +847,7 @@ func handlerTest1(c *Context) {} func handlerTest2(c *Context) {} func TestNewOptionFunc(t *testing.T) { - var fc = func(e *Engine) { + fc := func(e *Engine) { e.GET("/test1", handlerTest1) e.GET("/test2", handlerTest2) From 2e2bd1f408fdaa5e522bc56972e3f109fd7502fd Mon Sep 17 00:00:00 2001 From: Salim Absi Date: Tue, 20 May 2025 13:29:39 +0300 Subject: [PATCH 015/156] test(internal/fs): fix test function name (#4235) --- internal/fs/fs_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index 113e92b6..f937cf7f 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -18,7 +18,7 @@ func (m *mockFileSystem) Open(name string) (http.File, error) { return m.open(name) } -func TesFileSystem_Open(t *testing.T) { +func TestFileSystem_Open(t *testing.T) { var testFile *os.File mockFS := &mockFileSystem{ open: func(name string) (http.File, error) { From 3d8e288c64aa3d3064df6264ab1b5445a28b709d Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Tue, 20 May 2025 22:58:34 +0800 Subject: [PATCH 016/156] perf(all): use strings.Cut to replace strings.SplitN (#4239) Co-authored-by: 1911860538 --- recovery.go | 6 +++--- tree.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/recovery.go b/recovery.go index 515f9d2a..8e4633b4 100644 --- a/recovery.go +++ b/recovery.go @@ -74,9 +74,9 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { httpRequest, _ := httputil.DumpRequest(c.Request, false) headers := strings.Split(string(httpRequest), "\r\n") for idx, header := range headers { - current := strings.Split(header, ":") - if current[0] == "Authorization" { - headers[idx] = current[0] + ": *" + key, _, _ := strings.Cut(header, ":") + if key == "Authorization" { + headers[idx] = key + ": *" } } headersToStr := strings.Join(headers, "\r\n") diff --git a/tree.go b/tree.go index 0d3e5a8c..a298d748 100644 --- a/tree.go +++ b/tree.go @@ -234,7 +234,7 @@ walk: // Wildcard conflict pathSeg := path if n.nType != catchAll { - pathSeg = strings.SplitN(pathSeg, "/", 2)[0] + pathSeg, _, _ = strings.Cut(pathSeg, "/") } prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path panic("'" + pathSeg + @@ -358,7 +358,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { pathSeg := "" if len(n.children) != 0 { - pathSeg = strings.SplitN(n.children[0].path, "/", 2)[0] + pathSeg, _, _ = strings.Cut(n.children[0].path, "/") } panic("catch-all wildcard '" + path + "' in new path '" + fullPath + From fb09c825e8e13134daaa90debfda198520e1b347 Mon Sep 17 00:00:00 2001 From: NARITA <58836324+Narita-1095305@users.noreply.github.com> Date: Wed, 21 May 2025 09:20:44 +0900 Subject: [PATCH 017/156] feat(context): add SetCookieData (#4240) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(context): add SetCookieStruct (#4215)# This is a combination of 2 commits. feat(context): add SetCookieStruct (#4215) feat(context): add SetCookieStruct (#4215) * feat(context): add SetCookieStruct (#4215) * feat(context): fix SetCookieStruct→SetCookieData (gin-gonic#4215) * fix(context): respect caller-specified SameSite value in SetCookieData --- context.go | 13 +++++ context_test.go | 126 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/doc.md | 54 ++++++++++++++++++++- 3 files changed, 192 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 1c76c0f6..d3e4c6d7 100644 --- a/context.go +++ b/context.go @@ -1027,6 +1027,19 @@ func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, }) } +// 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) { + if cookie.Path == "" { + cookie.Path = "/" + } + if cookie.SameSite == http.SameSiteDefaultMode { + cookie.SameSite = c.sameSite + } + http.SetCookie(c.Writer, cookie) +} + // Cookie returns the named cookie provided in the request or // ErrNoCookie if not found. And return the named cookie is unescaped. // If multiple cookies match the given name, only one cookie will diff --git a/context_test.go b/context_test.go index ef0cfccd..c2f82410 100644 --- a/context_test.go +++ b/context_test.go @@ -3123,3 +3123,129 @@ func TestContextNext(t *testing.T) { assert.True(t, exists) assert.Equal(t, "value3", value) } + +func TestContextSetCookieData(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.SetSameSite(http.SameSiteLaxMode) + var setCookie string + + // Basic cookie settings + cookie := &http.Cookie{ + Name: "user", + Value: "gin", + MaxAge: 1, + Path: "/", + Domain: "localhost", + Secure: true, + HttpOnly: true, + } + c.SetCookieData(cookie) + setCookie = c.Writer.Header().Get("Set-Cookie") + assert.Contains(t, setCookie, "user=gin") + assert.Contains(t, setCookie, "Path=/") + assert.Contains(t, setCookie, "Domain=localhost") + assert.Contains(t, setCookie, "Max-Age=1") + assert.Contains(t, setCookie, "HttpOnly") + assert.Contains(t, setCookie, "Secure") + // SameSite=Lax might be omitted in Go 1.23+ as it's the default + // assert.Contains(t, setCookie, "SameSite=Lax") + + // Test that when Path is empty, "/" is automatically set + cookie = &http.Cookie{ + Name: "user", + Value: "gin", + MaxAge: 1, + Path: "", + Domain: "localhost", + Secure: true, + HttpOnly: true, + } + c.SetCookieData(cookie) + setCookie = c.Writer.Header().Get("Set-Cookie") + assert.Contains(t, setCookie, "user=gin") + assert.Contains(t, setCookie, "Path=/") + assert.Contains(t, setCookie, "Domain=localhost") + assert.Contains(t, setCookie, "Max-Age=1") + assert.Contains(t, setCookie, "HttpOnly") + assert.Contains(t, setCookie, "Secure") + // SameSite=Lax might be omitted in Go 1.23+ as it's the default + // assert.Contains(t, setCookie, "SameSite=Lax") + + // Test additional cookie attributes (Expires) + expireTime := time.Now().Add(24 * time.Hour) + cookie = &http.Cookie{ + Name: "user", + Value: "gin", + Path: "/", + Domain: "localhost", + Expires: expireTime, + Secure: true, + HttpOnly: true, + } + c.SetCookieData(cookie) + + // Since the Expires value varies by time, partially verify with Contains + setCookie = c.Writer.Header().Get("Set-Cookie") + assert.Contains(t, setCookie, "user=gin") + assert.Contains(t, setCookie, "Path=/") + assert.Contains(t, setCookie, "Domain=localhost") + assert.Contains(t, setCookie, "HttpOnly") + assert.Contains(t, setCookie, "Secure") + // SameSite=Lax might be omitted in Go 1.23+ as it's the default + // assert.Contains(t, setCookie, "SameSite=Lax") + + // Test for Partitioned attribute (Go 1.18+) + cookie = &http.Cookie{ + Name: "user", + Value: "gin", + Path: "/", + Domain: "localhost", + Secure: true, + HttpOnly: true, + Partitioned: true, + } + c.SetCookieData(cookie) + setCookie = c.Writer.Header().Get("Set-Cookie") + assert.Contains(t, setCookie, "user=gin") + assert.Contains(t, setCookie, "Path=/") + assert.Contains(t, setCookie, "Domain=localhost") + assert.Contains(t, setCookie, "HttpOnly") + assert.Contains(t, setCookie, "Secure") + // SameSite=Lax might be omitted in Go 1.23+ as it's the default + // assert.Contains(t, setCookie, "SameSite=Lax") + // Not testing for Partitioned attribute as it may not be supported in all Go versions + + // Test that SameSiteStrictMode is explicitly included in the header + t.Run("SameSite=Strict is included", func(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + cookie := &http.Cookie{ + Name: "user", + Value: "gin", + Path: "/", + Domain: "localhost", + Secure: true, + HttpOnly: true, + SameSite: http.SameSiteStrictMode, + } + c.SetCookieData(cookie) + setCookie := c.Writer.Header().Get("Set-Cookie") + assert.Contains(t, setCookie, "SameSite=Strict") + }) + + // Test that SameSiteNoneMode is explicitly included in the header + t.Run("SameSite=None is included", func(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + cookie := &http.Cookie{ + Name: "user", + Value: "gin", + Path: "/", + Domain: "localhost", + Secure: true, + HttpOnly: true, + SameSite: http.SameSiteNoneMode, + } + c.SetCookieData(cookie) + setCookie := c.Writer.Header().Get("Set-Cookie") + assert.Contains(t, setCookie, "SameSite=None") + }) +} diff --git a/docs/doc.md b/docs/doc.md index 9b7b1ec9..0e882a1b 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -2304,12 +2304,64 @@ func main() { router := gin.Default() router.GET("/cookie", func(c *gin.Context) { + cookie, err := c.Cookie("gin_cookie") + if err != nil { + cookie = "NotSet" + // Using http.Cookie struct for more control + c.SetCookieData(&http.Cookie{ + Name: "gin_cookie", + Value: "test", + Path: "/", + Domain: "localhost", + MaxAge: 3600, + Secure: false, + HttpOnly: true, + // Additional fields available in http.Cookie + Expires: time.Now().Add(24 * time.Hour), + // Partitioned: true, // Available in newer Go versions + }) + } + + fmt.Printf("Cookie value: %s \n", cookie) + }) + + router.Run() +} +``` + +You can also use the `SetCookieData` method, which accepts a `*http.Cookie` directly for more flexibility: + +```go +import ( + "fmt" + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +func main() { + router := gin.Default() + + router.GET("/cookie", func(c *gin.Context) { cookie, err := c.Cookie("gin_cookie") if err != nil { cookie = "NotSet" - c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) + // Using http.Cookie struct for more control + c.SetCookieData(&http.Cookie{ + Name: "gin_cookie", + Value: "test", + Path: "/", + Domain: "localhost", + MaxAge: 3600, + Secure: false, + HttpOnly: true, + // Additional fields available in http.Cookie + Expires: time.Now().Add(24 * time.Hour), + // Partitioned: true, // Available in newer Go versions + }) } fmt.Printf("Cookie value: %s \n", cookie) From 19f5a13fb421fd71c10a06f244734bba317f63a6 Mon Sep 17 00:00:00 2001 From: Liu Ziming Date: Wed, 21 May 2025 08:25:00 +0800 Subject: [PATCH 018/156] docs(readme): add gin-gonic/contrib (#4134) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f548cc0..1a582709 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr ## Middleware -You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib). +You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib) and [gin-gonic/contrib](https://github.com/gin-gonic/contrib). ## Uses From d00e6a5695c14933081faaba74e92d95cca9377f Mon Sep 17 00:00:00 2001 From: yangquanshi Date: Wed, 21 May 2025 20:14:28 +0900 Subject: [PATCH 019/156] chore: fix some function names in comment (#4131) Signed-off-by: yangquanshi --- context_test.go | 4 ++-- middleware_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/context_test.go b/context_test.go index c2f82410..9857272c 100644 --- a/context_test.go +++ b/context_test.go @@ -1142,7 +1142,7 @@ func TestContextRenderNoContentXML(t *testing.T) { assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } -// TestContextString tests that the response is returned +// TestContextRenderString tests that the response is returned // with Content-Type set to text/plain func TestContextRenderString(t *testing.T) { w := httptest.NewRecorder() @@ -1167,7 +1167,7 @@ func TestContextRenderNoContentString(t *testing.T) { assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } -// TestContextString tests that the response is returned +// TestContextRenderHTMLString tests that the response is returned // with Content-Type set to text/html func TestContextRenderHTMLString(t *testing.T) { w := httptest.NewRecorder() diff --git a/middleware_test.go b/middleware_test.go index eafc60ad..4390def7 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -203,7 +203,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { assert.Equal(t, "ACB", signature) } -// TestFailHandlersChain - ensure that Fail interrupt used middleware in fifo order as +// TestMiddlewareFailHandlersChain - ensure that Fail interrupt used middleware in fifo order as // as well as Abort func TestMiddlewareFailHandlersChain(t *testing.T) { // SETUP From 8f7c340577e19245827f7ba71ef3e0143cc7eeee Mon Sep 17 00:00:00 2001 From: Andreas Deininger Date: Wed, 21 May 2025 13:16:29 +0200 Subject: [PATCH 020/156] context_test.go: fix useless asserts (#4115) --- context_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/context_test.go b/context_test.go index 9857272c..ebcd08d4 100644 --- a/context_test.go +++ b/context_test.go @@ -885,10 +885,10 @@ func TestContextGetCookie(t *testing.T) { } func TestContextBodyAllowedForStatus(t *testing.T) { - assert.False(t, false, bodyAllowedForStatus(http.StatusProcessing)) - assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent)) - assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified)) - assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError)) + assert.False(t, bodyAllowedForStatus(http.StatusProcessing)) + assert.False(t, bodyAllowedForStatus(http.StatusNoContent)) + assert.False(t, bodyAllowedForStatus(http.StatusNotModified)) + assert.True(t, bodyAllowedForStatus(http.StatusInternalServerError)) } type TestRender struct{} From 674522db91d637d179c16c372d87756ea26fa089 Mon Sep 17 00:00:00 2001 From: Kashiwa <13825170+ksw2000@users.noreply.github.com> Date: Wed, 21 May 2025 19:21:46 +0800 Subject: [PATCH 021/156] fix(time): binding time with empty value (#4103) * fix: binding time with empty value (#4098) * refact: simplify null-to-zero filling logic * test: add test for zeroUnixNanoTime --- binding/form_mapping.go | 10 +++++----- binding/form_mapping_test.go | 26 ++++++++++++++++---------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 235692d2..c9f3197f 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -397,6 +397,11 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val timeFormat = time.RFC3339 } + if val == "" { + value.Set(reflect.ValueOf(time.Time{})) + return nil + } + switch tf := strings.ToLower(timeFormat); tf { case "unix", "unixmilli", "unixmicro", "unixnano": tv, err := strconv.ParseInt(val, 10, 64) @@ -420,11 +425,6 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val return nil } - if val == "" { - value.Set(reflect.ValueOf(time.Time{})) - return nil - } - l := time.Local if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC { l = time.UTC diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 1277fd5f..45cd9297 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -183,11 +183,13 @@ func TestMapFormWithTag(t *testing.T) { func TestMappingTime(t *testing.T) { var s struct { - Time time.Time - LocalTime time.Time `time_format:"2006-01-02"` - ZeroValue time.Time - CSTTime time.Time `time_format:"2006-01-02" time_location:"Asia/Shanghai"` - UTCTime time.Time `time_format:"2006-01-02" time_utc:"1"` + Time time.Time + LocalTime time.Time `time_format:"2006-01-02"` + ZeroValue time.Time + ZeroUnixTime time.Time `time_format:"unix"` + ZeroUnixNanoTime time.Time `time_format:"unixnano"` + CSTTime time.Time `time_format:"2006-01-02" time_location:"Asia/Shanghai"` + UTCTime time.Time `time_format:"2006-01-02" time_utc:"1"` } var err error @@ -195,11 +197,13 @@ func TestMappingTime(t *testing.T) { require.NoError(t, err) err = mapForm(&s, map[string][]string{ - "Time": {"2019-01-20T16:02:58Z"}, - "LocalTime": {"2019-01-20"}, - "ZeroValue": {}, - "CSTTime": {"2019-01-20"}, - "UTCTime": {"2019-01-20"}, + "Time": {"2019-01-20T16:02:58Z"}, + "LocalTime": {"2019-01-20"}, + "ZeroValue": {}, + "ZeroUnixTime": {}, + "ZeroUnixNanoTime": {}, + "CSTTime": {"2019-01-20"}, + "UTCTime": {"2019-01-20"}, }) require.NoError(t, err) @@ -207,6 +211,8 @@ func TestMappingTime(t *testing.T) { assert.Equal(t, "2019-01-20 00:00:00 +0100 CET", s.LocalTime.String()) assert.Equal(t, "2019-01-19 23:00:00 +0000 UTC", s.LocalTime.UTC().String()) assert.Equal(t, "0001-01-01 00:00:00 +0000 UTC", s.ZeroValue.String()) + assert.Equal(t, "1970-01-01 00:00:00 +0000 UTC", s.ZeroUnixTime.UTC().String()) + assert.Equal(t, "1970-01-01 00:00:00 +0000 UTC", s.ZeroUnixNanoTime.UTC().String()) assert.Equal(t, "2019-01-20 00:00:00 +0800 CST", s.CSTTime.String()) assert.Equal(t, "2019-01-19 16:00:00 +0000 UTC", s.CSTTime.UTC().String()) assert.Equal(t, "2019-01-20 00:00:00 +0000 UTC", s.UTCTime.String()) From 8fb3136664254d7c592127f00d52849caba18a67 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 22 May 2025 19:20:04 +0800 Subject: [PATCH 022/156] Revert "fix(time): binding time with empty value (#4103)" (#4245) This reverts commit 674522db91d637d179c16c372d87756ea26fa089. --- binding/form_mapping.go | 10 +++++----- binding/form_mapping_test.go | 26 ++++++++++---------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index c9f3197f..235692d2 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -397,11 +397,6 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val timeFormat = time.RFC3339 } - if val == "" { - value.Set(reflect.ValueOf(time.Time{})) - return nil - } - switch tf := strings.ToLower(timeFormat); tf { case "unix", "unixmilli", "unixmicro", "unixnano": tv, err := strconv.ParseInt(val, 10, 64) @@ -425,6 +420,11 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val return nil } + if val == "" { + value.Set(reflect.ValueOf(time.Time{})) + return nil + } + l := time.Local if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC { l = time.UTC diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 45cd9297..1277fd5f 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -183,13 +183,11 @@ func TestMapFormWithTag(t *testing.T) { func TestMappingTime(t *testing.T) { var s struct { - Time time.Time - LocalTime time.Time `time_format:"2006-01-02"` - ZeroValue time.Time - ZeroUnixTime time.Time `time_format:"unix"` - ZeroUnixNanoTime time.Time `time_format:"unixnano"` - CSTTime time.Time `time_format:"2006-01-02" time_location:"Asia/Shanghai"` - UTCTime time.Time `time_format:"2006-01-02" time_utc:"1"` + Time time.Time + LocalTime time.Time `time_format:"2006-01-02"` + ZeroValue time.Time + CSTTime time.Time `time_format:"2006-01-02" time_location:"Asia/Shanghai"` + UTCTime time.Time `time_format:"2006-01-02" time_utc:"1"` } var err error @@ -197,13 +195,11 @@ func TestMappingTime(t *testing.T) { require.NoError(t, err) err = mapForm(&s, map[string][]string{ - "Time": {"2019-01-20T16:02:58Z"}, - "LocalTime": {"2019-01-20"}, - "ZeroValue": {}, - "ZeroUnixTime": {}, - "ZeroUnixNanoTime": {}, - "CSTTime": {"2019-01-20"}, - "UTCTime": {"2019-01-20"}, + "Time": {"2019-01-20T16:02:58Z"}, + "LocalTime": {"2019-01-20"}, + "ZeroValue": {}, + "CSTTime": {"2019-01-20"}, + "UTCTime": {"2019-01-20"}, }) require.NoError(t, err) @@ -211,8 +207,6 @@ func TestMappingTime(t *testing.T) { assert.Equal(t, "2019-01-20 00:00:00 +0100 CET", s.LocalTime.String()) assert.Equal(t, "2019-01-19 23:00:00 +0000 UTC", s.LocalTime.UTC().String()) assert.Equal(t, "0001-01-01 00:00:00 +0000 UTC", s.ZeroValue.String()) - assert.Equal(t, "1970-01-01 00:00:00 +0000 UTC", s.ZeroUnixTime.UTC().String()) - assert.Equal(t, "1970-01-01 00:00:00 +0000 UTC", s.ZeroUnixNanoTime.UTC().String()) assert.Equal(t, "2019-01-20 00:00:00 +0800 CST", s.CSTTime.String()) assert.Equal(t, "2019-01-19 16:00:00 +0000 UTC", s.CSTTime.UTC().String()) assert.Equal(t, "2019-01-20 00:00:00 +0000 UTC", s.UTCTime.String()) From c4287b1300363cb3dc2c8408299d3ba6deded485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Fri, 23 May 2025 14:46:48 +0800 Subject: [PATCH 023/156] ci(golangci-lint): update configuration and fix lint issues (#4247) * ci: update golangci-lint configuration and lint settings - Update golangci-lint to version 2 - Enable new linters and adjust existing ones - Update lint settings across multiple test files - Remove unused struct and variable checks - Add new lint exclusions for generated code and specific directories Signed-off-by: Flc * ci(github): update golangci-lint-action to v8 and lint version to v2.3.4 Signed-off-by: Flc * ci: downgrade golangci-lint to v2.1.6 Signed-off-by: Flc * ci(golangci): add gofumpt linter and fix related issues- Added gofumpt linter to .golangci.yml Signed-off-by: Flc * test: ignore testifylint and gofumpt lints in specific test cases Signed-off-by: Flc * build(deps): remove golang.org/x/lint - Remove golang.org/x/lint package from go.mod - Update related dependencies in go.sum Signed-off-by: flc1125 * build(deps): downgrade golang.org/x/mod and golang.org/x/tools - Downgrade golang.org/x/mod from v0.24.0 to v0.18.0 - Downgrade golang.org/x/tools from v0.33.0 to v.22.0 These changes are made to address compatibility issues with the current project setup. Signed-off-by: flc1125 --------- Signed-off-by: Flc Signed-off-by: flc1125 --- .github/workflows/gin.yml | 4 +- .golangci.yml | 120 +++++++++++++++------------ binding/binding_test.go | 6 +- binding/default_validator_test.go | 6 +- binding/form.go | 8 +- binding/form_mapping_test.go | 36 ++++---- binding/header.go | 1 - binding/validate_test.go | 14 ++-- binding/xml.go | 1 + context_test.go | 38 ++++----- errors_test.go | 6 +- fs.go | 1 - gin.go | 3 +- ginS/gins.go | 6 +- gin_integration_test.go | 7 +- gin_test.go | 4 +- githubapi_test.go | 8 +- internal/bytesconv/bytesconv_test.go | 6 +- internal/fs/fs.go | 1 - logger_test.go | 4 +- middleware_test.go | 2 +- mode.go | 6 +- render/html.go | 1 + render/render_test.go | 12 +-- routes_test.go | 4 +- tree_test.go | 6 +- utils_test.go | 2 +- 27 files changed, 164 insertions(+), 149 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 54185a0a..6f0f7c11 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -24,9 +24,9 @@ jobs: with: go-version: "^1" - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v8 with: - version: v1.61.0 + version: v2.1.6 args: --verbose test: needs: lint diff --git a/.golangci.yml b/.golangci.yml index 925e1306..d9a60ce2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,16 +1,11 @@ -run: - timeout: 5m +version: "2" linters: enable: - asciicheck + - copyloopvar - dogsled - durationcheck - - errcheck - errorlint - - copyloopvar - - gci - - gofmt - - goimports - gosec - misspell - nakedret @@ -21,51 +16,66 @@ linters: - testifylint - usestdlibvars - wastedassign - -linters-settings: - gosec: - # To select a subset of rules to run. - # Available rules: https://github.com/securego/gosec#available-rules - # Default: [] - means include all rules - includes: - - G102 - - G106 - - G108 - - G109 - - G111 - - G112 - - G201 - - G203 - perfsprint: - err-error: true - errorf: true - int-conversion: true - sprintf1: true - strconcat: true - testifylint: - enable-all: true - -issues: - exclude-rules: - - linters: - - structcheck - - unused - text: "`data` is unused" - - linters: - - staticcheck - text: "SA1019:" - - linters: - - revive - text: "var-naming:" - - linters: - - revive - text: "exported:" - - path: _test\.go - linters: - - gosec # security is not make sense in tests - - linters: - - revive - path: _test\.go - - path: gin.go - linters: - - gci + settings: + gosec: + includes: + - G102 + - G106 + - G108 + - G109 + - G111 + - G112 + - G201 + - G203 + perfsprint: + int-conversion: true + err-error: true + errorf: true + sprintf1: true + strconcat: true + testifylint: + enable-all: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - structcheck + - unused + text: '`data` is unused' + - linters: + - staticcheck + text: 'SA1019:' + - linters: + - revive + text: 'var-naming:' + - linters: + - revive + text: 'exported:' + - linters: + - gosec + path: _test\.go + - linters: + - revive + path: _test\.go + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofmt + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ + - gin.go diff --git a/binding/binding_test.go b/binding/binding_test.go index bdab3694..07619ebf 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -51,8 +51,6 @@ type FooBarFileStruct struct { type FooBarFileFailStruct struct { FooBarStruct File *multipart.FileHeader `invalid_name:"file" binding:"required"` - // for unexport test - data *multipart.FileHeader `form:"data" binding:"required"` } type FooDefaultBarStruct struct { @@ -1063,7 +1061,7 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo } err := b.Bind(req, &obj) require.NoError(t, err) - assert.Equal(t, "", obj.TestName) + assert.Empty(t, obj.TestName) obj = InvalidNameType{} req = requestWithBody(method, badPath, badBody) @@ -1318,7 +1316,7 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad req := requestWithBody(http.MethodPost, path, body) err := b.Bind(req, &obj) require.Error(t, err) - assert.Equal(t, "", obj.Foo) + assert.Empty(t, obj.Foo) obj = FooStruct{} req = requestWithBody(http.MethodPost, badPath, badBody) diff --git a/binding/default_validator_test.go b/binding/default_validator_test.go index df7742b7..fc819eb4 100644 --- a/binding/default_validator_test.go +++ b/binding/default_validator_test.go @@ -18,14 +18,16 @@ func TestSliceValidationError(t *testing.T) { {"has nil elements", SliceValidationError{errors.New("test error"), nil}, "[0]: test error"}, {"has zero elements", SliceValidationError{}, ""}, {"has one element", SliceValidationError{errors.New("test one error")}, "[0]: test one error"}, - {"has two elements", + { + "has two elements", SliceValidationError{ errors.New("first error"), errors.New("second error"), }, "[0]: first error\n[1]: second error", }, - {"has many elements", + { + "has many elements", SliceValidationError{ errors.New("first error"), errors.New("second error"), diff --git a/binding/form.go b/binding/form.go index b17352ba..06732e97 100644 --- a/binding/form.go +++ b/binding/form.go @@ -11,9 +11,11 @@ import ( const defaultMemory = 32 << 20 -type formBinding struct{} -type formPostBinding struct{} -type formMultipartBinding struct{} +type ( + formBinding struct{} + formPostBinding struct{} + formMultipartBinding struct{} +) func (formBinding) Name() string { return "form" diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 1277fd5f..55b967a3 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -509,9 +509,9 @@ func TestMappingCustomStructTypeWithFormTag(t *testing.T) { err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form") require.NoError(t, err) - assert.EqualValues(t, "file", s.FileData.Protocol) - assert.EqualValues(t, "/foo", s.FileData.Path) - assert.EqualValues(t, "happiness", s.FileData.Name) + assert.Equal(t, "file", s.FileData.Protocol) + assert.Equal(t, "/foo", s.FileData.Path) + assert.Equal(t, "happiness", s.FileData.Name) } func TestMappingCustomStructTypeWithURITag(t *testing.T) { @@ -521,9 +521,9 @@ func TestMappingCustomStructTypeWithURITag(t *testing.T) { err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri") require.NoError(t, err) - assert.EqualValues(t, "file", s.FileData.Protocol) - assert.EqualValues(t, "/foo", s.FileData.Path) - assert.EqualValues(t, "happiness", s.FileData.Name) + assert.Equal(t, "file", s.FileData.Protocol) + assert.Equal(t, "/foo", s.FileData.Path) + assert.Equal(t, "happiness", s.FileData.Name) } func TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) { @@ -533,9 +533,9 @@ func TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) { err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form") require.NoError(t, err) - assert.EqualValues(t, "file", s.FileData.Protocol) - assert.EqualValues(t, "/foo", s.FileData.Path) - assert.EqualValues(t, "happiness", s.FileData.Name) + assert.Equal(t, "file", s.FileData.Protocol) + assert.Equal(t, "/foo", s.FileData.Path) + assert.Equal(t, "happiness", s.FileData.Name) } func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) { @@ -545,9 +545,9 @@ func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) { err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri") require.NoError(t, err) - assert.EqualValues(t, "file", s.FileData.Protocol) - assert.EqualValues(t, "/foo", s.FileData.Path) - assert.EqualValues(t, "happiness", s.FileData.Name) + assert.Equal(t, "file", s.FileData.Protocol) + assert.Equal(t, "/foo", s.FileData.Path) + assert.Equal(t, "happiness", s.FileData.Name) } type customPath []string @@ -570,8 +570,8 @@ func TestMappingCustomSliceUri(t *testing.T) { err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "uri") require.NoError(t, err) - assert.EqualValues(t, "bar", s.FileData[0]) - assert.EqualValues(t, "foo", s.FileData[1]) + assert.Equal(t, "bar", s.FileData[0]) + assert.Equal(t, "foo", s.FileData[1]) } func TestMappingCustomSliceForm(t *testing.T) { @@ -581,8 +581,8 @@ func TestMappingCustomSliceForm(t *testing.T) { err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "form") require.NoError(t, err) - assert.EqualValues(t, "bar", s.FileData[0]) - assert.EqualValues(t, "foo", s.FileData[1]) + assert.Equal(t, "bar", s.FileData[0]) + assert.Equal(t, "foo", s.FileData[1]) } type objectID [12]byte @@ -621,7 +621,7 @@ func TestMappingCustomArrayUri(t *testing.T) { require.NoError(t, err) expected, _ := convertTo(val) - assert.EqualValues(t, expected, s.FileData) + assert.Equal(t, expected, s.FileData) } func TestMappingCustomArrayForm(t *testing.T) { @@ -633,5 +633,5 @@ func TestMappingCustomArrayForm(t *testing.T) { require.NoError(t, err) expected, _ := convertTo(val) - assert.EqualValues(t, expected, s.FileData) + assert.Equal(t, expected, s.FileData) } diff --git a/binding/header.go b/binding/header.go index 03bc78da..6ed8c0c5 100644 --- a/binding/header.go +++ b/binding/header.go @@ -17,7 +17,6 @@ func (headerBinding) Name() string { } func (headerBinding) Bind(req *http.Request, obj any) error { - if err := mapHeader(obj, req.Header); err != nil { return err } diff --git a/binding/validate_test.go b/binding/validate_test.go index c9bbe601..792f64c4 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -158,16 +158,16 @@ type structNoValidationPointer struct { } func TestValidateNoValidationPointers(t *testing.T) { - //origin := createNoValidation_values() - //test := createNoValidation_values() + // origin := createNoValidation_values() + // test := createNoValidation_values() empty := structNoValidationPointer{} - //assert.Nil(t, validate(test)) - //assert.Nil(t, validate(&test)) + // assert.Nil(t, validate(test)) + // assert.Nil(t, validate(&test)) require.NoError(t, validate(empty)) require.NoError(t, validate(&empty)) - //assert.Equal(t, origin, test) + // assert.Equal(t, origin, test) } type Object map[string]any @@ -198,7 +198,7 @@ type structModifyValidation struct { } func toZero(sl validator.StructLevel) { - var s *structModifyValidation = sl.Top().Interface().(*structModifyValidation) + s := sl.Top().Interface().(*structModifyValidation) s.Integer = 0 } @@ -249,5 +249,5 @@ func TestValidatorEngine(t *testing.T) { // Check that we got back non-nil errs require.Error(t, errs) // Check that the error matches expectation - require.Error(t, errs, "", "", "notone") + require.Error(t, errs, "notone") } diff --git a/binding/xml.go b/binding/xml.go index a70f4ad3..acd6f942 100644 --- a/binding/xml.go +++ b/binding/xml.go @@ -24,6 +24,7 @@ func (xmlBinding) Bind(req *http.Request, obj any) error { func (xmlBinding) BindBody(body []byte, obj any) error { return decodeXML(bytes.NewReader(body), obj) } + func decodeXML(r io.Reader, obj any) error { decoder := xml.NewDecoder(r) if err := decoder.Decode(obj); err != nil { diff --git a/context_test.go b/context_test.go index ebcd08d4..084bae5d 100644 --- a/context_test.go +++ b/context_test.go @@ -918,7 +918,7 @@ func TestContextRenderJSON(t *testing.T) { c.JSON(http.StatusCreated, H{"foo": "bar", "html": ""}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -946,7 +946,7 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) { c.JSONP(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -972,7 +972,7 @@ func TestContextRenderAPIJSON(t *testing.T) { c.JSON(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type")) } @@ -998,7 +998,7 @@ func TestContextRenderIndentedJSON(t *testing.T) { c.IndentedJSON(http.StatusCreated, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String()) + assert.JSONEq(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -1059,7 +1059,7 @@ func TestContextRenderPureJSON(t *testing.T) { c, _ := CreateTestContext(w) c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": ""}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -1233,7 +1233,7 @@ func TestContextRenderSSE(t *testing.T) { "bar": "foo", }) - assert.Equal(t, strings.Replace(w.Body.String(), " ", "", -1), strings.Replace("event:float\ndata:1.5\n\nid:123\ndata:text\n\nevent:chat\ndata:{\"bar\":\"foo\",\"foo\":\"bar\"}\n\n", " ", "", -1)) + assert.Equal(t, strings.ReplaceAll(w.Body.String(), " ", ""), strings.ReplaceAll("event:float\ndata:1.5\n\nid:123\ndata:text\n\nevent:chat\ndata:{\"bar\":\"foo\",\"foo\":\"bar\"}\n\n", " ", "")) } func TestContextRenderFile(t *testing.T) { @@ -1247,7 +1247,7 @@ func TestContextRenderFile(t *testing.T) { assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {") // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, // else, Content-Type='text/x-go; charset=utf-8' - assert.NotEqual(t, "", w.Header().Get("Content-Type")) + assert.NotEmpty(t, w.Header().Get("Content-Type")) } func TestContextRenderFileFromFS(t *testing.T) { @@ -1261,7 +1261,7 @@ func TestContextRenderFileFromFS(t *testing.T) { assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {") // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, // else, Content-Type='text/x-go; charset=utf-8' - assert.NotEqual(t, "", w.Header().Get("Content-Type")) + assert.NotEmpty(t, w.Header().Get("Content-Type")) assert.Equal(t, "/some/path", c.Request.URL.Path) } @@ -1432,7 +1432,7 @@ func TestContextNegotiationWithJSON(t *testing.T) { }) assert.Equal(t, http.StatusOK, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -1518,7 +1518,7 @@ func TestContextNegotiationFormat(t *testing.T) { c.Request, _ = http.NewRequest(http.MethodPost, "", nil) assert.Panics(t, func() { c.NegotiateFormat() }) - assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) + assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) //nolint:testifylint assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML, MIMEJSON)) } @@ -1540,7 +1540,7 @@ func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { assert.Equal(t, "*/*", c.NegotiateFormat("*/*")) assert.Equal(t, "text/*", c.NegotiateFormat("text/*")) assert.Equal(t, "application/*", c.NegotiateFormat("application/*")) - assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) + assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) //nolint:testifylint assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML)) assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML)) @@ -1550,9 +1550,9 @@ func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { assert.Equal(t, "*/*", c.NegotiateFormat("*/*")) assert.Equal(t, "text/*", c.NegotiateFormat("text/*")) - assert.Equal(t, "", c.NegotiateFormat("application/*")) - assert.Equal(t, "", c.NegotiateFormat(MIMEJSON)) - assert.Equal(t, "", c.NegotiateFormat(MIMEXML)) + assert.Empty(t, c.NegotiateFormat("application/*")) + assert.Empty(t, c.NegotiateFormat(MIMEJSON)) + assert.Empty(t, c.NegotiateFormat(MIMEXML)) assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML)) } @@ -1564,9 +1564,9 @@ func TestContextNegotiationFormatCustom(t *testing.T) { c.Accepted = nil c.SetAccepted(MIMEJSON, MIMEXML) - assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) + 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)) + assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) //nolint:testifylint } func TestContextNegotiationFormat2(t *testing.T) { @@ -1574,7 +1574,7 @@ func TestContextNegotiationFormat2(t *testing.T) { c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("Accept", "image/tiff-fx") - assert.Equal(t, "", c.NegotiateFormat("image/tiff")) + assert.Empty(t, c.NegotiateFormat("image/tiff")) } func TestContextIsAborted(t *testing.T) { @@ -1634,7 +1634,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) { _, err := buf.ReadFrom(w.Body) require.NoError(t, err) jsonStringBody := buf.String() - assert.Equal(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody) + assert.JSONEq(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody) } func TestContextError(t *testing.T) { @@ -3076,7 +3076,7 @@ func TestInterceptedHeader(t *testing.T) { // Compared to this time, this is when the response headers will be flushed // As response is flushed on c.String, the Header cannot be set by the first // middleware. Assert this - assert.Equal(t, "", w.Result().Header.Get("X-Test")) + assert.Empty(t, w.Result().Header.Get("X-Test")) assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) } diff --git a/errors_test.go b/errors_test.go index 91ae9c92..85ed3dd5 100644 --- a/errors_test.go +++ b/errors_test.go @@ -34,7 +34,7 @@ func TestError(t *testing.T) { }, err.JSON()) jsonBytes, _ := json.Marshal(err) - assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) + assert.JSONEq(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) err.SetMeta(H{ //nolint: errcheck "status": "200", @@ -93,13 +93,13 @@ Error #03: third H{"error": "third", "status": "400"}, }, errs.JSON()) jsonBytes, _ := json.Marshal(errs) - assert.Equal(t, "[{\"error\":\"first\"},{\"error\":\"second\",\"meta\":\"some data\"},{\"error\":\"third\",\"status\":\"400\"}]", string(jsonBytes)) + assert.JSONEq(t, "[{\"error\":\"first\"},{\"error\":\"second\",\"meta\":\"some data\"},{\"error\":\"third\",\"status\":\"400\"}]", string(jsonBytes)) errs = errorMsgs{ {Err: errors.New("first"), Type: ErrorTypePrivate}, } assert.Equal(t, H{"error": "first"}, errs.JSON()) jsonBytes, _ = json.Marshal(errs) - assert.Equal(t, "{\"error\":\"first\"}", string(jsonBytes)) + assert.JSONEq(t, "{\"error\":\"first\"}", string(jsonBytes)) errs = errorMsgs{} assert.Nil(t, errs.Last()) diff --git a/fs.go b/fs.go index 51c3db86..ab18adb3 100644 --- a/fs.go +++ b/fs.go @@ -17,7 +17,6 @@ type OnlyFilesFS struct { // Open passes `Open` to the upstream implementation without `Readdir` functionality. func (o OnlyFilesFS) Open(name string) (http.File, error) { f, err := o.FileSystem.Open(name) - if err != nil { return nil, err } diff --git a/gin.go b/gin.go index f9813e1d..27eea83e 100644 --- a/gin.go +++ b/gin.go @@ -18,7 +18,6 @@ import ( "github.com/gin-gonic/gin/internal/bytesconv" filesystem "github.com/gin-gonic/gin/internal/fs" "github.com/gin-gonic/gin/render" - "github.com/quic-go/quic-go/http3" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" @@ -216,7 +215,7 @@ func New(opts ...OptionFunc) *Engine { trustedProxies: []string{"0.0.0.0/0", "::/0"}, trustedCIDRs: defaultTrustedCIDRs, } - engine.RouterGroup.engine = engine + engine.engine = engine engine.pool.New = func() any { return engine.allocateContext(engine.maxParams) } diff --git a/ginS/gins.go b/ginS/gins.go index 3e6a92eb..40088172 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -12,8 +12,10 @@ import ( "github.com/gin-gonic/gin" ) -var once sync.Once -var internalEngine *gin.Engine +var ( + once sync.Once + internalEngine *gin.Engine +) func engine() *gin.Engine { once.Do(func() { diff --git a/gin_integration_test.go b/gin_integration_test.go index 3082bc2c..c032d837 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -28,7 +28,6 @@ import ( // params[1]=response status (custom compare status) default:"200 OK" // params[2]=response body (custom compare content) default:"it worked" func testRequest(t *testing.T, params ...string) { - if len(params) == 0 { t.Fatal("url cannot be empty") } @@ -47,12 +46,12 @@ func testRequest(t *testing.T, params ...string) { body, ioerr := io.ReadAll(resp.Body) require.NoError(t, ioerr) - var responseStatus = "200 OK" + responseStatus := "200 OK" if len(params) > 1 && params[1] != "" { responseStatus = params[1] } - var responseBody = "it worked" + responseBody := "it worked" if len(params) > 2 && params[2] != "" { responseBody = params[2] } @@ -170,7 +169,7 @@ func TestRunTLS(t *testing.T) { } func TestPusher(t *testing.T) { - var html = template.Must(template.New("https").Parse(` + html := template.Must(template.New("https").Parse(` Https Test diff --git a/gin_test.go b/gin_test.go index 250269e5..be076537 100644 --- a/gin_test.go +++ b/gin_test.go @@ -46,7 +46,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine }) router.GET("/raw", func(c *Context) { c.HTML(http.StatusOK, "raw.tmpl", map[string]any{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), //nolint:gofumpt }) }) }) @@ -883,7 +883,7 @@ func TestWithOptionFunc(t *testing.T) { type Birthday string func (b *Birthday) UnmarshalParam(param string) error { - *b = Birthday(strings.Replace(param, "-", "/", -1)) + *b = Birthday(strings.ReplaceAll(param, "-", "/")) return nil } diff --git a/githubapi_test.go b/githubapi_test.go index 0c86af2e..20d4aeaf 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -298,8 +298,8 @@ func TestShouldBindUri(t *testing.T) { router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person require.NoError(t, c.ShouldBindUri(&person)) - assert.NotEqual(t, "", person.Name) - assert.NotEqual(t, "", person.ID) + assert.NotEmpty(t, person.Name) + assert.NotEmpty(t, person.ID) c.String(http.StatusOK, "ShouldBindUri test OK") }) @@ -320,8 +320,8 @@ func TestBindUri(t *testing.T) { router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person require.NoError(t, c.BindUri(&person)) - assert.NotEqual(t, "", person.Name) - assert.NotEqual(t, "", person.ID) + assert.NotEmpty(t, person.Name) + assert.NotEmpty(t, person.ID) c.String(http.StatusOK, "BindUri test OK") }) diff --git a/internal/bytesconv/bytesconv_test.go b/internal/bytesconv/bytesconv_test.go index eeaad5ee..497069ce 100644 --- a/internal/bytesconv/bytesconv_test.go +++ b/internal/bytesconv/bytesconv_test.go @@ -12,8 +12,10 @@ import ( "time" ) -var testString = "Albert Einstein: Logic will get you from A to B. Imagination will take you everywhere." -var testBytes = []byte(testString) +var ( + testString = "Albert Einstein: Logic will get you from A to B. Imagination will take you everywhere." + testBytes = []byte(testString) +) func rawBytesToStr(b []byte) string { return string(b) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 524ac08b..c2530383 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -13,7 +13,6 @@ type FileSystem struct { // Open passes `Open` to the upstream implementation and return an [fs.File]. func (o FileSystem) Open(name string) (fs.File, error) { f, err := o.FileSystem.Open(name) - if err != nil { return nil, err } diff --git a/logger_test.go b/logger_test.go index de00c499..30b25290 100644 --- a/logger_test.go +++ b/logger_test.go @@ -371,11 +371,11 @@ func TestErrorLogger(t *testing.T) { w := PerformRequest(router, http.MethodGet, "/error") assert.Equal(t, http.StatusOK, w.Code) - assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) + assert.JSONEq(t, "{\"error\":\"this is an error\"}", w.Body.String()) w = PerformRequest(router, http.MethodGet, "/abort") assert.Equal(t, http.StatusUnauthorized, w.Code) - assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) + assert.JSONEq(t, "{\"error\":\"no authorized\"}", w.Body.String()) w = PerformRequest(router, http.MethodGet, "/print") assert.Equal(t, http.StatusInternalServerError, w.Code) diff --git a/middleware_test.go b/middleware_test.go index 4390def7..8dc7c3b3 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -249,5 +249,5 @@ func TestMiddlewareWrite(t *testing.T) { w := PerformRequest(router, http.MethodGet, "/") assert.Equal(t, http.StatusBadRequest, w.Code) - assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) + assert.Equal(t, strings.ReplaceAll("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", ""), strings.ReplaceAll(w.Body.String(), " ", "")) } diff --git a/mode.go b/mode.go index 13aa3be0..cc313437 100644 --- a/mode.go +++ b/mode.go @@ -44,8 +44,10 @@ var DefaultWriter io.Writer = os.Stdout // DefaultErrorWriter is the default io.Writer used by Gin to debug errors var DefaultErrorWriter io.Writer = os.Stderr -var ginMode int32 = debugCode -var modeName atomic.Value +var ( + ginMode int32 = debugCode + modeName atomic.Value +) func init() { mode := os.Getenv(EnvGinMode) diff --git a/render/html.go b/render/html.go index f5e7455a..965d84c6 100644 --- a/render/html.go +++ b/render/html.go @@ -67,6 +67,7 @@ func (r HTMLDebug) Instance(name string, data any) Render { Data: data, } } + func (r HTMLDebug) loadTemplate() *template.Template { if r.FuncMap == nil { r.FuncMap = template.FuncMap{} diff --git a/render/render_test.go b/render/render_test.go index 4dd2a3af..447ea6a8 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -38,7 +38,7 @@ func TestRenderJSON(t *testing.T) { err := (JSON{data}).Render(w) require.NoError(t, err) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -60,7 +60,7 @@ func TestRenderIndentedJSON(t *testing.T) { err := (IndentedJSON{data}).Render(w) require.NoError(t, err) - assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String()) + assert.JSONEq(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -85,7 +85,7 @@ func TestRenderSecureJSON(t *testing.T) { err1 := (SecureJSON{"while(1);", data}).Render(w1) require.NoError(t, err1) - assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\"}", w1.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type")) w2 := httptest.NewRecorder() @@ -194,7 +194,7 @@ func TestRenderJsonpJSONError2(t *testing.T) { e := (JsonpJSON{"", data}).Render(w) require.NoError(t, e) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) } @@ -217,7 +217,7 @@ func TestRenderAsciiJSON(t *testing.T) { err := (AsciiJSON{data1}).Render(w1) require.NoError(t, err) - assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String()) + assert.JSONEq(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String()) assert.Equal(t, "application/json", w1.Header().Get("Content-Type")) w2 := httptest.NewRecorder() @@ -244,7 +244,7 @@ func TestRenderPureJSON(t *testing.T) { } err := (PureJSON{data}).Render(w) require.NoError(t, err) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) + assert.JSONEq(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } diff --git a/routes_test.go b/routes_test.go index 995ff51c..1cae3fce 100644 --- a/routes_test.go +++ b/routes_test.go @@ -484,7 +484,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) { assert.Contains(t, w.Body.String(), "package gin") // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, // else, Content-Type='text/x-go; charset=utf-8' - assert.NotEqual(t, "", w.Header().Get("Content-Type")) + assert.NotEmpty(t, w.Header().Get("Content-Type")) assert.NotEqual(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Last-Modified")) assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires")) assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN")) @@ -764,7 +764,7 @@ func TestRouteContextHoldsFullPath(t *testing.T) { // Test not found router.Use(func(c *Context) { // For not found routes full path is empty - assert.Equal(t, "", c.FullPath()) + assert.Empty(t, c.FullPath()) }) w := PerformRequest(router, http.MethodGet, "/not-found") diff --git a/tree_test.go b/tree_test.go index 74eb6104..b580007d 100644 --- a/tree_test.go +++ b/tree_test.go @@ -481,7 +481,7 @@ func TestTreeDuplicatePath(t *testing.T) { } } - //printChildren(tree, "") + // printChildren(tree, "") checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, @@ -532,7 +532,7 @@ func TestTreeCatchAllConflictRoot(t *testing.T) { func TestTreeCatchMaxParams(t *testing.T) { tree := &node{} - var route = "/cmd/*filepath" + route := "/cmd/*filepath" tree.addRoute(route, fakeHandler(route)) } @@ -692,7 +692,7 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) { } func TestRedirectTrailingSlash(t *testing.T) { - var data = []struct { + data := []struct { path string }{ {"/hello/:name"}, diff --git a/utils_test.go b/utils_test.go index 8098c681..dc9886d7 100644 --- a/utils_test.go +++ b/utils_test.go @@ -94,7 +94,7 @@ func somefunction() { } func TestJoinPaths(t *testing.T) { - assert.Equal(t, "", joinPaths("", "")) + assert.Empty(t, joinPaths("", "")) assert.Equal(t, "/", joinPaths("", "/")) assert.Equal(t, "/a", joinPaths("/a", "")) assert.Equal(t, "/a/", joinPaths("/a/", "")) From 40725d85badd647870df6f9fa7a75ac76341f804 Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Sun, 25 May 2025 05:36:33 -0700 Subject: [PATCH 024/156] chore(bind): return 413 status code when error is `http.MaxBytesError` (#4227) * Bind: return 413 status code when error is `http.MaxBytesError` The Go standard library includes a method `http.MaxBytesReader` that allows limiting the request body. For example, users can create a middleware like: ```go func MiddlewareMaxBodySize(c *gin.Context) { // Limit request body to 100 bytes c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 100) c.Next() } ``` When the body exceeds the limit, reading from the request body returns an error of type `http.MaxBytesError`. This PR makes sure that when the error is of kind `http.MaxBytesError`, Gin returns the correct status code 413 (Request Entity Too Large) instead of a generic 400 (Bad Request). * Disable test when using sonic * Fix * Disable for go-json too * Add references to GitHub issues * Test that the response is 400 for sonic and go-json --- context.go | 15 +++++++- context_test.go | 77 ++++++++++++++++++++++++++++----------- internal/json/go_json.go | 3 ++ internal/json/json.go | 3 ++ internal/json/jsoniter.go | 3 ++ internal/json/sonic.go | 3 ++ 6 files changed, 80 insertions(+), 24 deletions(-) diff --git a/context.go b/context.go index d3e4c6d7..3ebb3ee4 100644 --- a/context.go +++ b/context.go @@ -769,8 +769,19 @@ func (c *Context) BindUri(obj any) error { // It will abort the request with HTTP 400 if any error occurs. // See the binding package. func (c *Context) MustBindWith(obj any, b binding.Binding) error { - if err := c.ShouldBindWith(obj, b); err != nil { - c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck + err := c.ShouldBindWith(obj, b) + if err != nil { + var maxBytesErr *http.MaxBytesError + + // Note: When using sonic or go-json as JSON encoder, they do not propagate the http.MaxBytesError error + // https://github.com/goccy/go-json/issues/485 + // https://github.com/bytedance/sonic/issues/800 + switch { + case errors.As(err, &maxBytesErr): + c.AbortWithError(http.StatusRequestEntityTooLarge, err).SetType(ErrorTypeBind) //nolint: errcheck + default: + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck + } return err } return nil diff --git a/context_test.go b/context_test.go index 084bae5d..b64afba3 100644 --- a/context_test.go +++ b/context_test.go @@ -28,6 +28,7 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" + "github.com/gin-gonic/gin/internal/json" testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -671,7 +672,7 @@ func TestContextDefaultQueryOnEmptyRequest(t *testing.T) { func TestContextQueryAndPostForm(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second") + body := strings.NewReader("foo=bar&page=11&both=&foo=second") c.Request, _ = http.NewRequest(http.MethodPost, "/?both=GET&id=main&id=omit&array[]=first&array[]=second&ids[a]=hi&ids[b]=3.14", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) @@ -946,7 +947,7 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) { c.JSONP(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.JSONEq(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.JSONEq(t, `{"foo":"bar"}`, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -972,7 +973,7 @@ func TestContextRenderAPIJSON(t *testing.T) { c.JSON(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.JSONEq(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.JSONEq(t, `{"foo":"bar"}`, w.Body.String()) assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type")) } @@ -1432,7 +1433,7 @@ func TestContextNegotiationWithJSON(t *testing.T) { }) assert.Equal(t, http.StatusOK, w.Code) - assert.JSONEq(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.JSONEq(t, `{"foo":"bar"}`, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -1848,9 +1849,41 @@ func TestContextContentType(t *testing.T) { assert.Equal(t, "application/json", c.ContentType()) } +func TestContextBindRequestTooLarge(t *testing.T) { + // When using sonic or go-json as JSON encoder, they do not propagate the http.MaxBytesError error + // The response will fail with a generic 400 instead of 413 + // https://github.com/goccy/go-json/issues/485 + // https://github.com/bytedance/sonic/issues/800 + var expectedCode int + switch json.Package { + case "github.com/goccy/go-json", "github.com/bytedance/sonic": + expectedCode = http.StatusBadRequest + default: + expectedCode = http.StatusRequestEntityTooLarge + } + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"foo":"bar", "bar":"foo"}`)) + c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 10) + + var obj struct { + Foo string `json:"foo"` + Bar string `json:"bar"` + } + require.Error(t, c.BindJSON(&obj)) + c.Writer.WriteHeaderNow() + + assert.Empty(t, obj.Bar) + assert.Empty(t, obj.Foo) + assert.Equal(t, expectedCode, w.Code) + assert.True(t, c.IsAborted()) +} + func TestContextAutoBindJSON(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"foo":"bar", "bar":"foo"}`)) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { @@ -1867,7 +1900,7 @@ func TestContextBindWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"foo":"bar", "bar":"foo"}`)) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -1884,7 +1917,7 @@ func TestContextBindWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(` + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(` FOO BAR @@ -1951,7 +1984,7 @@ func TestContextBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) + c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo", strings.NewReader("foo=unused")) var obj struct { Foo string `form:"foo"` @@ -1967,7 +2000,7 @@ func TestContextBindWithYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo: bar\nbar: foo")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader("foo: bar\nbar: foo")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -1984,7 +2017,7 @@ func TestContextBindWithTOML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo = 'bar'\nbar = 'foo'")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader("foo = 'bar'\nbar = 'foo'")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -2001,7 +2034,7 @@ func TestContextBadAutoBind(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", strings.NewReader("\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { Foo string `json:"foo"` @@ -2020,7 +2053,7 @@ func TestContextBadAutoBind(t *testing.T) { func TestContextAutoShouldBindJSON(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"foo":"bar", "bar":"foo"}`)) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { @@ -2037,7 +2070,7 @@ func TestContextShouldBindWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"foo":"bar", "bar":"foo"}`)) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -2054,7 +2087,7 @@ func TestContextShouldBindWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(` + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(` FOO BAR @@ -2121,7 +2154,7 @@ func TestContextShouldBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", bytes.NewBufferString("foo=unused")) + c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", strings.NewReader("foo=unused")) var obj struct { Foo string `form:"foo"` @@ -2141,7 +2174,7 @@ func TestContextShouldBindWithYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo: bar\nbar: foo")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader("foo: bar\nbar: foo")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -2158,7 +2191,7 @@ func TestContextShouldBindWithTOML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo='bar'\nbar= 'foo'")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader("foo='bar'\nbar= 'foo'")) c.Request.Header.Add("Content-Type", MIMETOML) // set fake content-type var obj struct { @@ -2175,7 +2208,7 @@ func TestContextBadAutoShouldBind(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", strings.NewReader(`"foo":"bar", "bar":"foo"}`)) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { Foo string `json:"foo"` @@ -2239,7 +2272,7 @@ func TestContextShouldBindBodyWith(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest( - http.MethodPost, "http://example.com", bytes.NewBufferString(tt.bodyA), + http.MethodPost, "http://example.com", strings.NewReader(tt.bodyA), ) // When it binds to typeA and typeB, it finds the body is // not typeB but typeA. @@ -2257,7 +2290,7 @@ func TestContextShouldBindBodyWith(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest( - http.MethodPost, "http://example.com", bytes.NewBufferString(tt.bodyB), + http.MethodPost, "http://example.com", strings.NewReader(tt.bodyB), ) objA := typeA{} require.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) @@ -2603,7 +2636,7 @@ func TestContextShouldBindBodyWithPlain(t *testing.T) { func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"foo":"bar", "bar":"foo"}`)) require.NoError(t, c.Err()) assert.Nil(t, c.Done()) ti, ok := c.Deadline() @@ -2651,7 +2684,7 @@ func TestGetRequestHeaderValue(t *testing.T) { func TestContextGetRawData(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - body := bytes.NewBufferString("Fetch binary post data") + body := strings.NewReader("Fetch binary post data") c.Request, _ = http.NewRequest(http.MethodPost, "/", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) diff --git a/internal/json/go_json.go b/internal/json/go_json.go index 47c35598..dee09dec 100644 --- a/internal/json/go_json.go +++ b/internal/json/go_json.go @@ -20,3 +20,6 @@ var ( // NewEncoder is exported by gin/json package. NewEncoder = json.NewEncoder ) + +// Package indicates what library is being used for JSON encoding. +const Package = "github.com/goccy/go-json" diff --git a/internal/json/json.go b/internal/json/json.go index 26817786..539daa78 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -20,3 +20,6 @@ var ( // NewEncoder is exported by gin/json package. NewEncoder = json.NewEncoder ) + +// Package indicates what library is being used for JSON encoding. +const Package = "encoding/json" diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go index 45ed16ba..287ebf70 100644 --- a/internal/json/jsoniter.go +++ b/internal/json/jsoniter.go @@ -21,3 +21,6 @@ var ( // NewEncoder is exported by gin/json package. NewEncoder = json.NewEncoder ) + +// Package indicates what library is being used for JSON encoding. +const Package = "github.com/json-iterator/go" diff --git a/internal/json/sonic.go b/internal/json/sonic.go index 8cd88049..b3f72424 100644 --- a/internal/json/sonic.go +++ b/internal/json/sonic.go @@ -21,3 +21,6 @@ var ( // NewEncoder is exported by gin/json package. NewEncoder = json.NewEncoder ) + +// Package indicates what library is being used for JSON encoding. +const Package = "github.com/bytedance/sonic" From c8af82af15dd00d684113334d12e05f589f33ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Sun, 25 May 2025 20:38:39 +0800 Subject: [PATCH 025/156] test(context): add cleanup for uploaded file in SaveUploadedFile test (#4248) Signed-off-by: Flc --- context_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/context_test.go b/context_test.go index b64afba3..c8559505 100644 --- a/context_test.go +++ b/context_test.go @@ -174,6 +174,9 @@ func TestSaveUploadedFileWithPermission(t *testing.T) { assert.Equal(t, "permission_test", f.Filename) var mode fs.FileMode = 0o755 require.NoError(t, c.SaveUploadedFile(f, "permission_test", mode)) + t.Cleanup(func() { + assert.NoError(t, os.Remove("permission_test")) + }) info, err := os.Stat(filepath.Dir("permission_test")) require.NoError(t, err) assert.Equal(t, info.Mode().Perm(), mode) From 848e1cdd0d1525ce0a99b7e6a2a1cf0d84d76156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Mon, 26 May 2025 23:11:05 +0800 Subject: [PATCH 026/156] refactor: replace interface{} with any in type declarations (#4249) - Update golangci.yml to use 'any' instead of 'interface{}' in gofmt - Modify debug.go, plain.go, and render_test.go to use 'any' type - Improve code readability and follow modern Go conventions Signed-off-by: Flc --- .golangci.yml | 5 +++++ binding/plain.go | 2 +- debug.go | 2 +- render/render_test.go | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index d9a60ce2..d8887062 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -72,6 +72,11 @@ formatters: - gofmt - gofumpt - goimports + settings: + gofmt: + rewrite-rules: + - pattern: 'interface{}' + replacement: 'any' exclusions: generated: lax paths: diff --git a/binding/plain.go b/binding/plain.go index 3b250bb0..5d466bdd 100644 --- a/binding/plain.go +++ b/binding/plain.go @@ -15,7 +15,7 @@ func (plainBinding) Name() string { return "plain" } -func (plainBinding) Bind(req *http.Request, obj interface{}) error { +func (plainBinding) Bind(req *http.Request, obj any) error { all, err := io.ReadAll(req.Body) if err != nil { return err diff --git a/debug.go b/debug.go index f2016168..0ab14e4e 100644 --- a/debug.go +++ b/debug.go @@ -25,7 +25,7 @@ func IsDebugging() bool { var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int) // DebugPrintFunc indicates debug log output format. -var DebugPrintFunc func(format string, values ...interface{}) +var DebugPrintFunc func(format string, values ...any) func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) { if IsDebugging() { diff --git a/render/render_test.go b/render/render_test.go index 447ea6a8..4d8ebb19 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -608,7 +608,7 @@ func TestRenderReaderNoContentLength(t *testing.T) { } func TestRenderWriteError(t *testing.T) { - data := []interface{}{"value1", "value2"} + data := []any{"value1", "value2"} prefix := "my-prefix:" r := SecureJSON{Data: data, Prefix: prefix} ew := &errorWriter{ From 41d8591eb16bf23732de9ae2b699d6cae54c2ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Mon, 26 May 2025 23:15:14 +0800 Subject: [PATCH 027/156] refactor(context): refactor `Keys` type to `map[any]any` (#3963) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(context): refactor keys to `map[any]any` Signed-off-by: Flc゛ * refactor(context): refactor keys to `map[any]any` Signed-off-by: Flc゛ * style(context): remove empty lines before GetInt16, GetIntSlice, and GetStringMapString methods - Remove unnecessary empty lines in the context.go file - Improve code readability and consistency Signed-off-by: flc1125 * refactor(context): simplify GetStringSlice function - Replace manual type assertion with generic getTyped function - Reduce code duplication and improve type safety Signed-off-by: flc1125 * test(context): improve context.Set and context.Get tests - Split existing test into separate functions for different scenarios - Add test for setting and getting values with any key type - Add test for handling non-comparable keys - Improve assertions to check for key existence and value correctness Signed-off-by: flc1125 * refactor(context): replace fmt.Errorf with fmt.Sprintf in panic message * test(context): remove trailing hyphen from context_test.go * refactor(context): improve error message for missing key in context - Remove unnecessary quotes around the key in the error message - Simplify the error message format for better readability * test(context): improve panic test message for non-existent key --------- Signed-off-by: Flc゛ Signed-off-by: flc1125 --- context.go | 81 +++++++++++++++++++++++++------------------------ context_test.go | 41 ++++++++++++++++++++++++- logger.go | 2 +- logger_test.go | 2 +- 4 files changed, 83 insertions(+), 43 deletions(-) diff --git a/context.go b/context.go index 3ebb3ee4..bf12830c 100644 --- a/context.go +++ b/context.go @@ -6,6 +6,7 @@ package gin import ( "errors" + "fmt" "io" "io/fs" "log" @@ -72,7 +73,7 @@ type Context struct { mu sync.RWMutex // Keys is a key/value pair exclusively for the context of each request. - Keys map[string]any + Keys map[any]any // Errors is a list of errors attached to all the handlers/middlewares who used this context. Errors errorMsgs @@ -129,7 +130,7 @@ func (c *Context) Copy() *Context { cp.fullPath = c.fullPath cKeys := c.Keys - cp.Keys = make(map[string]any, len(cKeys)) + cp.Keys = make(map[any]any, len(cKeys)) c.mu.RLock() for k, v := range cKeys { cp.Keys[k] = v @@ -264,11 +265,11 @@ 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 string, value any) { +func (c *Context) Set(key any, value any) { c.mu.Lock() defer c.mu.Unlock() if c.Keys == nil { - c.Keys = make(map[string]any) + c.Keys = make(map[any]any) } c.Keys[key] = value @@ -276,7 +277,7 @@ func (c *Context) Set(key string, value any) { // Get returns the value for the given key, ie: (value, true). // If the value does not exist it returns (nil, false) -func (c *Context) Get(key string) (value any, exists bool) { +func (c *Context) Get(key any) (value any, exists bool) { c.mu.RLock() defer c.mu.RUnlock() value, exists = c.Keys[key] @@ -284,14 +285,14 @@ func (c *Context) Get(key string) (value any, exists bool) { } // MustGet returns the value for the given key if it exists, otherwise it panics. -func (c *Context) MustGet(key string) any { +func (c *Context) MustGet(key any) any { if value, exists := c.Get(key); exists { return value } - panic("Key \"" + key + "\" does not exist") + panic(fmt.Sprintf("key %v does not exist", key)) } -func getTyped[T any](c *Context, key string) (res T) { +func getTyped[T any](c *Context, key any) (res T) { if val, ok := c.Get(key); ok && val != nil { res, _ = val.(T) } @@ -299,162 +300,162 @@ func getTyped[T any](c *Context, key string) (res T) { } // GetString returns the value associated with the key as a string. -func (c *Context) GetString(key string) (s string) { +func (c *Context) GetString(key any) (s string) { return getTyped[string](c, key) } // GetBool returns the value associated with the key as a boolean. -func (c *Context) GetBool(key string) (b bool) { +func (c *Context) GetBool(key any) (b bool) { return getTyped[bool](c, key) } // GetInt returns the value associated with the key as an integer. -func (c *Context) GetInt(key string) (i int) { +func (c *Context) GetInt(key any) (i int) { return getTyped[int](c, key) } // GetInt8 returns the value associated with the key as an integer 8. -func (c *Context) GetInt8(key string) (i8 int8) { +func (c *Context) GetInt8(key any) (i8 int8) { return getTyped[int8](c, key) } // GetInt16 returns the value associated with the key as an integer 16. -func (c *Context) GetInt16(key string) (i16 int16) { +func (c *Context) GetInt16(key any) (i16 int16) { return getTyped[int16](c, key) } // GetInt32 returns the value associated with the key as an integer 32. -func (c *Context) GetInt32(key string) (i32 int32) { +func (c *Context) GetInt32(key any) (i32 int32) { return getTyped[int32](c, key) } // GetInt64 returns the value associated with the key as an integer 64. -func (c *Context) GetInt64(key string) (i64 int64) { +func (c *Context) GetInt64(key any) (i64 int64) { return getTyped[int64](c, key) } // GetUint returns the value associated with the key as an unsigned integer. -func (c *Context) GetUint(key string) (ui uint) { +func (c *Context) GetUint(key any) (ui uint) { return getTyped[uint](c, key) } // GetUint8 returns the value associated with the key as an unsigned integer 8. -func (c *Context) GetUint8(key string) (ui8 uint8) { +func (c *Context) GetUint8(key any) (ui8 uint8) { return getTyped[uint8](c, key) } // GetUint16 returns the value associated with the key as an unsigned integer 16. -func (c *Context) GetUint16(key string) (ui16 uint16) { +func (c *Context) GetUint16(key any) (ui16 uint16) { return getTyped[uint16](c, key) } // GetUint32 returns the value associated with the key as an unsigned integer 32. -func (c *Context) GetUint32(key string) (ui32 uint32) { +func (c *Context) GetUint32(key any) (ui32 uint32) { return getTyped[uint32](c, key) } // GetUint64 returns the value associated with the key as an unsigned integer 64. -func (c *Context) GetUint64(key string) (ui64 uint64) { +func (c *Context) GetUint64(key any) (ui64 uint64) { return getTyped[uint64](c, key) } // GetFloat32 returns the value associated with the key as a float32. -func (c *Context) GetFloat32(key string) (f32 float32) { +func (c *Context) GetFloat32(key any) (f32 float32) { return getTyped[float32](c, key) } // GetFloat64 returns the value associated with the key as a float64. -func (c *Context) GetFloat64(key string) (f64 float64) { +func (c *Context) GetFloat64(key any) (f64 float64) { return getTyped[float64](c, key) } // GetTime returns the value associated with the key as time. -func (c *Context) GetTime(key string) (t time.Time) { +func (c *Context) GetTime(key any) (t time.Time) { return getTyped[time.Time](c, key) } // GetDuration returns the value associated with the key as a duration. -func (c *Context) GetDuration(key string) (d time.Duration) { +func (c *Context) GetDuration(key any) (d time.Duration) { return getTyped[time.Duration](c, key) } // GetIntSlice returns the value associated with the key as a slice of integers. -func (c *Context) GetIntSlice(key string) (is []int) { +func (c *Context) GetIntSlice(key any) (is []int) { return getTyped[[]int](c, key) } // GetInt8Slice returns the value associated with the key as a slice of int8 integers. -func (c *Context) GetInt8Slice(key string) (i8s []int8) { +func (c *Context) GetInt8Slice(key any) (i8s []int8) { return getTyped[[]int8](c, key) } // GetInt16Slice returns the value associated with the key as a slice of int16 integers. -func (c *Context) GetInt16Slice(key string) (i16s []int16) { +func (c *Context) GetInt16Slice(key any) (i16s []int16) { return getTyped[[]int16](c, key) } // GetInt32Slice returns the value associated with the key as a slice of int32 integers. -func (c *Context) GetInt32Slice(key string) (i32s []int32) { +func (c *Context) GetInt32Slice(key any) (i32s []int32) { return getTyped[[]int32](c, key) } // GetInt64Slice returns the value associated with the key as a slice of int64 integers. -func (c *Context) GetInt64Slice(key string) (i64s []int64) { +func (c *Context) GetInt64Slice(key any) (i64s []int64) { return getTyped[[]int64](c, key) } // GetUintSlice returns the value associated with the key as a slice of unsigned integers. -func (c *Context) GetUintSlice(key string) (uis []uint) { +func (c *Context) GetUintSlice(key any) (uis []uint) { return getTyped[[]uint](c, key) } // GetUint8Slice returns the value associated with the key as a slice of uint8 integers. -func (c *Context) GetUint8Slice(key string) (ui8s []uint8) { +func (c *Context) GetUint8Slice(key any) (ui8s []uint8) { return getTyped[[]uint8](c, key) } // GetUint16Slice returns the value associated with the key as a slice of uint16 integers. -func (c *Context) GetUint16Slice(key string) (ui16s []uint16) { +func (c *Context) GetUint16Slice(key any) (ui16s []uint16) { return getTyped[[]uint16](c, key) } // GetUint32Slice returns the value associated with the key as a slice of uint32 integers. -func (c *Context) GetUint32Slice(key string) (ui32s []uint32) { +func (c *Context) GetUint32Slice(key any) (ui32s []uint32) { return getTyped[[]uint32](c, key) } // GetUint64Slice returns the value associated with the key as a slice of uint64 integers. -func (c *Context) GetUint64Slice(key string) (ui64s []uint64) { +func (c *Context) GetUint64Slice(key any) (ui64s []uint64) { return getTyped[[]uint64](c, key) } // GetFloat32Slice returns the value associated with the key as a slice of float32 numbers. -func (c *Context) GetFloat32Slice(key string) (f32s []float32) { +func (c *Context) GetFloat32Slice(key any) (f32s []float32) { return getTyped[[]float32](c, key) } // GetFloat64Slice returns the value associated with the key as a slice of float64 numbers. -func (c *Context) GetFloat64Slice(key string) (f64s []float64) { +func (c *Context) GetFloat64Slice(key any) (f64s []float64) { return getTyped[[]float64](c, key) } // GetStringSlice returns the value associated with the key as a slice of strings. -func (c *Context) GetStringSlice(key string) (ss []string) { +func (c *Context) GetStringSlice(key any) (ss []string) { return getTyped[[]string](c, key) } // GetStringMap returns the value associated with the key as a map of interfaces. -func (c *Context) GetStringMap(key string) (sm map[string]any) { +func (c *Context) GetStringMap(key any) (sm map[string]any) { return getTyped[map[string]any](c, key) } // GetStringMapString returns the value associated with the key as a map of strings. -func (c *Context) GetStringMapString(key string) (sms map[string]string) { +func (c *Context) GetStringMapString(key any) (sms map[string]string) { return getTyped[map[string]string](c, key) } // GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings. -func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) { +func (c *Context) GetStringMapStringSlice(key any) (smss map[string][]string) { return getTyped[map[string][]string](c, key) } diff --git a/context_test.go b/context_test.go index c8559505..ff43cd0a 100644 --- a/context_test.go +++ b/context_test.go @@ -257,7 +257,46 @@ func TestContextSetGet(t *testing.T) { assert.False(t, err) assert.Equal(t, "bar", c.MustGet("foo")) - assert.Panics(t, func() { c.MustGet("no_exist") }) + assert.Panicsf(t, func() { + c.MustGet("no_exist") + }, "key no_exist does not exist") +} + +func TestContextSetGetAnyKey(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + + type key struct{} + + tests := []struct { + key any + }{ + {1}, + {int32(1)}, + {int64(1)}, + {uint(1)}, + {float32(1)}, + {key{}}, + {&key{}}, + } + + for _, tt := range tests { + t.Run(reflect.TypeOf(tt.key).String(), func(t *testing.T) { + c.Set(tt.key, 1) + value, ok := c.Get(tt.key) + assert.True(t, ok) + assert.Equal(t, 1, value) + }) + } +} + +func TestContextSetGetPanicsWhenKeyNotComparable(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + + assert.Panics(t, func() { + c.Set([]int{1}, 1) + c.Set(func() {}, 1) + c.Set(make(chan int), 1) + }) } func TestContextSetGetValues(t *testing.T) { diff --git a/logger.go b/logger.go index db2c6832..f4a250ac 100644 --- a/logger.go +++ b/logger.go @@ -82,7 +82,7 @@ type LogFormatterParams struct { // BodySize is the size of the Response Body BodySize int // Keys are the keys set on the request's context. - Keys map[string]any + Keys map[any]any } // StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal. diff --git a/logger_test.go b/logger_test.go index 30b25290..8a542e97 100644 --- a/logger_test.go +++ b/logger_test.go @@ -181,7 +181,7 @@ func TestLoggerWithFormatter(t *testing.T) { func TestLoggerWithConfigFormatting(t *testing.T) { var gotParam LogFormatterParams - var gotKeys map[string]any + var gotKeys map[any]any buffer := new(strings.Builder) router := New() From 61c2b1c28f0c5a754330545e31f02cd6d6f7944e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 20:27:26 +0800 Subject: [PATCH 028/156] chore(deps): bump github.com/quic-go/quic-go from 0.51.0 to 0.52.0 (#4250) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.51.0 to 0.52.0. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Commits](https://github.com/quic-go/quic-go/compare/v0.51.0...v0.52.0) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go dependency-version: 0.52.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0a5c626d..83ec4978 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/quic-go/quic-go v0.51.0 + github.com/quic-go/quic-go v0.52.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.40.0 diff --git a/go.sum b/go.sum index b047363b..f3b9bbad 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.51.0 h1:K8exxe9zXxeRKxaXxi/GpUqYiTrtdiWP8bo1KFya6Wc= -github.com/quic-go/quic-go v0.51.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= +github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA= +github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= From 3c12d2a80e40930632fc4a4a4e1a45140f33fb12 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Sat, 31 May 2025 08:41:13 +0800 Subject: [PATCH 029/156] perf(recover): replace bytes with strings in function for better performance (#4252) Co-authored-by: huangzw --- recovery.go | 21 +++++++++------------ recovery_test.go | 4 ++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/recovery.go b/recovery.go index 8e4633b4..b3543e42 100644 --- a/recovery.go +++ b/recovery.go @@ -19,12 +19,9 @@ import ( "time" ) -var ( - dunno = []byte("???") - centerDot = []byte("·") - dot = []byte(".") - slash = []byte("/") -) +const dunno = "???" + +var dunnoBytes = []byte(dunno) // RecoveryFunc defines the function passable to CustomRecovery. type RecoveryFunc func(c *Context, err any) @@ -138,18 +135,18 @@ func stack(skip int) []byte { func source(lines [][]byte, n int) []byte { n-- // in stack trace, lines are 1-indexed but our array is 0-indexed if n < 0 || n >= len(lines) { - return dunno + return dunnoBytes } return bytes.TrimSpace(lines[n]) } // function returns, if possible, the name of the function containing the PC. -func function(pc uintptr) []byte { +func function(pc uintptr) string { fn := runtime.FuncForPC(pc) if fn == nil { return dunno } - name := []byte(fn.Name()) + name := fn.Name() // The name includes the path name to the package, which is unnecessary // since the file name is already included. Plus, it has center dots. // That is, we see @@ -158,13 +155,13 @@ func function(pc uintptr) []byte { // *T.ptrmethod // Also the package path might contain dot (e.g. code.google.com/...), // so first eliminate the path prefix - if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 { + if lastSlash := strings.LastIndexByte(name, '/'); lastSlash >= 0 { name = name[lastSlash+1:] } - if period := bytes.Index(name, dot); period >= 0 { + if period := strings.IndexByte(name, '.'); period >= 0 { name = name[period+1:] } - name = bytes.ReplaceAll(name, centerDot, dot) + name = strings.ReplaceAll(name, "·", ".") return name } diff --git a/recovery_test.go b/recovery_test.go index 08eec1e4..a6e61a97 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -90,14 +90,14 @@ func TestPanicWithAbort(t *testing.T) { func TestSource(t *testing.T) { bs := source(nil, 0) - assert.Equal(t, dunno, bs) + assert.Equal(t, dunnoBytes, bs) in := [][]byte{ []byte("Hello world."), []byte("Hi, gin.."), } bs = source(in, 10) - assert.Equal(t, dunno, bs) + assert.Equal(t, dunnoBytes, bs) bs = source(in, 1) assert.Equal(t, []byte("Hello world."), bs) From e30123ad7314411664a693f23ed0ade498ccdaf0 Mon Sep 17 00:00:00 2001 From: OHZEKI Naoki <0h23k1.n40k1@gmail.com> Date: Mon, 2 Jun 2025 13:38:19 +0900 Subject: [PATCH 030/156] refactor(recovery): extract Authorization header masking into maskAuthorization func (#4143) * refactor(recovery): extract Authorization header masking into maskAuthorization func * test(recovery): Add a test for maskAuthorization --- recovery.go | 17 +++++++++++------ recovery_test.go | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/recovery.go b/recovery.go index b3543e42..8a077dbb 100644 --- a/recovery.go +++ b/recovery.go @@ -70,12 +70,7 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { stack := stack(3) httpRequest, _ := httputil.DumpRequest(c.Request, false) headers := strings.Split(string(httpRequest), "\r\n") - for idx, header := range headers { - key, _, _ := strings.Cut(header, ":") - if key == "Authorization" { - headers[idx] = key + ": *" - } - } + maskAuthorization(headers) headersToStr := strings.Join(headers, "\r\n") if brokenPipe { logger.Printf("%s\n%s%s", err, headersToStr, reset) @@ -131,6 +126,16 @@ func stack(skip int) []byte { return buf.Bytes() } +// maskAuthorization replaces any "Authorization: " header with "Authorization: *", hiding sensitive credentials. +func maskAuthorization(headers []string) { + for idx, header := range headers { + key, _, _ := strings.Cut(header, ":") + if strings.EqualFold(key, "Authorization") { + headers[idx] = key + ": *" + } + } +} + // source returns a space-trimmed slice of the n'th line. func source(lines [][]byte, n int) []byte { n-- // in stack trace, lines are 1-indexed but our array is 0-indexed diff --git a/recovery_test.go b/recovery_test.go index a6e61a97..3a36fad9 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -88,6 +88,24 @@ func TestPanicWithAbort(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) } +func TestMaskAuthorization(t *testing.T) { + secret := "Bearer aaaabbbbccccddddeeeeffff" + headers := []string{ + "Host: www.example.com", + "Authorization: " + secret, + "User-Agent: curl/7.51.0", + "Accept: */*", + "Content-Type: application/json", + "Content-Length: 1", + } + maskAuthorization(headers) + + for _, h := range headers { + assert.NotContains(t, h, secret) + } + assert.Contains(t, headers, "Authorization: *") +} + func TestSource(t *testing.T) { bs := source(nil, 0) assert.Equal(t, dunnoBytes, bs) From a9c5b36578be3bb817c3578d13de666b0d4c8549 Mon Sep 17 00:00:00 2001 From: eqsdxr Date: Mon, 9 Jun 2025 18:04:23 +0500 Subject: [PATCH 031/156] docs: small changes (#4261) --- gin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gin.go b/gin.go index 27eea83e..1965a429 100644 --- a/gin.go +++ b/gin.go @@ -334,7 +334,7 @@ func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { return engine } -// With returns a Engine with the configuration set in the OptionFunc. +// With returns an Engine with the configuration set in the OptionFunc. func (engine *Engine) With(opts ...OptionFunc) *Engine { for _, opt := range opts { opt(engine) @@ -376,7 +376,7 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { } // Routes returns a slice of registered routes, including some useful information, such as: -// the http method, path and the handler name. +// the http method, path, and the handler name. func (engine *Engine) Routes() (routes RoutesInfo) { for _, tree := range engine.trees { routes = iterate("", tree.method, routes, tree.root) From 77d70e5858278193abfab732164b0c1415d8d4ba Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Mon, 9 Jun 2025 21:05:34 +0800 Subject: [PATCH 032/156] refactor(internal/bytesconv): replace rand usage with crypto/rand and rand.Int63 (#4259) Co-authored-by: huangzw --- internal/bytesconv/bytesconv_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/bytesconv/bytesconv_test.go b/internal/bytesconv/bytesconv_test.go index 497069ce..ff26e35e 100644 --- a/internal/bytesconv/bytesconv_test.go +++ b/internal/bytesconv/bytesconv_test.go @@ -6,6 +6,7 @@ package bytesconv import ( "bytes" + cRand "crypto/rand" "math/rand" "strings" "testing" @@ -30,7 +31,10 @@ func rawStrToBytes(s string) []byte { func TestBytesToString(t *testing.T) { data := make([]byte, 1024) for i := 0; i < 100; i++ { - rand.Read(data) + _, err := cRand.Read(data) + if err != nil { + t.Fatal(err) + } if rawBytesToStr(data) != BytesToString(data) { t.Fatal("don't match") } @@ -44,7 +48,7 @@ const ( letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits ) -var src = rand.NewSource(time.Now().UnixNano()) +var src = rand.New(rand.NewSource(time.Now().UnixNano())) func RandStringBytesMaskImprSrcSB(n int) string { sb := strings.Builder{} From dd33ff793861cee3baa77d575ff319119c366f3a Mon Sep 17 00:00:00 2001 From: Victor Dusart <43795504+vdusart@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:59:53 +0200 Subject: [PATCH 033/156] fix(docs): missing go markdown codeblock (#4266) --- docs/doc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doc.md b/docs/doc.md index 0e882a1b..249f93a0 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -872,7 +872,7 @@ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-1 If the server should bind a default value to a field when the client does not provide one, specify the default value using the `default` key within the `form` tag: -``` +```go package main import ( From 0a864884de806386e275ee096f681520799911fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 23:01:46 +0800 Subject: [PATCH 034/156] chore(deps): bump golang.org/x/net from 0.40.0 to 0.41.0 (#4262) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.40.0 to 0.41.0. - [Commits](https://github.com/golang/net/compare/v0.40.0...v0.41.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.41.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 83ec4978..24d3a895 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/quic-go/quic-go v0.52.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.40.0 + golang.org/x/net v0.41.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 ) @@ -37,10 +37,10 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.38.0 // indirect - golang.org/x/mod v0.18.0 // indirect - golang.org/x/sync v0.14.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/sync v0.15.0 // indirect golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect - golang.org/x/tools v0.22.0 // indirect + golang.org/x/text v0.26.0 // indirect + golang.org/x/tools v0.33.0 // indirect ) diff --git a/go.sum b/go.sum index f3b9bbad..3971e4b2 100644 --- a/go.sum +++ b/go.sum @@ -82,22 +82,22 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 688a429d19d8c804447bb889d3635e2c31a5564d Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 16 Jun 2025 23:16:36 +0800 Subject: [PATCH 035/156] feat: support custom json codec at runtime (#3391) * refactor(json): export json codec * feat(json): support custom json codec at runtime * chore(copyright): update copyright to 2025 gin core team * docs(gin): add custom json codec examples in doc file --- binding/form_mapping.go | 6 +- binding/json.go | 4 +- binding/json_test.go | 186 ++++++++++++++++++++++++++++++++++++++ codec/json/api.go | 57 ++++++++++++ codec/json/go_json.go | 42 +++++++++ codec/json/json.go | 41 +++++++++ codec/json/jsoniter.go | 44 +++++++++ codec/json/sonic.go | 44 +++++++++ context_test.go | 2 +- docs/doc.md | 60 ++++++++++++ errors.go | 6 +- errors_test.go | 8 +- go.mod | 2 +- internal/json/go_json.go | 25 ----- internal/json/json.go | 25 ----- internal/json/jsoniter.go | 26 ------ internal/json/sonic.go | 26 ------ render/json.go | 14 +-- render/render_test.go | 4 +- 19 files changed, 497 insertions(+), 125 deletions(-) create mode 100644 codec/json/api.go create mode 100644 codec/json/go_json.go create mode 100644 codec/json/json.go create mode 100644 codec/json/jsoniter.go create mode 100644 codec/json/sonic.go delete mode 100644 internal/json/go_json.go delete mode 100644 internal/json/json.go delete mode 100644 internal/json/jsoniter.go delete mode 100644 internal/json/sonic.go diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 235692d2..1501209e 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -13,8 +13,8 @@ import ( "strings" "time" + "github.com/gin-gonic/gin/codec/json" "github.com/gin-gonic/gin/internal/bytesconv" - "github.com/gin-gonic/gin/internal/json" ) var ( @@ -333,9 +333,9 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel case multipart.FileHeader: return nil } - return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) + return json.API.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) case reflect.Map: - return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) + return json.API.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) case reflect.Ptr: if !value.Elem().IsValid() { value.Set(reflect.New(value.Type().Elem())) diff --git a/binding/json.go b/binding/json.go index e21c2ee3..f4ae921a 100644 --- a/binding/json.go +++ b/binding/json.go @@ -10,7 +10,7 @@ import ( "io" "net/http" - "github.com/gin-gonic/gin/internal/json" + "github.com/gin-gonic/gin/codec/json" ) // EnableDecoderUseNumber is used to call the UseNumber method on the JSON @@ -42,7 +42,7 @@ func (jsonBinding) BindBody(body []byte, obj any) error { } func decodeJSON(r io.Reader, obj any) error { - decoder := json.NewDecoder(r) + decoder := json.API.NewDecoder(r) if EnableDecoderUseNumber { decoder.UseNumber() } diff --git a/binding/json_test.go b/binding/json_test.go index fbd5c527..942ee3eb 100644 --- a/binding/json_test.go +++ b/binding/json_test.go @@ -5,8 +5,16 @@ package binding import ( + "io" + "net/http/httptest" "testing" + "time" + "unsafe" + "github.com/gin-gonic/gin/codec/json" + "github.com/gin-gonic/gin/render" + jsoniter "github.com/json-iterator/go" + "github.com/modern-go/reflect2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -28,3 +36,181 @@ func TestJSONBindingBindBodyMap(t *testing.T) { assert.Equal(t, "FOO", s["foo"]) assert.Equal(t, "world", s["hello"]) } + +func TestCustomJsonCodec(t *testing.T) { + // Restore json encoding configuration after testing + oldMarshal := json.API + defer func() { + json.API = oldMarshal + }() + // Custom json api + json.API = customJsonApi{} + + // test decode json + obj := customReq{} + err := jsonBinding{}.BindBody([]byte(`{"time_empty":null,"time_struct": "2001-12-05 10:01:02.345","time_nil":null,"time_pointer":"2002-12-05 10:01:02.345"}`), &obj) + require.NoError(t, err) + assert.Equal(t, zeroTime, obj.TimeEmpty) + assert.Equal(t, time.Date(2001, 12, 5, 10, 1, 2, 345000000, time.Local), obj.TimeStruct) + assert.Nil(t, obj.TimeNil) + assert.Equal(t, time.Date(2002, 12, 5, 10, 1, 2, 345000000, time.Local), *obj.TimePointer) + // test encode json + w := httptest.NewRecorder() + err2 := (render.PureJSON{Data: obj}).Render(w) + require.NoError(t, err2) + assert.JSONEq(t, "{\"time_empty\":null,\"time_struct\":\"2001-12-05 10:01:02.345\",\"time_nil\":null,\"time_pointer\":\"2002-12-05 10:01:02.345\"}\n", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) +} + +type customReq struct { + TimeEmpty time.Time `json:"time_empty"` + TimeStruct time.Time `json:"time_struct"` + TimeNil *time.Time `json:"time_nil"` + TimePointer *time.Time `json:"time_pointer"` +} + +var customConfig = jsoniter.Config{ + EscapeHTML: true, + SortMapKeys: true, + ValidateJsonRawMessage: true, +}.Froze() + +func init() { + customConfig.RegisterExtension(&TimeEx{}) + customConfig.RegisterExtension(&TimePointerEx{}) +} + +type customJsonApi struct{} + +func (j customJsonApi) Marshal(v any) ([]byte, error) { + return customConfig.Marshal(v) +} + +func (j customJsonApi) Unmarshal(data []byte, v any) error { + return customConfig.Unmarshal(data, v) +} + +func (j customJsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { + return customConfig.MarshalIndent(v, prefix, indent) +} + +func (j customJsonApi) NewEncoder(writer io.Writer) json.Encoder { + return customConfig.NewEncoder(writer) +} + +func (j customJsonApi) NewDecoder(reader io.Reader) json.Decoder { + return customConfig.NewDecoder(reader) +} + +// region Time Extension + +var ( + zeroTime = time.Time{} + timeType = reflect2.TypeOfPtr((*time.Time)(nil)).Elem() + defaultTimeCodec = &timeCodec{} +) + +type TimeEx struct { + jsoniter.DummyExtension +} + +func (te *TimeEx) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder { + if typ == timeType { + return defaultTimeCodec + } + return nil +} + +func (te *TimeEx) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder { + if typ == timeType { + return defaultTimeCodec + } + return nil +} + +type timeCodec struct{} + +func (tc timeCodec) IsEmpty(ptr unsafe.Pointer) bool { + t := *((*time.Time)(ptr)) + return t.Equal(zeroTime) +} + +func (tc timeCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) { + t := *((*time.Time)(ptr)) + if t.Equal(zeroTime) { + stream.WriteNil() + return + } + stream.WriteString(t.In(time.Local).Format("2006-01-02 15:04:05.000")) +} + +func (tc timeCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + ts := iter.ReadString() + if len(ts) == 0 { + *((*time.Time)(ptr)) = zeroTime + return + } + t, err := time.ParseInLocation("2006-01-02 15:04:05.000", ts, time.Local) + if err != nil { + panic(err) + } + *((*time.Time)(ptr)) = t +} + +// endregion + +// region *Time Extension + +var ( + timePointerType = reflect2.TypeOfPtr((**time.Time)(nil)).Elem() + defaultTimePointerCodec = &timePointerCodec{} +) + +type TimePointerEx struct { + jsoniter.DummyExtension +} + +func (tpe *TimePointerEx) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder { + if typ == timePointerType { + return defaultTimePointerCodec + } + return nil +} + +func (tpe *TimePointerEx) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder { + if typ == timePointerType { + return defaultTimePointerCodec + } + return nil +} + +type timePointerCodec struct{} + +func (tpc timePointerCodec) IsEmpty(ptr unsafe.Pointer) bool { + t := *((**time.Time)(ptr)) + return t == nil || (*t).Equal(zeroTime) +} + +func (tpc timePointerCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) { + t := *((**time.Time)(ptr)) + if t == nil || (*t).Equal(zeroTime) { + stream.WriteNil() + return + } + stream.WriteString(t.In(time.Local).Format("2006-01-02 15:04:05.000")) +} + +func (tpc timePointerCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + ts := iter.ReadString() + if len(ts) == 0 { + *((**time.Time)(ptr)) = nil + return + } + t, err := time.ParseInLocation("2006-01-02 15:04:05.000", ts, time.Local) + if err != nil { + panic(err) + } + *((**time.Time)(ptr)) = &t +} + +// endregion diff --git a/codec/json/api.go b/codec/json/api.go new file mode 100644 index 00000000..f2135683 --- /dev/null +++ b/codec/json/api.go @@ -0,0 +1,57 @@ +// Copyright 2025 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package json + +import "io" + +// API the json codec in use. +var API Core + +// Core the api for json codec. +type Core interface { + Marshal(v any) ([]byte, error) + Unmarshal(data []byte, v any) error + MarshalIndent(v any, prefix, indent string) ([]byte, error) + NewEncoder(writer io.Writer) Encoder + NewDecoder(reader io.Reader) Decoder +} + +// Encoder an interface writes JSON values to an output stream. +type Encoder interface { + // SetEscapeHTML specifies whether problematic HTML characters + // should be escaped inside JSON quoted strings. + // The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e + // to avoid certain safety problems that can arise when embedding JSON in HTML. + // + // In non-HTML settings where the escaping interferes with the readability + // of the output, SetEscapeHTML(false) disables this behavior. + SetEscapeHTML(on bool) + + // Encode writes the JSON encoding of v to the stream, + // followed by a newline character. + // + // See the documentation for Marshal for details about the + // conversion of Go values to JSON. + Encode(v any) error +} + +// Decoder an interface reads and decodes JSON values from an input stream. +type Decoder interface { + // UseNumber causes the Decoder to unmarshal a number into an any as a + // Number instead of as a float64. + UseNumber() + + // DisallowUnknownFields causes the Decoder to return an error when the destination + // is a struct and the input contains object keys which do not match any + // non-ignored, exported fields in the destination. + DisallowUnknownFields() + + // Decode reads the next JSON-encoded value from its + // input and stores it in the value pointed to by v. + // + // See the documentation for Unmarshal for details about + // the conversion of JSON into a Go value. + Decode(v any) error +} diff --git a/codec/json/go_json.go b/codec/json/go_json.go new file mode 100644 index 00000000..42a476ac --- /dev/null +++ b/codec/json/go_json.go @@ -0,0 +1,42 @@ +// Copyright 2025 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build go_json + +package json + +import ( + "io" + + "github.com/goccy/go-json" +) + +// Package indicates what library is being used for JSON encoding. +const Package = "github.com/goccy/go-json" + +func init() { + API = gojsonApi{} +} + +type gojsonApi struct{} + +func (j gojsonApi) Marshal(v any) ([]byte, error) { + return json.Marshal(v) +} + +func (j gojsonApi) Unmarshal(data []byte, v any) error { + return json.Unmarshal(data, v) +} + +func (j gojsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { + return json.MarshalIndent(v, prefix, indent) +} + +func (j gojsonApi) NewEncoder(writer io.Writer) Encoder { + return json.NewEncoder(writer) +} + +func (j gojsonApi) NewDecoder(reader io.Reader) Decoder { + return json.NewDecoder(reader) +} diff --git a/codec/json/json.go b/codec/json/json.go new file mode 100644 index 00000000..2971f42f --- /dev/null +++ b/codec/json/json.go @@ -0,0 +1,41 @@ +// Copyright 2025 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build !jsoniter && !go_json && !(sonic && (linux || windows || darwin)) + +package json + +import ( + "encoding/json" + "io" +) + +// Package indicates what library is being used for JSON encoding. +const Package = "encoding/json" + +func init() { + API = jsonApi{} +} + +type jsonApi struct{} + +func (j jsonApi) Marshal(v any) ([]byte, error) { + return json.Marshal(v) +} + +func (j jsonApi) Unmarshal(data []byte, v any) error { + return json.Unmarshal(data, v) +} + +func (j jsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { + return json.MarshalIndent(v, prefix, indent) +} + +func (j jsonApi) NewEncoder(writer io.Writer) Encoder { + return json.NewEncoder(writer) +} + +func (j jsonApi) NewDecoder(reader io.Reader) Decoder { + return json.NewDecoder(reader) +} diff --git a/codec/json/jsoniter.go b/codec/json/jsoniter.go new file mode 100644 index 00000000..ea624e77 --- /dev/null +++ b/codec/json/jsoniter.go @@ -0,0 +1,44 @@ +// Copyright 2025 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build jsoniter + +package json + +import ( + "io" + + jsoniter "github.com/json-iterator/go" +) + +// Package indicates what library is being used for JSON encoding. +const Package = "github.com/json-iterator/go" + +func init() { + API = jsoniterApi{} +} + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +type jsoniterApi struct{} + +func (j jsoniterApi) Marshal(v any) ([]byte, error) { + return json.Marshal(v) +} + +func (j jsoniterApi) Unmarshal(data []byte, v any) error { + return json.Unmarshal(data, v) +} + +func (j jsoniterApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { + return json.MarshalIndent(v, prefix, indent) +} + +func (j jsoniterApi) NewEncoder(writer io.Writer) Encoder { + return json.NewEncoder(writer) +} + +func (j jsoniterApi) NewDecoder(reader io.Reader) Decoder { + return json.NewDecoder(reader) +} diff --git a/codec/json/sonic.go b/codec/json/sonic.go new file mode 100644 index 00000000..69496565 --- /dev/null +++ b/codec/json/sonic.go @@ -0,0 +1,44 @@ +// Copyright 2025 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build sonic && (linux || windows || darwin) + +package json + +import ( + "io" + + "github.com/bytedance/sonic" +) + +// Package indicates what library is being used for JSON encoding. +const Package = "github.com/bytedance/sonic" + +func init() { + API = sonicApi{} +} + +var json = sonic.ConfigStd + +type sonicApi struct{} + +func (j sonicApi) Marshal(v any) ([]byte, error) { + return json.Marshal(v) +} + +func (j sonicApi) Unmarshal(data []byte, v any) error { + return json.Unmarshal(data, v) +} + +func (j sonicApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { + return json.MarshalIndent(v, prefix, indent) +} + +func (j sonicApi) NewEncoder(writer io.Writer) Encoder { + return json.NewEncoder(writer) +} + +func (j sonicApi) NewDecoder(reader io.Reader) Decoder { + return json.NewDecoder(reader) +} diff --git a/context_test.go b/context_test.go index ff43cd0a..fe3ccd69 100644 --- a/context_test.go +++ b/context_test.go @@ -28,7 +28,7 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" - "github.com/gin-gonic/gin/internal/json" + "github.com/gin-gonic/gin/codec/json" testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/docs/doc.md b/docs/doc.md index 249f93a0..0dd86684 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -63,6 +63,7 @@ - [http2 server push](#http2-server-push) - [Define format for the log of routes](#define-format-for-the-log-of-routes) - [Set and get a cookie](#set-and-get-a-cookie) + - [Custom json codec at runtime](#custom-json-codec-at-runtime) - [Don't trust all proxies](#dont-trust-all-proxies) - [Testing](#testing) @@ -2371,6 +2372,65 @@ func main() { } ``` +### Custom json codec at runtime + +Gin support custom json serialization and deserialization logic without using compile tags. + +1. Define a custom struct implements the `json.Core` interface. + +2. Before your engine starts, assign values to `json.API` using the custom struct. + +```go +package main + +import ( + "io" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/codec/json" + jsoniter "github.com/json-iterator/go" +) + +var customConfig = jsoniter.Config{ + EscapeHTML: true, + SortMapKeys: true, + ValidateJsonRawMessage: true, +}.Froze() + +// implement api.JsonApi +type customJsonApi struct { +} + +func (j customJsonApi) Marshal(v any) ([]byte, error) { + return customConfig.Marshal(v) +} + +func (j customJsonApi) Unmarshal(data []byte, v any) error { + return customConfig.Unmarshal(data, v) +} + +func (j customJsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) { + return customConfig.MarshalIndent(v, prefix, indent) +} + +func (j customJsonApi) NewEncoder(writer io.Writer) json.Encoder { + return customConfig.NewEncoder(writer) +} + +func (j customJsonApi) NewDecoder(reader io.Reader) json.Decoder { + return customConfig.NewDecoder(reader) +} + +func main() { + //Replace the default json api + json.API = customJsonApi{} + + //Start your gin engine + router := gin.Default() + router.Run(":8080") +} +``` + ## Don't trust all proxies Gin lets you specify which headers to hold the real client IP (if any), diff --git a/errors.go b/errors.go index b0d70a94..829e9d2c 100644 --- a/errors.go +++ b/errors.go @@ -9,7 +9,7 @@ import ( "reflect" "strings" - "github.com/gin-gonic/gin/internal/json" + "github.com/gin-gonic/gin/codec/json" ) // ErrorType is an unsigned 64-bit error code as defined in the gin spec. @@ -77,7 +77,7 @@ func (msg *Error) JSON() any { // MarshalJSON implements the json.Marshaller interface. func (msg *Error) MarshalJSON() ([]byte, error) { - return json.Marshal(msg.JSON()) + return json.API.Marshal(msg.JSON()) } // Error implements the error interface. @@ -157,7 +157,7 @@ func (a errorMsgs) JSON() any { // MarshalJSON implements the json.Marshaller interface. func (a errorMsgs) MarshalJSON() ([]byte, error) { - return json.Marshal(a.JSON()) + return json.API.Marshal(a.JSON()) } func (a errorMsgs) String() string { diff --git a/errors_test.go b/errors_test.go index 85ed3dd5..6d8df278 100644 --- a/errors_test.go +++ b/errors_test.go @@ -9,7 +9,7 @@ import ( "fmt" "testing" - "github.com/gin-gonic/gin/internal/json" + "github.com/gin-gonic/gin/codec/json" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -33,7 +33,7 @@ func TestError(t *testing.T) { "meta": "some data", }, err.JSON()) - jsonBytes, _ := json.Marshal(err) + jsonBytes, _ := json.API.Marshal(err) assert.JSONEq(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) err.SetMeta(H{ //nolint: errcheck @@ -92,13 +92,13 @@ Error #03: third H{"error": "second", "meta": "some data"}, H{"error": "third", "status": "400"}, }, errs.JSON()) - jsonBytes, _ := json.Marshal(errs) + jsonBytes, _ := json.API.Marshal(errs) assert.JSONEq(t, "[{\"error\":\"first\"},{\"error\":\"second\",\"meta\":\"some data\"},{\"error\":\"third\",\"status\":\"400\"}]", string(jsonBytes)) errs = errorMsgs{ {Err: errors.New("first"), Type: ErrorTypePrivate}, } assert.Equal(t, H{"error": "first"}, errs.JSON()) - jsonBytes, _ = json.Marshal(errs) + jsonBytes, _ = json.API.Marshal(errs) assert.JSONEq(t, "{\"error\":\"first\"}", string(jsonBytes)) errs = errorMsgs{} diff --git a/go.mod b/go.mod index 24d3a895..1d480f84 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 + github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 github.com/quic-go/quic-go v0.52.0 github.com/stretchr/testify v1.10.0 @@ -30,7 +31,6 @@ require ( github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect diff --git a/internal/json/go_json.go b/internal/json/go_json.go deleted file mode 100644 index dee09dec..00000000 --- a/internal/json/go_json.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2017 Bo-Yi Wu. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build go_json - -package json - -import json "github.com/goccy/go-json" - -var ( - // Marshal is exported by gin/json package. - Marshal = json.Marshal - // Unmarshal is exported by gin/json package. - Unmarshal = json.Unmarshal - // MarshalIndent is exported by gin/json package. - MarshalIndent = json.MarshalIndent - // NewDecoder is exported by gin/json package. - NewDecoder = json.NewDecoder - // NewEncoder is exported by gin/json package. - NewEncoder = json.NewEncoder -) - -// Package indicates what library is being used for JSON encoding. -const Package = "github.com/goccy/go-json" diff --git a/internal/json/json.go b/internal/json/json.go deleted file mode 100644 index 539daa78..00000000 --- a/internal/json/json.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2017 Bo-Yi Wu. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !jsoniter && !go_json && !(sonic && (linux || windows || darwin)) - -package json - -import "encoding/json" - -var ( - // Marshal is exported by gin/json package. - Marshal = json.Marshal - // Unmarshal is exported by gin/json package. - Unmarshal = json.Unmarshal - // MarshalIndent is exported by gin/json package. - MarshalIndent = json.MarshalIndent - // NewDecoder is exported by gin/json package. - NewDecoder = json.NewDecoder - // NewEncoder is exported by gin/json package. - NewEncoder = json.NewEncoder -) - -// Package indicates what library is being used for JSON encoding. -const Package = "encoding/json" diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go deleted file mode 100644 index 287ebf70..00000000 --- a/internal/json/jsoniter.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2017 Bo-Yi Wu. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build jsoniter - -package json - -import jsoniter "github.com/json-iterator/go" - -var ( - json = jsoniter.ConfigCompatibleWithStandardLibrary - // Marshal is exported by gin/json package. - Marshal = json.Marshal - // Unmarshal is exported by gin/json package. - Unmarshal = json.Unmarshal - // MarshalIndent is exported by gin/json package. - MarshalIndent = json.MarshalIndent - // NewDecoder is exported by gin/json package. - NewDecoder = json.NewDecoder - // NewEncoder is exported by gin/json package. - NewEncoder = json.NewEncoder -) - -// Package indicates what library is being used for JSON encoding. -const Package = "github.com/json-iterator/go" diff --git a/internal/json/sonic.go b/internal/json/sonic.go deleted file mode 100644 index b3f72424..00000000 --- a/internal/json/sonic.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2022 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build sonic && (linux || windows || darwin) - -package json - -import "github.com/bytedance/sonic" - -var ( - json = sonic.ConfigStd - // Marshal is exported by gin/json package. - Marshal = json.Marshal - // Unmarshal is exported by gin/json package. - Unmarshal = json.Unmarshal - // MarshalIndent is exported by gin/json package. - MarshalIndent = json.MarshalIndent - // NewDecoder is exported by gin/json package. - NewDecoder = json.NewDecoder - // NewEncoder is exported by gin/json package. - NewEncoder = json.NewEncoder -) - -// Package indicates what library is being used for JSON encoding. -const Package = "github.com/bytedance/sonic" diff --git a/render/json.go b/render/json.go index 23923c44..2f98676c 100644 --- a/render/json.go +++ b/render/json.go @@ -11,8 +11,8 @@ import ( "net/http" "unicode" + "github.com/gin-gonic/gin/codec/json" "github.com/gin-gonic/gin/internal/bytesconv" - "github.com/gin-gonic/gin/internal/json" ) // JSON contains the given interface object. @@ -66,7 +66,7 @@ func (r JSON) WriteContentType(w http.ResponseWriter) { // WriteJSON marshals the given interface object and writes it with custom ContentType. func WriteJSON(w http.ResponseWriter, obj any) error { writeContentType(w, jsonContentType) - jsonBytes, err := json.Marshal(obj) + jsonBytes, err := json.API.Marshal(obj) if err != nil { return err } @@ -77,7 +77,7 @@ func WriteJSON(w http.ResponseWriter, obj any) error { // Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType. func (r IndentedJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) - jsonBytes, err := json.MarshalIndent(r.Data, "", " ") + jsonBytes, err := json.API.MarshalIndent(r.Data, "", " ") if err != nil { return err } @@ -93,7 +93,7 @@ func (r IndentedJSON) WriteContentType(w http.ResponseWriter) { // Render (SecureJSON) marshals the given interface object and writes it with custom ContentType. func (r SecureJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) - jsonBytes, err := json.Marshal(r.Data) + jsonBytes, err := json.API.Marshal(r.Data) if err != nil { return err } @@ -116,7 +116,7 @@ func (r SecureJSON) WriteContentType(w http.ResponseWriter) { // Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType. func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) - ret, err := json.Marshal(r.Data) + ret, err := json.API.Marshal(r.Data) if err != nil { return err } @@ -154,7 +154,7 @@ func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { // Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType. func (r AsciiJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) - ret, err := json.Marshal(r.Data) + ret, err := json.API.Marshal(r.Data) if err != nil { return err } @@ -183,7 +183,7 @@ func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { // Render (PureJSON) writes custom ContentType and encodes the given interface object. func (r PureJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) - encoder := json.NewEncoder(w) + encoder := json.API.NewEncoder(w) encoder.SetEscapeHTML(false) return encoder.Encode(r.Data) } diff --git a/render/render_test.go b/render/render_test.go index 4d8ebb19..0b98e63d 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -15,7 +15,7 @@ import ( "strings" "testing" - "github.com/gin-gonic/gin/internal/json" + "github.com/gin-gonic/gin/codec/json" testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -173,7 +173,7 @@ func TestRenderJsonpJSONError(t *testing.T) { err = jsonpJSON.Render(ew) assert.Equal(t, `write "`+`(`+`" error`, err.Error()) - data, _ := json.Marshal(jsonpJSON.Data) // error was returned while writing data + data, _ := json.API.Marshal(jsonpJSON.Data) // error was returned while writing data ew.bufString = string(data) err = jsonpJSON.Render(ew) assert.Equal(t, `write "`+string(data)+`" error`, err.Error()) From cf4775283ec30cda685355b5016c5abd2a56884e Mon Sep 17 00:00:00 2001 From: "M. Ilham Surya Pratama" Date: Sat, 21 Jun 2025 11:38:28 +0700 Subject: [PATCH 036/156] chroe: migrate yaml package to github.com/goccy/go-yaml (#4272) --- binding/yaml.go | 2 +- go.mod | 3 ++- go.sum | 2 ++ render/render_test.go | 9 ++++++++- render/yaml.go | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/binding/yaml.go b/binding/yaml.go index 2535f8c3..6638e739 100644 --- a/binding/yaml.go +++ b/binding/yaml.go @@ -9,7 +9,7 @@ import ( "io" "net/http" - "gopkg.in/yaml.v3" + "github.com/goccy/go-yaml" ) type yamlBinding struct{} diff --git a/go.mod b/go.mod index 1d480f84..9112f9d1 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.26.0 github.com/goccy/go-json v0.10.2 + github.com/goccy/go-yaml v1.18.0 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 @@ -16,7 +17,6 @@ require ( github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.41.0 google.golang.org/protobuf v1.36.6 - gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -43,4 +43,5 @@ require ( golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.26.0 // indirect golang.org/x/tools v0.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3971e4b2..09a7c5a1 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= diff --git a/render/render_test.go b/render/render_test.go index 0b98e63d..d9ae2067 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -285,7 +285,14 @@ b: err := (YAML{data}).Render(w) require.NoError(t, err) - assert.Equal(t, "|4-\n a : Easy!\n b:\n \tc: 2\n \td: [3, 4]\n \t\n", w.Body.String()) + + // With github.com/goccy/go-yaml, the output format is different from gopkg.in/yaml.v3 + // We're checking that the output contains the expected data, not the exact formatting + output := w.Body.String() + assert.Contains(t, output, "a : Easy!") + assert.Contains(t, output, "b:") + assert.Contains(t, output, "c: 2") + assert.Contains(t, output, "d: [3, 4]") assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type")) } diff --git a/render/yaml.go b/render/yaml.go index 042bb821..98b06442 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -7,7 +7,7 @@ package render import ( "net/http" - "gopkg.in/yaml.v3" + "github.com/goccy/go-yaml" ) // YAML contains the given interface object. From 76dd08d512504b80ef76a76c9e6bd1831e121b71 Mon Sep 17 00:00:00 2001 From: Denny Septian Panggabean <97607754+ddevsr@users.noreply.github.com> Date: Mon, 7 Jul 2025 16:20:47 +0700 Subject: [PATCH 037/156] docs: wrong badge workflow in README.md (#4286) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a582709..990ff142 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -[![Build Status](https://github.com/gin-gonic/gin/workflows/Run%20Tests/badge.svg?branch=master)](https://github.com/gin-gonic/gin/actions?query=branch%3Amaster) +[![Build Status](https://github.com/gin-gonic/gin/actions/workflows/gin.yml/badge.svg?branch=master)](https://github.com/gin-gonic/gin/actions/workflows/gin.yml) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![Go Reference](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc) From 4bdcd9d0f1154ea5e07fbe7855dd09ee14a6f1e9 Mon Sep 17 00:00:00 2001 From: Denny Septian Panggabean <97607754+ddevsr@users.noreply.github.com> Date: Sun, 13 Jul 2025 08:26:26 +0700 Subject: [PATCH 038/156] docs: added available `ID` documentation (#4287) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 990ff142..94e08a78 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in - [Persian](https://gin-gonic.com/fa/docs/) - [Português](https://gin-gonic.com/pt/docs/) - [Russian](https://gin-gonic.com/ru/docs/) +- [Indonesian](https://gin-gonic.com/id/docs/) ### Articles From b7d6308bcc84066df79a047ae363a6120fe87808 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Jul 2025 09:27:07 +0800 Subject: [PATCH 039/156] chore(deps): bump github.com/quic-go/quic-go from 0.52.0 to 0.53.0 (#4281) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.52.0 to 0.53.0. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Commits](https://github.com/quic-go/quic-go/compare/v0.52.0...v0.53.0) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go dependency-version: 0.53.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 5 +---- go.sum | 22 ++-------------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 9112f9d1..1ee9d8be 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/quic-go/quic-go v0.52.0 + github.com/quic-go/quic-go v0.53.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.41.0 @@ -26,12 +26,9 @@ require ( github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect - github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect - github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect diff --git a/go.sum b/go.sum index 09a7c5a1..979c20d3 100644 --- a/go.sum +++ b/go.sum @@ -3,9 +3,6 @@ github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1 github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= @@ -16,8 +13,6 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -26,20 +21,13 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= @@ -53,23 +41,18 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA= -github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= +github.com/quic-go/quic-go v0.53.0 h1:QHX46sISpG2S03dPeZBgVIZp8dGagIaiu2FiVYvpCZI= +github.com/quic-go/quic-go v0.53.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -92,7 +75,6 @@ golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= From dbd8a2515093ad47cadc5c1fface89861a0b765c Mon Sep 17 00:00:00 2001 From: Denny Septian Panggabean <97607754+ddevsr@users.noreply.github.com> Date: Sun, 13 Jul 2025 08:40:35 +0700 Subject: [PATCH 040/156] feat: added `AbortWithStatusPureJSON()` in `Context` (#4290) * feat: added `AbortWithStatusPureJSON()` in context * Update context_test.go --- context.go | 8 ++++++++ context_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/context.go b/context.go index bf12830c..b1846785 100644 --- a/context.go +++ b/context.go @@ -216,6 +216,14 @@ func (c *Context) AbortWithStatus(code int) { c.Abort() } +// AbortWithStatusJSON calls `Abort()` and then `PureJSON` internally. +// This method stops the chain, writes the status code and return a JSON body without escaping. +// It also sets the Content-Type as "application/json". +func (c *Context) AbortWithStatusPureJSON(code int, jsonObj any) { + c.Abort() + c.PureJSON(code, jsonObj) +} + // AbortWithStatusJSON calls `Abort()` and then `JSON` internally. // This method stops the chain, writes the status code and return a JSON body. // It also sets the Content-Type as "application/json". diff --git a/context_test.go b/context_test.go index fe3ccd69..74f0842a 100644 --- a/context_test.go +++ b/context_test.go @@ -1680,6 +1680,32 @@ func TestContextAbortWithStatusJSON(t *testing.T) { assert.JSONEq(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody) } +func TestContextAbortWithStatusPureJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.index = 4 + + in := new(testJSONAbortMsg) + in.Bar = "barValue" + in.Foo = "fooValue" + + c.AbortWithStatusPureJSON(http.StatusUnsupportedMediaType, in) + + assert.Equal(t, abortIndex, c.index) + assert.Equal(t, http.StatusUnsupportedMediaType, c.Writer.Status()) + assert.Equal(t, http.StatusUnsupportedMediaType, w.Code) + assert.True(t, c.IsAborted()) + + contentType := w.Header().Get("Content-Type") + assert.Equal(t, "application/json; charset=utf-8", contentType) + + buf := new(bytes.Buffer) + _, err := buf.ReadFrom(w.Body) + require.NoError(t, err) + jsonStringBody := buf.String() + assert.JSONEq(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody) +} + func TestContextError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) assert.Empty(t, c.Errors) From a6287825c95821a378a34f8a5c9139ea1f6ebd96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Jul 2025 09:41:31 +0800 Subject: [PATCH 041/156] chore(deps): bump github.com/ugorji/go/codec from 1.2.12 to 1.3.0 (#4268) Bumps [github.com/ugorji/go/codec](https://github.com/ugorji/go) from 1.2.12 to 1.3.0. - [Release notes](https://github.com/ugorji/go/releases) - [Commits](https://github.com/ugorji/go/compare/v1.2.12...codec/v1.3.0) --- updated-dependencies: - dependency-name: github.com/ugorji/go/codec dependency-version: 1.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 1ee9d8be..9193bd2e 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 github.com/quic-go/quic-go v0.53.0 github.com/stretchr/testify v1.10.0 - github.com/ugorji/go/codec v1.2.12 + github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.41.0 google.golang.org/protobuf v1.36.6 ) diff --git a/go.sum b/go.sum index 979c20d3..ab2a7750 100644 --- a/go.sum +++ b/go.sum @@ -25,8 +25,8 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -61,8 +61,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= From 545fd74379a0b167a918e38626ae5f7eb83fb243 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Jul 2025 09:41:58 +0800 Subject: [PATCH 042/156] chore(deps): bump github.com/go-playground/validator/v10 (#4289) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.26.0 to 10.27.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.26.0...v10.27.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-version: 10.27.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9193bd2e..386a03bc 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.0 require ( github.com/bytedance/sonic v1.13.2 github.com/gin-contrib/sse v1.1.0 - github.com/go-playground/validator/v10 v10.26.0 + github.com/go-playground/validator/v10 v10.27.0 github.com/goccy/go-json v0.10.2 github.com/goccy/go-yaml v1.18.0 github.com/json-iterator/go v1.1.12 diff --git a/go.sum b/go.sum index ab2a7750..fa948c7b 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= -github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= From bdc1ad7987b6931801af9d50e2df25667fdfaaf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AB=8F=E8=A8=AA=E5=8E=9F=E6=85=B6=E6=96=97?= <131525647+suwakei@users.noreply.github.com> Date: Sun, 13 Jul 2025 10:43:32 +0900 Subject: [PATCH 043/156] docs: added comment in doc.go (#4274) --- doc.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc.go b/doc.go index 1bd03864..9442aa70 100644 --- a/doc.go +++ b/doc.go @@ -2,5 +2,21 @@ Package gin implements a HTTP web framework called gin. See https://gin-gonic.com/ for more information about gin. + +Example: + + package main + + import "github.com/gin-gonic/gin" + + func main() { + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.JSON(200, gin.H{ + "message": "pong", + }) + }) + r.Run() // listen and serve on 0.0.0.0:8080 + } */ package gin // import "github.com/gin-gonic/gin" From 5826722a87cf5855fcc4b794cbef11612352771d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AB=8F=E8=A8=AA=E5=8E=9F=E6=85=B6=E6=96=97?= <131525647+suwakei@users.noreply.github.com> Date: Thu, 17 Jul 2025 20:51:40 +0900 Subject: [PATCH 044/156] fix: version number discrepancy (#4299) --- debug.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug.go b/debug.go index 0ab14e4e..7fe2762e 100644 --- a/debug.go +++ b/debug.go @@ -13,7 +13,7 @@ import ( "sync/atomic" ) -const ginSupportMinGoVer = 21 +const ginSupportMinGoVer = 23 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. From ad4f436ae9823ec577b746d0560201557f8c9691 Mon Sep 17 00:00:00 2001 From: Leon cap Date: Sat, 19 Jul 2025 14:58:12 +0800 Subject: [PATCH 045/156] docs: correct article usage in comments (#4301) - Fix 'an url' to 'a URL' in logger.go comment - Fix 'an form' to 'a form' in binding/form_mapping.go comment - Improve grammar consistency in code documentation --- binding/form_mapping.go | 2 +- logger.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 1501209e..45a39e15 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -175,7 +175,7 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter // BindUnmarshaler is the interface used to wrap the UnmarshalParam method. type BindUnmarshaler interface { - // UnmarshalParam decodes and assigns a value from an form or query param. + // UnmarshalParam decodes and assigns a value from a form or query param. UnmarshalParam(param string) error } diff --git a/logger.go b/logger.go index f4a250ac..47827787 100644 --- a/logger.go +++ b/logger.go @@ -44,7 +44,7 @@ type LoggerConfig struct { // Optional. Default value is gin.DefaultWriter. Output io.Writer - // SkipPaths is an url path array which logs are not written. + // SkipPaths is a URL path array which logs are not written. // Optional. SkipPaths []string From 57ec9e603642dd8a48fbd860e1f4fc5de7be37c0 Mon Sep 17 00:00:00 2001 From: maskpp Date: Sat, 19 Jul 2025 15:07:44 +0800 Subject: [PATCH 046/156] chore(mode): remove impossible case (empty value for mode) (#4303) --- mode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode.go b/mode.go index cc313437..dfef07d6 100644 --- a/mode.go +++ b/mode.go @@ -65,7 +65,7 @@ func SetMode(value string) { } switch value { - case DebugMode, "": + case DebugMode: atomic.StoreInt32(&ginMode, debugCode) case ReleaseMode: atomic.StoreInt32(&ginMode, releaseCode) From ae5be7fcb726ac6417f6b5deb70afd4a274f64f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:08:22 +0800 Subject: [PATCH 047/156] chore(deps): bump golang.org/x/net from 0.41.0 to 0.42.0 (#4297) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.41.0 to 0.42.0. - [Commits](https://github.com/golang/net/compare/v0.41.0...v0.42.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.42.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 386a03bc..8d0e48d1 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/quic-go/quic-go v0.53.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.3.0 - golang.org/x/net v0.41.0 + golang.org/x/net v0.42.0 google.golang.org/protobuf v1.36.6 ) @@ -34,11 +34,11 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.39.0 // indirect + golang.org/x/crypto v0.40.0 // indirect golang.org/x/mod v0.25.0 // indirect - golang.org/x/sync v0.15.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect - golang.org/x/tools v0.33.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect + golang.org/x/tools v0.34.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index fa948c7b..1d220589 100644 --- a/go.sum +++ b/go.sum @@ -67,21 +67,21 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From a4ac275e079d46d493965491d686bfe72d121e85 Mon Sep 17 00:00:00 2001 From: chenhuiluo <41547730+chenhuiluo@users.noreply.github.com> Date: Sat, 19 Jul 2025 15:49:41 +0800 Subject: [PATCH 048/156] test(route): add some test for routergroup (#4291) Co-authored-by: chenhuiluo --- routergroup_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/routergroup_test.go b/routergroup_test.go index 6848063e..182c5589 100644 --- a/routergroup_test.go +++ b/routergroup_test.go @@ -11,6 +11,8 @@ import ( "github.com/stretchr/testify/assert" ) +var MaxHandlers = 32 + func init() { SetMode(TestMode) } @@ -193,3 +195,25 @@ func testRoutesInterface(t *testing.T, r IRoutes) { assert.Equal(t, r, r.Static("/static", ".")) assert.Equal(t, r, r.StaticFS("/static2", Dir(".", false))) } + +func TestRouterGroupCombineHandlersTooManyHandlers(t *testing.T) { + group := &RouterGroup{ + Handlers: make(HandlersChain, MaxHandlers), // Assume group already has MaxHandlers middleware + } + tooManyHandlers := make(HandlersChain, MaxHandlers) // Add MaxHandlers more, total 2 * MaxHandlers + + // This should trigger panic + assert.Panics(t, func() { + group.combineHandlers(tooManyHandlers) + }, "should panic due to too many handlers") +} + +func TestRouterGroupCombineHandlersEmptySliceNotNil(t *testing.T) { + group := &RouterGroup{ + Handlers: HandlersChain{}, + } + + result := group.combineHandlers(HandlersChain{}) + assert.NotNil(t, result, "result should not be nil even with empty handlers") + assert.Empty(t, result, "empty handlers should return empty chain") +} From e4c2a2762448fbd094463a2022e97bc5be98ec63 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Tue, 22 Jul 2025 11:19:08 +0800 Subject: [PATCH 049/156] refactor(context): remove unused Context dependency in get method (#4304) Co-authored-by: 1911860538 --- context.go | 19 +++---- context_test.go | 131 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 9 deletions(-) diff --git a/context.go b/context.go index b1846785..cb3362fb 100644 --- a/context.go +++ b/context.go @@ -573,7 +573,7 @@ func (c *Context) QueryMap(key string) (dicts map[string]string) { // whether at least one value exists for the given key. func (c *Context) GetQueryMap(key string) (map[string]string, bool) { c.initQueryCache() - return c.get(c.queryCache, key) + return getMapFromFormData(c.queryCache, key) } // PostForm returns the specified key from a POST urlencoded form or multipart form @@ -646,22 +646,23 @@ func (c *Context) PostFormMap(key string) (dicts map[string]string) { // whether at least one value exists for the given key. func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { c.initFormCache() - return c.get(c.formCache, key) + return getMapFromFormData(c.formCache, key) } -// get is an internal method and returns a map which satisfies conditions. -func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) { - dicts := make(map[string]string) - exist := false +// getMapFromFormData return a map which satisfies conditions. +// It parses from data with bracket notation like "key[subkey]=value" into a map. +func getMapFromFormData(m map[string][]string, key string) (map[string]string, bool) { + d := make(map[string]string) + found := false for k, v := range m { if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key { if j := strings.IndexByte(k[i+1:], ']'); j >= 1 { - exist = true - dicts[k[i+1:][:j]] = v[0] + found = true + d[k[i+1:][:j]] = v[0] } } } - return dicts, exist + return d, found } // FormFile returns the first file for the provided form key. diff --git a/context_test.go b/context_test.go index 74f0842a..895cd8ad 100644 --- a/context_test.go +++ b/context_test.go @@ -3350,3 +3350,134 @@ func TestContextSetCookieData(t *testing.T) { assert.Contains(t, setCookie, "SameSite=None") }) } + +func TestGetMapFromFormData(t *testing.T) { + testCases := []struct { + name string + data map[string][]string + key string + expected map[string]string + found bool + }{ + { + name: "Basic bracket notation", + data: map[string][]string{ + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + }, + key: "ids", + expected: map[string]string{ + "a": "hi", + "b": "3.14", + }, + found: true, + }, + { + name: "Mixed data with bracket notation", + data: map[string][]string{ + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + "names[a]": {"mike"}, + "names[b]": {"maria"}, + "other[key]": {"value"}, + "simple": {"data"}, + }, + key: "ids", + expected: map[string]string{ + "a": "hi", + "b": "3.14", + }, + found: true, + }, + { + name: "Names key", + data: map[string][]string{ + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + "names[a]": {"mike"}, + "names[b]": {"maria"}, + "other[key]": {"value"}, + }, + key: "names", + expected: map[string]string{ + "a": "mike", + "b": "maria", + }, + found: true, + }, + { + name: "Key not found", + data: map[string][]string{ + "ids[a]": {"hi"}, + "names[b]": {"maria"}, + }, + key: "notfound", + expected: map[string]string{}, + found: false, + }, + { + name: "Empty data", + data: map[string][]string{}, + key: "ids", + expected: map[string]string{}, + found: false, + }, + { + name: "Malformed bracket notation", + data: map[string][]string{ + "ids[a": {"hi"}, // Missing closing bracket + "ids]b": {"3.14"}, // Missing opening bracket + "idsab": {"value"}, // No brackets + }, + key: "ids", + expected: map[string]string{}, + found: false, + }, + { + name: "Nested bracket notation", + data: map[string][]string{ + "ids[a][b]": {"nested"}, + "ids[c]": {"simple"}, + }, + key: "ids", + expected: map[string]string{ + "a": "nested", + "c": "simple", + }, + found: true, + }, + { + name: "Simple key without brackets", + data: map[string][]string{ + "simple": {"data"}, + "ids[a]": {"hi"}, + }, + key: "simple", + expected: map[string]string{}, + found: false, + }, + { + name: "Mixed simple and bracket keys", + data: map[string][]string{ + "simple": {"data"}, + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + "other": {"value"}, + }, + key: "ids", + expected: map[string]string{ + "a": "hi", + "b": "3.14", + }, + found: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, found := getMapFromFormData(tc.data, tc.key) + assert.Equal(t, tc.expected, result, "result mismatch") + assert.Equal(t, tc.found, found, "found mismatch") + }) + } +} From 9708475b3b2a4e1ac09cdf31f34398cab1b3e277 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Tue, 22 Jul 2025 21:36:47 +0800 Subject: [PATCH 050/156] docs(context): fix AbortWithStatusPureJSON comment typo (#4310) Co-authored-by: 1911860538 --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index cb3362fb..842ad2ff 100644 --- a/context.go +++ b/context.go @@ -216,7 +216,7 @@ func (c *Context) AbortWithStatus(code int) { c.Abort() } -// AbortWithStatusJSON calls `Abort()` and then `PureJSON` internally. +// AbortWithStatusPureJSON calls `Abort()` and then `PureJSON` internally. // This method stops the chain, writes the status code and return a JSON body without escaping. // It also sets the Content-Type as "application/json". func (c *Context) AbortWithStatusPureJSON(code int, jsonObj any) { From dab5944a7bca8ae37d947dda02ac591afc1177d3 Mon Sep 17 00:00:00 2001 From: Leon cap Date: Tue, 22 Jul 2025 21:38:32 +0800 Subject: [PATCH 051/156] test(context): add comprehensive unit tests for `Context.File()` method (#4307) * test: add comprehensive unit tests for Context.File() method - Add TestContextFile with multiple test scenarios in context_test.go - Add simplified tests in context_file_test.go - Cover file serving, 404 handling, directory access, HEAD requests, and Range requests - All tests pass successfully * fix: resolve gci formatting issues in test files * fix: correct test file paths and add test file to gin directory * move test_file.txt to testdata directory as suggested by maintainer * fix: update test file paths to use testdata directory --- context_file_test.go | 35 ++++++++++++++++++++ context_test.go | 73 ++++++++++++++++++++++++++++++++++++++++++ testdata/test_file.txt | 2 ++ 3 files changed, 110 insertions(+) create mode 100644 context_file_test.go create mode 100644 testdata/test_file.txt diff --git a/context_file_test.go b/context_file_test.go new file mode 100644 index 00000000..50cc3c8e --- /dev/null +++ b/context_file_test.go @@ -0,0 +1,35 @@ +package gin + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestContextFileSimple tests the Context.File() method with a simple case +func TestContextFileSimple(t *testing.T) { + // Test serving an existing file + testFile := "testdata/test_file.txt" + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/test", nil) + + c.File(testFile) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "This is a test file") + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) +} + +// TestContextFileNotFound tests serving a non-existent file +func TestContextFileNotFound(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/test", nil) + + c.File("non_existent_file.txt") + + assert.Equal(t, http.StatusNotFound, w.Code) +} diff --git a/context_test.go b/context_test.go index 895cd8ad..f51c147f 100644 --- a/context_test.go +++ b/context_test.go @@ -75,6 +75,79 @@ func must(err error) { } } +// TestContextFile tests the Context.File() method +func TestContextFile(t *testing.T) { + // Test serving an existing file + t.Run("serve existing file", func(t *testing.T) { + // Create a temporary test file + testFile := "testdata/test_file.txt" + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/test", nil) + + c.File(testFile) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "This is a test file") + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + }) + + // Test serving a non-existent file + t.Run("serve non-existent file", func(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/test", nil) + + c.File("non_existent_file.txt") + + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + // Test serving a directory (should return 200 with directory listing or 403 Forbidden) + t.Run("serve directory", func(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/test", nil) + + c.File(".") + + // Directory serving can return either 200 (with listing) or 403 (forbidden) + assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusForbidden) + }) + + // Test with HEAD request + t.Run("HEAD request", func(t *testing.T) { + testFile := "testdata/test_file.txt" + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodHead, "/test", nil) + + c.File(testFile) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Empty(t, w.Body.String()) // HEAD request should not return body + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + }) + + // Test with Range request + t.Run("Range request", func(t *testing.T) { + testFile := "testdata/test_file.txt" + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/test", nil) + c.Request.Header.Set("Range", "bytes=0-10") + + c.File(testFile) + + assert.Equal(t, http.StatusPartialContent, w.Code) + assert.Equal(t, "bytes", w.Header().Get("Accept-Ranges")) + assert.Contains(t, w.Header().Get("Content-Range"), "bytes 0-10") + }) +} + func TestContextFormFile(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) diff --git a/testdata/test_file.txt b/testdata/test_file.txt new file mode 100644 index 00000000..05fc0842 --- /dev/null +++ b/testdata/test_file.txt @@ -0,0 +1,2 @@ +This is a test file for Context.File() method testing. +It contains some sample content to verify file serving functionality. \ No newline at end of file From b987b6206f13a4c244739e4f4e6c6a2b7dfff9d3 Mon Sep 17 00:00:00 2001 From: Denny Septian Panggabean <97607754+ddevsr@users.noreply.github.com> Date: Sat, 26 Jul 2025 20:02:59 +0700 Subject: [PATCH 052/156] build: make automatically update package in golang (#4311) --- .github/workflows/goreleaser.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 22edf453..3ca5eb20 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -29,3 +29,8 @@ jobs: args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Trigger Go module reindex (pkg.go.dev) + run: | + echo "Triggering Go module reindex at proxy.golang.org" + curl -sSf "https://proxy.golang.org/github.com/${GITHUB_REPOSITORY,,}/@v/${GITHUB_REF_NAME}.info" From 32065bbd4298d566d060d234e452bbf44e92161d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AB=8F=E8=A8=AA=E5=8E=9F=E6=85=B6=E6=96=97?= <131525647+suwakei@users.noreply.github.com> Date: Sat, 2 Aug 2025 13:16:58 +0900 Subject: [PATCH 053/156] chore(response): prevention of Hijack() runtime panics (#4295) * Prevention of Hijack() runtime panics * added test of Hijack() * fix review * fix lint error * added check assertion of Wrrten() condition before calling Hijack() --- response_writer.go | 6 ++++ response_writer_test.go | 70 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/response_writer.go b/response_writer.go index 753a0b09..ab2f5fec 100644 --- a/response_writer.go +++ b/response_writer.go @@ -6,6 +6,7 @@ package gin import ( "bufio" + "errors" "io" "net" "net/http" @@ -16,6 +17,8 @@ const ( defaultStatus = http.StatusOK ) +var errHijackAlreadyWritten = errors.New("gin: response already written") + // ResponseWriter ... type ResponseWriter interface { http.ResponseWriter @@ -106,6 +109,9 @@ func (w *responseWriter) Written() bool { // Hijack implements the http.Hijacker interface. func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if w.Written() { + return nil, nil, errHijackAlreadyWritten + } if w.size < 0 { w.size = 0 } diff --git a/response_writer_test.go b/response_writer_test.go index 259b8fa8..ef198418 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -5,6 +5,8 @@ package gin import ( + "bufio" + "net" "net/http" "net/http/httptest" "testing" @@ -124,6 +126,74 @@ func TestResponseWriterHijack(t *testing.T) { w.Flush() } +type mockHijacker struct { + *httptest.ResponseRecorder + hijacked bool +} + +// Hijack implements the http.Hijacker interface. It just records that it was called. +func (m *mockHijacker) Hijack() (net.Conn, *bufio.ReadWriter, error) { + m.hijacked = true + return nil, nil, nil +} + +func TestResponseWriterHijackAfterWrite(t *testing.T) { + tests := []struct { + name string + action func(w ResponseWriter) error // Action to perform before hijacking + expectWrittenBeforeHijack bool + expectHijackSuccess bool + expectWrittenAfterHijack bool + expectError error + }{ + { + name: "hijack before write should succeed", + action: func(w ResponseWriter) error { return nil }, + expectWrittenBeforeHijack: false, + expectHijackSuccess: true, + expectWrittenAfterHijack: true, // Hijack itself marks the writer as written + expectError: nil, + }, + { + name: "hijack after write should fail", + action: func(w ResponseWriter) error { + _, err := w.Write([]byte("test")) + return err + }, + expectWrittenBeforeHijack: true, + expectHijackSuccess: false, + expectWrittenAfterHijack: true, + expectError: errHijackAlreadyWritten, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + hijacker := &mockHijacker{ResponseRecorder: httptest.NewRecorder()} + writer := &responseWriter{} + writer.reset(hijacker) + w := ResponseWriter(writer) + + // Check initial state + assert.False(t, w.Written(), "should not be written initially") + + // Perform pre-hijack action + require.NoError(t, tc.action(w), "unexpected error during pre-hijack action") + + // Check state before hijacking + assert.Equal(t, tc.expectWrittenBeforeHijack, w.Written(), "unexpected w.Written() state before hijack") + + // Attempt to hijack + _, _, hijackErr := w.Hijack() + + // Check results + require.ErrorIs(t, hijackErr, tc.expectError, "unexpected error from Hijack()") + assert.Equal(t, tc.expectHijackSuccess, hijacker.hijacked, "unexpected hijacker.hijacked state") + assert.Equal(t, tc.expectWrittenAfterHijack, w.Written(), "unexpected w.Written() state after hijack") + }) + } +} + func TestResponseWriterFlush(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { writer := &responseWriter{} From 42f93283cf4a37bf16ac045284b335a24b44aeca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AB=8F=E8=A8=AA=E5=8E=9F=E6=85=B6=E6=96=97?= <131525647+suwakei@users.noreply.github.com> Date: Sat, 2 Aug 2025 13:23:20 +0900 Subject: [PATCH 054/156] docs(test): improved GoDoc in test_helpers.go (#4270) --- test_helpers.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test_helpers.go b/test_helpers.go index 7508c5c9..a1a7c562 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -6,7 +6,10 @@ package gin import "net/http" -// CreateTestContext returns a fresh engine and context for testing purposes +// CreateTestContext returns a fresh Engine and a Context associated with it. +// This is useful for tests that need to set up a new Gin engine instance +// along with a context, for example, to test middleware that doesn't depend on +// specific routes. The ResponseWriter `w` is used to initialize the context's writer. func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { r = New() c = r.allocateContext(0) @@ -15,7 +18,11 @@ func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { return } -// CreateTestContextOnly returns a fresh context base on the engine for testing purposes +// CreateTestContextOnly returns a fresh Context associated with the provided Engine `r`. +// This is useful for tests that operate on an existing, possibly pre-configured, +// Gin engine instance and need a new context for it. +// The ResponseWriter `w` is used to initialize the context's writer. +// The context is allocated with the `maxParams` setting from the provided engine. func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) { c = r.allocateContext(r.maxParams) c.reset() From 17d0b553eac2a32fe82b06e3b4fa0d0cac3bff57 Mon Sep 17 00:00:00 2001 From: Varus Hsu Date: Sat, 2 Aug 2025 12:27:59 +0800 Subject: [PATCH 055/156] chore(render): do not export tomlContentType anymore (#4319) --- render/toml.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/render/toml.go b/render/toml.go index 40f044c8..379ac72d 100644 --- a/render/toml.go +++ b/render/toml.go @@ -15,7 +15,7 @@ type TOML struct { Data any } -var TOMLContentType = []string{"application/toml; charset=utf-8"} +var tomlContentType = []string{"application/toml; charset=utf-8"} // Render (TOML) marshals the given interface object and writes data with custom ContentType. func (r TOML) Render(w http.ResponseWriter) error { @@ -32,5 +32,5 @@ func (r TOML) Render(w http.ResponseWriter) error { // WriteContentType (TOML) writes TOML ContentType for response. func (r TOML) WriteContentType(w http.ResponseWriter) { - writeContentType(w, TOMLContentType) + writeContentType(w, tomlContentType) } From 45b805f6d59ba0b4f315adffe81ed4a82a51a591 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Sat, 2 Aug 2025 12:30:14 +0800 Subject: [PATCH 056/156] perf(recovery): optimize the log output of CustomRecoveryWithWriter (#4258) Co-authored-by: 1911860538 --- recovery.go | 39 ++++++++++++----------- recovery_test.go | 80 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 83 insertions(+), 36 deletions(-) diff --git a/recovery.go b/recovery.go index 8a077dbb..fdd463f3 100644 --- a/recovery.go +++ b/recovery.go @@ -17,6 +17,8 @@ import ( "runtime" "strings" "time" + + "github.com/gin-gonic/gin/internal/bytesconv" ) const dunno = "???" @@ -67,19 +69,15 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { } } if logger != nil { - stack := stack(3) - httpRequest, _ := httputil.DumpRequest(c.Request, false) - headers := strings.Split(string(httpRequest), "\r\n") - maskAuthorization(headers) - headersToStr := strings.Join(headers, "\r\n") + const stackSkip = 3 if brokenPipe { - logger.Printf("%s\n%s%s", err, headersToStr, reset) + logger.Printf("%s\n%s%s", err, secureRequestDump(c.Request), reset) } else if IsDebugging() { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", - timeFormat(time.Now()), headersToStr, err, stack, reset) + timeFormat(time.Now()), secureRequestDump(c.Request), err, stack(stackSkip), reset) } else { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s", - timeFormat(time.Now()), err, stack, reset) + timeFormat(time.Now()), err, stack(stackSkip), reset) } } if brokenPipe { @@ -95,6 +93,21 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { } } +// secureRequestDump returns a sanitized HTTP request dump where the Authorization header, +// if present, is replaced with a masked value ("Authorization: *") to avoid leaking sensitive credentials. +// +// Currently, only the Authorization header is sanitized. All other headers and request data remain unchanged. +func secureRequestDump(r *http.Request) string { + httpRequest, _ := httputil.DumpRequest(r, false) + lines := strings.Split(bytesconv.BytesToString(httpRequest), "\r\n") + for i, line := range lines { + if strings.HasPrefix(line, "Authorization:") { + lines[i] = "Authorization: *" + } + } + return strings.Join(lines, "\r\n") +} + func defaultHandleRecovery(c *Context, _ any) { c.AbortWithStatus(http.StatusInternalServerError) } @@ -126,16 +139,6 @@ func stack(skip int) []byte { return buf.Bytes() } -// maskAuthorization replaces any "Authorization: " header with "Authorization: *", hiding sensitive credentials. -func maskAuthorization(headers []string) { - for idx, header := range headers { - key, _, _ := strings.Cut(header, ":") - if strings.EqualFold(key, "Authorization") { - headers[idx] = key + ": *" - } - } -} - // source returns a space-trimmed slice of the n'th line. func source(lines [][]byte, n int) []byte { n-- // in stack trace, lines are 1-indexed but our array is 0-indexed diff --git a/recovery_test.go b/recovery_test.go index 3a36fad9..8a9e3475 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -88,24 +88,6 @@ func TestPanicWithAbort(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) } -func TestMaskAuthorization(t *testing.T) { - secret := "Bearer aaaabbbbccccddddeeeeffff" - headers := []string{ - "Host: www.example.com", - "Authorization: " + secret, - "User-Agent: curl/7.51.0", - "Accept: */*", - "Content-Type: application/json", - "Content-Length: 1", - } - maskAuthorization(headers) - - for _, h := range headers { - assert.NotContains(t, h, secret) - } - assert.Contains(t, headers, "Authorization: *") -} - func TestSource(t *testing.T) { bs := source(nil, 0) assert.Equal(t, dunnoBytes, bs) @@ -263,3 +245,65 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) { SetMode(TestMode) } + +func TestSecureRequestDump(t *testing.T) { + tests := []struct { + name string + req *http.Request + wantContains string + wantNotContain string + }{ + { + name: "Authorization header standard case", + req: func() *http.Request { + r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) + r.Header.Set("Authorization", "Bearer secret-token") + return r + }(), + wantContains: "Authorization: *", + wantNotContain: "Bearer secret-token", + }, + { + name: "authorization header lowercase", + req: func() *http.Request { + r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) + r.Header.Set("authorization", "some-secret") + return r + }(), + wantContains: "Authorization: *", + wantNotContain: "some-secret", + }, + { + name: "Authorization header mixed case", + req: func() *http.Request { + r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) + r.Header.Set("AuThOrIzAtIoN", "token123") + return r + }(), + wantContains: "Authorization: *", + wantNotContain: "token123", + }, + { + name: "No Authorization header", + req: func() *http.Request { + r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) + r.Header.Set("Content-Type", "application/json") + return r + }(), + wantContains: "", + wantNotContain: "Authorization: *", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := secureRequestDump(tt.req) + if tt.wantContains != "" && !strings.Contains(result, tt.wantContains) { + t.Errorf("maskHeaders() = %q, want contains %q", result, tt.wantContains) + } + if tt.wantNotContain != "" && strings.Contains(result, tt.wantNotContain) { + t.Errorf("maskHeaders() = %q, want NOT contain %q", result, tt.wantNotContain) + } + }) + } +} From 077a2f39c85700ba0823f85ed29cec0c8f2cbdfc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 22:16:53 +0800 Subject: [PATCH 057/156] chore(deps): bump github.com/quic-go/quic-go from 0.53.0 to 0.54.0 (#4328) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.53.0 to 0.54.0. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Commits](https://github.com/quic-go/quic-go/compare/v0.53.0...v0.54.0) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go dependency-version: 0.54.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8d0e48d1..5e3e7f76 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/quic-go/quic-go v0.53.0 + github.com/quic-go/quic-go v0.54.0 github.com/stretchr/testify v1.10.0 github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.42.0 diff --git a/go.sum b/go.sum index 1d220589..c97f1d95 100644 --- a/go.sum +++ b/go.sum @@ -47,8 +47,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.53.0 h1:QHX46sISpG2S03dPeZBgVIZp8dGagIaiu2FiVYvpCZI= -github.com/quic-go/quic-go v0.53.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= From e7693e67c23005743502962d3bb9839a354d6688 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Sep 2025 07:25:36 +0800 Subject: [PATCH 058/156] chore(deps): bump actions/setup-go from 5 to 6 (#4351) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 4 ++-- .github/workflows/goreleaser.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 6f0f7c11..63d6d968 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -20,7 +20,7 @@ jobs: with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: "^1" - name: Setup golangci-lint @@ -55,7 +55,7 @@ jobs: GOPROXY: https://proxy.golang.org steps: - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: ${{ matrix.go }} cache: false diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 3ca5eb20..c87cf2d6 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -17,7 +17,7 @@ jobs: with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: "^1" - name: Run GoReleaser From 46150257b3eec60e3e0bf1cee7c03439099aef83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Sep 2025 07:26:06 +0800 Subject: [PATCH 059/156] chore(deps): bump github.com/stretchr/testify from 1.10.0 to 1.11.1 (#4347) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.10.0 to 1.11.1. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.10.0...v1.11.1) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-version: 1.11.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5e3e7f76..e141a9c5 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 github.com/quic-go/quic-go v0.54.0 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.42.0 google.golang.org/protobuf v1.36.6 diff --git a/go.sum b/go.sum index c97f1d95..63974fd4 100644 --- a/go.sum +++ b/go.sum @@ -57,8 +57,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= From 28172fa68206b2ced9df3417fad50bcabd6d9eb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Sep 2025 07:26:29 +0800 Subject: [PATCH 060/156] chore(deps): bump google.golang.org/protobuf from 1.36.6 to 1.36.8 (#4346) Bumps google.golang.org/protobuf from 1.36.6 to 1.36.8. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e141a9c5..ec11d2e5 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.42.0 - google.golang.org/protobuf v1.36.6 + google.golang.org/protobuf v1.36.8 ) require ( diff --git a/go.sum b/go.sum index 63974fd4..5cf3ce4a 100644 --- a/go.sum +++ b/go.sum @@ -82,8 +82,8 @@ golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From f9bd00a6b7939b941fde3fdd239367f4a7d6b137 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Sun, 14 Sep 2025 07:29:11 +0800 Subject: [PATCH 061/156] perf(context): optimize getMapFromFormData performance (#4339) Co-authored-by: 1911860538 --- context.go | 19 ++++++++--- context_test.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 5 deletions(-) diff --git a/context.go b/context.go index 842ad2ff..422c2df7 100644 --- a/context.go +++ b/context.go @@ -654,14 +654,23 @@ func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { func getMapFromFormData(m map[string][]string, key string) (map[string]string, bool) { d := make(map[string]string) found := false + keyLen := len(key) + for k, v := range m { - if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key { - if j := strings.IndexByte(k[i+1:], ']'); j >= 1 { - found = true - d[k[i+1:][:j]] = v[0] - } + if len(k) < keyLen+3 { // key + "[" + at least one char + "]" + continue + } + + if k[:keyLen] != key || k[keyLen] != '[' { + continue + } + + if j := strings.IndexByte(k[keyLen+1:], ']'); j > 0 { + found = true + d[k[keyLen+1:keyLen+1+j]] = v[0] } } + return d, found } diff --git a/context_test.go b/context_test.go index f51c147f..9b584890 100644 --- a/context_test.go +++ b/context_test.go @@ -3554,3 +3554,88 @@ func TestGetMapFromFormData(t *testing.T) { }) } } + +func BenchmarkGetMapFromFormData(b *testing.B) { + // Test case 1: Small dataset with bracket notation + smallData := map[string][]string{ + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + "names[a]": {"mike"}, + "names[b]": {"maria"}, + } + + // Test case 2: Medium dataset with mixed data + mediumData := map[string][]string{ + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + "ids[c]": {"test"}, + "ids[d]": {"value"}, + "names[a]": {"mike"}, + "names[b]": {"maria"}, + "names[c]": {"john"}, + "names[d]": {"jane"}, + "other[key1]": {"value1"}, + "other[key2]": {"value2"}, + "simple": {"data"}, + "another": {"info"}, + } + + // Test case 3: Large dataset with many bracket keys + largeData := make(map[string][]string) + for i := 0; i < 100; i++ { + key := fmt.Sprintf("ids[%d]", i) + largeData[key] = []string{fmt.Sprintf("value%d", i)} + } + for i := 0; i < 50; i++ { + key := fmt.Sprintf("names[%d]", i) + largeData[key] = []string{fmt.Sprintf("name%d", i)} + } + for i := 0; i < 25; i++ { + key := fmt.Sprintf("other[key%d]", i) + largeData[key] = []string{fmt.Sprintf("other%d", i)} + } + + // Test case 4: Dataset with many non-matching keys (worst case) + worstCaseData := make(map[string][]string) + for i := 0; i < 100; i++ { + key := fmt.Sprintf("nonmatching%d", i) + worstCaseData[key] = []string{fmt.Sprintf("value%d", i)} + } + worstCaseData["ids[a]"] = []string{"hi"} + worstCaseData["ids[b]"] = []string{"3.14"} + + // Test case 5: Dataset with short keys (best case for early exit) + shortKeysData := map[string][]string{ + "a": {"value1"}, + "b": {"value2"}, + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + } + + benchmarks := []struct { + name string + data map[string][]string + key string + }{ + {"Small_Bracket", smallData, "ids"}, + {"Small_Names", smallData, "names"}, + {"Medium_Bracket", mediumData, "ids"}, + {"Medium_Names", mediumData, "names"}, + {"Medium_Other", mediumData, "other"}, + {"Large_Bracket", largeData, "ids"}, + {"Large_Names", largeData, "names"}, + {"Large_Other", largeData, "other"}, + {"WorstCase_Bracket", worstCaseData, "ids"}, + {"ShortKeys_Bracket", shortKeysData, "ids"}, + {"Empty_Key", smallData, "notfound"}, + } + + for _, bm := range benchmarks { + b.Run(bm.name, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _ = getMapFromFormData(bm.data, bm.key) + } + }) + } +} From 9b1e3533e2d17b6152b05efeab8280f450e68e52 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Fri, 19 Sep 2025 08:35:34 +0800 Subject: [PATCH 062/156] refactor(tree): replace string(/) with "/" in node.insertChild (#4354) Co-authored-by: 1911860538 --- tree.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tree.go b/tree.go index a298d748..78479b6f 100644 --- a/tree.go +++ b/tree.go @@ -383,7 +383,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) } n.addChild(child) - n.indices = string('/') + n.indices = "/" n = child n.priority++ From cca98d2d266d3797a8bf70f5903c2fbe32e8bf86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 08:35:54 +0800 Subject: [PATCH 063/156] chore(deps): bump google.golang.org/protobuf from 1.36.8 to 1.36.9 (#4356) Bumps google.golang.org/protobuf from 1.36.8 to 1.36.9. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ec11d2e5..c395bfe3 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.42.0 - google.golang.org/protobuf v1.36.8 + google.golang.org/protobuf v1.36.9 ) require ( diff --git a/go.sum b/go.sum index 5cf3ce4a..65bd4728 100644 --- a/go.sum +++ b/go.sum @@ -82,8 +82,8 @@ golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From e198f6e859220afd35bc2fb2fd5d404d7c0882ca Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Fri, 19 Sep 2025 08:39:17 +0800 Subject: [PATCH 064/156] refactor(render): remove headers parameter from writeHeader (#4353) Co-authored-by: 1911860538 --- render/reader.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/render/reader.go b/render/reader.go index 5752d8d8..ae1a7b5e 100644 --- a/render/reader.go +++ b/render/reader.go @@ -27,7 +27,7 @@ func (r Reader) Render(w http.ResponseWriter) (err error) { } r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10) } - r.writeHeaders(w, r.Headers) + r.writeHeaders(w) _, err = io.Copy(w, r.Reader) return } @@ -37,10 +37,10 @@ func (r Reader) WriteContentType(w http.ResponseWriter) { writeContentType(w, []string{r.ContentType}) } -// writeHeaders writes custom Header. -func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) { +// writeHeaders writes headers from r.Headers into response. +func (r Reader) writeHeaders(w http.ResponseWriter) { header := w.Header() - for k, v := range headers { + for k, v := range r.Headers { if header.Get(k) == "" { header.Set(k, v) } From da372fc77840b3badf4efef5ec2d203cdc73f1db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Fri, 19 Sep 2025 08:40:33 +0800 Subject: [PATCH 065/156] build(deps): upgrade github.com/bytedance/sonic from v1.13.2 to v1.14.0 (#4342) * build(deps): upgrade github.com/bytedance/sonic from v1.13.2 to v1.14.0 Signed-off-by: Flc * build(deps): upgrade github.com/bytedance/sonic from v1.13.2 to v1.14.0 Signed-off-by: Flc * test: update expected status code for request too large test Signed-off-by: Flc --------- Signed-off-by: Flc --- context_test.go | 5 ++--- go.mod | 12 ++++++------ go.sum | 29 ++++++++++++----------------- 3 files changed, 20 insertions(+), 26 deletions(-) diff --git a/context_test.go b/context_test.go index 9b584890..fbc13879 100644 --- a/context_test.go +++ b/context_test.go @@ -1991,13 +1991,12 @@ func TestContextContentType(t *testing.T) { } func TestContextBindRequestTooLarge(t *testing.T) { - // When using sonic or go-json as JSON encoder, they do not propagate the http.MaxBytesError error + // When using go-json as JSON encoder, they do not propagate the http.MaxBytesError error // The response will fail with a generic 400 instead of 413 // https://github.com/goccy/go-json/issues/485 - // https://github.com/bytedance/sonic/issues/800 var expectedCode int switch json.Package { - case "github.com/goccy/go-json", "github.com/bytedance/sonic": + case "github.com/goccy/go-json": expectedCode = http.StatusBadRequest default: expectedCode = http.StatusRequestEntityTooLarge diff --git a/go.mod b/go.mod index c395bfe3..dd7d99bd 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.23.0 require ( - github.com/bytedance/sonic v1.13.2 + github.com/bytedance/sonic v1.14.0 github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.27.0 github.com/goccy/go-json v0.10.2 @@ -20,24 +20,24 @@ require ( ) require ( - github.com/bytedance/sonic/loader v0.2.4 // indirect - github.com/cloudwego/base64x v0.1.5 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.5.0 // indirect - golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + golang.org/x/arch v0.20.0 // indirect golang.org/x/crypto v0.40.0 // indirect golang.org/x/mod v0.25.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect + golang.org/x/sys v0.35.0 // indirect golang.org/x/text v0.27.0 // indirect golang.org/x/tools v0.34.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 65bd4728..75ecb7c1 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,9 @@ -github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= -github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= -github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= -github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= -github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -30,9 +28,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -53,7 +50,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -65,8 +61,8 @@ github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= @@ -76,8 +72,8 @@ golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= @@ -89,4 +85,3 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= From 2119046230f0119c7c88f86a6b441d9d3aaad03e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Fri, 19 Sep 2025 09:44:22 +0800 Subject: [PATCH 066/156] ci: support Go 1.25 (#4341) - Update GitHub Actions workflow to include Go 1.25 in the test matrix - This change expands the range of Go versions tested for the project Signed-off-by: Flc --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 63d6d968..e049048c 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -33,7 +33,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.23", "1.24"] + go: ["1.23", "1.24", "1.25"] test-tags: [ "", From cb000f570c127a503535fa5be9c0237823ea7e4d Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 20 Sep 2025 19:24:57 +0800 Subject: [PATCH 067/156] ci: integrate Trivy vulnerability scanning into CI workflow (#4359) - Add a GitHub Actions job for vulnerability scanning using Trivy - Configure Trivy to scan the repository for vulnerabilities of severity critical, high, and medium - Ensure the workflow fails if vulnerabilities are found Signed-off-by: appleboy --- .github/workflows/gin.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index e049048c..17b54ab3 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -81,3 +81,19 @@ jobs: uses: codecov/codecov-action@v4 with: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} + + vulnerability-scanning: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Run Trivy vulnerability scanner in repo mode + uses: aquasecurity/trivy-action@0.33.1 + with: + scan-type: 'fs' + ignore-unfixed: true + format: 'table' + exit-code: '1' + severity: 'CRITICAL,HIGH,MEDIUM' From 7858527c8c2a15bddf27ea71162f8f70c82f2cdf Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 20 Sep 2025 19:32:43 +0800 Subject: [PATCH 068/156] docs(changelog): update release notes for Gin v1.10.1 (#4360) - Add release notes for Gin v1.10.1, including new features, enhancements, and build process updates Signed-off-by: appleboy --- CHANGELOG.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5648902d..37dd0e4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Gin ChangeLog +## Gin v1.10.1 + +### Features + +* refactor: strengthen HTTPS security and improve code organization +* feat(binding): Support custom BindUnmarshaler for binding. (#3933) + +### Enhancements + +* chore(deps): bump github.com/bytedance/sonic from 1.11.3 to 1.11.6 (#3940) +* chore(deps): bump golangci/golangci-lint-action from 4 to 5 (#3941) +* chore: update external dependencies to latest versions (#3950) +* chore: update various Go dependencies to latest versions (#3901) +* chore: refactor configuration files for better readability (#3951) +* chore: update changelog categories and improve documentation (#3917) +* feat: update version constant to v1.10.0 (#3952) + +### Build process updates + +* ci(release): refactor changelog regex patterns and exclusions (#3914) +* ci(Makefile): vet command add .PHONY (#3915) + ## Gin v1.10.0 ### Features @@ -26,7 +48,7 @@ * fix(uri): query binding bug (#3236) (@illiafox) * fix: Add pointer support for url query params (#3659) (#3666) (@omkar-foss) * fix: protect Context.Keys map when call Copy method (#3873) (@kingcanfish) - + ### Enhancements * chore(CI): update release args (#3595) (@qloog) From 6ad6205e9c94a4b8a320219e28c37c29d22a7a2c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 20 Sep 2025 19:38:42 +0800 Subject: [PATCH 069/156] docs(changelog): upgrade Gin to v1.11.0 and add release notes (#4361) - Add release notes for Gin v1.11.0, detailing new features, enhancements, bug fixes, CI/build improvements, dependency updates, and documentation changes - Update Gin framework version to v1.11.0 ref: https://github.com/gin-gonic/gin/issues/4325 Signed-off-by: appleboy --- CHANGELOG.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ version.go | 2 +- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37dd0e4a..9451db39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,77 @@ # Gin ChangeLog +## Gin v1.11.0 + +### Features + +* feat(gin): Experimental support for HTTP/3 using quic-go/quic-go ([#3210](https://github.com/gin-gonic/gin/pull/3210)) +* feat(form): add array collection format in form binding ([#3986](https://github.com/gin-gonic/gin/pull/3986)), add custom string slice for form tag unmarshal ([#3970](https://github.com/gin-gonic/gin/pull/3970)) +* feat(binding): add BindPlain ([#3904](https://github.com/gin-gonic/gin/pull/3904)) +* feat(fs): Export, test and document OnlyFilesFS ([#3939](https://github.com/gin-gonic/gin/pull/3939)) +* feat(binding): add support for unixMilli and unixMicro ([#4190](https://github.com/gin-gonic/gin/pull/4190)) +* feat(form): Support default values for collections in form binding ([#4048](https://github.com/gin-gonic/gin/pull/4048)) +* feat(context): GetXxx added support for more go native types ([#3633](https://github.com/gin-gonic/gin/pull/3633)) + +### Enhancements + +* perf(context): optimize getMapFromFormData performance ([#4339](https://github.com/gin-gonic/gin/pull/4339)) +* refactor(tree): replace string(/) with "/" in node.insertChild ([#4354](https://github.com/gin-gonic/gin/pull/4354)) +* refactor(render): remove headers parameter from writeHeader ([#4353](https://github.com/gin-gonic/gin/pull/4353)) +* refactor(context): simplify "GetType()" functions ([#4080](https://github.com/gin-gonic/gin/pull/4080)) +* refactor(slice): simplify SliceValidationError Error method ([#3910](https://github.com/gin-gonic/gin/pull/3910)) +* refactor(context):Avoid using filepath.Dir twice in SaveUploadedFile ([#4181](https://github.com/gin-gonic/gin/pull/4181)) +* refactor(context): refactor context handling and improve test robustness ([#4066](https://github.com/gin-gonic/gin/pull/4066)) +* refactor(binding): use strings.Cut to replace strings.Index ([#3522](https://github.com/gin-gonic/gin/pull/3522)) +* refactor(context): add an optional permission parameter to SaveUploadedFile ([#4068](https://github.com/gin-gonic/gin/pull/4068)) +* refactor(context): verify URL is Non-nil in initQueryCache() ([#3969](https://github.com/gin-gonic/gin/pull/3969)) +* refactor(context): YAML judgment logic in Negotiate ([#3966](https://github.com/gin-gonic/gin/pull/3966)) +* tree: replace the self-defined 'min' to official one ([#3975](https://github.com/gin-gonic/gin/pull/3975)) +* context: Remove redundant filepath.Dir usage ([#4181](https://github.com/gin-gonic/gin/pull/4181)) + +### Bug Fixes + +* fix: prevent middleware re-entry issue in HandleContext ([#3987](https://github.com/gin-gonic/gin/pull/3987)) +* fix(binding): prevent duplicate decoding and add validation in decodeToml ([#4193](https://github.com/gin-gonic/gin/pull/4193)) +* fix(gin): Do not panic when handling method not allowed on empty tree ([#4003](https://github.com/gin-gonic/gin/pull/4003)) +* fix(gin): data race warning for gin mode ([#1580](https://github.com/gin-gonic/gin/pull/1580)) +* fix(context): verify URL is Non-nil in initQueryCache() ([#3969](https://github.com/gin-gonic/gin/pull/3969)) +* fix(context): YAML judgment logic in Negotiate ([#3966](https://github.com/gin-gonic/gin/pull/3966)) +* fix(context): check handler is nil ([#3413](https://github.com/gin-gonic/gin/pull/3413)) +* fix(readme): fix broken link to English documentation ([#4222](https://github.com/gin-gonic/gin/pull/4222)) +* fix(tree): Keep panic infos consistent when wildcard type build faild ([#4077](https://github.com/gin-gonic/gin/pull/4077)) + +### Build process updates / CI + +* ci: integrate Trivy vulnerability scanning into CI workflow ([#4359](https://github.com/gin-gonic/gin/pull/4359)) +* ci: support Go 1.25 in CI/CD ([#4341](https://github.com/gin-gonic/gin/pull/4341)) +* build(deps): upgrade github.com/bytedance/sonic from v1.13.2 to v1.14.0 ([#4342](https://github.com/gin-gonic/gin/pull/4342)) +* ci: add Go version 1.24 to GitHub Actions ([#4154](https://github.com/gin-gonic/gin/pull/4154)) +* build: update Gin minimum Go version to 1.21 ([#3960](https://github.com/gin-gonic/gin/pull/3960)) +* ci(lint): enable new linters (testifylint, usestdlibvars, perfsprint, etc.) ([#4010](https://github.com/gin-gonic/gin/pull/4010), [#4091](https://github.com/gin-gonic/gin/pull/4091), [#4090](https://github.com/gin-gonic/gin/pull/4090)) +* ci(lint): update workflows and improve test request consistency ([#4126](https://github.com/gin-gonic/gin/pull/4126)) + +### Dependency updates + +* chore(deps): bump google.golang.org/protobuf from 1.36.6 to 1.36.9 ([#4346](https://github.com/gin-gonic/gin/pull/4346), [#4356](https://github.com/gin-gonic/gin/pull/4356)) +* chore(deps): bump github.com/stretchr/testify from 1.10.0 to 1.11.1 ([#4347](https://github.com/gin-gonic/gin/pull/4347)) +* chore(deps): bump actions/setup-go from 5 to 6 ([#4351](https://github.com/gin-gonic/gin/pull/4351)) +* chore(deps): bump github.com/quic-go/quic-go from 0.53.0 to 0.54.0 ([#4328](https://github.com/gin-gonic/gin/pull/4328)) +* chore(deps): bump golang.org/x/net from 0.33.0 to 0.38.0 ([#4178](https://github.com/gin-gonic/gin/pull/4178), [#4221](https://github.com/gin-gonic/gin/pull/4221)) +* chore(deps): bump github.com/go-playground/validator/v10 from 10.20.0 to 10.22.1 ([#4052](https://github.com/gin-gonic/gin/pull/4052)) + +### Documentation updates + +* docs(changelog): update release notes for Gin v1.10.1 ([#4360](https://github.com/gin-gonic/gin/pull/4360)) +* docs: Fixing English grammar mistakes and awkward sentence structure in doc/doc.md ([#4207](https://github.com/gin-gonic/gin/pull/4207)) +* docs: update documentation and release notes for Gin v1.10.0 ([#3953](https://github.com/gin-gonic/gin/pull/3953)) +* docs: fix typo in Gin Quick Start ([#3997](https://github.com/gin-gonic/gin/pull/3997)) +* docs: fix comment and link issues ([#4205](https://github.com/gin-gonic/gin/pull/4205), [#3938](https://github.com/gin-gonic/gin/pull/3938)) +* docs: fix route group example code ([#4020](https://github.com/gin-gonic/gin/pull/4020)) +* docs(readme): add Portuguese documentation ([#4078](https://github.com/gin-gonic/gin/pull/4078)) +* docs(context): fix some function names in comment ([#4079](https://github.com/gin-gonic/gin/pull/4079)) + +--- + ## Gin v1.10.1 ### Features diff --git a/version.go b/version.go index 93ad9654..8049058c 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.10.0" +const Version = "v1.11.0" From 4dd00f81b1124d1a72e3d0fe050a224ff0ffcb88 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 20 Sep 2025 20:58:46 +0800 Subject: [PATCH 070/156] docs(readme): revamp and expand documentation for clarity and completeness (#4362) - Update contributing header to single hash style - Remove deprecated badge and improve project summary wording - Reorganize and clarify feature descriptions and benefits - Restructure getting started and installation instructions for clarity - Expand and annotate the first example application walkthrough - Detail the steps for running the sample application and expected output - Improve guidance on learning resources and example projects - Enhance API reference, documentation links, and tutorial references - Add a clear performance benchmarks section comparing Gin to other frameworks - Expand middleware section with ecosystem highlights and usage details - Create a production usage section listing notable projects using Gin - Revamp contribution section with clearer procedure and encouragement for new contributors Signed-off-by: appleboy --- CONTRIBUTING.md | 2 +- README.md | 180 ++++++++++++++++++++++++++++++------------------ 2 files changed, 113 insertions(+), 69 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d1c723c6..623665ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -## Contributing +# Contributing - With issues: - Use the search tool before opening a new issue. diff --git a/README.md b/README.md index 94e08a78..a24b349f 100644 --- a/README.md +++ b/README.md @@ -9,46 +9,48 @@ [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) [![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) -[![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/gin-gonic/gin)](https://www.tickgit.com/browse?repo=github.com/gin-gonic/gin) -Gin is a web framework written in [Go](https://go.dev/). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). -If you need performance and good productivity, you will love Gin. +Gin is a high-performance HTTP web framework written in [Go](https://go.dev/). It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks to [httprouter](https://github.com/julienschmidt/httprouter). Gin is designed for building REST APIs, web applications, and microservices where speed and developer productivity are essential. -**Gin's key features are:** +**Why choose Gin?** -- Zero allocation router -- Speed -- Middleware support -- Crash-free -- JSON validation -- Route grouping -- Error management -- Built-in rendering -- Extensible +Gin combines the simplicity of Express.js-style routing with Go's performance characteristics, making it ideal for: -## Getting started +- Building high-throughput REST APIs +- Developing microservices that need to handle many concurrent requests +- Creating web applications that require fast response times +- Prototyping web services quickly with minimal boilerplate + +**Gin's key features:** + +- **Zero allocation router** - Extremely memory-efficient routing with no heap allocations +- **High performance** - Benchmarks show superior speed compared to other Go web frameworks +- **Middleware support** - Extensible middleware system for authentication, logging, CORS, etc. +- **Crash-free** - Built-in recovery middleware prevents panics from crashing your server +- **JSON validation** - Automatic request/response JSON binding and validation +- **Route grouping** - Organize related routes and apply common middleware +- **Error management** - Centralized error handling and logging +- **Built-in rendering** - Support for JSON, XML, HTML templates, and more +- **Extensible** - Large ecosystem of community middleware and plugins + +## Getting Started ### Prerequisites -Gin requires [Go](https://go.dev/) version [1.23](https://go.dev/doc/devel/release#go1.23.0) or above. +- **Go version**: Gin requires [Go](https://go.dev/) version [1.23](https://go.dev/doc/devel/release#go1.23.0) or above +- **Basic Go knowledge**: Familiarity with Go syntax and package management is helpful -### Getting Gin +### Installation -With [Go's module support](https://go.dev/wiki/Modules#how-to-use-modules), `go [build|run|test]` automatically fetches the necessary dependencies when you add the import in your code: +With [Go's module support](https://go.dev/wiki/Modules#how-to-use-modules), simply import Gin in your code and Go will automatically fetch it during build: -```sh +```go import "github.com/gin-gonic/gin" ``` -Alternatively, use `go get`: +### Your First Gin Application -```sh -go get -u github.com/gin-gonic/gin -``` - -### Running Gin - -A basic example: +Here's a complete example that demonstrates Gin's simplicity: ```go package main @@ -60,59 +62,80 @@ import ( ) func main() { + // Create a Gin router with default middleware (logger and recovery) r := gin.Default() + + // Define a simple GET endpoint r.GET("/ping", func(c *gin.Context) { + // Return JSON response c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) - r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") + + // Start server on port 8080 (default) + // Server will listen on 0.0.0.0:8080 (localhost:8080 on Windows) + r.Run() } ``` -To run the code, use the `go run` command, like: +**Running the application:** -```sh -go run example.go -``` +1. Save the code above as `main.go` +2. Run the application: -Then visit [`0.0.0.0:8080/ping`](http://0.0.0.0:8080/ping) in your browser to see the response! + ```sh + go run main.go + ``` -### See more examples +3. Open your browser and visit [`http://localhost:8080/ping`](http://localhost:8080/ping) +4. You should see: `{"message":"pong"}` -#### Quick Start +**What this example demonstrates:** -Learn and practice with the [Gin Quick Start](docs/doc.md), which includes API examples and builds tag. +- Creating a Gin router with default middleware +- Defining HTTP endpoints with simple handler functions +- Returning JSON responses +- Starting an HTTP server -#### Examples +### Next Steps -A number of ready-to-run examples demonstrating various use cases of Gin are available in the [Gin examples](https://github.com/gin-gonic/examples) repository. +After running your first Gin application, explore these resources to learn more: -## Documentation +#### 📚 Learning Resources -See the [API documentation on go.dev](https://pkg.go.dev/github.com/gin-gonic/gin). +- **[Gin Quick Start Guide](docs/doc.md)** - Comprehensive tutorial with API examples and build configurations +- **[Example Repository](https://github.com/gin-gonic/examples)** - Ready-to-run examples demonstrating various Gin use cases: + - REST API development + - Authentication & middleware + - File uploads and downloads + - WebSocket connections + - Template rendering -The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in several languages: +## 📖 Documentation -- [English](https://gin-gonic.com/en/docs/) -- [简体中文](https://gin-gonic.com/zh-cn/docs/) -- [繁體中文](https://gin-gonic.com/zh-tw/docs/) -- [日本語](https://gin-gonic.com/ja/docs/) -- [Español](https://gin-gonic.com/es/docs/) -- [한국어](https://gin-gonic.com/ko-kr/docs/) -- [Turkish](https://gin-gonic.com/tr/docs/) -- [Persian](https://gin-gonic.com/fa/docs/) -- [Português](https://gin-gonic.com/pt/docs/) -- [Russian](https://gin-gonic.com/ru/docs/) -- [Indonesian](https://gin-gonic.com/id/docs/) +### API Reference -### Articles +- **[Go.dev API Documentation](https://pkg.go.dev/github.com/gin-gonic/gin)** - Complete API reference with examples -- [Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin) +### User Guides -## Benchmarks +The comprehensive documentation is available on [gin-gonic.com](https://gin-gonic.com) in multiple languages: -Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter), [see all benchmarks](/BENCHMARKS.md). +- [English](https://gin-gonic.com/en/docs/) | [简体中文](https://gin-gonic.com/zh-cn/docs/) | [繁體中文](https://gin-gonic.com/zh-tw/docs/) +- [日本語](https://gin-gonic.com/ja/docs/) | [한국어](https://gin-gonic.com/ko-kr/docs/) | [Español](https://gin-gonic.com/es/docs/) +- [Turkish](https://gin-gonic.com/tr/docs/) | [Persian](https://gin-gonic.com/fa/docs/) | [Português](https://gin-gonic.com/pt/docs/) +- [Russian](https://gin-gonic.com/ru/docs/) | [Indonesian](https://gin-gonic.com/id/docs/) + +### Official Tutorials + +- [Go.dev Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin) + +## ⚡ Performance Benchmarks + +Gin demonstrates exceptional performance compared to other Go web frameworks. It uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) for maximum efficiency. [View detailed benchmarks →](/BENCHMARKS.md) + +**Gin vs. Other Go Frameworks** (GitHub API routing benchmark): | Benchmark name | (1) | (2) | (3) | (4) | | ------------------------------ | --------: | --------------: | -----------: | --------------: | @@ -152,23 +175,44 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr - (3): Heap Memory (B/op), lower is better - (4): Average Allocations per Repetition (allocs/op), lower is better -## Middleware +## 🔌 Middleware Ecosystem -You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib) and [gin-gonic/contrib](https://github.com/gin-gonic/contrib). +Gin has a rich ecosystem of middleware for common web development needs. Explore community-contributed middleware: -## Uses +- **[gin-contrib](https://github.com/gin-contrib)** - Official middleware collection including: + - Authentication (JWT, Basic Auth, Sessions) + - CORS, Rate limiting, Compression + - Logging, Metrics, Tracing + - Static file serving, Template engines + +- **[gin-gonic/contrib](https://github.com/gin-gonic/contrib)** - Additional community middleware -Here are some awesome projects that are using the [Gin](https://github.com/gin-gonic/gin) web framework. +## 🏢 Production Usage -- [gorush](https://github.com/appleboy/gorush): A push notification server. -- [fnproject](https://github.com/fnproject/fn): A container native, cloud agnostic serverless platform. -- [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Google TensorFlow. -- [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middleware. -- [picfit](https://github.com/thoas/picfit): An image resizing server. -- [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. +Gin powers many high-traffic applications and services in production: -## Contributing +- **[gorush](https://github.com/appleboy/gorush)** - High-performance push notification server +- **[fnproject](https://github.com/fnproject/fn)** - Container-native, serverless platform +- **[photoprism](https://github.com/photoprism/photoprism)** - AI-powered personal photo management +- **[lura](https://github.com/luraproject/lura)** - Ultra-performant API Gateway framework +- **[picfit](https://github.com/thoas/picfit)** - Real-time image processing server +- **[dkron](https://github.com/distribworks/dkron)** - Distributed job scheduling system -Gin is the work of hundreds of contributors. We appreciate your help! +## 🤝 Contributing -Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. +Gin is the work of hundreds of contributors from around the world. We welcome and appreciate your contributions! + +### How to Contribute + +- 🐛 **Report bugs** - Help us identify and fix issues +- 💡 **Suggest features** - Share your ideas for improvements +- 📝 **Improve documentation** - Help make our docs clearer +- 🔧 **Submit code** - Fix bugs or implement new features +- 🧪 **Write tests** - Improve our test coverage + +### Getting Started with Contributing + +1. Check out our [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines +2. Join our community discussions and ask questions + +**All contributions are valued and help make Gin better for everyone!** From 1bbbec0baf6370bfb74e07a9060292939534290d Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 21 Sep 2025 10:50:09 +0800 Subject: [PATCH 071/156] docs: announce Gin 1.11.0 release with blog link (#4363) - Add a new section announcing Gin 1.11.0 and link to its release blog post Signed-off-by: appleboy --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index a24b349f..8343a55b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,12 @@ [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) [![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) +## 📰 [Announcing Gin 1.11.0!](https://gin-gonic.com/en/blog/news/gin-1-11-0-release-announcement/) + +Read about the latest features and improvements in Gin 1.11.0 on our official blog. + +--- + Gin is a high-performance HTTP web framework written in [Go](https://go.dev/). It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks to [httprouter](https://github.com/julienschmidt/httprouter). Gin is designed for building REST APIs, web applications, and microservices where speed and developer productivity are essential. **Why choose Gin?** From 792541470403dac0487b6213a22c7c2491084d83 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 21 Sep 2025 12:48:19 +0800 Subject: [PATCH 072/156] docs: revamp GitHub contribution and support templates (#4364) - Replace the old issue template with new, structured YAML templates for bug reports and feature requests - Add a configuration file that directs users to relevant documentation and support links - Update the pull request template to use a checklist format and clarify documentation requirements Signed-off-by: appleboy --- .github/ISSUE_TEMPLATE.md | 49 ----------------- .github/ISSUE_TEMPLATE/bug-report.yaml | 60 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 11 ++++ .github/ISSUE_TEMPLATE/feature-request.yaml | 18 +++++++ .github/PULL_REQUEST_TEMPLATE.md | 15 +++--- 5 files changed, 98 insertions(+), 55 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yaml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yaml diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 864787ca..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,49 +0,0 @@ -- With issues: - - Use the search tool before opening a new issue. - - Please provide source code and commit sha if you found a bug. - - Review existing issues and provide feedback or react to them. - -## Description - - - -## How to reproduce - - -``` -package main - -import ( - "github.com/gin-gonic/gin" -) - -func main() { - g := gin.Default() - g.GET("/hello/:name", func(c *gin.Context) { - c.String(200, "Hello %s", c.Param("name")) - }) - g.Run(":9000") -} -``` - -## Expectations - - -``` -$ curl http://localhost:9000/hello/world -Hello world -``` - -## Actual result - - -``` -$ curl -i http://localhost:9000/hello/world - -``` - -## Environment - -- go version: -- gin version (or commit ref): -- operating system: diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml new file mode 100644 index 00000000..2cf2f362 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -0,0 +1,60 @@ +name: Bug Report +description: Found something you weren't expecting? Report it here! +labels: ["type/bug"] +body: + - type: markdown + attributes: + value: | + NOTE: If your issue is a security concern, please send an email to appleboy.tw@gmail.com instead of opening a public issue. + - type: markdown + attributes: + value: | + 1. Please speak English, this is the language all maintainers can speak and write. + 2. Please ask questions problems on our Discussions Forum (https://github.com/gin-gonic/gin/discussions). + 3. Make sure you are using the latest release and + take a moment to check that your issue hasn't been reported before. + - type: textarea + id: description + attributes: + label: Description + description: | + Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below) + - type: input + id: gin-ver + attributes: + label: Gin Version + description: Gin version (or commit reference) of your instance + validations: + required: true + - type: dropdown + id: can-reproduce + attributes: + label: Can you reproduce the bug? + description: | + If so, please write the steps to reproduce the bug. + options: + - "Yes" + - "No" + validations: + required: true + - type: markdown + attributes: + value: | + It's really important to provide pertinent logs + Please read https://docs.gitea.com/administration/logging-config#collecting-logs-for-help + In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini + - type: textarea + id: source-code + attributes: + label: Source Code + description: If this issue involves source code, please provide a minimal reproducible example + - type: input + id: go-ver + attributes: + label: Go Version + description: The version of Go running on the server + - type: input + id: os-ver + attributes: + label: Operating System + description: The operating system you are using to run Gin diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..ceff9fe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Go.dev API Documentation + url: https://pkg.go.dev/github.com/gin-gonic/gin + about: Comprehensive API documentation for Gin. + - name: Gin User Guides + url: https://gin-gonic.com/ + about: In-depth user guides and tutorials for using Gin. + - name: Discussions Forum + url: https://github.com/gin-gonic/gin/discussions + about: Questions and configuration or deployment problems can also be discussed. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml new file mode 100644 index 00000000..a40215aa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yaml @@ -0,0 +1,18 @@ +name: Feature Request +description: Got an idea for a feature that Gin doesn't have currently? Submit your idea here! +labels: ["type/proposal"] +body: + - type: markdown + attributes: + value: | + 1. Please speak English, this is the language all maintainers can speak and write. + 2. Please ask questions problems on our Discussions Forum (https://github.com/gin-gonic/gin/discussions). + 3. Please take a moment to check that your feature hasn't already been suggested. + - type: textarea + id: description + attributes: + label: Feature Description + placeholder: | + I think it would be great if Gin had... + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 96e70bba..846c04fb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,10 @@ -- With pull requests: - - Open your pull request against `master` - - Your pull request should have no more than two commits, if not you should squash them. - - It should pass all tests in the available continuous integration systems such as GitHub Actions. - - You should add/modify tests to cover your proposed code changes. - - If your pull request contains a new feature, please document it on the README. +# Pull Request Checklist +Please ensure your pull request meets the following requirements: + +- [ ] Open your pull request against the `master` branch. +- [ ] All tests pass in available continuous integration systems (e.g., GitHub Actions). +- [ ] Tests are added or modified as needed to cover code changes. +- [ ] If the pull request introduces a new feature, the feature is documented in the `docs/doc.md`. + +Thank you for contributing! From 6a1d1218c3dbfc11427abc1ba39b86e81dff1e54 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 21 Sep 2025 17:39:33 +0800 Subject: [PATCH 073/156] docs: revamp contributing guidelines with comprehensive instructions (#4365) - Rewrite and expand the contributing guidelines for clarity and thoroughness - Add distinct sections for Issues and Pull Requests with step-by-step instructions - Include links to documentation, user guides, and the discussions forum - Provide advice for reporting bugs and making feature requests - Specify requirements for pull requests, including branch, commit count, and test coverage - Clarify documentation expectations for new features and reference the pull request checklist - Add guidance for security-related bug reports and communication language Signed-off-by: appleboy --- CONTRIBUTING.md | 48 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 623665ec..6f556b9b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,13 +1,41 @@ # Contributing -- With issues: - - Use the search tool before opening a new issue. - - Please provide source code and commit sha if you found a bug. - - Review existing issues and provide feedback or react to them. +We welcome both issue reports and pull requests! Please follow these guidelines to help maintainers respond effectively. -- With pull requests: - - Open your pull request against `master` - - Your pull request should have no more than two commits, if not you should squash them. - - It should pass all tests in the available continuous integration systems such as GitHub Actions. - - You should add/modify tests to cover your proposed code changes. - - If your pull request contains a new feature, please document it on the README. +## Issues + +- **Before opening a new issue:** + - Use the search tool to check for existing issues or feature requests. + - Review existing issues and provide feedback or react to them. + - Use English for all communications — it is the language all maintainers read and write. + - For questions, configuration or deployment problems, please use the [Discussions Forum](https://github.com/gin-gonic/gin/discussions). + - For bug reports involving sensitive security issues, email instead of posting publicly. + +- **Reporting a bug:** + - Please provide a clear description of your issue, and a minimal reproducible code example if possible. + - Include the Gin version (or commit reference), Go version, and operating system. + - Indicate whether you can reproduce the bug and describe steps to do so. + - Attach relevant logs per [Logging Documentation](https://docs.gitea.com/administration/logging-config#collecting-logs-for-help). + +- **Feature requests:** + - Before opening a request, check that a similar idea hasn’t already been suggested. + - Clearly describe your proposed feature and its benefits. + +_For API Documentation, User Guides, and more, see:_ + +- [Go.dev API Documentation](https://pkg.go.dev/github.com/gin-gonic/gin) +- [Gin User Guides](https://gin-gonic.com/) +- [Discussions Forum](https://github.com/gin-gonic/gin/discussions) + +## Pull Requests + +Please ensure your pull request meets the following requirements: + +- Open your pull request against the `master` branch. +- Your pull request should have no more than two commits — squash them if necessary. +- All tests pass in available continuous integration systems (e.g., GitHub Actions). +- Add or modify tests to cover your code changes. +- If your pull request introduces a new feature, document it in [`docs/doc.md`](docs/doc.md:1), not in the README. +- Follow the checklist in the [Pull Request Template](.github/PULL_REQUEST_TEMPLATE.md:1). + +Thank you for contributing! From 59e9d4a794f12c4f9a6c7bed441b9644e5f6d99b Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Sun, 21 Sep 2025 17:41:54 +0800 Subject: [PATCH 074/156] refactor(ginS): use sync.OnceValue to simplify engine function (#4314) Co-authored-by: 1911860538 --- ginS/gins.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/ginS/gins.go b/ginS/gins.go index 40088172..7918ce3a 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -12,17 +12,9 @@ import ( "github.com/gin-gonic/gin" ) -var ( - once sync.Once - internalEngine *gin.Engine -) - -func engine() *gin.Engine { - once.Do(func() { - internalEngine = gin.Default() - }) - return internalEngine -} +var engine = sync.OnceValue(func() *gin.Engine { + return gin.Default() +}) // LoadHTMLGlob is a wrapper for Engine.LoadHTMLGlob. func LoadHTMLGlob(pattern string) { From 414de60574449457f3192a7a1d5528940db2836d Mon Sep 17 00:00:00 2001 From: cui Date: Sun, 21 Sep 2025 17:46:17 +0800 Subject: [PATCH 075/156] refactor(context): using maps.Clone (#4333) ref: https://go-review.googlesource.com/c/go/+/471400 --- context.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/context.go b/context.go index 422c2df7..d7280c5d 100644 --- a/context.go +++ b/context.go @@ -10,6 +10,7 @@ import ( "io" "io/fs" "log" + "maps" "math" "mime/multipart" "net" @@ -130,11 +131,8 @@ func (c *Context) Copy() *Context { cp.fullPath = c.fullPath cKeys := c.Keys - cp.Keys = make(map[any]any, len(cKeys)) c.mu.RLock() - for k, v := range cKeys { - cp.Keys[k] = v - } + cp.Keys = maps.Clone(cKeys) c.mu.RUnlock() cParams := c.Params From f3a5e787199f9ee1821fda15e93aec76737631ed Mon Sep 17 00:00:00 2001 From: appleboy Date: Sun, 21 Sep 2025 17:48:39 +0800 Subject: [PATCH 076/156] docs: update feature documentation instructions for broken doc link - Fix a broken link to docs/doc.md in the feature documentation instructions Signed-off-by: appleboy --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f556b9b..9703d6b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,7 @@ Please ensure your pull request meets the following requirements: - Your pull request should have no more than two commits — squash them if necessary. - All tests pass in available continuous integration systems (e.g., GitHub Actions). - Add or modify tests to cover your code changes. -- If your pull request introduces a new feature, document it in [`docs/doc.md`](docs/doc.md:1), not in the README. +- If your pull request introduces a new feature, document it in [`docs/doc.md`](docs/doc.md), not in the README. - Follow the checklist in the [Pull Request Template](.github/PULL_REQUEST_TEMPLATE.md:1). Thank you for contributing! From 61b67de522a189b568aced4c5c16917c558e3387 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 21 Sep 2025 21:11:11 +0800 Subject: [PATCH 077/156] ci(bot): increase frequency and group updates for dependencies (#4367) - Change the update schedule for both gomod and GitHub Actions dependencies from weekly to daily - Add grouping for GitHub Actions updates using a catch-all pattern Signed-off-by: appleboy --- .github/dependabot.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 632e8eb2..ab644980 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,14 @@ version: 2 updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - package-ecosystem: gomod directory: / schedule: - interval: weekly + interval: daily + - package-ecosystem: github-actions + directory: / + groups: + actions: + patterns: + - "*" + schedule: + interval: daily From 048f6fb8849b35be6a4e655076a2247f29f7c284 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 21 Sep 2025 21:18:25 +0800 Subject: [PATCH 078/156] chore(deps): bump the actions group with 2 updates (#4368) Bumps the actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [codecov/codecov-action](https://github.com/codecov/codecov-action). Updates `actions/checkout` from 4 to 5 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) Updates `codecov/codecov-action` from 4 to 5 - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: codecov/codecov-action dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 2 +- .github/workflows/gin.yml | 6 +++--- .github/workflows/goreleaser.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9a4c40d7..bd4c52c2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 17b54ab3..e0210214 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Set up Go @@ -61,7 +61,7 @@ jobs: cache: false - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ github.ref }} @@ -78,7 +78,7 @@ jobs: run: make test - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index c87cf2d6..37dfb5bb 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Set up Go From df2753778e7bc5c2dd559361cf0c97b2b313e9bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 08:10:02 +0800 Subject: [PATCH 079/156] chore(deps): bump github.com/quic-go/quic-go from 0.54.0 to 0.54.1 (#4379) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.54.0 to 0.54.1. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Commits](https://github.com/quic-go/quic-go/compare/v0.54.0...v0.54.1) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go dependency-version: 0.54.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dd7d99bd..7eeca3ef 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/quic-go/quic-go v0.54.0 + github.com/quic-go/quic-go v0.54.1 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.42.0 diff --git a/go.sum b/go.sum index 75ecb7c1..97bab5dd 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= -github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/quic-go/quic-go v0.54.1 h1:4ZAWm0AhCb6+hE+l5Q1NAL0iRn/ZrMwqHRGQiFwj2eg= +github.com/quic-go/quic-go v0.54.1/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= From 234a6d4c00cb77af9852aca0b8289745d5529b4b Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 26 Sep 2025 08:13:39 +0800 Subject: [PATCH 080/156] fix(response): refine hijack behavior for response lifecycle (#4373) * feat: refine hijack behavior for response lifecycle and add tests - Clarify the error message for attempted hijack after response body data is written - Modify hijack behavior: allow hijacking after headers are written (for better websocket compatibility), but block hijacking after any body data is sent - Add comprehensive tests to validate allowed hijack after header write and disallowed hijack after body write fix https://github.com/gin-gonic/gin/issues/4372 Signed-off-by: appleboy * test: use require for immediate test failure on errors - Replace assert with require for error checks to ensure test failures immediately halt execution Signed-off-by: appleboy * Update response_writer.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Signed-off-by: appleboy Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- response_writer.go | 6 +++-- response_writer_test.go | 58 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/response_writer.go b/response_writer.go index ab2f5fec..6907e514 100644 --- a/response_writer.go +++ b/response_writer.go @@ -17,7 +17,7 @@ const ( defaultStatus = http.StatusOK ) -var errHijackAlreadyWritten = errors.New("gin: response already written") +var errHijackAlreadyWritten = errors.New("gin: response body already written") // ResponseWriter ... type ResponseWriter interface { @@ -109,7 +109,9 @@ func (w *responseWriter) Written() bool { // Hijack implements the http.Hijacker interface. func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { - if w.Written() { + // Allow hijacking before any data is written (size == -1) or after headers are written (size == 0), + // but not after body data is written (size > 0). For compatibility with websocket libraries (e.g., github.com/coder/websocket) + if w.size > 0 { return nil, nil, errHijackAlreadyWritten } if w.size < 0 { diff --git a/response_writer_test.go b/response_writer_test.go index ef198418..dfc1d2c6 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -194,6 +194,64 @@ func TestResponseWriterHijackAfterWrite(t *testing.T) { } } +// Test: WebSocket compatibility - allow hijack after WriteHeaderNow(), but block after body data. +func TestResponseWriterHijackAfterWriteHeaderNow(t *testing.T) { + tests := []struct { + name string + action func(w ResponseWriter) error + expectWrittenBeforeHijack bool + expectHijackSuccess bool + expectWrittenAfterHijack bool + expectError error + }{ + { + name: "hijack after WriteHeaderNow only should succeed (websocket pattern)", + action: func(w ResponseWriter) error { + w.WriteHeaderNow() // Simulate websocket.Accept() behavior + return nil + }, + expectWrittenBeforeHijack: true, + expectHijackSuccess: true, // NEW BEHAVIOR: allow hijack after just header write + expectWrittenAfterHijack: true, + expectError: nil, + }, + { + name: "hijack after WriteHeaderNow + Write should fail", + action: func(w ResponseWriter) error { + w.WriteHeaderNow() + _, err := w.Write([]byte("test")) + return err + }, + expectWrittenBeforeHijack: true, + expectHijackSuccess: false, + expectWrittenAfterHijack: true, + expectError: errHijackAlreadyWritten, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + hijacker := &mockHijacker{ResponseRecorder: httptest.NewRecorder()} + writer := &responseWriter{} + writer.reset(hijacker) + w := ResponseWriter(writer) + + require.NoError(t, tc.action(w), "unexpected error during pre-hijack action") + + assert.Equal(t, tc.expectWrittenBeforeHijack, w.Written(), "unexpected w.Written() state before hijack") + + _, _, hijackErr := w.Hijack() + + if tc.expectError == nil { + require.NoError(t, hijackErr, "expected hijack to succeed") + } else { + require.ErrorIs(t, hijackErr, tc.expectError, "unexpected error from Hijack()") + } + assert.Equal(t, tc.expectHijackSuccess, hijacker.hijacked, "unexpected hijacker.hijacked state") + assert.Equal(t, tc.expectWrittenAfterHijack, w.Written(), "unexpected w.Written() state after hijack") + }) + } +} + func TestResponseWriterFlush(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { writer := &responseWriter{} From ed150e72544949a96e7eb0b7d1151cf1907068fb Mon Sep 17 00:00:00 2001 From: Meng Xun <30499307+mengxunQAQ@users.noreply.github.com> Date: Fri, 26 Sep 2025 08:15:35 +0800 Subject: [PATCH 081/156] test(benchmarks): fix the incorrect function name (#4375) Signed-off-by: mengxun --- benchmarks_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks_test.go b/benchmarks_test.go index 3a8d53f3..ca504ecb 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -87,7 +87,7 @@ func BenchmarkOneRouteString(B *testing.B) { runRequest(B, router, http.MethodGet, "/text") } -func BenchmarkManyRoutesFist(B *testing.B) { +func BenchmarkManyRoutesFirst(B *testing.B) { router := New() router.Any("/ping", func(c *Context) {}) runRequest(B, router, http.MethodGet, "/ping") From 39858a0859c914bd26948fa950477e11bd8d3823 Mon Sep 17 00:00:00 2001 From: russcoss Date: Fri, 26 Sep 2025 23:03:59 -0400 Subject: [PATCH 082/156] refactor(binding): use maps.Copy for cleaner map handling (#4352) Signed-off-by: russcoss --- binding/form_mapping.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 45a39e15..9cf56527 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -7,6 +7,7 @@ package binding import ( "errors" "fmt" + "maps" "mime/multipart" "reflect" "strconv" @@ -489,9 +490,7 @@ func setFormMap(ptr any, form map[string][]string) error { if !ok { return ErrConvertMapStringSlice } - for k, v := range form { - ptrMap[k] = v - } + maps.Copy(ptrMap, form) return nil } From 8ca975441f077e30c5dac73e51bad99ad00f0d4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 21:25:35 +0800 Subject: [PATCH 083/156] chore(deps): bump google.golang.org/protobuf from 1.36.9 to 1.36.10 (#4383) Bumps google.golang.org/protobuf from 1.36.9 to 1.36.10. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7eeca3ef..e673de56 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.42.0 - google.golang.org/protobuf v1.36.9 + google.golang.org/protobuf v1.36.10 ) require ( diff --git a/go.sum b/go.sum index 97bab5dd..f3de6b21 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 731374fb3682af61cb153dabeec89aae401f1ede Mon Sep 17 00:00:00 2001 From: goldlinker Date: Fri, 3 Oct 2025 21:26:47 +0800 Subject: [PATCH 084/156] docs(context): fix wrong function name in comment (#4382) Signed-off-by: goldlinker --- context_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context_test.go b/context_test.go index fbc13879..08ffc3f6 100644 --- a/context_test.go +++ b/context_test.go @@ -1233,7 +1233,7 @@ func TestContextRenderNoContentHTML(t *testing.T) { assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } -// TestContextXML tests that the response is serialized as XML +// TestContextRenderXML tests that the response is serialized as XML // and Content-Type is set to application/xml func TestContextRenderXML(t *testing.T) { w := httptest.NewRecorder() From 4dec17afdff48e8018c83618fbbe69fceeb2b41d Mon Sep 17 00:00:00 2001 From: ljz <641390597@qq.com> Date: Sun, 5 Oct 2025 11:23:57 +0800 Subject: [PATCH 085/156] feat(logger): color latency (#4146) Co-authored-by: lizhao --- logger.go | 38 +++++++++++++++++++++++++++++++++----- logger_test.go | 25 +++++++++++++++++++++---- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/logger.go b/logger.go index 47827787..6441f7ea 100644 --- a/logger.go +++ b/logger.go @@ -103,6 +103,27 @@ func (p *LogFormatterParams) StatusCodeColor() string { } } +// LatencyColor is the ANSI color for latency +func (p *LogFormatterParams) LatencyColor() string { + latency := p.Latency + switch { + case latency < time.Millisecond*100: + return white + case latency < time.Millisecond*200: + return green + case latency < time.Millisecond*300: + return cyan + case latency < time.Millisecond*500: + return blue + case latency < time.Second: + return yellow + case latency < time.Second*2: + return magenta + default: + return red + } +} + // MethodColor is the ANSI color for appropriately logging http method to a terminal. func (p *LogFormatterParams) MethodColor() string { method := p.Method @@ -139,20 +160,27 @@ func (p *LogFormatterParams) IsOutputColor() bool { // defaultLogFormatter is the default log format function Logger middleware uses. var defaultLogFormatter = func(param LogFormatterParams) string { - var statusColor, methodColor, resetColor string + var statusColor, methodColor, resetColor, latencyColor string if param.IsOutputColor() { statusColor = param.StatusCodeColor() methodColor = param.MethodColor() resetColor = param.ResetColor() + latencyColor = param.LatencyColor() } - if param.Latency > time.Minute { - param.Latency = param.Latency.Truncate(time.Second) + switch { + case param.Latency > time.Minute: + param.Latency = param.Latency.Truncate(time.Second * 10) + case param.Latency > time.Second: + param.Latency = param.Latency.Truncate(time.Millisecond * 10) + case param.Latency > time.Millisecond: + param.Latency = param.Latency.Truncate(time.Microsecond * 10) } - return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s", + + return fmt.Sprintf("[GIN] %v |%s %3d %s|%s %8v %s| %15s |%s %-7s %s %#v\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), statusColor, param.StatusCode, resetColor, - param.Latency, + latencyColor, param.Latency, resetColor, param.ClientIP, methodColor, param.Method, resetColor, param.Path, diff --git a/logger_test.go b/logger_test.go index 8a542e97..335b0e31 100644 --- a/logger_test.go +++ b/logger_test.go @@ -277,11 +277,11 @@ func TestDefaultLogFormatter(t *testing.T) { isTerm: false, } - assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET \"/\"\n", defaultLogFormatter(termFalseParam)) - assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m3s | 20.20.20.20 | GET \"/\"\n", defaultLogFormatter(termFalseLongDurationParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET \"/\"\n", defaultLogFormatter(termFalseParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m0s | 20.20.20.20 | GET \"/\"\n", defaultLogFormatter(termFalseLongDurationParam)) - assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam)) - assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|\x1b[97;41m 5s \x1b[0m| 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|\x1b[97;41m 2743h29m0s \x1b[0m| 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam)) } func TestColorForMethod(t *testing.T) { @@ -317,6 +317,23 @@ func TestColorForStatus(t *testing.T) { assert.Equal(t, red, colorForStatus(2), "other things should be red") } +func TestColorForLatency(t *testing.T) { + colorForLantency := func(latency time.Duration) string { + p := LogFormatterParams{ + Latency: latency, + } + return p.LatencyColor() + } + + assert.Equal(t, white, colorForLantency(time.Duration(0)), "0 should be white") + assert.Equal(t, white, colorForLantency(time.Millisecond*20), "20ms should be white") + assert.Equal(t, green, colorForLantency(time.Millisecond*150), "150ms should be green") + assert.Equal(t, cyan, colorForLantency(time.Millisecond*250), "250ms should be cyan") + assert.Equal(t, yellow, colorForLantency(time.Millisecond*600), "600ms should be yellow") + assert.Equal(t, magenta, colorForLantency(time.Millisecond*1500), "1.5s should be magenta") + assert.Equal(t, red, colorForLantency(time.Second*3), "other things should be red") +} + func TestResetColor(t *testing.T) { p := LogFormatterParams{} assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor()) From 0bd10a84f9d49ded6dd043d108b78c6b08e86cdf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 08:12:52 +0800 Subject: [PATCH 086/156] chore(deps): bump github/codeql-action from 3 to 4 in the actions group (#4387) Bumps the actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 3 to 4 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bd4c52c2..9ec3700e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,7 +37,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -46,4 +46,4 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 From 48a5dca087085e64fbfe757a4cf9de1c3b583dda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 08:13:29 +0800 Subject: [PATCH 087/156] chore(deps): bump github.com/go-playground/validator/v10 (#4385) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.27.0 to 10.28.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.27.0...v10.28.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-version: 10.28.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 20 ++++++++++---------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index e673de56..b1d1e9e5 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/gin-gonic/gin -go 1.23.0 +go 1.24.0 require ( github.com/bytedance/sonic v1.14.0 github.com/gin-contrib/sse v1.1.0 - github.com/go-playground/validator/v10 v10.27.0 + github.com/go-playground/validator/v10 v10.28.0 github.com/goccy/go-json v0.10.2 github.com/goccy/go-yaml v1.18.0 github.com/json-iterator/go v1.1.12 @@ -15,7 +15,7 @@ require ( github.com/quic-go/quic-go v0.54.1 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 - golang.org/x/net v0.42.0 + golang.org/x/net v0.43.0 google.golang.org/protobuf v1.36.10 ) @@ -23,7 +23,7 @@ require ( github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -34,11 +34,11 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/mod v0.25.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.27.0 // indirect - golang.org/x/tools v0.34.0 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/tools v0.36.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index f3de6b21..5c8ce990 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gE github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= -github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= +github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -17,8 +17,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= -github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= +github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= @@ -63,21 +63,21 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= -golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= -golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 5dd833f1f26de0eb30eae47b17e05ced2482dc41 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 8 Oct 2025 08:30:45 +0800 Subject: [PATCH 088/156] chore: bump minimum Go version to 1.24 and update workflows (#4388) - Update minimum required Go version from 1.23 to 1.24 throughout documentation, warnings, and tests - Remove Go 1.23 from the GitHub Actions workflow matrix - Change single quotes to double quotes for consistency in workflow configuration Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 10 +++++----- README.md | 2 +- context_test.go | 8 ++++---- debug.go | 2 +- debug_test.go | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index e0210214..f61c6486 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -33,7 +33,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.23", "1.24", "1.25"] + go: ["1.24", "1.25"] test-tags: [ "", @@ -92,8 +92,8 @@ jobs: - name: Run Trivy vulnerability scanner in repo mode uses: aquasecurity/trivy-action@0.33.1 with: - scan-type: 'fs' + scan-type: "fs" ignore-unfixed: true - format: 'table' - exit-code: '1' - severity: 'CRITICAL,HIGH,MEDIUM' + format: "table" + exit-code: "1" + severity: "CRITICAL,HIGH,MEDIUM" diff --git a/README.md b/README.md index 8343a55b..629cb98d 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Gin combines the simplicity of Express.js-style routing with Go's performance ch ### Prerequisites -- **Go version**: Gin requires [Go](https://go.dev/) version [1.23](https://go.dev/doc/devel/release#go1.23.0) or above +- **Go version**: Gin requires [Go](https://go.dev/) version [1.24](https://go.dev/doc/devel/release#go1.24.0) or above - **Basic Go knowledge**: Familiarity with Go syntax and package management is helpful ### Installation diff --git a/context_test.go b/context_test.go index 08ffc3f6..cc066ef8 100644 --- a/context_test.go +++ b/context_test.go @@ -3320,7 +3320,7 @@ func TestContextSetCookieData(t *testing.T) { assert.Contains(t, setCookie, "Max-Age=1") assert.Contains(t, setCookie, "HttpOnly") assert.Contains(t, setCookie, "Secure") - // SameSite=Lax might be omitted in Go 1.23+ as it's the default + // SameSite=Lax might be omitted in Go 1.24+ as it's the default // assert.Contains(t, setCookie, "SameSite=Lax") // Test that when Path is empty, "/" is automatically set @@ -3341,7 +3341,7 @@ func TestContextSetCookieData(t *testing.T) { assert.Contains(t, setCookie, "Max-Age=1") assert.Contains(t, setCookie, "HttpOnly") assert.Contains(t, setCookie, "Secure") - // SameSite=Lax might be omitted in Go 1.23+ as it's the default + // SameSite=Lax might be omitted in Go 1.24+ as it's the default // assert.Contains(t, setCookie, "SameSite=Lax") // Test additional cookie attributes (Expires) @@ -3364,7 +3364,7 @@ func TestContextSetCookieData(t *testing.T) { assert.Contains(t, setCookie, "Domain=localhost") assert.Contains(t, setCookie, "HttpOnly") assert.Contains(t, setCookie, "Secure") - // SameSite=Lax might be omitted in Go 1.23+ as it's the default + // SameSite=Lax might be omitted in Go 1.24+ as it's the default // assert.Contains(t, setCookie, "SameSite=Lax") // Test for Partitioned attribute (Go 1.18+) @@ -3384,7 +3384,7 @@ func TestContextSetCookieData(t *testing.T) { assert.Contains(t, setCookie, "Domain=localhost") assert.Contains(t, setCookie, "HttpOnly") assert.Contains(t, setCookie, "Secure") - // SameSite=Lax might be omitted in Go 1.23+ as it's the default + // SameSite=Lax might be omitted in Go 1.24+ as it's the default // assert.Contains(t, setCookie, "SameSite=Lax") // Not testing for Partitioned attribute as it may not be supported in all Go versions diff --git a/debug.go b/debug.go index 7fe2762e..f22dfd87 100644 --- a/debug.go +++ b/debug.go @@ -78,7 +78,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.23+. + debugPrint(`[WARNING] Now Gin requires Go 1.24+. `) } diff --git a/debug_test.go b/debug_test.go index 59b61beb..e9d8fe01 100644 --- a/debug_test.go +++ b/debug_test.go @@ -106,7 +106,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m < ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.23+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.24+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } From 0d085ed9fe2053e3c35971aa1870ebfebf90b2ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:32:58 +0800 Subject: [PATCH 089/156] chore(deps): bump golang.org/x/net from 0.43.0 to 0.46.0 (#4391) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.43.0 to 0.46.0. - [Commits](https://github.com/golang/net/compare/v0.43.0...v0.46.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.46.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index b1d1e9e5..3701deac 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/quic-go/quic-go v0.54.1 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 - golang.org/x/net v0.43.0 + golang.org/x/net v0.46.0 google.golang.org/protobuf v1.36.10 ) @@ -34,11 +34,11 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/mod v0.27.0 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/mod v0.28.0 // indirect golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/tools v0.37.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5c8ce990..aff5f21b 100644 --- a/go.sum +++ b/go.sum @@ -63,21 +63,21 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 053e5765fd6b31093928a2208e2a09ec1049653b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:33:36 +0800 Subject: [PATCH 090/156] chore(deps): bump github.com/quic-go/quic-go from 0.54.1 to 0.55.0 (#4384) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.54.1 to 0.55.0. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Commits](https://github.com/quic-go/quic-go/compare/v0.54.1...v0.55.0) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go dependency-version: 0.55.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 3 +-- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 3701deac..961916f0 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/quic-go/quic-go v0.54.1 + github.com/quic-go/quic-go v0.55.0 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.0 golang.org/x/net v0.46.0 @@ -32,7 +32,6 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect golang.org/x/crypto v0.43.0 // indirect golang.org/x/mod v0.28.0 // indirect diff --git a/go.sum b/go.sum index aff5f21b..2dfb4d75 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.54.1 h1:4ZAWm0AhCb6+hE+l5Q1NAL0iRn/ZrMwqHRGQiFwj2eg= -github.com/quic-go/quic-go v0.54.1/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= +github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -59,8 +59,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= -go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= From 9968c4bf9d5a99edc3eee2c068a4c9160ece8915 Mon Sep 17 00:00:00 2001 From: reddaisyy Date: Thu, 9 Oct 2025 11:36:56 +0800 Subject: [PATCH 091/156] refactor: use b.Loop() to simplify the code and improve performance (#4389) Signed-off-by: reddaisyy --- internal/bytesconv/bytesconv_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/bytesconv/bytesconv_test.go b/internal/bytesconv/bytesconv_test.go index ff26e35e..4972ae70 100644 --- a/internal/bytesconv/bytesconv_test.go +++ b/internal/bytesconv/bytesconv_test.go @@ -81,25 +81,25 @@ func TestStringToBytes(t *testing.T) { // go test -v -run=none -bench=^BenchmarkBytesConv -benchmem=true func BenchmarkBytesConvBytesToStrRaw(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { rawBytesToStr(testBytes) } } func BenchmarkBytesConvBytesToStr(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { BytesToString(testBytes) } } func BenchmarkBytesConvStrToBytesRaw(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { rawStrToBytes(testString) } } func BenchmarkBytesConvStrToBytes(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { StringToBytes(testString) } } From c3d1092b3b48addf6f9cd00fe274ec3bd14650eb Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Sat, 11 Oct 2025 19:20:41 +0800 Subject: [PATCH 092/156] fix(binding): improve empty slice/array handling in form binding (#4380) Co-authored-by: huangzw --- binding/form_mapping.go | 14 +++++-- binding/form_mapping_test.go | 80 ++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 9cf56527..1244b522 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -231,9 +231,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ switch value.Kind() { case reflect.Slice: - if !ok { - vs = []string{opt.defaultValue} + if len(vs) == 0 { + if !opt.isDefaultExists { + return false, nil + } + vs = []string{opt.defaultValue} // pre-process the default value for multi if present cfTag := field.Tag.Get("collection_format") if cfTag == "" || cfTag == "multi" { @@ -251,9 +254,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ return true, setSlice(vs, value, field) case reflect.Array: - if !ok { - vs = []string{opt.defaultValue} + if len(vs) == 0 { + if !opt.isDefaultExists { + return false, nil + } + vs = []string{opt.defaultValue} // pre-process the default value for multi if present cfTag := field.Tag.Get("collection_format") if cfTag == "" || cfTag == "multi" { diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 55b967a3..006eddf1 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -635,3 +635,83 @@ func TestMappingCustomArrayForm(t *testing.T) { expected, _ := convertTo(val) assert.Equal(t, expected, s.FileData) } + +func TestMappingEmptyValues(t *testing.T) { + t.Run("slice with default", func(t *testing.T) { + var s struct { + Slice []int `form:"slice,default=5"` + } + + // field not present + err := mappingByPtr(&s, formSource{}, "form") + require.NoError(t, err) + assert.Equal(t, []int{5}, s.Slice) + + // field present but empty + err = mappingByPtr(&s, formSource{"slice": {}}, "form") + require.NoError(t, err) + assert.Equal(t, []int{5}, s.Slice) + + // field present with values + err = mappingByPtr(&s, formSource{"slice": {"1", "2", "3"}}, "form") + require.NoError(t, err) + assert.Equal(t, []int{1, 2, 3}, s.Slice) + }) + + t.Run("array with default", func(t *testing.T) { + var s struct { + Array [1]int `form:"array,default=5"` + } + + // field not present + err := mappingByPtr(&s, formSource{}, "form") + require.NoError(t, err) + assert.Equal(t, [1]int{5}, s.Array) + + // field present but empty + err = mappingByPtr(&s, formSource{"array": {}}, "form") + require.NoError(t, err) + assert.Equal(t, [1]int{5}, s.Array) + }) + + t.Run("slice without default", func(t *testing.T) { + var s struct { + Slice []int `form:"slice"` + } + + // field present but empty + err := mappingByPtr(&s, formSource{"slice": {}}, "form") + require.NoError(t, err) + assert.Equal(t, []int(nil), s.Slice) + }) + + t.Run("array without default", func(t *testing.T) { + var s struct { + Array [1]int `form:"array"` + } + + // field present but empty + err := mappingByPtr(&s, formSource{"array": {}}, "form") + require.NoError(t, err) + assert.Equal(t, [1]int{0}, s.Array) + }) + + t.Run("slice with collection format", func(t *testing.T) { + var s struct { + SliceMulti []int `form:"slice_multi,default=1;2;3" collection_format:"multi"` + SliceCsv []int `form:"slice_csv,default=1;2;3" collection_format:"csv"` + } + + // field not present + err := mappingByPtr(&s, formSource{}, "form") + require.NoError(t, err) + assert.Equal(t, []int{1, 2, 3}, s.SliceMulti) + assert.Equal(t, []int{1, 2, 3}, s.SliceCsv) + + // field present but empty + err = mappingByPtr(&s, formSource{"slice_multi": {}, "slice_csv": {}}, "form") + require.NoError(t, err) + assert.Equal(t, []int{1, 2, 3}, s.SliceMulti) + assert.Equal(t, []int{1, 2, 3}, s.SliceCsv) + }) +} From c221133ee80c46e3a6c50717ca6f1b41d4ab7711 Mon Sep 17 00:00:00 2001 From: letreturn Date: Tue, 14 Oct 2025 22:37:07 +0800 Subject: [PATCH 093/156] docs(context): fix some comments (#4396) Signed-off-by: letreturn --- context.go | 2 +- logger_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index d7280c5d..8e39875d 100644 --- a/context.go +++ b/context.go @@ -270,7 +270,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. +// It also lazy initializes c.Keys if it was not used previously. func (c *Context) Set(key any, value any) { c.mu.Lock() defer c.mu.Unlock() diff --git a/logger_test.go b/logger_test.go index 335b0e31..53d0df95 100644 --- a/logger_test.go +++ b/logger_test.go @@ -39,7 +39,7 @@ func TestLogger(t *testing.T) { // I wrote these first (extending the above) but then realized they are more // like integration tests because they test the whole logging process rather - // than individual functions. Im not sure where these should go. + // than individual functions. I'm not sure where these should go. buffer.Reset() PerformRequest(router, http.MethodPost, "/example") assert.Contains(t, buffer.String(), "200") @@ -103,7 +103,7 @@ func TestLoggerWithConfig(t *testing.T) { // I wrote these first (extending the above) but then realized they are more // like integration tests because they test the whole logging process rather - // than individual functions. Im not sure where these should go. + // than individual functions. I'm not sure where these should go. buffer.Reset() PerformRequest(router, http.MethodPost, "/example") assert.Contains(t, buffer.String(), "200") From 38e765119241d990705169bedb5002a29ae0cbd1 Mon Sep 17 00:00:00 2001 From: Spyder01 <45194214+Spyder01@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:51:34 +0530 Subject: [PATCH 094/156] feat(context): implemented Delete method Co-authored-by: suhan --- context.go | 10 ++++++++++ context_test.go | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/context.go b/context.go index 8e39875d..c2309d31 100644 --- a/context.go +++ b/context.go @@ -465,6 +465,16 @@ func (c *Context) GetStringMapStringSlice(key any) (smss map[string][]string) { return getTyped[map[string][]string](c, key) } +// Delete deletes the key from the Context's Key map, if it exists. +// This operation is safe to be used by concurrent go-routines +func (c *Context) Delete(key any) { + c.mu.Lock() + defer c.mu.Unlock() + if c.Keys != nil { + delete(c.Keys, key) + } +} + /************************************/ /************ INPUT DATA ************/ /************************************/ diff --git a/context_test.go b/context_test.go index cc066ef8..e6b7519e 100644 --- a/context_test.go +++ b/context_test.go @@ -404,6 +404,19 @@ func TestContextSetGetBool(t *testing.T) { assert.True(t, c.GetBool("bool")) } +func TestSetGetDelete(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "example-key" + value := "example-value" + c.Set(key, value) + val, exists := c.Get(key) + assert.True(t, exists) + assert.Equal(t, val, value) + c.Delete(key) + _, exists = c.Get(key) + assert.False(t, exists) +} + func TestContextGetInt(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("int", 1) From c0048f645ee945c4db30593afdea10123e2c30a6 Mon Sep 17 00:00:00 2001 From: wanghaolong613 Date: Fri, 17 Oct 2025 11:39:49 +0800 Subject: [PATCH 095/156] refactor(context): omit the return value names (#4395) --- context.go | 64 +++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/context.go b/context.go index c2309d31..e64c7953 100644 --- a/context.go +++ b/context.go @@ -306,162 +306,162 @@ func getTyped[T any](c *Context, key any) (res T) { } // GetString returns the value associated with the key as a string. -func (c *Context) GetString(key any) (s string) { +func (c *Context) GetString(key any) string { return getTyped[string](c, key) } // GetBool returns the value associated with the key as a boolean. -func (c *Context) GetBool(key any) (b bool) { +func (c *Context) GetBool(key any) bool { return getTyped[bool](c, key) } // GetInt returns the value associated with the key as an integer. -func (c *Context) GetInt(key any) (i int) { +func (c *Context) GetInt(key any) int { return getTyped[int](c, key) } // GetInt8 returns the value associated with the key as an integer 8. -func (c *Context) GetInt8(key any) (i8 int8) { +func (c *Context) GetInt8(key any) int8 { return getTyped[int8](c, key) } // GetInt16 returns the value associated with the key as an integer 16. -func (c *Context) GetInt16(key any) (i16 int16) { +func (c *Context) GetInt16(key any) int16 { return getTyped[int16](c, key) } // GetInt32 returns the value associated with the key as an integer 32. -func (c *Context) GetInt32(key any) (i32 int32) { +func (c *Context) GetInt32(key any) int32 { return getTyped[int32](c, key) } // GetInt64 returns the value associated with the key as an integer 64. -func (c *Context) GetInt64(key any) (i64 int64) { +func (c *Context) GetInt64(key any) int64 { return getTyped[int64](c, key) } // GetUint returns the value associated with the key as an unsigned integer. -func (c *Context) GetUint(key any) (ui uint) { +func (c *Context) GetUint(key any) uint { return getTyped[uint](c, key) } // GetUint8 returns the value associated with the key as an unsigned integer 8. -func (c *Context) GetUint8(key any) (ui8 uint8) { +func (c *Context) GetUint8(key any) uint8 { return getTyped[uint8](c, key) } // GetUint16 returns the value associated with the key as an unsigned integer 16. -func (c *Context) GetUint16(key any) (ui16 uint16) { +func (c *Context) GetUint16(key any) uint16 { return getTyped[uint16](c, key) } // GetUint32 returns the value associated with the key as an unsigned integer 32. -func (c *Context) GetUint32(key any) (ui32 uint32) { +func (c *Context) GetUint32(key any) uint32 { return getTyped[uint32](c, key) } // GetUint64 returns the value associated with the key as an unsigned integer 64. -func (c *Context) GetUint64(key any) (ui64 uint64) { +func (c *Context) GetUint64(key any) uint64 { return getTyped[uint64](c, key) } // GetFloat32 returns the value associated with the key as a float32. -func (c *Context) GetFloat32(key any) (f32 float32) { +func (c *Context) GetFloat32(key any) float32 { return getTyped[float32](c, key) } // GetFloat64 returns the value associated with the key as a float64. -func (c *Context) GetFloat64(key any) (f64 float64) { +func (c *Context) GetFloat64(key any) float64 { return getTyped[float64](c, key) } // GetTime returns the value associated with the key as time. -func (c *Context) GetTime(key any) (t time.Time) { +func (c *Context) GetTime(key any) time.Time { return getTyped[time.Time](c, key) } // GetDuration returns the value associated with the key as a duration. -func (c *Context) GetDuration(key any) (d time.Duration) { +func (c *Context) GetDuration(key any) time.Duration { return getTyped[time.Duration](c, key) } // GetIntSlice returns the value associated with the key as a slice of integers. -func (c *Context) GetIntSlice(key any) (is []int) { +func (c *Context) GetIntSlice(key any) []int { return getTyped[[]int](c, key) } // GetInt8Slice returns the value associated with the key as a slice of int8 integers. -func (c *Context) GetInt8Slice(key any) (i8s []int8) { +func (c *Context) GetInt8Slice(key any) []int8 { return getTyped[[]int8](c, key) } // GetInt16Slice returns the value associated with the key as a slice of int16 integers. -func (c *Context) GetInt16Slice(key any) (i16s []int16) { +func (c *Context) GetInt16Slice(key any) []int16 { return getTyped[[]int16](c, key) } // GetInt32Slice returns the value associated with the key as a slice of int32 integers. -func (c *Context) GetInt32Slice(key any) (i32s []int32) { +func (c *Context) GetInt32Slice(key any) []int32 { return getTyped[[]int32](c, key) } // GetInt64Slice returns the value associated with the key as a slice of int64 integers. -func (c *Context) GetInt64Slice(key any) (i64s []int64) { +func (c *Context) GetInt64Slice(key any) []int64 { return getTyped[[]int64](c, key) } // GetUintSlice returns the value associated with the key as a slice of unsigned integers. -func (c *Context) GetUintSlice(key any) (uis []uint) { +func (c *Context) GetUintSlice(key any) []uint { return getTyped[[]uint](c, key) } // GetUint8Slice returns the value associated with the key as a slice of uint8 integers. -func (c *Context) GetUint8Slice(key any) (ui8s []uint8) { +func (c *Context) GetUint8Slice(key any) []uint8 { return getTyped[[]uint8](c, key) } // GetUint16Slice returns the value associated with the key as a slice of uint16 integers. -func (c *Context) GetUint16Slice(key any) (ui16s []uint16) { +func (c *Context) GetUint16Slice(key any) []uint16 { return getTyped[[]uint16](c, key) } // GetUint32Slice returns the value associated with the key as a slice of uint32 integers. -func (c *Context) GetUint32Slice(key any) (ui32s []uint32) { +func (c *Context) GetUint32Slice(key any) []uint32 { return getTyped[[]uint32](c, key) } // GetUint64Slice returns the value associated with the key as a slice of uint64 integers. -func (c *Context) GetUint64Slice(key any) (ui64s []uint64) { +func (c *Context) GetUint64Slice(key any) []uint64 { return getTyped[[]uint64](c, key) } // GetFloat32Slice returns the value associated with the key as a slice of float32 numbers. -func (c *Context) GetFloat32Slice(key any) (f32s []float32) { +func (c *Context) GetFloat32Slice(key any) []float32 { return getTyped[[]float32](c, key) } // GetFloat64Slice returns the value associated with the key as a slice of float64 numbers. -func (c *Context) GetFloat64Slice(key any) (f64s []float64) { +func (c *Context) GetFloat64Slice(key any) []float64 { return getTyped[[]float64](c, key) } // GetStringSlice returns the value associated with the key as a slice of strings. -func (c *Context) GetStringSlice(key any) (ss []string) { +func (c *Context) GetStringSlice(key any) []string { return getTyped[[]string](c, key) } // GetStringMap returns the value associated with the key as a map of interfaces. -func (c *Context) GetStringMap(key any) (sm map[string]any) { +func (c *Context) GetStringMap(key any) map[string]any { return getTyped[map[string]any](c, key) } // GetStringMapString returns the value associated with the key as a map of strings. -func (c *Context) GetStringMapString(key any) (sms map[string]string) { +func (c *Context) GetStringMapString(key any) map[string]string { return getTyped[map[string]string](c, key) } // GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings. -func (c *Context) GetStringMapStringSlice(key any) (smss map[string][]string) { +func (c *Context) GetStringMapStringSlice(key any) map[string][]string { return getTyped[map[string][]string](c, key) } From 87c207a14093666fae281e9ebabe3ce6dd0b5ecd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:02:56 +0800 Subject: [PATCH 096/156] chore(deps): bump github.com/bytedance/sonic from 1.14.0 to 1.14.2 (#4410) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.14.0 to 1.14.2. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.14.0...v1.14.2) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-version: 1.14.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 5 +++-- go.sum | 14 +++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 961916f0..e3a9ed25 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.24.0 require ( - github.com/bytedance/sonic v1.14.0 + github.com/bytedance/sonic v1.14.2 github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.28.0 github.com/goccy/go-json v0.10.2 @@ -20,7 +20,8 @@ require ( ) require ( - github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect diff --git a/go.sum b/go.sum index 2dfb4d75..31702f3a 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,9 @@ -github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= -github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= -github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= -github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= +github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= +github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= +github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -49,10 +51,12 @@ github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= From 52f70cf18a61939ab25696fa335ecb8934512fb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:03:29 +0800 Subject: [PATCH 097/156] chore(deps): bump github.com/ugorji/go/codec from 1.3.0 to 1.3.1 (#4409) Bumps [github.com/ugorji/go/codec](https://github.com/ugorji/go) from 1.3.0 to 1.3.1. - [Release notes](https://github.com/ugorji/go/releases) - [Commits](https://github.com/ugorji/go/compare/codec/v1.3.0...codec/v1.3.1) --- updated-dependencies: - dependency-name: github.com/ugorji/go/codec dependency-version: 1.3.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e3a9ed25..beabc954 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 github.com/quic-go/quic-go v0.55.0 github.com/stretchr/testify v1.11.1 - github.com/ugorji/go/codec v1.3.0 + github.com/ugorji/go/codec v1.3.1 golang.org/x/net v0.46.0 google.golang.org/protobuf v1.36.10 ) diff --git a/go.sum b/go.sum index 31702f3a..ed1361a9 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= -github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= From 2e22e5085960205fbb11c25776f6ea76b8053253 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Fri, 31 Oct 2025 22:09:07 +0800 Subject: [PATCH 098/156] perf(tree): optimize path parsing using strings.Count (#4246) Co-authored-by: 1911860538 --- tree.go | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/tree.go b/tree.go index 78479b6f..bcc83502 100644 --- a/tree.go +++ b/tree.go @@ -5,7 +5,6 @@ package gin import ( - "bytes" "net/url" "strings" "unicode" @@ -14,12 +13,6 @@ import ( "github.com/gin-gonic/gin/internal/bytesconv" ) -var ( - strColon = []byte(":") - strStar = []byte("*") - strSlash = []byte("/") -) - // Param is a single URL parameter, consisting of a key and a value. type Param struct { Key string @@ -85,16 +78,13 @@ func (n *node) addChild(child *node) { } func countParams(path string) uint16 { - var n uint16 - s := bytesconv.StringToBytes(path) - n += uint16(bytes.Count(s, strColon)) - n += uint16(bytes.Count(s, strStar)) - return n + colons := strings.Count(path, ":") + stars := strings.Count(path, "*") + return uint16(colons + stars) } func countSections(path string) uint16 { - s := bytesconv.StringToBytes(path) - return uint16(bytes.Count(s, strSlash)) + return uint16(strings.Count(path, "/")) } type nodeType uint8 From 5e5ff3ace496a31b138b0820136a146bfb5de0ef Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 6 Nov 2025 14:15:50 +0800 Subject: [PATCH 099/156] ci: replace vulnerability scanning workflow with Trivy integration (#4421) - Remove the vulnerability-scanning job from the gin workflow - Add a dedicated Trivy security scan workflow with scheduled, push, pull request, and manual triggers - Improve Trivy scan output by uploading SARIF results to the GitHub Security tab and logging table output Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 16 --------- .github/workflows/trivy-scan.yml | 57 ++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/trivy-scan.yml diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index f61c6486..eb0d7c26 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -81,19 +81,3 @@ jobs: uses: codecov/codecov-action@v5 with: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} - - vulnerability-scanning: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Run Trivy vulnerability scanner in repo mode - uses: aquasecurity/trivy-action@0.33.1 - with: - scan-type: "fs" - ignore-unfixed: true - format: "table" - exit-code: "1" - severity: "CRITICAL,HIGH,MEDIUM" diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml new file mode 100644 index 00000000..c2e29f07 --- /dev/null +++ b/.github/workflows/trivy-scan.yml @@ -0,0 +1,57 @@ +name: Trivy Security Scan + +on: + push: + branches: + - master + pull_request: + branches: + - master + schedule: + # Run every 3 months (quarterly) on the 1st day at 00:00 UTC + # Months: January (1), April (4), July (7), October (10) + - cron: '0 0 1 1,4,7,10 *' + workflow_dispatch: # Allow manual trigger + +permissions: + contents: read + security-events: write # Required for uploading SARIF results + +jobs: + trivy-scan: + name: Trivy Security Scan + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Run Trivy vulnerability scanner (source code) + uses: aquasecurity/trivy-action@0.33.1 + with: + scan-type: 'fs' + scan-ref: '.' + scanners: 'vuln,secret,misconfig' + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH,MEDIUM' + ignore-unfixed: true + + - name: Upload Trivy results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' + + - name: Run Trivy scanner (table output for logs) + uses: aquasecurity/trivy-action@0.33.1 + if: always() + with: + scan-type: 'fs' + scan-ref: '.' + scanners: 'vuln,secret,misconfig' + format: 'table' + severity: 'CRITICAL,HIGH,MEDIUM' + ignore-unfixed: true + exit-code: '1' From dceb61e6e76337b388109f6c553b026d3b6ff026 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 7 Nov 2025 11:57:12 +0800 Subject: [PATCH 100/156] docs(README): add a Trivy security scan badge (#4426) - Add a Trivy security scan badge to the documentation - Import the log package in the example code - Improve error handling for server startup in the example code Signed-off-by: Bo-Yi Wu --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 629cb98d..1b9ab808 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Build Status](https://github.com/gin-gonic/gin/actions/workflows/gin.yml/badge.svg?branch=master)](https://github.com/gin-gonic/gin/actions/workflows/gin.yml) +[![Trivy Security Scan](https://github.com/gin-gonic/gin/actions/workflows/trivy-scan.yml/badge.svg)](https://github.com/gin-gonic/gin/actions/workflows/trivy-scan.yml) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![Go Reference](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc) @@ -62,6 +63,7 @@ Here's a complete example that demonstrates Gin's simplicity: package main import ( + "log" "net/http" "github.com/gin-gonic/gin" @@ -70,7 +72,7 @@ import ( func main() { // Create a Gin router with default middleware (logger and recovery) r := gin.Default() - + // Define a simple GET endpoint r.GET("/ping", func(c *gin.Context) { // Return JSON response @@ -78,10 +80,12 @@ func main() { "message": "pong", }) }) - + // Start server on port 8080 (default) // Server will listen on 0.0.0.0:8080 (localhost:8080 on Windows) - r.Run() + if err := r.Run(); err != nil { + log.Fatalf("failed to run server: %v", err) + } } ``` @@ -190,7 +194,6 @@ Gin has a rich ecosystem of middleware for common web development needs. Explore - CORS, Rate limiting, Compression - Logging, Metrics, Tracing - Static file serving, Template engines - - **[gin-gonic/contrib](https://github.com/gin-gonic/contrib)** - Additional community middleware ## 🏢 Production Usage From 0c0e99d2538609d38c757b0a32f708b4dcf424c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:57:41 +0800 Subject: [PATCH 101/156] chore(deps): bump github/codeql-action from 3 to 4 in the actions group (#4425) Bumps the actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 3 to 4 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/trivy-scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index c2e29f07..12830633 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -39,7 +39,7 @@ jobs: ignore-unfixed: true - name: Upload Trivy results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@v4 if: always() with: sarif_file: 'trivy-results.sarif' From acc55e049e33b401e810dbd8c0d6dcb6b3ba2b05 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Fri, 7 Nov 2025 11:59:58 +0800 Subject: [PATCH 102/156] feat(context): add Protocol Buffers support to content negotiation (#4423) Co-authored-by: 1911860538 --- context.go | 22 ++++++++++++++-------- context_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/context.go b/context.go index e64c7953..d5ef8b81 100644 --- a/context.go +++ b/context.go @@ -39,6 +39,7 @@ const ( MIMEYAML = binding.MIMEYAML MIMEYAML2 = binding.MIMEYAML2 MIMETOML = binding.MIMETOML + MIMEPROTOBUF = binding.MIMEPROTOBUF ) // BodyBytesKey indicates a default body bytes key. @@ -1280,14 +1281,15 @@ func (c *Context) Stream(step func(w io.Writer) bool) bool { // Negotiate contains all negotiations data. type Negotiate struct { - Offered []string - HTMLName string - HTMLData any - JSONData any - XMLData any - YAMLData any - Data any - TOMLData any + Offered []string + HTMLName string + HTMLData any + JSONData any + XMLData any + YAMLData any + Data any + TOMLData any + PROTOBUFData any } // Negotiate calls different Render according to acceptable Accept format. @@ -1313,6 +1315,10 @@ func (c *Context) Negotiate(code int, config Negotiate) { data := chooseData(config.TOMLData, config.Data) c.TOML(code, data) + case binding.MIMEPROTOBUF: + data := chooseData(config.PROTOBUFData, config.Data) + c.ProtoBuf(code, data) + default: c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) //nolint: errcheck } diff --git a/context_test.go b/context_test.go index e6b7519e..26106129 100644 --- a/context_test.go +++ b/context_test.go @@ -1628,6 +1628,32 @@ func TestContextNegotiationWithHTML(t *testing.T) { assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } +func TestContextNegotiationWithPROTOBUF(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodPost, "/", nil) + + reps := []int64{int64(1), int64(2)} + label := "test" + data := &testdata.Test{ + Label: &label, + Reps: reps, + } + + c.Negotiate(http.StatusCreated, Negotiate{ + Offered: []string{MIMEPROTOBUF, MIMEJSON, MIMEXML}, + Data: data, + }) + + // Marshal original data for comparison + protoData, err := proto.Marshal(data) + require.NoError(t, err) + + assert.Equal(t, http.StatusCreated, w.Code) + assert.Equal(t, string(protoData), w.Body.String()) + assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) +} + func TestContextNegotiationNotSupport(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) From c3d5a28ed6d3849da820195b6774d212bcc038a9 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Fri, 7 Nov 2025 12:01:19 +0800 Subject: [PATCH 103/156] fix(gin): close os.File in RunFd to prevent resource leak (#4422) Co-authored-by: 1911860538 --- gin.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gin.go b/gin.go index 1965a429..38361a4b 100644 --- a/gin.go +++ b/gin.go @@ -593,6 +593,7 @@ func (engine *Engine) RunFd(fd int) (err error) { } f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd)) + defer f.Close() listener, err := net.FileListener(f) if err != nil { return From d1bcabc7ee4cbd3631c71f5a25da14bf1b84a0d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 15 Nov 2025 12:32:23 +0800 Subject: [PATCH 104/156] chore(deps): bump golangci/golangci-lint-action in the actions group (#4431) Bumps the actions group with 1 update: [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action). Updates `golangci/golangci-lint-action` from 8 to 9 - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v8...v9) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: '9' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index eb0d7c26..d74a8bb4 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -24,7 +24,7 @@ jobs: with: go-version: "^1" - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v8 + uses: golangci/golangci-lint-action@v9 with: version: v2.1.6 args: --verbose From a9401cd238378d6ecaf4fe90f7c825f624bd8ea1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 15 Nov 2025 12:32:48 +0800 Subject: [PATCH 105/156] chore(deps): bump github.com/quic-go/quic-go from 0.55.0 to 0.56.0 (#4430) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.55.0 to 0.56.0. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Commits](https://github.com/quic-go/quic-go/compare/v0.55.0...v0.56.0) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go dependency-version: 0.56.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 ++---- go.sum | 22 +++++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index beabc954..ab2fc86a 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/quic-go/quic-go v0.55.0 + github.com/quic-go/quic-go v0.56.0 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 golang.org/x/net v0.46.0 @@ -28,6 +28,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -35,10 +36,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.20.0 // indirect golang.org/x/crypto v0.43.0 // indirect - golang.org/x/mod v0.28.0 // indirect - golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.37.0 // indirect golang.org/x/text v0.30.0 // indirect - golang.org/x/tools v0.37.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ed1361a9..5650d5c9 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,7 @@ github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2N github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -32,6 +33,10 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -46,8 +51,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= -github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= +github.com/quic-go/quic-go v0.56.0 h1:q/TW+OLismmXAehgFLczhCDTYB3bFmua4D9lsNBWxvY= +github.com/quic-go/quic-go v0.56.0/go.mod h1:9gx5KsFQtw2oZ6GZTyh+7YEvOxWCL9WZAepnHxgAo6c= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -69,23 +76,20 @@ golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= -golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= -golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= -golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 19c2d5c0d1d096e1014fb7be62116ee9025d0f56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 15 Nov 2025 12:42:25 +0800 Subject: [PATCH 106/156] chore(deps): bump golang.org/x/net from 0.46.0 to 0.47.0 (#4433) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.46.0 to 0.47.0. - [Commits](https://github.com/golang/net/compare/v0.46.0...v0.47.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.47.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index ab2fc86a..c756803a 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/quic-go/quic-go v0.56.0 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 - golang.org/x/net v0.46.0 + golang.org/x/net v0.47.0 google.golang.org/protobuf v1.36.10 ) @@ -35,8 +35,8 @@ require ( github.com/quic-go/qpack v0.5.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.43.0 // indirect - golang.org/x/sys v0.37.0 // indirect - golang.org/x/text v0.30.0 // indirect + golang.org/x/crypto v0.44.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5650d5c9..1ef1ad18 100644 --- a/go.sum +++ b/go.sum @@ -74,15 +74,15 @@ go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= From fb27ef26c2fdfe25344b4c039d8a53551f9e912c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 15 Nov 2025 19:21:42 +0800 Subject: [PATCH 107/156] ci(lint): refactor test assertions and linter configuration (#4436) - Update golangci-lint GitHub Action version from v2.1.6 to v2.6 - Remove the gci formatter and exclusions for third_party, builtin, and examples from the linter config - Fix argument order for assert.EqualValues and assert.Exactly in context tests for clarity - Refactor integration tests to build response strings using strings.Builder instead of direct concatenation for improved performance and readability Signed-off-by: appleboy --- .github/workflows/gin.yml | 2 +- .golangci.yml | 4 ---- context_test.go | 4 ++-- gin_integration_test.go | 16 ++++++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index d74a8bb4..8bca364d 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -26,7 +26,7 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v9 with: - version: v2.1.6 + version: v2.6 args: --verbose test: needs: lint diff --git a/.golangci.yml b/.golangci.yml index d8887062..318eb811 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -68,7 +68,6 @@ linters: - examples$ formatters: enable: - - gci - gofmt - gofumpt - goimports @@ -80,7 +79,4 @@ formatters: exclusions: generated: lax paths: - - third_party$ - - builtin$ - - examples$ - gin.go diff --git a/context_test.go b/context_test.go index 26106129..126646fc 100644 --- a/context_test.go +++ b/context_test.go @@ -292,7 +292,7 @@ func TestContextReset(t *testing.T) { assert.Empty(t, c.Errors.Errors()) assert.Empty(t, c.Errors.ByType(ErrorTypeAny)) assert.Empty(t, c.Params) - assert.EqualValues(t, c.index, -1) + assert.EqualValues(t, -1, c.index) assert.Equal(t, c.Writer.(*responseWriter), &c.writermem) } @@ -384,7 +384,7 @@ func TestContextSetGetValues(t *testing.T) { c.Set("intInterface", a) assert.Exactly(t, "this is a string", c.MustGet("string").(string)) - assert.Exactly(t, c.MustGet("int32").(int32), int32(-42)) + assert.Exactly(t, int32(-42), c.MustGet("int32").(int32)) assert.Exactly(t, int64(42424242424242), c.MustGet("int64").(int64)) assert.Exactly(t, uint64(42), c.MustGet("uint64").(uint64)) assert.InDelta(t, float32(4.2), c.MustGet("float32").(float32), 0.01) diff --git a/gin_integration_test.go b/gin_integration_test.go index c032d837..e040993a 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -16,6 +16,7 @@ import ( "os" "path/filepath" "runtime" + "strings" "sync" "testing" "time" @@ -261,10 +262,11 @@ func TestUnixSocket(t *testing.T) { fmt.Fprint(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) - var response string + var responseBuilder strings.Builder for scanner.Scan() { - response += scanner.Text() + responseBuilder.WriteString(scanner.Text()) } + response := responseBuilder.String() assert.Contains(t, response, "HTTP/1.0 200", "should get a 200") assert.Contains(t, response, "it worked", "resp body should match") } @@ -322,10 +324,11 @@ func TestFileDescriptor(t *testing.T) { fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) - var response string + var responseBuilder strings.Builder for scanner.Scan() { - response += scanner.Text() + responseBuilder.WriteString(scanner.Text()) } + response := responseBuilder.String() assert.Contains(t, response, "HTTP/1.0 200", "should get a 200") assert.Contains(t, response, "it worked", "resp body should match") } @@ -354,10 +357,11 @@ func TestListener(t *testing.T) { fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) - var response string + var responseBuilder strings.Builder for scanner.Scan() { - response += scanner.Text() + responseBuilder.WriteString(scanner.Text()) } + response := responseBuilder.String() assert.Contains(t, response, "HTTP/1.0 200", "should get a 200") assert.Contains(t, response, "it worked", "resp body should match") } From a85ef5ce4d0cda8834c59c855068ed48b51192d1 Mon Sep 17 00:00:00 2001 From: efcking Date: Sat, 15 Nov 2025 19:22:18 +0800 Subject: [PATCH 108/156] refactor: use b.Loop() to simplify the code and improve performance (#4432) Signed-off-by: efcking --- binding/default_validator_benchmark_test.go | 3 +-- binding/form_mapping_benchmark_test.go | 4 ++-- path_test.go | 6 +++--- utils_test.go | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/binding/default_validator_benchmark_test.go b/binding/default_validator_benchmark_test.go index 44547412..a7b22696 100644 --- a/binding/default_validator_benchmark_test.go +++ b/binding/default_validator_benchmark_test.go @@ -18,9 +18,8 @@ func BenchmarkSliceValidationError(b *testing.B) { } b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { if len(e.Error()) == 0 { b.Errorf("error") } diff --git a/binding/form_mapping_benchmark_test.go b/binding/form_mapping_benchmark_test.go index 5788133f..d40699e9 100644 --- a/binding/form_mapping_benchmark_test.go +++ b/binding/form_mapping_benchmark_test.go @@ -31,7 +31,7 @@ type structFull struct { func BenchmarkMapFormFull(b *testing.B) { var s structFull - for i := 0; i < b.N; i++ { + for b.Loop() { err := mapForm(&s, form) if err != nil { b.Fatalf("Error on a form mapping") @@ -54,7 +54,7 @@ type structName struct { func BenchmarkMapFormName(b *testing.B) { var s structName - for i := 0; i < b.N; i++ { + for b.Loop() { err := mapForm(&s, form) if err != nil { b.Fatalf("Error on a form mapping") diff --git a/path_test.go b/path_test.go index 2269b78e..7d86086f 100644 --- a/path_test.go +++ b/path_test.go @@ -94,7 +94,7 @@ func TestPathCleanMallocs(t *testing.T) { func BenchmarkPathClean(b *testing.B) { b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { for _, test := range cleanTests { cleanPath(test.path) } @@ -134,10 +134,10 @@ func TestPathCleanLong(t *testing.T) { func BenchmarkPathCleanLong(b *testing.B) { cleanTests := genLongPaths() - b.ResetTimer() + b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { for _, test := range cleanTests { cleanPath(test.path) } diff --git a/utils_test.go b/utils_test.go index dc9886d7..8bcf00e4 100644 --- a/utils_test.go +++ b/utils_test.go @@ -19,7 +19,7 @@ func init() { } func BenchmarkParseAccept(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8") } } From 58135f06cf206a9ff713eb14150ef04a05b031d4 Mon Sep 17 00:00:00 2001 From: AtoriUzawa <110576658+AtoriUzawa@users.noreply.github.com> Date: Sat, 15 Nov 2025 19:46:45 +0800 Subject: [PATCH 109/156] docs(context): add example comments for ShouldBind* methods (#4428) - Added detailed example for ShouldBindJSON - Added consistent descriptive comments for ShouldBindXML, ShouldBindQuery, ShouldBindYAML, ShouldBindTOML, ShouldBindPlain, ShouldBindHeader, ShouldBindUri - Makes binding method usage clearer for new users --- context.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/context.go b/context.go index d5ef8b81..059e85a8 100644 --- a/context.go +++ b/context.go @@ -830,41 +830,71 @@ func (c *Context) ShouldBind(obj any) error { } // ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON). +// +// Example: +// +// POST /user +// Content-Type: application/json +// +// Request Body: +// { +// "name": "Manu", +// "age": 20 +// } +// +// type User struct { +// Name string `json:"name"` +// Age int `json:"age"` +// } +// +// var user User +// if err := c.ShouldBindJSON(&user); err != nil { +// c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) +// return +// } +// c.JSON(http.StatusOK, user) func (c *Context) ShouldBindJSON(obj any) error { return c.ShouldBindWith(obj, binding.JSON) } // ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML). +// It works like ShouldBindJSON but binds the request body as XML data. func (c *Context) ShouldBindXML(obj any) error { return c.ShouldBindWith(obj, binding.XML) } // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). +// It works like ShouldBindJSON but binds query parameters from the URL. func (c *Context) ShouldBindQuery(obj any) error { return c.ShouldBindWith(obj, binding.Query) } // ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML). +// It works like ShouldBindJSON but binds the request body as YAML data. func (c *Context) ShouldBindYAML(obj any) error { return c.ShouldBindWith(obj, binding.YAML) } // ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML). +// It works like ShouldBindJSON but binds the request body as TOML data. func (c *Context) ShouldBindTOML(obj any) error { return c.ShouldBindWith(obj, binding.TOML) } // ShouldBindPlain is a shortcut for c.ShouldBindWith(obj, binding.Plain). +// It works like ShouldBindJSON but binds plain text data from the request body. func (c *Context) ShouldBindPlain(obj any) error { return c.ShouldBindWith(obj, binding.Plain) } // ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header). +// It works like ShouldBindJSON but binds values from HTTP headers. func (c *Context) ShouldBindHeader(obj any) error { return c.ShouldBindWith(obj, binding.Header) } // ShouldBindUri binds the passed struct pointer using the specified binding engine. +// It works like ShouldBindJSON but binds parameters from the URI. func (c *Context) ShouldBindUri(obj any) error { m := make(map[string][]string, len(c.Params)) for _, v := range c.Params { From 93ff771e6dbf10e432864b30f3719ac5c84a4d4a Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 15 Nov 2025 23:03:32 +0800 Subject: [PATCH 110/156] ci(sec): improve type safety and server organization in HTTP middleware (#4437) - Update linting configuration to exclude G115 gosec check instead of including specific checks - Add the safeInt8 helper for safer type conversions and use it to prevent int8 overflow in middleware handler execution - Group related constants and variables together for better organization in gin.go - Refactor HTTP server instantiation to use a dedicated http.Server object for all Run methods - Add the safeUint16 helper and use it to safely handle conversions in tree node functions to prevent uint16 overflow Signed-off-by: appleboy --- .golangci.yml | 11 ++--------- context.go | 10 +++++++++- gin.go | 38 ++++++++++++++++++++++++++++---------- tree.go | 13 +++++++++++-- 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 318eb811..f0898565 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -18,15 +18,8 @@ linters: - wastedassign settings: gosec: - includes: - - G102 - - G106 - - G108 - - G109 - - G111 - - G112 - - G201 - - G203 + excludes: + - G115 perfsprint: int-conversion: true err-error: true diff --git a/context.go b/context.go index 059e85a8..112f0ee0 100644 --- a/context.go +++ b/context.go @@ -55,6 +55,14 @@ const ContextRequestKey ContextKeyType = 0 // abortIndex represents a typical value used in abort functions. const abortIndex int8 = math.MaxInt8 >> 1 +// safeInt8 converts int to int8 safely, capping at math.MaxInt8 +func safeInt8(n int) int8 { + if n > math.MaxInt8 { + return math.MaxInt8 + } + return int8(n) +} + // Context is the most important part of gin. It allows us to pass variables between middleware, // manage the flow, validate the JSON of a request and render a JSON response for example. type Context struct { @@ -186,7 +194,7 @@ func (c *Context) FullPath() string { // See example in GitHub. func (c *Context) Next() { c.index++ - for c.index < int8(len(c.handlers)) { + for c.index < safeInt8(len(c.handlers)) { if c.handlers[c.index] != nil { c.handlers[c.index](c) } diff --git a/gin.go b/gin.go index 38361a4b..4d0c7ec0 100644 --- a/gin.go +++ b/gin.go @@ -23,10 +23,12 @@ import ( "golang.org/x/net/http2/h2c" ) -const defaultMultipartMemory = 32 << 20 // 32 MB -const escapedColon = "\\:" -const colon = ":" -const backslash = "\\" +const ( + defaultMultipartMemory = 32 << 20 // 32 MB + escapedColon = "\\:" + colon = ":" + backslash = "\\" +) var ( default404Body = []byte("404 page not found") @@ -46,8 +48,10 @@ var defaultTrustedCIDRs = []*net.IPNet{ }, } -var regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+") -var regRemoveRepeatedChar = regexp.MustCompile("/{2,}") +var ( + regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+") + regRemoveRepeatedChar = regexp.MustCompile("/{2,}") +) // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) @@ -537,7 +541,11 @@ func (engine *Engine) Run(addr ...string) (err error) { engine.updateRouteTrees() address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) - err = http.ListenAndServe(address, engine.Handler()) + server := &http.Server{ // #nosec G112 + Addr: address, + Handler: engine.Handler(), + } + err = server.ListenAndServe() return } @@ -553,7 +561,11 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) { "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } - err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler()) + server := &http.Server{ // #nosec G112 + Addr: addr, + Handler: engine.Handler(), + } + err = server.ListenAndServeTLS(certFile, keyFile) return } @@ -576,7 +588,10 @@ func (engine *Engine) RunUnix(file string) (err error) { defer listener.Close() defer os.Remove(file) - err = http.Serve(listener, engine.Handler()) + server := &http.Server{ // #nosec G112 + Handler: engine.Handler(), + } + err = server.Serve(listener) return } @@ -630,7 +645,10 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) { "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } - err = http.Serve(listener, engine.Handler()) + server := &http.Server{ // #nosec G112 + Handler: engine.Handler(), + } + err = server.Serve(listener) return } diff --git a/tree.go b/tree.go index bcc83502..eff07734 100644 --- a/tree.go +++ b/tree.go @@ -5,6 +5,7 @@ package gin import ( + "math" "net/url" "strings" "unicode" @@ -77,14 +78,22 @@ func (n *node) addChild(child *node) { } } +// safeUint16 converts int to uint16 safely, capping at math.MaxUint16 +func safeUint16(n int) uint16 { + if n > math.MaxUint16 { + return math.MaxUint16 + } + return uint16(n) +} + func countParams(path string) uint16 { colons := strings.Count(path, ":") stars := strings.Count(path, "*") - return uint16(colons + stars) + return safeUint16(colons + stars) } func countSections(path string) uint16 { - return uint16(strings.Count(path, "/")) + return safeUint16(strings.Count(path, "/")) } type nodeType uint8 From 5fad976b372e381312f8de69f0969f1284d229d3 Mon Sep 17 00:00:00 2001 From: Pawan Kalyan <91543630+pawannn@users.noreply.github.com> Date: Sun, 16 Nov 2025 06:52:07 +0530 Subject: [PATCH 111/156] fix(gin): literal colon routes not working with engine.Handler() (#4415) * fix: call updateRouteTrees in ServeHTTP using sync.Once to support literal colon routes in all usage scenarios (#4413) * chore: fixed golangci-lint issue in test cases for literal colon * fix: gofumpt formatting issue * fix: gofumpt issue in gin.go * chore: updated routeTreesUpdated comments * chore: removed unused variable and updated TestUpdateRouteTreesCalledOnce testcase * chore: moved tests from literal_colon_test.go into gin_test.go --------- Co-authored-by: pawannn --- gin.go | 8 +++++ gin_test.go | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/gin.go b/gin.go index 4d0c7ec0..d71086d1 100644 --- a/gin.go +++ b/gin.go @@ -98,6 +98,10 @@ const ( type Engine struct { RouterGroup + // routeTreesUpdated ensures that the initialization or update of the route trees + // (used for routing HTTP requests) happens only once, even if called multiple times concurrently. + routeTreesUpdated sync.Once + // RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a // handler for the path with (without) the trailing slash exists. // For example if /foo/ is requested but a route only exists for /foo, the @@ -654,6 +658,10 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) { // ServeHTTP conforms to the http.Handler interface. func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { + engine.routeTreesUpdated.Do(func() { + engine.updateRouteTrees() + }) + c := engine.pool.Get().(*Context) c.writermem.reset(w) c.Request = req diff --git a/gin_test.go b/gin_test.go index be076537..cee1f3cc 100644 --- a/gin_test.go +++ b/gin_test.go @@ -913,3 +913,102 @@ func TestMethodNotAllowedNoRoute(t *testing.T) { assert.NotPanics(t, func() { g.ServeHTTP(resp, req) }) assert.Equal(t, http.StatusNotFound, resp.Code) } + +// Test the fix for https://github.com/gin-gonic/gin/pull/4415 +func TestLiteralColonWithRun(t *testing.T) { + SetMode(TestMode) + router := New() + + router.GET(`/test\:action`, func(c *Context) { + c.JSON(http.StatusOK, H{"path": "literal_colon"}) + }) + + router.updateRouteTrees() + + w := httptest.NewRecorder() + + req, _ := http.NewRequest(http.MethodGet, "/test:action", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "literal_colon") +} + +func TestLiteralColonWithDirectServeHTTP(t *testing.T) { + SetMode(TestMode) + router := New() + + router.GET(`/test\:action`, func(c *Context) { + c.JSON(http.StatusOK, H{"path": "literal_colon"}) + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/test:action", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "literal_colon") +} + +func TestLiteralColonWithHandler(t *testing.T) { + SetMode(TestMode) + router := New() + + router.GET(`/test\:action`, func(c *Context) { + c.JSON(http.StatusOK, H{"path": "literal_colon"}) + }) + + handler := router.Handler() + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/test:action", nil) + handler.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "literal_colon") +} + +func TestLiteralColonWithHTTPServer(t *testing.T) { + SetMode(TestMode) + router := New() + + router.GET(`/test\:action`, func(c *Context) { + c.JSON(http.StatusOK, H{"path": "literal_colon"}) + }) + + router.GET("/test/:param", func(c *Context) { + c.JSON(http.StatusOK, H{"param": c.Param("param")}) + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/test:action", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "literal_colon") + + w2 := httptest.NewRecorder() + req2, _ := http.NewRequest(http.MethodGet, "/test/foo", nil) + router.ServeHTTP(w2, req2) + + assert.Equal(t, http.StatusOK, w2.Code) + assert.Contains(t, w2.Body.String(), "foo") +} + +// Test that updateRouteTrees is called only once +func TestUpdateRouteTreesCalledOnce(t *testing.T) { + SetMode(TestMode) + router := New() + + router.GET(`/test\:action`, func(c *Context) { + c.String(http.StatusOK, "ok") + }) + + for range 5 { + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/test:action", nil) + router.ServeHTTP(w, req) + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "ok", w.Body.String()) + } +} From e88fc8927a52b74f55bec0351604a56ac0aa1c51 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 18 Nov 2025 23:05:54 +0800 Subject: [PATCH 112/156] ci(sec): schedule Trivy security scans to run daily at midnight UTC (#4439) - Change Trivy scan schedule from quarterly to daily runs at 00:00 UTC Signed-off-by: appleboy --- .github/workflows/trivy-scan.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index 12830633..da31dd59 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -8,9 +8,8 @@ on: branches: - master schedule: - # Run every 3 months (quarterly) on the 1st day at 00:00 UTC - # Months: January (1), April (4), July (7), October (10) - - cron: '0 0 1 1,4,7,10 *' + # Run daily at 00:00 UTC + - cron: '0 0 * * *' workflow_dispatch: # Allow manual trigger permissions: From ecb3f7b5e2f3915bf1db240ed5eee572f8dbea36 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 23 Nov 2025 11:46:13 +0800 Subject: [PATCH 113/156] chore(deps): upgrade golang.org/x/crypto to v0.45.0 (#4449) - Update golang.org/x/crypto dependency to version 0.45.0 1. https://avd.aquasec.com/nvd/cve-2025-47914 2. https://avd.aquasec.com/nvd/cve-2025-58181 Signed-off-by: appleboy --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c756803a..628ab4c5 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/quic-go/qpack v0.5.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.44.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 1ef1ad18..90d5e526 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 440eb14ab8ed503d4a31dfecc9946a90cd73b955 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Wed, 26 Nov 2025 23:32:18 +0800 Subject: [PATCH 114/156] perf(path): replace regex with custom functions in redirectTrailingSlash (#4414) * perf: replace regex with custom functions in redirectTrailingSlash * perf: use more efficient removeRepeatedChar for path slash handling --------- Co-authored-by: 1911860538 --- gin.go | 21 ++++++++++++-------- path.go | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++- path_test.go | 47 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 9 deletions(-) diff --git a/gin.go b/gin.go index d71086d1..16067e55 100644 --- a/gin.go +++ b/gin.go @@ -11,7 +11,6 @@ import ( "net/http" "os" "path" - "regexp" "strings" "sync" @@ -48,11 +47,6 @@ var defaultTrustedCIDRs = []*net.IPNet{ }, } -var ( - regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+") - regRemoveRepeatedChar = regexp.MustCompile("/{2,}") -) - // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) @@ -776,8 +770,8 @@ func redirectTrailingSlash(c *Context) { req := c.Request p := req.URL.Path if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." { - prefix = regSafePrefix.ReplaceAllString(prefix, "") - prefix = regRemoveRepeatedChar.ReplaceAllString(prefix, "/") + prefix = sanitizePathChars(prefix) + prefix = removeRepeatedChar(prefix, '/') p = prefix + "/" + req.URL.Path } @@ -788,6 +782,17 @@ func redirectTrailingSlash(c *Context) { redirectRequest(c) } +// sanitizePathChars removes unsafe characters from path strings, +// keeping only ASCII letters, ASCII numbers, forward slashes, and hyphens. +func sanitizePathChars(s string) string { + return strings.Map(func(r rune) rune { + if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '/' || r == '-' { + return r + } + return -1 + }, s) +} + func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { req := c.Request rPath := req.URL.Path diff --git a/path.go b/path.go index 82438c13..3b67caa9 100644 --- a/path.go +++ b/path.go @@ -5,6 +5,8 @@ package gin +const stackBufSize = 128 + // cleanPath is the URL version of path.Clean, it returns a canonical URL path // for p, eliminating . and .. elements. // @@ -19,7 +21,6 @@ package gin // // If the result of this process is an empty string, "/" is returned. func cleanPath(p string) string { - const stackBufSize = 128 // Turn empty string into "/" if p == "" { return "/" @@ -148,3 +149,55 @@ func bufApp(buf *[]byte, s string, w int, c byte) { } b[w] = c } + +// removeRepeatedChar removes multiple consecutive 'char's from a string. +// if s == "/a//b///c////" && char == '/', it returns "/a/b/c/" +func removeRepeatedChar(s string, char byte) string { + // Check if there are any consecutive chars + hasRepeatedChar := false + for i := 1; i < len(s); i++ { + if s[i] == char && s[i-1] == char { + hasRepeatedChar = true + break + } + } + if !hasRepeatedChar { + return s + } + + // Reasonably sized buffer on stack to avoid allocations in the common case. + buf := make([]byte, 0, stackBufSize) + + // Invariants: + // reading from s; r is index of next byte to process. + // writing to buf; w is index of next byte to write. + r := 0 + w := 0 + + for n := len(s); r < n; { + if s[r] == char { + // Write the first char + bufApp(&buf, s, w, char) + w++ + r++ + + // Skip all consecutive chars + for r < n && s[r] == char { + r++ + } + } else { + // Copy non-char character + bufApp(&buf, s, w, s[r]) + w++ + r++ + } + } + + // If the original string was not modified (or only shortened at the end), + // return the respective substring of the original string. + // Otherwise, return a new string from the buffer. + if len(buf) == 0 { + return s[:w] + } + return string(buf[:w]) +} diff --git a/path_test.go b/path_test.go index 7d86086f..eba1be08 100644 --- a/path_test.go +++ b/path_test.go @@ -143,3 +143,50 @@ func BenchmarkPathCleanLong(b *testing.B) { } } } + +func TestRemoveRepeatedChar(t *testing.T) { + testCases := []struct { + name string + str string + char byte + want string + }{ + { + name: "empty", + str: "", + char: 'a', + want: "", + }, + { + name: "noSlash", + str: "abc", + char: ',', + want: "abc", + }, + { + name: "withSlash", + str: "/a/b/c/", + char: '/', + want: "/a/b/c/", + }, + { + name: "withRepeatedSlashes", + str: "/a//b///c////", + char: '/', + want: "/a/b/c/", + }, + { + name: "threeSlashes", + str: "///", + char: '/', + want: "/", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + res := removeRepeatedChar(tc.str, tc.char) + assert.Equal(t, tc.want, res) + }) + } +} From 52ecf029bd2e9b4d2652f96dd2b753f8bc6b6e95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 23:33:08 +0800 Subject: [PATCH 115/156] chore(deps): bump actions/checkout from 5 to 6 in the actions group (#4446) Bumps the actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 5 to 6 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bo-Yi Wu --- .github/workflows/codeql.yml | 2 +- .github/workflows/gin.yml | 4 ++-- .github/workflows/goreleaser.yml | 2 +- .github/workflows/trivy-scan.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9ec3700e..f287c265 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 8bca364d..4e3b8753 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Go @@ -61,7 +61,7 @@ jobs: cache: false - name: Checkout Code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ github.ref }} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 37dfb5bb..0098b952 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Go diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index da31dd59..b86aed7f 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 From 771dcc6476d7bc6abb9ec0235ecefa4d38fe6fb0 Mon Sep 17 00:00:00 2001 From: Aeddis Desauw <89919264+ldesauw@users.noreply.github.com> Date: Thu, 27 Nov 2025 10:55:34 +0100 Subject: [PATCH 116/156] feat(gin): add option to use escaped path (#4420) Co-authored-by: Bo-Yi Wu --- gin.go | 16 ++++++++++++++-- gin_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/gin.go b/gin.go index 16067e55..2e033bf3 100644 --- a/gin.go +++ b/gin.go @@ -135,10 +135,16 @@ type Engine struct { AppEngine bool // UseRawPath if enabled, the url.RawPath will be used to find parameters. + // The RawPath is only a hint, EscapedPath() should be use instead. (https://pkg.go.dev/net/url@master#URL) + // Only use RawPath if you know what you are doing. UseRawPath bool + // UseEscapedPath if enable, the url.EscapedPath() will be used to find parameters + // It overrides UseRawPath + UseEscapedPath bool + // UnescapePathValues if true, the path value will be unescaped. - // If UseRawPath is false (by default), the UnescapePathValues effectively is true, + // If UseRawPath and UseEscapedPath are false (by default), the UnescapePathValues effectively is true, // as url.Path gonna be used, which is already unescaped. UnescapePathValues bool @@ -191,6 +197,7 @@ var _ IRouter = (*Engine)(nil) // - HandleMethodNotAllowed: false // - ForwardedByClientIP: true // - UseRawPath: false +// - UseEscapedPath: false // - UnescapePathValues: true func New(opts ...OptionFunc) *Engine { debugPrintWARNINGNew() @@ -208,6 +215,7 @@ func New(opts ...OptionFunc) *Engine { RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"}, TrustedPlatform: defaultPlatform, UseRawPath: false, + UseEscapedPath: false, RemoveExtraSlash: false, UnescapePathValues: true, MaxMultipartMemory: defaultMultipartMemory, @@ -683,7 +691,11 @@ func (engine *Engine) handleHTTPRequest(c *Context) { httpMethod := c.Request.Method rPath := c.Request.URL.Path unescape := false - if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { + + if engine.UseEscapedPath { + rPath = c.Request.URL.EscapedPath() + unescape = engine.UnescapePathValues + } else if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { rPath = c.Request.URL.RawPath unescape = engine.UnescapePathValues } diff --git a/gin_test.go b/gin_test.go index cee1f3cc..21bf71d8 100644 --- a/gin_test.go +++ b/gin_test.go @@ -720,6 +720,55 @@ func TestEngineHandleContextPreventsMiddlewareReEntry(t *testing.T) { assert.Equal(t, int64(1), handlerCounterV2) } +func TestEngineHandleContextUseEscapedPathPercentEncoded(t *testing.T) { + r := New() + r.UseEscapedPath = true + r.UnescapePathValues = false + + r.GET("/v1/:path", func(c *Context) { + // Path is Escaped, the %25 is not interpreted as % + assert.Equal(t, "foo%252Fbar", c.Param("path")) + c.Status(http.StatusOK) + }) + + req := httptest.NewRequest(http.MethodGet, "/v1/foo%252Fbar", nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) +} + +func TestEngineHandleContextUseRawPathPercentEncoded(t *testing.T) { + r := New() + r.UseRawPath = true + r.UnescapePathValues = false + + r.GET("/v1/:path", func(c *Context) { + // Path is used, the %25 is interpreted as % + assert.Equal(t, "foo%2Fbar", c.Param("path")) + c.Status(http.StatusOK) + }) + + req := httptest.NewRequest(http.MethodGet, "/v1/foo%252Fbar", nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) +} + +func TestEngineHandleContextUseEscapedPathOverride(t *testing.T) { + r := New() + r.UseEscapedPath = true + r.UseRawPath = true + r.UnescapePathValues = false + + r.GET("/v1/:path", func(c *Context) { + assert.Equal(t, "foo%25bar", c.Param("path")) + c.Status(http.StatusOK) + }) + + assert.NotPanics(t, func() { + w := PerformRequest(r, http.MethodGet, "/v1/foo%25bar") + assert.Equal(t, 200, w.Code) + }) +} + func TestPrepareTrustedCIRDsWith(t *testing.T) { r := New() From c358d5656d0feb8b310d4ec379bccde46ccc8cc7 Mon Sep 17 00:00:00 2001 From: Milad Date: Thu, 27 Nov 2025 18:31:57 +0330 Subject: [PATCH 117/156] test(gin): Add comprehensive test coverage for ginS package (#4442) * test(ginS): add comprehensive test coverage for ginS package Improve test coverage for ginS package by adding 18 test functions covering HTTP methods, routing, middleware, static files, and templates. * use http.Method* constants instead of raw strings in gins_test.go * copyright updated in gins_test.go --------- Co-authored-by: Bo-Yi Wu --- ginS/gins_test.go | 246 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 ginS/gins_test.go diff --git a/ginS/gins_test.go b/ginS/gins_test.go new file mode 100644 index 00000000..ffde85d2 --- /dev/null +++ b/ginS/gins_test.go @@ -0,0 +1,246 @@ +// Copyright 2025 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package ginS + +import ( + "html/template" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func init() { + gin.SetMode(gin.TestMode) +} + +func TestGET(t *testing.T) { + GET("/test", func(c *gin.Context) { + c.String(http.StatusOK, "test") + }) + + req := httptest.NewRequest(http.MethodGet, "/test", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "test", w.Body.String()) +} + +func TestPOST(t *testing.T) { + POST("/post", func(c *gin.Context) { + c.String(http.StatusCreated, "created") + }) + + req := httptest.NewRequest(http.MethodPost, "/post", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusCreated, w.Code) + assert.Equal(t, "created", w.Body.String()) +} + +func TestPUT(t *testing.T) { + PUT("/put", func(c *gin.Context) { + c.String(http.StatusOK, "updated") + }) + + req := httptest.NewRequest(http.MethodPut, "/put", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "updated", w.Body.String()) +} + +func TestDELETE(t *testing.T) { + DELETE("/delete", func(c *gin.Context) { + c.String(http.StatusOK, "deleted") + }) + + req := httptest.NewRequest(http.MethodDelete, "/delete", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "deleted", w.Body.String()) +} + +func TestPATCH(t *testing.T) { + PATCH("/patch", func(c *gin.Context) { + c.String(http.StatusOK, "patched") + }) + + req := httptest.NewRequest(http.MethodPatch, "/patch", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "patched", w.Body.String()) +} + +func TestOPTIONS(t *testing.T) { + OPTIONS("/options", func(c *gin.Context) { + c.String(http.StatusOK, "options") + }) + + req := httptest.NewRequest(http.MethodOptions, "/options", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "options", w.Body.String()) +} + +func TestHEAD(t *testing.T) { + HEAD("/head", func(c *gin.Context) { + c.String(http.StatusOK, "head") + }) + + req := httptest.NewRequest(http.MethodHead, "/head", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestAny(t *testing.T) { + Any("/any", func(c *gin.Context) { + c.String(http.StatusOK, "any") + }) + + req := httptest.NewRequest(http.MethodGet, "/any", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "any", w.Body.String()) +} + +func TestHandle(t *testing.T) { + Handle(http.MethodGet, "/handle", func(c *gin.Context) { + c.String(http.StatusOK, "handle") + }) + + req := httptest.NewRequest(http.MethodGet, "/handle", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "handle", w.Body.String()) +} + +func TestGroup(t *testing.T) { + group := Group("/group") + group.GET("/test", func(c *gin.Context) { + c.String(http.StatusOK, "group test") + }) + + req := httptest.NewRequest(http.MethodGet, "/group/test", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "group test", w.Body.String()) +} + +func TestUse(t *testing.T) { + var middlewareExecuted bool + Use(func(c *gin.Context) { + middlewareExecuted = true + c.Next() + }) + + GET("/middleware-test", func(c *gin.Context) { + c.String(http.StatusOK, "ok") + }) + + req := httptest.NewRequest(http.MethodGet, "/middleware-test", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.True(t, middlewareExecuted) + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestNoRoute(t *testing.T) { + NoRoute(func(c *gin.Context) { + c.String(http.StatusNotFound, "custom 404") + }) + + req := httptest.NewRequest(http.MethodGet, "/nonexistent", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusNotFound, w.Code) + assert.Equal(t, "custom 404", w.Body.String()) +} + +func TestNoMethod(t *testing.T) { + NoMethod(func(c *gin.Context) { + c.String(http.StatusMethodNotAllowed, "method not allowed") + }) + + // This just verifies that NoMethod is callable + // Testing the actual behavior would require a separate engine instance + assert.NotNil(t, engine()) +} + +func TestRoutes(t *testing.T) { + GET("/routes-test", func(c *gin.Context) {}) + + routes := Routes() + assert.NotEmpty(t, routes) + + found := false + for _, route := range routes { + if route.Path == "/routes-test" && route.Method == http.MethodGet { + found = true + break + } + } + assert.True(t, found) +} + +func TestSetHTMLTemplate(t *testing.T) { + tmpl := template.Must(template.New("test").Parse("Hello {{.}}")) + SetHTMLTemplate(tmpl) + + // Verify engine has template set + assert.NotNil(t, engine()) +} + +func TestStaticFile(t *testing.T) { + StaticFile("/static-file", "../testdata/test_file.txt") + + req := httptest.NewRequest(http.MethodGet, "/static-file", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestStatic(t *testing.T) { + Static("/static-dir", "../testdata") + + req := httptest.NewRequest(http.MethodGet, "/static-dir/test_file.txt", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestStaticFS(t *testing.T) { + fs := http.Dir("../testdata") + StaticFS("/static-fs", fs) + + req := httptest.NewRequest(http.MethodGet, "/static-fs/test_file.txt", nil) + w := httptest.NewRecorder() + engine().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} From 63dd3e60cab89c27fb66bce1423bd268d52abad1 Mon Sep 17 00:00:00 2001 From: Yilong Li Date: Thu, 27 Nov 2025 23:20:52 +0800 Subject: [PATCH 118/156] fix(recover): suppress http.ErrAbortHandler in recover (#4336) Co-authored-by: Bo-Yi Wu --- recovery.go | 3 +++ recovery_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/recovery.go b/recovery.go index fdd463f3..e79e118a 100644 --- a/recovery.go +++ b/recovery.go @@ -68,6 +68,9 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { } } } + if e, ok := err.(error); ok && errors.Is(e, http.ErrAbortHandler) { + brokenPipe = true + } if logger != nil { const stackSkip = 3 if brokenPipe { diff --git a/recovery_test.go b/recovery_test.go index 8a9e3475..073f4858 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -142,6 +142,30 @@ func TestPanicWithBrokenPipe(t *testing.T) { } } +// TestPanicWithAbortHandler asserts that recovery handles http.ErrAbortHandler as broken pipe +func TestPanicWithAbortHandler(t *testing.T) { + const expectCode = 204 + + var buf strings.Builder + router := New() + router.Use(RecoveryWithWriter(&buf)) + router.GET("/recovery", func(c *Context) { + // Start writing response + c.Header("X-Test", "Value") + c.Status(expectCode) + + // Panic with ErrAbortHandler which should be treated as broken pipe + panic(http.ErrAbortHandler) + }) + // RUN + w := PerformRequest(router, http.MethodGet, "/recovery") + // TEST + assert.Equal(t, expectCode, w.Code) + out := buf.String() + assert.Contains(t, out, "net/http: abort Handler") + assert.NotContains(t, out, "panic recovered") +} + func TestCustomRecoveryWithWriter(t *testing.T) { errBuffer := new(strings.Builder) buffer := new(strings.Builder) From af6e8b70b8261bb0c99ad094fe552ab92991620a Mon Sep 17 00:00:00 2001 From: appleboy Date: Sun, 30 Nov 2025 11:52:25 +0800 Subject: [PATCH 119/156] chore(deps): upgrade quic-go to v0.57.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix CVE-2025-59530 vulnerability (quic-go Crash Due to Premature HANDSHAKE_DONE Frame) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 628ab4c5..58ec6fc9 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/quic-go/quic-go v0.56.0 + github.com/quic-go/quic-go v0.57.1 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 golang.org/x/net v0.47.0 @@ -32,7 +32,7 @@ require ( github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/qpack v0.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.20.0 // indirect golang.org/x/crypto v0.45.0 // indirect diff --git a/go.sum b/go.sum index 90d5e526..bcdb4493 100644 --- a/go.sum +++ b/go.sum @@ -49,10 +49,10 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= -github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.56.0 h1:q/TW+OLismmXAehgFLczhCDTYB3bFmua4D9lsNBWxvY= -github.com/quic-go/quic-go v0.56.0/go.mod h1:9gx5KsFQtw2oZ6GZTyh+7YEvOxWCL9WZAepnHxgAo6c= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10= +github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 583db590ec2a488ebcf7f8dc6232d11c7db62eac Mon Sep 17 00:00:00 2001 From: Milad Date: Sun, 30 Nov 2025 10:55:46 +0330 Subject: [PATCH 120/156] test(bytesconv): add tests for empty/nil cases (#4454) Co-authored-by: Bo-Yi Wu --- internal/bytesconv/bytesconv_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/internal/bytesconv/bytesconv_test.go b/internal/bytesconv/bytesconv_test.go index 4972ae70..60e28fb4 100644 --- a/internal/bytesconv/bytesconv_test.go +++ b/internal/bytesconv/bytesconv_test.go @@ -41,6 +41,15 @@ func TestBytesToString(t *testing.T) { } } +func TestBytesToStringEmpty(t *testing.T) { + if got := BytesToString([]byte{}); got != "" { + t.Fatalf("BytesToString([]byte{}) = %q; want empty string", got) + } + if got := BytesToString(nil); got != "" { + t.Fatalf("BytesToString(nil) = %q; want empty string", got) + } +} + const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const ( letterIdxBits = 6 // 6 bits to represent a letter index @@ -78,6 +87,16 @@ func TestStringToBytes(t *testing.T) { } } +func TestStringToBytesEmpty(t *testing.T) { + b := StringToBytes("") + if len(b) != 0 { + t.Fatalf(`StringToBytes("") length = %d; want 0`, len(b)) + } + if !bytes.Equal(b, []byte("")) { + t.Fatalf(`StringToBytes("") = %v; want []byte("")`, b) + } +} + // go test -v -run=none -bench=^BenchmarkBytesConv -benchmem=true func BenchmarkBytesConvBytesToStrRaw(b *testing.B) { From f416d1e594a027063e73f66ac873a82113036fd8 Mon Sep 17 00:00:00 2001 From: Wayne Aki <111057868+Planckbaka@users.noreply.github.com> Date: Sun, 30 Nov 2025 15:38:07 +0800 Subject: [PATCH 121/156] test(gin): resolve race conditions in integration tests (#4453) - Implement TestRebuild404Handlers to verify 404 handler chain rebuilding when global middleware is added via Use() - Add waitForServerReady helper with exponential backoff to replace unreliable time.Sleep() calls in integration tests - Fix race conditions in TestRunEmpty, TestRunEmptyWithEnv, and TestRunWithPort by using proper server readiness checks - All tests now pass consistently with -race flag This addresses the empty test function and eliminates flaky test failures caused by insufficient wait times for server startup. Co-authored-by: Bo-Yi Wu --- gin_integration_test.go | 21 ++++++++++++--------- gin_test.go | 23 +++++++++++++++++++++++ test_helpers.go | 31 ++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/gin_integration_test.go b/gin_integration_test.go index e040993a..3ea5fe2f 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -70,9 +70,10 @@ func TestRunEmpty(t *testing.T) { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.Run()) }() - // have to wait for the goroutine to start and run the server - // otherwise the main thread will complete - time.Sleep(5 * time.Millisecond) + + // Wait for server to be ready with exponential backoff + err := waitForServerReady("http://localhost:8080/example", 10) + require.NoError(t, err, "server should start successfully") require.Error(t, router.Run(":8080")) testRequest(t, "http://localhost:8080/example") @@ -213,9 +214,10 @@ func TestRunEmptyWithEnv(t *testing.T) { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.Run()) }() - // have to wait for the goroutine to start and run the server - // otherwise the main thread will complete - time.Sleep(5 * time.Millisecond) + + // Wait for server to be ready with exponential backoff + err := waitForServerReady("http://localhost:3123/example", 10) + require.NoError(t, err, "server should start successfully") require.Error(t, router.Run(":3123")) testRequest(t, "http://localhost:3123/example") @@ -234,9 +236,10 @@ func TestRunWithPort(t *testing.T) { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.Run(":5150")) }() - // have to wait for the goroutine to start and run the server - // otherwise the main thread will complete - time.Sleep(5 * time.Millisecond) + + // Wait for server to be ready with exponential backoff + err := waitForServerReady("http://localhost:5150/example", 10) + require.NoError(t, err, "server should start successfully") require.Error(t, router.Run(":5150")) testRequest(t, "http://localhost:5150/example") diff --git a/gin_test.go b/gin_test.go index 21bf71d8..81343d88 100644 --- a/gin_test.go +++ b/gin_test.go @@ -545,6 +545,29 @@ func TestNoMethodWithoutGlobalHandlers(t *testing.T) { } func TestRebuild404Handlers(t *testing.T) { + var middleware0 HandlerFunc = func(c *Context) {} + var middleware1 HandlerFunc = func(c *Context) {} + + router := New() + + // Initially, allNoRoute should be nil + assert.Nil(t, router.allNoRoute) + + // Set NoRoute handlers + router.NoRoute(middleware0) + assert.Len(t, router.allNoRoute, 1) + assert.Len(t, router.noRoute, 1) + compareFunc(t, router.allNoRoute[0], middleware0) + + // Add Use middleware should trigger rebuild404Handlers + router.Use(middleware1) + assert.Len(t, router.allNoRoute, 2) + assert.Len(t, router.Handlers, 1) + assert.Len(t, router.noRoute, 1) + + // Global middleware should come first + compareFunc(t, router.allNoRoute[0], middleware1) + compareFunc(t, router.allNoRoute[1], middleware0) } func TestNoMethodWithGlobalHandlers(t *testing.T) { diff --git a/test_helpers.go b/test_helpers.go index a1a7c562..20d20032 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -4,7 +4,11 @@ package gin -import "net/http" +import ( + "fmt" + "net/http" + "time" +) // CreateTestContext returns a fresh Engine and a Context associated with it. // This is useful for tests that need to set up a new Gin engine instance @@ -29,3 +33,28 @@ func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) { c.writermem.reset(w) return } + +// waitForServerReady waits for a server to be ready by making HTTP requests +// with exponential backoff. This is more reliable than time.Sleep() for testing. +func waitForServerReady(url string, maxAttempts int) error { + client := &http.Client{ + Timeout: 100 * time.Millisecond, + } + + for i := 0; i < maxAttempts; i++ { + resp, err := client.Get(url) + if err == nil { + resp.Body.Close() + return nil + } + + // Exponential backoff: 10ms, 20ms, 40ms, 80ms, 160ms... + backoff := time.Duration(10*(1< 500*time.Millisecond { + backoff = 500 * time.Millisecond + } + time.Sleep(backoff) + } + + return fmt.Errorf("server at %s did not become ready after %d attempts", url, maxAttempts) +} From fad706f1216e6d12bdd51d28d5a40ec27e6c6453 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 20:09:41 +0800 Subject: [PATCH 122/156] chore(deps): bump github.com/goccy/go-yaml from 1.18.0 to 1.19.0 (#4458) Bumps [github.com/goccy/go-yaml](https://github.com/goccy/go-yaml) from 1.18.0 to 1.19.0. - [Release notes](https://github.com/goccy/go-yaml/releases) - [Changelog](https://github.com/goccy/go-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-yaml/compare/v1.18.0...v1.19.0) --- updated-dependencies: - dependency-name: github.com/goccy/go-yaml dependency-version: 1.19.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 58ec6fc9..3a2b2bf2 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.28.0 github.com/goccy/go-json v0.10.2 - github.com/goccy/go-yaml v1.18.0 + github.com/goccy/go-yaml v1.19.0 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 diff --git a/go.sum b/go.sum index bcdb4493..a487aaaf 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0 github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= -github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/goccy/go-yaml v1.19.0 h1:EmkZ9RIsX+Uq4DYFowegAuJo8+xdX3T/2dwNPXbxEYE= +github.com/goccy/go-yaml v1.19.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= From b917b14ff9d189f16a7492be79d123a47806ee19 Mon Sep 17 00:00:00 2001 From: guonaihong Date: Wed, 3 Dec 2025 19:18:10 +0800 Subject: [PATCH 123/156] fix(binding): empty value error (#2169) * fix empty value error Here is the code that can report an error ```go package main import ( "fmt" "github.com/gin-gonic/gin" "io" "net/http" "os" "time" ) type header struct { Duration time.Duration `header:"duration"` CreateTime time.Time `header:"createTime" time_format:"unix"` } func needFix1() { g := gin.Default() g.GET("/", func(c *gin.Context) { h := header{} err := c.ShouldBindHeader(&h) if err != nil { c.JSON(500, fmt.Sprintf("fail:%s\n", err)) return } c.JSON(200, h) }) g.Run(":8081") } func needFix2() { g := gin.Default() g.GET("/", func(c *gin.Context) { h := header{} err := c.ShouldBindHeader(&h) if err != nil { c.JSON(500, fmt.Sprintf("fail:%s\n", err)) return } c.JSON(200, h) }) g.Run(":8082") } func sendNeedFix1() { // send to needFix1 sendBadData("http://127.0.0.1:8081", "duration") } func sendNeedFix2() { // send to needFix2 sendBadData("http://127.0.0.1:8082", "createTime") } func sendBadData(url, key string) { req, err := http.NewRequest("GET", "http://127.0.0.1:8081", nil) if err != nil { fmt.Printf("err:%s\n", err) return } // Only the key and no value can cause an error req.Header.Add(key, "") rsp, err := http.DefaultClient.Do(req) if err != nil { return } io.Copy(os.Stdout, rsp.Body) rsp.Body.Close() } func main() { go needFix1() go needFix2() time.Sleep(time.Second / 1000 * 200) // 200ms sendNeedFix1() sendNeedFix2() } ``` * modify code * add comment * test(binding): use 'any' alias and require.NoError in form mapping tests - Replace 'interface{}' with 'any' alias in bindTestData struct - Change assert.NoError to require.NoError in TestMappingTimeUnixNano and TestMappingTimeDuration to fail fast on mapping errors --------- Co-authored-by: Bo-Yi Wu --- binding/form_mapping.go | 19 +++++++++++++----- binding/form_mapping_test.go | 39 ++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 1244b522..e76e7510 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -300,6 +300,11 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ } func setWithProperType(val string, value reflect.Value, field reflect.StructField) error { + // If it is a string type, no spaces are removed, and the user data is not modified here + if value.Kind() != reflect.String { + val = strings.TrimSpace(val) + } + switch value.Kind() { case reflect.Int: return setIntField(val, 0, value) @@ -404,6 +409,11 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val timeFormat = time.RFC3339 } + if val == "" { + value.Set(reflect.ValueOf(time.Time{})) + return nil + } + switch tf := strings.ToLower(timeFormat); tf { case "unix", "unixmilli", "unixmicro", "unixnano": tv, err := strconv.ParseInt(val, 10, 64) @@ -427,11 +437,6 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val return nil } - if val == "" { - value.Set(reflect.ValueOf(time.Time{})) - return nil - } - l := time.Local if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC { l = time.UTC @@ -475,6 +480,10 @@ func setSlice(vals []string, value reflect.Value, field reflect.StructField) err } func setTimeDuration(val string, value reflect.Value) error { + if val == "" { + val = "0" + } + d, err := time.ParseDuration(val) if err != nil { return err diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 006eddf1..e007573c 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -226,7 +226,35 @@ func TestMappingTime(t *testing.T) { require.Error(t, err) } +type bindTestData struct { + need any + got any + in map[string][]string +} + +func TestMappingTimeUnixNano(t *testing.T) { + type needFixUnixNanoEmpty struct { + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + } + + // ok + tests := []bindTestData{ + {need: &needFixUnixNanoEmpty{}, got: &needFixUnixNanoEmpty{}, in: formSource{"createTime": []string{" "}}}, + {need: &needFixUnixNanoEmpty{}, got: &needFixUnixNanoEmpty{}, in: formSource{"createTime": []string{}}}, + } + + for _, v := range tests { + err := mapForm(v.got, v.in) + require.NoError(t, err) + assert.Equal(t, v.need, v.got) + } +} + func TestMappingTimeDuration(t *testing.T) { + type needFixDurationEmpty struct { + Duration time.Duration `form:"duration"` + } + var s struct { D time.Duration } @@ -236,6 +264,17 @@ func TestMappingTimeDuration(t *testing.T) { require.NoError(t, err) assert.Equal(t, 5*time.Second, s.D) + // ok + tests := []bindTestData{ + {need: &needFixDurationEmpty{}, got: &needFixDurationEmpty{}, in: formSource{"duration": []string{" "}}}, + {need: &needFixDurationEmpty{}, got: &needFixDurationEmpty{}, in: formSource{"duration": []string{}}}, + } + + for _, v := range tests { + err := mapForm(v.got, v.in) + require.NoError(t, err) + assert.Equal(t, v.need, v.got) + } // error err = mappingByPtr(&s, formSource{"D": {"wrong"}}, "form") require.Error(t, err) From 2a794cd0b0faa7d829291375b27a3467ea972b0d Mon Sep 17 00:00:00 2001 From: OHZEKI Naoki <0h23k1.n40k1@gmail.com> Date: Thu, 4 Dec 2025 11:49:37 +0900 Subject: [PATCH 124/156] fix(debug): version mismatch (#4403) Co-authored-by: Bo-Yi Wu --- debug.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug.go b/debug.go index f22dfd87..e07c8b46 100644 --- a/debug.go +++ b/debug.go @@ -13,7 +13,7 @@ import ( "sync/atomic" ) -const ginSupportMinGoVer = 23 +const ginSupportMinGoVer = 24 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. From 19b877fa50cbbb9282763099fb177a1e5cc5c850 Mon Sep 17 00:00:00 2001 From: OHZEKI Naoki <0h23k1.n40k1@gmail.com> Date: Fri, 5 Dec 2025 12:18:08 +0900 Subject: [PATCH 125/156] test(debug): improve the test coverage of debug.go to 100% (#4404) --- debug.go | 4 +++- debug_test.go | 34 +++++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/debug.go b/debug.go index e07c8b46..1cfa3721 100644 --- a/debug.go +++ b/debug.go @@ -15,6 +15,8 @@ import ( const ginSupportMinGoVer = 24 +var runtimeVersion = runtime.Version() + // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. func IsDebugging() bool { @@ -77,7 +79,7 @@ func getMinVer(v string) (uint64, error) { } func debugPrintWARNINGDefault() { - if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer { + if v, e := getMinVer(runtimeVersion); e == nil && v < ginSupportMinGoVer { debugPrint(`[WARNING] Now Gin requires Go 1.24+. `) diff --git a/debug_test.go b/debug_test.go index e9d8fe01..dab02133 100644 --- a/debug_test.go +++ b/debug_test.go @@ -12,7 +12,6 @@ import ( "log" "net/http" "os" - "runtime" "strings" "sync" "testing" @@ -21,10 +20,6 @@ import ( "github.com/stretchr/testify/require" ) -// TODO -// func debugRoute(httpMethod, absolutePath string, handlers HandlersChain) { -// func debugPrint(format string, values ...any) { - func TestIsDebugging(t *testing.T) { SetMode(DebugMode) assert.True(t, IsDebugging()) @@ -48,6 +43,18 @@ func TestDebugPrint(t *testing.T) { assert.Equal(t, "[GIN-debug] these are 2 error messages\n", re) } +func TestDebugPrintFunc(t *testing.T) { + DebugPrintFunc = func(format string, values ...any) { + fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...) + } + re := captureOutput(t, func() { + SetMode(DebugMode) + debugPrint("debug print func test: %d", 123) + SetMode(TestMode) + }) + assert.Regexp(t, `^\[GIN-debug\] debug print func test: 123`, re) +} + func TestDebugPrintError(t *testing.T) { re := captureOutput(t, func() { SetMode(DebugMode) @@ -104,12 +111,17 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { debugPrintWARNINGDefault() SetMode(TestMode) }) - m, e := getMinVer(runtime.Version()) - if e == nil && m < ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.24+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) - } else { - assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) - } + assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) +} + +func TestDebugPrintWARNINGDefaultWithUnsupportedVersion(t *testing.T) { + runtimeVersion = "go1.23.12" + re := captureOutput(t, func() { + SetMode(DebugMode) + debugPrintWARNINGDefault() + SetMode(TestMode) + }) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.24+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } func TestDebugPrintWARNINGNew(t *testing.T) { From 64a6ed9a41ab72076506bd590dc4b916ca4b66a5 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Fri, 12 Dec 2025 13:42:03 +0800 Subject: [PATCH 126/156] perf(recovery): optimize line reading in stack function (#4466) Co-authored-by: 1911860538 --- recovery.go | 54 +++++++++++++++++++++++++++++----------- recovery_test.go | 65 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 89 insertions(+), 30 deletions(-) diff --git a/recovery.go b/recovery.go index e79e118a..6d4b4b2b 100644 --- a/recovery.go +++ b/recovery.go @@ -5,7 +5,9 @@ package gin import ( + "bufio" "bytes" + "cmp" "errors" "fmt" "io" @@ -21,9 +23,10 @@ import ( "github.com/gin-gonic/gin/internal/bytesconv" ) -const dunno = "???" - -var dunnoBytes = []byte(dunno) +const ( + dunno = "???" + stackSkip = 3 +) // RecoveryFunc defines the function passable to CustomRecovery. type RecoveryFunc func(c *Context, err any) @@ -72,7 +75,6 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { brokenPipe = true } if logger != nil { - const stackSkip = 3 if brokenPipe { logger.Printf("%s\n%s%s", err, secureRequestDump(c.Request), reset) } else if IsDebugging() { @@ -120,8 +122,11 @@ func stack(skip int) []byte { buf := new(bytes.Buffer) // the returned data // As we loop, we open files and read them. These variables record the currently // loaded file. - var lines [][]byte - var lastFile string + var ( + nLine string + lastFile string + err error + ) for i := skip; ; i++ { // Skip the expected number of frames pc, file, line, ok := runtime.Caller(i) if !ok { @@ -130,25 +135,44 @@ func stack(skip int) []byte { // Print this much at least. If we can't find the source, it won't show. fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) if file != lastFile { - data, err := os.ReadFile(file) + nLine, err = readNthLine(file, line-1) if err != nil { continue } - lines = bytes.Split(data, []byte{'\n'}) lastFile = file } - fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) + fmt.Fprintf(buf, "\t%s: %s\n", function(pc), cmp.Or(nLine, dunno)) } return buf.Bytes() } -// source returns a space-trimmed slice of the n'th line. -func source(lines [][]byte, n int) []byte { - n-- // in stack trace, lines are 1-indexed but our array is 0-indexed - if n < 0 || n >= len(lines) { - return dunnoBytes +// readNthLine reads the nth line from the file. +// It returns the trimmed content of the line if found, +// or an empty string if the line doesn't exist. +// If there's an error opening the file, it returns the error. +func readNthLine(file string, n int) (string, error) { + if n < 0 { + return "", nil } - return bytes.TrimSpace(lines[n]) + + f, err := os.Open(file) + if err != nil { + return "", err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for i := 0; i < n; i++ { + if !scanner.Scan() { + return "", nil + } + } + + if scanner.Scan() { + return strings.TrimSpace(scanner.Text()), nil + } + + return "", nil } // function returns, if possible, the name of the function containing the PC. diff --git a/recovery_test.go b/recovery_test.go index 073f4858..912ab601 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -88,21 +88,6 @@ func TestPanicWithAbort(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) } -func TestSource(t *testing.T) { - bs := source(nil, 0) - assert.Equal(t, dunnoBytes, bs) - - in := [][]byte{ - []byte("Hello world."), - []byte("Hi, gin.."), - } - bs = source(in, 10) - assert.Equal(t, dunnoBytes, bs) - - bs = source(in, 1) - assert.Equal(t, []byte("Hello world."), bs) -} - func TestFunction(t *testing.T) { bs := function(1) assert.Equal(t, dunno, bs) @@ -331,3 +316,53 @@ func TestSecureRequestDump(t *testing.T) { }) } } + +// TestReadNthLine tests the readNthLine function with various scenarios. +func TestReadNthLine(t *testing.T) { + // Create a temporary test file + testContent := "line 0 \n line 1 \nline 2 \nline 3 \nline 4" + tempFile, err := os.CreateTemp("", "testfile*.txt") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tempFile.Name()) + + // Write test content to the temporary file + if _, err := tempFile.WriteString(testContent); err != nil { + t.Fatal(err) + } + if err := tempFile.Close(); err != nil { + t.Fatal(err) + } + + // Test cases + tests := []struct { + name string + lineNum int + fileName string + want string + wantErr bool + }{ + {name: "Read first line", lineNum: 0, fileName: tempFile.Name(), want: "line 0", wantErr: false}, + {name: "Read middle line", lineNum: 2, fileName: tempFile.Name(), want: "line 2", wantErr: false}, + {name: "Read last line", lineNum: 4, fileName: tempFile.Name(), want: "line 4", wantErr: false}, + {name: "Line number exceeds file length", lineNum: 10, fileName: tempFile.Name(), want: "", wantErr: false}, + {name: "Negative line number", lineNum: -1, fileName: tempFile.Name(), want: "", wantErr: false}, + {name: "Non-existent file", lineNum: 1, fileName: "/non/existent/file.txt", want: "", wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := readNthLine(tt.fileName, tt.lineNum) + assert.Equal(t, tt.wantErr, err != nil) + assert.Equal(t, tt.want, got) + }) + } +} + +func BenchmarkStack(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + _ = stack(stackSkip) + } +} From d1a15347b1e45a8ee816193d3578a93bfd73b70f Mon Sep 17 00:00:00 2001 From: OHZEKI Naoki <0h23k1.n40k1@gmail.com> Date: Fri, 12 Dec 2025 14:43:25 +0900 Subject: [PATCH 127/156] refactor(utils): move util functions to utils.go (#4467) Co-authored-by: Bo-Yi Wu --- context.go | 8 -------- tree.go | 9 --------- utils.go | 17 +++++++++++++++++ utils_test.go | 11 +++++++++++ 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/context.go b/context.go index 112f0ee0..e1d9be43 100644 --- a/context.go +++ b/context.go @@ -55,14 +55,6 @@ const ContextRequestKey ContextKeyType = 0 // abortIndex represents a typical value used in abort functions. const abortIndex int8 = math.MaxInt8 >> 1 -// safeInt8 converts int to int8 safely, capping at math.MaxInt8 -func safeInt8(n int) int8 { - if n > math.MaxInt8 { - return math.MaxInt8 - } - return int8(n) -} - // Context is the most important part of gin. It allows us to pass variables between middleware, // manage the flow, validate the JSON of a request and render a JSON response for example. type Context struct { diff --git a/tree.go b/tree.go index eff07734..88f25fcb 100644 --- a/tree.go +++ b/tree.go @@ -5,7 +5,6 @@ package gin import ( - "math" "net/url" "strings" "unicode" @@ -78,14 +77,6 @@ func (n *node) addChild(child *node) { } } -// safeUint16 converts int to uint16 safely, capping at math.MaxUint16 -func safeUint16(n int) uint16 { - if n > math.MaxUint16 { - return math.MaxUint16 - } - return uint16(n) -} - func countParams(path string) uint16 { colons := strings.Count(path, ":") stars := strings.Count(path, "*") diff --git a/utils.go b/utils.go index 47106a7a..971e9433 100644 --- a/utils.go +++ b/utils.go @@ -6,6 +6,7 @@ package gin import ( "encoding/xml" + "math" "net/http" "os" "path" @@ -162,3 +163,19 @@ func isASCII(s string) bool { } return true } + +// safeInt8 converts int to int8 safely, capping at math.MaxInt8 +func safeInt8(n int) int8 { + if n > math.MaxInt8 { + return math.MaxInt8 + } + return int8(n) +} + +// safeUint16 converts int to uint16 safely, capping at math.MaxUint16 +func safeUint16(n int) uint16 { + if n > math.MaxUint16 { + return math.MaxUint16 + } + return uint16(n) +} diff --git a/utils_test.go b/utils_test.go index 8bcf00e4..893ebc88 100644 --- a/utils_test.go +++ b/utils_test.go @@ -8,6 +8,7 @@ import ( "bytes" "encoding/xml" "fmt" + "math" "net/http" "testing" @@ -148,3 +149,13 @@ func TestIsASCII(t *testing.T) { assert.True(t, isASCII("test")) assert.False(t, isASCII("🧡💛💚💙💜")) } + +func TestSafeInt8(t *testing.T) { + assert.Equal(t, int8(100), safeInt8(100)) + assert.Equal(t, int8(math.MaxInt8), safeInt8(int(math.MaxInt8)+123)) +} + +func TestSafeUint16(t *testing.T) { + assert.Equal(t, uint16(100), safeUint16(100)) + assert.Equal(t, uint16(math.MaxUint16), safeUint16(int(math.MaxUint16)+123)) +} From 22c274c84ba5a502ce6453a9d2bb9e05ed91b911 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Dec 2025 18:33:46 +0800 Subject: [PATCH 128/156] chore(deps): bump actions/cache from 4 to 5 in the actions group (#4469) Bumps the actions group with 1 update: [actions/cache](https://github.com/actions/cache). Updates `actions/cache` from 4 to 5 - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/cache dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 4e3b8753..8ece7f1d 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -65,7 +65,7 @@ jobs: with: ref: ${{ github.ref }} - - uses: actions/cache@v4 + - uses: actions/cache@v5 with: path: | ${{ matrix.go-build }} From 26c3a628655cad2388380cb8102d6ce7d4875f3b Mon Sep 17 00:00:00 2001 From: Twacqwq <69360546+Twacqwq@users.noreply.github.com> Date: Wed, 24 Dec 2025 18:35:20 +0800 Subject: [PATCH 129/156] chore(response): prevent Flush() panic when `http.Flusher` (#4479) --- response_writer.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/response_writer.go b/response_writer.go index 6907e514..9035e6f1 100644 --- a/response_writer.go +++ b/response_writer.go @@ -128,7 +128,9 @@ func (w *responseWriter) CloseNotify() <-chan bool { // Flush implements the http.Flusher interface. func (w *responseWriter) Flush() { w.WriteHeaderNow() - w.ResponseWriter.(http.Flusher).Flush() + if f, ok := w.ResponseWriter.(http.Flusher); ok { + f.Flush() + } } func (w *responseWriter) Pusher() (pusher http.Pusher) { From 915e4c90d28ec4cffc6eb146e208ab5a65eac772 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Date: Sat, 27 Dec 2025 08:25:17 -0300 Subject: [PATCH 130/156] refactor(context): replace hardcoded localhost IPs with constants (#4481) --- context_test.go | 4 ++-- gin_test.go | 2 +- utils.go | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/context_test.go b/context_test.go index 126646fc..016fa86d 100644 --- a/context_test.go +++ b/context_test.go @@ -1910,7 +1910,7 @@ func TestContextClientIP(t *testing.T) { resetContextForClientIPTests(c) // IPv6 support - c.Request.RemoteAddr = "[::1]:12345" + c.Request.RemoteAddr = fmt.Sprintf("[%s]:12345", localhostIPv6) assert.Equal(t, "20.20.20.20", c.ClientIP()) resetContextForClientIPTests(c) @@ -3212,7 +3212,7 @@ func TestContextCopyShouldNotCancel(t *testing.T) { }() addr := strings.Split(l.Addr().String(), ":") - res, err := http.Get(fmt.Sprintf("http://127.0.0.1:%s/", addr[len(addr)-1])) + res, err := http.Get(fmt.Sprintf("http://%s:%s/", localhostIP, addr[len(addr)-1])) if err != nil { t.Error(fmt.Errorf("request error: %w", err)) return diff --git a/gin_test.go b/gin_test.go index 81343d88..43c9494d 100644 --- a/gin_test.go +++ b/gin_test.go @@ -83,7 +83,7 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) { } func TestH2c(t *testing.T) { - ln, err := net.Listen("tcp", "127.0.0.1:0") + ln, err := net.Listen("tcp", localhostIP+":0") if err != nil { t.Error(err) } diff --git a/utils.go b/utils.go index 971e9433..62517784 100644 --- a/utils.go +++ b/utils.go @@ -19,6 +19,12 @@ import ( // BindKey indicates a default bind key. const BindKey = "_gin-gonic/gin/bindkey" +// localhostIP indicates the default localhost IP address. +const localhostIP = "127.0.0.1" + +// localhostIPv6 indicates the default localhost IPv6 address. +const localhostIPv6 = "::1" + // Bind is a helper function for given interface object and returns a Gin middleware. func Bind(val any) HandlerFunc { value := reflect.ValueOf(val) From 9914178584e42458ff7d23891463a880f58c9d86 Mon Sep 17 00:00:00 2001 From: Nurysso <152021471+Nurysso@users.noreply.github.com> Date: Fri, 2 Jan 2026 02:15:27 +0000 Subject: [PATCH 131/156] fix(context): ClientIP handling for multiple X-Forwarded-For header values (#4472) * Fix ClientIP calculation by concatenating all RemoteIPHeaders values * test: used http.MethodGet instead constants and fix lints * lint error fixed * Refactor ClientIP X-Forwarded-For tests --------- Co-authored-by: Bo-Yi Wu --- context.go | 3 ++- context_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index e1d9be43..c42459ff 100644 --- a/context.go +++ b/context.go @@ -989,7 +989,8 @@ func (c *Context) ClientIP() string { if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil { for _, headerName := range c.engine.RemoteIPHeaders { - ip, valid := c.engine.validateHeader(c.requestHeader(headerName)) + headerValue := strings.Join(c.Request.Header.Values(headerName), ",") + ip, valid := c.engine.validateHeader(headerValue) if valid { return ip } diff --git a/context_test.go b/context_test.go index 016fa86d..3080015c 100644 --- a/context_test.go +++ b/context_test.go @@ -1143,6 +1143,37 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } +func TestContextClientIPWithMultipleHeaders(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodGet, "/test", nil) + + // Multiple X-Forwarded-For headers + c.Request.Header.Add("X-Forwarded-For", "1.2.3.4, "+localhostIP) + c.Request.Header.Add("X-Forwarded-For", "5.6.7.8") + c.Request.RemoteAddr = localhostIP + ":1234" + + c.engine.ForwardedByClientIP = true + c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"} + _ = c.engine.SetTrustedProxies([]string{localhostIP}) + + // Should return 5.6.7.8 (last non-trusted IP) + assert.Equal(t, "5.6.7.8", c.ClientIP()) +} + +func TestContextClientIPWithSingleHeader(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodGet, "/test", nil) + c.Request.Header.Set("X-Forwarded-For", "1.2.3.4, "+localhostIP) + c.Request.RemoteAddr = localhostIP + ":1234" + + c.engine.ForwardedByClientIP = true + c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"} + _ = c.engine.SetTrustedProxies([]string{localhostIP}) + + // Should return 1.2.3.4 + assert.Equal(t, "1.2.3.4", c.ClientIP()) +} + // Tests that the response is serialized as Secure JSON // and Content-Type is set to application/json func TestContextRenderSecureJSON(t *testing.T) { From 3ab698dc5110af1977d57226e4995c57dd34c233 Mon Sep 17 00:00:00 2001 From: OHZEKI Naoki <0h23k1.n40k1@gmail.com> Date: Sat, 17 Jan 2026 17:40:43 +0900 Subject: [PATCH 132/156] refactor(recovery): smart error comparison (#4142) * refactor(recovery): rename var in CustomRecoveryWithWriter * refactor(recovery): smart error comparison * test(recovery): Directly reference the syscall error string --- recovery.go | 37 +++++++++++++++---------------------- recovery_test.go | 13 +++++++------ 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/recovery.go b/recovery.go index 6d4b4b2b..bbf1d565 100644 --- a/recovery.go +++ b/recovery.go @@ -12,12 +12,12 @@ import ( "fmt" "io" "log" - "net" "net/http" "net/http/httputil" "os" "runtime" "strings" + "syscall" "time" "github.com/gin-gonic/gin/internal/bytesconv" @@ -57,40 +57,33 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { } return func(c *Context) { defer func() { - if err := recover(); err != nil { + if rec := recover(); rec != nil { // Check for a broken connection, as it is not really a // condition that warrants a panic stack trace. - var brokenPipe bool - if ne, ok := err.(*net.OpError); ok { - var se *os.SyscallError - if errors.As(ne, &se) { - seStr := strings.ToLower(se.Error()) - if strings.Contains(seStr, "broken pipe") || - strings.Contains(seStr, "connection reset by peer") { - brokenPipe = true - } - } - } - if e, ok := err.(error); ok && errors.Is(e, http.ErrAbortHandler) { - brokenPipe = true + var isBrokenPipe bool + err, ok := rec.(error) + if ok { + isBrokenPipe = errors.Is(err, syscall.EPIPE) || + errors.Is(err, syscall.ECONNRESET) || + errors.Is(err, http.ErrAbortHandler) } if logger != nil { - if brokenPipe { - logger.Printf("%s\n%s%s", err, secureRequestDump(c.Request), reset) + if isBrokenPipe { + logger.Printf("%s\n%s%s", rec, secureRequestDump(c.Request), reset) } else if IsDebugging() { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", - timeFormat(time.Now()), secureRequestDump(c.Request), err, stack(stackSkip), reset) + timeFormat(time.Now()), secureRequestDump(c.Request), rec, stack(stackSkip), reset) } else { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s", - timeFormat(time.Now()), err, stack(stackSkip), reset) + timeFormat(time.Now()), rec, stack(stackSkip), reset) } } - if brokenPipe { + if isBrokenPipe { // If the connection is dead, we can't write a status to it. - c.Error(err.(error)) //nolint: errcheck + c.Error(err) //nolint: errcheck c.Abort() } else { - handle(c, err) + handle(c, rec) } } }() diff --git a/recovery_test.go b/recovery_test.go index 912ab601..0faa3280 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -98,13 +98,13 @@ func TestFunction(t *testing.T) { func TestPanicWithBrokenPipe(t *testing.T) { const expectCode = 204 - expectMsgs := map[syscall.Errno]string{ - syscall.EPIPE: "broken pipe", - syscall.ECONNRESET: "connection reset by peer", + expectErrnos := []syscall.Errno{ + syscall.EPIPE, + syscall.ECONNRESET, } - for errno, expectMsg := range expectMsgs { - t.Run(expectMsg, func(t *testing.T) { + for _, errno := range expectErrnos { + t.Run("Recovery from "+errno.Error(), func(t *testing.T) { var buf strings.Builder router := New() @@ -122,7 +122,8 @@ func TestPanicWithBrokenPipe(t *testing.T) { w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, expectCode, w.Code) - assert.Contains(t, strings.ToLower(buf.String()), expectMsg) + assert.Contains(t, strings.ToLower(buf.String()), errno.Error()) + assert.NotContains(t, strings.ToLower(buf.String()), "[Recovery]") }) } } From b2b489dbf4826c2c630717a77fd5e42774625410 Mon Sep 17 00:00:00 2001 From: WeidiDeng Date: Sun, 18 Jan 2026 12:56:22 +0800 Subject: [PATCH 133/156] chore(context): always trust xff headers from unix socket (#3359) Co-authored-by: Bo-Yi Wu --- context.go | 27 ++++++++++++++++++++------- context_test.go | 10 ++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/context.go b/context.go index c42459ff..be552078 100644 --- a/context.go +++ b/context.go @@ -978,14 +978,27 @@ func (c *Context) ClientIP() string { } } - // It also checks if the remoteIP is a trusted proxy or not. - // In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks - // defined by Engine.SetTrustedProxies() - remoteIP := net.ParseIP(c.RemoteIP()) - if remoteIP == nil { - return "" + var ( + trusted bool + remoteIP net.IP + ) + // If gin is listening a unix socket, always trust it. + localAddr, ok := c.Request.Context().Value(http.LocalAddrContextKey).(net.Addr) + if ok && strings.HasPrefix(localAddr.Network(), "unix") { + trusted = true + } + + // Fallback + if !trusted { + // It also checks if the remoteIP is a trusted proxy or not. + // In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks + // defined by Engine.SetTrustedProxies() + remoteIP = net.ParseIP(c.RemoteIP()) + if remoteIP == nil { + return "" + } + trusted = c.engine.isTrustedProxy(remoteIP) } - trusted := c.engine.isTrustedProxy(remoteIP) if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil { for _, headerName := range c.engine.RemoteIPHeaders { diff --git a/context_test.go b/context_test.go index 3080015c..f69d574f 100644 --- a/context_test.go +++ b/context_test.go @@ -1915,6 +1915,16 @@ func TestContextClientIP(t *testing.T) { c.engine.trustedCIDRs, _ = c.engine.prepareTrustedCIDRs() resetContextForClientIPTests(c) + // unix address + addr := &net.UnixAddr{Net: "unix", Name: "@"} + c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), http.LocalAddrContextKey, addr)) + c.Request.RemoteAddr = addr.String() + assert.Equal(t, "20.20.20.20", c.ClientIP()) + + // reset + c.Request = c.Request.WithContext(context.Background()) + resetContextForClientIPTests(c) + // Legacy tests (validating that the defaults don't break the // (insecure!) old behaviour) assert.Equal(t, "20.20.20.20", c.ClientIP()) From 192ac89eefc1c30f7c97ae48a9ffb1c6f1c8c8bc Mon Sep 17 00:00:00 2001 From: takanuva15 <6986426+takanuva15@users.noreply.github.com> Date: Sat, 24 Jan 2026 02:20:24 -0500 Subject: [PATCH 134/156] feat(binding): add support for encoding.UnmarshalText in uri/query binding (#4203) --- binding/form_mapping.go | 68 ++++-- binding/form_mapping_test.go | 390 +++++++++++++++++++++++++++++++++++ docs/doc.md | 78 ++++++- 3 files changed, 509 insertions(+), 27 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index e76e7510..db6aa0dc 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -5,6 +5,7 @@ package binding import ( + "encoding" "errors" "fmt" "maps" @@ -137,6 +138,8 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag type setOptions struct { isDefaultExists bool defaultValue string + // parser specifies what interface to use for reading the request & default values (e.g. `encoding.TextUnmarshaler`) + parser string } func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { @@ -168,6 +171,8 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter setOpt.defaultValue = strings.ReplaceAll(v, ";", ",") } } + } else if k, v = head(opt, "="); k == "parser" { + setOpt.parser = v } } @@ -191,6 +196,20 @@ func trySetCustom(val string, value reflect.Value) (isSet bool, err error) { return false, nil } +// trySetUsingParser tries to set a custom type value based on the presence of the "parser" tag on the field. +// If the parser tag does not exist or does not match any of the supported parsers, gin will skip over this. +func trySetUsingParser(val string, value reflect.Value, parser string) (isSet bool, err error) { + switch parser { + case "encoding.TextUnmarshaler": + v, ok := value.Addr().Interface().(encoding.TextUnmarshaler) + if !ok { + return false, nil + } + return true, v.UnmarshalText([]byte(val)) + } + return false, nil +} + func trySplit(vs []string, field reflect.StructField) (newVs []string, err error) { cfTag := field.Tag.Get("collection_format") if cfTag == "" || cfTag == "multi" { @@ -208,7 +227,7 @@ func trySplit(vs []string, field reflect.StructField) (newVs []string, err error case "pipes": sep = "|" default: - return vs, fmt.Errorf("%s is not supported in the collection_format. (csv, ssv, pipes)", cfTag) + return vs, fmt.Errorf("%s is not supported in the collection_format. (multi, csv, ssv, tsv, pipes)", cfTag) } totalLength := 0 @@ -244,7 +263,9 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ } } - if ok, err = trySetCustom(vs[0], value); ok { + if ok, err = trySetUsingParser(vs[0], value, opt.parser); ok { + return ok, err + } else if ok, err = trySetCustom(vs[0], value); ok { return ok, err } @@ -252,7 +273,7 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ return false, err } - return true, setSlice(vs, value, field) + return true, setSlice(vs, value, field, opt) case reflect.Array: if len(vs) == 0 { if !opt.isDefaultExists { @@ -267,7 +288,9 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ } } - if ok, err = trySetCustom(vs[0], value); ok { + if ok, err = trySetUsingParser(vs[0], value, opt.parser); ok { + return ok, err + } else if ok, err = trySetCustom(vs[0], value); ok { return ok, err } @@ -279,27 +302,32 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) } - return true, setArray(vs, value, field) + return true, setArray(vs, value, field, opt) default: var val string - if !ok { + if !ok || len(vs) == 0 || (len(vs) > 0 && vs[0] == "") { val = opt.defaultValue + } else if len(vs) > 0 { + val = vs[0] } - if len(vs) > 0 { - val = vs[0] - if val == "" { - val = opt.defaultValue - } - } - if ok, err := trySetCustom(val, value); ok { + if ok, err = trySetUsingParser(val, value, opt.parser); ok { + return ok, err + } else if ok, err = trySetCustom(val, value); ok { return ok, err } - return true, setWithProperType(val, value, field) + return true, setWithProperType(val, value, field, opt) } } -func setWithProperType(val string, value reflect.Value, field reflect.StructField) error { +func setWithProperType(val string, value reflect.Value, field reflect.StructField, opt setOptions) error { + // this if-check is required for parsing nested types like []MyId, where MyId is [12]byte + if ok, err := trySetUsingParser(val, value, opt.parser); ok { + return err + } else if ok, err = trySetCustom(val, value); ok { + return err + } + // If it is a string type, no spaces are removed, and the user data is not modified here if value.Kind() != reflect.String { val = strings.TrimSpace(val) @@ -352,7 +380,7 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel if !value.Elem().IsValid() { value.Set(reflect.New(value.Type().Elem())) } - return setWithProperType(val, value.Elem(), field) + return setWithProperType(val, value.Elem(), field, opt) default: return errUnknownType } @@ -459,9 +487,9 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val return nil } -func setArray(vals []string, value reflect.Value, field reflect.StructField) error { +func setArray(vals []string, value reflect.Value, field reflect.StructField, opt setOptions) error { for i, s := range vals { - err := setWithProperType(s, value.Index(i), field) + err := setWithProperType(s, value.Index(i), field, opt) if err != nil { return err } @@ -469,9 +497,9 @@ func setArray(vals []string, value reflect.Value, field reflect.StructField) err return nil } -func setSlice(vals []string, value reflect.Value, field reflect.StructField) error { +func setSlice(vals []string, value reflect.Value, field reflect.StructField, opt setOptions) error { slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) - err := setArray(vals, slice, field) + err := setArray(vals, slice, field, opt) if err != nil { return err } diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index e007573c..c78f7398 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -5,6 +5,7 @@ package binding import ( + "encoding" "encoding/hex" "errors" "mime/multipart" @@ -524,6 +525,16 @@ func TestMappingCustomUnmarshalParamHexWithURITag(t *testing.T) { assert.EqualValues(t, 245, s.Foo) } +func TestMappingCustomUnmarshalParamHexDefault(t *testing.T) { + var s struct { + Foo customUnmarshalParamHex `form:"foo,default=f5"` + } + err := mappingByPtr(&s, formSource{"foo": {}}, "form") + require.NoError(t, err) + + assert.EqualValues(t, 0xf5, s.Foo) +} + type customUnmarshalParamType struct { Protocol string Path string @@ -624,6 +635,33 @@ func TestMappingCustomSliceForm(t *testing.T) { assert.Equal(t, "foo", s.FileData[1]) } +func TestMappingCustomSliceStopsWhenError(t *testing.T) { + var s struct { + FileData customPath `form:"path"` + } + err := mappingByPtr(&s, formSource{"path": {"invalid"}}, "form") + require.ErrorContains(t, err, "invalid format") + require.Empty(t, s.FileData) +} + +func TestMappingCustomSliceOfSliceUri(t *testing.T) { + var s struct { + FileData []customPath `uri:"path" collection_format:"csv"` + } + err := mappingByPtr(&s, formSource{"path": {"bar/foo,bar/foo/spam"}}, "uri") + require.NoError(t, err) + assert.Equal(t, []customPath{{"bar", "foo"}, {"bar", "foo", "spam"}}, s.FileData) +} + +func TestMappingCustomSliceOfSliceForm(t *testing.T) { + var s struct { + FileData []customPath `form:"path" collection_format:"csv"` + } + err := mappingByPtr(&s, formSource{"path": {"bar/foo,bar/foo/spam"}}, "form") + require.NoError(t, err) + assert.Equal(t, []customPath{{"bar", "foo"}, {"bar", "foo", "spam"}}, s.FileData) +} + type objectID [12]byte func (o *objectID) UnmarshalParam(param string) error { @@ -675,6 +713,358 @@ func TestMappingCustomArrayForm(t *testing.T) { assert.Equal(t, expected, s.FileData) } +func TestMappingCustomArrayOfArrayUri(t *testing.T) { + id1, _ := convertTo(`664a062ac74a8ad104e0e80e`) + id2, _ := convertTo(`664a062ac74a8ad104e0e80f`) + + var s struct { + FileData []objectID `uri:"ids" collection_format:"csv"` + } + err := mappingByPtr(&s, formSource{"ids": {`664a062ac74a8ad104e0e80e,664a062ac74a8ad104e0e80f`}}, "uri") + require.NoError(t, err) + assert.Equal(t, []objectID{id1, id2}, s.FileData) +} + +func TestMappingCustomArrayOfArrayForm(t *testing.T) { + id1, _ := convertTo(`664a062ac74a8ad104e0e80e`) + id2, _ := convertTo(`664a062ac74a8ad104e0e80f`) + + var s struct { + FileData []objectID `form:"ids" collection_format:"csv"` + } + err := mappingByPtr(&s, formSource{"ids": {`664a062ac74a8ad104e0e80e,664a062ac74a8ad104e0e80f`}}, "form") + require.NoError(t, err) + assert.Equal(t, []objectID{id1, id2}, s.FileData) +} + +// ==== TextUnmarshaler tests START ==== + +type customUnmarshalTextHex int + +func (f *customUnmarshalTextHex) UnmarshalText(text []byte) error { + v, err := strconv.ParseInt(string(text), 16, 64) + if err != nil { + return err + } + *f = customUnmarshalTextHex(v) + return nil +} + +// verify type implements TextUnmarshaler +var _ encoding.TextUnmarshaler = (*customUnmarshalTextHex)(nil) + +func TestMappingCustomUnmarshalTextHexUri(t *testing.T) { + var s struct { + Field customUnmarshalTextHex `uri:"field,parser=encoding.TextUnmarshaler"` + } + err := mappingByPtr(&s, formSource{"field": {`f5`}}, "uri") + require.NoError(t, err) + assert.EqualValues(t, 245, s.Field) +} + +func TestMappingCustomUnmarshalTextHexForm(t *testing.T) { + var s struct { + Field customUnmarshalTextHex `form:"field,parser=encoding.TextUnmarshaler"` + } + err := mappingByPtr(&s, formSource{"field": {`f5`}}, "form") + require.NoError(t, err) + assert.EqualValues(t, 245, s.Field) +} + +func TestMappingCustomUnmarshalTextHexDefault(t *testing.T) { + var s struct { + Field customUnmarshalTextHex `form:"field,default=f5,parser=encoding.TextUnmarshaler"` + } + err := mappingByPtr(&s, formSource{"field1": {}}, "form") + require.NoError(t, err) + assert.EqualValues(t, 0xf5, s.Field) +} + +type customUnmarshalTextType struct { + Protocol string + Path string + Name string +} + +func (f *customUnmarshalTextType) UnmarshalText(text []byte) error { + parts := strings.Split(string(text), ":") + if len(parts) != 3 { + return errors.New("invalid format") + } + f.Protocol = parts[0] + f.Path = parts[1] + f.Name = parts[2] + return nil +} + +var _ encoding.TextUnmarshaler = (*customUnmarshalTextType)(nil) + +func TestMappingCustomStructTypeUnmarshalTextForm(t *testing.T) { + var s struct { + FileData customUnmarshalTextType `form:"data,parser=encoding.TextUnmarshaler"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form") + require.NoError(t, err) + + assert.Equal(t, "file", s.FileData.Protocol) + assert.Equal(t, "/foo", s.FileData.Path) + assert.Equal(t, "happiness", s.FileData.Name) +} + +func TestMappingCustomStructTypeUnmarshalTextUri(t *testing.T) { + var s struct { + FileData customUnmarshalTextType `uri:"data,parser=encoding.TextUnmarshaler"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri") + require.NoError(t, err) + + assert.Equal(t, "file", s.FileData.Protocol) + assert.Equal(t, "/foo", s.FileData.Path) + assert.Equal(t, "happiness", s.FileData.Name) +} + +func TestMappingCustomPointerStructTypeUnmarshalTextForm(t *testing.T) { + var s struct { + FileData *customUnmarshalTextType `form:"data,parser=encoding.TextUnmarshaler"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form") + require.NoError(t, err) + + assert.Equal(t, "file", s.FileData.Protocol) + assert.Equal(t, "/foo", s.FileData.Path) + assert.Equal(t, "happiness", s.FileData.Name) +} + +func TestMappingCustomPointerStructTypeUnmarshalTextUri(t *testing.T) { + var s struct { + FileData *customUnmarshalTextType `uri:"data,parser=encoding.TextUnmarshaler"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri") + require.NoError(t, err) + + assert.Equal(t, "file", s.FileData.Protocol) + assert.Equal(t, "/foo", s.FileData.Path) + assert.Equal(t, "happiness", s.FileData.Name) +} + +type customPathUnmarshalText []string + +func (p *customPathUnmarshalText) UnmarshalText(text []byte) error { + elems := strings.Split(string(text), "/") + n := len(elems) + if n < 2 { + return errors.New("invalid format") + } + + *p = elems + return nil +} + +var _ encoding.TextUnmarshaler = (*customPathUnmarshalText)(nil) + +func TestMappingCustomSliceUnmarshalTextUri(t *testing.T) { + var s struct { + FileData customPathUnmarshalText `uri:"path,parser=encoding.TextUnmarshaler"` + } + err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "uri") + require.NoError(t, err) + + assert.Equal(t, "bar", s.FileData[0]) + assert.Equal(t, "foo", s.FileData[1]) +} + +func TestMappingCustomSliceUnmarshalTextForm(t *testing.T) { + var s struct { + FileData customPathUnmarshalText `form:"path,parser=encoding.TextUnmarshaler"` + } + err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "form") + require.NoError(t, err) + + assert.Equal(t, "bar", s.FileData[0]) + assert.Equal(t, "foo", s.FileData[1]) +} + +func TestMappingCustomSliceUnmarshalTextStopsWhenError(t *testing.T) { + var s struct { + FileData customPathUnmarshalText `form:"path,parser=encoding.TextUnmarshaler"` + } + err := mappingByPtr(&s, formSource{"path": {"invalid"}}, "form") + require.ErrorContains(t, err, "invalid format") + require.Empty(t, s.FileData) +} + +func TestMappingCustomSliceOfSliceUnmarshalTextUri(t *testing.T) { + var s struct { + FileData []customPathUnmarshalText `uri:"path,parser=encoding.TextUnmarshaler" collection_format:"csv"` + } + err := mappingByPtr(&s, formSource{"path": {"bar/foo,bar/foo/spam"}}, "uri") + require.NoError(t, err) + assert.Equal(t, []customPathUnmarshalText{{"bar", "foo"}, {"bar", "foo", "spam"}}, s.FileData) +} + +func TestMappingCustomSliceOfSliceUnmarshalTextForm(t *testing.T) { + var s struct { + FileData []customPathUnmarshalText `form:"path,parser=encoding.TextUnmarshaler" collection_format:"csv"` + } + err := mappingByPtr(&s, formSource{"path": {"bar/foo,bar/foo/spam"}}, "form") + require.NoError(t, err) + assert.Equal(t, []customPathUnmarshalText{{"bar", "foo"}, {"bar", "foo", "spam"}}, s.FileData) +} + +func TestMappingCustomSliceOfSliceUnmarshalTextDefault(t *testing.T) { + var s struct { + FileData []customPathUnmarshalText `form:"path,default=bar/foo;bar/foo/spam,parser=encoding.TextUnmarshaler" collection_format:"csv"` + } + err := mappingByPtr(&s, formSource{"path": {}}, "form") + require.NoError(t, err) + assert.Equal(t, []customPathUnmarshalText{{"bar", "foo"}, {"bar", "foo", "spam"}}, s.FileData) +} + +type objectIDUnmarshalText [12]byte + +func (o *objectIDUnmarshalText) UnmarshalText(text []byte) error { + oid, err := convertToOidUnmarshalText(string(text)) + if err != nil { + return err + } + + *o = oid + return nil +} + +func convertToOidUnmarshalText(s string) (objectIDUnmarshalText, error) { + oid, err := convertTo(s) + return objectIDUnmarshalText(oid), err +} + +var _ encoding.TextUnmarshaler = (*objectIDUnmarshalText)(nil) + +func TestMappingCustomArrayUnmarshalTextUri(t *testing.T) { + var s struct { + FileData objectIDUnmarshalText `uri:"id,parser=encoding.TextUnmarshaler"` + } + val := `664a062ac74a8ad104e0e80f` + err := mappingByPtr(&s, formSource{"id": {val}}, "uri") + require.NoError(t, err) + + expected, _ := convertToOidUnmarshalText(val) + assert.Equal(t, expected, s.FileData) +} + +func TestMappingCustomArrayUnmarshalTextForm(t *testing.T) { + var s struct { + FileData objectIDUnmarshalText `form:"id,parser=encoding.TextUnmarshaler"` + } + val := `664a062ac74a8ad104e0e80f` + err := mappingByPtr(&s, formSource{"id": {val}}, "form") + require.NoError(t, err) + + expected, _ := convertToOidUnmarshalText(val) + assert.Equal(t, expected, s.FileData) +} + +func TestMappingCustomArrayOfArrayUnmarshalTextUri(t *testing.T) { + id1, _ := convertToOidUnmarshalText(`664a062ac74a8ad104e0e80e`) + id2, _ := convertToOidUnmarshalText(`664a062ac74a8ad104e0e80f`) + + var s struct { + FileData []objectIDUnmarshalText `uri:"ids,parser=encoding.TextUnmarshaler" collection_format:"csv"` + } + err := mappingByPtr(&s, formSource{"ids": {`664a062ac74a8ad104e0e80e,664a062ac74a8ad104e0e80f`}}, "uri") + require.NoError(t, err) + assert.Equal(t, []objectIDUnmarshalText{id1, id2}, s.FileData) +} + +func TestMappingCustomArrayOfArrayUnmarshalTextForm(t *testing.T) { + id1, _ := convertToOidUnmarshalText(`664a062ac74a8ad104e0e80e`) + id2, _ := convertToOidUnmarshalText(`664a062ac74a8ad104e0e80f`) + + var s struct { + FileData []objectIDUnmarshalText `form:"ids,parser=encoding.TextUnmarshaler" collection_format:"csv"` + } + err := mappingByPtr(&s, formSource{"ids": {`664a062ac74a8ad104e0e80e,664a062ac74a8ad104e0e80f`}}, "form") + require.NoError(t, err) + assert.Equal(t, []objectIDUnmarshalText{id1, id2}, s.FileData) +} + +func TestMappingCustomArrayOfArrayUnmarshalTextDefault(t *testing.T) { + id1, _ := convertToOidUnmarshalText(`664a062ac74a8ad104e0e80e`) + id2, _ := convertToOidUnmarshalText(`664a062ac74a8ad104e0e80f`) + + var s struct { + FileData []objectIDUnmarshalText `form:"ids,default=664a062ac74a8ad104e0e80e;664a062ac74a8ad104e0e80f,parser=encoding.TextUnmarshaler" collection_format:"csv"` + } + err := mappingByPtr(&s, formSource{"ids": {}}, "form") + require.NoError(t, err) + assert.Equal(t, []objectIDUnmarshalText{id1, id2}, s.FileData) +} + +// If someone specifies parser=TextUnmarshaler and it's not defined for the type, gin should revert to using its default +// binding logic. +func TestMappingUsingBindUnmarshalerAndTextUnmarshalerWhenOnlyBindUnmarshalerDefined(t *testing.T) { + var s struct { + Hex customUnmarshalParamHex `form:"hex"` + HexByUnmarshalText customUnmarshalParamHex `form:"hex2,parser=encoding.TextUnmarshaler"` + } + err := mappingByPtr(&s, formSource{ + "hex": {`f5`}, + "hex2": {`f5`}, + }, "form") + require.NoError(t, err) + + assert.EqualValues(t, 0xf5, s.Hex) + assert.EqualValues(t, 0xf5, s.HexByUnmarshalText) // reverts to BindUnmarshaler binding +} + +// If someone does not specify parser=TextUnmarshaler even when it's defined for the type, gin should ignore the +// UnmarshalText logic and continue using its default binding logic. (This ensures gin does not break backwards +// compatibility) +func TestMappingUsingBindUnmarshalerAndTextUnmarshalerWhenOnlyTextUnmarshalerDefined(t *testing.T) { + var s struct { + Hex customUnmarshalTextHex `form:"hex"` + HexByUnmarshalText customUnmarshalTextHex `form:"hex2,parser=encoding.TextUnmarshaler"` + } + err := mappingByPtr(&s, formSource{ + "hex": {`11`}, + "hex2": {`11`}, + }, "form") + require.NoError(t, err) + + assert.EqualValues(t, 11, s.Hex) // this is using default int binding, not our custom hex binding. 0x11 should be 17 in decimal + assert.EqualValues(t, 0x11, s.HexByUnmarshalText) // correct expected value for normal hex binding +} + +type customHexUnmarshalParamAndUnmarshalText int + +func (f *customHexUnmarshalParamAndUnmarshalText) UnmarshalParam(param string) error { + return errors.New("should not be called in unit test if parser tag present") +} + +func (f *customHexUnmarshalParamAndUnmarshalText) UnmarshalText(text []byte) error { + v, err := strconv.ParseInt(string(text), 16, 64) + if err != nil { + return err + } + *f = customHexUnmarshalParamAndUnmarshalText(v) + return nil +} + +// If a type has both UnmarshalParam and UnmarshalText methods defined, but the parser tag is set to TextUnmarshaler, +// then only the UnmarshalText method should be invoked. +func TestMappingUsingTextUnmarshalerWhenBindUnmarshalerAlsoDefined(t *testing.T) { + var s struct { + Hex customHexUnmarshalParamAndUnmarshalText `form:"hex,parser=encoding.TextUnmarshaler"` + } + err := mappingByPtr(&s, formSource{ + "hex": {`f5`}, + }, "form") + require.NoError(t, err) + + assert.EqualValues(t, 0xf5, s.Hex) +} + +// ==== TextUnmarshaler tests END ==== + func TestMappingEmptyValues(t *testing.T) { t.Run("slice with default", func(t *testing.T) { var s struct { diff --git a/docs/doc.md b/docs/doc.md index 0dd86684..449c8d02 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -911,7 +911,7 @@ curl -X POST http://localhost:8080/person NOTE: For default [collection values](#collection-format-for-arrays), the following rules apply: - Since commas are used to delimit tag options, they are not supported within a default value and will result in undefined behavior -- For the collection formats "multi" and "csv", a semicolon should be used in place of a comma to delimited default values +- For the collection formats "multi" and "csv", a semicolon should be used in place of a comma to delimit default values - Since semicolons are used to delimit default values for "multi" and "csv", they are not supported within a default value for "multi" and "csv" @@ -1009,12 +1009,68 @@ curl -v localhost:8088/thinkerou/not-uuid ### Bind custom unmarshaler +To override gin's default binding logic, define a function on your type that satisfies the `encoding.TextUnmarshaler` interface from the Golang standard library. Then specify `parser=encoding.TextUnmarshaler` in the `uri`/`form` tag of the field being bound. + ```go package main import ( - "github.com/gin-gonic/gin" + "encoding" "strings" + + "github.com/gin-gonic/gin" +) + +type Birthday string + +func (b *Birthday) UnmarshalText(text []byte) error { + *b = Birthday(strings.Replace(string(text), "-", "/", -1)) + return nil +} + +var _ encoding.TextUnmarshaler = (*Birthday)(nil) //assert Birthday implements encoding.TextUnmarshaler + +func main() { + route := gin.Default() + var request struct { + Birthday Birthday `form:"birthday,parser=encoding.TextUnmarshaler"` + Birthdays []Birthday `form:"birthdays,parser=encoding.TextUnmarshaler" collection_format:"csv"` + BirthdaysDefault []Birthday `form:"birthdaysDef,default=2020-09-01;2020-09-02,parser=encoding.TextUnmarshaler" collection_format:"csv"` + } + route.GET("/test", func(ctx *gin.Context) { + _ = ctx.BindQuery(&request) + ctx.JSON(200, request) + }) + _ = route.Run(":8088") +} +``` + +Test it with: + +```sh +curl 'localhost:8088/test?birthday=2000-01-01&birthdays=2000-01-01,2000-01-02' +``` +Result +```sh +{"Birthday":"2000/01/01","Birthdays":["2000/01/01","2000/01/02"],"BirthdaysDefault":["2020/09/01","2020/09/02"]} +``` + +Note: +- If `parser=encoding.TextUnmarshaler` is specified for a type that does **not** implement `encoding.TextUnmarshaler`, gin will ignore it and proceed with its default binding logic. +- If `parser=encoding.TextUnmarshaler` is specified for a type and that type's implementation of `encoding.TextUnmarshaler` returns an error, gin will stop binding and return the error to the client. + +--- + +If a type already implements `encoding.TextUnmarshaler` but you want to customize how gin binds the type differently (eg to change what error message is returned), you can implement the dedicated `BindUnmarshaler` interface provided by gin instead. + +```go +package main + +import ( + "strings" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" ) type Birthday string @@ -1024,29 +1080,37 @@ func (b *Birthday) UnmarshalParam(param string) error { return nil } +var _ binding.BindUnmarshaler = (*Birthday)(nil) //assert Birthday implements binding.BindUnmarshaler + func main() { route := gin.Default() var request struct { - Birthday Birthday `form:"birthday"` + Birthday Birthday `form:"birthday"` + Birthdays []Birthday `form:"birthdays" collection_format:"csv"` + BirthdaysDefault []Birthday `form:"birthdaysDef,default=2020-09-01;2020-09-02" collection_format:"csv"` } route.GET("/test", func(ctx *gin.Context) { _ = ctx.BindQuery(&request) - ctx.JSON(200, request.Birthday) + ctx.JSON(200, request) }) - route.Run(":8088") + _ = route.Run(":8088") } ``` Test it with: ```sh -curl 'localhost:8088/test?birthday=2000-01-01' +curl 'localhost:8088/test?birthday=2000-01-01&birthdays=2000-01-01,2000-01-02' ``` Result ```sh -"2000/01/01" +{"Birthday":"2000/01/01","Birthdays":["2000/01/01","2000/01/02"],"BirthdaysDefault":["2020/09/01","2020/09/02"]} ``` +Note: +- If a type implements both `encoding.TextUnmarshaler` and `BindUnmarshaler`, gin will use `BindUnmarshaler` by default unless you specify `parser=encoding.TextUnmarshaler` in the binding tag. +- If a type returns an error from its implementation of `BindUnmarshaler`, gin will stop binding and return the error to the client. + ### Bind Header ```go From ac95fa6bbcf5ec102bc81fbbb70e8fccba90f0b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 15:22:06 +0800 Subject: [PATCH 135/156] chore(deps): bump goreleaser/goreleaser-action from 5 to 6 (#3992) Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 5 to 6. - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](https://github.com/goreleaser/goreleaser-action/compare/v5...v6) --- updated-dependencies: - dependency-name: goreleaser/goreleaser-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 53410d2e07054369e0960fbe2eed97e1b9966f12 Mon Sep 17 00:00:00 2001 From: Raju Ahmed <73926176+raju-mechatronics@users.noreply.github.com> Date: Sat, 24 Jan 2026 15:54:37 +0600 Subject: [PATCH 136/156] feat(context): add GetError and GetErrorSlice methods for error retrieval (#4502) Co-authored-by: Bo-Yi Wu --- context.go | 10 ++++++++++ context_test.go | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/context.go b/context.go index be552078..c7bc61fe 100644 --- a/context.go +++ b/context.go @@ -386,6 +386,11 @@ func (c *Context) GetDuration(key any) time.Duration { return getTyped[time.Duration](c, key) } +// GetError returns the value associated with the key as an error. +func (c *Context) GetError(key any) error { + return getTyped[error](c, key) +} + // GetIntSlice returns the value associated with the key as a slice of integers. func (c *Context) GetIntSlice(key any) []int { return getTyped[[]int](c, key) @@ -451,6 +456,11 @@ func (c *Context) GetStringSlice(key any) []string { return getTyped[[]string](c, key) } +// GetErrorSlice returns the value associated with the key as a slice of errors. +func (c *Context) GetErrorSlice(key any) []error { + return getTyped[[]error](c, key) +} + // GetStringMap returns the value associated with the key as a map of interfaces. func (c *Context) GetStringMap(key any) map[string]any { return getTyped[map[string]any](c, key) diff --git a/context_test.go b/context_test.go index f69d574f..53baf6d5 100644 --- a/context_test.go +++ b/context_test.go @@ -516,6 +516,14 @@ func TestContextGetDuration(t *testing.T) { assert.Equal(t, time.Second, c.GetDuration("duration")) } +func TestContextGetError(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "error" + value := errors.New("test error") + c.Set(key, value) + assert.Equal(t, value, c.GetError(key)) +} + func TestContextGetIntSlice(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) key := "int-slice" @@ -618,6 +626,14 @@ func TestContextGetStringSlice(t *testing.T) { assert.Equal(t, []string{"foo"}, c.GetStringSlice("slice")) } +func TestContextGetErrorSlice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "error-slice" + value := []error{errors.New("error1"), errors.New("error2")} + c.Set(key, value) + assert.Equal(t, value, c.GetErrorSlice(key)) +} + func TestContextGetStringMap(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) m := make(map[string]any) From d9e5cdf9c6f9c1643be6e081516469c71645d93d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 17:55:09 +0800 Subject: [PATCH 137/156] chore(deps): bump github.com/goccy/go-yaml from 1.19.0 to 1.19.1 (#4476) Bumps [github.com/goccy/go-yaml](https://github.com/goccy/go-yaml) from 1.19.0 to 1.19.1. - [Release notes](https://github.com/goccy/go-yaml/releases) - [Changelog](https://github.com/goccy/go-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-yaml/compare/v1.19.0...v1.19.1) --- updated-dependencies: - dependency-name: github.com/goccy/go-yaml dependency-version: 1.19.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3a2b2bf2..b755e40d 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.28.0 github.com/goccy/go-json v0.10.2 - github.com/goccy/go-yaml v1.19.0 + github.com/goccy/go-yaml v1.19.1 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 diff --git a/go.sum b/go.sum index a487aaaf..06442efb 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0 github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-yaml v1.19.0 h1:EmkZ9RIsX+Uq4DYFowegAuJo8+xdX3T/2dwNPXbxEYE= -github.com/goccy/go-yaml v1.19.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/goccy/go-yaml v1.19.1 h1:3rG3+v8pkhRqoQ/88NYNMHYVGYztCOCIZ7UQhu7H+NE= +github.com/goccy/go-yaml v1.19.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= From cad29c5e3f50a9764edbfc4787825c6caabd8579 Mon Sep 17 00:00:00 2001 From: Artur Melanchyk Date: Sat, 24 Jan 2026 17:46:02 +0100 Subject: [PATCH 138/156] perf(tree): reduce allocations in findCaseInsensitivePath (#4417) Co-authored-by: Artur Melanchyk <13834276+arturmelanchyk@users.noreply.github.com> --- tree.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tree.go b/tree.go index 88f25fcb..3ac0a3b1 100644 --- a/tree.go +++ b/tree.go @@ -671,12 +671,7 @@ walk: // Outer loop for walking the tree func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) ([]byte, bool) { const stackBufSize = 128 - // Use a static sized buffer on the stack in the common case. - // If the path is too long, allocate a buffer on the heap instead. - buf := make([]byte, 0, stackBufSize) - if length := len(path) + 1; length > stackBufSize { - buf = make([]byte, 0, length) - } + buf := make([]byte, 0, max(stackBufSize, len(path)+1)) ciPath := n.findCaseInsensitivePathRec( path, From e3118cc378d263454098924ebbde7e8d1dd2e904 Mon Sep 17 00:00:00 2001 From: wanghaolong613 Date: Sun, 25 Jan 2026 00:51:11 +0800 Subject: [PATCH 139/156] refactor: for loop can be modernized using range over int (#4392) Co-authored-by: Bo-Yi Wu --- benchmarks_test.go | 2 +- binding/default_validator.go | 4 ++-- binding/form_mapping.go | 2 +- context_test.go | 10 +++++----- gin_integration_test.go | 2 +- internal/bytesconv/bytesconv_test.go | 4 ++-- utils.go | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/benchmarks_test.go b/benchmarks_test.go index ca504ecb..5c5163d9 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -154,7 +154,7 @@ func runRequest(B *testing.B, r *Engine, method, path string) { w := newMockWriter() B.ReportAllocs() B.ResetTimer() - for i := 0; i < B.N; i++ { + for B.Loop() { r.ServeHTTP(w, req) } } diff --git a/binding/default_validator.go b/binding/default_validator.go index 44b7a2ac..8203bcaa 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -27,7 +27,7 @@ func (err SliceValidationError) Error() string { } var b strings.Builder - for i := 0; i < len(err); i++ { + for i := range len(err) { if err[i] != nil { if b.Len() > 0 { b.WriteString("\n") @@ -58,7 +58,7 @@ func (v *defaultValidator) ValidateStruct(obj any) error { case reflect.Slice, reflect.Array: count := value.Len() validateRet := make(SliceValidationError, 0) - for i := 0; i < count; i++ { + for i := range count { if err := v.ValidateStruct(value.Index(i).Interface()); err != nil { validateRet = append(validateRet, err) } diff --git a/binding/form_mapping.go b/binding/form_mapping.go index db6aa0dc..6982fd4f 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -119,7 +119,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag tValue := value.Type() var isSet bool - for i := 0; i < value.NumField(); i++ { + for i := range value.NumField() { sf := tValue.Field(i) if sf.PkgPath != "" && !sf.Anonymous { // unexported continue diff --git a/context_test.go b/context_test.go index 53baf6d5..cb534884 100644 --- a/context_test.go +++ b/context_test.go @@ -3677,22 +3677,22 @@ func BenchmarkGetMapFromFormData(b *testing.B) { // Test case 3: Large dataset with many bracket keys largeData := make(map[string][]string) - for i := 0; i < 100; i++ { + for i := range 100 { key := fmt.Sprintf("ids[%d]", i) largeData[key] = []string{fmt.Sprintf("value%d", i)} } - for i := 0; i < 50; i++ { + for i := range 50 { key := fmt.Sprintf("names[%d]", i) largeData[key] = []string{fmt.Sprintf("name%d", i)} } - for i := 0; i < 25; i++ { + for i := range 25 { key := fmt.Sprintf("other[key%d]", i) largeData[key] = []string{fmt.Sprintf("other%d", i)} } // Test case 4: Dataset with many non-matching keys (worst case) worstCaseData := make(map[string][]string) - for i := 0; i < 100; i++ { + for i := range 100 { key := fmt.Sprintf("nonmatching%d", i) worstCaseData[key] = []string{fmt.Sprintf("value%d", i)} } @@ -3728,7 +3728,7 @@ func BenchmarkGetMapFromFormData(b *testing.B) { for _, bm := range benchmarks { b.Run(bm.name, func(b *testing.B) { b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { _, _ = getMapFromFormData(bm.data, bm.key) } }) diff --git a/gin_integration_test.go b/gin_integration_test.go index 3ea5fe2f..720b140f 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -400,7 +400,7 @@ func TestConcurrentHandleContext(t *testing.T) { var wg sync.WaitGroup iterations := 200 wg.Add(iterations) - for i := 0; i < iterations; i++ { + for range iterations { go func() { req, err := http.NewRequest(http.MethodGet, "/", nil) assert.NoError(t, err) diff --git a/internal/bytesconv/bytesconv_test.go b/internal/bytesconv/bytesconv_test.go index 60e28fb4..debfd8c2 100644 --- a/internal/bytesconv/bytesconv_test.go +++ b/internal/bytesconv/bytesconv_test.go @@ -30,7 +30,7 @@ func rawStrToBytes(s string) []byte { func TestBytesToString(t *testing.T) { data := make([]byte, 1024) - for i := 0; i < 100; i++ { + for range 100 { _, err := cRand.Read(data) if err != nil { t.Fatal(err) @@ -79,7 +79,7 @@ func RandStringBytesMaskImprSrcSB(n int) string { } func TestStringToBytes(t *testing.T) { - for i := 0; i < 100; i++ { + for range 100 { s := RandStringBytesMaskImprSrcSB(64) if !bytes.Equal(rawStrToBytes(s), StringToBytes(s)) { t.Fatal("don't match") diff --git a/utils.go b/utils.go index 62517784..2fecce46 100644 --- a/utils.go +++ b/utils.go @@ -162,7 +162,7 @@ func resolveAddress(addr []string) string { // https://stackoverflow.com/questions/53069040/checking-a-string-contains-only-ascii-characters func isASCII(s string) bool { - for i := 0; i < len(s); i++ { + for i := range len(s) { if s[i] > unicode.MaxASCII { return false } From d7776de7d444935ea4385999711bd6331a98fecb Mon Sep 17 00:00:00 2001 From: Laurent Caumont <40688874+laurentcau@users.noreply.github.com> Date: Tue, 27 Jan 2026 03:09:01 +0100 Subject: [PATCH 140/156] feat(render): add bson protocol (#4145) --- binding/binding.go | 4 ++++ binding/binding_nomsgpack.go | 4 ++++ binding/binding_test.go | 16 ++++++++++++++++ binding/bson.go | 30 ++++++++++++++++++++++++++++++ context.go | 11 +++++++++++ context_test.go | 18 ++++++++++++++++++ go.mod | 13 +++++++++---- go.sum | 17 ++++++++++------- render/bson.go | 34 ++++++++++++++++++++++++++++++++++ render/render_test.go | 26 ++++++++++++++++++++++++++ 10 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 binding/bson.go create mode 100644 render/bson.go diff --git a/binding/binding.go b/binding/binding.go index 702d0e82..eced0ae2 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -23,6 +23,7 @@ const ( MIMEYAML = "application/x-yaml" MIMEYAML2 = "application/yaml" MIMETOML = "application/toml" + MIMEBSON = "application/bson" ) // Binding describes the interface which needs to be implemented for binding the @@ -86,6 +87,7 @@ var ( Header Binding = headerBinding{} Plain BindingBody = plainBinding{} TOML BindingBody = tomlBinding{} + BSON BindingBody = bsonBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method @@ -110,6 +112,8 @@ func Default(method, contentType string) Binding { return TOML case MIMEMultipartPOSTForm: return FormMultipart + case MIMEBSON: + return BSON default: // case MIMEPOSTForm: return Form } diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go index c8e61310..ae364d79 100644 --- a/binding/binding_nomsgpack.go +++ b/binding/binding_nomsgpack.go @@ -21,6 +21,7 @@ const ( MIMEYAML = "application/x-yaml" MIMEYAML2 = "application/yaml" MIMETOML = "application/toml" + MIMEBSON = "application/bson" ) // Binding describes the interface which needs to be implemented for binding the @@ -82,6 +83,7 @@ var ( Header = headerBinding{} TOML = tomlBinding{} Plain = plainBinding{} + BSON BindingBody = bsonBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method @@ -104,6 +106,8 @@ func Default(method, contentType string) Binding { return FormMultipart case MIMETOML: return TOML + case MIMEBSON: + return BSON default: // case MIMEPOSTForm: return Form } diff --git a/binding/binding_test.go b/binding/binding_test.go index 07619ebf..a9f8b9e3 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -21,6 +21,7 @@ import ( "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" "google.golang.org/protobuf/proto" ) @@ -172,6 +173,9 @@ func TestBindingDefault(t *testing.T) { assert.Equal(t, TOML, Default(http.MethodPost, MIMETOML)) assert.Equal(t, TOML, Default(http.MethodPut, MIMETOML)) + + assert.Equal(t, BSON, Default(http.MethodPost, MIMEBSON)) + assert.Equal(t, BSON, Default(http.MethodPut, MIMEBSON)) } func TestBindingJSONNilBody(t *testing.T) { @@ -731,6 +735,18 @@ func TestBindingProtoBufFail(t *testing.T) { string(data), string(data[1:])) } +func TestBindingBSON(t *testing.T) { + var obj FooStruct + obj.Foo = "bar" + data, _ := bson.Marshal(&obj) + testBodyBinding(t, + BSON, "bson", + "/", "/", + string(data), + // note: for badbody, we remove first byte to make it invalid + string(data[1:])) +} + func TestValidationFails(t *testing.T) { var obj FooStruct req := requestWithBody(http.MethodPost, "/", `{"bar": "foo"}`) diff --git a/binding/bson.go b/binding/bson.go new file mode 100644 index 00000000..4a698247 --- /dev/null +++ b/binding/bson.go @@ -0,0 +1,30 @@ +// Copyright 2025 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "io" + "net/http" + + "go.mongodb.org/mongo-driver/bson" +) + +type bsonBinding struct{} + +func (bsonBinding) Name() string { + return "bson" +} + +func (b bsonBinding) Bind(req *http.Request, obj any) error { + buf, err := io.ReadAll(req.Body) + if err == nil { + err = b.BindBody(buf, obj) + } + return err +} + +func (bsonBinding) BindBody(body []byte, obj any) error { + return bson.Unmarshal(body, obj) +} diff --git a/context.go b/context.go index c7bc61fe..d73f59e3 100644 --- a/context.go +++ b/context.go @@ -40,6 +40,7 @@ const ( MIMEYAML2 = binding.MIMEYAML2 MIMETOML = binding.MIMETOML MIMEPROTOBUF = binding.MIMEPROTOBUF + MIMEBSON = binding.MIMEBSON ) // BodyBytesKey indicates a default body bytes key. @@ -1237,6 +1238,11 @@ func (c *Context) ProtoBuf(code int, obj any) { c.Render(code, render.ProtoBuf{Data: obj}) } +// BSON serializes the given struct as BSON into the response body. +func (c *Context) BSON(code int, obj any) { + c.Render(code, render.BSON{Data: obj}) +} + // String writes the given string into the response body. func (c *Context) String(code int, format string, values ...any) { c.Render(code, render.String{Format: format, Data: values}) @@ -1344,6 +1350,7 @@ type Negotiate struct { Data any TOMLData any PROTOBUFData any + BSONData any } // Negotiate calls different Render according to acceptable Accept format. @@ -1373,6 +1380,10 @@ func (c *Context) Negotiate(code int, config Negotiate) { data := chooseData(config.PROTOBUFData, config.Data) c.ProtoBuf(code, data) + case binding.MIMEBSON: + data := chooseData(config.BSONData, config.Data) + c.BSON(code, data) + default: c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) //nolint: errcheck } diff --git a/context_test.go b/context_test.go index cb534884..41694585 100644 --- a/context_test.go +++ b/context_test.go @@ -32,6 +32,7 @@ import ( testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" "google.golang.org/protobuf/proto" ) @@ -1701,6 +1702,23 @@ func TestContextNegotiationWithPROTOBUF(t *testing.T) { assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) } +func TestContextNegotiationWithBSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) + + c.Negotiate(http.StatusOK, Negotiate{ + Offered: []string{MIMEBSON, MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2}, + Data: H{"foo": "bar"}, + }) + + bData, _ := bson.Marshal(H{"foo": "bar"}) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, string(bData), w.Body.String()) + assert.Equal(t, "application/bson", w.Header().Get("Content-Type")) +} + func TestContextNegotiationNotSupport(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) diff --git a/go.mod b/go.mod index b755e40d..425c7a7f 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,13 @@ module github.com/gin-gonic/gin go 1.24.0 +toolchain go1.24.7 + require ( github.com/bytedance/sonic v1.14.2 github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.28.0 - github.com/goccy/go-json v0.10.2 + github.com/goccy/go-json v0.10.5 github.com/goccy/go-yaml v1.19.1 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 @@ -15,10 +17,13 @@ require ( github.com/quic-go/quic-go v0.57.1 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 + go.mongodb.org/mongo-driver v1.17.7 golang.org/x/net v0.47.0 google.golang.org/protobuf v1.36.10 ) +require gopkg.in/yaml.v3 v3.0.1 // indirect + require ( github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect @@ -30,13 +35,13 @@ require ( github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.20.0 // indirect + go.uber.org/mock v0.6.0 // indirect + golang.org/x/arch v0.22.0 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 06442efb..2a6cb14c 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.19.1 h1:3rG3+v8pkhRqoQ/88NYNMHYVGYztCOCIZ7UQhu7H+NE= github.com/goccy/go-yaml v1.19.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -41,8 +41,9 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -70,10 +71,12 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= -go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= -go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= -golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= -golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +go.mongodb.org/mongo-driver v1.17.7 h1:a9w+U3Vt67eYzcfq3k/OAv284/uUUkL0uP75VE5rCOU= +go.mongodb.org/mongo-driver v1.17.7/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= diff --git a/render/bson.go b/render/bson.go new file mode 100644 index 00000000..7332b8b2 --- /dev/null +++ b/render/bson.go @@ -0,0 +1,34 @@ +// Copyright 2025 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package render + +import ( + "net/http" + + "go.mongodb.org/mongo-driver/bson" +) + +// BSON contains the given interface object. +type BSON struct { + Data any +} + +var bsonContentType = []string{"application/bson"} + +// Render (BSON) marshals the given interface object and writes data with custom ContentType. +func (r BSON) Render(w http.ResponseWriter) error { + r.WriteContentType(w) + + bytes, err := bson.Marshal(&r.Data) + if err == nil { + _, err = w.Write(bytes) + } + return err +} + +// WriteContentType (BSONBuf) writes BSONBuf ContentType. +func (r BSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, bsonContentType) +} diff --git a/render/render_test.go b/render/render_test.go index d9ae2067..7213e48f 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -19,6 +19,7 @@ import ( testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" "google.golang.org/protobuf/proto" ) @@ -359,6 +360,31 @@ func TestRenderProtoBufFail(t *testing.T) { require.Error(t, err) } +func TestRenderBSON(t *testing.T) { + w := httptest.NewRecorder() + reps := []int64{int64(1), int64(2)} + type mystruct struct { + Label string + Reps []int64 + } + + data := &mystruct{ + Label: "test", + Reps: reps, + } + + (BSON{data}).WriteContentType(w) + bsonData, err := bson.Marshal(data) + require.NoError(t, err) + assert.Equal(t, "application/bson", w.Header().Get("Content-Type")) + + err = (BSON{data}).Render(w) + + require.NoError(t, err) + assert.Equal(t, bsonData, w.Body.Bytes()) + assert.Equal(t, "application/bson", w.Header().Get("Content-Type")) +} + func TestRenderXML(t *testing.T) { w := httptest.NewRecorder() data := xmlmap{ From 8e07d37c63e5536eb25f4af4c91eabeee4011fba Mon Sep 17 00:00:00 2001 From: Mahan Adhikari <41227164+mahanadh@users.noreply.github.com> Date: Fri, 13 Feb 2026 11:39:14 +0545 Subject: [PATCH 141/156] fix: Correct typos, improve documentation clarity, and remove dead code (#4511) * fix: correct typos and improve documentation clarity - Fix typo "Oupps" to "Oops" in recovery test panic messages - Fix confusing documentation in Bind() and ShouldBind() methods that incorrectly stated "JSON or XML as a JSON input" - Remove double period in StaticFileFS documentation comment - Remove unused ErrorTypeNu constant that had duplicate comment with ErrorTypeAny and was never used in the codebase * tech: Fix the pull request routing link --- CONTRIBUTING.md | 2 +- context.go | 8 ++++---- errors.go | 2 -- recovery_test.go | 26 +++++++++++++------------- routergroup.go | 2 +- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9703d6b4..3b05a160 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,6 +36,6 @@ Please ensure your pull request meets the following requirements: - All tests pass in available continuous integration systems (e.g., GitHub Actions). - Add or modify tests to cover your code changes. - If your pull request introduces a new feature, document it in [`docs/doc.md`](docs/doc.md), not in the README. -- Follow the checklist in the [Pull Request Template](.github/PULL_REQUEST_TEMPLATE.md:1). +- Follow the checklist in the [Pull Request Template](.github/PULL_REQUEST_TEMPLATE.md). Thank you for contributing! diff --git a/context.go b/context.go index d73f59e3..282fb9ac 100644 --- a/context.go +++ b/context.go @@ -751,8 +751,8 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string, perm // "application/json" --> JSON binding // "application/xml" --> XML binding // -// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. -// It decodes the json payload into the struct specified as a pointer. +// It parses the request's body based on the Content-Type (e.g., JSON or XML). +// It decodes the payload into the struct specified as a pointer. // It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid. func (c *Context) Bind(obj any) error { b := binding.Default(c.Request.Method, c.ContentType()) @@ -832,8 +832,8 @@ func (c *Context) MustBindWith(obj any, b binding.Binding) error { // "application/json" --> JSON binding // "application/xml" --> XML binding // -// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. -// It decodes the json payload into the struct specified as a pointer. +// It parses the request's body based on the Content-Type (e.g., JSON or XML). +// It decodes the payload into the struct specified as a pointer. // Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid. func (c *Context) ShouldBind(obj any) error { b := binding.Default(c.Request.Method, c.ContentType()) diff --git a/errors.go b/errors.go index 829e9d2c..c0d907b9 100644 --- a/errors.go +++ b/errors.go @@ -26,8 +26,6 @@ const ( ErrorTypePublic ErrorType = 1 << 1 // ErrorTypeAny indicates any other error. ErrorTypeAny ErrorType = 1<<64 - 1 - // ErrorTypeNu indicates any other error. - ErrorTypeNu = 2 ) // Error represents a error's specification. diff --git a/recovery_test.go b/recovery_test.go index 0faa3280..028c4ad6 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -22,7 +22,7 @@ func TestPanicClean(t *testing.T) { router.Use(RecoveryWithWriter(buffer)) router.GET("/recovery", func(c *Context) { c.AbortWithStatus(http.StatusBadRequest) - panic("Oupps, Houston, we have a problem") + panic("Oops, Houston, we have a problem") }) // RUN w := PerformRequest(router, http.MethodGet, "/recovery", @@ -52,14 +52,14 @@ func TestPanicInHandler(t *testing.T) { router := New() router.Use(RecoveryWithWriter(buffer)) router.GET("/recovery", func(_ *Context) { - panic("Oupps, Houston, we have a problem") + panic("Oops, Houston, we have a problem") }) // RUN w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "panic recovered") - assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") + assert.Contains(t, buffer.String(), "Oops, Houston, we have a problem") assert.Contains(t, buffer.String(), t.Name()) assert.NotContains(t, buffer.String(), "GET /recovery") @@ -80,7 +80,7 @@ func TestPanicWithAbort(t *testing.T) { router.Use(RecoveryWithWriter(nil)) router.GET("/recovery", func(c *Context) { c.AbortWithStatus(http.StatusBadRequest) - panic("Oupps, Houston, we have a problem") + panic("Oops, Houston, we have a problem") }) // RUN w := PerformRequest(router, http.MethodGet, "/recovery") @@ -162,14 +162,14 @@ func TestCustomRecoveryWithWriter(t *testing.T) { } router.Use(CustomRecoveryWithWriter(buffer, handleRecovery)) router.GET("/recovery", func(_ *Context) { - panic("Oupps, Houston, we have a problem") + panic("Oops, Houston, we have a problem") }) // RUN w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "panic recovered") - assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") + assert.Contains(t, buffer.String(), "Oops, Houston, we have a problem") assert.Contains(t, buffer.String(), t.Name()) assert.NotContains(t, buffer.String(), "GET /recovery") @@ -181,7 +181,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") - assert.Equal(t, strings.Repeat("Oupps, Houston, we have a problem", 2), errBuffer.String()) + assert.Equal(t, strings.Repeat("Oops, Houston, we have a problem", 2), errBuffer.String()) SetMode(TestMode) } @@ -197,14 +197,14 @@ func TestCustomRecovery(t *testing.T) { } router.Use(CustomRecovery(handleRecovery)) router.GET("/recovery", func(_ *Context) { - panic("Oupps, Houston, we have a problem") + panic("Oops, Houston, we have a problem") }) // RUN w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "panic recovered") - assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") + assert.Contains(t, buffer.String(), "Oops, Houston, we have a problem") assert.Contains(t, buffer.String(), t.Name()) assert.NotContains(t, buffer.String(), "GET /recovery") @@ -216,7 +216,7 @@ func TestCustomRecovery(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") - assert.Equal(t, strings.Repeat("Oupps, Houston, we have a problem", 2), errBuffer.String()) + assert.Equal(t, strings.Repeat("Oops, Houston, we have a problem", 2), errBuffer.String()) SetMode(TestMode) } @@ -232,14 +232,14 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) { } router.Use(RecoveryWithWriter(DefaultErrorWriter, handleRecovery)) router.GET("/recovery", func(_ *Context) { - panic("Oupps, Houston, we have a problem") + panic("Oops, Houston, we have a problem") }) // RUN w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "panic recovered") - assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") + assert.Contains(t, buffer.String(), "Oops, Houston, we have a problem") assert.Contains(t, buffer.String(), t.Name()) assert.NotContains(t, buffer.String(), "GET /recovery") @@ -251,7 +251,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") - assert.Equal(t, strings.Repeat("Oupps, Houston, we have a problem", 2), errBuffer.String()) + assert.Equal(t, strings.Repeat("Oops, Houston, we have a problem", 2), errBuffer.String()) SetMode(TestMode) } diff --git a/routergroup.go b/routergroup.go index b2540ec1..c01b917e 100644 --- a/routergroup.go +++ b/routergroup.go @@ -169,7 +169,7 @@ func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes { }) } -// StaticFileFS works just like `StaticFile` but a custom `http.FileSystem` can be used instead.. +// StaticFileFS works just like `StaticFile` but a custom `http.FileSystem` can be used instead. // router.StaticFileFS("favicon.ico", "./resources/favicon.ico", Dir{".", false}) // Gin by default uses: gin.Dir() func (group *RouterGroup) StaticFileFS(relativePath, filepath string, fs http.FileSystem) IRoutes { From 488f8c3ffa579a8d19beb2bae95ff8ef36b3d53f Mon Sep 17 00:00:00 2001 From: Varun Chawla <34209028+veeceey@users.noreply.github.com> Date: Thu, 12 Feb 2026 21:55:23 -0800 Subject: [PATCH 142/156] refactor: replace magic numbers with named constants in bodyAllowedForStatus (#4529) Use http.StatusContinue and http.StatusOK instead of hardcoded 100 and 199 for the 1xx informational status range check, consistent with the pattern already used in logger.go. Fixes #4489 --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index 282fb9ac..a00d1e55 100644 --- a/context.go +++ b/context.go @@ -1058,7 +1058,7 @@ func (c *Context) requestHeader(key string) string { // bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function. func bodyAllowedForStatus(status int) bool { switch { - case status >= 100 && status <= 199: + case status >= http.StatusContinue && status < http.StatusOK: return false case status == http.StatusNoContent: return false From 882f42b0ed7bbb0e5f381ea45ab8fa36638b85d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:56:24 +0800 Subject: [PATCH 143/156] chore(deps): bump golang.org/x/net from 0.47.0 to 0.49.0 (#4508) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.47.0 to 0.49.0. - [Commits](https://github.com/golang/net/compare/v0.47.0...v0.49.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.49.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 425c7a7f..1863f04c 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 go.mongodb.org/mongo-driver v1.17.7 - golang.org/x/net v0.47.0 + golang.org/x/net v0.49.0 google.golang.org/protobuf v1.36.10 ) @@ -41,7 +41,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.6.0 // indirect golang.org/x/arch v0.22.0 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect ) diff --git a/go.sum b/go.sum index 2a6cb14c..9b17d180 100644 --- a/go.sum +++ b/go.sum @@ -77,15 +77,15 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= From 71cefce08e9ee91f6fb841a668207ccfda1df169 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:56:40 +0800 Subject: [PATCH 144/156] chore(deps): bump github.com/goccy/go-yaml from 1.19.1 to 1.19.2 (#4507) Bumps [github.com/goccy/go-yaml](https://github.com/goccy/go-yaml) from 1.19.1 to 1.19.2. - [Release notes](https://github.com/goccy/go-yaml/releases) - [Changelog](https://github.com/goccy/go-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-yaml/compare/v1.19.1...v1.19.2) --- updated-dependencies: - dependency-name: github.com/goccy/go-yaml dependency-version: 1.19.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1863f04c..0496bc80 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.28.0 github.com/goccy/go-json v0.10.5 - github.com/goccy/go-yaml v1.19.1 + github.com/goccy/go-yaml v1.19.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 diff --git a/go.sum b/go.sum index 9b17d180..f59108a6 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0 github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/goccy/go-yaml v1.19.1 h1:3rG3+v8pkhRqoQ/88NYNMHYVGYztCOCIZ7UQhu7H+NE= -github.com/goccy/go-yaml v1.19.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= From 6e3ac82fa71efda7d042b009b58215ca321f32ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 Feb 2026 10:57:06 +0800 Subject: [PATCH 145/156] chore(deps): bump github.com/quic-go/quic-go from 0.57.1 to 0.59.0 (#4532) Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.57.1 to 0.59.0. - [Release notes](https://github.com/quic-go/quic-go/releases) - [Commits](https://github.com/quic-go/quic-go/compare/v0.57.1...v0.59.0) --- updated-dependencies: - dependency-name: github.com/quic-go/quic-go dependency-version: 0.59.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 0496bc80..1bd4001f 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/modern-go/reflect2 v1.0.2 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/quic-go/quic-go v0.57.1 + github.com/quic-go/quic-go v0.59.0 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 go.mongodb.org/mongo-driver v1.17.7 diff --git a/go.sum b/go.sum index f59108a6..6609e78d 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10= -github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -86,8 +86,6 @@ golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From bf52b077c812e1917d17944cb6f109ab4f462ae7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 Feb 2026 10:57:27 +0800 Subject: [PATCH 146/156] chore(deps): bump go.mongodb.org/mongo-driver from 1.17.7 to 1.17.9 (#4533) Bumps [go.mongodb.org/mongo-driver](https://github.com/mongodb/mongo-go-driver) from 1.17.7 to 1.17.9. - [Release notes](https://github.com/mongodb/mongo-go-driver/releases) - [Commits](https://github.com/mongodb/mongo-go-driver/compare/v1.17.7...v1.17.9) --- updated-dependencies: - dependency-name: go.mongodb.org/mongo-driver dependency-version: 1.17.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1bd4001f..42774882 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/quic-go/quic-go v0.59.0 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 - go.mongodb.org/mongo-driver v1.17.7 + go.mongodb.org/mongo-driver v1.17.9 golang.org/x/net v0.49.0 google.golang.org/protobuf v1.36.10 ) diff --git a/go.sum b/go.sum index 6609e78d..51355809 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= -go.mongodb.org/mongo-driver v1.17.7 h1:a9w+U3Vt67eYzcfq3k/OAv284/uUUkL0uP75VE5rCOU= -go.mongodb.org/mongo-driver v1.17.7/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU= +go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= From f5c267d2f84ba8192dd9c4c67b0490abfa3dfe7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 Feb 2026 10:58:17 +0800 Subject: [PATCH 147/156] chore(deps): bump aquasecurity/trivy-action in the actions group (#4534) Bumps the actions group with 1 update: [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action). Updates `aquasecurity/trivy-action` from 0.33.1 to 0.34.0 - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/0.33.1...0.34.0) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-version: 0.34.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/trivy-scan.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index b86aed7f..ec8f55ad 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -27,7 +27,7 @@ jobs: fetch-depth: 0 - name: Run Trivy vulnerability scanner (source code) - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@0.34.0 with: scan-type: 'fs' scan-ref: '.' @@ -44,7 +44,7 @@ jobs: sarif_file: 'trivy-results.sarif' - name: Run Trivy scanner (table output for logs) - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@0.34.0 if: always() with: scan-type: 'fs' From 216a4a7c283bae102c4e365662566c895dbdad50 Mon Sep 17 00:00:00 2001 From: Amirhf Date: Tue, 17 Feb 2026 20:08:36 +0330 Subject: [PATCH 148/156] test(render): add comprehensive tests for MsgPack render (#4537) * test(render): add comprehensive tests for MsgPack render * test(render): make msgpack tests deterministic Decode the rendered msgpack output and assert values instead of comparing raw bytes (which can vary with map iteration order). Enable MsgpackHandle.RawToString so msgpack strings decode as Go strings. --------- Co-authored-by: AmirHossein Fallah --- render/render_msgpack_test.go | 58 ++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/render/render_msgpack_test.go b/render/render_msgpack_test.go index 579897cc..48b23870 100644 --- a/render/render_msgpack_test.go +++ b/render/render_msgpack_test.go @@ -7,7 +7,7 @@ package render import ( - "bytes" + "errors" "net/http/httptest" "testing" @@ -16,9 +16,6 @@ import ( "github.com/ugorji/go/codec" ) -// TODO unit tests -// test errors - func TestRenderMsgPack(t *testing.T) { w := httptest.NewRecorder() data := map[string]any{ @@ -32,13 +29,52 @@ func TestRenderMsgPack(t *testing.T) { require.NoError(t, err) - h := new(codec.MsgpackHandle) - assert.NotNil(t, h) - buf := bytes.NewBuffer([]byte{}) - assert.NotNil(t, buf) - err = codec.NewEncoder(buf, h).Encode(data) - + var decoded map[string]any + var mh codec.MsgpackHandle + mh.RawToString = true + err = codec.NewDecoderBytes(w.Body.Bytes(), &mh).Decode(&decoded) require.NoError(t, err) - assert.Equal(t, w.Body.String(), buf.String()) + assert.Equal(t, data, decoded) assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) } + +func TestWriteMsgPack(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]any{ + "foo": "bar", + "num": 42, + } + + err := WriteMsgPack(w, data) + require.NoError(t, err) + + assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) + + var decoded map[string]any + var mh codec.MsgpackHandle + mh.RawToString = true + err = codec.NewDecoderBytes(w.Body.Bytes(), &mh).Decode(&decoded) + require.NoError(t, err) + assert.Len(t, decoded, 2) + assert.Equal(t, "bar", decoded["foo"]) + assert.EqualValues(t, 42, decoded["num"]) +} + +type failWriter struct { + *httptest.ResponseRecorder +} + +func (w *failWriter) Write(data []byte) (int, error) { + return 0, errors.New("write error") +} + +func TestRenderMsgPackError(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]any{ + "foo": "bar", + } + + err := (MsgPack{data}).Render(&failWriter{w}) + require.Error(t, err) + assert.Contains(t, err.Error(), "write error") +} From 5f424ff6f6316511b7f1598ea6dba62ea1a62eb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 00:39:40 +0800 Subject: [PATCH 149/156] chore(deps): bump github.com/bytedance/sonic from 1.14.2 to 1.15.0 (#4539) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.14.2 to 1.15.0. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.14.2...v1.15.0) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-version: 1.15.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 42774882..87570423 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.0 toolchain go1.24.7 require ( - github.com/bytedance/sonic v1.14.2 + github.com/bytedance/sonic v1.15.0 github.com/gin-contrib/sse v1.1.0 github.com/go-playground/validator/v10 v10.28.0 github.com/goccy/go-json v0.10.5 @@ -26,7 +26,7 @@ require gopkg.in/yaml.v3 v3.0.1 // indirect require ( github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic/loader v0.4.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect diff --git a/go.sum b/go.sum index 51355809..207dff51 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,9 @@ github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= -github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= -github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= -github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= From 5260de6a83283abb87e827130accd495ad543cf3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 00:40:02 +0800 Subject: [PATCH 150/156] chore(deps): bump golang.org/x/net from 0.49.0 to 0.50.0 (#4538) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.49.0 to 0.50.0. - [Commits](https://github.com/golang/net/compare/v0.49.0...v0.50.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.50.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 87570423..19ff4752 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 go.mongodb.org/mongo-driver v1.17.9 - golang.org/x/net v0.49.0 + golang.org/x/net v0.50.0 google.golang.org/protobuf v1.36.10 ) @@ -41,7 +41,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.6.0 // indirect golang.org/x/arch v0.22.0 // indirect - golang.org/x/crypto v0.47.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.33.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect ) diff --git a/go.sum b/go.sum index 207dff51..ef6d6eff 100644 --- a/go.sum +++ b/go.sum @@ -77,15 +77,15 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 00900fb3e1ea9dde33985a0e4f6afec793d5e786 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 21 Feb 2026 22:32:32 +0800 Subject: [PATCH 151/156] ci: update CI workflows and standardize Trivy config quotes (#4531) - Update gin workflow to use v2.9 and add Go 1.26 to the matrix - Upgrade Trivy action to v0.34.0 in the scan workflow - Change all single quotes to double quotes in Trivy workflow configuration Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 4 ++-- .github/workflows/trivy-scan.yml | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 8ece7f1d..df774eab 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -26,14 +26,14 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v9 with: - version: v2.6 + version: v2.9 args: --verbose test: needs: lint strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.24", "1.25"] + go: ["1.24", "1.25", "1.26"] test-tags: [ "", diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index ec8f55ad..57aceb76 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -9,7 +9,7 @@ on: - master schedule: # Run daily at 00:00 UTC - - cron: '0 0 * * *' + - cron: "0 0 * * *" workflow_dispatch: # Allow manual trigger permissions: @@ -29,28 +29,28 @@ jobs: - name: Run Trivy vulnerability scanner (source code) uses: aquasecurity/trivy-action@0.34.0 with: - scan-type: 'fs' - scan-ref: '.' - scanners: 'vuln,secret,misconfig' - format: 'sarif' - output: 'trivy-results.sarif' - severity: 'CRITICAL,HIGH,MEDIUM' + scan-type: "fs" + scan-ref: "." + scanners: "vuln,secret,misconfig" + format: "sarif" + output: "trivy-results.sarif" + severity: "CRITICAL,HIGH,MEDIUM" ignore-unfixed: true - name: Upload Trivy results to GitHub Security tab uses: github/codeql-action/upload-sarif@v4 if: always() with: - sarif_file: 'trivy-results.sarif' + sarif_file: "trivy-results.sarif" - name: Run Trivy scanner (table output for logs) uses: aquasecurity/trivy-action@0.34.0 if: always() with: - scan-type: 'fs' - scan-ref: '.' - scanners: 'vuln,secret,misconfig' - format: 'table' - severity: 'CRITICAL,HIGH,MEDIUM' + scan-type: "fs" + scan-ref: "." + scanners: "vuln,secret,misconfig" + format: "table" + severity: "CRITICAL,HIGH,MEDIUM" ignore-unfixed: true - exit-code: '1' + exit-code: "1" From 0c219e7902e88b27be1736d46efb241e4482b30f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 21 Feb 2026 22:33:30 +0800 Subject: [PATCH 152/156] chore(deps): bump aquasecurity/trivy-action in the actions group (#4544) Bumps the actions group with 1 update: [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action). Updates `aquasecurity/trivy-action` from 0.34.0 to 0.34.1 - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/0.34.0...0.34.1) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-version: 0.34.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/trivy-scan.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index 57aceb76..a4c62bf4 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -27,7 +27,7 @@ jobs: fetch-depth: 0 - name: Run Trivy vulnerability scanner (source code) - uses: aquasecurity/trivy-action@0.34.0 + uses: aquasecurity/trivy-action@0.34.1 with: scan-type: "fs" scan-ref: "." @@ -44,7 +44,7 @@ jobs: sarif_file: "trivy-results.sarif" - name: Run Trivy scanner (table output for logs) - uses: aquasecurity/trivy-action@0.34.0 + uses: aquasecurity/trivy-action@0.34.1 if: always() with: scan-type: "fs" From 81dba468722f41347ed74ee66e9c1781d72f68a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 21 Feb 2026 22:39:56 +0800 Subject: [PATCH 153/156] chore(deps): bump github.com/go-playground/validator/v10 (#4509) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.28.0 to 10.30.1. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.28.0...v10.30.1) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-version: 10.30.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 19ff4752..459e9cdc 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.24.7 require ( github.com/bytedance/sonic v1.15.0 github.com/gin-contrib/sse v1.1.0 - github.com/go-playground/validator/v10 v10.28.0 + github.com/go-playground/validator/v10 v10.30.1 github.com/goccy/go-json v0.10.5 github.com/goccy/go-yaml v1.19.2 github.com/json-iterator/go v1.1.12 @@ -29,7 +29,7 @@ require ( github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.10 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect diff --git a/go.sum b/go.sum index ef6d6eff..624997b8 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= -github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -20,8 +20,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= -github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= From 1b414bd54e467cb7278de8877401af07f185edb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 23:18:28 +0800 Subject: [PATCH 154/156] chore(deps): bump goreleaser/goreleaser-action in the actions group (#4546) Bumps the actions group with 1 update: [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action). Updates `goreleaser/goreleaser-action` from 6 to 7 - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](https://github.com/goreleaser/goreleaser-action/compare/v6...v7) --- updated-dependencies: - dependency-name: goreleaser/goreleaser-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 0098b952..ea933e7e 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -21,7 +21,7 @@ jobs: with: go-version: "^1" - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 + uses: goreleaser/goreleaser-action@v7 with: # either 'goreleaser' (default) or 'goreleaser-pro' distribution: goreleaser From ba093d19477b896ac89a7fc3246af23d290b8e26 Mon Sep 17 00:00:00 2001 From: Bob Du Date: Fri, 27 Feb 2026 23:20:01 +0800 Subject: [PATCH 155/156] chore(binding): upgrade bson dependency to mongo-driver v2 (#4549) Signed-off-by: Bob Du --- binding/binding_test.go | 2 +- binding/bson.go | 2 +- context_test.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- render/bson.go | 2 +- render/render_test.go | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index a9f8b9e3..f90488cd 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -21,7 +21,7 @@ import ( "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" "google.golang.org/protobuf/proto" ) diff --git a/binding/bson.go b/binding/bson.go index 4a698247..464890f0 100644 --- a/binding/bson.go +++ b/binding/bson.go @@ -8,7 +8,7 @@ import ( "io" "net/http" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) type bsonBinding struct{} diff --git a/context_test.go b/context_test.go index 41694585..41ec7bd5 100644 --- a/context_test.go +++ b/context_test.go @@ -32,7 +32,7 @@ import ( testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" "google.golang.org/protobuf/proto" ) diff --git a/go.mod b/go.mod index 459e9cdc..0f278049 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/quic-go/quic-go v0.59.0 github.com/stretchr/testify v1.11.1 github.com/ugorji/go/codec v1.3.1 - go.mongodb.org/mongo-driver v1.17.9 + go.mongodb.org/mongo-driver/v2 v2.5.0 golang.org/x/net v0.50.0 google.golang.org/protobuf v1.36.10 ) diff --git a/go.sum b/go.sum index 624997b8..71c07795 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= -go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU= -go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= +go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= +go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= diff --git a/render/bson.go b/render/bson.go index 7332b8b2..07f02333 100644 --- a/render/bson.go +++ b/render/bson.go @@ -7,7 +7,7 @@ package render import ( "net/http" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) // BSON contains the given interface object. diff --git a/render/render_test.go b/render/render_test.go index 7213e48f..40e7e7be 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -19,7 +19,7 @@ import ( testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" "google.golang.org/protobuf/proto" ) From db309081bc5c137b2aa15701ef53f7f19788da25 Mon Sep 17 00:00:00 2001 From: Jacob McSwain Date: Fri, 27 Feb 2026 09:33:46 -0600 Subject: [PATCH 156/156] chore(logger): allow skipping query string output (#4547) This is useful for APIs that might have sensitive information in the query string, such as API keys. This patch does not change the default behavior of the code unless the new `SkipQueryString` config option is passed in. The "skip" term is a bit of a misnomer here, as this doesn't actually skip that log, but modifies the output. I'm open to suggestions for a more appropriate name. Co-authored-by: Bo-Yi Wu --- docs/doc.md | 15 +++++++++++++++ logger.go | 7 ++++++- logger_test.go | 14 ++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/doc.md b/docs/doc.md index 449c8d02..7201df5c 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -22,6 +22,7 @@ - [How to write log file](#how-to-write-log-file) - [Custom Log Format](#custom-log-format) - [Controlling Log output coloring](#controlling-log-output-coloring) + - [Avoid logging query strings](#avoid-loging-query-strings) - [Model binding and validation](#model-binding-and-validation) - [Custom Validators](#custom-validators) - [Only Bind Query String](#only-bind-query-string) @@ -592,6 +593,20 @@ func main() { } ``` +### Avoid logging query strings + +```go +func main() { + router := gin.New() + + // SkipQueryString indicates that the logger should not log the query string. + // For example, /path?q=1 will be logged as /path + loggerConfig := gin.LoggerConfig{SkipQueryString: true} + + router.Use(gin.LoggerWithConfig(loggerConfig)) +} +``` + ### Model binding and validation To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz). diff --git a/logger.go b/logger.go index 6441f7ea..cf92553a 100644 --- a/logger.go +++ b/logger.go @@ -48,6 +48,11 @@ type LoggerConfig struct { // Optional. SkipPaths []string + // SkipQueryString indicates that query strings should not be written + // for cases such as when API keys are passed via query strings. + // Optional. Default value is false. + SkipQueryString bool + // Skip is a Skipper that indicates which logs should not be written. // Optional. Skip Skipper @@ -298,7 +303,7 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { param.BodySize = c.Writer.Size() - if raw != "" { + if raw != "" && !conf.SkipQueryString { path = path + "?" + raw } diff --git a/logger_test.go b/logger_test.go index 53d0df95..8099a894 100644 --- a/logger_test.go +++ b/logger_test.go @@ -471,3 +471,17 @@ func TestForceConsoleColor(t *testing.T) { // reset console color mode. consoleColorMode = autoColor } + +func TestLoggerWithConfigSkipQueryString(t *testing.T) { + buffer := new(strings.Builder) + router := New() + router.Use(LoggerWithConfig(LoggerConfig{ + Output: buffer, + SkipQueryString: true, + })) + router.GET("/logged", func(c *Context) { c.Status(http.StatusOK) }) + + PerformRequest(router, "GET", "/logged?a=21") + assert.Contains(t, buffer.String(), "200") + assert.NotContains(t, buffer.String(), "a=21") +}