add basic nested binding support (not yet for slices)

bind
Muhammed Efe Çetin 2022-11-17 16:00:33 +03:00
parent d52652ef83
commit 6cb876a51b
No known key found for this signature in database
GPG Key ID: 0AA4D45CBAA86F73
3 changed files with 102 additions and 18 deletions

26
bind.go
View File

@ -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)
} }

View File

@ -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()

View File

@ -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
} }