form - form.go
1 // Package form provides an easy to use way to parse form values from an HTTP
2 // request into a struct.
3 package form // import "vimagination.zapto.org/form"
4
5 import (
6 "net/http"
7 "reflect"
8 "strings"
9 "sync"
10 )
11
12 var interType = reflect.TypeOf((*formParser)(nil)).Elem()
13
14 type processorDetails struct {
15 processor
16 Post, Required bool
17 Index []int
18 }
19
20 type typeMap map[string]processorDetails
21
22 var (
23 tmMu sync.RWMutex
24 typeMaps = make(map[reflect.Type]typeMap)
25 )
26
27 func getTypeMap(t reflect.Type) typeMap {
28 tmMu.RLock()
29 defer tmMu.RUnlock()
30
31 tm, ok := typeMaps[t]
32
33 if !ok {
34 tm = createTypeMap(t)
35 }
36
37 return tm
38 }
39
40 func basicTypeProcessor(t reflect.Type, tag reflect.StructTag) processor {
41 switch t.Kind() {
42 case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
43 return newInum(tag, t.Bits())
44 case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
45 return newUnum(tag, t.Bits())
46 case reflect.Float32, reflect.Float64:
47 return newFloat(tag, t.Bits())
48 case reflect.String:
49 return newString(tag)
50 case reflect.Bool:
51 return boolean{}
52 }
53
54 return nil
55 }
56
57 func createTypeMap(t reflect.Type) typeMap {
58 tm, ok := typeMaps[t]
59 if ok {
60 return tm
61 }
62
63 tm = make(typeMap)
64
65 for i := 0; i < t.NumField(); i++ {
66 f := t.Field(i)
67 if f.PkgPath != "" {
68 continue
69 }
70
71 name, required, post, ignore := parseTag(f.Name, f.Tag.Get("form"))
72 if ignore {
73 continue
74 }
75
76 p := createProcessor(f, tm, i)
77 if p == nil {
78 continue
79 }
80
81 tm[name] = processorDetails{
82 processor: p,
83 Required: required,
84 Post: post,
85 Index: []int{i},
86 }
87 }
88
89 typeMaps[t] = tm
90
91 return tm
92 }
93
94 func parseTag(name, tag string) (string, bool, bool, bool) {
95 var required, post bool
96
97 if tag == "-" {
98 return "", false, false, true
99 } else if tag != "" {
100 if p := strings.IndexByte(tag, ','); p >= 0 {
101 if p > 0 {
102 name = tag[:p]
103 }
104
105 rest := tag[p:]
106 required = strings.Contains(rest, ",required,") || strings.HasSuffix(rest, ",required")
107 post = strings.Contains(rest, ",post,") || strings.HasSuffix(rest, ",post")
108 } else {
109 name = tag
110 }
111 }
112
113 return name, required, post, false
114 }
115
116 func createProcessor(f reflect.StructField, tm typeMap, i int) processor {
117 if f.Type.Implements(interType) {
118 return inter(false)
119 } else if reflect.PointerTo(f.Type).Implements(interType) {
120 return inter(true)
121 } else if k := f.Type.Kind(); k == reflect.Slice || k == reflect.Ptr {
122 return createSlicePtrProcessor(f, k)
123 } else if k == reflect.Struct && f.Anonymous {
124 return createMapProcessor(f, tm, i)
125 }
126
127 return basicTypeProcessor(f.Type, f.Tag)
128 }
129
130 func createSlicePtrProcessor(f reflect.StructField, k reflect.Kind) processor {
131 et := f.Type.Elem()
132
133 s := basicTypeProcessor(et, f.Tag)
134 if s == nil {
135 return nil
136 }
137
138 if k == reflect.Slice {
139 return slice{
140 processor: s,
141 typ: reflect.SliceOf(et),
142 }
143 }
144
145 return pointer{
146 processor: s,
147 typ: et,
148 }
149 }
150
151 func createMapProcessor(f reflect.StructField, tm typeMap, i int) processor {
152 for n, p := range createTypeMap(f.Type) {
153 if _, ok := tm[n]; !ok {
154 tm[n] = processorDetails{
155 processor: p.processor,
156 Required: p.Required,
157 Post: p.Post,
158 Index: append(append(make([]int, 0, len(p.Index)+1), i), p.Index...),
159 }
160 }
161 }
162
163 return nil
164 }
165
166 // Process parses the form data from the request into the passed value, which
167 // must be a pointer to a struct.
168 //
169 // Form keys are assumed to be the field names unless a 'form' tag is provided
170 // with an alternate name, for example, in the following struct, the int is
171 // parse with key 'A' and the bool is parsed with key 'C'.
172 //
173 // type Example struct {
174 // A int
175 // B bool `form:"C"`
176 // }
177 //
178 // Two options can be added to the form tag to modify the processing. The
179 // 'post' option forces the processor to parse a value from the PostForm field
180 // of the Request, and the 'required' option will have an error thrown if the
181 // key in not set.
182 //
183 // Number types can also have minimums and maximums checked during processing
184 // by setting the 'min' and 'max' tags accordingly.
185 //
186 // In a similar vein, string types can utilise the 'regex' tag to set a
187 // regular expression to be matched against.
188 //
189 // Anonymous structs are traversed, but will not override more local fields.
190 //
191 // Slices of basic types can be processed, and errors returned from any such
192 // processing will be of the Errors type, which each indexed entry
193 // corresponding to the index of the processed data.
194 //
195 // Pointers to basic types can also be processed, with the type being allocated
196 // even if an error occurs.
197 //
198 // Lastly, a custom data processor can be specified by attaching a method to
199 // the field type with the following specification:
200 //
201 // ParseForm([]string) error.
202 func Process(r *http.Request, fv interface{}) error {
203 v := reflect.ValueOf(fv)
204 if v.Kind() != reflect.Ptr {
205 return ErrNeedPointer
206 }
207
208 for v.Kind() == reflect.Ptr {
209 v = v.Elem()
210 }
211
212 if v.Kind() != reflect.Struct {
213 return ErrNeedStruct
214 }
215
216 tm := getTypeMap(v.Type())
217
218 if err := r.ParseForm(); err != nil {
219 return err
220 }
221
222 return processForm(r, v, tm)
223 }
224
225 func processForm(r *http.Request, v reflect.Value, tm typeMap) error {
226 var errors ErrorMap
227
228 for key, pd := range tm {
229 var (
230 val []string
231 ok bool
232 )
233
234 if pd.Post {
235 val, ok = r.PostForm[key]
236 } else {
237 val, ok = r.Form[key]
238 }
239
240 if ok {
241 if err := pd.processor.process(v.FieldByIndex(pd.Index), val); err != nil {
242 if errors == nil {
243 errors = make(ErrorMap)
244 }
245
246 errors[key] = err
247 }
248 } else if pd.Required {
249 if errors == nil {
250 errors = make(ErrorMap)
251 }
252
253 errors[key] = ErrRequiredMissing
254 }
255 }
256
257 if len(errors) > 0 {
258 return errors
259 }
260
261 return nil
262 }
263