mirror of https://github.com/gofiber/fiber.git
309 lines
7.1 KiB
Go
309 lines
7.1 KiB
Go
package fiber
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
|
|
"github.com/gofiber/fiber/v3/internal/bind"
|
|
"github.com/gofiber/utils/v2"
|
|
)
|
|
|
|
type Decoder func(c Ctx, rv reflect.Value) error
|
|
|
|
const bindTagRespHeader = "respHeader"
|
|
const bindTagHeader = "header"
|
|
const bindTagQuery = "query"
|
|
const bindTagParam = "param"
|
|
const bindTagCookie = "cookie"
|
|
|
|
const bindTagForm = "form"
|
|
const bindTagMultipart = "multipart"
|
|
|
|
var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
|
var bindUnmarshalerType = reflect.TypeOf((*Binder)(nil)).Elem()
|
|
|
|
type bindCompileOption struct {
|
|
bodyDecoder bool // to parse `form` or `multipart/form-data`
|
|
reqDecoder bool // to parse header/cookie/param/query/header/respHeader
|
|
}
|
|
|
|
func compileReqParser(rt reflect.Type, opt bindCompileOption) (Decoder, error) {
|
|
var decoders []decoder
|
|
|
|
el := rt.Elem()
|
|
if el.Kind() != reflect.Struct {
|
|
return nil, &UnsupportedBinderError{Type: rt}
|
|
}
|
|
|
|
for i := 0; i < el.NumField(); i++ {
|
|
if !el.Field(i).IsExported() {
|
|
// ignore unexported field
|
|
continue
|
|
}
|
|
|
|
dec, err := compileFieldDecoder(el.Field(i), i, opt, parentStruct{})
|
|
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
|
|
}
|
|
|
|
type parentStruct struct {
|
|
tag string
|
|
index []int
|
|
}
|
|
|
|
func lookupTagScope(field reflect.StructField, opt bindCompileOption) (tagScope string) {
|
|
var tags = []string{bindTagRespHeader, bindTagQuery, bindTagParam, bindTagHeader, bindTagCookie}
|
|
if opt.bodyDecoder {
|
|
tags = []string{bindTagForm, bindTagMultipart}
|
|
}
|
|
|
|
for _, loopTagScope := range tags {
|
|
if _, ok := field.Tag.Lookup(loopTagScope); ok {
|
|
tagScope = loopTagScope
|
|
break
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func compileFieldDecoder(field reflect.StructField, index int, opt bindCompileOption, parent parentStruct) ([]decoder, error) {
|
|
if reflect.PtrTo(field.Type).Implements(bindUnmarshalerType) {
|
|
return []decoder{&fieldCtxDecoder{index: index, fieldName: field.Name, fieldType: field.Type}}, nil
|
|
}
|
|
|
|
tagScope := lookupTagScope(field, opt)
|
|
if tagScope == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
if field.Type.Kind() == reflect.Slice {
|
|
return compileSliceFieldTextBasedDecoder(field, index, tagScope, tagContent)
|
|
}
|
|
|
|
// Nested binding support
|
|
if field.Type.Kind() == reflect.Ptr {
|
|
field.Type = field.Type.Elem()
|
|
}
|
|
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 {
|
|
return utils.UnsafeString(ctx.Request().PostArgs().Peek(key))
|
|
}
|
|
|
|
func multipartGetter(ctx Ctx, key string, defaultValue ...string) string {
|
|
f, err := ctx.Request().MultipartForm()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
v, ok := f.Value[key]
|
|
if !ok {
|
|
return ""
|
|
}
|
|
|
|
return v[0]
|
|
}
|
|
|
|
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:
|
|
get = Ctx.Query
|
|
case bindTagHeader:
|
|
get = Ctx.Get
|
|
case bindTagRespHeader:
|
|
get = Ctx.GetRespHeader
|
|
case bindTagParam:
|
|
get = Ctx.Params
|
|
case bindTagCookie:
|
|
get = Ctx.Cookies
|
|
case bindTagMultipart:
|
|
get = multipartGetter
|
|
case bindTagForm:
|
|
get = formGetter
|
|
default:
|
|
return nil, errors.New("unexpected tag scope " + strconv.Quote(tagScope))
|
|
}
|
|
|
|
et := field.Type
|
|
if field.Type.Kind() == reflect.Ptr {
|
|
et = field.Type.Elem()
|
|
}
|
|
|
|
textDecoder, err := bind.CompileTextDecoder(et)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fieldDecoder := &fieldTextDecoder{
|
|
index: index,
|
|
fieldName: field.Name,
|
|
tag: tagScope,
|
|
reqField: tagContent,
|
|
dec: textDecoder,
|
|
get: get,
|
|
et: et,
|
|
}
|
|
|
|
if len(parentIndex) > 0 {
|
|
fieldDecoder.parentIndex = parentIndex[0]
|
|
}
|
|
|
|
return []decoder{fieldDecoder}, nil
|
|
}
|
|
|
|
// TODO
|
|
type subElem struct {
|
|
et reflect.Type
|
|
tag string
|
|
index int
|
|
elementDecoder bind.TextDecoder
|
|
//subElems []subElem
|
|
}
|
|
|
|
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())
|
|
}
|
|
|
|
var elems []subElem
|
|
var elementUnmarshaler bind.TextDecoder
|
|
var err error
|
|
|
|
et := field.Type.Elem()
|
|
if et.Kind() == reflect.Struct {
|
|
elems = make([]subElem, et.NumField())
|
|
for i := 0; i < et.NumField(); i++ {
|
|
if !et.Field(i).IsExported() {
|
|
// ignore unexported field
|
|
continue
|
|
}
|
|
|
|
// Skip different tag scopes (main -> sub)
|
|
subScope := lookupTagScope(et.Field(i), bindCompileOption{})
|
|
if subScope != tagScope {
|
|
continue
|
|
}
|
|
|
|
elementUnmarshaler, err := bind.CompileTextDecoder(et.Field(i).Type)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to build slice binder: %w", err)
|
|
}
|
|
|
|
elem := subElem{
|
|
index: i,
|
|
tag: et.Field(i).Tag.Get(subScope),
|
|
et: et.Field(i).Type,
|
|
elementDecoder: elementUnmarshaler,
|
|
}
|
|
|
|
elems = append(elems, elem)
|
|
}
|
|
} else {
|
|
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 bindTagForm:
|
|
visitAll = visitForm
|
|
case bindTagMultipart:
|
|
visitAll = visitMultipart
|
|
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))
|
|
}
|
|
|
|
fieldSliceDecoder := &fieldSliceDecoder{
|
|
elems: elems,
|
|
fieldIndex: index,
|
|
eqBytes: eqBytes,
|
|
fieldName: field.Name,
|
|
visitAll: visitAll,
|
|
reqKey: []byte(tagContent),
|
|
fieldType: field.Type,
|
|
elementType: et,
|
|
elementDecoder: elementUnmarshaler,
|
|
}
|
|
|
|
return []decoder{fieldSliceDecoder}, nil
|
|
}
|