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 }