mirror of https://github.com/gofiber/fiber.git
add basic nested binding support (not yet for slices)
parent
d52652ef83
commit
6cb876a51b
26
bind.go
26
bind.go
|
@ -36,12 +36,13 @@ func (d *fieldCtxDecoder) Decode(ctx Ctx, reqValue reflect.Value) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type fieldTextDecoder struct {
|
type fieldTextDecoder struct {
|
||||||
index int
|
index int
|
||||||
fieldName string
|
parentIndex []int
|
||||||
tag string // query,param,header,respHeader ...
|
fieldName string
|
||||||
reqField string
|
tag string // query,param,header,respHeader ...
|
||||||
dec bind.TextDecoder
|
reqField string
|
||||||
get func(c Ctx, key string, defaultValue ...string) string
|
dec bind.TextDecoder
|
||||||
|
get func(c Ctx, key string, defaultValue ...string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *fieldTextDecoder) Decode(ctx Ctx, reqValue reflect.Value) error {
|
func (d *fieldTextDecoder) Decode(ctx Ctx, reqValue reflect.Value) error {
|
||||||
|
@ -50,7 +51,18 @@ func (d *fieldTextDecoder) Decode(ctx Ctx, reqValue reflect.Value) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := d.dec.UnmarshalString(text, reqValue.Field(d.index))
|
var err error
|
||||||
|
if len(d.parentIndex) > 0 {
|
||||||
|
for _, i := range d.parentIndex {
|
||||||
|
reqValue = reqValue.Field(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.dec.UnmarshalString(text, reqValue.Field(d.index))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
err = d.dec.UnmarshalString(text, reqValue.Field(d.index))
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to decode '%s' as %s: %w", text, d.reqField, err)
|
return fmt.Errorf("unable to decode '%s' as %s: %w", text, d.reqField, err)
|
||||||
}
|
}
|
||||||
|
|
26
bind_test.go
26
bind_test.go
|
@ -38,6 +38,32 @@ func Test_Binder(t *testing.T) {
|
||||||
require.Equal(t, "john doe", body.Name)
|
require.Equal(t, "john doe", body.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_Binder_Nested(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New()
|
||||||
|
|
||||||
|
c := app.NewCtx(&fasthttp.RequestCtx{}).(*DefaultCtx)
|
||||||
|
c.Request().SetBody([]byte(``))
|
||||||
|
c.Request().Header.SetContentType("")
|
||||||
|
c.Request().URI().SetQueryString("name=tom&nested.and.age=10&nested.and.test=john")
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
Name string `query:"name"`
|
||||||
|
Nested struct {
|
||||||
|
And struct {
|
||||||
|
Age int `query:"age"`
|
||||||
|
Test string `query:"test"`
|
||||||
|
} `query:"and"`
|
||||||
|
} `query:"nested"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.Bind().Req(&req).Err()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "tom", req.Name)
|
||||||
|
require.Equal(t, "john", req.Nested.And.Test)
|
||||||
|
require.Equal(t, 10, req.Nested.And.Age)
|
||||||
|
}
|
||||||
|
|
||||||
// go test -run Test_Bind_BasicType -v
|
// go test -run Test_Bind_BasicType -v
|
||||||
func Test_Bind_BasicType(t *testing.T) {
|
func Test_Bind_BasicType(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
|
@ -45,13 +45,13 @@ func compileReqParser(rt reflect.Type, opt bindCompileOption) (Decoder, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
dec, err := compileFieldDecoder(el.Field(i), i, opt)
|
dec, err := compileFieldDecoder(el.Field(i), i, opt, parentStruct{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if dec != nil {
|
if dec != nil {
|
||||||
decoders = append(decoders, dec)
|
decoders = append(decoders, dec...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,9 +67,14 @@ func compileReqParser(rt reflect.Type, opt bindCompileOption) (Decoder, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func compileFieldDecoder(field reflect.StructField, index int, opt bindCompileOption) (decoder, error) {
|
type parentStruct struct {
|
||||||
|
tag string
|
||||||
|
index []int
|
||||||
|
}
|
||||||
|
|
||||||
|
func compileFieldDecoder(field reflect.StructField, index int, opt bindCompileOption, parent parentStruct) ([]decoder, error) {
|
||||||
if reflect.PtrTo(field.Type).Implements(bindUnmarshalerType) {
|
if reflect.PtrTo(field.Type).Implements(bindUnmarshalerType) {
|
||||||
return &fieldCtxDecoder{index: index, fieldName: field.Name, fieldType: field.Type}, nil
|
return []decoder{&fieldCtxDecoder{index: index, fieldName: field.Name, fieldType: field.Type}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var tags = []string{bindTagRespHeader, bindTagQuery, bindTagParam, bindTagHeader, bindTagCookie}
|
var tags = []string{bindTagRespHeader, bindTagQuery, bindTagParam, bindTagHeader, bindTagCookie}
|
||||||
|
@ -91,6 +96,10 @@ func compileFieldDecoder(field reflect.StructField, index int, opt bindCompileOp
|
||||||
|
|
||||||
tagContent := field.Tag.Get(tagScope)
|
tagContent := field.Tag.Get(tagScope)
|
||||||
|
|
||||||
|
if parent.tag != "" {
|
||||||
|
tagContent = parent.tag + "." + tagContent
|
||||||
|
}
|
||||||
|
|
||||||
if reflect.PtrTo(field.Type).Implements(textUnmarshalerType) {
|
if reflect.PtrTo(field.Type).Implements(textUnmarshalerType) {
|
||||||
return compileTextBasedDecoder(field, index, tagScope, tagContent)
|
return compileTextBasedDecoder(field, index, tagScope, tagContent)
|
||||||
}
|
}
|
||||||
|
@ -99,7 +108,38 @@ func compileFieldDecoder(field reflect.StructField, index int, opt bindCompileOp
|
||||||
return compileSliceFieldTextBasedDecoder(field, index, tagScope, tagContent)
|
return compileSliceFieldTextBasedDecoder(field, index, tagScope, tagContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
return compileTextBasedDecoder(field, index, tagScope, tagContent)
|
// Nested binding support
|
||||||
|
if field.Type.Kind() == reflect.Struct {
|
||||||
|
var decoders []decoder
|
||||||
|
el := field.Type
|
||||||
|
|
||||||
|
for i := 0; i < el.NumField(); i++ {
|
||||||
|
if !el.Field(i).IsExported() {
|
||||||
|
// ignore unexported field
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var indexes []int
|
||||||
|
if len(parent.index) > 0 {
|
||||||
|
indexes = append(indexes, parent.index...)
|
||||||
|
}
|
||||||
|
indexes = append(indexes, index)
|
||||||
|
dec, err := compileFieldDecoder(el.Field(i), i, opt, parentStruct{
|
||||||
|
tag: tagContent,
|
||||||
|
index: indexes,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if dec != nil {
|
||||||
|
decoders = append(decoders, dec...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoders, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return compileTextBasedDecoder(field, index, tagScope, tagContent, parent.index)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formGetter(ctx Ctx, key string, defaultValue ...string) string {
|
func formGetter(ctx Ctx, key string, defaultValue ...string) string {
|
||||||
|
@ -120,7 +160,7 @@ func multipartGetter(ctx Ctx, key string, defaultValue ...string) string {
|
||||||
return v[0]
|
return v[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func compileTextBasedDecoder(field reflect.StructField, index int, tagScope, tagContent string) (decoder, error) {
|
func compileTextBasedDecoder(field reflect.StructField, index int, tagScope, tagContent string, parentIndex ...[]int) ([]decoder, error) {
|
||||||
var get func(ctx Ctx, key string, defaultValue ...string) string
|
var get func(ctx Ctx, key string, defaultValue ...string) string
|
||||||
switch tagScope {
|
switch tagScope {
|
||||||
case bindTagQuery:
|
case bindTagQuery:
|
||||||
|
@ -146,17 +186,23 @@ func compileTextBasedDecoder(field reflect.StructField, index int, tagScope, tag
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &fieldTextDecoder{
|
fieldDecoder := &fieldTextDecoder{
|
||||||
index: index,
|
index: index,
|
||||||
fieldName: field.Name,
|
fieldName: field.Name,
|
||||||
tag: tagScope,
|
tag: tagScope,
|
||||||
reqField: tagContent,
|
reqField: tagContent,
|
||||||
dec: textDecoder,
|
dec: textDecoder,
|
||||||
get: get,
|
get: get,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
if len(parentIndex) > 0 {
|
||||||
|
fieldDecoder.parentIndex = parentIndex[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return []decoder{fieldDecoder}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func compileSliceFieldTextBasedDecoder(field reflect.StructField, index int, tagScope string, tagContent string) (decoder, error) {
|
func compileSliceFieldTextBasedDecoder(field reflect.StructField, index int, tagScope string, tagContent string) ([]decoder, error) {
|
||||||
if field.Type.Kind() != reflect.Slice {
|
if field.Type.Kind() != reflect.Slice {
|
||||||
panic("BUG: unexpected type, expecting slice " + field.Type.String())
|
panic("BUG: unexpected type, expecting slice " + field.Type.String())
|
||||||
}
|
}
|
||||||
|
@ -190,7 +236,7 @@ func compileSliceFieldTextBasedDecoder(field reflect.StructField, index int, tag
|
||||||
return nil, errors.New("unexpected tag scope " + strconv.Quote(tagScope))
|
return nil, errors.New("unexpected tag scope " + strconv.Quote(tagScope))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &fieldSliceDecoder{
|
return []decoder{&fieldSliceDecoder{
|
||||||
fieldIndex: index,
|
fieldIndex: index,
|
||||||
eqBytes: eqBytes,
|
eqBytes: eqBytes,
|
||||||
fieldName: field.Name,
|
fieldName: field.Name,
|
||||||
|
@ -199,5 +245,5 @@ func compileSliceFieldTextBasedDecoder(field reflect.StructField, index int, tag
|
||||||
fieldType: field.Type,
|
fieldType: field.Type,
|
||||||
elementType: et,
|
elementType: et,
|
||||||
elementDecoder: elementUnmarshaler,
|
elementDecoder: elementUnmarshaler,
|
||||||
}, nil
|
}}, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue