mirror of
https://github.com/gin-gonic/gin.git
synced 2026-04-30 07:38:15 +08:00
feat(binding): add support for binding whole request at once
This commit is contained in:
parent
d3ffc99852
commit
39b84f00ad
43
binding/all.go
Normal file
43
binding/all.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type allBinding struct{}
|
||||||
|
|
||||||
|
var _ BindingMany = allBinding{}
|
||||||
|
|
||||||
|
func (allBinding) Name() string {
|
||||||
|
return "all"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (allBinding) BindMany(req *http.Request, uriParams map[string][]string, obj any) error {
|
||||||
|
// from binding.Header
|
||||||
|
if err := mapHeader(obj, req.Header); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// from binding.Uri
|
||||||
|
if err := mapURI(obj, uriParams); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// from binding.Query
|
||||||
|
values := req.URL.Query()
|
||||||
|
if err := mapForm(obj, values); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// from context.Bind (for body/post-form/anything else)
|
||||||
|
contentType := req.Header.Get("Content-Type")
|
||||||
|
// trim contentType parameters, e.g. "application/json; charset=utf-8" -> "application/json"
|
||||||
|
contentTypeLastIdx := strings.IndexAny(contentType, " ;")
|
||||||
|
if contentTypeLastIdx != -1 {
|
||||||
|
contentType = contentType[:contentTypeLastIdx]
|
||||||
|
}
|
||||||
|
b := Default(req.Method, contentType)
|
||||||
|
// final validation done by whatever binding is selected here
|
||||||
|
return b.Bind(req, obj)
|
||||||
|
}
|
||||||
@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
// Content-Type MIME of the most common data formats.
|
// Content-Type MIME of the most common data formats.
|
||||||
const (
|
const (
|
||||||
@ -48,6 +50,15 @@ type BindingUri interface {
|
|||||||
BindUri(map[string][]string, any) error
|
BindUri(map[string][]string, any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BindingMany adds BindMany method to Binding. BindingMany is similar to Binding,
|
||||||
|
// but it has full access to all request aspects to bind multiple parts simultaneously.
|
||||||
|
//
|
||||||
|
// NOTE: External projects should NOT depend on this interface as it is for Gin's internal binding logic
|
||||||
|
type BindingMany interface {
|
||||||
|
Name() string
|
||||||
|
BindMany(req *http.Request, uriParams map[string][]string, obj any) error
|
||||||
|
}
|
||||||
|
|
||||||
// StructValidator is the minimal interface which needs to be implemented in
|
// StructValidator is the minimal interface which needs to be implemented in
|
||||||
// order for it to be used as the validator engine for ensuring the correctness
|
// order for it to be used as the validator engine for ensuring the correctness
|
||||||
// of the request. Gin provides a default implementation for this using
|
// of the request. Gin provides a default implementation for this using
|
||||||
@ -74,6 +85,7 @@ var Validator StructValidator = &defaultValidator{}
|
|||||||
// These implement the Binding interface and can be used to bind the data
|
// These implement the Binding interface and can be used to bind the data
|
||||||
// present in the request to struct instances.
|
// present in the request to struct instances.
|
||||||
var (
|
var (
|
||||||
|
All BindingMany = allBinding{} // NOTE: This combines other bindings and doesn't have its own Content-Type
|
||||||
JSON BindingBody = jsonBinding{}
|
JSON BindingBody = jsonBinding{}
|
||||||
XML BindingBody = xmlBinding{}
|
XML BindingBody = xmlBinding{}
|
||||||
Form Binding = formBinding{}
|
Form Binding = formBinding{}
|
||||||
|
|||||||
@ -46,6 +46,15 @@ type BindingUri interface {
|
|||||||
BindUri(map[string][]string, any) error
|
BindUri(map[string][]string, any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BindingMany adds BindMany method to Binding. BindingMany is similar to Binding,
|
||||||
|
// but it has full access to all request aspects to bind multiple parts simultaneously.
|
||||||
|
//
|
||||||
|
// NOTE: External projects should NOT depend on this interface as it is for Gin's internal binding logic
|
||||||
|
type BindingMany interface {
|
||||||
|
Name() string
|
||||||
|
BindMany(req *http.Request, uriParams map[string][]string, obj any) error
|
||||||
|
}
|
||||||
|
|
||||||
// StructValidator is the minimal interface which needs to be implemented in
|
// StructValidator is the minimal interface which needs to be implemented in
|
||||||
// order for it to be used as the validator engine for ensuring the correctness
|
// order for it to be used as the validator engine for ensuring the correctness
|
||||||
// of the request. Gin provides a default implementation for this using
|
// of the request. Gin provides a default implementation for this using
|
||||||
@ -71,6 +80,7 @@ var Validator StructValidator = &defaultValidator{}
|
|||||||
// These implement the Binding interface and can be used to bind the data
|
// These implement the Binding interface and can be used to bind the data
|
||||||
// present in the request to struct instances.
|
// present in the request to struct instances.
|
||||||
var (
|
var (
|
||||||
|
All BindingMany = allBinding{} // NOTE: This combines other bindings and doesn't have its own Content-Type
|
||||||
JSON = jsonBinding{}
|
JSON = jsonBinding{}
|
||||||
XML = xmlBinding{}
|
XML = xmlBinding{}
|
||||||
Form = formBinding{}
|
Form = formBinding{}
|
||||||
|
|||||||
@ -35,6 +35,13 @@ type QueryTest struct {
|
|||||||
appkey
|
appkey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FooStructHeaderUriQueryBody struct {
|
||||||
|
Limit int `header:"limit" binding:"required"`
|
||||||
|
ID string `uri:"id" binding:"required,numeric"`
|
||||||
|
Foo bool `form:"foo" binding:"required"`
|
||||||
|
Bar string `form:"bar" xml:"bar" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type FooStruct struct {
|
type FooStruct struct {
|
||||||
Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required,max=32"`
|
Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required,max=32"`
|
||||||
}
|
}
|
||||||
@ -1428,6 +1435,152 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBindingAll(t *testing.T) {
|
||||||
|
b := All
|
||||||
|
assert.Equal(t, "all", b.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingAllHeader(t *testing.T) {
|
||||||
|
b := All
|
||||||
|
|
||||||
|
type tHeader struct {
|
||||||
|
Limit int `header:"limit"`
|
||||||
|
}
|
||||||
|
var theader tHeader
|
||||||
|
req := requestWithBody(http.MethodGet, "/", "")
|
||||||
|
req.Header.Add("limit", "1000")
|
||||||
|
require.NoError(t, b.BindMany(req, nil, &theader))
|
||||||
|
assert.Equal(t, 1000, theader.Limit)
|
||||||
|
|
||||||
|
// fail case
|
||||||
|
type failStruct struct {
|
||||||
|
Fail map[string]any `header:"fail"`
|
||||||
|
}
|
||||||
|
req = requestWithBody(http.MethodGet, "/", "")
|
||||||
|
req.Header.Add("fail", `{fail:fail}`)
|
||||||
|
err := b.BindMany(req, nil, &failStruct{})
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingAllUri(t *testing.T) {
|
||||||
|
b := All
|
||||||
|
|
||||||
|
type Tag struct {
|
||||||
|
Name string `uri:"name"`
|
||||||
|
}
|
||||||
|
var tag Tag
|
||||||
|
req := requestWithBody(http.MethodGet, "/thinkerou", "")
|
||||||
|
m := map[string][]string{"name": {"thinkerou"}}
|
||||||
|
require.NoError(t, b.BindMany(req, m, &tag))
|
||||||
|
assert.Equal(t, "thinkerou", tag.Name)
|
||||||
|
|
||||||
|
// fail case
|
||||||
|
type NotSupportStruct struct {
|
||||||
|
Name map[string]any `uri:"name"`
|
||||||
|
}
|
||||||
|
var not NotSupportStruct
|
||||||
|
require.Error(t, b.BindMany(req, m, ¬))
|
||||||
|
assert.Equal(t, map[string]any(nil), not.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingAllQuery(t *testing.T) {
|
||||||
|
b := All
|
||||||
|
|
||||||
|
obj := FooBarStruct{}
|
||||||
|
req := requestWithBody(http.MethodGet, "/?foo=bar&bar=foo", "")
|
||||||
|
err := b.BindMany(req, nil, &obj)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
|
|
||||||
|
// fail case
|
||||||
|
obj2 := FooStructForBoolType{}
|
||||||
|
req = requestWithBody(http.MethodGet, "/?bool_foo=fasl", "")
|
||||||
|
err = b.BindMany(req, nil, &obj2)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingAllBody(t *testing.T) {
|
||||||
|
b := All
|
||||||
|
|
||||||
|
obj := FooStruct{}
|
||||||
|
req := requestWithBody(http.MethodPost, "/", `{"foo": "bar"}`)
|
||||||
|
req.Header.Set("Content-Type", MIMEJSON+"; charset=utf-8")
|
||||||
|
err := b.BindMany(req, nil, &obj)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
|
||||||
|
// fail case
|
||||||
|
obj2 := FooStruct{}
|
||||||
|
req = requestWithBody(http.MethodPost, "/", `{"bar": "foo"}`)
|
||||||
|
req.Header.Set("Content-Type", MIMEJSON)
|
||||||
|
err = b.BindMany(req, nil, &obj2)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingAllHeaderUriQueryBody(t *testing.T) {
|
||||||
|
b := All
|
||||||
|
|
||||||
|
obj := FooStructHeaderUriQueryBody{}
|
||||||
|
req := requestWithBody(http.MethodPost, "/?foo=true", `bar=spam`)
|
||||||
|
req.Header.Set("Limit", "100")
|
||||||
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
|
m := map[string][]string{"id": {"1234"}}
|
||||||
|
err := b.BindMany(req, m, &obj)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 100, obj.Limit)
|
||||||
|
assert.Equal(t, "1234", obj.ID)
|
||||||
|
assert.True(t, obj.Foo)
|
||||||
|
assert.Equal(t, "spam", obj.Bar)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingAllHeaderUriQueryBody_FailWhenHeaderMissing(t *testing.T) {
|
||||||
|
b := All
|
||||||
|
|
||||||
|
obj := FooStructHeaderUriQueryBody{}
|
||||||
|
req := requestWithBody(http.MethodPost, "/?foo=true", `bar=spam`)
|
||||||
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
|
m := map[string][]string{"id": {"1234"}}
|
||||||
|
err := b.BindMany(req, m, &obj)
|
||||||
|
require.ErrorContains(t, err, "validation for 'Limit' failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingAllHeaderUriQueryBody_FailWhenUriInvalid(t *testing.T) {
|
||||||
|
b := All
|
||||||
|
|
||||||
|
obj := FooStructHeaderUriQueryBody{}
|
||||||
|
req := requestWithBody(http.MethodPost, "/?foo=true", `bar=spam`)
|
||||||
|
req.Header.Set("Limit", "100")
|
||||||
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
|
m := map[string][]string{"id": {"123x"}}
|
||||||
|
err := b.BindMany(req, m, &obj)
|
||||||
|
require.ErrorContains(t, err, "validation for 'ID' failed on the 'numeric' tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingAllHeaderUriQueryBody_FailWhenQueryMissing(t *testing.T) {
|
||||||
|
b := All
|
||||||
|
|
||||||
|
obj := FooStructHeaderUriQueryBody{}
|
||||||
|
req := requestWithBody(http.MethodPost, "/", `bar=spam`)
|
||||||
|
req.Header.Set("Limit", "100")
|
||||||
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
|
m := map[string][]string{"id": {"1234"}}
|
||||||
|
err := b.BindMany(req, m, &obj)
|
||||||
|
require.ErrorContains(t, err, "validation for 'Foo' failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingAllHeaderUriQueryBody_FailWhenBodyMissing(t *testing.T) {
|
||||||
|
b := All
|
||||||
|
|
||||||
|
obj := FooStructHeaderUriQueryBody{}
|
||||||
|
req := requestWithBody(http.MethodPost, "/?foo=true", `xxx=spam`)
|
||||||
|
req.Header.Set("Limit", "100")
|
||||||
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
|
m := map[string][]string{"id": {"1234"}}
|
||||||
|
err := b.BindMany(req, m, &obj)
|
||||||
|
require.ErrorContains(t, err, "validation for 'Bar' failed")
|
||||||
|
}
|
||||||
|
|
||||||
func requestWithBody(method, path, body string) (req *http.Request) {
|
func requestWithBody(method, path, body string) (req *http.Request) {
|
||||||
req, _ = http.NewRequest(method, path, bytes.NewBufferString(body))
|
req, _ = http.NewRequest(method, path, bytes.NewBufferString(body))
|
||||||
return
|
return
|
||||||
|
|||||||
32
context.go
32
context.go
@ -804,6 +804,22 @@ func (c *Context) BindUri(obj any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BindAll binds the passed struct pointer using all available binding engines.
|
||||||
|
// It will abort the request with HTTP 400 if any error occurs.
|
||||||
|
//
|
||||||
|
// Note:
|
||||||
|
// - Caller must tag struct fields appropriately for the desired binding (eg `header:"xxx"` vs `uri:"xxx"`)
|
||||||
|
// - Caller must ensure no duplication between field names (else use separate binding engines instead)
|
||||||
|
// - Caller must provide Content-Type header to select the correct body binding (eg "application/json" for JSON binding)
|
||||||
|
// - Binding validation tags are verified after all request parts have been bound
|
||||||
|
func (c *Context) BindAll(obj any) error {
|
||||||
|
if err := c.ShouldBindAll(obj); err != nil {
|
||||||
|
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// MustBindWith binds the passed struct pointer using the specified binding engine.
|
// MustBindWith binds the passed struct pointer using the specified binding engine.
|
||||||
// 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.
|
||||||
@ -914,6 +930,22 @@ func (c *Context) ShouldBindUri(obj any) error {
|
|||||||
return binding.Uri.BindUri(m, obj)
|
return binding.Uri.BindUri(m, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShouldBindAll binds the passed struct pointer using all the available binding engines.
|
||||||
|
// Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid.
|
||||||
|
//
|
||||||
|
// Note:
|
||||||
|
// - Caller must tag struct fields appropriately for the desired binding (eg `header:"xxx"` vs `uri:"xxx"`)
|
||||||
|
// - Caller must ensure no duplication between field names (else use separate binding engines instead)
|
||||||
|
// - Caller must provide Content-Type header to select the correct body binding (eg "application/json" for JSON binding)
|
||||||
|
// - Binding validation tags are verified after all request parts have been bound
|
||||||
|
func (c *Context) ShouldBindAll(obj any) error {
|
||||||
|
uriParams := make(map[string][]string, len(c.Params))
|
||||||
|
for _, v := range c.Params {
|
||||||
|
uriParams[v.Key] = []string{v.Value}
|
||||||
|
}
|
||||||
|
return binding.All.BindMany(c.Request, uriParams, obj)
|
||||||
|
}
|
||||||
|
|
||||||
// ShouldBindWith binds the passed struct pointer using the specified binding engine.
|
// ShouldBindWith binds the passed struct pointer using the specified binding engine.
|
||||||
// See the binding package.
|
// See the binding package.
|
||||||
func (c *Context) ShouldBindWith(obj any, b binding.Binding) error {
|
func (c *Context) ShouldBindWith(obj any, b binding.Binding) error {
|
||||||
|
|||||||
@ -2312,6 +2312,47 @@ func TestContextBindWithTOML(t *testing.T) {
|
|||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextBindAll(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest(http.MethodPost, "/1234?foo=true", strings.NewReader(`{"bar":"spam"}`))
|
||||||
|
c.Params = []Param{{Key: "id", Value: "1234"}}
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEJSON) // set fake content-type
|
||||||
|
c.Request.Header.Add("Limit", "100")
|
||||||
|
|
||||||
|
var obj struct {
|
||||||
|
Limit int `header:"limit" binding:"required"`
|
||||||
|
ID string `uri:"id" binding:"required,numeric"`
|
||||||
|
Foo bool `form:"foo" binding:"required"`
|
||||||
|
Bar string `form:"bar" xml:"bar" binding:"required"`
|
||||||
|
}
|
||||||
|
require.NoError(t, c.BindAll(&obj))
|
||||||
|
assert.Equal(t, 100, obj.Limit)
|
||||||
|
assert.Equal(t, "1234", obj.ID)
|
||||||
|
assert.Equal(t, "true", c.Request.FormValue("foo"))
|
||||||
|
assert.Equal(t, "spam", obj.Bar)
|
||||||
|
assert.Empty(t, c.Errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextBindAll_400OnError(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest(http.MethodPost, "/1234?foo=true", strings.NewReader(`{"bar":"spam"}`))
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEJSON) // set fake content-type
|
||||||
|
c.Request.Header.Add("Limit", "100")
|
||||||
|
|
||||||
|
var obj struct {
|
||||||
|
Limit int `header:"limit" binding:"required"`
|
||||||
|
ID string `uri:"id" binding:"required,numeric"`
|
||||||
|
Foo bool `form:"foo" binding:"required"`
|
||||||
|
Bar string `form:"bar" xml:"bar" binding:"required"`
|
||||||
|
}
|
||||||
|
err := c.BindAll(&obj)
|
||||||
|
require.ErrorContains(t, err, "Field validation for 'ID' failed")
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextBadAutoBind(t *testing.T) {
|
func TestContextBadAutoBind(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
@ -2486,6 +2527,29 @@ func TestContextShouldBindWithTOML(t *testing.T) {
|
|||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextShouldBindAll(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest(http.MethodPost, "/1234?foo=true", strings.NewReader(`{"bar":"spam"}`))
|
||||||
|
c.Params = []Param{{Key: "id", Value: "1234"}}
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEJSON) // set fake content-type
|
||||||
|
c.Request.Header.Add("Limit", "100")
|
||||||
|
|
||||||
|
var obj struct {
|
||||||
|
Limit int `header:"limit" binding:"required"`
|
||||||
|
ID string `uri:"id" binding:"required,numeric"`
|
||||||
|
Foo bool `form:"foo" binding:"required"`
|
||||||
|
Bar string `form:"bar" xml:"bar" binding:"required"`
|
||||||
|
}
|
||||||
|
require.NoError(t, c.ShouldBindAll(&obj))
|
||||||
|
assert.Equal(t, 100, obj.Limit)
|
||||||
|
assert.Equal(t, "1234", obj.ID)
|
||||||
|
assert.Equal(t, "true", c.Request.FormValue("foo"))
|
||||||
|
assert.Equal(t, "spam", obj.Bar)
|
||||||
|
assert.Empty(t, c.Errors)
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextBadAutoShouldBind(t *testing.T) {
|
func TestContextBadAutoShouldBind(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
|||||||
45
docs/doc.md
45
docs/doc.md
@ -44,6 +44,7 @@
|
|||||||
- [Bind HTML checkboxes](#bind-html-checkboxes)
|
- [Bind HTML checkboxes](#bind-html-checkboxes)
|
||||||
- [Multipart/Urlencoded binding](#multiparturlencoded-binding)
|
- [Multipart/Urlencoded binding](#multiparturlencoded-binding)
|
||||||
- [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
|
- [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
|
||||||
|
- [Bind All](#bind-all)
|
||||||
- [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
|
- [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
|
||||||
- [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag)
|
- [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag)
|
||||||
- [Response Rendering](#response-rendering)
|
- [Response Rendering](#response-rendering)
|
||||||
@ -1541,6 +1542,50 @@ $ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
|
|||||||
{"d":"world","x":{"FieldX":"hello"}}
|
{"d":"world","x":{"FieldX":"hello"}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Bind All
|
||||||
|
|
||||||
|
BindAll will bind header, uri, query parameters, and body in one call. Validation is only applied after request parts are bound.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Person struct {
|
||||||
|
Age int `header:"x-age" binding:"required"`
|
||||||
|
ID string `uri:"id" binding:"required,uuid"`
|
||||||
|
Name string `form:"name" binding:"required"`
|
||||||
|
Addresses [2]string `json:"addresses" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
route := gin.Default()
|
||||||
|
route.POST("/:id", func(c *gin.Context) {
|
||||||
|
var person Person
|
||||||
|
if err := c.ShouldBindAll(&person); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, person)
|
||||||
|
})
|
||||||
|
route.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Test it with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -X POST -H "x-age:25" -H "Content-Type: application/json" -d '{"addresses":["foo","bar"]}' 'http://localhost:8080/987fbc97-4bed-5078-9f07-9141ba07c9f3?name=Bob'
|
||||||
|
# {"Age":25,"ID":"987fbc97-4bed-5078-9f07-9141ba07c9f3","Name":"Bob","addresses":["foo","bar"]}
|
||||||
|
|
||||||
|
curl -X POST -H "x-age:25" -H "Content-Type: application/json" -d '{"addresses":["foo","bar"]}' 'http://localhost:8080/not-uuid'
|
||||||
|
# {"msg":"Key: 'Person.ID' Error:Field validation for 'ID' failed on the 'uuid' tag\nKey: 'Person.Name' Error:Field validation for 'Name' failed on the 'required' tag"}
|
||||||
|
```
|
||||||
|
|
||||||
### Try to bind body into different structs
|
### Try to bind body into different structs
|
||||||
|
|
||||||
The normal methods for binding request body consumes `c.Request.Body` and they
|
The normal methods for binding request body consumes `c.Request.Body` and they
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user