diff --git a/README.md b/README.md
index d3fa075764..b83f4efc21 100644
--- a/README.md
+++ b/README.md
@@ -657,7 +657,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).
@@ -665,10 +665,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/binding/binding.go b/binding/binding.go
index 0414a3453b..0c16fa7744 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_test.go b/binding/binding_test.go
index 5b0ce39d3e..c3f631b292 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",
diff --git a/binding/toml.go b/binding/toml.go
new file mode 100644
index 0000000000..46ec2dd6be
--- /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 interface{}) 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 interface{}) error {
+ return decodeToml(req.Body, obj)
+}
+
+func (tomlBinding) BindBody(body []byte, obj interface{}) error {
+ return decodeToml(bytes.NewReader(body), obj)
+}
diff --git a/binding/toml_test.go b/binding/toml_test.go
new file mode 100644
index 0000000000..2bc0e3a47e
--- /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 d69df70b24..cc4a495bb6 100644
--- a/context.go
+++ b/context.go
@@ -35,6 +35,7 @@ const (
MIMEPOSTForm = binding.MIMEPOSTForm
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
MIMEYAML = binding.MIMEYAML
+ MIMETOML = binding.MIMETOML
)
// BodyBytesKey indicates a default body bytes key.
@@ -633,6 +634,11 @@ func (c *Context) BindYAML(obj interface{}) 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 interface{}) error {
return c.MustBindWith(obj, binding.Header)
@@ -962,6 +968,11 @@ func (c *Context) YAML(code int, obj interface{}) {
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 interface{}) {
c.Render(code, render.ProtoBuf{Data: obj})
@@ -1061,6 +1072,7 @@ type Negotiate struct {
JSONData interface{}
XMLData interface{}
YAMLData interface{}
+ TOMLData interface{}
Data interface{}
}
@@ -1083,6 +1095,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
}
diff --git a/go.mod b/go.mod
index 8087227e67..9f9a81ef05 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,8 @@ require (
github.com/goccy/go-json v0.9.5
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.6
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
google.golang.org/protobuf v1.27.1
diff --git a/go.sum b/go.sum
index 7f892453b7..eae4bd5344 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
+github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -36,6 +38,10 @@ 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 v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
+github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+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=
@@ -47,6 +53,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
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.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
diff --git a/render/render.go b/render/render.go
index bcd568bfba..1fa48061fa 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 0000000000..a581b7cb8e
--- /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 interface{}
+}
+
+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)
+}