mirror of https://github.com/gofiber/fiber.git
165 lines
4.0 KiB
Go
165 lines
4.0 KiB
Go
package fiber
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
|
|
"github.com/gofiber/fiber/v3/internal/bind"
|
|
"github.com/gofiber/fiber/v3/utils"
|
|
)
|
|
|
|
type Decoder func(c Ctx, rv reflect.Value) error
|
|
|
|
const bindTagRespHeader = "respHeader"
|
|
const bindTagHeader = "header"
|
|
const bindTagQuery = "query"
|
|
const bindTagParam = "param"
|
|
const bindTagCookie = "cookie"
|
|
|
|
var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
|
var bindUnmarshalerType = reflect.TypeOf((*Binder)(nil)).Elem()
|
|
|
|
func compileReqParser(rt reflect.Type) (Decoder, error) {
|
|
var decoders []decoder
|
|
|
|
el := rt.Elem()
|
|
if el.Kind() != reflect.Struct {
|
|
panic("wrapped request need to struct")
|
|
}
|
|
|
|
for i := 0; i < el.NumField(); i++ {
|
|
if !el.Field(i).IsExported() {
|
|
// ignore unexported field
|
|
continue
|
|
}
|
|
|
|
dec, err := compileFieldDecoder(el.Field(i), i)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if dec != nil {
|
|
decoders = append(decoders, dec)
|
|
}
|
|
}
|
|
|
|
return func(c Ctx, rv reflect.Value) error {
|
|
for _, decoder := range decoders {
|
|
err := decoder.Decode(c, rv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}, nil
|
|
}
|
|
|
|
func compileFieldDecoder(field reflect.StructField, index int) (decoder, error) {
|
|
if reflect.PtrTo(field.Type).Implements(bindUnmarshalerType) {
|
|
return &fieldCtxDecoder{index: index, fieldName: field.Name, fieldType: field.Type}, nil
|
|
}
|
|
|
|
var tagScope = ""
|
|
for _, loopTagScope := range []string{bindTagRespHeader, bindTagQuery, bindTagParam, bindTagHeader, bindTagCookie} {
|
|
if _, ok := field.Tag.Lookup(loopTagScope); ok {
|
|
tagScope = loopTagScope
|
|
break
|
|
}
|
|
}
|
|
|
|
if tagScope == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
tagContent := field.Tag.Get(tagScope)
|
|
|
|
if reflect.PtrTo(field.Type).Implements(textUnmarshalerType) {
|
|
return compileTextBasedDecoder(field, index, tagScope, tagContent)
|
|
}
|
|
|
|
if field.Type.Kind() == reflect.Slice {
|
|
return compileSliceFieldTextBasedDecoder(field, index, tagScope, tagContent)
|
|
}
|
|
|
|
return compileTextBasedDecoder(field, index, tagScope, tagContent)
|
|
}
|
|
|
|
func compileTextBasedDecoder(field reflect.StructField, index int, tagScope, tagContent string) (decoder, error) {
|
|
var get func(ctx Ctx, key string, defaultValue ...string) string
|
|
switch tagScope {
|
|
case bindTagQuery:
|
|
get = Ctx.Query
|
|
case bindTagHeader:
|
|
get = Ctx.Get
|
|
case bindTagRespHeader:
|
|
get = Ctx.GetRespHeader
|
|
case bindTagParam:
|
|
get = Ctx.Params
|
|
case bindTagCookie:
|
|
get = Ctx.Cookies
|
|
default:
|
|
return nil, errors.New("unexpected tag scope " + strconv.Quote(tagScope))
|
|
}
|
|
|
|
textDecoder, err := bind.CompileTextDecoder(field.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &fieldTextDecoder{
|
|
index: index,
|
|
fieldName: field.Name,
|
|
tag: tagScope,
|
|
reqField: tagContent,
|
|
dec: textDecoder,
|
|
get: get,
|
|
}, nil
|
|
}
|
|
|
|
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())
|
|
}
|
|
|
|
et := field.Type.Elem()
|
|
elementUnmarshaler, err := bind.CompileTextDecoder(et)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to build slice binder: %w", err)
|
|
}
|
|
|
|
var eqBytes = bytes.Equal
|
|
var visitAll func(Ctx, func(key, value []byte))
|
|
switch tagScope {
|
|
case bindTagQuery:
|
|
visitAll = visitQuery
|
|
case bindTagHeader:
|
|
visitAll = visitHeader
|
|
eqBytes = utils.EqualFold[[]byte]
|
|
case bindTagRespHeader:
|
|
visitAll = visitResHeader
|
|
eqBytes = utils.EqualFold[[]byte]
|
|
case bindTagCookie:
|
|
visitAll = visitCookie
|
|
case bindTagParam:
|
|
return nil, errors.New("using params with slice type is not supported")
|
|
default:
|
|
return nil, errors.New("unexpected tag scope " + strconv.Quote(tagScope))
|
|
}
|
|
|
|
return &fieldSliceDecoder{
|
|
fieldIndex: index,
|
|
eqBytes: eqBytes,
|
|
fieldName: field.Name,
|
|
visitAll: visitAll,
|
|
reqKey: []byte(tagContent),
|
|
fieldType: field.Type,
|
|
elementType: et,
|
|
elementDecoder: elementUnmarshaler,
|
|
}, nil
|
|
}
|