mirror of
https://github.com/gin-gonic/gin.git
synced 2025-04-24 10:20:28 +08:00
Merge 842bd597a8d668e7065b179280571eac2f616f0d into 8763f33c65f7df8be5b9fe7504ab7fcf20abb41d
This commit is contained in:
commit
1ff750e1d1
@ -5,6 +5,7 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
@ -136,6 +137,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
|
|||||||
type setOptions struct {
|
type setOptions struct {
|
||||||
isDefaultExists bool
|
isDefaultExists bool
|
||||||
defaultValue string
|
defaultValue string
|
||||||
|
parser string
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
|
func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
|
||||||
@ -167,6 +169,8 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter
|
|||||||
setOpt.defaultValue = strings.ReplaceAll(v, ";", ",")
|
setOpt.defaultValue = strings.ReplaceAll(v, ";", ",")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if k, v = head(opt, "="); k == "parser" {
|
||||||
|
setOpt.parser = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,6 +194,20 @@ func trySetCustom(val string, value reflect.Value) (isSet bool, err error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// trySetUsingParser tries to set a custom type value based on the presence of the "parser" tag on the field.
|
||||||
|
// If the parser tag does not exist or does not match any of the supported parsers, gin will skip over this.
|
||||||
|
func trySetUsingParser(val string, value reflect.Value, parser string) (isSet bool, err error) {
|
||||||
|
switch parser {
|
||||||
|
case "encoding.TextUnmarshaler":
|
||||||
|
v, ok := value.Addr().Interface().(encoding.TextUnmarshaler)
|
||||||
|
if !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, v.UnmarshalText([]byte(val))
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func trySplit(vs []string, field reflect.StructField) (newVs []string, err error) {
|
func trySplit(vs []string, field reflect.StructField) (newVs []string, err error) {
|
||||||
cfTag := field.Tag.Get("collection_format")
|
cfTag := field.Tag.Get("collection_format")
|
||||||
if cfTag == "" || cfTag == "multi" {
|
if cfTag == "" || cfTag == "multi" {
|
||||||
@ -207,7 +225,7 @@ func trySplit(vs []string, field reflect.StructField) (newVs []string, err error
|
|||||||
case "pipes":
|
case "pipes":
|
||||||
sep = "|"
|
sep = "|"
|
||||||
default:
|
default:
|
||||||
return vs, fmt.Errorf("%s is not supported in the collection_format. (csv, ssv, pipes)", cfTag)
|
return vs, fmt.Errorf("%s is not supported in the collection_format. (multi, csv, ssv, tsv, pipes)", cfTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
totalLength := 0
|
totalLength := 0
|
||||||
@ -230,7 +248,7 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
|
|||||||
|
|
||||||
switch value.Kind() {
|
switch value.Kind() {
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
if !ok {
|
if !ok || len(vs) == 0 || (len(vs) > 0 && vs[0] == "") {
|
||||||
vs = []string{opt.defaultValue}
|
vs = []string{opt.defaultValue}
|
||||||
|
|
||||||
// pre-process the default value for multi if present
|
// pre-process the default value for multi if present
|
||||||
@ -240,7 +258,9 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok, err = trySetCustom(vs[0], value); ok {
|
if ok, err = trySetUsingParser(vs[0], value, opt.parser); ok {
|
||||||
|
return ok, err
|
||||||
|
} else if ok, err = trySetCustom(vs[0], value); ok {
|
||||||
return ok, err
|
return ok, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,9 +268,9 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, setSlice(vs, value, field)
|
return true, setSlice(vs, value, field, opt)
|
||||||
case reflect.Array:
|
case reflect.Array:
|
||||||
if !ok {
|
if !ok || len(vs) == 0 || (len(vs) > 0 && vs[0] == "") {
|
||||||
vs = []string{opt.defaultValue}
|
vs = []string{opt.defaultValue}
|
||||||
|
|
||||||
// pre-process the default value for multi if present
|
// pre-process the default value for multi if present
|
||||||
@ -260,7 +280,9 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok, err = trySetCustom(vs[0], value); ok {
|
if ok, err = trySetUsingParser(vs[0], value, opt.parser); ok {
|
||||||
|
return ok, err
|
||||||
|
} else if ok, err = trySetCustom(vs[0], value); ok {
|
||||||
return ok, err
|
return ok, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,27 +294,32 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
|
|||||||
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
|
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, setArray(vs, value, field)
|
return true, setArray(vs, value, field, opt)
|
||||||
default:
|
default:
|
||||||
var val string
|
var val string
|
||||||
if !ok {
|
if !ok || len(vs) == 0 || (len(vs) > 0 && vs[0] == "") {
|
||||||
val = opt.defaultValue
|
val = opt.defaultValue
|
||||||
|
} else if len(vs) > 0 {
|
||||||
|
val = vs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(vs) > 0 {
|
if ok, err = trySetUsingParser(val, value, opt.parser); ok {
|
||||||
val = vs[0]
|
return ok, err
|
||||||
if val == "" {
|
} else if ok, err = trySetCustom(val, value); ok {
|
||||||
val = opt.defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ok, err := trySetCustom(val, value); ok {
|
|
||||||
return ok, err
|
return ok, err
|
||||||
}
|
}
|
||||||
return true, setWithProperType(val, value, field)
|
return true, setWithProperType(val, value, field, opt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {
|
func setWithProperType(val string, value reflect.Value, field reflect.StructField, opt setOptions) error {
|
||||||
|
// this if-check is required for parsing nested types like []MyId, where MyId is [12]byte
|
||||||
|
if ok, err := trySetUsingParser(val, value, opt.parser); ok {
|
||||||
|
return err
|
||||||
|
} else if ok, err = trySetCustom(val, value); ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
switch value.Kind() {
|
switch value.Kind() {
|
||||||
case reflect.Int:
|
case reflect.Int:
|
||||||
return setIntField(val, 0, value)
|
return setIntField(val, 0, value)
|
||||||
@ -340,7 +367,7 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
|
|||||||
if !value.Elem().IsValid() {
|
if !value.Elem().IsValid() {
|
||||||
value.Set(reflect.New(value.Type().Elem()))
|
value.Set(reflect.New(value.Type().Elem()))
|
||||||
}
|
}
|
||||||
return setWithProperType(val, value.Elem(), field)
|
return setWithProperType(val, value.Elem(), field, opt)
|
||||||
default:
|
default:
|
||||||
return errUnknownType
|
return errUnknownType
|
||||||
}
|
}
|
||||||
@ -447,9 +474,9 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setArray(vals []string, value reflect.Value, field reflect.StructField) error {
|
func setArray(vals []string, value reflect.Value, field reflect.StructField, opt setOptions) error {
|
||||||
for i, s := range vals {
|
for i, s := range vals {
|
||||||
err := setWithProperType(s, value.Index(i), field)
|
err := setWithProperType(s, value.Index(i), field, opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -457,9 +484,9 @@ func setArray(vals []string, value reflect.Value, field reflect.StructField) err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSlice(vals []string, value reflect.Value, field reflect.StructField) error {
|
func setSlice(vals []string, value reflect.Value, field reflect.StructField, opt setOptions) error {
|
||||||
slice := reflect.MakeSlice(value.Type(), len(vals), len(vals))
|
slice := reflect.MakeSlice(value.Type(), len(vals), len(vals))
|
||||||
err := setArray(vals, slice, field)
|
err := setArray(vals, slice, field, opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
@ -454,44 +455,44 @@ func TestMappingIgnoredCircularRef(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
type customUnmarshalParamHex int
|
// ==== BindUmarshaler tests START ====
|
||||||
|
|
||||||
func (f *customUnmarshalParamHex) UnmarshalParam(param string) error {
|
type customHexUnmarshalParam int
|
||||||
|
|
||||||
|
func (f *customHexUnmarshalParam) UnmarshalParam(param string) error {
|
||||||
v, err := strconv.ParseInt(param, 16, 64)
|
v, err := strconv.ParseInt(param, 16, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
*f = customUnmarshalParamHex(v)
|
*f = customHexUnmarshalParam(v)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMappingCustomUnmarshalParamHexWithFormTag(t *testing.T) {
|
func TestMappingCustomHexUnmarshalParam(t *testing.T) {
|
||||||
var s struct {
|
RunMappingUsingUriAndFormTagAndAssertForUnmarshalParam[customHexUnmarshalParam](
|
||||||
Foo customUnmarshalParamHex `form:"foo"`
|
t,
|
||||||
}
|
`f5`,
|
||||||
err := mappingByPtr(&s, formSource{"foo": {`f5`}}, "form")
|
func(hex customHexUnmarshalParam, t *testing.T) {
|
||||||
require.NoError(t, err)
|
assert.EqualValues(t, 245, hex)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
assert.EqualValues(t, 245, s.Foo)
|
// verify default binding works with UnmarshalParam
|
||||||
|
var sDefaultValue struct {
|
||||||
|
Field1 customHexUnmarshalParam `form:"field1,default=f5"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&sDefaultValue, formSource{"field1": {}}, "form")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 0xf5, sDefaultValue.Field1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMappingCustomUnmarshalParamHexWithURITag(t *testing.T) {
|
type customTypeUnmarshalParam struct {
|
||||||
var s struct {
|
|
||||||
Foo customUnmarshalParamHex `uri:"foo"`
|
|
||||||
}
|
|
||||||
err := mappingByPtr(&s, formSource{"foo": {`f5`}}, "uri")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.EqualValues(t, 245, s.Foo)
|
|
||||||
}
|
|
||||||
|
|
||||||
type customUnmarshalParamType struct {
|
|
||||||
Protocol string
|
Protocol string
|
||||||
Path string
|
Path string
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *customUnmarshalParamType) UnmarshalParam(param string) error {
|
func (f *customTypeUnmarshalParam) UnmarshalParam(param string) error {
|
||||||
parts := strings.Split(param, ":")
|
parts := strings.Split(param, ":")
|
||||||
if len(parts) != 3 {
|
if len(parts) != 3 {
|
||||||
return errors.New("invalid format")
|
return errors.New("invalid format")
|
||||||
@ -502,52 +503,28 @@ func (f *customUnmarshalParamType) UnmarshalParam(param string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMappingCustomStructTypeWithFormTag(t *testing.T) {
|
func TestMappingCustomStructType(t *testing.T) {
|
||||||
var s struct {
|
RunMappingUsingUriAndFormTagAndAssertForUnmarshalParam[customTypeUnmarshalParam](
|
||||||
FileData customUnmarshalParamType `form:"data"`
|
t,
|
||||||
}
|
`file:/foo:happiness`,
|
||||||
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form")
|
func(data customTypeUnmarshalParam, t *testing.T) {
|
||||||
require.NoError(t, err)
|
assert.EqualValues(t, "file", data.Protocol)
|
||||||
|
assert.EqualValues(t, "/foo", data.Path)
|
||||||
assert.EqualValues(t, "file", s.FileData.Protocol)
|
assert.EqualValues(t, "happiness", data.Name)
|
||||||
assert.EqualValues(t, "/foo", s.FileData.Path)
|
},
|
||||||
assert.EqualValues(t, "happiness", s.FileData.Name)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMappingCustomStructTypeWithURITag(t *testing.T) {
|
func TestMappingCustomPointerStructType(t *testing.T) {
|
||||||
var s struct {
|
RunMappingUsingUriAndFormTagAndAssertForUnmarshalParam[*customTypeUnmarshalParam](
|
||||||
FileData customUnmarshalParamType `uri:"data"`
|
t,
|
||||||
}
|
`file:/foo:happiness`,
|
||||||
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri")
|
func(data *customTypeUnmarshalParam, t *testing.T) {
|
||||||
require.NoError(t, err)
|
assert.EqualValues(t, "file", data.Protocol)
|
||||||
|
assert.EqualValues(t, "/foo", data.Path)
|
||||||
assert.EqualValues(t, "file", s.FileData.Protocol)
|
assert.EqualValues(t, "happiness", data.Name)
|
||||||
assert.EqualValues(t, "/foo", s.FileData.Path)
|
},
|
||||||
assert.EqualValues(t, "happiness", s.FileData.Name)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
func TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) {
|
|
||||||
var s struct {
|
|
||||||
FileData *customUnmarshalParamType `form:"data"`
|
|
||||||
}
|
|
||||||
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.EqualValues(t, "file", s.FileData.Protocol)
|
|
||||||
assert.EqualValues(t, "/foo", s.FileData.Path)
|
|
||||||
assert.EqualValues(t, "happiness", s.FileData.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) {
|
|
||||||
var s struct {
|
|
||||||
FileData *customUnmarshalParamType `uri:"data"`
|
|
||||||
}
|
|
||||||
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.EqualValues(t, "file", s.FileData.Protocol)
|
|
||||||
assert.EqualValues(t, "/foo", s.FileData.Path)
|
|
||||||
assert.EqualValues(t, "happiness", s.FileData.Name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type customPath []string
|
type customPath []string
|
||||||
@ -563,32 +540,49 @@ func (p *customPath) UnmarshalParam(param string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMappingCustomSliceUri(t *testing.T) {
|
func TestMappingCustomSlice(t *testing.T) {
|
||||||
var s struct {
|
RunMappingUsingUriAndFormTagAndAssertForUnmarshalParam[customPath](
|
||||||
FileData customPath `uri:"path"`
|
t,
|
||||||
}
|
`bar/foo`,
|
||||||
err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "uri")
|
func(path customPath, t *testing.T) {
|
||||||
require.NoError(t, err)
|
assert.EqualValues(t, "bar", path[0])
|
||||||
|
assert.EqualValues(t, "foo", path[1])
|
||||||
assert.EqualValues(t, "bar", s.FileData[0])
|
},
|
||||||
assert.EqualValues(t, "foo", s.FileData[1])
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMappingCustomSliceForm(t *testing.T) {
|
func TestMappingCustomSliceStopsWhenError(t *testing.T) {
|
||||||
var s struct {
|
var sForm struct {
|
||||||
FileData customPath `form:"path"`
|
Field1 customPath `form:"field1"`
|
||||||
}
|
}
|
||||||
err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "form")
|
err := mappingByPtr(&sForm, formSource{"field1": {"invalid"}}, "form")
|
||||||
require.NoError(t, err)
|
require.ErrorContains(t, err, "invalid format")
|
||||||
|
require.Empty(t, sForm.Field1)
|
||||||
|
}
|
||||||
|
|
||||||
assert.EqualValues(t, "bar", s.FileData[0])
|
func TestMappingCustomSliceOfSlice(t *testing.T) {
|
||||||
assert.EqualValues(t, "foo", s.FileData[1])
|
val := `bar/foo,bar/foo/spam`
|
||||||
|
expected := []customPath{{"bar", "foo"}, {"bar", "foo", "spam"}}
|
||||||
|
|
||||||
|
var sUri struct {
|
||||||
|
Field1 []customPath `uri:"field1" collection_format:"csv"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&sUri, formSource{"field1": {val}}, "uri")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, expected, sUri.Field1)
|
||||||
|
|
||||||
|
var sForm struct {
|
||||||
|
Field1 []customPath `form:"field1" collection_format:"csv"`
|
||||||
|
}
|
||||||
|
err = mappingByPtr(&sForm, formSource{"field1": {val}}, "form")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, expected, sForm.Field1)
|
||||||
}
|
}
|
||||||
|
|
||||||
type objectID [12]byte
|
type objectID [12]byte
|
||||||
|
|
||||||
func (o *objectID) UnmarshalParam(param string) error {
|
func (o *objectID) UnmarshalParam(param string) error {
|
||||||
oid, err := convertTo(param)
|
oid, err := convertTo[objectID](param)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -597,8 +591,8 @@ func (o *objectID) UnmarshalParam(param string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertTo(s string) (objectID, error) {
|
func convertTo[T ~[12]byte](s string) (T, error) {
|
||||||
var nilObjectID objectID
|
var nilObjectID T
|
||||||
if len(s) != 24 {
|
if len(s) != 24 {
|
||||||
return nilObjectID, errors.New("invalid format")
|
return nilObjectID, errors.New("invalid format")
|
||||||
}
|
}
|
||||||
@ -612,26 +606,351 @@ func convertTo(s string) (objectID, error) {
|
|||||||
return oid, nil
|
return oid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMappingCustomArrayUri(t *testing.T) {
|
func TestMappingCustomArray(t *testing.T) {
|
||||||
var s struct {
|
RunMappingUsingUriAndFormTagAndAssertForUnmarshalParam[objectID](
|
||||||
FileData objectID `uri:"id"`
|
t,
|
||||||
}
|
`664a062ac74a8ad104e0e80f`,
|
||||||
val := `664a062ac74a8ad104e0e80f`
|
func(oid objectID, t *testing.T) {
|
||||||
err := mappingByPtr(&s, formSource{"id": {val}}, "uri")
|
expected, _ := convertTo[objectID](`664a062ac74a8ad104e0e80f`)
|
||||||
require.NoError(t, err)
|
assert.EqualValues(t, expected, oid)
|
||||||
|
},
|
||||||
expected, _ := convertTo(val)
|
)
|
||||||
assert.EqualValues(t, expected, s.FileData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMappingCustomArrayForm(t *testing.T) {
|
func TestMappingCustomArrayOfArray(t *testing.T) {
|
||||||
var s struct {
|
val := `664a062ac74a8ad104e0e80e,664a062ac74a8ad104e0e80f`
|
||||||
FileData objectID `form:"id"`
|
expected1, _ := convertTo[objectID](`664a062ac74a8ad104e0e80e`)
|
||||||
|
expected2, _ := convertTo[objectID](`664a062ac74a8ad104e0e80f`)
|
||||||
|
expected := []objectID{expected1, expected2}
|
||||||
|
|
||||||
|
var sUri struct {
|
||||||
|
Field1 []objectID `uri:"field1" collection_format:"csv"`
|
||||||
}
|
}
|
||||||
val := `664a062ac74a8ad104e0e80f`
|
err := mappingByPtr(&sUri, formSource{"field1": {val}}, "uri")
|
||||||
err := mappingByPtr(&s, formSource{"id": {val}}, "form")
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, expected, sUri.Field1)
|
||||||
|
|
||||||
|
var sForm struct {
|
||||||
|
Field1 []objectID `form:"field1" collection_format:"csv"`
|
||||||
|
}
|
||||||
|
err = mappingByPtr(&sForm, formSource{"field1": {val}}, "form")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, expected, sForm.Field1)
|
||||||
|
|
||||||
|
var sDefaultValue struct {
|
||||||
|
Field1 []objectID `form:"field1,default=664a062ac74a8ad104e0e80e;664a062ac74a8ad104e0e80f" collection_format:"csv"`
|
||||||
|
}
|
||||||
|
err = mappingByPtr(&sDefaultValue, formSource{"field1": {}}, "form")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, expected, sDefaultValue.Field1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunMappingUsingUriAndFormTagAndAssertForUnmarshalParam declares a struct with a field of the given generic type T
|
||||||
|
// and runs a mapping test using the given value for both the uri and form tag. Any asserts that should be done on the
|
||||||
|
// result are passed as a function in the last parameter.
|
||||||
|
//
|
||||||
|
// This method eliminates the need for writing duplicate tests to verify both form+uri tags for BindUnmarshaler tests
|
||||||
|
func RunMappingUsingUriAndFormTagAndAssertForUnmarshalParam[T any](
|
||||||
|
t *testing.T,
|
||||||
|
valueToBind string,
|
||||||
|
assertsToRunAfterBind func(T, *testing.T),
|
||||||
|
) {
|
||||||
|
var sUri struct {
|
||||||
|
Field1 T `uri:"field1"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&sUri, formSource{"field1": {valueToBind}}, "uri")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertsToRunAfterBind(sUri.Field1, t)
|
||||||
|
|
||||||
|
var sForm struct {
|
||||||
|
Field1 T `form:"field1"`
|
||||||
|
}
|
||||||
|
err = mappingByPtr(&sForm, formSource{"field1": {valueToBind}}, "form")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertsToRunAfterBind(sForm.Field1, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== BindUmarshaler tests END ====
|
||||||
|
|
||||||
|
// ==== TextUnmarshaler tests START ====
|
||||||
|
|
||||||
|
type customHexUnmarshalText int
|
||||||
|
|
||||||
|
func (f *customHexUnmarshalText) UnmarshalText(text []byte) error {
|
||||||
|
v, err := strconv.ParseInt(string(text), 16, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*f = customHexUnmarshalText(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify type implements TextUnmarshaler
|
||||||
|
var _ encoding.TextUnmarshaler = (*customHexUnmarshalText)(nil)
|
||||||
|
|
||||||
|
func TestMappingCustomHexUnmarshalText(t *testing.T) {
|
||||||
|
RunMappingUsingUriAndFormTagAndAssertForUnmarshalText[customHexUnmarshalText](
|
||||||
|
t,
|
||||||
|
`f5`,
|
||||||
|
func(hex customHexUnmarshalText, t *testing.T) {
|
||||||
|
assert.EqualValues(t, 245, hex)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// verify default binding works with UnmarshalText
|
||||||
|
var sDefaultValue struct {
|
||||||
|
Field1 customHexUnmarshalText `form:"field1,default=f5,parser=encoding.TextUnmarshaler"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&sDefaultValue, formSource{"field1": {}}, "form")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 0xf5, sDefaultValue.Field1)
|
||||||
|
}
|
||||||
|
|
||||||
|
type customTypeUnmarshalText struct {
|
||||||
|
Protocol string
|
||||||
|
Path string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *customTypeUnmarshalText) UnmarshalText(text []byte) error {
|
||||||
|
parts := strings.Split(string(text), ":")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return errors.New("invalid format")
|
||||||
|
}
|
||||||
|
f.Protocol = parts[0]
|
||||||
|
f.Path = parts[1]
|
||||||
|
f.Name = parts[2]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ encoding.TextUnmarshaler = (*customTypeUnmarshalText)(nil)
|
||||||
|
|
||||||
|
func TestMappingCustomStructTypeUnmarshalText(t *testing.T) {
|
||||||
|
RunMappingUsingUriAndFormTagAndAssertForUnmarshalText[customTypeUnmarshalText](
|
||||||
|
t,
|
||||||
|
`file:/foo:happiness`,
|
||||||
|
func(data customTypeUnmarshalText, t *testing.T) {
|
||||||
|
assert.EqualValues(t, "file", data.Protocol)
|
||||||
|
assert.EqualValues(t, "/foo", data.Path)
|
||||||
|
assert.EqualValues(t, "happiness", data.Name)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingCustomPointerStructTypeUnmarshalText(t *testing.T) {
|
||||||
|
RunMappingUsingUriAndFormTagAndAssertForUnmarshalText[*customTypeUnmarshalText](
|
||||||
|
t,
|
||||||
|
`file:/foo:happiness`,
|
||||||
|
func(data *customTypeUnmarshalText, t *testing.T) {
|
||||||
|
assert.EqualValues(t, "file", data.Protocol)
|
||||||
|
assert.EqualValues(t, "/foo", data.Path)
|
||||||
|
assert.EqualValues(t, "happiness", data.Name)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type customPathUnmarshalText []string
|
||||||
|
|
||||||
|
func (p *customPathUnmarshalText) UnmarshalText(text []byte) error {
|
||||||
|
elems := strings.Split(string(text), "/")
|
||||||
|
n := len(elems)
|
||||||
|
if n < 2 {
|
||||||
|
return errors.New("invalid format")
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = elems
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ encoding.TextUnmarshaler = (*customPathUnmarshalText)(nil)
|
||||||
|
|
||||||
|
func TestMappingCustomSliceUnmarshalText(t *testing.T) {
|
||||||
|
RunMappingUsingUriAndFormTagAndAssertForUnmarshalText[customPathUnmarshalText](
|
||||||
|
t,
|
||||||
|
`bar/foo`,
|
||||||
|
func(path customPathUnmarshalText, t *testing.T) {
|
||||||
|
assert.EqualValues(t, "bar", path[0])
|
||||||
|
assert.EqualValues(t, "foo", path[1])
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingCustomSliceUnmarshalTextStopsWhenError(t *testing.T) {
|
||||||
|
var sForm struct {
|
||||||
|
Field1 customPathUnmarshalText `form:"field1,parser=encoding.TextUnmarshaler"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&sForm, formSource{"field1": {"invalid"}}, "form")
|
||||||
|
require.ErrorContains(t, err, "invalid format")
|
||||||
|
require.Empty(t, sForm.Field1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingCustomSliceOfSliceUnmarshalText(t *testing.T) {
|
||||||
|
val := `bar/foo,bar/foo/spam`
|
||||||
|
expected := []customPathUnmarshalText{{"bar", "foo"}, {"bar", "foo", "spam"}}
|
||||||
|
|
||||||
|
var sUri struct {
|
||||||
|
Field1 []customPathUnmarshalText `uri:"field1,parser=encoding.TextUnmarshaler" collection_format:"csv"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&sUri, formSource{"field1": {val}}, "uri")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, expected, sUri.Field1)
|
||||||
|
|
||||||
|
var sForm struct {
|
||||||
|
Field1 []customPathUnmarshalText `form:"field1,parser=encoding.TextUnmarshaler" collection_format:"csv"`
|
||||||
|
}
|
||||||
|
err = mappingByPtr(&sForm, formSource{"field1": {val}}, "form")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, expected, sForm.Field1)
|
||||||
|
|
||||||
|
var sDefaultValue struct {
|
||||||
|
Field1 []customPathUnmarshalText `form:"field1,default=bar/foo;bar/foo/spam,parser=encoding.TextUnmarshaler" collection_format:"csv"`
|
||||||
|
}
|
||||||
|
err = mappingByPtr(&sDefaultValue, formSource{"field1": {}}, "form")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, expected, sDefaultValue.Field1)
|
||||||
|
}
|
||||||
|
|
||||||
|
type objectIDUnmarshalText [12]byte
|
||||||
|
|
||||||
|
func (o *objectIDUnmarshalText) UnmarshalText(text []byte) error {
|
||||||
|
oid, err := convertTo[objectIDUnmarshalText](string(text))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*o = oid
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ encoding.TextUnmarshaler = (*objectIDUnmarshalText)(nil)
|
||||||
|
|
||||||
|
func TestMappingCustomArrayUnmarshalText(t *testing.T) {
|
||||||
|
RunMappingUsingUriAndFormTagAndAssertForUnmarshalText[objectIDUnmarshalText](
|
||||||
|
t,
|
||||||
|
`664a062ac74a8ad104e0e80f`,
|
||||||
|
func(oid objectIDUnmarshalText, t *testing.T) {
|
||||||
|
expected, _ := convertTo[objectIDUnmarshalText](`664a062ac74a8ad104e0e80f`)
|
||||||
|
assert.EqualValues(t, expected, oid)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingCustomArrayOfArrayUnmarshalText(t *testing.T) {
|
||||||
|
val := `664a062ac74a8ad104e0e80e,664a062ac74a8ad104e0e80f`
|
||||||
|
expected1, _ := convertTo[objectIDUnmarshalText](`664a062ac74a8ad104e0e80e`)
|
||||||
|
expected2, _ := convertTo[objectIDUnmarshalText](`664a062ac74a8ad104e0e80f`)
|
||||||
|
expected := []objectIDUnmarshalText{expected1, expected2}
|
||||||
|
|
||||||
|
var sUri struct {
|
||||||
|
Field1 []objectIDUnmarshalText `uri:"field1,parser=encoding.TextUnmarshaler" collection_format:"csv"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&sUri, formSource{"field1": {val}}, "uri")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, expected, sUri.Field1)
|
||||||
|
|
||||||
|
var sForm struct {
|
||||||
|
Field1 []objectIDUnmarshalText `form:"field1,parser=encoding.TextUnmarshaler" collection_format:"csv"`
|
||||||
|
}
|
||||||
|
err = mappingByPtr(&sForm, formSource{"field1": {val}}, "form")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, expected, sForm.Field1)
|
||||||
|
|
||||||
|
var sDefaultValue struct {
|
||||||
|
Field1 []objectIDUnmarshalText `form:"field1,default=664a062ac74a8ad104e0e80e;664a062ac74a8ad104e0e80f,parser=encoding.TextUnmarshaler" collection_format:"csv"`
|
||||||
|
}
|
||||||
|
err = mappingByPtr(&sDefaultValue, formSource{"field1": {}}, "form")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, expected, sDefaultValue.Field1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunMappingUsingUriAndFormTagAndAssertForUnmarshalText declares a struct with a field of the given generic type T
|
||||||
|
// and runs a mapping test using the given value for both the uri and form tag. Any asserts that should be done on the
|
||||||
|
// result are passed as a function in the last parameter.
|
||||||
|
//
|
||||||
|
// This method eliminates the need for writing duplicate tests to verify both form+uri tags for TextUnmarshaler tests
|
||||||
|
func RunMappingUsingUriAndFormTagAndAssertForUnmarshalText[T any](
|
||||||
|
t *testing.T,
|
||||||
|
valueToBind string,
|
||||||
|
assertsToRunAfterBind func(T, *testing.T),
|
||||||
|
) {
|
||||||
|
var sUri struct {
|
||||||
|
Field1 T `uri:"field1,parser=encoding.TextUnmarshaler"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&sUri, formSource{"field1": {valueToBind}}, "uri")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertsToRunAfterBind(sUri.Field1, t)
|
||||||
|
|
||||||
|
var sForm struct {
|
||||||
|
Field1 T `form:"field1,parser=encoding.TextUnmarshaler"`
|
||||||
|
}
|
||||||
|
err = mappingByPtr(&sForm, formSource{"field1": {valueToBind}}, "form")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertsToRunAfterBind(sForm.Field1, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If someone specifies parser=TextUnmarshaler and it's not defined for the type, gin should revert to using its default
|
||||||
|
// binding logic.
|
||||||
|
func TestMappingUsingBindUnmarshalerAndTextUnmarshalerWhenOnlyBindUnmarshalerDefined(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Hex customHexUnmarshalParam `form:"hex"`
|
||||||
|
HexByUnmarshalText customHexUnmarshalParam `form:"hex2,parser=encoding.TextUnmarshaler"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{
|
||||||
|
"hex": {`f5`},
|
||||||
|
"hex2": {`f5`},
|
||||||
|
}, "form")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expected, _ := convertTo(val)
|
assert.EqualValues(t, 0xf5, s.Hex)
|
||||||
assert.EqualValues(t, expected, s.FileData)
|
assert.EqualValues(t, 0xf5, s.HexByUnmarshalText) // reverts to BindUnmarshaler binding
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If someone does not specify parser=TextUnmarshaler even when it's defined for the type, gin should ignore the
|
||||||
|
// UnmarshalText logic and continue using its default binding logic. (This ensures gin does not break backwards
|
||||||
|
// compatibility)
|
||||||
|
func TestMappingUsingBindUnmarshalerAndTextUnmarshalerWhenOnlyTextUnmarshalerDefined(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Hex customHexUnmarshalText `form:"hex"`
|
||||||
|
HexByUnmarshalText customHexUnmarshalText `form:"hex2,parser=encoding.TextUnmarshaler"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{
|
||||||
|
"hex": {`11`},
|
||||||
|
"hex2": {`11`},
|
||||||
|
}, "form")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualValues(t, 11, s.Hex) // this is using default int binding, not our custom hex binding. 0x11 should be 17 in decimal
|
||||||
|
assert.EqualValues(t, 0x11, s.HexByUnmarshalText) // correct expected value for hex binding
|
||||||
|
}
|
||||||
|
|
||||||
|
type customHexUnmarshalParamAndUnmarshalText int
|
||||||
|
|
||||||
|
func (f *customHexUnmarshalParamAndUnmarshalText) UnmarshalParam(param string) error {
|
||||||
|
return errors.New("should not be called in unit test if parser tag present")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *customHexUnmarshalParamAndUnmarshalText) UnmarshalText(text []byte) error {
|
||||||
|
v, err := strconv.ParseInt(string(text), 16, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*f = customHexUnmarshalParamAndUnmarshalText(v)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a type has both UnmarshalParam and UnmarshalText methods defined, but the parser tag is set to TextUnmarshaler,
|
||||||
|
// then only the UnmarshalText method should be invoked.
|
||||||
|
func TestMappingUsingTextUnmarshalerWhenBindUnmarshalerAlsoDefined(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Hex customHexUnmarshalParamAndUnmarshalText `form:"hex,parser=encoding.TextUnmarshaler"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{
|
||||||
|
"hex": {`f5`},
|
||||||
|
}, "form")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualValues(t, 0xf5, s.Hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== TextUnmarshaler tests END ====
|
||||||
|
76
docs/doc.md
76
docs/doc.md
@ -910,7 +910,7 @@ curl -X POST http://localhost:8080/person
|
|||||||
|
|
||||||
NOTE: For default [collection values](#collection-format-for-arrays), the following rules apply:
|
NOTE: For default [collection values](#collection-format-for-arrays), the following rules apply:
|
||||||
- Since commas are used to delimit tag options, they are not supported within a default value and will result in undefined behavior
|
- Since commas are used to delimit tag options, they are not supported within a default value and will result in undefined behavior
|
||||||
- For the collection formats "multi" and "csv", a semicolon should be used in place of a comma to delimited default values
|
- For the collection formats "multi" and "csv", a semicolon should be used in place of a comma to delimit default values
|
||||||
- Since semicolons are used to delimit default values for "multi" and "csv", they are not supported within a default value for "multi" and "csv"
|
- Since semicolons are used to delimit default values for "multi" and "csv", they are not supported within a default value for "multi" and "csv"
|
||||||
|
|
||||||
|
|
||||||
@ -1008,12 +1008,68 @@ curl -v localhost:8088/thinkerou/not-uuid
|
|||||||
|
|
||||||
### Bind custom unmarshaler
|
### Bind custom unmarshaler
|
||||||
|
|
||||||
|
To override gin's default binding logic, define a function on your type that satisfies the `encoding.TextUnmarshaler` interface from the Golang standard library. Then specify `parser=encoding.TextUnmarshaler` in the `uri`/`form` tag of the field being bound.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"encoding"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Birthday string
|
||||||
|
|
||||||
|
func (b *Birthday) UnmarshalText(text []byte) error {
|
||||||
|
*b = Birthday(strings.Replace(string(text), "-", "/", -1))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ encoding.TextUnmarshaler = (*Birthday)(nil) //assert Birthday implements encoding.TextUnmarshaler
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
route := gin.Default()
|
||||||
|
var request struct {
|
||||||
|
Birthday Birthday `form:"birthday,parser=encoding.TextUnmarshaler"`
|
||||||
|
Birthdays []Birthday `form:"birthdays,parser=encoding.TextUnmarshaler" collection_format:"csv"`
|
||||||
|
BirthdaysDefault []Birthday `form:"birthdaysDef,default=2020-09-01;2020-09-02,parser=encoding.TextUnmarshaler" collection_format:"csv"`
|
||||||
|
}
|
||||||
|
route.GET("/test", func(ctx *gin.Context) {
|
||||||
|
_ = ctx.BindQuery(&request)
|
||||||
|
ctx.JSON(200, request)
|
||||||
|
})
|
||||||
|
_ = route.Run(":8088")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Test it with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl 'localhost:8088/test?birthday=2000-01-01&birthdays=2000-01-01,2000-01-02'
|
||||||
|
```
|
||||||
|
Result
|
||||||
|
```sh
|
||||||
|
{"Birthday":"2000/01/01","Birthdays":["2000/01/01","2000/01/02"],"BirthdaysDefault":["2020/09/01","2020/09/02"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Note:
|
||||||
|
- If `parser=encoding.TextUnmarshaler` is specified for a type that does **not** implement `encoding.TextUnmarshaler`, gin will ignore it and proceed with its default binding logic.
|
||||||
|
- If `parser=encoding.TextUnmarshaler` is specified for a type and that type's implementation of `encoding.TextUnmarshaler` returns an error, gin will stop binding and return the error to the client.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
If a type implements `encoding.TextUnmarshaler` but you still want to customize how gin binds the type separately (eg to change what error message is returned), you can implement the dedicated `BindUnmarshaler` interface provided by gin.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Birthday string
|
type Birthday string
|
||||||
@ -1023,29 +1079,37 @@ func (b *Birthday) UnmarshalParam(param string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ binding.BindUnmarshaler = (*Birthday)(nil) //assert Birthday implements binding.BindUnmarshaler
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
route := gin.Default()
|
route := gin.Default()
|
||||||
var request struct {
|
var request struct {
|
||||||
Birthday Birthday `form:"birthday"`
|
Birthday Birthday `form:"birthday"`
|
||||||
|
Birthdays []Birthday `form:"birthdays" collection_format:"csv"`
|
||||||
|
BirthdaysDefault []Birthday `form:"birthdaysDef,default=2020-09-01;2020-09-02" collection_format:"csv"`
|
||||||
}
|
}
|
||||||
route.GET("/test", func(ctx *gin.Context) {
|
route.GET("/test", func(ctx *gin.Context) {
|
||||||
_ = ctx.BindQuery(&request)
|
_ = ctx.BindQuery(&request)
|
||||||
ctx.JSON(200, request.Birthday)
|
ctx.JSON(200, request)
|
||||||
})
|
})
|
||||||
route.Run(":8088")
|
_ = route.Run(":8088")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Test it with:
|
Test it with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl 'localhost:8088/test?birthday=2000-01-01'
|
curl 'localhost:8088/test?birthday=2000-01-01&birthdays=2000-01-01,2000-01-02'
|
||||||
```
|
```
|
||||||
Result
|
Result
|
||||||
```sh
|
```sh
|
||||||
"2000/01/01"
|
{"Birthday":"2000/01/01","Birthdays":["2000/01/01","2000/01/02"],"BirthdaysDefault":["2020/09/01","2020/09/02"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note:
|
||||||
|
- If a type implements both `encoding.TextUnmarshaler` and `BindUnmarshaler`, gin will use `BindUnmarshaler` by default unless you specify `parser=encoding.TextUnmarshaler` in the binding tag.
|
||||||
|
- If a type returns an error from its implementation of `BindUnmarshaler`, gin will stop binding and return the error to the client.
|
||||||
|
|
||||||
### Bind Header
|
### Bind Header
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
Loading…
x
Reference in New Issue
Block a user