Merge tag 'tags/v1.11.0'
This commit is contained in:
+95
-1
@@ -1,5 +1,99 @@
|
|||||||
# Gin ChangeLog
|
# 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
|
||||||
|
|
||||||
|
* 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
|
## Gin v1.10.0
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
@@ -488,7 +582,7 @@
|
|||||||
- [FIX] Refactor render
|
- [FIX] Refactor render
|
||||||
- [FIX] Reworked tests
|
- [FIX] Reworked tests
|
||||||
- [FIX] logger now supports cygwin
|
- [FIX] logger now supports cygwin
|
||||||
- [FIX] Use X-Forwarded-For before X-Real-Ip
|
- [FIX] Use X-Forwarded-For before X-Real-IP
|
||||||
- [FIX] time.Time binding (#904)
|
- [FIX] time.Time binding (#904)
|
||||||
|
|
||||||
## Gin 1.1.4
|
## Gin 1.1.4
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ If you need performance and good productivity, you will love Gin.
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
Gin requires [Go](https://go.dev/) version [1.21](https://go.dev/doc/devel/release#go1.21.0) or above.
|
Gin requires [Go](https://go.dev/) version [1.23](https://go.dev/doc/devel/release#go1.23.0) or above.
|
||||||
|
|
||||||
### Getting Gin
|
### Getting Gin
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ func main() {
|
|||||||
To run the code, use the `go run` command, like:
|
To run the code, use the `go run` command, like:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ go run example.go
|
go run example.go
|
||||||
```
|
```
|
||||||
|
|
||||||
Then visit [`0.0.0.0:8080/ping`](http://0.0.0.0:8080/ping) in your browser to see the response!
|
Then visit [`0.0.0.0:8080/ping`](http://0.0.0.0:8080/ping) in your browser to see the response!
|
||||||
@@ -83,7 +83,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:
|
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-cn/docs/)
|
||||||
- [繁體中文](https://gin-gonic.com/zh-tw/docs/)
|
- [繁體中文](https://gin-gonic.com/zh-tw/docs/)
|
||||||
- [日本語](https://gin-gonic.com/ja/docs/)
|
- [日本語](https://gin-gonic.com/ja/docs/)
|
||||||
@@ -92,6 +92,8 @@ The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in
|
|||||||
- [Turkish](https://gin-gonic.com/tr/docs/)
|
- [Turkish](https://gin-gonic.com/tr/docs/)
|
||||||
- [Persian](https://gin-gonic.com/fa/docs/)
|
- [Persian](https://gin-gonic.com/fa/docs/)
|
||||||
- [Português](https://gin-gonic.com/pt/docs/)
|
- [Português](https://gin-gonic.com/pt/docs/)
|
||||||
|
- [Russian](https://gin-gonic.com/ru/docs/)
|
||||||
|
- [Indonesian](https://gin-gonic.com/id/docs/)
|
||||||
|
|
||||||
### Articles
|
### Articles
|
||||||
|
|
||||||
@@ -141,7 +143,7 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr
|
|||||||
|
|
||||||
## Middleware
|
## Middleware
|
||||||
|
|
||||||
You can find many useful Gin middlewares at [gin-contrib](https://git.company.lan/gopkg/gin-contrib).
|
You can find many useful Gin middlewares at [gin-contrib](https://git.company.lan/gopkg/gin-contrib) and [gin-gonic/contrib](https://github.com/gin-gonic/contrib).
|
||||||
|
|
||||||
## Uses
|
## Uses
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -90,7 +90,7 @@ func TestBasicAuthSucceed(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest("GET", "/login", nil)
|
req, _ := http.NewRequest(http.MethodGet, "/login", nil)
|
||||||
req.Header.Set("Authorization", authorizationHeader("admin", "password"))
|
req.Header.Set("Authorization", authorizationHeader("admin", "password"))
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ func TestBasicAuth401(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest("GET", "/login", nil)
|
req, _ := http.NewRequest(http.MethodGet, "/login", nil)
|
||||||
req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password")))
|
req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password")))
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest("GET", "/login", nil)
|
req, _ := http.NewRequest(http.MethodGet, "/login", nil)
|
||||||
req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password")))
|
req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password")))
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ func TestBasicAuthForProxySucceed(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest("GET", "/test", nil)
|
req, _ := http.NewRequest(http.MethodGet, "/test", nil)
|
||||||
req.Header.Set("Proxy-Authorization", authorizationHeader("admin", "password"))
|
req.Header.Set("Proxy-Authorization", authorizationHeader("admin", "password"))
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ func TestBasicAuthForProxy407(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest("GET", "/test", nil)
|
req, _ := http.NewRequest(http.MethodGet, "/test", nil)
|
||||||
req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password")))
|
req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password")))
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -14,21 +14,21 @@ import (
|
|||||||
func BenchmarkOneRoute(B *testing.B) {
|
func BenchmarkOneRoute(B *testing.B) {
|
||||||
router := New()
|
router := New()
|
||||||
router.GET("/ping", func(c *Context) {})
|
router.GET("/ping", func(c *Context) {})
|
||||||
runRequest(B, router, "GET", "/ping")
|
runRequest(B, router, http.MethodGet, "/ping")
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkRecoveryMiddleware(B *testing.B) {
|
func BenchmarkRecoveryMiddleware(B *testing.B) {
|
||||||
router := New()
|
router := New()
|
||||||
router.Use(Recovery())
|
router.Use(Recovery())
|
||||||
router.GET("/", func(c *Context) {})
|
router.GET("/", func(c *Context) {})
|
||||||
runRequest(B, router, "GET", "/")
|
runRequest(B, router, http.MethodGet, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkLoggerMiddleware(B *testing.B) {
|
func BenchmarkLoggerMiddleware(B *testing.B) {
|
||||||
router := New()
|
router := New()
|
||||||
router.Use(LoggerWithWriter(newMockWriter()))
|
router.Use(LoggerWithWriter(newMockWriter()))
|
||||||
router.GET("/", func(c *Context) {})
|
router.GET("/", func(c *Context) {})
|
||||||
runRequest(B, router, "GET", "/")
|
runRequest(B, router, http.MethodGet, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkManyHandlers(B *testing.B) {
|
func BenchmarkManyHandlers(B *testing.B) {
|
||||||
@@ -37,7 +37,7 @@ func BenchmarkManyHandlers(B *testing.B) {
|
|||||||
router.Use(func(c *Context) {})
|
router.Use(func(c *Context) {})
|
||||||
router.Use(func(c *Context) {})
|
router.Use(func(c *Context) {})
|
||||||
router.GET("/ping", func(c *Context) {})
|
router.GET("/ping", func(c *Context) {})
|
||||||
runRequest(B, router, "GET", "/ping")
|
runRequest(B, router, http.MethodGet, "/ping")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Benchmark5Params(B *testing.B) {
|
func Benchmark5Params(B *testing.B) {
|
||||||
@@ -45,7 +45,7 @@ func Benchmark5Params(B *testing.B) {
|
|||||||
router := New()
|
router := New()
|
||||||
router.Use(func(c *Context) {})
|
router.Use(func(c *Context) {})
|
||||||
router.GET("/param/:param1/:params2/:param3/:param4/:param5", func(c *Context) {})
|
router.GET("/param/:param1/:params2/:param3/:param4/:param5", func(c *Context) {})
|
||||||
runRequest(B, router, "GET", "/param/path/to/parameter/john/12345")
|
runRequest(B, router, http.MethodGet, "/param/path/to/parameter/john/12345")
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkOneRouteJSON(B *testing.B) {
|
func BenchmarkOneRouteJSON(B *testing.B) {
|
||||||
@@ -56,7 +56,7 @@ func BenchmarkOneRouteJSON(B *testing.B) {
|
|||||||
router.GET("/json", func(c *Context) {
|
router.GET("/json", func(c *Context) {
|
||||||
c.JSON(http.StatusOK, data)
|
c.JSON(http.StatusOK, data)
|
||||||
})
|
})
|
||||||
runRequest(B, router, "GET", "/json")
|
runRequest(B, router, http.MethodGet, "/json")
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkOneRouteHTML(B *testing.B) {
|
func BenchmarkOneRouteHTML(B *testing.B) {
|
||||||
@@ -68,7 +68,7 @@ func BenchmarkOneRouteHTML(B *testing.B) {
|
|||||||
router.GET("/html", func(c *Context) {
|
router.GET("/html", func(c *Context) {
|
||||||
c.HTML(http.StatusOK, "index", "hola")
|
c.HTML(http.StatusOK, "index", "hola")
|
||||||
})
|
})
|
||||||
runRequest(B, router, "GET", "/html")
|
runRequest(B, router, http.MethodGet, "/html")
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkOneRouteSet(B *testing.B) {
|
func BenchmarkOneRouteSet(B *testing.B) {
|
||||||
@@ -76,7 +76,7 @@ func BenchmarkOneRouteSet(B *testing.B) {
|
|||||||
router.GET("/ping", func(c *Context) {
|
router.GET("/ping", func(c *Context) {
|
||||||
c.Set("key", "value")
|
c.Set("key", "value")
|
||||||
})
|
})
|
||||||
runRequest(B, router, "GET", "/ping")
|
runRequest(B, router, http.MethodGet, "/ping")
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkOneRouteString(B *testing.B) {
|
func BenchmarkOneRouteString(B *testing.B) {
|
||||||
@@ -84,13 +84,13 @@ func BenchmarkOneRouteString(B *testing.B) {
|
|||||||
router.GET("/text", func(c *Context) {
|
router.GET("/text", func(c *Context) {
|
||||||
c.String(http.StatusOK, "this is a plain text")
|
c.String(http.StatusOK, "this is a plain text")
|
||||||
})
|
})
|
||||||
runRequest(B, router, "GET", "/text")
|
runRequest(B, router, http.MethodGet, "/text")
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkManyRoutesFist(B *testing.B) {
|
func BenchmarkManyRoutesFist(B *testing.B) {
|
||||||
router := New()
|
router := New()
|
||||||
router.Any("/ping", func(c *Context) {})
|
router.Any("/ping", func(c *Context) {})
|
||||||
runRequest(B, router, "GET", "/ping")
|
runRequest(B, router, http.MethodGet, "/ping")
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkManyRoutesLast(B *testing.B) {
|
func BenchmarkManyRoutesLast(B *testing.B) {
|
||||||
@@ -103,7 +103,7 @@ func Benchmark404(B *testing.B) {
|
|||||||
router := New()
|
router := New()
|
||||||
router.Any("/something", func(c *Context) {})
|
router.Any("/something", func(c *Context) {})
|
||||||
router.NoRoute(func(c *Context) {})
|
router.NoRoute(func(c *Context) {})
|
||||||
runRequest(B, router, "GET", "/ping")
|
runRequest(B, router, http.MethodGet, "/ping")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Benchmark404Many(B *testing.B) {
|
func Benchmark404Many(B *testing.B) {
|
||||||
@@ -118,7 +118,7 @@ func Benchmark404Many(B *testing.B) {
|
|||||||
router.GET("/user/:id/:mode", func(c *Context) {})
|
router.GET("/user/:id/:mode", func(c *Context) {})
|
||||||
|
|
||||||
router.NoRoute(func(c *Context) {})
|
router.NoRoute(func(c *Context) {})
|
||||||
runRequest(B, router, "GET", "/viewfake")
|
runRequest(B, router, http.MethodGet, "/viewfake")
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockWriter struct {
|
type mockWriter struct {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -39,20 +40,20 @@ func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body,
|
|||||||
assert.Equal(t, name, b.Name())
|
assert.Equal(t, name, b.Name())
|
||||||
|
|
||||||
obj := FooStruct{}
|
obj := FooStruct{}
|
||||||
req := requestWithBody("POST", path, body)
|
req := requestWithBody(http.MethodPost, path, body)
|
||||||
req.Header.Add("Content-Type", MIMEMSGPACK)
|
req.Header.Add("Content-Type", MIMEMSGPACK)
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "bar", obj.Foo)
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
|
||||||
obj = FooStruct{}
|
obj = FooStruct{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody(http.MethodPost, badPath, badBody)
|
||||||
req.Header.Add("Content-Type", MIMEMSGPACK)
|
req.Header.Add("Content-Type", MIMEMSGPACK)
|
||||||
err = MsgPack.Bind(req, &obj)
|
err = MsgPack.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingDefaultMsgPack(t *testing.T) {
|
func TestBindingDefaultMsgPack(t *testing.T) {
|
||||||
assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK))
|
assert.Equal(t, MsgPack, Default(http.MethodPost, MIMEMSGPACK))
|
||||||
assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2))
|
assert.Equal(t, MsgPack, Default(http.MethodPut, MIMEMSGPACK2))
|
||||||
}
|
}
|
||||||
|
|||||||
+128
-124
@@ -51,8 +51,6 @@ type FooBarFileStruct struct {
|
|||||||
type FooBarFileFailStruct struct {
|
type FooBarFileFailStruct struct {
|
||||||
FooBarStruct
|
FooBarStruct
|
||||||
File *multipart.FileHeader `invalid_name:"file" binding:"required"`
|
File *multipart.FileHeader `invalid_name:"file" binding:"required"`
|
||||||
// for unexport test
|
|
||||||
data *multipart.FileHeader `form:"data" binding:"required"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FooDefaultBarStruct struct {
|
type FooDefaultBarStruct struct {
|
||||||
@@ -73,11 +71,15 @@ type FooBarStructForTimeType struct {
|
|||||||
TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"`
|
TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"`
|
||||||
CreateTime time.Time `form:"createTime" time_format:"unixNano"`
|
CreateTime time.Time `form:"createTime" time_format:"unixNano"`
|
||||||
UnixTime time.Time `form:"unixTime" time_format:"unix"`
|
UnixTime time.Time `form:"unixTime" time_format:"unix"`
|
||||||
|
UnixMilliTime time.Time `form:"unixMilliTime" time_format:"unixmilli"`
|
||||||
|
UnixMicroTime time.Time `form:"unixMicroTime" time_format:"uNiXmiCrO"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FooStructForTimeTypeNotUnixFormat struct {
|
type FooStructForTimeTypeNotUnixFormat struct {
|
||||||
CreateTime time.Time `form:"createTime" time_format:"unixNano"`
|
CreateTime time.Time `form:"createTime" time_format:"unixNano"`
|
||||||
UnixTime time.Time `form:"unixTime" time_format:"unix"`
|
UnixTime time.Time `form:"unixTime" time_format:"unix"`
|
||||||
|
UnixMilliTime time.Time `form:"unixMilliTime" time_format:"unixMilli"`
|
||||||
|
UnixMicroTime time.Time `form:"unixMicroTime" time_format:"unixMicro"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FooStructForTimeTypeNotFormat struct {
|
type FooStructForTimeTypeNotFormat struct {
|
||||||
@@ -145,31 +147,31 @@ type FooStructForMapPtrType struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingDefault(t *testing.T) {
|
func TestBindingDefault(t *testing.T) {
|
||||||
assert.Equal(t, Form, Default("GET", ""))
|
assert.Equal(t, Form, Default(http.MethodGet, ""))
|
||||||
assert.Equal(t, Form, Default("GET", MIMEJSON))
|
assert.Equal(t, Form, Default(http.MethodGet, MIMEJSON))
|
||||||
|
|
||||||
assert.Equal(t, JSON, Default("POST", MIMEJSON))
|
assert.Equal(t, JSON, Default(http.MethodPost, MIMEJSON))
|
||||||
assert.Equal(t, JSON, Default("PUT", MIMEJSON))
|
assert.Equal(t, JSON, Default(http.MethodPut, MIMEJSON))
|
||||||
|
|
||||||
assert.Equal(t, XML, Default("POST", MIMEXML))
|
assert.Equal(t, XML, Default(http.MethodPost, MIMEXML))
|
||||||
assert.Equal(t, XML, Default("PUT", MIMEXML2))
|
assert.Equal(t, XML, Default(http.MethodPut, MIMEXML2))
|
||||||
|
|
||||||
assert.Equal(t, Form, Default("POST", MIMEPOSTForm))
|
assert.Equal(t, Form, Default(http.MethodPost, MIMEPOSTForm))
|
||||||
assert.Equal(t, Form, Default("PUT", MIMEPOSTForm))
|
assert.Equal(t, Form, Default(http.MethodPut, MIMEPOSTForm))
|
||||||
|
|
||||||
assert.Equal(t, FormMultipart, Default("POST", MIMEMultipartPOSTForm))
|
assert.Equal(t, FormMultipart, Default(http.MethodPost, MIMEMultipartPOSTForm))
|
||||||
assert.Equal(t, FormMultipart, Default("PUT", MIMEMultipartPOSTForm))
|
assert.Equal(t, FormMultipart, Default(http.MethodPut, MIMEMultipartPOSTForm))
|
||||||
|
|
||||||
assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF))
|
assert.Equal(t, ProtoBuf, Default(http.MethodPost, MIMEPROTOBUF))
|
||||||
assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF))
|
assert.Equal(t, ProtoBuf, Default(http.MethodPut, MIMEPROTOBUF))
|
||||||
|
|
||||||
assert.Equal(t, YAML, Default("POST", MIMEYAML))
|
assert.Equal(t, YAML, Default(http.MethodPost, MIMEYAML))
|
||||||
assert.Equal(t, YAML, Default("PUT", MIMEYAML))
|
assert.Equal(t, YAML, Default(http.MethodPut, MIMEYAML))
|
||||||
assert.Equal(t, YAML, Default("POST", MIMEYAML2))
|
assert.Equal(t, YAML, Default(http.MethodPost, MIMEYAML2))
|
||||||
assert.Equal(t, YAML, Default("PUT", MIMEYAML2))
|
assert.Equal(t, YAML, Default(http.MethodPut, MIMEYAML2))
|
||||||
|
|
||||||
assert.Equal(t, TOML, Default("POST", MIMETOML))
|
assert.Equal(t, TOML, Default(http.MethodPost, MIMETOML))
|
||||||
assert.Equal(t, TOML, Default("PUT", MIMETOML))
|
assert.Equal(t, TOML, Default(http.MethodPut, MIMETOML))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingJSONNilBody(t *testing.T) {
|
func TestBindingJSONNilBody(t *testing.T) {
|
||||||
@@ -227,137 +229,137 @@ func TestBindingJSONStringMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingForm(t *testing.T) {
|
func TestBindingForm(t *testing.T) {
|
||||||
testFormBinding(t, "POST",
|
testFormBinding(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"foo=bar&bar=foo", "bar2=foo")
|
"foo=bar&bar=foo", "bar2=foo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingForm2(t *testing.T) {
|
func TestBindingForm2(t *testing.T) {
|
||||||
testFormBinding(t, "GET",
|
testFormBinding(t, http.MethodGet,
|
||||||
"/?foo=bar&bar=foo", "/?bar2=foo",
|
"/?foo=bar&bar=foo", "/?bar2=foo",
|
||||||
"", "")
|
"", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormEmbeddedStruct(t *testing.T) {
|
func TestBindingFormEmbeddedStruct(t *testing.T) {
|
||||||
testFormBindingEmbeddedStruct(t, "POST",
|
testFormBindingEmbeddedStruct(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"page=1&size=2&appkey=test-appkey", "bar2=foo")
|
"page=1&size=2&appkey=test-appkey", "bar2=foo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormEmbeddedStruct2(t *testing.T) {
|
func TestBindingFormEmbeddedStruct2(t *testing.T) {
|
||||||
testFormBindingEmbeddedStruct(t, "GET",
|
testFormBindingEmbeddedStruct(t, http.MethodGet,
|
||||||
"/?page=1&size=2&appkey=test-appkey", "/?bar2=foo",
|
"/?page=1&size=2&appkey=test-appkey", "/?bar2=foo",
|
||||||
"", "")
|
"", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormDefaultValue(t *testing.T) {
|
func TestBindingFormDefaultValue(t *testing.T) {
|
||||||
testFormBindingDefaultValue(t, "POST",
|
testFormBindingDefaultValue(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"foo=bar", "bar2=foo")
|
"foo=bar", "bar2=foo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormDefaultValue2(t *testing.T) {
|
func TestBindingFormDefaultValue2(t *testing.T) {
|
||||||
testFormBindingDefaultValue(t, "GET",
|
testFormBindingDefaultValue(t, http.MethodGet,
|
||||||
"/?foo=bar", "/?bar2=foo",
|
"/?foo=bar", "/?bar2=foo",
|
||||||
"", "")
|
"", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormForTime(t *testing.T) {
|
func TestBindingFormForTime(t *testing.T) {
|
||||||
testFormBindingForTime(t, "POST",
|
testFormBindingForTime(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "bar2=foo")
|
"time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033&unixMilliTime=1562400033001&unixMicroTime=1562400033000012", "bar2=foo")
|
||||||
testFormBindingForTimeNotUnixFormat(t, "POST",
|
testFormBindingForTimeNotUnixFormat(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo")
|
"time_foo=2017-11-15&createTime=bad&unixTime=bad&unixMilliTime=bad&unixMicroTime=bad", "bar2=foo")
|
||||||
testFormBindingForTimeNotFormat(t, "POST",
|
testFormBindingForTimeNotFormat(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"time_foo=2017-11-15", "bar2=foo")
|
"time_foo=2017-11-15", "bar2=foo")
|
||||||
testFormBindingForTimeFailFormat(t, "POST",
|
testFormBindingForTimeFailFormat(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"time_foo=2017-11-15", "bar2=foo")
|
"time_foo=2017-11-15", "bar2=foo")
|
||||||
testFormBindingForTimeFailLocation(t, "POST",
|
testFormBindingForTimeFailLocation(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"time_foo=2017-11-15", "bar2=foo")
|
"time_foo=2017-11-15", "bar2=foo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormForTime2(t *testing.T) {
|
func TestBindingFormForTime2(t *testing.T) {
|
||||||
testFormBindingForTime(t, "GET",
|
testFormBindingForTime(t, http.MethodGet,
|
||||||
"/?time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "/?bar2=foo",
|
"/?time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033&unixMilliTime=1562400033001&unixMicroTime=1562400033000012", "/?bar2=foo",
|
||||||
"", "")
|
"", "")
|
||||||
testFormBindingForTimeNotUnixFormat(t, "POST",
|
testFormBindingForTimeNotUnixFormat(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo")
|
"time_foo=2017-11-15&createTime=bad&unixTime=bad&unixMilliTime=bad&unixMicroTime=bad", "bar2=foo")
|
||||||
testFormBindingForTimeNotFormat(t, "GET",
|
testFormBindingForTimeNotFormat(t, http.MethodGet,
|
||||||
"/?time_foo=2017-11-15", "/?bar2=foo",
|
"/?time_foo=2017-11-15", "/?bar2=foo",
|
||||||
"", "")
|
"", "")
|
||||||
testFormBindingForTimeFailFormat(t, "GET",
|
testFormBindingForTimeFailFormat(t, http.MethodGet,
|
||||||
"/?time_foo=2017-11-15", "/?bar2=foo",
|
"/?time_foo=2017-11-15", "/?bar2=foo",
|
||||||
"", "")
|
"", "")
|
||||||
testFormBindingForTimeFailLocation(t, "GET",
|
testFormBindingForTimeFailLocation(t, http.MethodGet,
|
||||||
"/?time_foo=2017-11-15", "/?bar2=foo",
|
"/?time_foo=2017-11-15", "/?bar2=foo",
|
||||||
"", "")
|
"", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormBindingIgnoreField(t *testing.T) {
|
func TestFormBindingIgnoreField(t *testing.T) {
|
||||||
testFormBindingIgnoreField(t, "POST",
|
testFormBindingIgnoreField(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"-=bar", "")
|
"-=bar", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormInvalidName(t *testing.T) {
|
func TestBindingFormInvalidName(t *testing.T) {
|
||||||
testFormBindingInvalidName(t, "POST",
|
testFormBindingInvalidName(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"test_name=bar", "bar2=foo")
|
"test_name=bar", "bar2=foo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormInvalidName2(t *testing.T) {
|
func TestBindingFormInvalidName2(t *testing.T) {
|
||||||
testFormBindingInvalidName2(t, "POST",
|
testFormBindingInvalidName2(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"map_foo=bar", "bar2=foo")
|
"map_foo=bar", "bar2=foo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormForType(t *testing.T) {
|
func TestBindingFormForType(t *testing.T) {
|
||||||
testFormBindingForType(t, "POST",
|
testFormBindingForType(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"map_foo={\"bar\":123}", "map_foo=1", "Map")
|
"map_foo={\"bar\":123}", "map_foo=1", "Map")
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
testFormBindingForType(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"slice_foo=1&slice_foo=2", "bar2=1&bar2=2", "Slice")
|
"slice_foo=1&slice_foo=2", "bar2=1&bar2=2", "Slice")
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
testFormBindingForType(t, http.MethodGet,
|
||||||
"/?slice_foo=1&slice_foo=2", "/?bar2=1&bar2=2",
|
"/?slice_foo=1&slice_foo=2", "/?bar2=1&bar2=2",
|
||||||
"", "", "Slice")
|
"", "", "Slice")
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
testFormBindingForType(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"slice_map_foo=1&slice_map_foo=2", "bar2=1&bar2=2", "SliceMap")
|
"slice_map_foo=1&slice_map_foo=2", "bar2=1&bar2=2", "SliceMap")
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
testFormBindingForType(t, http.MethodGet,
|
||||||
"/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2",
|
"/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2",
|
||||||
"", "", "SliceMap")
|
"", "", "SliceMap")
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
testFormBindingForType(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"ptr_bar=test", "bar2=test", "Ptr")
|
"ptr_bar=test", "bar2=test", "Ptr")
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
testFormBindingForType(t, http.MethodGet,
|
||||||
"/?ptr_bar=test", "/?bar2=test",
|
"/?ptr_bar=test", "/?bar2=test",
|
||||||
"", "", "Ptr")
|
"", "", "Ptr")
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
testFormBindingForType(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"idx=123", "id1=1", "Struct")
|
"idx=123", "id1=1", "Struct")
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
testFormBindingForType(t, http.MethodGet,
|
||||||
"/?idx=123", "/?id1=1",
|
"/?idx=123", "/?id1=1",
|
||||||
"", "", "Struct")
|
"", "", "Struct")
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
testFormBindingForType(t, http.MethodPost,
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"name=thinkerou", "name1=ou", "StructPointer")
|
"name=thinkerou", "name1=ou", "StructPointer")
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
testFormBindingForType(t, http.MethodGet,
|
||||||
"/?name=thinkerou", "/?name1=ou",
|
"/?name=thinkerou", "/?name1=ou",
|
||||||
"", "", "StructPointer")
|
"", "", "StructPointer")
|
||||||
}
|
}
|
||||||
@@ -374,7 +376,7 @@ func TestBindingFormStringMap(t *testing.T) {
|
|||||||
|
|
||||||
func TestBindingFormStringSliceMap(t *testing.T) {
|
func TestBindingFormStringSliceMap(t *testing.T) {
|
||||||
obj := make(map[string][]string)
|
obj := make(map[string][]string)
|
||||||
req := requestWithBody("POST", "/", "foo=something&foo=bar&hello=world")
|
req := requestWithBody(http.MethodPost, "/", "foo=something&foo=bar&hello=world")
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
err := Form.Bind(req, &obj)
|
err := Form.Bind(req, &obj)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -387,38 +389,38 @@ func TestBindingFormStringSliceMap(t *testing.T) {
|
|||||||
assert.True(t, reflect.DeepEqual(obj, target))
|
assert.True(t, reflect.DeepEqual(obj, target))
|
||||||
|
|
||||||
objInvalid := make(map[string][]int)
|
objInvalid := make(map[string][]int)
|
||||||
req = requestWithBody("POST", "/", "foo=something&foo=bar&hello=world")
|
req = requestWithBody(http.MethodPost, "/", "foo=something&foo=bar&hello=world")
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
err = Form.Bind(req, &objInvalid)
|
err = Form.Bind(req, &objInvalid)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingQuery(t *testing.T) {
|
func TestBindingQuery(t *testing.T) {
|
||||||
testQueryBinding(t, "POST",
|
testQueryBinding(t, http.MethodPost,
|
||||||
"/?foo=bar&bar=foo", "/",
|
"/?foo=bar&bar=foo", "/",
|
||||||
"foo=unused", "bar2=foo")
|
"foo=unused", "bar2=foo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingQuery2(t *testing.T) {
|
func TestBindingQuery2(t *testing.T) {
|
||||||
testQueryBinding(t, "GET",
|
testQueryBinding(t, http.MethodGet,
|
||||||
"/?foo=bar&bar=foo", "/?bar2=foo",
|
"/?foo=bar&bar=foo", "/?bar2=foo",
|
||||||
"foo=unused", "")
|
"foo=unused", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingQueryFail(t *testing.T) {
|
func TestBindingQueryFail(t *testing.T) {
|
||||||
testQueryBindingFail(t, "POST",
|
testQueryBindingFail(t, http.MethodPost,
|
||||||
"/?map_foo=", "/",
|
"/?map_foo=", "/",
|
||||||
"map_foo=unused", "bar2=foo")
|
"map_foo=unused", "bar2=foo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingQueryFail2(t *testing.T) {
|
func TestBindingQueryFail2(t *testing.T) {
|
||||||
testQueryBindingFail(t, "GET",
|
testQueryBindingFail(t, http.MethodGet,
|
||||||
"/?map_foo=", "/?bar2=foo",
|
"/?map_foo=", "/?bar2=foo",
|
||||||
"map_foo=unused", "")
|
"map_foo=unused", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingQueryBoolFail(t *testing.T) {
|
func TestBindingQueryBoolFail(t *testing.T) {
|
||||||
testQueryBindingBoolFail(t, "GET",
|
testQueryBindingBoolFail(t, http.MethodGet,
|
||||||
"/?bool_foo=fasl", "/?bar2=foo",
|
"/?bool_foo=fasl", "/?bar2=foo",
|
||||||
"bool_foo=unused", "")
|
"bool_foo=unused", "")
|
||||||
}
|
}
|
||||||
@@ -427,7 +429,7 @@ func TestBindingQueryStringMap(t *testing.T) {
|
|||||||
b := Query
|
b := Query
|
||||||
|
|
||||||
obj := make(map[string]string)
|
obj := make(map[string]string)
|
||||||
req := requestWithBody("GET", "/?foo=bar&hello=world", "")
|
req := requestWithBody(http.MethodGet, "/?foo=bar&hello=world", "")
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, obj)
|
assert.NotNil(t, obj)
|
||||||
@@ -436,7 +438,7 @@ func TestBindingQueryStringMap(t *testing.T) {
|
|||||||
assert.Equal(t, "world", obj["hello"])
|
assert.Equal(t, "world", obj["hello"])
|
||||||
|
|
||||||
obj = make(map[string]string)
|
obj = make(map[string]string)
|
||||||
req = requestWithBody("GET", "/?foo=bar&foo=2&hello=world", "") // should pick last
|
req = requestWithBody(http.MethodGet, "/?foo=bar&foo=2&hello=world", "") // should pick last
|
||||||
err = b.Bind(req, &obj)
|
err = b.Bind(req, &obj)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, obj)
|
assert.NotNil(t, obj)
|
||||||
@@ -495,28 +497,28 @@ func TestBindingYAMLFail(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createFormPostRequest(t *testing.T) *http.Request {
|
func createFormPostRequest(t *testing.T) *http.Request {
|
||||||
req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo"))
|
req, err := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req.Header.Set("Content-Type", MIMEPOSTForm)
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDefaultFormPostRequest(t *testing.T) *http.Request {
|
func createDefaultFormPostRequest(t *testing.T) *http.Request {
|
||||||
req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar"))
|
req, err := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req.Header.Set("Content-Type", MIMEPOSTForm)
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFormPostRequestForMap(t *testing.T) *http.Request {
|
func createFormPostRequestForMap(t *testing.T) *http.Request {
|
||||||
req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}"))
|
req, err := http.NewRequest(http.MethodPost, "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req.Header.Set("Content-Type", MIMEPOSTForm)
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFormPostRequestForMapFail(t *testing.T) *http.Request {
|
func createFormPostRequestForMapFail(t *testing.T) *http.Request {
|
||||||
req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello"))
|
req, err := http.NewRequest(http.MethodPost, "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req.Header.Set("Content-Type", MIMEPOSTForm)
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
return req
|
return req
|
||||||
@@ -540,7 +542,7 @@ func createFormFilesMultipartRequest(t *testing.T) *http.Request {
|
|||||||
_, err = io.Copy(fw, f)
|
_, err = io.Copy(fw, f)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
|
req, err2 := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", body)
|
||||||
require.NoError(t, err2)
|
require.NoError(t, err2)
|
||||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||||
|
|
||||||
@@ -565,7 +567,7 @@ func createFormFilesMultipartRequestFail(t *testing.T) *http.Request {
|
|||||||
_, err = io.Copy(fw, f)
|
_, err = io.Copy(fw, f)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
|
req, err2 := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", body)
|
||||||
require.NoError(t, err2)
|
require.NoError(t, err2)
|
||||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||||
|
|
||||||
@@ -581,7 +583,7 @@ func createFormMultipartRequest(t *testing.T) *http.Request {
|
|||||||
require.NoError(t, mw.SetBoundary(boundary))
|
require.NoError(t, mw.SetBoundary(boundary))
|
||||||
require.NoError(t, mw.WriteField("foo", "bar"))
|
require.NoError(t, mw.WriteField("foo", "bar"))
|
||||||
require.NoError(t, mw.WriteField("bar", "foo"))
|
require.NoError(t, mw.WriteField("bar", "foo"))
|
||||||
req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
|
req, err := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||||
return req
|
return req
|
||||||
@@ -595,7 +597,7 @@ func createFormMultipartRequestForMap(t *testing.T) *http.Request {
|
|||||||
|
|
||||||
require.NoError(t, mw.SetBoundary(boundary))
|
require.NoError(t, mw.SetBoundary(boundary))
|
||||||
require.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}"))
|
require.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}"))
|
||||||
req, err := http.NewRequest("POST", "/?map_foo=getfoo", body)
|
req, err := http.NewRequest(http.MethodPost, "/?map_foo=getfoo", body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||||
return req
|
return req
|
||||||
@@ -609,7 +611,7 @@ func createFormMultipartRequestForMapFail(t *testing.T) *http.Request {
|
|||||||
|
|
||||||
require.NoError(t, mw.SetBoundary(boundary))
|
require.NoError(t, mw.SetBoundary(boundary))
|
||||||
require.NoError(t, mw.WriteField("map_foo", "3.14"))
|
require.NoError(t, mw.WriteField("map_foo", "3.14"))
|
||||||
req, err := http.NewRequest("POST", "/?map_foo=getfoo", body)
|
req, err := http.NewRequest(http.MethodPost, "/?map_foo=getfoo", body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||||
return req
|
return req
|
||||||
@@ -731,7 +733,7 @@ func TestBindingProtoBufFail(t *testing.T) {
|
|||||||
|
|
||||||
func TestValidationFails(t *testing.T) {
|
func TestValidationFails(t *testing.T) {
|
||||||
var obj FooStruct
|
var obj FooStruct
|
||||||
req := requestWithBody("POST", "/", `{"bar": "foo"}`)
|
req := requestWithBody(http.MethodPost, "/", `{"bar": "foo"}`)
|
||||||
err := JSON.Bind(req, &obj)
|
err := JSON.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
@@ -742,7 +744,7 @@ func TestValidationDisabled(t *testing.T) {
|
|||||||
defer func() { Validator = backup }()
|
defer func() { Validator = backup }()
|
||||||
|
|
||||||
var obj FooStruct
|
var obj FooStruct
|
||||||
req := requestWithBody("POST", "/", `{"bar": "foo"}`)
|
req := requestWithBody(http.MethodPost, "/", `{"bar": "foo"}`)
|
||||||
err := JSON.Bind(req, &obj)
|
err := JSON.Bind(req, &obj)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
@@ -753,7 +755,7 @@ func TestRequiredSucceeds(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var obj HogeStruct
|
var obj HogeStruct
|
||||||
req := requestWithBody("POST", "/", `{"hoge": 0}`)
|
req := requestWithBody(http.MethodPost, "/", `{"hoge": 0}`)
|
||||||
err := JSON.Bind(req, &obj)
|
err := JSON.Bind(req, &obj)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
@@ -764,7 +766,7 @@ func TestRequiredFails(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var obj HogeStruct
|
var obj HogeStruct
|
||||||
req := requestWithBody("POST", "/", `{"boen": 0}`)
|
req := requestWithBody(http.MethodPost, "/", `{"boen": 0}`)
|
||||||
err := JSON.Bind(req, &obj)
|
err := JSON.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
@@ -778,12 +780,12 @@ func TestHeaderBinding(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var theader tHeader
|
var theader tHeader
|
||||||
req := requestWithBody("GET", "/", "")
|
req := requestWithBody(http.MethodGet, "/", "")
|
||||||
req.Header.Add("limit", "1000")
|
req.Header.Add("limit", "1000")
|
||||||
require.NoError(t, h.Bind(req, &theader))
|
require.NoError(t, h.Bind(req, &theader))
|
||||||
assert.Equal(t, 1000, theader.Limit)
|
assert.Equal(t, 1000, theader.Limit)
|
||||||
|
|
||||||
req = requestWithBody("GET", "/", "")
|
req = requestWithBody(http.MethodGet, "/", "")
|
||||||
req.Header.Add("fail", `{fail:fail}`)
|
req.Header.Add("fail", `{fail:fail}`)
|
||||||
|
|
||||||
type failStruct struct {
|
type failStruct struct {
|
||||||
@@ -843,7 +845,7 @@ func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, ba
|
|||||||
|
|
||||||
obj := QueryTest{}
|
obj := QueryTest{}
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@@ -859,7 +861,7 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string)
|
|||||||
|
|
||||||
obj := FooBarStruct{}
|
obj := FooBarStruct{}
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@@ -879,7 +881,7 @@ func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badB
|
|||||||
|
|
||||||
obj := FooDefaultBarStruct{}
|
obj := FooDefaultBarStruct{}
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@@ -898,14 +900,14 @@ func TestFormBindingFail(t *testing.T) {
|
|||||||
assert.Equal(t, "form", b.Name())
|
assert.Equal(t, "form", b.Name())
|
||||||
|
|
||||||
obj := FooBarStruct{}
|
obj := FooBarStruct{}
|
||||||
req, _ := http.NewRequest("POST", "/", nil)
|
req, _ := http.NewRequest(http.MethodPost, "/", nil)
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormBindingMultipartFail(t *testing.T) {
|
func TestFormBindingMultipartFail(t *testing.T) {
|
||||||
obj := FooBarStruct{}
|
obj := FooBarStruct{}
|
||||||
req, err := http.NewRequest("POST", "/", strings.NewReader("foo=bar"))
|
req, err := http.NewRequest(http.MethodPost, "/", strings.NewReader("foo=bar"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+";boundary=testboundary")
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+";boundary=testboundary")
|
||||||
_, err = req.MultipartReader()
|
_, err = req.MultipartReader()
|
||||||
@@ -919,7 +921,7 @@ func TestFormPostBindingFail(t *testing.T) {
|
|||||||
assert.Equal(t, "form-urlencoded", b.Name())
|
assert.Equal(t, "form-urlencoded", b.Name())
|
||||||
|
|
||||||
obj := FooBarStruct{}
|
obj := FooBarStruct{}
|
||||||
req, _ := http.NewRequest("POST", "/", nil)
|
req, _ := http.NewRequest(http.MethodPost, "/", nil)
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
@@ -929,7 +931,7 @@ func TestFormMultipartBindingFail(t *testing.T) {
|
|||||||
assert.Equal(t, "multipart/form-data", b.Name())
|
assert.Equal(t, "multipart/form-data", b.Name())
|
||||||
|
|
||||||
obj := FooBarStruct{}
|
obj := FooBarStruct{}
|
||||||
req, _ := http.NewRequest("POST", "/", nil)
|
req, _ := http.NewRequest(http.MethodPost, "/", nil)
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
@@ -940,7 +942,7 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s
|
|||||||
|
|
||||||
obj := FooBarStructForTimeType{}
|
obj := FooBarStructForTimeType{}
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@@ -952,6 +954,8 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s
|
|||||||
assert.Equal(t, "UTC", obj.TimeBar.Location().String())
|
assert.Equal(t, "UTC", obj.TimeBar.Location().String())
|
||||||
assert.Equal(t, int64(1562400033000000123), obj.CreateTime.UnixNano())
|
assert.Equal(t, int64(1562400033000000123), obj.CreateTime.UnixNano())
|
||||||
assert.Equal(t, int64(1562400033), obj.UnixTime.Unix())
|
assert.Equal(t, int64(1562400033), obj.UnixTime.Unix())
|
||||||
|
assert.Equal(t, int64(1562400033001), obj.UnixMilliTime.UnixMilli())
|
||||||
|
assert.Equal(t, int64(1562400033000012), obj.UnixMicroTime.UnixMicro())
|
||||||
|
|
||||||
obj = FooBarStructForTimeType{}
|
obj = FooBarStructForTimeType{}
|
||||||
req = requestWithBody(method, badPath, badBody)
|
req = requestWithBody(method, badPath, badBody)
|
||||||
@@ -965,7 +969,7 @@ func testFormBindingForTimeNotUnixFormat(t *testing.T, method, path, badPath, bo
|
|||||||
|
|
||||||
obj := FooStructForTimeTypeNotUnixFormat{}
|
obj := FooStructForTimeTypeNotUnixFormat{}
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@@ -983,7 +987,7 @@ func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body,
|
|||||||
|
|
||||||
obj := FooStructForTimeTypeNotFormat{}
|
obj := FooStructForTimeTypeNotFormat{}
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@@ -1001,7 +1005,7 @@ func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body,
|
|||||||
|
|
||||||
obj := FooStructForTimeTypeFailFormat{}
|
obj := FooStructForTimeTypeFailFormat{}
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@@ -1019,7 +1023,7 @@ func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, bod
|
|||||||
|
|
||||||
obj := FooStructForTimeTypeFailLocation{}
|
obj := FooStructForTimeTypeFailLocation{}
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@@ -1037,7 +1041,7 @@ func testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBo
|
|||||||
|
|
||||||
obj := FooStructForIgnoreFormTag{}
|
obj := FooStructForIgnoreFormTag{}
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@@ -1052,12 +1056,12 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo
|
|||||||
|
|
||||||
obj := InvalidNameType{}
|
obj := InvalidNameType{}
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "", obj.TestName)
|
assert.Empty(t, obj.TestName)
|
||||||
|
|
||||||
obj = InvalidNameType{}
|
obj = InvalidNameType{}
|
||||||
req = requestWithBody(method, badPath, badBody)
|
req = requestWithBody(method, badPath, badBody)
|
||||||
@@ -1071,7 +1075,7 @@ func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badB
|
|||||||
|
|
||||||
obj := InvalidNameMapType{}
|
obj := InvalidNameMapType{}
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@@ -1088,7 +1092,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
|
|||||||
assert.Equal(t, "form", b.Name())
|
assert.Equal(t, "form", b.Name())
|
||||||
|
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
switch typ {
|
switch typ {
|
||||||
@@ -1159,7 +1163,7 @@ func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string)
|
|||||||
|
|
||||||
obj := FooBarStruct{}
|
obj := FooBarStruct{}
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@@ -1174,7 +1178,7 @@ func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody str
|
|||||||
|
|
||||||
obj := FooStructForMapType{}
|
obj := FooStructForMapType{}
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@@ -1187,7 +1191,7 @@ func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody
|
|||||||
|
|
||||||
obj := FooStructForBoolType{}
|
obj := FooStructForBoolType{}
|
||||||
req := requestWithBody(method, path, body)
|
req := requestWithBody(method, path, body)
|
||||||
if method == "POST" {
|
if method == http.MethodPost {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@@ -1198,13 +1202,13 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody
|
|||||||
assert.Equal(t, name, b.Name())
|
assert.Equal(t, name, b.Name())
|
||||||
|
|
||||||
obj := FooStruct{}
|
obj := FooStruct{}
|
||||||
req := requestWithBody("POST", path, body)
|
req := requestWithBody(http.MethodPost, path, body)
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "bar", obj.Foo)
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
|
||||||
obj = FooStruct{}
|
obj = FooStruct{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody(http.MethodPost, badPath, badBody)
|
||||||
err = JSON.Bind(req, &obj)
|
err = JSON.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
@@ -1213,19 +1217,19 @@ func testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, ba
|
|||||||
assert.Equal(t, name, b.Name())
|
assert.Equal(t, name, b.Name())
|
||||||
|
|
||||||
var obj1 []FooStruct
|
var obj1 []FooStruct
|
||||||
req := requestWithBody("POST", path, body)
|
req := requestWithBody(http.MethodPost, path, body)
|
||||||
err := b.Bind(req, &obj1)
|
err := b.Bind(req, &obj1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var obj2 []FooStruct
|
var obj2 []FooStruct
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody(http.MethodPost, badPath, badBody)
|
||||||
err = JSON.Bind(req, &obj2)
|
err = JSON.Bind(req, &obj2)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) {
|
func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) {
|
||||||
obj := make(map[string]string)
|
obj := make(map[string]string)
|
||||||
req := requestWithBody("POST", path, body)
|
req := requestWithBody(http.MethodPost, path, body)
|
||||||
if b.Name() == "form" {
|
if b.Name() == "form" {
|
||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
@@ -1238,13 +1242,13 @@ func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badB
|
|||||||
|
|
||||||
if badPath != "" && badBody != "" {
|
if badPath != "" && badBody != "" {
|
||||||
obj = make(map[string]string)
|
obj = make(map[string]string)
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody(http.MethodPost, badPath, badBody)
|
||||||
err = b.Bind(req, &obj)
|
err = b.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objInt := make(map[string]int)
|
objInt := make(map[string]int)
|
||||||
req = requestWithBody("POST", path, body)
|
req = requestWithBody(http.MethodPost, path, body)
|
||||||
err = b.Bind(req, &objInt)
|
err = b.Bind(req, &objInt)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
@@ -1253,7 +1257,7 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body
|
|||||||
assert.Equal(t, name, b.Name())
|
assert.Equal(t, name, b.Name())
|
||||||
|
|
||||||
obj := FooStructUseNumber{}
|
obj := FooStructUseNumber{}
|
||||||
req := requestWithBody("POST", path, body)
|
req := requestWithBody(http.MethodPost, path, body)
|
||||||
EnableDecoderUseNumber = true
|
EnableDecoderUseNumber = true
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -1263,7 +1267,7 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body
|
|||||||
assert.Equal(t, int64(123), v)
|
assert.Equal(t, int64(123), v)
|
||||||
|
|
||||||
obj = FooStructUseNumber{}
|
obj = FooStructUseNumber{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody(http.MethodPost, badPath, badBody)
|
||||||
err = JSON.Bind(req, &obj)
|
err = JSON.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
@@ -1272,7 +1276,7 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod
|
|||||||
assert.Equal(t, name, b.Name())
|
assert.Equal(t, name, b.Name())
|
||||||
|
|
||||||
obj := FooStructUseNumber{}
|
obj := FooStructUseNumber{}
|
||||||
req := requestWithBody("POST", path, body)
|
req := requestWithBody(http.MethodPost, path, body)
|
||||||
EnableDecoderUseNumber = false
|
EnableDecoderUseNumber = false
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -1281,7 +1285,7 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod
|
|||||||
assert.InDelta(t, float64(123), obj.Foo, 0.01)
|
assert.InDelta(t, float64(123), obj.Foo, 0.01)
|
||||||
|
|
||||||
obj = FooStructUseNumber{}
|
obj = FooStructUseNumber{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody(http.MethodPost, badPath, badBody)
|
||||||
err = JSON.Bind(req, &obj)
|
err = JSON.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
@@ -1293,13 +1297,13 @@ func testBodyBindingDisallowUnknownFields(t *testing.T, b Binding, path, badPath
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
obj := FooStructDisallowUnknownFields{}
|
obj := FooStructDisallowUnknownFields{}
|
||||||
req := requestWithBody("POST", path, body)
|
req := requestWithBody(http.MethodPost, path, body)
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "bar", obj.Foo)
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
|
||||||
obj = FooStructDisallowUnknownFields{}
|
obj = FooStructDisallowUnknownFields{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody(http.MethodPost, badPath, badBody)
|
||||||
err = JSON.Bind(req, &obj)
|
err = JSON.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "what")
|
assert.Contains(t, err.Error(), "what")
|
||||||
@@ -1309,13 +1313,13 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad
|
|||||||
assert.Equal(t, name, b.Name())
|
assert.Equal(t, name, b.Name())
|
||||||
|
|
||||||
obj := FooStruct{}
|
obj := FooStruct{}
|
||||||
req := requestWithBody("POST", path, body)
|
req := requestWithBody(http.MethodPost, path, body)
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, "", obj.Foo)
|
assert.Empty(t, obj.Foo)
|
||||||
|
|
||||||
obj = FooStruct{}
|
obj = FooStruct{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody(http.MethodPost, badPath, badBody)
|
||||||
err = JSON.Bind(req, &obj)
|
err = JSON.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
@@ -1324,14 +1328,14 @@ func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, ba
|
|||||||
assert.Equal(t, name, b.Name())
|
assert.Equal(t, name, b.Name())
|
||||||
|
|
||||||
obj := protoexample.Test{}
|
obj := protoexample.Test{}
|
||||||
req := requestWithBody("POST", path, body)
|
req := requestWithBody(http.MethodPost, path, body)
|
||||||
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "yes", *obj.Label)
|
assert.Equal(t, "yes", *obj.Label)
|
||||||
|
|
||||||
obj = protoexample.Test{}
|
obj = protoexample.Test{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody(http.MethodPost, badPath, badBody)
|
||||||
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
||||||
err = ProtoBuf.Bind(req, &obj)
|
err = ProtoBuf.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@@ -1358,28 +1362,28 @@ func TestPlainBinding(t *testing.T) {
|
|||||||
assert.Equal(t, "plain", p.Name())
|
assert.Equal(t, "plain", p.Name())
|
||||||
|
|
||||||
var s string
|
var s string
|
||||||
req := requestWithBody("POST", "/", "test string")
|
req := requestWithBody(http.MethodPost, "/", "test string")
|
||||||
require.NoError(t, p.Bind(req, &s))
|
require.NoError(t, p.Bind(req, &s))
|
||||||
assert.Equal(t, "test string", s)
|
assert.Equal(t, "test string", s)
|
||||||
|
|
||||||
var bs []byte
|
var bs []byte
|
||||||
req = requestWithBody("POST", "/", "test []byte")
|
req = requestWithBody(http.MethodPost, "/", "test []byte")
|
||||||
require.NoError(t, p.Bind(req, &bs))
|
require.NoError(t, p.Bind(req, &bs))
|
||||||
assert.Equal(t, bs, []byte("test []byte"))
|
assert.Equal(t, bs, []byte("test []byte"))
|
||||||
|
|
||||||
var i int
|
var i int
|
||||||
req = requestWithBody("POST", "/", "test fail")
|
req = requestWithBody(http.MethodPost, "/", "test fail")
|
||||||
require.Error(t, p.Bind(req, &i))
|
require.Error(t, p.Bind(req, &i))
|
||||||
|
|
||||||
req = requestWithBody("POST", "/", "")
|
req = requestWithBody(http.MethodPost, "/", "")
|
||||||
req.Body = &failRead{}
|
req.Body = &failRead{}
|
||||||
require.Error(t, p.Bind(req, &s))
|
require.Error(t, p.Bind(req, &s))
|
||||||
|
|
||||||
req = requestWithBody("POST", "/", "")
|
req = requestWithBody(http.MethodPost, "/", "")
|
||||||
require.NoError(t, p.Bind(req, nil))
|
require.NoError(t, p.Bind(req, nil))
|
||||||
|
|
||||||
var ptr *string
|
var ptr *string
|
||||||
req = requestWithBody("POST", "/", "")
|
req = requestWithBody(http.MethodPost, "/", "")
|
||||||
require.NoError(t, p.Bind(req, ptr))
|
require.NoError(t, p.Bind(req, ptr))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1387,7 +1391,7 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body
|
|||||||
assert.Equal(t, name, b.Name())
|
assert.Equal(t, name, b.Name())
|
||||||
|
|
||||||
obj := protoexample.Test{}
|
obj := protoexample.Test{}
|
||||||
req := requestWithBody("POST", path, body)
|
req := requestWithBody(http.MethodPost, path, body)
|
||||||
|
|
||||||
req.Body = io.NopCloser(&hook{})
|
req.Body = io.NopCloser(&hook{})
|
||||||
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
||||||
@@ -1402,7 +1406,7 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body
|
|||||||
assert.Equal(t, "obj is not ProtoMessage", err.Error())
|
assert.Equal(t, "obj is not ProtoMessage", err.Error())
|
||||||
|
|
||||||
obj = protoexample.Test{}
|
obj = protoexample.Test{}
|
||||||
req = requestWithBody("POST", badPath, badBody)
|
req = requestWithBody(http.MethodPost, badPath, badBody)
|
||||||
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
||||||
err = ProtoBuf.Bind(req, &obj)
|
err = ProtoBuf.Bind(req, &obj)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|||||||
@@ -18,14 +18,16 @@ func TestSliceValidationError(t *testing.T) {
|
|||||||
{"has nil elements", SliceValidationError{errors.New("test error"), nil}, "[0]: test error"},
|
{"has nil elements", SliceValidationError{errors.New("test error"), nil}, "[0]: test error"},
|
||||||
{"has zero elements", SliceValidationError{}, ""},
|
{"has zero elements", SliceValidationError{}, ""},
|
||||||
{"has one element", SliceValidationError{errors.New("test one error")}, "[0]: test one error"},
|
{"has one element", SliceValidationError{errors.New("test one error")}, "[0]: test one error"},
|
||||||
{"has two elements",
|
{
|
||||||
|
"has two elements",
|
||||||
SliceValidationError{
|
SliceValidationError{
|
||||||
errors.New("first error"),
|
errors.New("first error"),
|
||||||
errors.New("second error"),
|
errors.New("second error"),
|
||||||
},
|
},
|
||||||
"[0]: first error\n[1]: second error",
|
"[0]: first error\n[1]: second error",
|
||||||
},
|
},
|
||||||
{"has many elements",
|
{
|
||||||
|
"has many elements",
|
||||||
SliceValidationError{
|
SliceValidationError{
|
||||||
errors.New("first error"),
|
errors.New("first error"),
|
||||||
errors.New("second error"),
|
errors.New("second error"),
|
||||||
|
|||||||
+5
-3
@@ -11,9 +11,11 @@ import (
|
|||||||
|
|
||||||
const defaultMemory = 32 << 20
|
const defaultMemory = 32 << 20
|
||||||
|
|
||||||
type formBinding struct{}
|
type (
|
||||||
type formPostBinding struct{}
|
formBinding struct{}
|
||||||
type formMultipartBinding struct{}
|
formPostBinding struct{}
|
||||||
|
formMultipartBinding struct{}
|
||||||
|
)
|
||||||
|
|
||||||
func (formBinding) Name() string {
|
func (formBinding) Name() string {
|
||||||
return "form"
|
return "form"
|
||||||
|
|||||||
+15
-9
@@ -13,8 +13,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.company.lan/gopkg/gin/codec/json"
|
||||||
"git.company.lan/gopkg/gin/internal/bytesconv"
|
"git.company.lan/gopkg/gin/internal/bytesconv"
|
||||||
"git.company.lan/gopkg/gin/internal/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -175,7 +175,7 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter
|
|||||||
|
|
||||||
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
|
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
|
||||||
type BindUnmarshaler interface {
|
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
|
UnmarshalParam(param string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,9 +333,9 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
|
|||||||
case multipart.FileHeader:
|
case multipart.FileHeader:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
return json.API.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
||||||
case reflect.Map:
|
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:
|
case reflect.Ptr:
|
||||||
if !value.Elem().IsValid() {
|
if !value.Elem().IsValid() {
|
||||||
value.Set(reflect.New(value.Type().Elem()))
|
value.Set(reflect.New(value.Type().Elem()))
|
||||||
@@ -398,18 +398,24 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch tf := strings.ToLower(timeFormat); tf {
|
switch tf := strings.ToLower(timeFormat); tf {
|
||||||
case "unix", "unixnano":
|
case "unix", "unixmilli", "unixmicro", "unixnano":
|
||||||
tv, err := strconv.ParseInt(val, 10, 64)
|
tv, err := strconv.ParseInt(val, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d := time.Duration(1)
|
var t time.Time
|
||||||
if tf == "unixnano" {
|
switch tf {
|
||||||
d = time.Second
|
case "unix":
|
||||||
|
t = time.Unix(tv, 0)
|
||||||
|
case "unixmilli":
|
||||||
|
t = time.UnixMilli(tv)
|
||||||
|
case "unixmicro":
|
||||||
|
t = time.UnixMicro(tv)
|
||||||
|
default:
|
||||||
|
t = time.Unix(0, tv)
|
||||||
}
|
}
|
||||||
|
|
||||||
t := time.Unix(tv/int64(d), tv%int64(d))
|
|
||||||
value.Set(reflect.ValueOf(t))
|
value.Set(reflect.ValueOf(t))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"errors"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -494,7 +494,7 @@ type customUnmarshalParamType struct {
|
|||||||
func (f *customUnmarshalParamType) UnmarshalParam(param string) error {
|
func (f *customUnmarshalParamType) UnmarshalParam(param string) error {
|
||||||
parts := strings.Split(param, ":")
|
parts := strings.Split(param, ":")
|
||||||
if len(parts) != 3 {
|
if len(parts) != 3 {
|
||||||
return fmt.Errorf("invalid format")
|
return errors.New("invalid format")
|
||||||
}
|
}
|
||||||
f.Protocol = parts[0]
|
f.Protocol = parts[0]
|
||||||
f.Path = parts[1]
|
f.Path = parts[1]
|
||||||
@@ -509,9 +509,9 @@ func TestMappingCustomStructTypeWithFormTag(t *testing.T) {
|
|||||||
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form")
|
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.EqualValues(t, "file", s.FileData.Protocol)
|
assert.Equal(t, "file", s.FileData.Protocol)
|
||||||
assert.EqualValues(t, "/foo", s.FileData.Path)
|
assert.Equal(t, "/foo", s.FileData.Path)
|
||||||
assert.EqualValues(t, "happiness", s.FileData.Name)
|
assert.Equal(t, "happiness", s.FileData.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMappingCustomStructTypeWithURITag(t *testing.T) {
|
func TestMappingCustomStructTypeWithURITag(t *testing.T) {
|
||||||
@@ -521,9 +521,9 @@ func TestMappingCustomStructTypeWithURITag(t *testing.T) {
|
|||||||
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri")
|
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.EqualValues(t, "file", s.FileData.Protocol)
|
assert.Equal(t, "file", s.FileData.Protocol)
|
||||||
assert.EqualValues(t, "/foo", s.FileData.Path)
|
assert.Equal(t, "/foo", s.FileData.Path)
|
||||||
assert.EqualValues(t, "happiness", s.FileData.Name)
|
assert.Equal(t, "happiness", s.FileData.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) {
|
func TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) {
|
||||||
@@ -533,9 +533,9 @@ func TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) {
|
|||||||
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form")
|
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.EqualValues(t, "file", s.FileData.Protocol)
|
assert.Equal(t, "file", s.FileData.Protocol)
|
||||||
assert.EqualValues(t, "/foo", s.FileData.Path)
|
assert.Equal(t, "/foo", s.FileData.Path)
|
||||||
assert.EqualValues(t, "happiness", s.FileData.Name)
|
assert.Equal(t, "happiness", s.FileData.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) {
|
func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) {
|
||||||
@@ -545,9 +545,9 @@ func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) {
|
|||||||
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri")
|
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.EqualValues(t, "file", s.FileData.Protocol)
|
assert.Equal(t, "file", s.FileData.Protocol)
|
||||||
assert.EqualValues(t, "/foo", s.FileData.Path)
|
assert.Equal(t, "/foo", s.FileData.Path)
|
||||||
assert.EqualValues(t, "happiness", s.FileData.Name)
|
assert.Equal(t, "happiness", s.FileData.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
type customPath []string
|
type customPath []string
|
||||||
@@ -556,7 +556,7 @@ func (p *customPath) UnmarshalParam(param string) error {
|
|||||||
elems := strings.Split(param, "/")
|
elems := strings.Split(param, "/")
|
||||||
n := len(elems)
|
n := len(elems)
|
||||||
if n < 2 {
|
if n < 2 {
|
||||||
return fmt.Errorf("invalid format")
|
return errors.New("invalid format")
|
||||||
}
|
}
|
||||||
|
|
||||||
*p = elems
|
*p = elems
|
||||||
@@ -570,8 +570,8 @@ func TestMappingCustomSliceUri(t *testing.T) {
|
|||||||
err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "uri")
|
err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "uri")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.EqualValues(t, "bar", s.FileData[0])
|
assert.Equal(t, "bar", s.FileData[0])
|
||||||
assert.EqualValues(t, "foo", s.FileData[1])
|
assert.Equal(t, "foo", s.FileData[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMappingCustomSliceForm(t *testing.T) {
|
func TestMappingCustomSliceForm(t *testing.T) {
|
||||||
@@ -581,8 +581,8 @@ func TestMappingCustomSliceForm(t *testing.T) {
|
|||||||
err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "form")
|
err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "form")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.EqualValues(t, "bar", s.FileData[0])
|
assert.Equal(t, "bar", s.FileData[0])
|
||||||
assert.EqualValues(t, "foo", s.FileData[1])
|
assert.Equal(t, "foo", s.FileData[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
type objectID [12]byte
|
type objectID [12]byte
|
||||||
@@ -600,7 +600,7 @@ func (o *objectID) UnmarshalParam(param string) error {
|
|||||||
func convertTo(s string) (objectID, error) {
|
func convertTo(s string) (objectID, error) {
|
||||||
var nilObjectID objectID
|
var nilObjectID objectID
|
||||||
if len(s) != 24 {
|
if len(s) != 24 {
|
||||||
return nilObjectID, fmt.Errorf("invalid format")
|
return nilObjectID, errors.New("invalid format")
|
||||||
}
|
}
|
||||||
|
|
||||||
var oid [12]byte
|
var oid [12]byte
|
||||||
@@ -621,7 +621,7 @@ func TestMappingCustomArrayUri(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expected, _ := convertTo(val)
|
expected, _ := convertTo(val)
|
||||||
assert.EqualValues(t, expected, s.FileData)
|
assert.Equal(t, expected, s.FileData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMappingCustomArrayForm(t *testing.T) {
|
func TestMappingCustomArrayForm(t *testing.T) {
|
||||||
@@ -633,5 +633,5 @@ func TestMappingCustomArrayForm(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expected, _ := convertTo(val)
|
expected, _ := convertTo(val)
|
||||||
assert.EqualValues(t, expected, s.FileData)
|
assert.Equal(t, expected, s.FileData)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ func (headerBinding) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (headerBinding) Bind(req *http.Request, obj any) error {
|
func (headerBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
|
||||||
if err := mapHeader(obj, req.Header); err != nil {
|
if err := mapHeader(obj, req.Header); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -10,7 +10,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.company.lan/gopkg/gin/internal/json"
|
"git.company.lan/gopkg/gin/codec/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnableDecoderUseNumber is used to call the UseNumber method on the 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 {
|
func decodeJSON(r io.Reader, obj any) error {
|
||||||
decoder := json.NewDecoder(r)
|
decoder := json.API.NewDecoder(r)
|
||||||
if EnableDecoderUseNumber {
|
if EnableDecoderUseNumber {
|
||||||
decoder.UseNumber()
|
decoder.UseNumber()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,16 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"git.company.lan/gopkg/gin/codec/json"
|
||||||
|
"git.company.lan/gopkg/gin/render"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -28,3 +36,181 @@ func TestJSONBindingBindBodyMap(t *testing.T) {
|
|||||||
assert.Equal(t, "FOO", s["foo"])
|
assert.Equal(t, "FOO", s["foo"])
|
||||||
assert.Equal(t, "world", s["hello"])
|
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
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request
|
|||||||
err := mw.Close()
|
err := mw.Close()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", "/", &body)
|
req, err := http.NewRequest(http.MethodPost, "/", &body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+mw.Boundary())
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+mw.Boundary())
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ func (plainBinding) Name() string {
|
|||||||
return "plain"
|
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)
|
all, err := io.ReadAll(req.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ func (protobufBinding) BindBody(body []byte, obj any) error {
|
|||||||
if err := proto.Unmarshal(body, msg); err != nil {
|
if err := proto.Unmarshal(body, msg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Here it's same to return validate(obj), but util now we can't add
|
// Here it's same to return validate(obj), but until now we can't add
|
||||||
// `binding:""` to the struct which automatically generate by gen-proto
|
// `binding:""` to the struct which automatically generate by gen-proto
|
||||||
return nil
|
return nil
|
||||||
// return validate(obj)
|
// return validate(obj)
|
||||||
|
|||||||
+1
-1
@@ -31,5 +31,5 @@ func decodeToml(r io.Reader, obj any) error {
|
|||||||
if err := decoder.Decode(obj); err != nil {
|
if err := decoder.Decode(obj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return decoder.Decode(obj)
|
return validate(obj)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,16 +158,16 @@ type structNoValidationPointer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateNoValidationPointers(t *testing.T) {
|
func TestValidateNoValidationPointers(t *testing.T) {
|
||||||
//origin := createNoValidation_values()
|
// origin := createNoValidation_values()
|
||||||
//test := createNoValidation_values()
|
// test := createNoValidation_values()
|
||||||
empty := structNoValidationPointer{}
|
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))
|
||||||
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
|
type Object map[string]any
|
||||||
@@ -198,7 +198,7 @@ type structModifyValidation struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func toZero(sl validator.StructLevel) {
|
func toZero(sl validator.StructLevel) {
|
||||||
var s *structModifyValidation = sl.Top().Interface().(*structModifyValidation)
|
s := sl.Top().Interface().(*structModifyValidation)
|
||||||
s.Integer = 0
|
s.Integer = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,5 +249,5 @@ func TestValidatorEngine(t *testing.T) {
|
|||||||
// Check that we got back non-nil errs
|
// Check that we got back non-nil errs
|
||||||
require.Error(t, errs)
|
require.Error(t, errs)
|
||||||
// Check that the error matches expectation
|
// Check that the error matches expectation
|
||||||
require.Error(t, errs, "", "", "notone")
|
require.Error(t, errs, "notone")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ func (xmlBinding) Bind(req *http.Request, obj any) error {
|
|||||||
func (xmlBinding) BindBody(body []byte, obj any) error {
|
func (xmlBinding) BindBody(body []byte, obj any) error {
|
||||||
return decodeXML(bytes.NewReader(body), obj)
|
return decodeXML(bytes.NewReader(body), obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeXML(r io.Reader, obj any) error {
|
func decodeXML(r io.Reader, obj any) error {
|
||||||
decoder := xml.NewDecoder(r)
|
decoder := xml.NewDecoder(r)
|
||||||
if err := decoder.Decode(obj); err != nil {
|
if err := decoder.Decode(obj); err != nil {
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"github.com/goccy/go-yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
type yamlBinding struct{}
|
type yamlBinding struct{}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
+160
-186
@@ -6,7 +6,9 @@ package gin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
@@ -71,7 +73,7 @@ type Context struct {
|
|||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
// Keys is a key/value pair exclusively for the context of each request.
|
// 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 is a list of errors attached to all the handlers/middlewares who used this context.
|
||||||
Errors errorMsgs
|
Errors errorMsgs
|
||||||
@@ -128,7 +130,7 @@ func (c *Context) Copy() *Context {
|
|||||||
cp.fullPath = c.fullPath
|
cp.fullPath = c.fullPath
|
||||||
|
|
||||||
cKeys := c.Keys
|
cKeys := c.Keys
|
||||||
cp.Keys = make(map[string]any, len(cKeys))
|
cp.Keys = make(map[any]any, len(cKeys))
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
for k, v := range cKeys {
|
for k, v := range cKeys {
|
||||||
cp.Keys[k] = v
|
cp.Keys[k] = v
|
||||||
@@ -186,10 +188,9 @@ func (c *Context) FullPath() string {
|
|||||||
func (c *Context) Next() {
|
func (c *Context) Next() {
|
||||||
c.index++
|
c.index++
|
||||||
for c.index < int8(len(c.handlers)) {
|
for c.index < int8(len(c.handlers)) {
|
||||||
if c.handlers[c.index] == nil {
|
if c.handlers[c.index] != nil {
|
||||||
continue
|
|
||||||
}
|
|
||||||
c.handlers[c.index](c)
|
c.handlers[c.index](c)
|
||||||
|
}
|
||||||
c.index++
|
c.index++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,6 +216,14 @@ func (c *Context) AbortWithStatus(code int) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
c.Abort()
|
||||||
|
c.PureJSON(code, jsonObj)
|
||||||
|
}
|
||||||
|
|
||||||
// AbortWithStatusJSON calls `Abort()` and then `JSON` internally.
|
// AbortWithStatusJSON calls `Abort()` and then `JSON` internally.
|
||||||
// This method stops the chain, writes the status code and return a JSON body.
|
// This method stops the chain, writes the status code and return a JSON body.
|
||||||
// It also sets the Content-Type as "application/json".
|
// It also sets the Content-Type as "application/json".
|
||||||
@@ -264,11 +273,11 @@ func (c *Context) Error(err error) *Error {
|
|||||||
|
|
||||||
// Set is used to store a new key/value pair exclusively for this context.
|
// 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 string, value any) {
|
func (c *Context) Set(key any, value any) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
if c.Keys == nil {
|
if c.Keys == nil {
|
||||||
c.Keys = make(map[string]any)
|
c.Keys = make(map[any]any)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Keys[key] = value
|
c.Keys[key] = value
|
||||||
@@ -276,7 +285,7 @@ func (c *Context) Set(key string, value any) {
|
|||||||
|
|
||||||
// Get returns the value for the given key, ie: (value, true).
|
// Get returns the value for the given key, ie: (value, true).
|
||||||
// If the value does not exist it returns (nil, false)
|
// 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()
|
c.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
defer c.mu.RUnlock()
|
||||||
value, exists = c.Keys[key]
|
value, exists = c.Keys[key]
|
||||||
@@ -284,255 +293,178 @@ func (c *Context) Get(key string) (value any, exists bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MustGet returns the value for the given key if it exists, otherwise it panics.
|
// 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 {
|
if value, exists := c.Get(key); exists {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
panic("Key \"" + key + "\" does not exist")
|
panic(fmt.Sprintf("key %v does not exist", key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTyped[T any](c *Context, key any) (res T) {
|
||||||
|
if val, ok := c.Get(key); ok && val != nil {
|
||||||
|
res, _ = val.(T)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetString returns the value associated with the key as a string.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[string](c, key)
|
||||||
s, _ = val.(string)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBool returns the value associated with the key as a boolean.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[bool](c, key)
|
||||||
b, _ = val.(bool)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt returns the value associated with the key as an integer.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[int](c, key)
|
||||||
i, _ = val.(int)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt8 returns the value associated with the key as an integer 8.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[int8](c, key)
|
||||||
i8, _ = val.(int8)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt16 returns the value associated with the key as an integer 16.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[int16](c, key)
|
||||||
i16, _ = val.(int16)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt32 returns the value associated with the key as an integer 32.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[int32](c, key)
|
||||||
i32, _ = val.(int32)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt64 returns the value associated with the key as an integer 64.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[int64](c, key)
|
||||||
i64, _ = val.(int64)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUint returns the value associated with the key as an unsigned integer.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[uint](c, key)
|
||||||
ui, _ = val.(uint)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUint8 returns the value associated with the key as an unsigned integer 8.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[uint8](c, key)
|
||||||
ui8, _ = val.(uint8)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUint16 returns the value associated with the key as an unsigned integer 16.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[uint16](c, key)
|
||||||
ui16, _ = val.(uint16)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUint32 returns the value associated with the key as an unsigned integer 32.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[uint32](c, key)
|
||||||
ui32, _ = val.(uint32)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUint64 returns the value associated with the key as an unsigned integer 64.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[uint64](c, key)
|
||||||
ui64, _ = val.(uint64)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFloat32 returns the value associated with the key as a float32.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[float32](c, key)
|
||||||
f32, _ = val.(float32)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFloat64 returns the value associated with the key as a float64.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[float64](c, key)
|
||||||
f64, _ = val.(float64)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTime returns the value associated with the key as time.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[time.Time](c, key)
|
||||||
t, _ = val.(time.Time)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDuration returns the value associated with the key as a duration.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[time.Duration](c, key)
|
||||||
d, _ = val.(time.Duration)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) GetIntSlice(key string) (is []int) {
|
// GetIntSlice returns the value associated with the key as a slice of integers.
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
func (c *Context) GetIntSlice(key any) (is []int) {
|
||||||
is, _ = val.([]int)
|
return getTyped[[]int](c, key)
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) GetInt8Slice(key string) (i8s []int8) {
|
// GetInt8Slice returns the value associated with the key as a slice of int8 integers.
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
func (c *Context) GetInt8Slice(key any) (i8s []int8) {
|
||||||
i8s, _ = val.([]int8)
|
return getTyped[[]int8](c, key)
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) GetInt16Slice(key string) (i16s []int16) {
|
// GetInt16Slice returns the value associated with the key as a slice of int16 integers.
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
func (c *Context) GetInt16Slice(key any) (i16s []int16) {
|
||||||
i16s, _ = val.([]int16)
|
return getTyped[[]int16](c, key)
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) GetInt32Slice(key string) (i32s []int32) {
|
// GetInt32Slice returns the value associated with the key as a slice of int32 integers.
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
func (c *Context) GetInt32Slice(key any) (i32s []int32) {
|
||||||
i32s, _ = val.([]int32)
|
return getTyped[[]int32](c, key)
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) GetInt64Slice(key string) (i64s []int64) {
|
// GetInt64Slice returns the value associated with the key as a slice of int64 integers.
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
func (c *Context) GetInt64Slice(key any) (i64s []int64) {
|
||||||
i64s, _ = val.([]int64)
|
return getTyped[[]int64](c, key)
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) GetUintSlice(key string) (uis []uint) {
|
// GetUintSlice returns the value associated with the key as a slice of unsigned integers.
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
func (c *Context) GetUintSlice(key any) (uis []uint) {
|
||||||
uis, _ = val.([]uint)
|
return getTyped[[]uint](c, key)
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) GetUint8Slice(key string) (ui8s []uint8) {
|
// GetUint8Slice returns the value associated with the key as a slice of uint8 integers.
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
func (c *Context) GetUint8Slice(key any) (ui8s []uint8) {
|
||||||
ui8s, _ = val.([]uint8)
|
return getTyped[[]uint8](c, key)
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) GetUint16Slice(key string) (ui16s []uint16) {
|
// GetUint16Slice returns the value associated with the key as a slice of uint16 integers.
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
func (c *Context) GetUint16Slice(key any) (ui16s []uint16) {
|
||||||
ui16s, _ = val.([]uint16)
|
return getTyped[[]uint16](c, key)
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) GetUint32Slice(key string) (ui32s []uint32) {
|
// GetUint32Slice returns the value associated with the key as a slice of uint32 integers.
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
func (c *Context) GetUint32Slice(key any) (ui32s []uint32) {
|
||||||
ui32s, _ = val.([]uint32)
|
return getTyped[[]uint32](c, key)
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) GetUint64Slice(key string) (ui64s []uint64) {
|
// GetUint64Slice returns the value associated with the key as a slice of uint64 integers.
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
func (c *Context) GetUint64Slice(key any) (ui64s []uint64) {
|
||||||
ui64s, _ = val.([]uint64)
|
return getTyped[[]uint64](c, key)
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) GetFloat32Slice(key string) (f32s []float32) {
|
// GetFloat32Slice returns the value associated with the key as a slice of float32 numbers.
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
func (c *Context) GetFloat32Slice(key any) (f32s []float32) {
|
||||||
f32s, _ = val.([]float32)
|
return getTyped[[]float32](c, key)
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) GetFloat64Slice(key string) (f64s []float64) {
|
// GetFloat64Slice returns the value associated with the key as a slice of float64 numbers.
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
func (c *Context) GetFloat64Slice(key any) (f64s []float64) {
|
||||||
f64s, _ = val.([]float64)
|
return getTyped[[]float64](c, key)
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStringSlice returns the value associated with the key as a slice of strings.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[[]string](c, key)
|
||||||
ss, _ = val.([]string)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStringMap returns the value associated with the key as a map of interfaces.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[map[string]any](c, key)
|
||||||
sm, _ = val.(map[string]any)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStringMapString returns the value associated with the key as a map of strings.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[map[string]string](c, key)
|
||||||
sms, _ = val.(map[string]string)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
|
// 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) {
|
||||||
if val, ok := c.Get(key); ok && val != nil {
|
return getTyped[map[string][]string](c, key)
|
||||||
smss, _ = val.(map[string][]string)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************/
|
/************************************/
|
||||||
@@ -641,7 +573,7 @@ func (c *Context) QueryMap(key string) (dicts map[string]string) {
|
|||||||
// whether at least one value exists for the given key.
|
// whether at least one value exists for the given key.
|
||||||
func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
|
func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
|
||||||
c.initQueryCache()
|
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
|
// PostForm returns the specified key from a POST urlencoded form or multipart form
|
||||||
@@ -714,22 +646,32 @@ func (c *Context) PostFormMap(key string) (dicts map[string]string) {
|
|||||||
// whether at least one value exists for the given key.
|
// whether at least one value exists for the given key.
|
||||||
func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
|
func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
|
||||||
c.initFormCache()
|
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.
|
// getMapFromFormData return a map which satisfies conditions.
|
||||||
func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) {
|
// It parses from data with bracket notation like "key[subkey]=value" into a map.
|
||||||
dicts := make(map[string]string)
|
func getMapFromFormData(m map[string][]string, key string) (map[string]string, bool) {
|
||||||
exist := false
|
d := make(map[string]string)
|
||||||
|
found := false
|
||||||
|
keyLen := len(key)
|
||||||
|
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key {
|
if len(k) < keyLen+3 { // key + "[" + at least one char + "]"
|
||||||
if j := strings.IndexByte(k[i+1:], ']'); j >= 1 {
|
continue
|
||||||
exist = true
|
}
|
||||||
dicts[k[i+1:][:j]] = v[0]
|
|
||||||
|
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 dicts, exist
|
return d, found
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormFile returns the first file for the provided form key.
|
// FormFile returns the first file for the provided form key.
|
||||||
@@ -754,14 +696,22 @@ func (c *Context) MultipartForm() (*multipart.Form, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SaveUploadedFile uploads the form file to specific dst.
|
// SaveUploadedFile uploads the form file to specific dst.
|
||||||
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error {
|
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string, perm ...fs.FileMode) error {
|
||||||
src, err := file.Open()
|
src, err := file.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer src.Close()
|
defer src.Close()
|
||||||
|
|
||||||
if err = os.MkdirAll(filepath.Dir(dst), 0o750); err != nil {
|
var mode os.FileMode = 0o750
|
||||||
|
if len(perm) > 0 {
|
||||||
|
mode = perm[0]
|
||||||
|
}
|
||||||
|
dir := filepath.Dir(dst)
|
||||||
|
if err = os.MkdirAll(dir, mode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = os.Chmod(dir, mode); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -838,8 +788,19 @@ func (c *Context) BindUri(obj any) error {
|
|||||||
// It will abort the request with HTTP 400 if any error occurs.
|
// It will abort the request with HTTP 400 if any error occurs.
|
||||||
// See the binding package.
|
// See the binding package.
|
||||||
func (c *Context) MustBindWith(obj any, b binding.Binding) error {
|
func (c *Context) MustBindWith(obj any, b binding.Binding) error {
|
||||||
if err := c.ShouldBindWith(obj, b); err != nil {
|
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
|
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -958,7 +919,7 @@ func (c *Context) ShouldBindBodyWithPlain(obj any) error {
|
|||||||
|
|
||||||
// ClientIP implements one best effort algorithm to return the real client IP.
|
// ClientIP implements one best effort algorithm to return the real client IP.
|
||||||
// It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
|
// It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
|
||||||
// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
|
// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-IP]).
|
||||||
// If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy,
|
// If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy,
|
||||||
// the remote IP (coming from Request.RemoteAddr) is returned.
|
// the remote IP (coming from Request.RemoteAddr) is returned.
|
||||||
func (c *Context) ClientIP() string {
|
func (c *Context) ClientIP() string {
|
||||||
@@ -1096,6 +1057,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
|
// Cookie returns the named cookie provided in the request or
|
||||||
// ErrNoCookie if not found. And return the named cookie is unescaped.
|
// ErrNoCookie if not found. And return the named cookie is unescaped.
|
||||||
// If multiple cookies match the given name, only one cookie will
|
// If multiple cookies match the given name, only one cookie will
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
+710
-110
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ginSupportMinGoVer = 21
|
const ginSupportMinGoVer = 23
|
||||||
|
|
||||||
// IsDebugging returns true if the framework is running in debug mode.
|
// IsDebugging returns true if the framework is running in debug mode.
|
||||||
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||||
@@ -25,7 +25,7 @@ func IsDebugging() bool {
|
|||||||
var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int)
|
var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int)
|
||||||
|
|
||||||
// DebugPrintFunc indicates debug log output format.
|
// 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) {
|
func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {
|
||||||
if IsDebugging() {
|
if IsDebugging() {
|
||||||
@@ -78,7 +78,7 @@ func getMinVer(v string) (uint64, error) {
|
|||||||
|
|
||||||
func debugPrintWARNINGDefault() {
|
func debugPrintWARNINGDefault() {
|
||||||
if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer {
|
if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer {
|
||||||
debugPrint(`[WARNING] Now Gin requires Go 1.21+.
|
debugPrint(`[WARNING] Now Gin requires Go 1.23+.
|
||||||
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -10,6 +10,7 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -60,7 +61,7 @@ func TestDebugPrintError(t *testing.T) {
|
|||||||
func TestDebugPrintRoutes(t *testing.T) {
|
func TestDebugPrintRoutes(t *testing.T) {
|
||||||
re := captureOutput(t, func() {
|
re := captureOutput(t, func() {
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest})
|
debugPrintRoute(http.MethodGet, "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest})
|
||||||
SetMode(TestMode)
|
SetMode(TestMode)
|
||||||
})
|
})
|
||||||
assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?git.company.lan/gopkg/gin.handlerNameTest \(2 handlers\)\n$`, re)
|
assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?git.company.lan/gopkg/gin.handlerNameTest \(2 handlers\)\n$`, re)
|
||||||
@@ -72,7 +73,7 @@ func TestDebugPrintRouteFunc(t *testing.T) {
|
|||||||
}
|
}
|
||||||
re := captureOutput(t, func() {
|
re := captureOutput(t, func() {
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
debugPrintRoute("GET", "/path/to/route/:param1/:param2", HandlersChain{func(c *Context) {}, handlerNameTest})
|
debugPrintRoute(http.MethodGet, "/path/to/route/:param1/:param2", HandlersChain{func(c *Context) {}, handlerNameTest})
|
||||||
SetMode(TestMode)
|
SetMode(TestMode)
|
||||||
})
|
})
|
||||||
assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param1/:param2 --> (.*/vendor/)?git.company.lan/gopkg/gin.handlerNameTest \(2 handlers\)\n$`, re)
|
assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param1/:param2 --> (.*/vendor/)?git.company.lan/gopkg/gin.handlerNameTest \(2 handlers\)\n$`, re)
|
||||||
@@ -105,7 +106,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
|
|||||||
})
|
})
|
||||||
m, e := getMinVer(runtime.Version())
|
m, e := getMinVer(runtime.Version())
|
||||||
if e == nil && m < ginSupportMinGoVer {
|
if e == nil && m < ginSupportMinGoVer {
|
||||||
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.21+.\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.23+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||||
} else {
|
} 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)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ func TestBindWith(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused"))
|
c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused"))
|
||||||
|
|
||||||
var obj struct {
|
var obj struct {
|
||||||
Foo string `form:"foo"`
|
Foo string `form:"foo"`
|
||||||
|
|||||||
@@ -2,5 +2,21 @@
|
|||||||
Package gin implements a HTTP web framework called gin.
|
Package gin implements a HTTP web framework called gin.
|
||||||
|
|
||||||
See https://gin-gonic.com/ for more information about 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 "git.company.lan/gopkg/gin"
|
package gin // import "git.company.lan/gopkg/gin"
|
||||||
|
|||||||
+139
-17
@@ -63,6 +63,7 @@
|
|||||||
- [http2 server push](#http2-server-push)
|
- [http2 server push](#http2-server-push)
|
||||||
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
|
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
|
||||||
- [Set and get a cookie](#set-and-get-a-cookie)
|
- [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)
|
- [Don't trust all proxies](#dont-trust-all-proxies)
|
||||||
- [Testing](#testing)
|
- [Testing](#testing)
|
||||||
|
|
||||||
@@ -70,7 +71,7 @@
|
|||||||
|
|
||||||
### Build with json replacement
|
### Build with json replacement
|
||||||
|
|
||||||
Gin uses `encoding/json` as default json package but you can change it by build from other tags.
|
Gin uses `encoding/json` as the default JSON package but you can change it by building from other tags.
|
||||||
|
|
||||||
[jsoniter](https://github.com/json-iterator/go)
|
[jsoniter](https://github.com/json-iterator/go)
|
||||||
|
|
||||||
@@ -84,10 +85,10 @@ go build -tags=jsoniter .
|
|||||||
go build -tags=go_json .
|
go build -tags=go_json .
|
||||||
```
|
```
|
||||||
|
|
||||||
[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu support avx instruction.)
|
[sonic](https://github.com/bytedance/sonic)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ go build -tags="sonic avx" .
|
$ go build -tags=sonic .
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build without `MsgPack` rendering feature
|
### Build without `MsgPack` rendering feature
|
||||||
@@ -120,7 +121,7 @@ func main() {
|
|||||||
router.HEAD("/someHead", head)
|
router.HEAD("/someHead", head)
|
||||||
router.OPTIONS("/someOptions", options)
|
router.OPTIONS("/someOptions", options)
|
||||||
|
|
||||||
// By default it serves on :8080 unless a
|
// By default, it serves on :8080 unless a
|
||||||
// PORT environment variable was defined.
|
// PORT environment variable was defined.
|
||||||
router.Run()
|
router.Run()
|
||||||
// router.Run(":3000") for a hard coded port
|
// router.Run(":3000") for a hard coded port
|
||||||
@@ -172,7 +173,7 @@ func main() {
|
|||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
// Query string parameters are parsed using the existing underlying request object.
|
// Query string parameters are parsed using the existing underlying request object.
|
||||||
// The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe
|
// The request responds to a URL matching: /welcome?firstname=Jane&lastname=Doe
|
||||||
router.GET("/welcome", func(c *gin.Context) {
|
router.GET("/welcome", func(c *gin.Context) {
|
||||||
firstname := c.DefaultQuery("firstname", "Guest")
|
firstname := c.DefaultQuery("firstname", "Guest")
|
||||||
lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
|
lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
|
||||||
@@ -300,7 +301,7 @@ curl -X POST http://localhost:8080/upload \
|
|||||||
|
|
||||||
#### Multiple files
|
#### Multiple files
|
||||||
|
|
||||||
See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple).
|
See the detailed [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple).
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
@@ -704,7 +705,7 @@ $ curl -v -X POST \
|
|||||||
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
|
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
|
||||||
```
|
```
|
||||||
|
|
||||||
Skip validate: when running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again.
|
Skip-validation: Running the example above using the `curl` command returns an error. This is because the example uses `binding:"required"` for `Password`. If instead, you use `binding:"-"` for `Password`, then it will not return an error when you run the example again.
|
||||||
|
|
||||||
### Custom Validators
|
### Custom Validators
|
||||||
|
|
||||||
@@ -832,6 +833,8 @@ type Person struct {
|
|||||||
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
|
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
|
||||||
CreateTime time.Time `form:"createTime" time_format:"unixNano"`
|
CreateTime time.Time `form:"createTime" time_format:"unixNano"`
|
||||||
UnixTime time.Time `form:"unixTime" time_format:"unix"`
|
UnixTime time.Time `form:"unixTime" time_format:"unix"`
|
||||||
|
UnixMilliTime time.Time `form:"unixMilliTime" time_format:"unixmilli"`
|
||||||
|
UnixMicroTime time.Time `form:"unixMicroTime" time_format:"uNiXmIcRo"` // case does not matter for "unix*" time formats
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -851,6 +854,8 @@ func startPage(c *gin.Context) {
|
|||||||
log.Println(person.Birthday)
|
log.Println(person.Birthday)
|
||||||
log.Println(person.CreateTime)
|
log.Println(person.CreateTime)
|
||||||
log.Println(person.UnixTime)
|
log.Println(person.UnixTime)
|
||||||
|
log.Println(person.UnixMilliTime)
|
||||||
|
log.Println(person.UnixMicroTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.String(http.StatusOK, "Success")
|
c.String(http.StatusOK, "Success")
|
||||||
@@ -860,14 +865,14 @@ func startPage(c *gin.Context) {
|
|||||||
Test it with:
|
Test it with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"
|
curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033&unixMilliTime=1562400033001&unixMicroTime=1562400033000012"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Bind default value if none provided
|
### Bind default value if none provided
|
||||||
|
|
||||||
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:
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -1186,7 +1191,7 @@ func main() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
r.GET("/moreJSON", func(c *gin.Context) {
|
r.GET("/moreJSON", func(c *gin.Context) {
|
||||||
// You also can use a struct
|
// You can also use a struct
|
||||||
var msg struct {
|
var msg struct {
|
||||||
Name string `json:"user"`
|
Name string `json:"user"`
|
||||||
Message string
|
Message string
|
||||||
@@ -1392,13 +1397,19 @@ func main() {
|
|||||||
|
|
||||||
### HTML rendering
|
### HTML rendering
|
||||||
|
|
||||||
Using LoadHTMLGlob() or LoadHTMLFiles()
|
Using LoadHTMLGlob() or LoadHTMLFiles() or LoadHTMLFS()
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
//go:embed templates/*
|
||||||
|
var templates embed.FS
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
router.LoadHTMLGlob("templates/*")
|
router.LoadHTMLGlob("templates/*")
|
||||||
//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
|
//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) {
|
router.GET("/index", func(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||||
"title": "Main website",
|
"title": "Main website",
|
||||||
@@ -1485,7 +1496,7 @@ You may use custom delims
|
|||||||
|
|
||||||
#### Custom Template Funcs
|
#### Custom Template Funcs
|
||||||
|
|
||||||
See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template).
|
See the detailed [example code](https://github.com/gin-gonic/examples/tree/master/template).
|
||||||
|
|
||||||
main.go
|
main.go
|
||||||
|
|
||||||
@@ -1537,7 +1548,7 @@ Date: 2017/07/01
|
|||||||
|
|
||||||
### Multitemplate
|
### Multitemplate
|
||||||
|
|
||||||
Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`.
|
Gin allows only one html.Template by default. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`.
|
||||||
|
|
||||||
### Redirects
|
### Redirects
|
||||||
|
|
||||||
@@ -2086,7 +2097,7 @@ type formB struct {
|
|||||||
func SomeHandler(c *gin.Context) {
|
func SomeHandler(c *gin.Context) {
|
||||||
objA := formA{}
|
objA := formA{}
|
||||||
objB := formB{}
|
objB := formB{}
|
||||||
// This c.ShouldBind consumes c.Request.Body and it cannot be reused.
|
// Calling c.ShouldBind consumes c.Request.Body and it cannot be reused.
|
||||||
if errA := c.ShouldBind(&objA); errA == nil {
|
if errA := c.ShouldBind(&objA); errA == nil {
|
||||||
c.String(http.StatusOK, `the body should be formA`)
|
c.String(http.StatusOK, `the body should be formA`)
|
||||||
// Always an error is occurred by this because c.Request.Body is EOF now.
|
// Always an error is occurred by this because c.Request.Body is EOF now.
|
||||||
@@ -2293,12 +2304,23 @@ func main() {
|
|||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
router.GET("/cookie", func(c *gin.Context) {
|
router.GET("/cookie", func(c *gin.Context) {
|
||||||
|
|
||||||
cookie, err := c.Cookie("gin_cookie")
|
cookie, err := c.Cookie("gin_cookie")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cookie = "NotSet"
|
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)
|
fmt.Printf("Cookie value: %s \n", cookie)
|
||||||
@@ -2308,6 +2330,106 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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"
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
## Don't trust all proxies
|
||||||
|
|
||||||
Gin lets you specify which headers to hold the real client IP (if any),
|
Gin lets you specify which headers to hold the real client IP (if any),
|
||||||
@@ -2319,7 +2441,7 @@ or network CIDRs from where clients which their request headers related to clien
|
|||||||
IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
|
IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
|
||||||
IPv6 CIDRs.
|
IPv6 CIDRs.
|
||||||
|
|
||||||
**Attention:** Gin trust all proxies by default if you don't specify a trusted
|
**Attention:** Gin trusts all proxies by default if you don't specify a trusted
|
||||||
proxy using the function above, **this is NOT safe**. At the same time, if you don't
|
proxy using the function above, **this is NOT safe**. At the same time, if you don't
|
||||||
use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`,
|
use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`,
|
||||||
then `Context.ClientIP()` will return the remote address directly to avoid some
|
then `Context.ClientIP()` will return the remote address directly to avoid some
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.company.lan/gopkg/gin/internal/json"
|
"git.company.lan/gopkg/gin/codec/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrorType is an unsigned 64-bit error code as defined in the gin spec.
|
// 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.
|
// MarshalJSON implements the json.Marshaller interface.
|
||||||
func (msg *Error) MarshalJSON() ([]byte, error) {
|
func (msg *Error) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(msg.JSON())
|
return json.API.Marshal(msg.JSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error implements the error interface.
|
// Error implements the error interface.
|
||||||
@@ -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()
|
// 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
|
return msg.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ func (a errorMsgs) JSON() any {
|
|||||||
|
|
||||||
// MarshalJSON implements the json.Marshaller interface.
|
// MarshalJSON implements the json.Marshaller interface.
|
||||||
func (a errorMsgs) MarshalJSON() ([]byte, error) {
|
func (a errorMsgs) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(a.JSON())
|
return json.API.Marshal(a.JSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a errorMsgs) String() string {
|
func (a errorMsgs) String() string {
|
||||||
|
|||||||
+18
-7
@@ -9,7 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.company.lan/gopkg/gin/internal/json"
|
"git.company.lan/gopkg/gin/codec/json"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -33,8 +33,8 @@ func TestError(t *testing.T) {
|
|||||||
"meta": "some data",
|
"meta": "some data",
|
||||||
}, err.JSON())
|
}, err.JSON())
|
||||||
|
|
||||||
jsonBytes, _ := json.Marshal(err)
|
jsonBytes, _ := json.API.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
|
err.SetMeta(H{ //nolint: errcheck
|
||||||
"status": "200",
|
"status": "200",
|
||||||
@@ -92,14 +92,14 @@ Error #03: third
|
|||||||
H{"error": "second", "meta": "some data"},
|
H{"error": "second", "meta": "some data"},
|
||||||
H{"error": "third", "status": "400"},
|
H{"error": "third", "status": "400"},
|
||||||
}, errs.JSON())
|
}, errs.JSON())
|
||||||
jsonBytes, _ := json.Marshal(errs)
|
jsonBytes, _ := json.API.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{
|
errs = errorMsgs{
|
||||||
{Err: errors.New("first"), Type: ErrorTypePrivate},
|
{Err: errors.New("first"), Type: ErrorTypePrivate},
|
||||||
}
|
}
|
||||||
assert.Equal(t, H{"error": "first"}, errs.JSON())
|
assert.Equal(t, H{"error": "first"}, errs.JSON())
|
||||||
jsonBytes, _ = json.Marshal(errs)
|
jsonBytes, _ = json.API.Marshal(errs)
|
||||||
assert.Equal(t, "{\"error\":\"first\"}", string(jsonBytes))
|
assert.JSONEq(t, "{\"error\":\"first\"}", string(jsonBytes))
|
||||||
|
|
||||||
errs = errorMsgs{}
|
errs = errorMsgs{}
|
||||||
assert.Nil(t, errs.Last())
|
assert.Nil(t, errs.Last())
|
||||||
@@ -126,4 +126,15 @@ func TestErrorUnwrap(t *testing.T) {
|
|||||||
require.ErrorIs(t, err, innerErr)
|
require.ErrorIs(t, err, innerErr)
|
||||||
var testErr TestErr
|
var testErr TestErr
|
||||||
require.ErrorAs(t, err, &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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ type OnlyFilesFS struct {
|
|||||||
// Open passes `Open` to the upstream implementation without `Readdir` functionality.
|
// Open passes `Open` to the upstream implementation without `Readdir` functionality.
|
||||||
func (o OnlyFilesFS) Open(name string) (http.File, error) {
|
func (o OnlyFilesFS) Open(name string) (http.File, error) {
|
||||||
f, err := o.FileSystem.Open(name)
|
f, err := o.FileSystem.Open(name)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.company.lan/gopkg/gin/internal/bytesconv"
|
"git.company.lan/gopkg/gin/internal/bytesconv"
|
||||||
|
filesystem "git.company.lan/gopkg/gin/internal/fs"
|
||||||
"git.company.lan/gopkg/gin/render"
|
"git.company.lan/gopkg/gin/render"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go/http3"
|
"github.com/quic-go/quic-go/http3"
|
||||||
@@ -215,7 +216,7 @@ func New(opts ...OptionFunc) *Engine {
|
|||||||
trustedProxies: []string{"0.0.0.0/0", "::/0"},
|
trustedProxies: []string{"0.0.0.0/0", "::/0"},
|
||||||
trustedCIDRs: defaultTrustedCIDRs,
|
trustedCIDRs: defaultTrustedCIDRs,
|
||||||
}
|
}
|
||||||
engine.RouterGroup.engine = engine
|
engine.engine = engine
|
||||||
engine.pool.New = func() any {
|
engine.pool.New = func() any {
|
||||||
return engine.allocateContext(engine.maxParams)
|
return engine.allocateContext(engine.maxParams)
|
||||||
}
|
}
|
||||||
@@ -285,6 +286,19 @@ func (engine *Engine) LoadHTMLFiles(files ...string) {
|
|||||||
engine.SetHTMLTemplate(templ)
|
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.
|
// SetHTMLTemplate associate a template with HTML renderer.
|
||||||
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
||||||
if len(engine.trees) > 0 {
|
if len(engine.trees) > 0 {
|
||||||
@@ -321,7 +335,7 @@ func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
|||||||
return engine
|
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 {
|
func (engine *Engine) With(opts ...OptionFunc) *Engine {
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(engine)
|
opt(engine)
|
||||||
@@ -363,7 +377,7 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Routes returns a slice of registered routes, including some useful information, such as:
|
// 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) {
|
func (engine *Engine) Routes() (routes RoutesInfo) {
|
||||||
for _, tree := range engine.trees {
|
for _, tree := range engine.trees {
|
||||||
routes = iterate("", tree.method, routes, tree.root)
|
routes = iterate("", tree.method, routes, tree.root)
|
||||||
@@ -637,10 +651,12 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
// Disclaimer: You can loop yourself to deal with this, use wisely.
|
// Disclaimer: You can loop yourself to deal with this, use wisely.
|
||||||
func (engine *Engine) HandleContext(c *Context) {
|
func (engine *Engine) HandleContext(c *Context) {
|
||||||
oldIndexValue := c.index
|
oldIndexValue := c.index
|
||||||
|
oldHandlers := c.handlers
|
||||||
c.reset()
|
c.reset()
|
||||||
engine.handleHTTPRequest(c)
|
engine.handleHTTPRequest(c)
|
||||||
|
|
||||||
c.index = oldIndexValue
|
c.index = oldIndexValue
|
||||||
|
c.handlers = oldHandlers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) handleHTTPRequest(c *Context) {
|
func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||||
|
|||||||
+10
-3
@@ -12,8 +12,10 @@ import (
|
|||||||
"git.company.lan/gopkg/gin"
|
"git.company.lan/gopkg/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var once sync.Once
|
var (
|
||||||
var internalEngine *gin.Engine
|
once sync.Once
|
||||||
|
internalEngine *gin.Engine
|
||||||
|
)
|
||||||
|
|
||||||
func engine() *gin.Engine {
|
func engine() *gin.Engine {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
@@ -32,6 +34,11 @@ func LoadHTMLFiles(files ...string) {
|
|||||||
engine().LoadHTMLFiles(files...)
|
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.
|
// SetHTMLTemplate is a wrapper for Engine.SetHTMLTemplate.
|
||||||
func SetHTMLTemplate(templ *template.Template) {
|
func SetHTMLTemplate(templ *template.Template) {
|
||||||
engine().SetHTMLTemplate(templ)
|
engine().SetHTMLTemplate(templ)
|
||||||
@@ -154,7 +161,7 @@ func RunUnix(file string) (err error) {
|
|||||||
|
|
||||||
// RunFd attaches the router to a http.Server and starts listening and serving HTTP requests
|
// RunFd attaches the router to a http.Server and starts listening and serving HTTP requests
|
||||||
// through the specified file descriptor.
|
// through the specified file descriptor.
|
||||||
// Note: the method will block the calling goroutine indefinitely unless on error happens.
|
// Note: the method will block the calling goroutine indefinitely unless an error happens.
|
||||||
func RunFd(fd int) (err error) {
|
func RunFd(fd int) (err error) {
|
||||||
return engine().RunFd(fd)
|
return engine().RunFd(fd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import (
|
|||||||
// params[1]=response status (custom compare status) default:"200 OK"
|
// params[1]=response status (custom compare status) default:"200 OK"
|
||||||
// params[2]=response body (custom compare content) default:"it worked"
|
// params[2]=response body (custom compare content) default:"it worked"
|
||||||
func testRequest(t *testing.T, params ...string) {
|
func testRequest(t *testing.T, params ...string) {
|
||||||
|
|
||||||
if len(params) == 0 {
|
if len(params) == 0 {
|
||||||
t.Fatal("url cannot be empty")
|
t.Fatal("url cannot be empty")
|
||||||
}
|
}
|
||||||
@@ -47,12 +46,12 @@ func testRequest(t *testing.T, params ...string) {
|
|||||||
body, ioerr := io.ReadAll(resp.Body)
|
body, ioerr := io.ReadAll(resp.Body)
|
||||||
require.NoError(t, ioerr)
|
require.NoError(t, ioerr)
|
||||||
|
|
||||||
var responseStatus = "200 OK"
|
responseStatus := "200 OK"
|
||||||
if len(params) > 1 && params[1] != "" {
|
if len(params) > 1 && params[1] != "" {
|
||||||
responseStatus = params[1]
|
responseStatus = params[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
var responseBody = "it worked"
|
responseBody := "it worked"
|
||||||
if len(params) > 2 && params[2] != "" {
|
if len(params) > 2 && params[2] != "" {
|
||||||
responseBody = params[2]
|
responseBody = params[2]
|
||||||
}
|
}
|
||||||
@@ -170,7 +169,7 @@ func TestRunTLS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPusher(t *testing.T) {
|
func TestPusher(t *testing.T) {
|
||||||
var html = template.Must(template.New("https").Parse(`
|
html := template.Must(template.New("https").Parse(`
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Https Test</title>
|
<title>Https Test</title>
|
||||||
|
|||||||
+184
-37
@@ -46,7 +46,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine
|
|||||||
})
|
})
|
||||||
router.GET("/raw", func(c *Context) {
|
router.GET("/raw", func(c *Context) {
|
||||||
c.HTML(http.StatusOK, "raw.tmpl", map[string]any{
|
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
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -73,7 +73,7 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) {
|
|||||||
)
|
)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
|
res, err := http.Get(ts.URL + "/test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -131,7 +131,7 @@ func TestLoadHTMLGlobTestMode(t *testing.T) {
|
|||||||
)
|
)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
|
res, err := http.Get(ts.URL + "/test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ func TestLoadHTMLGlobReleaseMode(t *testing.T) {
|
|||||||
)
|
)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
|
res, err := http.Get(ts.URL + "/test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -178,7 +178,7 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
client := &http.Client{Transport: tr}
|
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 {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
|
|||||||
)
|
)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL))
|
res, err := http.Get(ts.URL + "/raw")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -229,7 +229,7 @@ func TestLoadHTMLFilesTestMode(t *testing.T) {
|
|||||||
)
|
)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
|
res, err := http.Get(ts.URL + "/test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -249,7 +249,7 @@ func TestLoadHTMLFilesDebugMode(t *testing.T) {
|
|||||||
)
|
)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
|
res, err := http.Get(ts.URL + "/test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -269,7 +269,7 @@ func TestLoadHTMLFilesReleaseMode(t *testing.T) {
|
|||||||
)
|
)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
|
res, err := http.Get(ts.URL + "/test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -296,7 +296,7 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
client := &http.Client{Transport: tr}
|
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 {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -316,7 +316,116 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
|
|||||||
)
|
)
|
||||||
defer ts.Close()
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _ := io.ReadAll(res.Body)
|
||||||
|
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(ts.URL + "/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _ := io.ReadAll(res.Body)
|
||||||
|
assert.Equal(t, "<h1>Hello world</h1>", 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(ts.URL + "/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _ := io.ReadAll(res.Body)
|
||||||
|
assert.Equal(t, "<h1>Hello world</h1>", 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(ts.URL + "/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _ := io.ReadAll(res.Body)
|
||||||
|
assert.Equal(t, "<h1>Hello world</h1>", 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(ts.URL + "/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _ := io.ReadAll(res.Body)
|
||||||
|
assert.Equal(t, "<h1>Hello world</h1>", 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(ts.URL + "/raw")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -327,31 +436,31 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
|
|||||||
|
|
||||||
func TestAddRoute(t *testing.T) {
|
func TestAddRoute(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})
|
router.addRoute(http.MethodGet, "/", HandlersChain{func(_ *Context) {}})
|
||||||
|
|
||||||
assert.Len(t, router.trees, 1)
|
assert.Len(t, router.trees, 1)
|
||||||
assert.NotNil(t, router.trees.get("GET"))
|
assert.NotNil(t, router.trees.get(http.MethodGet))
|
||||||
assert.Nil(t, router.trees.get("POST"))
|
assert.Nil(t, router.trees.get(http.MethodPost))
|
||||||
|
|
||||||
router.addRoute("POST", "/", HandlersChain{func(_ *Context) {}})
|
router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}})
|
||||||
|
|
||||||
assert.Len(t, router.trees, 2)
|
assert.Len(t, router.trees, 2)
|
||||||
assert.NotNil(t, router.trees.get("GET"))
|
assert.NotNil(t, router.trees.get(http.MethodGet))
|
||||||
assert.NotNil(t, router.trees.get("POST"))
|
assert.NotNil(t, router.trees.get(http.MethodPost))
|
||||||
|
|
||||||
router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}})
|
router.addRoute(http.MethodPost, "/post", HandlersChain{func(_ *Context) {}})
|
||||||
assert.Len(t, router.trees, 2)
|
assert.Len(t, router.trees, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddRouteFails(t *testing.T) {
|
func TestAddRouteFails(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
assert.Panics(t, func() { router.addRoute("", "/", HandlersChain{func(_ *Context) {}}) })
|
assert.Panics(t, func() { router.addRoute("", "/", HandlersChain{func(_ *Context) {}}) })
|
||||||
assert.Panics(t, func() { router.addRoute("GET", "a", HandlersChain{func(_ *Context) {}}) })
|
assert.Panics(t, func() { router.addRoute(http.MethodGet, "a", HandlersChain{func(_ *Context) {}}) })
|
||||||
assert.Panics(t, func() { router.addRoute("GET", "/", HandlersChain{}) })
|
assert.Panics(t, func() { router.addRoute(http.MethodGet, "/", HandlersChain{}) })
|
||||||
|
|
||||||
router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}})
|
router.addRoute(http.MethodPost, "/post", HandlersChain{func(_ *Context) {}})
|
||||||
assert.Panics(t, func() {
|
assert.Panics(t, func() {
|
||||||
router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}})
|
router.addRoute(http.MethodPost, "/post", HandlersChain{func(_ *Context) {}})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,27 +602,27 @@ func TestListOfRoutes(t *testing.T) {
|
|||||||
|
|
||||||
assert.Len(t, list, 7)
|
assert.Len(t, list, 7)
|
||||||
assertRoutePresent(t, list, RouteInfo{
|
assertRoutePresent(t, list, RouteInfo{
|
||||||
Method: "GET",
|
Method: http.MethodGet,
|
||||||
Path: "/favicon.ico",
|
Path: "/favicon.ico",
|
||||||
Handler: "^(.*/vendor/)?git.company.lan/gopkg/gin.handlerTest1$",
|
Handler: "^(.*/vendor/)?git.company.lan/gopkg/gin.handlerTest1$",
|
||||||
})
|
})
|
||||||
assertRoutePresent(t, list, RouteInfo{
|
assertRoutePresent(t, list, RouteInfo{
|
||||||
Method: "GET",
|
Method: http.MethodGet,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
Handler: "^(.*/vendor/)?git.company.lan/gopkg/gin.handlerTest1$",
|
Handler: "^(.*/vendor/)?git.company.lan/gopkg/gin.handlerTest1$",
|
||||||
})
|
})
|
||||||
assertRoutePresent(t, list, RouteInfo{
|
assertRoutePresent(t, list, RouteInfo{
|
||||||
Method: "GET",
|
Method: http.MethodGet,
|
||||||
Path: "/users/",
|
Path: "/users/",
|
||||||
Handler: "^(.*/vendor/)?git.company.lan/gopkg/gin.handlerTest2$",
|
Handler: "^(.*/vendor/)?git.company.lan/gopkg/gin.handlerTest2$",
|
||||||
})
|
})
|
||||||
assertRoutePresent(t, list, RouteInfo{
|
assertRoutePresent(t, list, RouteInfo{
|
||||||
Method: "GET",
|
Method: http.MethodGet,
|
||||||
Path: "/users/:id",
|
Path: "/users/:id",
|
||||||
Handler: "^(.*/vendor/)?git.company.lan/gopkg/gin.handlerTest1$",
|
Handler: "^(.*/vendor/)?git.company.lan/gopkg/gin.handlerTest1$",
|
||||||
})
|
})
|
||||||
assertRoutePresent(t, list, RouteInfo{
|
assertRoutePresent(t, list, RouteInfo{
|
||||||
Method: "POST",
|
Method: http.MethodPost,
|
||||||
Path: "/users/:id",
|
Path: "/users/:id",
|
||||||
Handler: "^(.*/vendor/)?git.company.lan/gopkg/gin.handlerTest2$",
|
Handler: "^(.*/vendor/)?git.company.lan/gopkg/gin.handlerTest2$",
|
||||||
})
|
})
|
||||||
@@ -531,7 +640,7 @@ func TestEngineHandleContext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
w := PerformRequest(r, "GET", "/")
|
w := PerformRequest(r, http.MethodGet, "/")
|
||||||
assert.Equal(t, 301, w.Code)
|
assert.Equal(t, 301, w.Code)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -564,7 +673,7 @@ func TestEngineHandleContextManyReEntries(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
w := PerformRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value
|
w := PerformRequest(r, http.MethodGet, "/"+strconv.Itoa(expectValue-1)) // include 0 value
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, expectValue, w.Body.Len())
|
assert.Equal(t, expectValue, w.Body.Len())
|
||||||
})
|
})
|
||||||
@@ -573,6 +682,44 @@ func TestEngineHandleContextManyReEntries(t *testing.T) {
|
|||||||
assert.Equal(t, int64(expectValue), middlewareCounter)
|
assert.Equal(t, int64(expectValue), middlewareCounter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEngineHandleContextPreventsMiddlewareReEntry(t *testing.T) {
|
||||||
|
// given
|
||||||
|
var handlerCounterV1, handlerCounterV2, middlewareCounterV1 int64
|
||||||
|
|
||||||
|
r := New()
|
||||||
|
v1 := r.Group("/v1")
|
||||||
|
{
|
||||||
|
v1.Use(func(c *Context) {
|
||||||
|
atomic.AddInt64(&middlewareCounterV1, 1)
|
||||||
|
})
|
||||||
|
v1.GET("/test", func(c *Context) {
|
||||||
|
atomic.AddInt64(&handlerCounterV1, 1)
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
v2 := r.Group("/v2")
|
||||||
|
{
|
||||||
|
v2.GET("/test", func(c *Context) {
|
||||||
|
c.Request.URL.Path = "/v1/test"
|
||||||
|
r.HandleContext(c)
|
||||||
|
}, func(c *Context) {
|
||||||
|
atomic.AddInt64(&handlerCounterV2, 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
responseV1 := PerformRequest(r, "GET", "/v1/test")
|
||||||
|
responseV2 := PerformRequest(r, "GET", "/v2/test")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert.Equal(t, 200, responseV1.Code)
|
||||||
|
assert.Equal(t, 200, responseV2.Code)
|
||||||
|
assert.Equal(t, int64(2), handlerCounterV1)
|
||||||
|
assert.Equal(t, int64(2), middlewareCounterV1)
|
||||||
|
assert.Equal(t, int64(1), handlerCounterV2)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPrepareTrustedCIRDsWith(t *testing.T) {
|
func TestPrepareTrustedCIRDsWith(t *testing.T) {
|
||||||
r := New()
|
r := New()
|
||||||
|
|
||||||
@@ -700,7 +847,7 @@ func handlerTest1(c *Context) {}
|
|||||||
func handlerTest2(c *Context) {}
|
func handlerTest2(c *Context) {}
|
||||||
|
|
||||||
func TestNewOptionFunc(t *testing.T) {
|
func TestNewOptionFunc(t *testing.T) {
|
||||||
var fc = func(e *Engine) {
|
fc := func(e *Engine) {
|
||||||
e.GET("/test1", handlerTest1)
|
e.GET("/test1", handlerTest1)
|
||||||
e.GET("/test2", handlerTest2)
|
e.GET("/test2", handlerTest2)
|
||||||
|
|
||||||
@@ -712,8 +859,8 @@ func TestNewOptionFunc(t *testing.T) {
|
|||||||
r := New(fc)
|
r := New(fc)
|
||||||
|
|
||||||
routes := r.Routes()
|
routes := r.Routes()
|
||||||
assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "git.company.lan/gopkg/gin.handlerTest1"})
|
assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: http.MethodGet, Handler: "git.company.lan/gopkg/gin.handlerTest1"})
|
||||||
assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "git.company.lan/gopkg/gin.handlerTest2"})
|
assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: http.MethodGet, Handler: "git.company.lan/gopkg/gin.handlerTest2"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWithOptionFunc(t *testing.T) {
|
func TestWithOptionFunc(t *testing.T) {
|
||||||
@@ -729,14 +876,14 @@ func TestWithOptionFunc(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
routes := r.Routes()
|
routes := r.Routes()
|
||||||
assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "git.company.lan/gopkg/gin.handlerTest1"})
|
assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: http.MethodGet, Handler: "git.company.lan/gopkg/gin.handlerTest1"})
|
||||||
assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "git.company.lan/gopkg/gin.handlerTest2"})
|
assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: http.MethodGet, Handler: "git.company.lan/gopkg/gin.handlerTest2"})
|
||||||
}
|
}
|
||||||
|
|
||||||
type Birthday string
|
type Birthday string
|
||||||
|
|
||||||
func (b *Birthday) UnmarshalParam(param string) error {
|
func (b *Birthday) UnmarshalParam(param string) error {
|
||||||
*b = Birthday(strings.Replace(param, "-", "/", -1))
|
*b = Birthday(strings.ReplaceAll(param, "-", "/"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -749,7 +896,7 @@ func TestCustomUnmarshalStruct(t *testing.T) {
|
|||||||
_ = ctx.BindQuery(&request)
|
_ = ctx.BindQuery(&request)
|
||||||
ctx.JSON(200, request.Birthday)
|
ctx.JSON(200, request.Birthday)
|
||||||
})
|
})
|
||||||
req := httptest.NewRequest("GET", "/test?birthday=2000-01-01", nil)
|
req := httptest.NewRequest(http.MethodGet, "/test?birthday=2000-01-01", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
route.ServeHTTP(w, req)
|
route.ServeHTTP(w, req)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
@@ -761,7 +908,7 @@ func TestMethodNotAllowedNoRoute(t *testing.T) {
|
|||||||
g := New()
|
g := New()
|
||||||
g.HandleMethodNotAllowed = true
|
g.HandleMethodNotAllowed = true
|
||||||
|
|
||||||
req := httptest.NewRequest("GET", "/", nil)
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
resp := httptest.NewRecorder()
|
resp := httptest.NewRecorder()
|
||||||
assert.NotPanics(t, func() { g.ServeHTTP(resp, req) })
|
assert.NotPanics(t, func() { g.ServeHTTP(resp, req) })
|
||||||
assert.Equal(t, http.StatusNotFound, resp.Code)
|
assert.Equal(t, http.StatusNotFound, resp.Code)
|
||||||
|
|||||||
+7
-6
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -297,8 +298,8 @@ func TestShouldBindUri(t *testing.T) {
|
|||||||
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
|
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
|
||||||
var person Person
|
var person Person
|
||||||
require.NoError(t, c.ShouldBindUri(&person))
|
require.NoError(t, c.ShouldBindUri(&person))
|
||||||
assert.NotEqual(t, "", person.Name)
|
assert.NotEmpty(t, person.Name)
|
||||||
assert.NotEqual(t, "", person.ID)
|
assert.NotEmpty(t, person.ID)
|
||||||
c.String(http.StatusOK, "ShouldBindUri test OK")
|
c.String(http.StatusOK, "ShouldBindUri test OK")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -319,8 +320,8 @@ func TestBindUri(t *testing.T) {
|
|||||||
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
|
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
|
||||||
var person Person
|
var person Person
|
||||||
require.NoError(t, c.BindUri(&person))
|
require.NoError(t, c.BindUri(&person))
|
||||||
assert.NotEqual(t, "", person.Name)
|
assert.NotEmpty(t, person.Name)
|
||||||
assert.NotEqual(t, "", person.ID)
|
assert.NotEmpty(t, person.ID)
|
||||||
c.String(http.StatusOK, "BindUri test OK")
|
c.String(http.StatusOK, "BindUri test OK")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -411,7 +412,7 @@ func exampleFromPath(path string) (string, Params) {
|
|||||||
}
|
}
|
||||||
if start >= 0 {
|
if start >= 0 {
|
||||||
if c == '/' {
|
if c == '/' {
|
||||||
value := fmt.Sprint(rand.Intn(100000))
|
value := strconv.Itoa(rand.Intn(100000))
|
||||||
params = append(params, Param{
|
params = append(params, Param{
|
||||||
Key: path[start:i],
|
Key: path[start:i],
|
||||||
Value: value,
|
Value: value,
|
||||||
@@ -425,7 +426,7 @@ func exampleFromPath(path string) (string, Params) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if start >= 0 {
|
if start >= 0 {
|
||||||
value := fmt.Sprint(rand.Intn(100000))
|
value := strconv.Itoa(rand.Intn(100000))
|
||||||
params = append(params, Param{
|
params = append(params, Param{
|
||||||
Key: path[start:],
|
Key: path[start:],
|
||||||
Value: value,
|
Value: value,
|
||||||
|
|||||||
@@ -1,45 +1,44 @@
|
|||||||
module git.company.lan/gopkg/gin
|
module git.company.lan/gopkg/gin
|
||||||
|
|
||||||
go 1.23.6
|
go 1.23.11
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.company.lan/gopkg/gin-sse v0.0.0-20250709115435-deecef94c1ae
|
git.company.lan/gopkg/gin-sse v1.1.0
|
||||||
github.com/bytedance/sonic v1.13.3
|
github.com/bytedance/sonic v1.14.0
|
||||||
github.com/go-playground/validator/v10 v10.27.0
|
github.com/go-playground/validator/v10 v10.27.0
|
||||||
github.com/goccy/go-json v0.10.5
|
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/json-iterator/go v1.1.12
|
||||||
github.com/mattn/go-isatty v0.0.20
|
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/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/stretchr/testify v1.11.1
|
||||||
github.com/ugorji/go/codec v1.3.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
|
google.golang.org/protobuf v1.36.9
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // 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.5.1 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
go.uber.org/mock v0.5.2 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
golang.org/x/arch v0.18.0 // indirect
|
golang.org/x/arch v0.20.0 // 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/mod v0.25.0 // indirect
|
||||||
golang.org/x/sync v0.15.0 // indirect
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
golang.org/x/text v0.26.0 // indirect
|
golang.org/x/text v0.27.0 // indirect
|
||||||
golang.org/x/tools v0.34.0 // indirect
|
golang.org/x/tools v0.34.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
git.company.lan/gopkg/gin-sse v0.0.0-20250709115435-deecef94c1ae h1:Y2esYXNuB+rhi1wfaz3CBpIqQWySnXnbFrsFKb97E3A=
|
git.company.lan/gopkg/gin-sse v1.1.0 h1:urYsv8wVYXjMbP6y17WudEsJcecQ5Y5ekymjEXZLE1A=
|
||||||
git.company.lan/gopkg/gin-sse v0.0.0-20250709115435-deecef94c1ae/go.mod h1:Bonf0ESOIe1LQBNFBkGNTYy9geegItybXhkOGmmydPI=
|
git.company.lan/gopkg/gin-sse v1.1.0/go.mod h1:Bonf0ESOIe1LQBNFBkGNTYy9geegItybXhkOGmmydPI=
|
||||||
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
|
||||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
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/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
|
||||||
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
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/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
@@ -22,80 +19,69 @@ 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/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 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
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/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/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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
|
||||||
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 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
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-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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
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=
|
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/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/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 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
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.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||||
github.com/quic-go/quic-go v0.53.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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.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.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
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.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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
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 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
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 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
|
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||||
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
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 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
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.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
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 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
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.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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=
|
|
||||||
|
|||||||
@@ -6,14 +6,17 @@ package bytesconv
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
cRand "crypto/rand"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testString = "Albert Einstein: Logic will get you from A to B. Imagination will take you everywhere."
|
var (
|
||||||
var testBytes = []byte(testString)
|
testString = "Albert Einstein: Logic will get you from A to B. Imagination will take you everywhere."
|
||||||
|
testBytes = []byte(testString)
|
||||||
|
)
|
||||||
|
|
||||||
func rawBytesToStr(b []byte) string {
|
func rawBytesToStr(b []byte) string {
|
||||||
return string(b)
|
return string(b)
|
||||||
@@ -28,7 +31,10 @@ func rawStrToBytes(s string) []byte {
|
|||||||
func TestBytesToString(t *testing.T) {
|
func TestBytesToString(t *testing.T) {
|
||||||
data := make([]byte, 1024)
|
data := make([]byte, 1024)
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
rand.Read(data)
|
_, err := cRand.Read(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if rawBytesToStr(data) != BytesToString(data) {
|
if rawBytesToStr(data) != BytesToString(data) {
|
||||||
t.Fatal("don't match")
|
t.Fatal("don't match")
|
||||||
}
|
}
|
||||||
@@ -42,7 +48,7 @@ const (
|
|||||||
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
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 {
|
func RandStringBytesMaskImprSrcSB(n int) string {
|
||||||
sb := strings.Builder{}
|
sb := strings.Builder{}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -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 TestFileSystem_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)
|
||||||
|
}
|
||||||
@@ -1,22 +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
|
|
||||||
)
|
|
||||||
@@ -1,22 +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 && avx && (linux || windows || darwin) && amd64)
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
@@ -1,23 +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
|
|
||||||
)
|
|
||||||
@@ -1,23 +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 && avx && (linux || windows || darwin) && amd64
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
@@ -44,7 +44,7 @@ type LoggerConfig struct {
|
|||||||
// Optional. Default value is gin.DefaultWriter.
|
// Optional. Default value is gin.DefaultWriter.
|
||||||
Output io.Writer
|
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.
|
// Optional.
|
||||||
SkipPaths []string
|
SkipPaths []string
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ type LogFormatterParams struct {
|
|||||||
// BodySize is the size of the Response Body
|
// BodySize is the size of the Response Body
|
||||||
BodySize int
|
BodySize int
|
||||||
// Keys are the keys set on the request's context.
|
// 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.
|
// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
|
||||||
|
|||||||
+45
-45
@@ -31,9 +31,9 @@ func TestLogger(t *testing.T) {
|
|||||||
router.HEAD("/example", func(c *Context) {})
|
router.HEAD("/example", func(c *Context) {})
|
||||||
router.OPTIONS("/example", func(c *Context) {})
|
router.OPTIONS("/example", func(c *Context) {})
|
||||||
|
|
||||||
PerformRequest(router, "GET", "/example?a=100")
|
PerformRequest(router, http.MethodGet, "/example?a=100")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "GET")
|
assert.Contains(t, buffer.String(), http.MethodGet)
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
assert.Contains(t, buffer.String(), "a=100")
|
assert.Contains(t, buffer.String(), "a=100")
|
||||||
|
|
||||||
@@ -41,21 +41,21 @@ func TestLogger(t *testing.T) {
|
|||||||
// like integration tests because they test the whole logging process rather
|
// like integration tests because they test the whole logging process rather
|
||||||
// than individual functions. Im not sure where these should go.
|
// than individual functions. Im not sure where these should go.
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
PerformRequest(router, "POST", "/example")
|
PerformRequest(router, http.MethodPost, "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "POST")
|
assert.Contains(t, buffer.String(), http.MethodPost)
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
PerformRequest(router, "PUT", "/example")
|
PerformRequest(router, http.MethodPut, "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "PUT")
|
assert.Contains(t, buffer.String(), http.MethodPut)
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
PerformRequest(router, "DELETE", "/example")
|
PerformRequest(router, http.MethodDelete, "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "DELETE")
|
assert.Contains(t, buffer.String(), http.MethodDelete)
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
@@ -77,9 +77,9 @@ func TestLogger(t *testing.T) {
|
|||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
PerformRequest(router, "GET", "/notfound")
|
PerformRequest(router, http.MethodGet, "/notfound")
|
||||||
assert.Contains(t, buffer.String(), "404")
|
assert.Contains(t, buffer.String(), "404")
|
||||||
assert.Contains(t, buffer.String(), "GET")
|
assert.Contains(t, buffer.String(), http.MethodGet)
|
||||||
assert.Contains(t, buffer.String(), "/notfound")
|
assert.Contains(t, buffer.String(), "/notfound")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,9 +95,9 @@ func TestLoggerWithConfig(t *testing.T) {
|
|||||||
router.HEAD("/example", func(c *Context) {})
|
router.HEAD("/example", func(c *Context) {})
|
||||||
router.OPTIONS("/example", func(c *Context) {})
|
router.OPTIONS("/example", func(c *Context) {})
|
||||||
|
|
||||||
PerformRequest(router, "GET", "/example?a=100")
|
PerformRequest(router, http.MethodGet, "/example?a=100")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "GET")
|
assert.Contains(t, buffer.String(), http.MethodGet)
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
assert.Contains(t, buffer.String(), "a=100")
|
assert.Contains(t, buffer.String(), "a=100")
|
||||||
|
|
||||||
@@ -105,21 +105,21 @@ func TestLoggerWithConfig(t *testing.T) {
|
|||||||
// like integration tests because they test the whole logging process rather
|
// like integration tests because they test the whole logging process rather
|
||||||
// than individual functions. Im not sure where these should go.
|
// than individual functions. Im not sure where these should go.
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
PerformRequest(router, "POST", "/example")
|
PerformRequest(router, http.MethodPost, "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "POST")
|
assert.Contains(t, buffer.String(), http.MethodPost)
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
PerformRequest(router, "PUT", "/example")
|
PerformRequest(router, http.MethodPut, "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "PUT")
|
assert.Contains(t, buffer.String(), http.MethodPut)
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
PerformRequest(router, "DELETE", "/example")
|
PerformRequest(router, http.MethodDelete, "/example")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "DELETE")
|
assert.Contains(t, buffer.String(), http.MethodDelete)
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
@@ -141,9 +141,9 @@ func TestLoggerWithConfig(t *testing.T) {
|
|||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
PerformRequest(router, "GET", "/notfound")
|
PerformRequest(router, http.MethodGet, "/notfound")
|
||||||
assert.Contains(t, buffer.String(), "404")
|
assert.Contains(t, buffer.String(), "404")
|
||||||
assert.Contains(t, buffer.String(), "GET")
|
assert.Contains(t, buffer.String(), http.MethodGet)
|
||||||
assert.Contains(t, buffer.String(), "/notfound")
|
assert.Contains(t, buffer.String(), "/notfound")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,19 +169,19 @@ func TestLoggerWithFormatter(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
router.GET("/example", func(c *Context) {})
|
router.GET("/example", func(c *Context) {})
|
||||||
PerformRequest(router, "GET", "/example?a=100")
|
PerformRequest(router, http.MethodGet, "/example?a=100")
|
||||||
|
|
||||||
// output test
|
// output test
|
||||||
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
|
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "GET")
|
assert.Contains(t, buffer.String(), http.MethodGet)
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
assert.Contains(t, buffer.String(), "a=100")
|
assert.Contains(t, buffer.String(), "a=100")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoggerWithConfigFormatting(t *testing.T) {
|
func TestLoggerWithConfigFormatting(t *testing.T) {
|
||||||
var gotParam LogFormatterParams
|
var gotParam LogFormatterParams
|
||||||
var gotKeys map[string]any
|
var gotKeys map[any]any
|
||||||
buffer := new(strings.Builder)
|
buffer := new(strings.Builder)
|
||||||
|
|
||||||
router := New()
|
router := New()
|
||||||
@@ -210,12 +210,12 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
|
|||||||
gotKeys = c.Keys
|
gotKeys = c.Keys
|
||||||
time.Sleep(time.Millisecond)
|
time.Sleep(time.Millisecond)
|
||||||
})
|
})
|
||||||
PerformRequest(router, "GET", "/example?a=100")
|
PerformRequest(router, http.MethodGet, "/example?a=100")
|
||||||
|
|
||||||
// output test
|
// output test
|
||||||
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
|
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
assert.Contains(t, buffer.String(), "GET")
|
assert.Contains(t, buffer.String(), http.MethodGet)
|
||||||
assert.Contains(t, buffer.String(), "/example")
|
assert.Contains(t, buffer.String(), "/example")
|
||||||
assert.Contains(t, buffer.String(), "a=100")
|
assert.Contains(t, buffer.String(), "a=100")
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
|
|||||||
assert.Equal(t, 200, gotParam.StatusCode)
|
assert.Equal(t, 200, gotParam.StatusCode)
|
||||||
assert.NotEmpty(t, gotParam.Latency)
|
assert.NotEmpty(t, gotParam.Latency)
|
||||||
assert.Equal(t, "20.20.20.20", gotParam.ClientIP)
|
assert.Equal(t, "20.20.20.20", gotParam.ClientIP)
|
||||||
assert.Equal(t, "GET", gotParam.Method)
|
assert.Equal(t, http.MethodGet, gotParam.Method)
|
||||||
assert.Equal(t, "/example?a=100", gotParam.Path)
|
assert.Equal(t, "/example?a=100", gotParam.Path)
|
||||||
assert.Empty(t, gotParam.ErrorMessage)
|
assert.Empty(t, gotParam.ErrorMessage)
|
||||||
assert.Equal(t, gotKeys, gotParam.Keys)
|
assert.Equal(t, gotKeys, gotParam.Keys)
|
||||||
@@ -239,7 +239,7 @@ func TestDefaultLogFormatter(t *testing.T) {
|
|||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
Latency: time.Second * 5,
|
Latency: time.Second * 5,
|
||||||
ClientIP: "20.20.20.20",
|
ClientIP: "20.20.20.20",
|
||||||
Method: "GET",
|
Method: http.MethodGet,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
ErrorMessage: "",
|
ErrorMessage: "",
|
||||||
isTerm: false,
|
isTerm: false,
|
||||||
@@ -250,7 +250,7 @@ func TestDefaultLogFormatter(t *testing.T) {
|
|||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
Latency: time.Second * 5,
|
Latency: time.Second * 5,
|
||||||
ClientIP: "20.20.20.20",
|
ClientIP: "20.20.20.20",
|
||||||
Method: "GET",
|
Method: http.MethodGet,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
ErrorMessage: "",
|
ErrorMessage: "",
|
||||||
isTerm: true,
|
isTerm: true,
|
||||||
@@ -260,7 +260,7 @@ func TestDefaultLogFormatter(t *testing.T) {
|
|||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
Latency: time.Millisecond * 9876543210,
|
Latency: time.Millisecond * 9876543210,
|
||||||
ClientIP: "20.20.20.20",
|
ClientIP: "20.20.20.20",
|
||||||
Method: "GET",
|
Method: http.MethodGet,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
ErrorMessage: "",
|
ErrorMessage: "",
|
||||||
isTerm: true,
|
isTerm: true,
|
||||||
@@ -271,7 +271,7 @@ func TestDefaultLogFormatter(t *testing.T) {
|
|||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
Latency: time.Millisecond * 9876543210,
|
Latency: time.Millisecond * 9876543210,
|
||||||
ClientIP: "20.20.20.20",
|
ClientIP: "20.20.20.20",
|
||||||
Method: "GET",
|
Method: http.MethodGet,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
ErrorMessage: "",
|
ErrorMessage: "",
|
||||||
isTerm: false,
|
isTerm: false,
|
||||||
@@ -292,10 +292,10 @@ func TestColorForMethod(t *testing.T) {
|
|||||||
return p.MethodColor()
|
return p.MethodColor()
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, blue, colorForMethod("GET"), "get should be blue")
|
assert.Equal(t, blue, colorForMethod(http.MethodGet), "get should be blue")
|
||||||
assert.Equal(t, cyan, colorForMethod("POST"), "post should be cyan")
|
assert.Equal(t, cyan, colorForMethod(http.MethodPost), "post should be cyan")
|
||||||
assert.Equal(t, yellow, colorForMethod("PUT"), "put should be yellow")
|
assert.Equal(t, yellow, colorForMethod(http.MethodPut), "put should be yellow")
|
||||||
assert.Equal(t, red, colorForMethod("DELETE"), "delete should be red")
|
assert.Equal(t, red, colorForMethod(http.MethodDelete), "delete should be red")
|
||||||
assert.Equal(t, green, colorForMethod("PATCH"), "patch should be green")
|
assert.Equal(t, green, colorForMethod("PATCH"), "patch should be green")
|
||||||
assert.Equal(t, magenta, colorForMethod("HEAD"), "head should be magenta")
|
assert.Equal(t, magenta, colorForMethod("HEAD"), "head should be magenta")
|
||||||
assert.Equal(t, white, colorForMethod("OPTIONS"), "options should be white")
|
assert.Equal(t, white, colorForMethod("OPTIONS"), "options should be white")
|
||||||
@@ -369,15 +369,15 @@ func TestErrorLogger(t *testing.T) {
|
|||||||
c.String(http.StatusInternalServerError, "hola!")
|
c.String(http.StatusInternalServerError, "hola!")
|
||||||
})
|
})
|
||||||
|
|
||||||
w := PerformRequest(router, "GET", "/error")
|
w := PerformRequest(router, http.MethodGet, "/error")
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
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, "GET", "/abort")
|
w = PerformRequest(router, http.MethodGet, "/abort")
|
||||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
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, "GET", "/print")
|
w = PerformRequest(router, http.MethodGet, "/print")
|
||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
|
assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
|
||||||
}
|
}
|
||||||
@@ -389,11 +389,11 @@ func TestLoggerWithWriterSkippingPaths(t *testing.T) {
|
|||||||
router.GET("/logged", func(c *Context) {})
|
router.GET("/logged", func(c *Context) {})
|
||||||
router.GET("/skipped", func(c *Context) {})
|
router.GET("/skipped", func(c *Context) {})
|
||||||
|
|
||||||
PerformRequest(router, "GET", "/logged")
|
PerformRequest(router, http.MethodGet, "/logged")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
PerformRequest(router, "GET", "/skipped")
|
PerformRequest(router, http.MethodGet, "/skipped")
|
||||||
assert.Contains(t, buffer.String(), "")
|
assert.Contains(t, buffer.String(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,11 +407,11 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) {
|
|||||||
router.GET("/logged", func(c *Context) {})
|
router.GET("/logged", func(c *Context) {})
|
||||||
router.GET("/skipped", func(c *Context) {})
|
router.GET("/skipped", func(c *Context) {})
|
||||||
|
|
||||||
PerformRequest(router, "GET", "/logged")
|
PerformRequest(router, http.MethodGet, "/logged")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
PerformRequest(router, "GET", "/skipped")
|
PerformRequest(router, http.MethodGet, "/skipped")
|
||||||
assert.Contains(t, buffer.String(), "")
|
assert.Contains(t, buffer.String(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,11 +427,11 @@ func TestLoggerWithConfigSkipper(t *testing.T) {
|
|||||||
router.GET("/logged", func(c *Context) { c.Status(http.StatusOK) })
|
router.GET("/logged", func(c *Context) { c.Status(http.StatusOK) })
|
||||||
router.GET("/skipped", func(c *Context) { c.Status(http.StatusNoContent) })
|
router.GET("/skipped", func(c *Context) { c.Status(http.StatusNoContent) })
|
||||||
|
|
||||||
PerformRequest(router, "GET", "/logged")
|
PerformRequest(router, http.MethodGet, "/logged")
|
||||||
assert.Contains(t, buffer.String(), "200")
|
assert.Contains(t, buffer.String(), "200")
|
||||||
|
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
PerformRequest(router, "GET", "/skipped")
|
PerformRequest(router, http.MethodGet, "/skipped")
|
||||||
assert.Contains(t, buffer.String(), "")
|
assert.Contains(t, buffer.String(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -35,7 +35,7 @@ func TestMiddlewareGeneralCase(t *testing.T) {
|
|||||||
signature += " XX "
|
signature += " XX "
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := PerformRequest(router, "GET", "/")
|
w := PerformRequest(router, http.MethodGet, "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
@@ -71,7 +71,7 @@ func TestMiddlewareNoRoute(t *testing.T) {
|
|||||||
signature += " X "
|
signature += " X "
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := PerformRequest(router, "GET", "/")
|
w := PerformRequest(router, http.MethodGet, "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
@@ -108,7 +108,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
|
|||||||
signature += " XX "
|
signature += " XX "
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := PerformRequest(router, "GET", "/")
|
w := PerformRequest(router, http.MethodGet, "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||||
@@ -149,7 +149,7 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// RUN
|
// RUN
|
||||||
w := PerformRequest(router, "GET", "/")
|
w := PerformRequest(router, http.MethodGet, "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
@@ -175,7 +175,7 @@ func TestMiddlewareAbort(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// RUN
|
// RUN
|
||||||
w := PerformRequest(router, "GET", "/")
|
w := PerformRequest(router, http.MethodGet, "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||||
@@ -196,14 +196,14 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
|
|||||||
c.Next()
|
c.Next()
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := PerformRequest(router, "GET", "/")
|
w := PerformRequest(router, http.MethodGet, "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusGone, w.Code)
|
assert.Equal(t, http.StatusGone, w.Code)
|
||||||
assert.Equal(t, "ACB", signature)
|
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
|
// as well as Abort
|
||||||
func TestMiddlewareFailHandlersChain(t *testing.T) {
|
func TestMiddlewareFailHandlersChain(t *testing.T) {
|
||||||
// SETUP
|
// SETUP
|
||||||
@@ -219,7 +219,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
|
|||||||
signature += "C"
|
signature += "C"
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := PerformRequest(router, "GET", "/")
|
w := PerformRequest(router, http.MethodGet, "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
@@ -246,8 +246,8 @@ func TestMiddlewareWrite(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
w := PerformRequest(router, "GET", "/")
|
w := PerformRequest(router, http.MethodGet, "/")
|
||||||
|
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
|
assert.Equal(t, strings.ReplaceAll("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", ""), strings.ReplaceAll(w.Body.String(), " ", ""))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,8 +44,10 @@ var DefaultWriter io.Writer = os.Stdout
|
|||||||
// DefaultErrorWriter is the default io.Writer used by Gin to debug errors
|
// DefaultErrorWriter is the default io.Writer used by Gin to debug errors
|
||||||
var DefaultErrorWriter io.Writer = os.Stderr
|
var DefaultErrorWriter io.Writer = os.Stderr
|
||||||
|
|
||||||
var ginMode int32 = debugCode
|
var (
|
||||||
var modeName atomic.Value
|
ginMode int32 = debugCode
|
||||||
|
modeName atomic.Value
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
mode := os.Getenv(EnvGinMode)
|
mode := os.Getenv(EnvGinMode)
|
||||||
@@ -63,7 +65,7 @@ func SetMode(value string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch value {
|
switch value {
|
||||||
case DebugMode, "":
|
case DebugMode:
|
||||||
atomic.StoreInt32(&ginMode, debugCode)
|
atomic.StoreInt32(&ginMode, debugCode)
|
||||||
case ReleaseMode:
|
case ReleaseMode:
|
||||||
atomic.StoreInt32(&ginMode, releaseCode)
|
atomic.StoreInt32(&ginMode, releaseCode)
|
||||||
|
|||||||
+30
-25
@@ -17,14 +17,13 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.company.lan/gopkg/gin/internal/bytesconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const dunno = "???"
|
||||||
dunno = []byte("???")
|
|
||||||
centerDot = []byte("·")
|
var dunnoBytes = []byte(dunno)
|
||||||
dot = []byte(".")
|
|
||||||
slash = []byte("/")
|
|
||||||
)
|
|
||||||
|
|
||||||
// RecoveryFunc defines the function passable to CustomRecovery.
|
// RecoveryFunc defines the function passable to CustomRecovery.
|
||||||
type RecoveryFunc func(c *Context, err any)
|
type RecoveryFunc func(c *Context, err any)
|
||||||
@@ -70,24 +69,15 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if logger != nil {
|
if logger != nil {
|
||||||
stack := stack(3)
|
const stackSkip = 3
|
||||||
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] + ": *"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
headersToStr := strings.Join(headers, "\r\n")
|
|
||||||
if brokenPipe {
|
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() {
|
} else if IsDebugging() {
|
||||||
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
|
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 {
|
} else {
|
||||||
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
|
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 {
|
if brokenPipe {
|
||||||
@@ -103,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) {
|
func defaultHandleRecovery(c *Context, _ any) {
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
@@ -138,18 +143,18 @@ func stack(skip int) []byte {
|
|||||||
func source(lines [][]byte, n int) []byte {
|
func source(lines [][]byte, n int) []byte {
|
||||||
n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
|
n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
|
||||||
if n < 0 || n >= len(lines) {
|
if n < 0 || n >= len(lines) {
|
||||||
return dunno
|
return dunnoBytes
|
||||||
}
|
}
|
||||||
return bytes.TrimSpace(lines[n])
|
return bytes.TrimSpace(lines[n])
|
||||||
}
|
}
|
||||||
|
|
||||||
// function returns, if possible, the name of the function containing the PC.
|
// 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)
|
fn := runtime.FuncForPC(pc)
|
||||||
if fn == nil {
|
if fn == nil {
|
||||||
return dunno
|
return dunno
|
||||||
}
|
}
|
||||||
name := []byte(fn.Name())
|
name := fn.Name()
|
||||||
// The name includes the path name to the package, which is unnecessary
|
// The name includes the path name to the package, which is unnecessary
|
||||||
// since the file name is already included. Plus, it has center dots.
|
// since the file name is already included. Plus, it has center dots.
|
||||||
// That is, we see
|
// That is, we see
|
||||||
@@ -158,13 +163,13 @@ func function(pc uintptr) []byte {
|
|||||||
// *T.ptrmethod
|
// *T.ptrmethod
|
||||||
// Also the package path might contain dot (e.g. code.google.com/...),
|
// Also the package path might contain dot (e.g. code.google.com/...),
|
||||||
// so first eliminate the path prefix
|
// 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:]
|
name = name[lastSlash+1:]
|
||||||
}
|
}
|
||||||
if period := bytes.Index(name, dot); period >= 0 {
|
if period := strings.IndexByte(name, '.'); period >= 0 {
|
||||||
name = name[period+1:]
|
name = name[period+1:]
|
||||||
}
|
}
|
||||||
name = bytes.ReplaceAll(name, centerDot, dot)
|
name = strings.ReplaceAll(name, "·", ".")
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+76
-15
@@ -5,7 +5,6 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -26,14 +25,14 @@ func TestPanicClean(t *testing.T) {
|
|||||||
panic("Oupps, Houston, we have a problem")
|
panic("Oupps, Houston, we have a problem")
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := PerformRequest(router, "GET", "/recovery",
|
w := PerformRequest(router, http.MethodGet, "/recovery",
|
||||||
header{
|
header{
|
||||||
Key: "Host",
|
Key: "Host",
|
||||||
Value: "www.google.com",
|
Value: "www.google.com",
|
||||||
},
|
},
|
||||||
header{
|
header{
|
||||||
Key: "Authorization",
|
Key: "Authorization",
|
||||||
Value: fmt.Sprintf("Bearer %s", password),
|
Value: "Bearer " + password,
|
||||||
},
|
},
|
||||||
header{
|
header{
|
||||||
Key: "Content-Type",
|
Key: "Content-Type",
|
||||||
@@ -56,7 +55,7 @@ func TestPanicInHandler(t *testing.T) {
|
|||||||
panic("Oupps, Houston, we have a problem")
|
panic("Oupps, Houston, we have a problem")
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := PerformRequest(router, "GET", "/recovery")
|
w := PerformRequest(router, http.MethodGet, "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "panic recovered")
|
assert.Contains(t, buffer.String(), "panic recovered")
|
||||||
@@ -67,7 +66,7 @@ func TestPanicInHandler(t *testing.T) {
|
|||||||
// Debug mode prints the request
|
// Debug mode prints the request
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
// RUN
|
// RUN
|
||||||
w = PerformRequest(router, "GET", "/recovery")
|
w = PerformRequest(router, http.MethodGet, "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||||
@@ -84,21 +83,21 @@ func TestPanicWithAbort(t *testing.T) {
|
|||||||
panic("Oupps, Houston, we have a problem")
|
panic("Oupps, Houston, we have a problem")
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := PerformRequest(router, "GET", "/recovery")
|
w := PerformRequest(router, http.MethodGet, "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSource(t *testing.T) {
|
func TestSource(t *testing.T) {
|
||||||
bs := source(nil, 0)
|
bs := source(nil, 0)
|
||||||
assert.Equal(t, dunno, bs)
|
assert.Equal(t, dunnoBytes, bs)
|
||||||
|
|
||||||
in := [][]byte{
|
in := [][]byte{
|
||||||
[]byte("Hello world."),
|
[]byte("Hello world."),
|
||||||
[]byte("Hi, gin.."),
|
[]byte("Hi, gin.."),
|
||||||
}
|
}
|
||||||
bs = source(in, 10)
|
bs = source(in, 10)
|
||||||
assert.Equal(t, dunno, bs)
|
assert.Equal(t, dunnoBytes, bs)
|
||||||
|
|
||||||
bs = source(in, 1)
|
bs = source(in, 1)
|
||||||
assert.Equal(t, []byte("Hello world."), bs)
|
assert.Equal(t, []byte("Hello world."), bs)
|
||||||
@@ -135,7 +134,7 @@ func TestPanicWithBrokenPipe(t *testing.T) {
|
|||||||
panic(e)
|
panic(e)
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := PerformRequest(router, "GET", "/recovery")
|
w := PerformRequest(router, http.MethodGet, "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, expectCode, w.Code)
|
assert.Equal(t, expectCode, w.Code)
|
||||||
assert.Contains(t, strings.ToLower(buf.String()), expectMsg)
|
assert.Contains(t, strings.ToLower(buf.String()), expectMsg)
|
||||||
@@ -156,7 +155,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
|
|||||||
panic("Oupps, Houston, we have a problem")
|
panic("Oupps, Houston, we have a problem")
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := PerformRequest(router, "GET", "/recovery")
|
w := PerformRequest(router, http.MethodGet, "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "panic recovered")
|
assert.Contains(t, buffer.String(), "panic recovered")
|
||||||
@@ -167,7 +166,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) {
|
|||||||
// Debug mode prints the request
|
// Debug mode prints the request
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
// RUN
|
// RUN
|
||||||
w = PerformRequest(router, "GET", "/recovery")
|
w = PerformRequest(router, http.MethodGet, "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||||
@@ -191,7 +190,7 @@ func TestCustomRecovery(t *testing.T) {
|
|||||||
panic("Oupps, Houston, we have a problem")
|
panic("Oupps, Houston, we have a problem")
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := PerformRequest(router, "GET", "/recovery")
|
w := PerformRequest(router, http.MethodGet, "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "panic recovered")
|
assert.Contains(t, buffer.String(), "panic recovered")
|
||||||
@@ -202,7 +201,7 @@ func TestCustomRecovery(t *testing.T) {
|
|||||||
// Debug mode prints the request
|
// Debug mode prints the request
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
// RUN
|
// RUN
|
||||||
w = PerformRequest(router, "GET", "/recovery")
|
w = PerformRequest(router, http.MethodGet, "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||||
@@ -226,7 +225,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
|
|||||||
panic("Oupps, Houston, we have a problem")
|
panic("Oupps, Houston, we have a problem")
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := PerformRequest(router, "GET", "/recovery")
|
w := PerformRequest(router, http.MethodGet, "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "panic recovered")
|
assert.Contains(t, buffer.String(), "panic recovered")
|
||||||
@@ -237,7 +236,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
|
|||||||
// Debug mode prints the request
|
// Debug mode prints the request
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
// RUN
|
// RUN
|
||||||
w = PerformRequest(router, "GET", "/recovery")
|
w = PerformRequest(router, http.MethodGet, "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||||
@@ -246,3 +245,65 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
|
|||||||
|
|
||||||
SetMode(TestMode)
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+10
-1
@@ -7,6 +7,8 @@ package render
|
|||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"git.company.lan/gopkg/gin/internal/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Delims represents a set of Left and Right delimiters for HTML template rendering.
|
// Delims represents a set of Left and Right delimiters for HTML template rendering.
|
||||||
@@ -33,6 +35,8 @@ type HTMLProduction struct {
|
|||||||
type HTMLDebug struct {
|
type HTMLDebug struct {
|
||||||
Files []string
|
Files []string
|
||||||
Glob string
|
Glob string
|
||||||
|
FileSystem http.FileSystem
|
||||||
|
Patterns []string
|
||||||
Delims Delims
|
Delims Delims
|
||||||
FuncMap template.FuncMap
|
FuncMap template.FuncMap
|
||||||
}
|
}
|
||||||
@@ -63,6 +67,7 @@ func (r HTMLDebug) Instance(name string, data any) Render {
|
|||||||
Data: data,
|
Data: data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r HTMLDebug) loadTemplate() *template.Template {
|
func (r HTMLDebug) loadTemplate() *template.Template {
|
||||||
if r.FuncMap == nil {
|
if r.FuncMap == nil {
|
||||||
r.FuncMap = template.FuncMap{}
|
r.FuncMap = template.FuncMap{}
|
||||||
@@ -73,7 +78,11 @@ func (r HTMLDebug) loadTemplate() *template.Template {
|
|||||||
if r.Glob != "" {
|
if r.Glob != "" {
|
||||||
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(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.
|
// Render (HTML) executes template and writes its result with custom ContentType for response.
|
||||||
|
|||||||
+16
-12
@@ -9,9 +9,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"git.company.lan/gopkg/gin/codec/json"
|
||||||
"git.company.lan/gopkg/gin/internal/bytesconv"
|
"git.company.lan/gopkg/gin/internal/bytesconv"
|
||||||
"git.company.lan/gopkg/gin/internal/json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// JSON contains the given interface object.
|
// JSON contains the given interface object.
|
||||||
@@ -65,7 +66,7 @@ func (r JSON) WriteContentType(w http.ResponseWriter) {
|
|||||||
// WriteJSON marshals the given interface object and writes it with custom ContentType.
|
// WriteJSON marshals the given interface object and writes it with custom ContentType.
|
||||||
func WriteJSON(w http.ResponseWriter, obj any) error {
|
func WriteJSON(w http.ResponseWriter, obj any) error {
|
||||||
writeContentType(w, jsonContentType)
|
writeContentType(w, jsonContentType)
|
||||||
jsonBytes, err := json.Marshal(obj)
|
jsonBytes, err := json.API.Marshal(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -76,7 +77,7 @@ func WriteJSON(w http.ResponseWriter, obj any) error {
|
|||||||
// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.
|
// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.
|
||||||
func (r IndentedJSON) Render(w http.ResponseWriter) error {
|
func (r IndentedJSON) Render(w http.ResponseWriter) error {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
jsonBytes, err := json.MarshalIndent(r.Data, "", " ")
|
jsonBytes, err := json.API.MarshalIndent(r.Data, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -92,7 +93,7 @@ func (r IndentedJSON) WriteContentType(w http.ResponseWriter) {
|
|||||||
// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType.
|
// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType.
|
||||||
func (r SecureJSON) Render(w http.ResponseWriter) error {
|
func (r SecureJSON) Render(w http.ResponseWriter) error {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
jsonBytes, err := json.Marshal(r.Data)
|
jsonBytes, err := json.API.Marshal(r.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -115,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.
|
// 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) {
|
func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
ret, err := json.Marshal(r.Data)
|
ret, err := json.API.Marshal(r.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -151,20 +152,23 @@ func (r JsonpJSON) WriteContentType(w http.ResponseWriter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType.
|
// 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)
|
r.WriteContentType(w)
|
||||||
ret, err := json.Marshal(r.Data)
|
ret, err := json.API.Marshal(r.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
escapeBuf := make([]byte, 0, 6) // Preallocate 6 bytes for Unicode escape sequences
|
||||||
|
|
||||||
for _, r := range bytesconv.BytesToString(ret) {
|
for _, r := range bytesconv.BytesToString(ret) {
|
||||||
cvt := string(r)
|
if r > unicode.MaxASCII {
|
||||||
if r >= 128 {
|
escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x", r) // Reuse escapeBuf
|
||||||
cvt = fmt.Sprintf("\\u%04x", int64(r))
|
buffer.Write(escapeBuf)
|
||||||
|
} else {
|
||||||
|
buffer.WriteByte(byte(r))
|
||||||
}
|
}
|
||||||
buffer.WriteString(cvt)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = w.Write(buffer.Bytes())
|
_, err = w.Write(buffer.Bytes())
|
||||||
@@ -179,7 +183,7 @@ func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
|
|||||||
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
|
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
|
||||||
func (r PureJSON) Render(w http.ResponseWriter) error {
|
func (r PureJSON) Render(w http.ResponseWriter) error {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
encoder := json.NewEncoder(w)
|
encoder := json.API.NewEncoder(w)
|
||||||
encoder.SetEscapeHTML(false)
|
encoder.SetEscapeHTML(false)
|
||||||
return encoder.Encode(r.Data)
|
return encoder.Encode(r.Data)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -27,7 +27,7 @@ func (r Reader) Render(w http.ResponseWriter) (err error) {
|
|||||||
}
|
}
|
||||||
r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10)
|
r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10)
|
||||||
}
|
}
|
||||||
r.writeHeaders(w, r.Headers)
|
r.writeHeaders(w)
|
||||||
_, err = io.Copy(w, r.Reader)
|
_, err = io.Copy(w, r.Reader)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -37,10 +37,10 @@ func (r Reader) WriteContentType(w http.ResponseWriter) {
|
|||||||
writeContentType(w, []string{r.ContentType})
|
writeContentType(w, []string{r.ContentType})
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeHeaders writes custom Header.
|
// writeHeaders writes headers from r.Headers into response.
|
||||||
func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) {
|
func (r Reader) writeHeaders(w http.ResponseWriter) {
|
||||||
header := w.Header()
|
header := w.Header()
|
||||||
for k, v := range headers {
|
for k, v := range r.Headers {
|
||||||
if header.Get(k) == "" {
|
if header.Get(k) == "" {
|
||||||
header.Set(k, v)
|
header.Set(k, v)
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-11
@@ -15,7 +15,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.company.lan/gopkg/gin/internal/json"
|
"git.company.lan/gopkg/gin/codec/json"
|
||||||
testdata "git.company.lan/gopkg/gin/testdata/protoexample"
|
testdata "git.company.lan/gopkg/gin/testdata/protoexample"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -38,7 +38,7 @@ func TestRenderJSON(t *testing.T) {
|
|||||||
err := (JSON{data}).Render(w)
|
err := (JSON{data}).Render(w)
|
||||||
|
|
||||||
require.NoError(t, err)
|
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"))
|
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)
|
err := (IndentedJSON{data}).Render(w)
|
||||||
|
|
||||||
require.NoError(t, err)
|
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"))
|
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)
|
err1 := (SecureJSON{"while(1);", data}).Render(w1)
|
||||||
|
|
||||||
require.NoError(t, err1)
|
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"))
|
assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type"))
|
||||||
|
|
||||||
w2 := httptest.NewRecorder()
|
w2 := httptest.NewRecorder()
|
||||||
@@ -173,7 +173,7 @@ func TestRenderJsonpJSONError(t *testing.T) {
|
|||||||
err = jsonpJSON.Render(ew)
|
err = jsonpJSON.Render(ew)
|
||||||
assert.Equal(t, `write "`+`(`+`" error`, err.Error())
|
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)
|
ew.bufString = string(data)
|
||||||
err = jsonpJSON.Render(ew)
|
err = jsonpJSON.Render(ew)
|
||||||
assert.Equal(t, `write "`+string(data)+`" error`, err.Error())
|
assert.Equal(t, `write "`+string(data)+`" error`, err.Error())
|
||||||
@@ -194,7 +194,7 @@ func TestRenderJsonpJSONError2(t *testing.T) {
|
|||||||
e := (JsonpJSON{"", data}).Render(w)
|
e := (JsonpJSON{"", data}).Render(w)
|
||||||
require.NoError(t, e)
|
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"))
|
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)
|
err := (AsciiJSON{data1}).Render(w1)
|
||||||
|
|
||||||
require.NoError(t, err)
|
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"))
|
assert.Equal(t, "application/json", w1.Header().Get("Content-Type"))
|
||||||
|
|
||||||
w2 := httptest.NewRecorder()
|
w2 := httptest.NewRecorder()
|
||||||
@@ -244,7 +244,7 @@ func TestRenderPureJSON(t *testing.T) {
|
|||||||
}
|
}
|
||||||
err := (PureJSON{data}).Render(w)
|
err := (PureJSON{data}).Render(w)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
|
assert.JSONEq(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
|
||||||
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,7 +285,14 @@ b:
|
|||||||
|
|
||||||
err := (YAML{data}).Render(w)
|
err := (YAML{data}).Render(w)
|
||||||
require.NoError(t, err)
|
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"))
|
assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,7 +376,7 @@ func TestRenderXML(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderRedirect(t *testing.T) {
|
func TestRenderRedirect(t *testing.T) {
|
||||||
req, err := http.NewRequest("GET", "/test-redirect", nil)
|
req, err := http.NewRequest(http.MethodGet, "/test-redirect", nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
data1 := Redirect{
|
data1 := Redirect{
|
||||||
@@ -491,6 +498,8 @@ func TestRenderHTMLDebugFiles(t *testing.T) {
|
|||||||
htmlRender := HTMLDebug{
|
htmlRender := HTMLDebug{
|
||||||
Files: []string{"../testdata/template/hello.tmpl"},
|
Files: []string{"../testdata/template/hello.tmpl"},
|
||||||
Glob: "",
|
Glob: "",
|
||||||
|
FileSystem: nil,
|
||||||
|
Patterns: nil,
|
||||||
Delims: Delims{Left: "{[{", Right: "}]}"},
|
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||||
FuncMap: nil,
|
FuncMap: nil,
|
||||||
}
|
}
|
||||||
@@ -510,6 +519,29 @@ func TestRenderHTMLDebugGlob(t *testing.T) {
|
|||||||
htmlRender := HTMLDebug{
|
htmlRender := HTMLDebug{
|
||||||
Files: nil,
|
Files: nil,
|
||||||
Glob: "../testdata/template/hello*",
|
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, "<h1>Hello thinkerou</h1>", 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: "}]}"},
|
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||||
FuncMap: nil,
|
FuncMap: nil,
|
||||||
}
|
}
|
||||||
@@ -528,6 +560,8 @@ func TestRenderHTMLDebugPanics(t *testing.T) {
|
|||||||
htmlRender := HTMLDebug{
|
htmlRender := HTMLDebug{
|
||||||
Files: nil,
|
Files: nil,
|
||||||
Glob: "",
|
Glob: "",
|
||||||
|
FileSystem: nil,
|
||||||
|
Patterns: nil,
|
||||||
Delims: Delims{"{{", "}}"},
|
Delims: Delims{"{{", "}}"},
|
||||||
FuncMap: nil,
|
FuncMap: nil,
|
||||||
}
|
}
|
||||||
@@ -581,7 +615,7 @@ func TestRenderReaderNoContentLength(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderWriteError(t *testing.T) {
|
func TestRenderWriteError(t *testing.T) {
|
||||||
data := []interface{}{"value1", "value2"}
|
data := []any{"value1", "value2"}
|
||||||
prefix := "my-prefix:"
|
prefix := "my-prefix:"
|
||||||
r := SecureJSON{Data: data, Prefix: prefix}
|
r := SecureJSON{Data: data, Prefix: prefix}
|
||||||
ew := &errorWriter{
|
ew := &errorWriter{
|
||||||
|
|||||||
+2
-2
@@ -15,7 +15,7 @@ type TOML struct {
|
|||||||
Data any
|
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.
|
// Render (TOML) marshals the given interface object and writes data with custom ContentType.
|
||||||
func (r TOML) Render(w http.ResponseWriter) error {
|
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.
|
// WriteContentType (TOML) writes TOML ContentType for response.
|
||||||
func (r TOML) WriteContentType(w http.ResponseWriter) {
|
func (r TOML) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, TOMLContentType)
|
writeContentType(w, tomlContentType)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ package render
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"github.com/goccy/go-yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// YAML contains the given interface object.
|
// YAML contains the given interface object.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package gin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -16,6 +17,8 @@ const (
|
|||||||
defaultStatus = http.StatusOK
|
defaultStatus = http.StatusOK
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errHijackAlreadyWritten = errors.New("gin: response already written")
|
||||||
|
|
||||||
// ResponseWriter ...
|
// ResponseWriter ...
|
||||||
type ResponseWriter interface {
|
type ResponseWriter interface {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
@@ -106,6 +109,9 @@ func (w *responseWriter) Written() bool {
|
|||||||
|
|
||||||
// Hijack implements the http.Hijacker interface.
|
// Hijack implements the http.Hijacker interface.
|
||||||
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
if w.Written() {
|
||||||
|
return nil, nil, errHijackAlreadyWritten
|
||||||
|
}
|
||||||
if w.size < 0 {
|
if w.size < 0 {
|
||||||
w.size = 0
|
w.size = 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -124,6 +126,74 @@ func TestResponseWriterHijack(t *testing.T) {
|
|||||||
w.Flush()
|
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) {
|
func TestResponseWriterFlush(t *testing.T) {
|
||||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
writer := &responseWriter{}
|
writer := &responseWriter{}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var MaxHandlers = 32
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
SetMode(TestMode)
|
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.Static("/static", "."))
|
||||||
assert.Equal(t, r, r.StaticFS("/static2", Dir(".", false)))
|
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")
|
||||||
|
}
|
||||||
|
|||||||
+8
-8
@@ -484,7 +484,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
|
|||||||
assert.Contains(t, w.Body.String(), "package gin")
|
assert.Contains(t, w.Body.String(), "package gin")
|
||||||
// Content-Type='text/plain; charset=utf-8' when go version <= 1.16,
|
// Content-Type='text/plain; charset=utf-8' when go version <= 1.16,
|
||||||
// else, Content-Type='text/x-go; charset=utf-8'
|
// 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.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, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires"))
|
||||||
assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN"))
|
assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN"))
|
||||||
@@ -523,8 +523,8 @@ func TestRouteNotAllowedEnabled3(t *testing.T) {
|
|||||||
w := PerformRequest(router, http.MethodPut, "/path")
|
w := PerformRequest(router, http.MethodPut, "/path")
|
||||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||||
allowed := w.Header().Get("Allow")
|
allowed := w.Header().Get("Allow")
|
||||||
assert.Contains(t, allowed, "GET")
|
assert.Contains(t, allowed, http.MethodGet)
|
||||||
assert.Contains(t, allowed, "POST")
|
assert.Contains(t, allowed, http.MethodPost)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteNotAllowedDisabled(t *testing.T) {
|
func TestRouteNotAllowedDisabled(t *testing.T) {
|
||||||
@@ -557,10 +557,10 @@ func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) {
|
|||||||
{"/nope", http.StatusNotFound, ""}, // NotFound
|
{"/nope", http.StatusNotFound, ""}, // NotFound
|
||||||
}
|
}
|
||||||
for _, tr := range testRoutes {
|
for _, tr := range testRoutes {
|
||||||
w := PerformRequest(router, "GET", tr.route)
|
w := PerformRequest(router, http.MethodGet, tr.route)
|
||||||
assert.Equal(t, tr.code, w.Code)
|
assert.Equal(t, tr.code, w.Code)
|
||||||
if w.Code != http.StatusNotFound {
|
if w.Code != http.StatusNotFound {
|
||||||
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
|
assert.Equal(t, tr.location, w.Header().Get("Location"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -590,7 +590,7 @@ func TestRouterNotFound(t *testing.T) {
|
|||||||
w := PerformRequest(router, http.MethodGet, tr.route)
|
w := PerformRequest(router, http.MethodGet, tr.route)
|
||||||
assert.Equal(t, tr.code, w.Code)
|
assert.Equal(t, tr.code, w.Code)
|
||||||
if w.Code != http.StatusNotFound {
|
if w.Code != http.StatusNotFound {
|
||||||
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
|
assert.Equal(t, tr.location, w.Header().Get("Location"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -764,7 +764,7 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
|
|||||||
// Test not found
|
// Test not found
|
||||||
router.Use(func(c *Context) {
|
router.Use(func(c *Context) {
|
||||||
// For not found routes full path is empty
|
// 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")
|
w := PerformRequest(router, http.MethodGet, "/not-found")
|
||||||
@@ -786,6 +786,6 @@ func TestEngineHandleMethodNotAllowedCornerCase(t *testing.T) {
|
|||||||
v1.GET("/orgs/:id", handlerTest1)
|
v1.GET("/orgs/:id", handlerTest1)
|
||||||
v1.DELETE("/orgs/:id", handlerTest1)
|
v1.DELETE("/orgs/:id", handlerTest1)
|
||||||
|
|
||||||
w := PerformRequest(r, "GET", "/base/v1/user/groups")
|
w := PerformRequest(r, http.MethodGet, "/base/v1/user/groups")
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-2
@@ -6,7 +6,10 @@ package gin
|
|||||||
|
|
||||||
import "net/http"
|
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) {
|
func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
|
||||||
r = New()
|
r = New()
|
||||||
c = r.allocateContext(0)
|
c = r.allocateContext(0)
|
||||||
@@ -15,7 +18,11 @@ func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
|
|||||||
return
|
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) {
|
func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) {
|
||||||
c = r.allocateContext(r.maxParams)
|
c = r.allocateContext(r.maxParams)
|
||||||
c.reset()
|
c.reset()
|
||||||
|
|||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
This is a test file for Context.File() method testing.
|
||||||
|
It contains some sample content to verify file serving functionality.
|
||||||
@@ -234,7 +234,7 @@ walk:
|
|||||||
// Wildcard conflict
|
// Wildcard conflict
|
||||||
pathSeg := path
|
pathSeg := path
|
||||||
if n.nType != catchAll {
|
if n.nType != catchAll {
|
||||||
pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
|
pathSeg, _, _ = strings.Cut(pathSeg, "/")
|
||||||
}
|
}
|
||||||
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
|
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
|
||||||
panic("'" + pathSeg +
|
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] == '/' {
|
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
||||||
pathSeg := ""
|
pathSeg := ""
|
||||||
if len(n.children) != 0 {
|
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 +
|
panic("catch-all wildcard '" + path +
|
||||||
"' in new path '" + fullPath +
|
"' in new path '" + fullPath +
|
||||||
@@ -383,7 +383,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
|
|||||||
}
|
}
|
||||||
|
|
||||||
n.addChild(child)
|
n.addChild(child)
|
||||||
n.indices = string('/')
|
n.indices = "/"
|
||||||
n = child
|
n = child
|
||||||
n.priority++
|
n.priority++
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -481,7 +481,7 @@ func TestTreeDuplicatePath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//printChildren(tree, "")
|
// printChildren(tree, "")
|
||||||
|
|
||||||
checkRequests(t, tree, testRequests{
|
checkRequests(t, tree, testRequests{
|
||||||
{"/", false, "/", nil},
|
{"/", false, "/", nil},
|
||||||
@@ -532,7 +532,7 @@ func TestTreeCatchAllConflictRoot(t *testing.T) {
|
|||||||
|
|
||||||
func TestTreeCatchMaxParams(t *testing.T) {
|
func TestTreeCatchMaxParams(t *testing.T) {
|
||||||
tree := &node{}
|
tree := &node{}
|
||||||
var route = "/cmd/*filepath"
|
route := "/cmd/*filepath"
|
||||||
tree.addRoute(route, fakeHandler(route))
|
tree.addRoute(route, fakeHandler(route))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,7 +692,7 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedirectTrailingSlash(t *testing.T) {
|
func TestRedirectTrailingSlash(t *testing.T) {
|
||||||
var data = []struct {
|
data := []struct {
|
||||||
path string
|
path string
|
||||||
}{
|
}{
|
||||||
{"/hello/:name"},
|
{"/hello/:name"},
|
||||||
|
|||||||
+7
-7
@@ -29,7 +29,7 @@ type testStruct struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
assert.Equal(t.T, "POST", req.Method)
|
assert.Equal(t.T, http.MethodPost, req.Method)
|
||||||
assert.Equal(t.T, "/path", req.URL.Path)
|
assert.Equal(t.T, "/path", req.URL.Path)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
fmt.Fprint(w, "hello")
|
fmt.Fprint(w, "hello")
|
||||||
@@ -39,17 +39,17 @@ func TestWrap(t *testing.T) {
|
|||||||
router := New()
|
router := New()
|
||||||
router.POST("/path", WrapH(&testStruct{t}))
|
router.POST("/path", WrapH(&testStruct{t}))
|
||||||
router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) {
|
router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) {
|
||||||
assert.Equal(t, "GET", req.Method)
|
assert.Equal(t, http.MethodGet, req.Method)
|
||||||
assert.Equal(t, "/path2", req.URL.Path)
|
assert.Equal(t, "/path2", req.URL.Path)
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
fmt.Fprint(w, "hola!")
|
fmt.Fprint(w, "hola!")
|
||||||
}))
|
}))
|
||||||
|
|
||||||
w := PerformRequest(router, "POST", "/path")
|
w := PerformRequest(router, http.MethodPost, "/path")
|
||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
assert.Equal(t, "hello", w.Body.String())
|
assert.Equal(t, "hello", w.Body.String())
|
||||||
|
|
||||||
w = PerformRequest(router, "GET", "/path2")
|
w = PerformRequest(router, http.MethodGet, "/path2")
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Equal(t, "hola!", w.Body.String())
|
assert.Equal(t, "hola!", w.Body.String())
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ func somefunction() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestJoinPaths(t *testing.T) {
|
func TestJoinPaths(t *testing.T) {
|
||||||
assert.Equal(t, "", joinPaths("", ""))
|
assert.Empty(t, joinPaths("", ""))
|
||||||
assert.Equal(t, "/", joinPaths("", "/"))
|
assert.Equal(t, "/", joinPaths("", "/"))
|
||||||
assert.Equal(t, "/a", joinPaths("/a", ""))
|
assert.Equal(t, "/a", joinPaths("/a", ""))
|
||||||
assert.Equal(t, "/a/", joinPaths("/a/", ""))
|
assert.Equal(t, "/a/", joinPaths("/a/", ""))
|
||||||
@@ -119,13 +119,13 @@ func TestBindMiddleware(t *testing.T) {
|
|||||||
called = true
|
called = true
|
||||||
value = c.MustGet(BindKey).(*bindTestStruct)
|
value = c.MustGet(BindKey).(*bindTestStruct)
|
||||||
})
|
})
|
||||||
PerformRequest(router, "GET", "/?foo=hola&bar=10")
|
PerformRequest(router, http.MethodGet, "/?foo=hola&bar=10")
|
||||||
assert.True(t, called)
|
assert.True(t, called)
|
||||||
assert.Equal(t, "hola", value.Foo)
|
assert.Equal(t, "hola", value.Foo)
|
||||||
assert.Equal(t, 10, value.Bar)
|
assert.Equal(t, 10, value.Bar)
|
||||||
|
|
||||||
called = false
|
called = false
|
||||||
PerformRequest(router, "GET", "/?foo=hola&bar=1")
|
PerformRequest(router, http.MethodGet, "/?foo=hola&bar=1")
|
||||||
assert.False(t, called)
|
assert.False(t, called)
|
||||||
|
|
||||||
assert.Panics(t, func() {
|
assert.Panics(t, func() {
|
||||||
|
|||||||
+1
-1
@@ -5,4 +5,4 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
// Version is the current gin framework's version.
|
// Version is the current gin framework's version.
|
||||||
const Version = "v1.10.0"
|
const Version = "v1.11.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user