145 lines
3.6 KiB
Go
145 lines
3.6 KiB
Go
package form
|
|
|
|
import (
|
|
"bytes"
|
|
"net/url"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// EncodeCustomTypeFunc allows for registering/overriding types to be parsed.
|
|
type EncodeCustomTypeFunc func(x interface{}) ([]string, error)
|
|
|
|
// EncodeErrors is a map of errors encountered during form encoding
|
|
type EncodeErrors map[string]error
|
|
|
|
func (e EncodeErrors) Error() string {
|
|
buff := bytes.NewBufferString(blank)
|
|
|
|
for k, err := range e {
|
|
buff.WriteString(fieldNS)
|
|
buff.WriteString(k)
|
|
buff.WriteString(errorText)
|
|
buff.WriteString(err.Error())
|
|
buff.WriteString("\n")
|
|
}
|
|
|
|
return strings.TrimSpace(buff.String())
|
|
}
|
|
|
|
// An InvalidEncodeError describes an invalid argument passed to Encode.
|
|
type InvalidEncodeError struct {
|
|
Type reflect.Type
|
|
}
|
|
|
|
func (e *InvalidEncodeError) Error() string {
|
|
|
|
if e.Type == nil {
|
|
return "form: Encode(nil)"
|
|
}
|
|
|
|
return "form: Encode(nil " + e.Type.String() + ")"
|
|
}
|
|
|
|
// Encoder is the main encode instance
|
|
type Encoder struct {
|
|
tagName string
|
|
structCache *structCacheMap
|
|
customTypeFuncs map[reflect.Type]EncodeCustomTypeFunc
|
|
dataPool *sync.Pool
|
|
mode Mode
|
|
embedAnonymous bool
|
|
}
|
|
|
|
// NewEncoder creates a new encoder instance with sane defaults
|
|
func NewEncoder() *Encoder {
|
|
|
|
e := &Encoder{
|
|
tagName: "form",
|
|
mode: ModeImplicit,
|
|
structCache: newStructCacheMap(),
|
|
embedAnonymous: true,
|
|
}
|
|
|
|
e.dataPool = &sync.Pool{New: func() interface{} {
|
|
return &encoder{
|
|
e: e,
|
|
namespace: make([]byte, 0, 64),
|
|
}
|
|
}}
|
|
|
|
return e
|
|
}
|
|
|
|
// SetTagName sets the given tag name to be used by the encoder.
|
|
// Default is "form"
|
|
func (e *Encoder) SetTagName(tagName string) {
|
|
e.tagName = tagName
|
|
}
|
|
|
|
// SetMode sets the mode the encoder should run
|
|
// Default is ModeImplicit
|
|
func (e *Encoder) SetMode(mode Mode) {
|
|
e.mode = mode
|
|
}
|
|
|
|
// SetAnonymousMode sets the mode the encoder should run
|
|
// Default is AnonymousEmbed
|
|
func (e *Encoder) SetAnonymousMode(mode AnonymousMode) {
|
|
e.embedAnonymous = mode == AnonymousEmbed
|
|
}
|
|
|
|
// RegisterTagNameFunc registers a custom tag name parser function
|
|
// NOTE: This method is not thread-safe it is intended that these all be registered prior to any parsing
|
|
//
|
|
// ADDITIONAL: once a custom function has been registered the default, or custom set, tag name is ignored
|
|
// and relies 100% on the function for the name data. The return value WILL BE CACHED and so return value
|
|
// must be consistent.
|
|
func (e *Encoder) RegisterTagNameFunc(fn TagNameFunc) {
|
|
e.structCache.tagFn = fn
|
|
}
|
|
|
|
// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
|
|
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any parsing
|
|
func (e *Encoder) RegisterCustomTypeFunc(fn EncodeCustomTypeFunc, types ...interface{}) {
|
|
|
|
if e.customTypeFuncs == nil {
|
|
e.customTypeFuncs = map[reflect.Type]EncodeCustomTypeFunc{}
|
|
}
|
|
|
|
for _, t := range types {
|
|
e.customTypeFuncs[reflect.TypeOf(t)] = fn
|
|
}
|
|
}
|
|
|
|
// Encode encodes the given values and sets the corresponding struct values
|
|
func (e *Encoder) Encode(v interface{}) (values url.Values, err error) {
|
|
|
|
val, kind := ExtractType(reflect.ValueOf(v))
|
|
|
|
if kind == reflect.Ptr || kind == reflect.Interface || kind == reflect.Invalid {
|
|
return nil, &InvalidEncodeError{reflect.TypeOf(v)}
|
|
}
|
|
|
|
enc := e.dataPool.Get().(*encoder)
|
|
enc.values = make(url.Values)
|
|
|
|
if kind == reflect.Struct && val.Type() != timeType {
|
|
enc.traverseStruct(val, enc.namespace[0:0], -1)
|
|
} else {
|
|
enc.setFieldByType(val, enc.namespace[0:0], -1, false)
|
|
}
|
|
|
|
if len(enc.errs) > 0 {
|
|
err = enc.errs
|
|
enc.errs = nil
|
|
}
|
|
|
|
values = enc.values
|
|
|
|
e.dataPool.Put(enc)
|
|
|
|
return
|
|
}
|