diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 4cbc4554..e27022d1 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -37,7 +37,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v1
+ uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -46,4 +46,4 @@ jobs:
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml
index c432a337..e8ef30ca 100644
--- a/.github/workflows/gin.yml
+++ b/.github/workflows/gin.yml
@@ -13,13 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Setup go
- uses: actions/setup-go@v2
+ uses: actions/setup-go@v3
with:
go-version: '^1.16'
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup golangci-lint
- uses: golangci/golangci-lint-action@v3.1.0
+ uses: golangci/golangci-lint-action@v3.2.0
with:
version: v1.45.0
args: --verbose
@@ -43,7 +43,7 @@ jobs:
GOPROXY: https://proxy.golang.org
steps:
- name: Set up Go ${{ matrix.go }}
- uses: actions/setup-go@v2
+ uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
@@ -52,7 +52,7 @@ jobs:
with:
ref: ${{ github.ref }}
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
with:
path: |
${{ matrix.go-build }}
@@ -65,7 +65,7 @@ jobs:
run: make test
- name: Upload coverage to Codecov
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v3
with:
flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
notification-gitter:
diff --git a/README.md b/README.md
index 4aa638d6..6b4cabb4 100644
--- a/README.md
+++ b/README.md
@@ -86,7 +86,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
To install Gin package, you need to install Go and set your Go workspace first.
-1. The first need [Go](https://golang.org/) installed (**version 1.14+ is required**), then you can use the below Go command to install Gin.
+1. You first need [Go](https://golang.org/) installed (**version 1.14+ is required**), then you can use the below Go command to install Gin.
```sh
$ go get -u github.com/gin-gonic/gin
@@ -658,7 +658,7 @@ func main() {
### Model binding and validation
-To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz).
+To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz).
Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags).
@@ -666,10 +666,10 @@ Note that you need to set the corresponding binding tag on all fields you want t
Also, Gin provides two sets of methods for binding:
- **Type** - Must bind
- - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`
+ - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML`
- **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.
- **Type** - Should bind
- - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`
+ - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`, `ShouldBindTOML`,
- **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.
When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
diff --git a/auth.go b/auth.go
index 4d8a6ce4..482c499a 100644
--- a/auth.go
+++ b/auth.go
@@ -31,7 +31,7 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
return "", false
}
for _, pair := range a {
- if subtle.ConstantTimeCompare([]byte(pair.value), []byte(authValue)) == 1 {
+ if subtle.ConstantTimeCompare(bytesconv.StringToBytes(pair.value), bytesconv.StringToBytes(authValue)) == 1 {
return pair.user, true
}
}
diff --git a/binding/binding.go b/binding/binding.go
index 703a1cf8..50510514 100644
--- a/binding/binding.go
+++ b/binding/binding.go
@@ -22,6 +22,7 @@ const (
MIMEMSGPACK = "application/x-msgpack"
MIMEMSGPACK2 = "application/msgpack"
MIMEYAML = "application/x-yaml"
+ MIMETOML = "application/toml"
)
// Binding describes the interface which needs to be implemented for binding the
@@ -83,6 +84,7 @@ var (
YAML = yamlBinding{}
Uri = uriBinding{}
Header = headerBinding{}
+ TOML = tomlBinding{}
)
// Default returns the appropriate Binding instance based on the HTTP method
@@ -103,6 +105,8 @@ func Default(method, contentType string) Binding {
return MsgPack
case MIMEYAML:
return YAML
+ case MIMETOML:
+ return TOML
case MIMEMultipartPOSTForm:
return FormMultipart
default: // case MIMEPOSTForm:
diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go
index b3818549..7f6a904a 100644
--- a/binding/binding_nomsgpack.go
+++ b/binding/binding_nomsgpack.go
@@ -20,6 +20,7 @@ const (
MIMEMultipartPOSTForm = "multipart/form-data"
MIMEPROTOBUF = "application/x-protobuf"
MIMEYAML = "application/x-yaml"
+ MIMETOML = "application/toml"
)
// Binding describes the interface which needs to be implemented for binding the
@@ -79,6 +80,7 @@ var (
YAML = yamlBinding{}
Uri = uriBinding{}
Header = headerBinding{}
+ TOML = tomlBinding{}
)
// Default returns the appropriate Binding instance based on the HTTP method
@@ -99,6 +101,8 @@ func Default(method, contentType string) Binding {
return YAML
case MIMEMultipartPOSTForm:
return FormMultipart
+ case MIMETOML:
+ return TOML
default: // case MIMEPOSTForm:
return Form
}
diff --git a/binding/binding_test.go b/binding/binding_test.go
index b1edbf5a..f08e173f 100644
--- a/binding/binding_test.go
+++ b/binding/binding_test.go
@@ -165,6 +165,9 @@ func TestBindingDefault(t *testing.T) {
assert.Equal(t, YAML, Default("POST", MIMEYAML))
assert.Equal(t, YAML, Default("PUT", MIMEYAML))
+
+ assert.Equal(t, TOML, Default("POST", MIMETOML))
+ assert.Equal(t, TOML, Default("PUT", MIMETOML))
}
func TestBindingJSONNilBody(t *testing.T) {
@@ -454,6 +457,20 @@ func TestBindingXMLFail(t *testing.T) {
"", "")
}
+func TestBindingTOML(t *testing.T) {
+ testBodyBinding(t,
+ TOML, "toml",
+ "/", "/",
+ `foo="bar"`, `bar="foo"`)
+}
+
+func TestBindingTOMLFail(t *testing.T) {
+ testBodyBindingFail(t,
+ TOML, "toml",
+ "/", "/",
+ `foo=\n"bar"`, `bar="foo"`)
+}
+
func TestBindingYAML(t *testing.T) {
testBodyBinding(t,
YAML, "yaml",
@@ -1339,10 +1356,10 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body
err := b.Bind(req, &obj)
assert.Error(t, err)
- invalid_obj := FooStruct{}
+ invalidobj := FooStruct{}
req.Body = ioutil.NopCloser(strings.NewReader(`{"msg":"hello"}`))
req.Header.Add("Content-Type", MIMEPROTOBUF)
- err = b.Bind(req, &invalid_obj)
+ err = b.Bind(req, &invalidobj)
assert.Error(t, err)
assert.Equal(t, err.Error(), "obj is not ProtoMessage")
diff --git a/binding/toml.go b/binding/toml.go
new file mode 100644
index 00000000..5b9ad165
--- /dev/null
+++ b/binding/toml.go
@@ -0,0 +1,31 @@
+package binding
+
+import (
+ "bytes"
+ "io"
+ "net/http"
+
+ "github.com/pelletier/go-toml/v2"
+)
+
+type tomlBinding struct{}
+
+func (tomlBinding) Name() string {
+ return "toml"
+}
+
+func decodeToml(r io.Reader, obj any) error {
+ decoder := toml.NewDecoder(r)
+ if err := decoder.Decode(obj); err != nil {
+ return err
+ }
+ return decoder.Decode(obj)
+}
+
+func (tomlBinding) Bind(req *http.Request, obj any) error {
+ return decodeToml(req.Body, obj)
+}
+
+func (tomlBinding) BindBody(body []byte, obj any) error {
+ return decodeToml(bytes.NewReader(body), obj)
+}
diff --git a/binding/toml_test.go b/binding/toml_test.go
new file mode 100644
index 00000000..2bc0e3a4
--- /dev/null
+++ b/binding/toml_test.go
@@ -0,0 +1,22 @@
+// 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.
+
+package binding
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestTOMLBindingBindBody(t *testing.T) {
+ var s struct {
+ Foo string `toml:"foo"`
+ }
+ tomlBody := `foo="FOO"`
+ err := tomlBinding{}.BindBody([]byte(tomlBody), &s)
+ require.NoError(t, err)
+ assert.Equal(t, "FOO", s.Foo)
+}
diff --git a/context.go b/context.go
index faa48133..ecbd3dac 100644
--- a/context.go
+++ b/context.go
@@ -34,11 +34,15 @@ const (
MIMEPOSTForm = binding.MIMEPOSTForm
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
MIMEYAML = binding.MIMEYAML
+ MIMETOML = binding.MIMETOML
)
// BodyBytesKey indicates a default body bytes key.
const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
+// ContextKey is the key that a Context returns itself for.
+const ContextKey = "_gin-gonic/gin/contextkey"
+
// abortIndex represents a typical value used in abort functions.
const abortIndex int8 = math.MaxInt8 >> 1
@@ -98,6 +102,7 @@ func (c *Context) reset() {
c.Accepted = nil
c.queryCache = nil
c.formCache = nil
+ c.sameSite = 0
*c.params = (*c.params)[:0]
*c.skippedNodes = (*c.skippedNodes)[:0]
}
@@ -632,6 +637,11 @@ func (c *Context) BindYAML(obj any) error {
return c.MustBindWith(obj, binding.YAML)
}
+// BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML).
+func (c *Context) BindTOML(obj interface{}) error {
+ return c.MustBindWith(obj, binding.TOML)
+}
+
// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
func (c *Context) BindHeader(obj any) error {
return c.MustBindWith(obj, binding.Header)
@@ -690,6 +700,11 @@ func (c *Context) ShouldBindYAML(obj any) error {
return c.ShouldBindWith(obj, binding.YAML)
}
+// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).
+func (c *Context) ShouldBindTOML(obj interface{}) error {
+ return c.ShouldBindWith(obj, binding.TOML)
+}
+
// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
func (c *Context) ShouldBindHeader(obj any) error {
return c.ShouldBindWith(obj, binding.Header)
@@ -961,6 +976,11 @@ func (c *Context) YAML(code int, obj any) {
c.Render(code, render.YAML{Data: obj})
}
+// TOML serializes the given struct as TOML into the response body.
+func (c *Context) TOML(code int, obj interface{}) {
+ c.Render(code, render.TOML{Data: obj})
+}
+
// ProtoBuf serializes the given struct as ProtoBuf into the response body.
func (c *Context) ProtoBuf(code int, obj any) {
c.Render(code, render.ProtoBuf{Data: obj})
@@ -1065,6 +1085,7 @@ type Negotiate struct {
XMLData any
YAMLData any
Data any
+ TOMLData any
}
// Negotiate calls different Render according to acceptable Accept format.
@@ -1086,6 +1107,10 @@ func (c *Context) Negotiate(code int, config Negotiate) {
data := chooseData(config.YAMLData, config.Data)
c.YAML(code, data)
+ case binding.MIMETOML:
+ data := chooseData(config.TOMLData, config.Data)
+ c.TOML(code, data)
+
default:
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck
}
@@ -1162,6 +1187,9 @@ func (c *Context) Value(key any) any {
if key == 0 {
return c.Request
}
+ if key == ContextKey {
+ return c
+ }
if keyAsString, ok := key.(string); ok {
if val, exists := c.Get(keyAsString); exists {
return val
diff --git a/context_go17_test.go b/context_go17_test.go
new file mode 100644
index 00000000..eca089ce
--- /dev/null
+++ b/context_go17_test.go
@@ -0,0 +1,50 @@
+// +build go1.7
+
+package gin
+
+import (
+ "bytes"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+type interceptedWriter struct {
+ ResponseWriter
+ b *bytes.Buffer
+}
+
+func (i interceptedWriter) WriteHeader(code int) {
+ i.Header().Del("X-Test")
+ i.ResponseWriter.WriteHeader(code)
+}
+func TestInterceptedHeader(t *testing.T) {
+ w := httptest.NewRecorder()
+ c, r := CreateTestContext(w)
+
+ r.Use(func(c *Context) {
+ i := interceptedWriter{
+ ResponseWriter: c.Writer,
+ b: bytes.NewBuffer(nil),
+ }
+ c.Writer = i
+ c.Next()
+ c.Header("X-Test", "overridden")
+ c.Writer = i.ResponseWriter
+ })
+ r.GET("/", func(c *Context) {
+ c.Header("X-Test", "original")
+ c.Header("X-Test-2", "present")
+ c.String(http.StatusOK, "hello world")
+ })
+ c.Request = httptest.NewRequest("GET", "/", nil)
+ r.HandleContext(c)
+ // Result() has headers frozen when WriteHeaderNow() has been called
+ // Compared to this time, this is when the response headers will be flushed
+ // As response is flushed on c.String, the Header cannot be set by the first
+ // middleware. Assert this
+ assert.Equal(t, "", w.Result().Header.Get("X-Test"))
+ assert.Equal(t, "present", w.Result().Header.Get("X-Test-2"))
+}
diff --git a/context_test.go b/context_test.go
index fb46e679..7e4d0b3f 100644
--- a/context_test.go
+++ b/context_test.go
@@ -1773,6 +1773,23 @@ func TestContextShouldBindWithYAML(t *testing.T) {
assert.Equal(t, 0, w.Body.Len())
}
+func TestContextShouldBindWithTOML(t *testing.T) {
+ w := httptest.NewRecorder()
+ c, _ := CreateTestContext(w)
+
+ c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo='bar'\nbar= 'foo'"))
+ c.Request.Header.Add("Content-Type", MIMETOML) // set fake content-type
+
+ var obj struct {
+ Foo string `toml:"foo"`
+ Bar string `toml:"bar"`
+ }
+ assert.NoError(t, c.ShouldBindTOML(&obj))
+ assert.Equal(t, "foo", obj.Bar)
+ assert.Equal(t, "bar", obj.Foo)
+ assert.Equal(t, 0, w.Body.Len())
+}
+
func TestContextBadAutoShouldBind(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
@@ -1880,6 +1897,7 @@ func TestContextGolangContext(t *testing.T) {
assert.Equal(t, ti, time.Time{})
assert.False(t, ok)
assert.Equal(t, c.Value(0), c.Request)
+ assert.Equal(t, c.Value(ContextKey), c)
assert.Nil(t, c.Value("foo"))
c.Set("foo", "bar")
diff --git a/gin.go b/gin.go
index b0e0154f..3a831e5b 100644
--- a/gin.go
+++ b/gin.go
@@ -67,10 +67,10 @@ type RoutesInfo []RouteInfo
// Trusted platforms
const (
- // When running on Google App Engine. Trust X-Appengine-Remote-Addr
+ // PlatformGoogleAppEngine when running on Google App Engine. Trust X-Appengine-Remote-Addr
// for determining the client's IP
PlatformGoogleAppEngine = "X-Appengine-Remote-Addr"
- // When using Cloudflare's CDN. Trust CF-Connecting-IP for determining
+ // PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining
// the client's IP
PlatformCloudflare = "CF-Connecting-IP"
)
@@ -80,14 +80,14 @@ const (
type Engine struct {
RouterGroup
- // Enables automatic redirection if the current route can't be matched but a
+ // RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a
// handler for the path with (without) the trailing slash exists.
// For example if /foo/ is requested but a route only exists for /foo, the
// client is redirected to /foo with http status code 301 for GET requests
// and 307 for all other request methods.
RedirectTrailingSlash bool
- // If enabled, the router tries to fix the current request path, if no
+ // RedirectFixedPath if enabled, the router tries to fix the current request path, if no
// handle is registered for it.
// First superfluous path elements like ../ or // are removed.
// Afterwards the router does a case-insensitive lookup of the cleaned path.
@@ -98,7 +98,7 @@ type Engine struct {
// RedirectTrailingSlash is independent of this option.
RedirectFixedPath bool
- // If enabled, the router checks if another method is allowed for the
+ // HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the
// current route, if the current request can not be routed.
// If this is the case, the request is answered with 'Method Not Allowed'
// and HTTP status code 405.
@@ -106,21 +106,22 @@ type Engine struct {
// handler.
HandleMethodNotAllowed bool
- // If enabled, client IP will be parsed from the request's headers that
+ // ForwardedByClientIP if enabled, client IP will be parsed from the request's headers that
// match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was
// fetched, it falls back to the IP obtained from
// `(*gin.Context).Request.RemoteAddr`.
ForwardedByClientIP bool
- // DEPRECATED: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD
+ // AppEngine was deprecated.
+ // Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD
// #726 #755 If enabled, it will trust some headers starting with
// 'X-AppEngine...' for better integration with that PaaS.
AppEngine bool
- // If enabled, the url.RawPath will be used to find parameters.
+ // UseRawPath if enabled, the url.RawPath will be used to find parameters.
UseRawPath bool
- // If true, the path value will be unescaped.
+ // UnescapePathValues if true, the path value will be unescaped.
// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
// as url.Path gonna be used, which is already unescaped.
UnescapePathValues bool
@@ -129,21 +130,21 @@ type Engine struct {
// See the PR #1817 and issue #1644
RemoveExtraSlash bool
- // List of headers used to obtain the client IP when
+ // RemoteIPHeaders list of headers used to obtain the client IP when
// `(*gin.Engine).ForwardedByClientIP` is `true` and
// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the
// network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.
RemoteIPHeaders []string
- // If set to a constant of value gin.Platform*, trusts the headers set by
+ // TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by
// that platform, for example to determine the client IP
TrustedPlatform string
- // Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
+ // MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
// method call.
MaxMultipartMemory int64
- // Enable h2c support.
+ // UseH2C enable h2c support.
UseH2C bool
delims render.Delims
@@ -194,7 +195,7 @@ func New() *Engine {
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJSONPrefix: "while(1);",
- trustedProxies: []string{"0.0.0.0/0"},
+ trustedProxies: []string{"0.0.0.0/0", "::/0"},
trustedCIDRs: defaultTrustedCIDRs,
}
engine.RouterGroup.engine = engine
diff --git a/gin_integration_test.go b/gin_integration_test.go
index 8c22e7bd..0dfa9032 100644
--- a/gin_integration_test.go
+++ b/gin_integration_test.go
@@ -15,6 +15,7 @@ import (
"net/http/httptest"
"os"
"path/filepath"
+ "runtime"
"sync"
"testing"
"time"
@@ -281,7 +282,16 @@ func TestFileDescriptor(t *testing.T) {
listener, err := net.ListenTCP("tcp", addr)
assert.NoError(t, err)
socketFile, err := listener.File()
- assert.NoError(t, err)
+ if isWindows() {
+ // not supported by windows, it is unimplemented now
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ }
+
+ if socketFile == nil {
+ return
+ }
go func() {
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
@@ -547,3 +557,7 @@ func TestTreeRunDynamicRouting(t *testing.T) {
testRequest(t, ts.URL+"/addr/dd/aa", "404 Not Found")
testRequest(t, ts.URL+"/something/secondthing/121", "404 Not Found")
}
+
+func isWindows() bool {
+ return runtime.GOOS == "windows"
+}
diff --git a/gin_test.go b/gin_test.go
index 0c11134f..a4380622 100644
--- a/gin_test.go
+++ b/gin_test.go
@@ -202,7 +202,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
}
resp, _ := ioutil.ReadAll(res.Body)
- assert.Equal(t, "Date: 2017/07/01\n", string(resp))
+ assert.Equal(t, "Date: 2017/07/01", string(resp))
}
func init() {
@@ -320,7 +320,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
}
resp, _ := ioutil.ReadAll(res.Body)
- assert.Equal(t, "Date: 2017/07/01\n", string(resp))
+ assert.Equal(t, "Date: 2017/07/01", string(resp))
}
func TestAddRoute(t *testing.T) {
diff --git a/githubapi_test.go b/githubapi_test.go
index e74bddd5..5fe65a4b 100644
--- a/githubapi_test.go
+++ b/githubapi_test.go
@@ -296,8 +296,8 @@ func TestShouldBindUri(t *testing.T) {
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
var person Person
assert.NoError(t, c.ShouldBindUri(&person))
- assert.True(t, "" != person.Name)
- assert.True(t, "" != person.ID)
+ assert.True(t, person.Name != "")
+ assert.True(t, person.ID != "")
c.String(http.StatusOK, "ShouldBindUri test OK")
})
@@ -318,8 +318,8 @@ func TestBindUri(t *testing.T) {
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
var person Person
assert.NoError(t, c.BindUri(&person))
- assert.True(t, "" != person.Name)
- assert.True(t, "" != person.ID)
+ assert.True(t, person.Name != "")
+ assert.True(t, person.ID != "")
c.String(http.StatusOK, "BindUri test OK")
})
diff --git a/go.mod b/go.mod
index dcd83686..e519b36e 100644
--- a/go.mod
+++ b/go.mod
@@ -5,13 +5,14 @@ go 1.18
require (
github.com/gin-contrib/sse v0.1.0
github.com/go-playground/validator/v10 v10.10.0
- github.com/goccy/go-json v0.9.5
+ github.com/goccy/go-json v0.9.7
github.com/json-iterator/go v1.1.12
github.com/mattn/go-isatty v0.0.14
- github.com/stretchr/testify v1.7.0
+ github.com/pelletier/go-toml/v2 v2.0.0-beta.6
+ github.com/stretchr/testify v1.7.1
github.com/ugorji/go/codec v1.2.7
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
- google.golang.org/protobuf v1.27.1
+ google.golang.org/protobuf v1.28.0
gopkg.in/yaml.v2 v2.4.0
)
diff --git a/go.sum b/go.sum
index c0980ad5..d7555f9a 100644
--- a/go.sum
+++ b/go.sum
@@ -12,8 +12,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
-github.com/goccy/go-json v0.9.5 h1:ooSMW526ZjK+EaL5elrSyN2EzIfi/3V0m4+HJEDYLik=
-github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
+github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -36,6 +36,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pelletier/go-toml/v2 v2.0.0-beta.6 h1:JFNqj2afbbhCqTiyN16D7Tudc/aaDzE2FBDk+VlBQnE=
+github.com/pelletier/go-toml/v2 v2.0.0-beta.6/go.mod h1:ke6xncR3W76Ba8xnVxkrZG0js6Rd2BsQEAYrfgJ6eQA=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -45,8 +47,11 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
@@ -67,8 +72,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
-google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
diff --git a/logger.go b/logger.go
index ab43d112..1f9d63ae 100644
--- a/logger.go
+++ b/logger.go
@@ -44,7 +44,7 @@ type LoggerConfig struct {
// Optional. Default value is gin.DefaultWriter.
Output io.Writer
- // SkipPaths is a url path array which logs are not written.
+ // SkipPaths is an url path array which logs are not written.
// Optional.
SkipPaths []string
}
@@ -161,12 +161,12 @@ func ForceConsoleColor() {
consoleColorMode = forceColor
}
-// ErrorLogger returns a handlerfunc for any error type.
+// ErrorLogger returns a HandlerFunc for any error type.
func ErrorLogger() HandlerFunc {
return ErrorLoggerT(ErrorTypeAny)
}
-// ErrorLoggerT returns a handlerfunc for a given error type.
+// ErrorLoggerT returns a HandlerFunc for a given error type.
func ErrorLoggerT(typ ErrorType) HandlerFunc {
return func(c *Context) {
c.Next()
diff --git a/logger_test.go b/logger_test.go
index da1b654e..b7049988 100644
--- a/logger_test.go
+++ b/logger_test.go
@@ -208,6 +208,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
// set dummy ClientIP
c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
gotKeys = c.Keys
+ time.Sleep(time.Millisecond)
})
PerformRequest(router, "GET", "/example?a=100")
diff --git a/mode.go b/mode.go
index 1fb994b4..4e7d734a 100644
--- a/mode.go
+++ b/mode.go
@@ -5,6 +5,7 @@
package gin
import (
+ "flag"
"io"
"os"
@@ -54,7 +55,11 @@ func init() {
// SetMode sets gin mode according to input string.
func SetMode(value string) {
if value == "" {
- value = DebugMode
+ if flag.Lookup("test.v") != nil {
+ value = TestMode
+ } else {
+ value = DebugMode
+ }
}
switch value {
diff --git a/mode_test.go b/mode_test.go
index 1b5fb2ff..6fd9a137 100644
--- a/mode_test.go
+++ b/mode_test.go
@@ -5,6 +5,7 @@
package gin
import (
+ "flag"
"os"
"testing"
@@ -21,9 +22,16 @@ func TestSetMode(t *testing.T) {
assert.Equal(t, TestMode, Mode())
os.Unsetenv(EnvGinMode)
+ SetMode("")
+ assert.Equal(t, testCode, ginMode)
+ assert.Equal(t, TestMode, Mode())
+
+ tmp := flag.CommandLine
+ flag.CommandLine = flag.NewFlagSet("", flag.ContinueOnError)
SetMode("")
assert.Equal(t, debugCode, ginMode)
assert.Equal(t, DebugMode, Mode())
+ flag.CommandLine = tmp
SetMode(DebugMode)
assert.Equal(t, debugCode, ginMode)
diff --git a/render/render.go b/render/render.go
index bcd568bf..1fa48061 100644
--- a/render/render.go
+++ b/render/render.go
@@ -30,6 +30,7 @@ var (
_ Render = Reader{}
_ Render = AsciiJSON{}
_ Render = ProtoBuf{}
+ _ Render = TOML{}
)
func writeContentType(w http.ResponseWriter, value []string) {
diff --git a/render/toml.go b/render/toml.go
new file mode 100644
index 00000000..1192c78c
--- /dev/null
+++ b/render/toml.go
@@ -0,0 +1,32 @@
+package render
+
+import (
+ "net/http"
+
+ "github.com/pelletier/go-toml/v2"
+)
+
+// TOML contains the given interface object.
+type TOML struct {
+ Data any
+}
+
+var TOMLContentType = []string{"application/toml; charset=utf-8"}
+
+// Render (TOML) marshals the given interface object and writes data with custom ContentType.
+func (r TOML) Render(w http.ResponseWriter) error {
+ r.WriteContentType(w)
+
+ bytes, err := toml.Marshal(r.Data)
+ if err != nil {
+ return err
+ }
+
+ _, err = w.Write(bytes)
+ return err
+}
+
+// WriteContentType (TOML) writes TOML ContentType for response.
+func (r TOML) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, TOMLContentType)
+}
diff --git a/response_writer.go b/response_writer.go
index 26826689..7f9095f0 100644
--- a/response_writer.go
+++ b/response_writer.go
@@ -23,23 +23,23 @@ type ResponseWriter interface {
http.Flusher
http.CloseNotifier
- // Returns the HTTP response status code of the current request.
+ // Status returns the HTTP response status code of the current request.
Status() int
- // Returns the number of bytes already written into the response http body.
+ // Size returns the number of bytes already written into the response http body.
// See Written()
Size() int
- // Writes the string into the response body.
+ // WriteString writes the string into the response body.
WriteString(string) (int, error)
- // Returns true if the response body was already written.
+ // Written returns true if the response body was already written.
Written() bool
- // Forces to write the http header (status code + headers).
+ // WriteHeaderNow forces to write the http header (status code + headers).
WriteHeaderNow()
- // get the http.Pusher for server push
+ // Pusher get the http.Pusher for server push
Pusher() http.Pusher
}
@@ -107,12 +107,12 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.ResponseWriter.(http.Hijacker).Hijack()
}
-// CloseNotify implements the http.CloseNotify interface.
+// CloseNotify implements the http.CloseNotifier interface.
func (w *responseWriter) CloseNotify() <-chan bool {
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
}
-// Flush implements the http.Flush interface.
+// Flush implements the http.Flusher interface.
func (w *responseWriter) Flush() {
w.WriteHeaderNow()
w.ResponseWriter.(http.Flusher).Flush()
diff --git a/testdata/template/raw.tmpl b/testdata/template/raw.tmpl
index 8bc75703..f3f530a4 100644
--- a/testdata/template/raw.tmpl
+++ b/testdata/template/raw.tmpl
@@ -1 +1 @@
-Date: {[{.now | formatAsDate}]}
+Date: {[{.now | formatAsDate}]}
\ No newline at end of file