fiber/binder.go

249 lines
4.9 KiB
Go

package fiber
import (
"bytes"
"net/http"
"reflect"
"sync"
"github.com/gofiber/fiber/v3/internal/bind"
"github.com/gofiber/utils/v2"
)
var binderPool = sync.Pool{New: func() any {
return &Bind{}
}}
type Bind struct {
err error
ctx Ctx
val any // last decoded val
strict bool
}
func (c *DefaultCtx) Bind() *Bind {
b := binderPool.Get().(*Bind)
b.ctx = c
return b
}
func (b *Bind) Strict() *Bind {
b.strict = true
return b
}
func (b *Bind) setErr(err error) *Bind {
b.err = err
return b
}
func (b *Bind) reset() {
b.ctx = nil
b.val = nil
b.err = nil
b.strict = false
}
// HTTPErr return a wrapped fiber.Error for 400 http bad request.
// it's not safe to use after HTTPErr is called.
func (b *Bind) HTTPErr() error {
err := b.Err()
if err != nil {
if fe, ok := err.(*Error); ok {
return fe
}
return NewError(http.StatusBadRequest, err.Error())
}
return nil
}
// Err return binding error and put binder back to pool
// it's not safe to use after Err is called.
func (b *Bind) Err() error {
err := b.err
b.reset()
binderPool.Put(b)
return err
}
// JSON unmarshal body as json
func (b *Bind) JSON(v any) *Bind {
if b.err != nil {
return b
}
if b.strict {
if !bytes.HasPrefix(b.ctx.Request().Header.ContentType(), utils.UnsafeBytes(MIMEApplicationJSON)) {
return b.setErr(NewError(http.StatusUnsupportedMediaType, "expecting content-type \"application/json\""))
}
}
if err := b.ctx.App().config.JSONDecoder(b.ctx.Body(), v); err != nil {
return b.setErr(err)
}
b.val = v
return b
}
// XML unmarshal body as xml
func (b *Bind) XML(v any) *Bind {
if b.err != nil {
return b
}
if b.strict {
if !bytes.HasPrefix(b.ctx.Request().Header.ContentType(), utils.UnsafeBytes(MIMEApplicationXML)) {
return b.setErr(NewError(http.StatusUnsupportedMediaType, "expecting content-type \"application/xml\""))
}
}
if err := b.ctx.App().config.XMLDecoder(b.ctx.Body(), v); err != nil {
return b.setErr(err)
}
b.val = v
return b
}
// Form unmarshal body as form
func (b *Bind) Form(v any) *Bind {
if b.err != nil {
return b
}
if b.strict {
if !bytes.HasPrefix(b.ctx.Request().Header.ContentType(), utils.UnsafeBytes(MIMEApplicationForm)) {
return b.setErr(NewError(http.StatusUnsupportedMediaType, "expecting content-type \"application/x-www-form-urlencoded\""))
}
}
if err := b.formDecode(v); err != nil {
return b.setErr(err)
}
b.val = v
return b
}
// Multipart unmarshal body as multipart/form-data
// TODO: handle multipart files.
func (b *Bind) Multipart(v any) *Bind {
if b.err != nil {
return b
}
if b.strict {
if !bytes.HasPrefix(b.ctx.Request().Header.ContentType(), utils.UnsafeBytes(MIMEMultipartForm)) {
return b.setErr(NewError(http.StatusUnsupportedMediaType, "expecting content-type \"multipart/form-data\""))
}
}
if err := b.multipartDecode(v); err != nil {
return b.setErr(err)
}
b.val = v
return b
}
func (b *Bind) Req(v any) *Bind {
if b.err != nil {
return b
}
if err := b.reqDecode(v); err != nil {
return b.setErr(err)
}
b.val = v
return b
}
func (b *Bind) Validate() *Bind {
if b.err != nil {
return b
}
if b.val == nil {
return b
}
if err := b.ctx.Validate(b.val); err != nil {
return b.setErr(err)
}
return b
}
func (b *Bind) reqDecode(v any) error {
rv, typeID := bind.ValueAndTypeID(v)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
return &InvalidBinderError{Type: reflect.TypeOf(v)}
}
cached, ok := b.ctx.App().bindDecoderCache.Load(typeID)
if ok {
// cached decoder, fast path
decoder := cached.(Decoder)
return decoder(b.ctx, rv.Elem())
}
decoder, err := compileReqParser(rv.Type(), bindCompileOption{reqDecoder: true})
if err != nil {
return err
}
b.ctx.App().bindDecoderCache.Store(typeID, decoder)
return decoder(b.ctx, rv.Elem())
}
func (b *Bind) formDecode(v any) error {
rv, typeID := bind.ValueAndTypeID(v)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
return &InvalidBinderError{Type: reflect.TypeOf(v)}
}
cached, ok := b.ctx.App().formDecoderCache.Load(typeID)
if ok {
// cached decoder, fast path
decoder := cached.(Decoder)
return decoder(b.ctx, rv.Elem())
}
decoder, err := compileReqParser(rv.Type(), bindCompileOption{bodyDecoder: true})
if err != nil {
return err
}
b.ctx.App().formDecoderCache.Store(typeID, decoder)
return decoder(b.ctx, rv.Elem())
}
func (b *Bind) multipartDecode(v any) error {
rv, typeID := bind.ValueAndTypeID(v)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
return &InvalidBinderError{Type: reflect.TypeOf(v)}
}
cached, ok := b.ctx.App().multipartDecoderCache.Load(typeID)
if ok {
// cached decoder, fast path
decoder := cached.(Decoder)
return decoder(b.ctx, rv.Elem())
}
decoder, err := compileReqParser(rv.Type(), bindCompileOption{bodyDecoder: true})
if err != nil {
return err
}
b.ctx.App().multipartDecoderCache.Store(typeID, decoder)
return decoder(b.ctx, rv.Elem())
}