1
0
mirror of https://github.com/ggicci/httpin.git synced 2024-11-28 08:49:05 +02:00

feat: parse multipart/form-data add a new file decoder

This commit is contained in:
Ggicci 2021-12-07 19:33:14 +08:00
parent 0cade0b6db
commit 946319ca6d
17 changed files with 419 additions and 164 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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".

View File

@ -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 {

View File

@ -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
View 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
View 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))
})
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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"`
}

View File

@ -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

View File

@ -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)
})
}

View File

@ -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
}
}

View File

@ -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)
}