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 := f.Name
72
73 var required, post bool
74
75 if n := f.Tag.Get("form"); n == "-" {
76 continue
77 } else if n != "" {
78 if p := strings.IndexByte(n, ','); p >= 0 {
79 if p > 0 {
80 name = n[:p]
81 }
82
83 rest := n[p:]
84 required = strings.Contains(rest, ",required,") || strings.HasSuffix(rest, ",required")
85 post = strings.Contains(rest, ",post,") || strings.HasSuffix(rest, ",post")
86 } else {
87 name = n
88 }
89 }
90
91 var p processor
92
93 if f.Type.Implements(interType) {
94 p = inter(false)
95 } else if reflect.PointerTo(f.Type).Implements(interType) {
96 p = inter(true)
97 } else if k := f.Type.Kind(); k == reflect.Slice || k == reflect.Ptr {
98 et := f.Type.Elem()
99
100 s := basicTypeProcessor(et, f.Tag)
101 if s == nil {
102 continue
103 }
104
105 if k == reflect.Slice {
106 p = slice{
107 processor: s,
108 typ: reflect.SliceOf(et),
109 }
110 } else {
111 p = pointer{
112 processor: s,
113 typ: et,
114 }
115 }
116 } else if k == reflect.Struct && f.Anonymous {
117 for n, p := range createTypeMap(f.Type) {
118 if _, ok := tm[n]; !ok {
119 tm[n] = processorDetails{
120 processor: p.processor,
121 Required: p.Required,
122 Post: p.Post,
123 Index: append(append(make([]int, 0, len(p.Index)+1), i), p.Index...),
124 }
125 }
126 }
127
128 continue
129 } else {
130 if p = basicTypeProcessor(f.Type, f.Tag); p == nil {
131 continue
132 }
133 }
134
135 tm[name] = processorDetails{
136 processor: p,
137 Required: required,
138 Post: post,
139 Index: []int{i},
140 }
141 }
142
143 typeMaps[t] = tm
144
145 return tm
146 }
147
148 // Process parses the form data from the request into the passed value, which
149 // must be a pointer to a struct.
150 //
151 // Form keys are assumed to be the field names unless a 'form' tag is provided
152 // with an alternate name, for example, in the following struct, the int is
153 // parse with key 'A' and the bool is parsed with key 'C'.
154 //
155 // type Example struct {
156 // A int
157 // B bool `form:"C"`
158 // }
159 //
160 // Two options can be added to the form tag to modify the processing. The
161 // 'post' option forces the processor to parse a value from the PostForm field
162 // of the Request, and the 'required' option will have an error thrown if the
163 // key in not set.
164 //
165 // Number types can also have minimums and maximums checked during processing
166 // by setting the 'min' and 'max' tags accordingly.
167 //
168 // In a similar vein, string types can utilise the 'regex' tag to set a
169 // regular expression to be matched against.
170 //
171 // Anonymous structs are traversed, but will not override more local fields.
172 //
173 // Slices of basic types can be processed, and errors returned from any such
174 // processing will be of the Errors type, which each indexed entry
175 // corresponding to the index of the processed data.
176 //
177 // Pointers to basic types can also be processed, with the type being allocated
178 // even if an error occurs.
179 //
180 // Lastly, a custom data processor can be specified by attaching a method to
181 // the field type with the following specification:
182 //
183 // ParseForm([]string) error.
184 func Process(r *http.Request, fv interface{}) error {
185 v := reflect.ValueOf(fv)
186 if v.Kind() != reflect.Ptr {
187 return ErrNeedPointer
188 }
189
190 for v.Kind() == reflect.Ptr {
191 v = v.Elem()
192 }
193
194 if v.Kind() != reflect.Struct {
195 return ErrNeedStruct
196 }
197
198 tm := getTypeMap(v.Type())
199
200 if err := r.ParseForm(); err != nil {
201 return err
202 }
203
204 var errors ErrorMap
205
206 for key, pd := range tm {
207 var (
208 val []string
209 ok bool
210 )
211
212 if pd.Post {
213 val, ok = r.PostForm[key]
214 } else {
215 val, ok = r.Form[key]
216 }
217
218 if ok {
219 if err := pd.processor.process(v.FieldByIndex(pd.Index), val); err != nil {
220 if errors == nil {
221 errors = make(ErrorMap)
222 }
223
224 errors[key] = err
225 }
226 } else if pd.Required {
227 if errors == nil {
228 errors = make(ErrorMap)
229 }
230
231 errors[key] = ErrRequiredMissing
232 }
233 }
234
235 if len(errors) > 0 {
236 return errors
237 }
238
239 return nil
240 }
241