mirror of https://github.com/gofiber/fiber.git
249 lines
4.9 KiB
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())
|
|
}
|