mirror of
https://github.com/ggicci/httpin.git
synced 2024-11-24 08:32:45 +02:00
feat: parse multipart/form-data add a new file decoder
This commit is contained in:
parent
0cade0b6db
commit
946319ca6d
@ -10,8 +10,8 @@ import (
|
||||
)
|
||||
|
||||
// DecodeCustomBool additionally parses "yes/no".
|
||||
func DecodeCustomBool(data []byte) (interface{}, error) {
|
||||
sdata := strings.ToLower(string(data))
|
||||
func DecodeCustomBool(value string) (interface{}, error) {
|
||||
sdata := strings.ToLower(value)
|
||||
if sdata == "yes" {
|
||||
return true, nil
|
||||
}
|
||||
@ -29,15 +29,24 @@ func TestDecoders(t *testing.T) {
|
||||
})
|
||||
delete(decoders, boolType) // remove the custom decoder
|
||||
|
||||
var invalidDecoder = func(string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
Convey("Register invalid decoder", t, func() {
|
||||
So(func() { RegisterTypeDecoder(boolType, invalidDecoder) }, ShouldPanic)
|
||||
})
|
||||
delete(decoders, boolType) // remove the custom decoder
|
||||
|
||||
Convey("Register duplicate decoder", t, func() {
|
||||
So(func() { RegisterTypeDecoder(boolType, TypeDecoderFunc(DecodeCustomBool)) }, ShouldNotPanic)
|
||||
So(func() { RegisterTypeDecoder(boolType, TypeDecoderFunc(DecodeCustomBool)) }, ShouldPanic)
|
||||
So(func() { RegisterTypeDecoder(boolType, ValueTypeDecoderFunc(DecodeCustomBool)) }, ShouldNotPanic)
|
||||
So(func() { RegisterTypeDecoder(boolType, ValueTypeDecoderFunc(DecodeCustomBool)) }, ShouldPanic)
|
||||
})
|
||||
delete(decoders, boolType) // remove the custom decoder
|
||||
|
||||
Convey("Replace a decoder", t, func() {
|
||||
So(func() { ReplaceTypeDecoder(boolType, TypeDecoderFunc(DecodeCustomBool)) }, ShouldNotPanic)
|
||||
So(func() { ReplaceTypeDecoder(boolType, TypeDecoderFunc(DecodeCustomBool)) }, ShouldNotPanic)
|
||||
So(func() { ReplaceTypeDecoder(boolType, ValueTypeDecoderFunc(DecodeCustomBool)) }, ShouldNotPanic)
|
||||
So(func() { ReplaceTypeDecoder(boolType, ValueTypeDecoderFunc(DecodeCustomBool)) }, ShouldNotPanic)
|
||||
})
|
||||
delete(decoders, boolType) // remove the custom decoder
|
||||
}
|
||||
|
40
decoders.go
40
decoders.go
@ -7,34 +7,54 @@ import (
|
||||
"github.com/ggicci/httpin/internal"
|
||||
)
|
||||
|
||||
// TypeDecoder is the interface implemented by types that can decode bytes to
|
||||
// themselves.
|
||||
type TypeDecoder = internal.TypeDecoder
|
||||
// ValueTypeDecoder is the interface implemented by types that can decode a
|
||||
// string to themselves.
|
||||
type ValueTypeDecoder = internal.ValueTypeDecoder
|
||||
|
||||
// TypeDecoderFunc is an adaptor to allow the use of ordinary functions as httpin
|
||||
// TypeDecoders.
|
||||
type TypeDecoderFunc = internal.TypeDecoderFunc
|
||||
// FileTypeDecoder is the interface implemented by types that can decode a
|
||||
// *multipart.FileHeader to themselves.
|
||||
type FileTypeDecoder = internal.FileTypeDecoder
|
||||
|
||||
var decoders = map[reflect.Type]TypeDecoder{} // custom decoders
|
||||
// ValueTypeDecoderFunc is an adaptor to allow the use of ordinary functions as
|
||||
// httpin `ValueTypeDecoder`s.
|
||||
type ValueTypeDecoderFunc = internal.ValueTypeDecoderFunc
|
||||
|
||||
// FileTypeDecoderFunc is an adaptor to allow the use of ordinary functions as
|
||||
// httpin `FileTypeDecoder`s.
|
||||
type FileTypeDecoderFunc = internal.FileTypeDecoderFunc
|
||||
|
||||
var decoders = make(map[reflect.Type]interface{}) // custom decoders
|
||||
|
||||
func isTypeDecoder(decoder interface{}) bool {
|
||||
_, isValueTypeDecoder := decoder.(ValueTypeDecoder)
|
||||
_, isFileTypeDecoder := decoder.(FileTypeDecoder)
|
||||
return isValueTypeDecoder || isFileTypeDecoder
|
||||
}
|
||||
|
||||
// RegisterTypeDecoder registers a specific type decoder. Panics on conflicts.
|
||||
func RegisterTypeDecoder(typ reflect.Type, decoder TypeDecoder) {
|
||||
func RegisterTypeDecoder(typ reflect.Type, decoder interface{}) {
|
||||
if _, ok := decoders[typ]; ok {
|
||||
panic(fmt.Errorf("%w: %q", ErrDuplicateTypeDecoder, typ))
|
||||
}
|
||||
|
||||
ReplaceTypeDecoder(typ, decoder)
|
||||
}
|
||||
|
||||
// ReplaceTypeDecoder replaces a specific type decoder.
|
||||
func ReplaceTypeDecoder(typ reflect.Type, decoder TypeDecoder) {
|
||||
func ReplaceTypeDecoder(typ reflect.Type, decoder interface{}) {
|
||||
if decoder == nil {
|
||||
panic(fmt.Errorf("%w: %q", ErrNilTypeDecoder, typ))
|
||||
}
|
||||
|
||||
if !isTypeDecoder(decoder) {
|
||||
panic(fmt.Errorf("%w: %q", ErrInvalidTypeDecoder, typ))
|
||||
}
|
||||
|
||||
decoders[typ] = decoder
|
||||
}
|
||||
|
||||
// decoderOf retrieves a decoder by type.
|
||||
func decoderOf(t reflect.Type) TypeDecoder {
|
||||
func decoderOf(t reflect.Type) interface{} {
|
||||
dec := decoders[t]
|
||||
if dec != nil {
|
||||
return dec
|
||||
|
@ -27,6 +27,7 @@ func init() {
|
||||
DirectiveNormalizerFunc(bodyDirectiveNormalizer),
|
||||
)
|
||||
RegisterDirectiveExecutor("required", DirectiveExecutorFunc(required), nil)
|
||||
// RegisterDirectiveExecutor("file", DirectiveExecutorFunc(fileValueExtractor), nil)
|
||||
}
|
||||
|
||||
// DirectiveExecutor is the interface implemented by a "directive executor".
|
||||
|
@ -12,11 +12,14 @@ var (
|
||||
ErrUnregisteredExecutor = errors.New("unregistered executor")
|
||||
ErrDuplicateTypeDecoder = errors.New("duplicate type decoder")
|
||||
ErrNilTypeDecoder = errors.New("nil type decoder")
|
||||
ErrInvalidTypeDecoder = errors.New("invalid type decoder")
|
||||
ErrDuplicateExecutor = errors.New("duplicate executor")
|
||||
ErrNilExecutor = errors.New("nil executor")
|
||||
ErrUnknownBodyType = errors.New("unknown body type")
|
||||
ErrDuplicateAnnotationField = errors.New("duplicate annotation field")
|
||||
ErrNilErrorHandler = errors.New("nil error handler")
|
||||
ErrInvalidMaxMemory = errors.New("invalid max memory, must be positive")
|
||||
ErrNilFile = errors.New("nil file")
|
||||
)
|
||||
|
||||
type UnsupportedTypeError struct {
|
||||
|
125
extractor.go
125
extractor.go
@ -2,30 +2,60 @@ package httpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func extractFromKVS(ctx *DirectiveContext, kvs map[string][]string, isHeaderKey bool) error {
|
||||
for _, key := range ctx.Directive.Argv {
|
||||
if isHeaderKey {
|
||||
key = http.CanonicalHeaderKey(key)
|
||||
type Extractor struct {
|
||||
multipart.Form
|
||||
|
||||
KeyNormalizer func(string) string
|
||||
}
|
||||
|
||||
func NewExtractor(r *http.Request) *Extractor {
|
||||
var form multipart.Form
|
||||
|
||||
if r.MultipartForm != nil {
|
||||
form = *r.MultipartForm
|
||||
} else {
|
||||
if r.Form != nil {
|
||||
form.Value = r.Form
|
||||
}
|
||||
if err := extractFromKVSWithKey(ctx, kvs, key); err != nil {
|
||||
}
|
||||
|
||||
return &Extractor{
|
||||
Form: form,
|
||||
KeyNormalizer: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Extractor) Execute(ctx *DirectiveContext) error {
|
||||
for _, key := range ctx.Directive.Argv {
|
||||
if e.KeyNormalizer != nil {
|
||||
key = e.KeyNormalizer(key)
|
||||
}
|
||||
if err := e.extract(ctx, key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractFromKVSWithKey(ctx *DirectiveContext, kvs map[string][]string, key string) error {
|
||||
func (e *Extractor) extract(ctx *DirectiveContext, key string) error {
|
||||
if ctx.Context.Value(FieldSet) == true {
|
||||
return nil
|
||||
}
|
||||
|
||||
values := e.Form.Value[key]
|
||||
files := e.Form.File[key]
|
||||
if len(values) == 0 && len(files) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NOTE(ggicci): Array?
|
||||
if ctx.ValueType.Kind() == reflect.Slice {
|
||||
return extractFromKVSWithKeyForSlice(ctx, kvs, key)
|
||||
return e.extractMulti(ctx, key)
|
||||
}
|
||||
|
||||
decoder := decoderOf(ctx.ValueType)
|
||||
@ -33,47 +63,80 @@ func extractFromKVSWithKey(ctx *DirectiveContext, kvs map[string][]string, key s
|
||||
return UnsupportedTypeError{ctx.ValueType}
|
||||
}
|
||||
|
||||
formValues, exists := kvs[key]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
var got string
|
||||
if len(formValues) > 0 {
|
||||
got = formValues[0]
|
||||
}
|
||||
if interfaceValue, err := decoder.Decode([]byte(got)); err != nil {
|
||||
return fieldError{key, got, err}
|
||||
} else {
|
||||
ctx.Value.Elem().Set(reflect.ValueOf(interfaceValue))
|
||||
switch dec := decoder.(type) {
|
||||
case ValueTypeDecoder:
|
||||
if gotValue, interfaceValue, err := decodeValueAt(dec, e.Form.Value[key], 0); err != nil {
|
||||
return fieldError{key, gotValue, err}
|
||||
} else {
|
||||
ctx.Value.Elem().Set(reflect.ValueOf(interfaceValue))
|
||||
}
|
||||
case FileTypeDecoder:
|
||||
if gotFile, interfaceValue, err := decodeFileAt(dec, e.Form.File[key], 0); err != nil {
|
||||
return fieldError{key, gotFile, err}
|
||||
} else {
|
||||
ctx.Value.Elem().Set(reflect.ValueOf(interfaceValue))
|
||||
}
|
||||
default:
|
||||
return UnsupportedTypeError{ctx.ValueType}
|
||||
}
|
||||
|
||||
ctx.DeliverContextValue(FieldSet, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractFromKVSWithKeyForSlice(ctx *DirectiveContext, kvs map[string][]string, key string) error {
|
||||
func (e *Extractor) extractMulti(ctx *DirectiveContext, key string) error {
|
||||
elemType := ctx.ValueType.Elem()
|
||||
|
||||
decoder := decoderOf(elemType)
|
||||
if decoder == nil {
|
||||
return UnsupportedTypeError{ctx.ValueType}
|
||||
}
|
||||
|
||||
formValues, exists := kvs[key]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
var theSlice reflect.Value
|
||||
values := e.Form.Value[key]
|
||||
files := e.Form.File[key]
|
||||
|
||||
theSlice := reflect.MakeSlice(ctx.ValueType, len(formValues), len(formValues))
|
||||
for i, formValue := range formValues {
|
||||
if interfaceValue, err := decoder.Decode([]byte(formValue)); err != nil {
|
||||
return fieldError{key, formValues, fmt.Errorf("at index %d: %w", i, err)}
|
||||
} else {
|
||||
theSlice.Index(i).Set(reflect.ValueOf(interfaceValue))
|
||||
switch dec := decoder.(type) {
|
||||
case ValueTypeDecoder:
|
||||
theSlice = reflect.MakeSlice(ctx.ValueType, len(values), len(values))
|
||||
for i := 0; i < len(values); i++ {
|
||||
if _, interfaceValue, err := decodeValueAt(dec, values, i); err != nil {
|
||||
return fieldError{key, values, fmt.Errorf("at index %d: %w", i, err)}
|
||||
} else {
|
||||
theSlice.Index(i).Set(reflect.ValueOf(interfaceValue))
|
||||
}
|
||||
}
|
||||
case FileTypeDecoder:
|
||||
theSlice = reflect.MakeSlice(ctx.ValueType, len(files), len(files))
|
||||
for i := 0; i < len(files); i++ {
|
||||
if _, interfaceValue, err := decodeFileAt(dec, files, i); err != nil {
|
||||
return fieldError{key, files, fmt.Errorf("at index %d: %w", i, err)}
|
||||
} else {
|
||||
theSlice.Index(i).Set(reflect.ValueOf(interfaceValue))
|
||||
}
|
||||
}
|
||||
default:
|
||||
return UnsupportedTypeError{ctx.ValueType}
|
||||
}
|
||||
|
||||
ctx.Value.Elem().Set(theSlice)
|
||||
ctx.DeliverContextValue(FieldSet, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeValueAt(decoder ValueTypeDecoder, values []string, index int) (string, interface{}, error) {
|
||||
var gotValue = ""
|
||||
if index < len(values) {
|
||||
gotValue = values[index]
|
||||
}
|
||||
res, err := decoder.Decode(gotValue)
|
||||
return gotValue, res, err
|
||||
}
|
||||
|
||||
func decodeFileAt(decoder FileTypeDecoder, files []*multipart.FileHeader, index int) (*multipart.FileHeader, interface{}, error) {
|
||||
var gotFile *multipart.FileHeader
|
||||
if index < len(files) {
|
||||
gotFile = files[index]
|
||||
}
|
||||
res, err := decoder.Decode(gotFile)
|
||||
return gotFile, res, err
|
||||
}
|
||||
|
34
file.go
Normal file
34
file.go
Normal file
@ -0,0 +1,34 @@
|
||||
package httpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterTypeDecoder(reflect.TypeOf(File{}), FileTypeDecoderFunc(DecodeFile))
|
||||
}
|
||||
|
||||
type File struct {
|
||||
multipart.File
|
||||
Header *multipart.FileHeader
|
||||
}
|
||||
|
||||
func DecodeFile(meta *multipart.FileHeader) (interface{}, error) {
|
||||
if meta == nil {
|
||||
return nil, ErrNilFile
|
||||
}
|
||||
|
||||
file, err := meta.Open()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open file: %w", err)
|
||||
}
|
||||
|
||||
inFile := File{
|
||||
File: file,
|
||||
Header: meta,
|
||||
}
|
||||
|
||||
return inFile, nil
|
||||
}
|
56
file_test.go
Normal file
56
file_test.go
Normal file
@ -0,0 +1,56 @@
|
||||
package httpin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
type UpdateUserProfileInput struct {
|
||||
Name string `in:"form=name"`
|
||||
Gender string `in:"form=gender"`
|
||||
Avatar File `in:"form=avatar"`
|
||||
}
|
||||
|
||||
func TestMultipartForm_DecodeFile(t *testing.T) {
|
||||
var AvatarBytes = []byte("avatar image content")
|
||||
|
||||
Convey("Upload a file through multipart/form-data requests", t, func() {
|
||||
body := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
nameFieldWriter, err := writer.CreateFormField("name")
|
||||
So(err, ShouldBeNil)
|
||||
nameFieldWriter.Write([]byte("Ggicci T'ang"))
|
||||
|
||||
genderFieldWriter, err := writer.CreateFormField("gender")
|
||||
So(err, ShouldBeNil)
|
||||
genderFieldWriter.Write([]byte("male"))
|
||||
|
||||
avatarFileWriter, err := writer.CreateFormFile("avatar", "avatar.png")
|
||||
So(err, ShouldBeNil)
|
||||
_, err = avatarFileWriter.Write(AvatarBytes)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_ = writer.Close() // error ignored
|
||||
|
||||
r, _ := http.NewRequest("POST", "/", body)
|
||||
r.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
// rw := httptest.NewRecorder()
|
||||
core, err := New(UpdateUserProfileInput{})
|
||||
So(err, ShouldBeNil)
|
||||
gotInput, err := core.Decode(r)
|
||||
So(err, ShouldBeNil)
|
||||
got, ok := gotInput.(*UpdateUserProfileInput)
|
||||
So(ok, ShouldBeTrue)
|
||||
So(got.Name, ShouldEqual, "Ggicci T'ang")
|
||||
So(got.Gender, ShouldEqual, "male")
|
||||
So(got.Avatar.Header.Filename, ShouldEqual, "avatar.png")
|
||||
So(got.Avatar.Header.Size, ShouldEqual, len(AvatarBytes))
|
||||
})
|
||||
|
||||
}
|
2
form.go
2
form.go
@ -3,5 +3,5 @@ package httpin
|
||||
// formValueExtractor implements the "form" executor who extracts values from
|
||||
// the forms of an HTTP request.
|
||||
func formValueExtractor(ctx *DirectiveContext) error {
|
||||
return extractFromKVS(ctx, ctx.Request.Form, false)
|
||||
return NewExtractor(ctx.Request).Execute(ctx)
|
||||
}
|
||||
|
12
gochi.go
12
gochi.go
@ -2,7 +2,10 @@
|
||||
|
||||
package httpin
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// GochiURLParamFunc is chi.URLParam
|
||||
type GochiURLParamFunc func(r *http.Request, key string) string
|
||||
@ -25,5 +28,10 @@ func (chi *gochiURLParamExtractor) Execute(ctx *DirectiveContext) error {
|
||||
}
|
||||
}
|
||||
|
||||
return extractFromKVS(ctx, kvs, false)
|
||||
extractor := &Extractor{
|
||||
Form: multipart.Form{
|
||||
Value: kvs,
|
||||
},
|
||||
}
|
||||
return extractor.Execute(ctx)
|
||||
}
|
||||
|
12
gorilla.go
12
gorilla.go
@ -2,7 +2,10 @@
|
||||
|
||||
package httpin
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// GorillaMuxVarsFunc is mux.Vars
|
||||
type GorillaMuxVarsFunc func(*http.Request) map[string]string
|
||||
@ -31,5 +34,10 @@ func (mux *gorillaMuxVarsExtractor) Execute(ctx *DirectiveContext) error {
|
||||
kvs[key] = []string{value}
|
||||
}
|
||||
|
||||
return extractFromKVS(ctx, kvs, false)
|
||||
extractor := &Extractor{
|
||||
Form: multipart.Form{
|
||||
Value: kvs,
|
||||
},
|
||||
}
|
||||
return extractor.Execute(ctx)
|
||||
}
|
||||
|
13
header.go
13
header.go
@ -1,7 +1,18 @@
|
||||
package httpin
|
||||
|
||||
import (
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// headerValueExtractor implements the "header" executor who extracts values
|
||||
// from the HTTP headers.
|
||||
func headerValueExtractor(ctx *DirectiveContext) error {
|
||||
return extractFromKVS(ctx, ctx.Request.Header, true)
|
||||
extractor := &Extractor{
|
||||
Form: multipart.Form{
|
||||
Value: ctx.Request.Header,
|
||||
},
|
||||
KeyNormalizer: http.CanonicalHeaderKey,
|
||||
}
|
||||
return extractor.Execute(ctx)
|
||||
}
|
||||
|
27
httpin.go
27
httpin.go
@ -2,6 +2,7 @@ package httpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sync"
|
||||
@ -10,11 +11,16 @@ import (
|
||||
type ContextKey int
|
||||
|
||||
const (
|
||||
Input ContextKey = iota // the primary key to get the input object in the context injected by httpin
|
||||
defaultMaxMemory = 32 << 20 // 32 MB
|
||||
|
||||
// Input is the key to get the input object from Request.Context() injected by httpin. e.g.
|
||||
//
|
||||
// input := r.Context().Value(httpin.Input).(*InputStruct)
|
||||
Input ContextKey = iota
|
||||
|
||||
// Set this context value to true to indicate that the field has been set.
|
||||
// When multiple executors were applied to a field, if the field value were set by
|
||||
// an executor, the latter executors may skip running by consulting this context value.
|
||||
// an executor, the latter executors MAY skip running by consulting this context value.
|
||||
FieldSet
|
||||
|
||||
StopRecursion
|
||||
@ -31,6 +37,7 @@ type Engine struct {
|
||||
|
||||
// options
|
||||
errorHandler ErrorHandler
|
||||
maxMemory int64 // in bytes
|
||||
}
|
||||
|
||||
// New builds an HTTP request decoder for the specified struct type with custom options.
|
||||
@ -64,8 +71,10 @@ func New(inputStruct interface{}, opts ...Option) (*Engine, error) {
|
||||
|
||||
// Apply default options and user custom options to the engine.
|
||||
var allOptions []Option
|
||||
// defaultOptions := []Option{}
|
||||
// allOptions = append(allOptions, defaultOptions...)
|
||||
defaultOptions := []Option{
|
||||
WithMaxMemory(defaultMaxMemory),
|
||||
}
|
||||
allOptions = append(allOptions, defaultOptions...)
|
||||
allOptions = append(allOptions, opts...)
|
||||
|
||||
for _, opt := range allOptions {
|
||||
@ -79,7 +88,15 @@ func New(inputStruct interface{}, opts ...Option) (*Engine, error) {
|
||||
|
||||
// Decode decodes an HTTP request to a struct instance.
|
||||
func (e *Engine) Decode(req *http.Request) (interface{}, error) {
|
||||
if err := req.ParseForm(); err != nil {
|
||||
var err error
|
||||
ct, _, _ := mime.ParseMediaType(req.Header.Get("Content-Type"))
|
||||
if ct == "multipart/form-data" {
|
||||
err = req.ParseMultipartForm(e.maxMemory)
|
||||
} else {
|
||||
err = req.ParseForm()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rv, err := e.tree.resolve(req)
|
||||
|
@ -356,7 +356,7 @@ func TestEngine(t *testing.T) {
|
||||
|
||||
Convey("Custom decoder should work", t, func() {
|
||||
var boolType = reflect.TypeOf(bool(true))
|
||||
RegisterTypeDecoder(boolType, TypeDecoderFunc(DecodeCustomBool))
|
||||
RegisterTypeDecoder(boolType, ValueTypeDecoderFunc(DecodeCustomBool))
|
||||
type BoolInput struct {
|
||||
IsMember bool `in:"form=is_member"`
|
||||
}
|
||||
|
@ -2,167 +2,175 @@ package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TypeDecoder interface {
|
||||
Decode([]byte) (interface{}, error)
|
||||
type ValueTypeDecoder interface {
|
||||
Decode(value string) (interface{}, error)
|
||||
}
|
||||
|
||||
type TypeDecoderFunc func([]byte) (interface{}, error)
|
||||
type FileTypeDecoder interface {
|
||||
Decode(file *multipart.FileHeader) (interface{}, error)
|
||||
}
|
||||
|
||||
type ValueTypeDecoderFunc func(string) (interface{}, error)
|
||||
type FileTypeDecoderFunc func(*multipart.FileHeader) (interface{}, error)
|
||||
|
||||
// Decode calls fn(data).
|
||||
func (fn TypeDecoderFunc) Decode(data []byte) (interface{}, error) {
|
||||
return fn(data)
|
||||
func (fn ValueTypeDecoderFunc) Decode(value string) (interface{}, error) {
|
||||
return fn(value)
|
||||
}
|
||||
|
||||
var builtinDecoders = map[reflect.Type]TypeDecoder{
|
||||
reflect.TypeOf(true): TypeDecoderFunc(DecodeBool),
|
||||
reflect.TypeOf(int(0)): TypeDecoderFunc(DecodeInt),
|
||||
reflect.TypeOf(int8(0)): TypeDecoderFunc(DecodeInt8),
|
||||
reflect.TypeOf(int16(0)): TypeDecoderFunc(DecodeInt16),
|
||||
reflect.TypeOf(int32(0)): TypeDecoderFunc(DecodeInt32),
|
||||
reflect.TypeOf(int64(0)): TypeDecoderFunc(DecodeInt64),
|
||||
reflect.TypeOf(uint(0)): TypeDecoderFunc(DecodeUint),
|
||||
reflect.TypeOf(uint8(0)): TypeDecoderFunc(DecodeUint8),
|
||||
reflect.TypeOf(uint16(0)): TypeDecoderFunc(DecodeUint16),
|
||||
reflect.TypeOf(uint32(0)): TypeDecoderFunc(DecodeUint32),
|
||||
reflect.TypeOf(uint64(0)): TypeDecoderFunc(DecodeUint64),
|
||||
reflect.TypeOf(float32(0.0)): TypeDecoderFunc(DecodeFloat32),
|
||||
reflect.TypeOf(float64(0.0)): TypeDecoderFunc(DecodeFloat64),
|
||||
reflect.TypeOf(complex64(0 + 1i)): TypeDecoderFunc(DecodeComplex64),
|
||||
reflect.TypeOf(complex128(0 + 1i)): TypeDecoderFunc(DecodeComplex128),
|
||||
reflect.TypeOf(string("0")): TypeDecoderFunc(DecodeString),
|
||||
reflect.TypeOf(time.Now()): TypeDecoderFunc(DecodeTime),
|
||||
func (fn FileTypeDecoderFunc) Decode(file *multipart.FileHeader) (interface{}, error) {
|
||||
return fn(file)
|
||||
}
|
||||
|
||||
func DecoderOf(t reflect.Type) TypeDecoder {
|
||||
var builtinDecoders = map[reflect.Type]interface{}{
|
||||
reflect.TypeOf(true): ValueTypeDecoderFunc(DecodeBool),
|
||||
reflect.TypeOf(int(0)): ValueTypeDecoderFunc(DecodeInt),
|
||||
reflect.TypeOf(int8(0)): ValueTypeDecoderFunc(DecodeInt8),
|
||||
reflect.TypeOf(int16(0)): ValueTypeDecoderFunc(DecodeInt16),
|
||||
reflect.TypeOf(int32(0)): ValueTypeDecoderFunc(DecodeInt32),
|
||||
reflect.TypeOf(int64(0)): ValueTypeDecoderFunc(DecodeInt64),
|
||||
reflect.TypeOf(uint(0)): ValueTypeDecoderFunc(DecodeUint),
|
||||
reflect.TypeOf(uint8(0)): ValueTypeDecoderFunc(DecodeUint8),
|
||||
reflect.TypeOf(uint16(0)): ValueTypeDecoderFunc(DecodeUint16),
|
||||
reflect.TypeOf(uint32(0)): ValueTypeDecoderFunc(DecodeUint32),
|
||||
reflect.TypeOf(uint64(0)): ValueTypeDecoderFunc(DecodeUint64),
|
||||
reflect.TypeOf(float32(0.0)): ValueTypeDecoderFunc(DecodeFloat32),
|
||||
reflect.TypeOf(float64(0.0)): ValueTypeDecoderFunc(DecodeFloat64),
|
||||
reflect.TypeOf(complex64(0 + 1i)): ValueTypeDecoderFunc(DecodeComplex64),
|
||||
reflect.TypeOf(complex128(0 + 1i)): ValueTypeDecoderFunc(DecodeComplex128),
|
||||
reflect.TypeOf(string("0")): ValueTypeDecoderFunc(DecodeString),
|
||||
reflect.TypeOf(time.Now()): ValueTypeDecoderFunc(DecodeTime),
|
||||
}
|
||||
|
||||
func DecoderOf(t reflect.Type) interface{} {
|
||||
return builtinDecoders[t]
|
||||
}
|
||||
|
||||
func DecodeBool(data []byte) (interface{}, error) {
|
||||
return strconv.ParseBool(string(data))
|
||||
func DecodeBool(value string) (interface{}, error) {
|
||||
return strconv.ParseBool(value)
|
||||
}
|
||||
|
||||
func DecodeInt(data []byte) (interface{}, error) {
|
||||
v, err := strconv.ParseInt(string(data), 10, 64)
|
||||
func DecodeInt(value string) (interface{}, error) {
|
||||
v, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return int(v), nil
|
||||
}
|
||||
|
||||
func DecodeInt8(data []byte) (interface{}, error) {
|
||||
v, err := strconv.ParseInt(string(data), 10, 8)
|
||||
func DecodeInt8(value string) (interface{}, error) {
|
||||
v, err := strconv.ParseInt(value, 10, 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return int8(v), nil
|
||||
}
|
||||
func DecodeInt16(data []byte) (interface{}, error) {
|
||||
v, err := strconv.ParseInt(string(data), 10, 16)
|
||||
func DecodeInt16(value string) (interface{}, error) {
|
||||
v, err := strconv.ParseInt(value, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return int16(v), nil
|
||||
}
|
||||
func DecodeInt32(data []byte) (interface{}, error) {
|
||||
v, err := strconv.ParseInt(string(data), 10, 32)
|
||||
func DecodeInt32(value string) (interface{}, error) {
|
||||
v, err := strconv.ParseInt(value, 10, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return int32(v), nil
|
||||
}
|
||||
func DecodeInt64(data []byte) (interface{}, error) {
|
||||
v, err := strconv.ParseInt(string(data), 10, 64)
|
||||
func DecodeInt64(value string) (interface{}, error) {
|
||||
v, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return int64(v), nil
|
||||
}
|
||||
|
||||
func DecodeUint(data []byte) (interface{}, error) {
|
||||
v, err := strconv.ParseUint(string(data), 10, 64)
|
||||
func DecodeUint(value string) (interface{}, error) {
|
||||
v, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uint(v), nil
|
||||
}
|
||||
|
||||
func DecodeUint8(data []byte) (interface{}, error) {
|
||||
v, err := strconv.ParseUint(string(data), 10, 8)
|
||||
func DecodeUint8(value string) (interface{}, error) {
|
||||
v, err := strconv.ParseUint(value, 10, 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uint8(v), nil
|
||||
}
|
||||
func DecodeUint16(data []byte) (interface{}, error) {
|
||||
v, err := strconv.ParseUint(string(data), 10, 16)
|
||||
func DecodeUint16(value string) (interface{}, error) {
|
||||
v, err := strconv.ParseUint(value, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uint16(v), nil
|
||||
}
|
||||
func DecodeUint32(data []byte) (interface{}, error) {
|
||||
v, err := strconv.ParseUint(string(data), 10, 32)
|
||||
func DecodeUint32(value string) (interface{}, error) {
|
||||
v, err := strconv.ParseUint(value, 10, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uint32(v), nil
|
||||
}
|
||||
func DecodeUint64(data []byte) (interface{}, error) {
|
||||
v, err := strconv.ParseUint(string(data), 10, 64)
|
||||
func DecodeUint64(value string) (interface{}, error) {
|
||||
v, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uint64(v), nil
|
||||
}
|
||||
|
||||
func DecodeFloat32(data []byte) (interface{}, error) {
|
||||
v, err := strconv.ParseFloat(string(data), 32)
|
||||
func DecodeFloat32(value string) (interface{}, error) {
|
||||
v, err := strconv.ParseFloat(value, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return float32(v), nil
|
||||
}
|
||||
|
||||
func DecodeFloat64(data []byte) (interface{}, error) {
|
||||
v, err := strconv.ParseFloat(string(data), 64)
|
||||
func DecodeFloat64(value string) (interface{}, error) {
|
||||
v, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return float64(v), nil
|
||||
}
|
||||
|
||||
func DecodeComplex64(data []byte) (interface{}, error) {
|
||||
v, err := strconv.ParseComplex(string(data), 64)
|
||||
func DecodeComplex64(value string) (interface{}, error) {
|
||||
v, err := strconv.ParseComplex(value, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return complex64(v), nil
|
||||
}
|
||||
|
||||
func DecodeComplex128(data []byte) (interface{}, error) {
|
||||
v, err := strconv.ParseComplex(string(data), 128)
|
||||
func DecodeComplex128(value string) (interface{}, error) {
|
||||
v, err := strconv.ParseComplex(value, 128)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return complex128(v), nil
|
||||
}
|
||||
|
||||
func DecodeString(data []byte) (interface{}, error) {
|
||||
return string(data), nil
|
||||
func DecodeString(value string) (interface{}, error) {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// DecodeTime parses data bytes as time.Time in UTC timezone.
|
||||
// Supported formats of the data bytes are:
|
||||
// 1. RFC3339Nano string, e.g. "2006-01-02T15:04:05-07:00"
|
||||
// 2. Unix timestamp, e.g. "1136239445"
|
||||
func DecodeTime(data []byte) (interface{}, error) {
|
||||
value := string(data)
|
||||
|
||||
func DecodeTime(value string) (interface{}, error) {
|
||||
// Try parsing value as RFC3339 format.
|
||||
if t, err := time.Parse(time.RFC3339Nano, value); err == nil {
|
||||
return t.UTC(), nil
|
||||
|
@ -13,7 +13,7 @@ type Thing struct{}
|
||||
func TestBuiltinDecoders(t *testing.T) {
|
||||
|
||||
Convey("DecoderFunc implements Decoder interface", t, func() {
|
||||
v, err := TypeDecoderFunc(DecodeBool).Decode([]byte("true"))
|
||||
v, err := ValueTypeDecoderFunc(DecodeBool).Decode("true")
|
||||
So(v, ShouldBeTrue)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
@ -24,183 +24,183 @@ func TestBuiltinDecoders(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Decoder for bool type", t, func() {
|
||||
v, err := DecodeBool([]byte("true"))
|
||||
v, err := DecodeBool("true")
|
||||
So(v, ShouldBeTrue)
|
||||
So(v, ShouldHaveSameTypeAs, true)
|
||||
So(err, ShouldBeNil)
|
||||
v, err = DecodeBool([]byte("false"))
|
||||
v, err = DecodeBool("false")
|
||||
So(v, ShouldBeFalse)
|
||||
So(err, ShouldBeNil)
|
||||
v, err = DecodeBool([]byte("1"))
|
||||
v, err = DecodeBool("1")
|
||||
So(v, ShouldBeTrue)
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeBool([]byte("apple"))
|
||||
_, err = DecodeBool("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for int type", t, func() {
|
||||
v, err := DecodeInt([]byte("2045"))
|
||||
v, err := DecodeInt("2045")
|
||||
So(v, ShouldEqual, 2045)
|
||||
So(v, ShouldHaveSameTypeAs, int(1))
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeInt([]byte("apple"))
|
||||
_, err = DecodeInt("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for int8 type", t, func() {
|
||||
v, err := DecodeInt8([]byte("127"))
|
||||
v, err := DecodeInt8("127")
|
||||
So(v, ShouldEqual, 127)
|
||||
So(v, ShouldHaveSameTypeAs, int8(1))
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeInt8([]byte("128"))
|
||||
_, err = DecodeInt8("128")
|
||||
So(err, ShouldBeError)
|
||||
_, err = DecodeInt8([]byte("apple"))
|
||||
_, err = DecodeInt8("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for int16 type", t, func() {
|
||||
v, err := DecodeInt16([]byte("32767"))
|
||||
v, err := DecodeInt16("32767")
|
||||
So(v, ShouldEqual, 32767)
|
||||
So(v, ShouldHaveSameTypeAs, int16(1))
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeInt16([]byte("32768"))
|
||||
_, err = DecodeInt16("32768")
|
||||
So(err, ShouldBeError)
|
||||
_, err = DecodeInt16([]byte("apple"))
|
||||
_, err = DecodeInt16("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for int32 type", t, func() {
|
||||
v, err := DecodeInt32([]byte("2147483647"))
|
||||
v, err := DecodeInt32("2147483647")
|
||||
So(v, ShouldEqual, 2147483647)
|
||||
So(v, ShouldHaveSameTypeAs, int32(1))
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeInt32([]byte("2147483648"))
|
||||
_, err = DecodeInt32("2147483648")
|
||||
So(err, ShouldBeError)
|
||||
_, err = DecodeInt32([]byte("apple"))
|
||||
_, err = DecodeInt32("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for int64 type", t, func() {
|
||||
v, err := DecodeInt64([]byte("9223372036854775807"))
|
||||
v, err := DecodeInt64("9223372036854775807")
|
||||
So(v, ShouldEqual, 9223372036854775807)
|
||||
So(v, ShouldHaveSameTypeAs, int64(1))
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeInt64([]byte("9223372036854775808"))
|
||||
_, err = DecodeInt64("9223372036854775808")
|
||||
So(err, ShouldBeError)
|
||||
_, err = DecodeInt64([]byte("apple"))
|
||||
_, err = DecodeInt64("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for uint type", t, func() {
|
||||
v, err := DecodeUint([]byte("2045"))
|
||||
v, err := DecodeUint("2045")
|
||||
So(v, ShouldEqual, uint(2045))
|
||||
So(v, ShouldHaveSameTypeAs, uint(1))
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeUint([]byte("apple"))
|
||||
_, err = DecodeUint("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for uint8 type", t, func() {
|
||||
v, err := DecodeUint8([]byte("255"))
|
||||
v, err := DecodeUint8("255")
|
||||
So(v, ShouldEqual, uint8(255))
|
||||
So(v, ShouldHaveSameTypeAs, uint8(1))
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeUint8([]byte("256"))
|
||||
_, err = DecodeUint8("256")
|
||||
So(err, ShouldBeError)
|
||||
_, err = DecodeUint8([]byte("apple"))
|
||||
_, err = DecodeUint8("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for uint16 type", t, func() {
|
||||
v, err := DecodeUint16([]byte("65535"))
|
||||
v, err := DecodeUint16("65535")
|
||||
So(v, ShouldEqual, uint16(65535))
|
||||
So(v, ShouldHaveSameTypeAs, uint16(1))
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeUint16([]byte("65536"))
|
||||
_, err = DecodeUint16("65536")
|
||||
So(err, ShouldBeError)
|
||||
_, err = DecodeUint16([]byte("apple"))
|
||||
_, err = DecodeUint16("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for uint32 type", t, func() {
|
||||
v, err := DecodeUint32([]byte("4294967295"))
|
||||
v, err := DecodeUint32("4294967295")
|
||||
So(v, ShouldEqual, uint32(4294967295))
|
||||
So(v, ShouldHaveSameTypeAs, uint32(1))
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeUint32([]byte("4294967296"))
|
||||
_, err = DecodeUint32("4294967296")
|
||||
So(err, ShouldBeError)
|
||||
_, err = DecodeUint32([]byte("apple"))
|
||||
_, err = DecodeUint32("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for uint64 type", t, func() {
|
||||
v, err := DecodeUint64([]byte("18446744073709551615"))
|
||||
v, err := DecodeUint64("18446744073709551615")
|
||||
So(v, ShouldEqual, uint64(18446744073709551615))
|
||||
So(v, ShouldHaveSameTypeAs, uint64(1))
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeUint64([]byte("18446744073709551616"))
|
||||
_, err = DecodeUint64("18446744073709551616")
|
||||
So(err, ShouldBeError)
|
||||
_, err = DecodeUint64([]byte("apple"))
|
||||
_, err = DecodeUint64("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for float32 type", t, func() {
|
||||
v, err := DecodeFloat32([]byte("3.1415926"))
|
||||
v, err := DecodeFloat32("3.1415926")
|
||||
So(v, ShouldEqual, 3.1415926)
|
||||
So(v, ShouldHaveSameTypeAs, float32(0.0))
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeFloat32([]byte("apple"))
|
||||
_, err = DecodeFloat32("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for float64 type", t, func() {
|
||||
v, err := DecodeFloat64([]byte("3.1415926"))
|
||||
v, err := DecodeFloat64("3.1415926")
|
||||
So(v, ShouldEqual, 3.1415926)
|
||||
So(v, ShouldHaveSameTypeAs, float64(0.0))
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeFloat64([]byte("apple"))
|
||||
_, err = DecodeFloat64("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for complex64 type", t, func() {
|
||||
v, err := DecodeComplex64([]byte("1+4i"))
|
||||
v, err := DecodeComplex64("1+4i")
|
||||
So(v, ShouldEqual, 1+4i)
|
||||
So(v, ShouldHaveSameTypeAs, complex64(1+4i))
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeComplex64([]byte("apple"))
|
||||
_, err = DecodeComplex64("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for complex128 type", t, func() {
|
||||
v, err := DecodeComplex128([]byte("1+4i"))
|
||||
v, err := DecodeComplex128("1+4i")
|
||||
So(v, ShouldEqual, 1+4i)
|
||||
So(v, ShouldHaveSameTypeAs, complex128(1+4i))
|
||||
So(err, ShouldBeNil)
|
||||
_, err = DecodeComplex128([]byte("apple"))
|
||||
_, err = DecodeComplex128("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
Convey("Decoder for string type", t, func() {
|
||||
v, err := DecodeString([]byte("hello"))
|
||||
v, err := DecodeString("hello")
|
||||
So(v, ShouldEqual, "hello")
|
||||
So(v, ShouldHaveSameTypeAs, string(""))
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Decoder for time.Time type", t, func() {
|
||||
v, err := DecodeTime([]byte("1991-11-10T08:00:00+08:00"))
|
||||
v, err := DecodeTime("1991-11-10T08:00:00+08:00")
|
||||
So(v, ShouldEqual, time.Date(1991, 11, 10, 8, 0, 0, 0, time.FixedZone("Asia/Shanghai", +8*3600)))
|
||||
So(v, ShouldHaveSameTypeAs, time.Time{})
|
||||
So(v.(time.Time).Location(), ShouldEqual, time.UTC)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
v, err = DecodeTime([]byte("678088800"))
|
||||
v, err = DecodeTime("678088800")
|
||||
So(v, ShouldEqual, time.Date(1991, 6, 28, 6, 0, 0, 0, time.UTC))
|
||||
So(v, ShouldHaveSameTypeAs, time.Time{})
|
||||
So(v.(time.Time).Location(), ShouldEqual, time.UTC)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = DecodeTime([]byte("apple"))
|
||||
_, err = DecodeTime("apple")
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
}
|
||||
|
10
options.go
10
options.go
@ -12,3 +12,13 @@ func WithErrorHandler(custom ErrorHandler) Option {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithMaxMemory(maxMemory int64) Option {
|
||||
return func(c *Engine) error {
|
||||
if maxMemory <= 0 {
|
||||
return ErrInvalidMaxMemory
|
||||
}
|
||||
c.maxMemory = maxMemory
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
9
query.go
9
query.go
@ -1,7 +1,14 @@
|
||||
package httpin
|
||||
|
||||
import "mime/multipart"
|
||||
|
||||
// queryValueExtractor implements the "query" executor who extracts values from
|
||||
// the querystring of an HTTP request.
|
||||
func queryValueExtractor(ctx *DirectiveContext) error {
|
||||
return extractFromKVS(ctx, ctx.Request.URL.Query(), false)
|
||||
extractor := &Extractor{
|
||||
Form: multipart.Form{
|
||||
Value: ctx.Request.URL.Query(),
|
||||
},
|
||||
}
|
||||
return extractor.Execute(ctx)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user