-
Notifications
You must be signed in to change notification settings - Fork 42
/
form_encoder.go
157 lines (124 loc) · 3.93 KB
/
form_encoder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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
namespacePrefix string
namespaceSuffix string
}
// NewEncoder creates a new encoder instance with sane defaults
func NewEncoder() *Encoder {
e := &Encoder{
tagName: "form",
mode: ModeImplicit,
structCache: newStructCacheMap(),
embedAnonymous: true,
namespacePrefix: ".",
}
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
}
// SetNamespacePrefix sets a struct namespace prefix.
func (e *Encoder) SetNamespacePrefix(namespacePrefix string) {
e.namespacePrefix = namespacePrefix
}
// SetNamespaceSuffix sets a struct namespace suffix.
func (e *Encoder) SetNamespaceSuffix(namespaceSuffix string) {
e.namespaceSuffix = namespaceSuffix
}
// 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
}