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 {
index int
fieldName string
tag string // query,param,header,respHeader ...
reqField string
dec bind.TextDecoder
get func(c Ctx, key string, defaultValue ...string) string
index int
parentIndex []int
fieldName string
tag string // query,param,header,respHeader ...
reqField string
dec bind.TextDecoder
get func(c Ctx, key string, defaultValue ...string) string
}
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
}
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 {
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)
}
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
func Test_Bind_BasicType(t *testing.T) {
t.Parallel()

View File

@ -45,13 +45,13 @@ func compileReqParser(rt reflect.Type, opt bindCompileOption) (Decoder, error) {
continue
}
dec, err := compileFieldDecoder(el.Field(i), i, opt)
dec, err := compileFieldDecoder(el.Field(i), i, opt, parentStruct{})
if err != nil {
return nil, err
}
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
}
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) {
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}
@ -91,6 +96,10 @@ func compileFieldDecoder(field reflect.StructField, index int, opt bindCompileOp
tagContent := field.Tag.Get(tagScope)
if parent.tag != "" {
tagContent = parent.tag + "." + tagContent
}
if reflect.PtrTo(field.Type).Implements(textUnmarshalerType) {
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 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 {
@ -120,7 +160,7 @@ func multipartGetter(ctx Ctx, key string, defaultValue ...string) string {
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
switch tagScope {
case bindTagQuery:
@ -146,17 +186,23 @@ func compileTextBasedDecoder(field reflect.StructField, index int, tagScope, tag
return nil, err
}
return &fieldTextDecoder{
fieldDecoder := &fieldTextDecoder{
index: index,
fieldName: field.Name,
tag: tagScope,
reqField: tagContent,
dec: textDecoder,
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 {
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 &fieldSliceDecoder{
return []decoder{&fieldSliceDecoder{
fieldIndex: index,
eqBytes: eqBytes,
fieldName: field.Name,
@ -199,5 +245,5 @@ func compileSliceFieldTextBasedDecoder(field reflect.StructField, index int, tag
fieldType: field.Type,
elementType: et,
elementDecoder: elementUnmarshaler,
}, nil
}}, nil
}