-
Notifications
You must be signed in to change notification settings - Fork 24
/
data.go
executable file
·332 lines (302 loc) · 9.99 KB
/
data.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
// Copyright 2015 Alex Browne. All rights reserved.
// Use of this source code is governed by the MIT
// license, which can be found in the LICENSE file.
package forms
import (
"encoding/json"
"fmt"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"strconv"
"strings"
)
// DefaultMaxFormSize is the default maximum form size (in bytes) used by the Parse function.
const DefaultMaxFormSize = 500000
// Data holds data obtained from the request body and url query parameters.
// Because Data is built from multiple sources, sometimes there will be more
// than one value for a given key. You can use Get, Set, Add, and Del to access
// the first element for a given key or access the Values and Files properties directly
// to access additional elements for a given key. You can also use helper methods to convert
// the first value for a given key to a different type (e.g. bool or int).
type Data struct {
// Values holds any basic key-value string data
// This includes all fields from a json body or
// urlencoded form, and the form fields only (not
// files) from a multipart form
Values url.Values
// Files holds files from a multipart form only.
// For any other type of request, it will always
// be empty. Files only supports one file per key,
// since this is by far the most common use. If you
// need to have more than one file per key, parse the
// files manually using req.MultipartForm.File.
Files map[string]*multipart.FileHeader
// jsonBody holds the original body of the request.
// Only available for json requests.
jsonBody []byte
}
func newData() *Data {
return &Data{
Values: url.Values{},
Files: map[string]*multipart.FileHeader{},
}
}
// ParseMax parses the request body and url query parameters into
// Data. The content in the body of the request has a higher priority,
// will be added to Data first, and will be the result of any operation
// which gets the first element for a given key (e.g. Get, GetInt, or GetBool).
func ParseMax(req *http.Request, max int64) (*Data, error) {
data := newData()
contentType := req.Header.Get("Content-Type")
if strings.Contains(contentType, "multipart/form-data") {
if err := req.ParseMultipartForm(max); err != nil {
return nil, err
}
for key, vals := range req.MultipartForm.Value {
for _, val := range vals {
data.Add(key, val)
}
}
for key, files := range req.MultipartForm.File {
if len(files) != 0 {
data.AddFile(key, files[0])
}
}
} else if strings.Contains(contentType, "form-urlencoded") {
if err := req.ParseForm(); err != nil {
return nil, err
}
for key, vals := range req.PostForm {
for _, val := range vals {
data.Add(key, val)
}
}
} else if strings.Contains(contentType, "application/json") {
body, err := ioutil.ReadAll(req.Body)
if err != nil {
return nil, err
}
data.jsonBody = body
if err := parseJSON(data.Values, data.jsonBody); err != nil {
return nil, err
}
}
for key, vals := range req.URL.Query() {
for _, val := range vals {
data.Add(key, val)
}
}
return data, nil
}
// Parse uses the default max form size defined above and calls ParseMax
func Parse(req *http.Request) (*Data, error) {
return ParseMax(req, DefaultMaxFormSize)
}
// CreateFromMap returns a Data object with keys and values matching
// the map.
func CreateFromMap(m map[string]string) *Data {
data := newData()
for key, value := range m {
data.Add(key, value)
}
return data
}
func parseJSON(values url.Values, body []byte) error {
if len(body) == 0 {
// don't attempt to parse empty bodies
return nil
}
rawData := map[string]interface{}{}
if err := json.Unmarshal(body, &rawData); err != nil {
return err
}
// Whatever the underlying type is, we need to convert it to a
// string. There are only a few possible types, so we can just
// do a type switch over the possibilities.
for key, val := range rawData {
switch val.(type) {
case string, bool, float64:
values.Add(key, fmt.Sprint(val))
case nil:
values.Add(key, "")
case map[string]interface{}, []interface{}:
// for more complicated data structures, convert back to
// a JSON string and let user decide how to unmarshal
jsonVal, err := json.Marshal(val)
if err != nil {
return err
}
values.Add(key, string(jsonVal))
}
}
return nil
}
// Add adds the value to key. It appends to any existing values associated with key.
func (d *Data) Add(key string, value string) {
d.Values.Add(key, value)
}
// AddFile adds the multipart form file to data with the given key.
func (d *Data) AddFile(key string, file *multipart.FileHeader) {
d.Files[key] = file
}
// Del deletes the values associated with key.
func (d *Data) Del(key string) {
d.Values.Del(key)
}
// DelFile deletes the file associated with key (if any).
// If there is no file associated with key, it does nothing.
func (d *Data) DelFile(key string) {
delete(d.Files, key)
}
// Encode encodes the values into “URL encoded” form ("bar=baz&foo=quux") sorted by key.
// Any files in d will be ignored because there is no direct way to convert a file to a
// URL encoded value.
func (d *Data) Encode() string {
return d.Values.Encode()
}
// Get gets the first value associated with the given key. If there are no values
// associated with the key, Get returns the empty string. To access multiple values,
// use the map directly.
func (d Data) Get(key string) string {
return d.Values.Get(key)
}
// GetFile returns the multipart form file associated with key, if any, as a *multipart.FileHeader.
// If there is no file associated with key, it returns nil. If you just want the body of the
// file, use GetFileBytes.
func (d Data) GetFile(key string) *multipart.FileHeader {
return d.Files[key]
}
// Set sets the key to value. It replaces any existing values.
func (d *Data) Set(key string, value string) {
d.Values.Set(key, value)
}
// KeyExists returns true iff data.Values[key] exists. When parsing a request body, the key
// is considered to be in existence if it was provided in the request body, even if its value
// is empty.
func (d Data) KeyExists(key string) bool {
_, found := d.Values[key]
return found
}
// FileExists returns true iff data.Files[key] exists. When parsing a request body, the key
// is considered to be in existence if it was provided in the request body, even if the file
// is empty.
func (d Data) FileExists(key string) bool {
_, found := d.Files[key]
return found
}
// GetInt returns the first element in data[key] converted to an int.
func (d Data) GetInt(key string) int {
if !d.KeyExists(key) || len(d.Values[key]) == 0 {
return 0
}
str := d.Get(key)
if result, err := strconv.Atoi(str); err != nil {
panic(err)
} else {
return result
}
}
// GetFloat returns the first element in data[key] converted to a float.
func (d Data) GetFloat(key string) float64 {
if !d.KeyExists(key) || len(d.Values[key]) == 0 {
return 0.0
}
str := d.Get(key)
if result, err := strconv.ParseFloat(str, 64); err != nil {
panic(err)
} else {
return result
}
}
// GetBool returns the first element in data[key] converted to a bool.
func (d Data) GetBool(key string) bool {
if !d.KeyExists(key) || len(d.Values[key]) == 0 {
return false
}
str := d.Get(key)
if result, err := strconv.ParseBool(str); err != nil {
panic(err)
} else {
return result
}
}
// GetBytes returns the first element in data[key] converted to a slice of bytes.
func (d Data) GetBytes(key string) []byte {
return []byte(d.Get(key))
}
// GetFileBytes returns the body of the file associated with key. If there is no
// file associated with key, it returns nil (not an error). It may return an error if
// there was a problem reading the file. If you need to know whether or not the file
// exists (i.e. whether it was provided in the request), use the FileExists method.
func (d Data) GetFileBytes(key string) ([]byte, error) {
fileHeader, found := d.Files[key]
if !found {
return nil, nil
} else {
file, err := fileHeader.Open()
if err != nil {
return nil, err
}
return ioutil.ReadAll(file)
}
}
// GetStringsSplit returns the first element in data[key] split into a slice delimited by delim.
func (d Data) GetStringsSplit(key string, delim string) []string {
if !d.KeyExists(key) || len(d.Values[key]) == 0 {
return nil
}
return strings.Split(d.Values[key][0], delim)
}
// BindJSON binds v to the json data in the request body. It calls json.Unmarshal and
// sets the value of v.
func (d Data) BindJSON(v interface{}) error {
if len(d.jsonBody) == 0 {
return nil
}
return json.Unmarshal(d.jsonBody, v)
}
// GetMapFromJSON assumes that the first element in data[key] is a json string, attempts to
// unmarshal it into a map[string]interface{}, and if successful, returns the result. If
// unmarshaling was not successful, returns an error.
func (d Data) GetMapFromJSON(key string) (map[string]interface{}, error) {
if !d.KeyExists(key) || len(d.Values[key]) == 0 {
return nil, nil
}
result := map[string]interface{}{}
if err := json.Unmarshal([]byte(d.Get(key)), &result); err != nil {
return nil, err
} else {
return result, nil
}
}
// GetSliceFromJSON assumes that the first element in data[key] is a json string, attempts to
// unmarshal it into a []interface{}, and if successful, returns the result. If unmarshaling
// was not successful, returns an error.
func (d Data) GetSliceFromJSON(key string) ([]interface{}, error) {
if !d.KeyExists(key) || len(d.Values[key]) == 0 {
return nil, nil
}
result := []interface{}{}
if err := json.Unmarshal([]byte(d.Get(key)), &result); err != nil {
return nil, err
} else {
return result, nil
}
}
// GetAndUnmarshalJSON assumes that the first element in data[key] is a json string and
// attempts to unmarshal it into v. If unmarshaling was not successful, returns an error.
// v should be a pointer to some data structure.
func (d Data) GetAndUnmarshalJSON(key string, v interface{}) error {
if err := json.Unmarshal([]byte(d.Get(key)), v); err != nil {
return err
}
return nil
}
// Validator returns a Validator which can be used to easily validate data.
func (d *Data) Validator() *Validator {
return &Validator{
data: d,
}
}