aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/google/go-querystring/query/encode.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/google/go-querystring/query/encode.go')
-rw-r--r--vendor/github.com/google/go-querystring/query/encode.go320
1 files changed, 320 insertions, 0 deletions
diff --git a/vendor/github.com/google/go-querystring/query/encode.go b/vendor/github.com/google/go-querystring/query/encode.go
new file mode 100644
index 0000000..37080b1
--- /dev/null
+++ b/vendor/github.com/google/go-querystring/query/encode.go
@@ -0,0 +1,320 @@
1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package query implements encoding of structs into URL query parameters.
6//
7// As a simple example:
8//
9// type Options struct {
10// Query string `url:"q"`
11// ShowAll bool `url:"all"`
12// Page int `url:"page"`
13// }
14//
15// opt := Options{ "foo", true, 2 }
16// v, _ := query.Values(opt)
17// fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2"
18//
19// The exact mapping between Go values and url.Values is described in the
20// documentation for the Values() function.
21package query
22
23import (
24 "bytes"
25 "fmt"
26 "net/url"
27 "reflect"
28 "strconv"
29 "strings"
30 "time"
31)
32
33var timeType = reflect.TypeOf(time.Time{})
34
35var encoderType = reflect.TypeOf(new(Encoder)).Elem()
36
37// Encoder is an interface implemented by any type that wishes to encode
38// itself into URL values in a non-standard way.
39type Encoder interface {
40 EncodeValues(key string, v *url.Values) error
41}
42
43// Values returns the url.Values encoding of v.
44//
45// Values expects to be passed a struct, and traverses it recursively using the
46// following encoding rules.
47//
48// Each exported struct field is encoded as a URL parameter unless
49//
50// - the field's tag is "-", or
51// - the field is empty and its tag specifies the "omitempty" option
52//
53// The empty values are false, 0, any nil pointer or interface value, any array
54// slice, map, or string of length zero, and any time.Time that returns true
55// for IsZero().
56//
57// The URL parameter name defaults to the struct field name but can be
58// specified in the struct field's tag value. The "url" key in the struct
59// field's tag value is the key name, followed by an optional comma and
60// options. For example:
61//
62// // Field is ignored by this package.
63// Field int `url:"-"`
64//
65// // Field appears as URL parameter "myName".
66// Field int `url:"myName"`
67//
68// // Field appears as URL parameter "myName" and the field is omitted if
69// // its value is empty
70// Field int `url:"myName,omitempty"`
71//
72// // Field appears as URL parameter "Field" (the default), but the field
73// // is skipped if empty. Note the leading comma.
74// Field int `url:",omitempty"`
75//
76// For encoding individual field values, the following type-dependent rules
77// apply:
78//
79// Boolean values default to encoding as the strings "true" or "false".
80// Including the "int" option signals that the field should be encoded as the
81// strings "1" or "0".
82//
83// time.Time values default to encoding as RFC3339 timestamps. Including the
84// "unix" option signals that the field should be encoded as a Unix time (see
85// time.Unix())
86//
87// Slice and Array values default to encoding as multiple URL values of the
88// same name. Including the "comma" option signals that the field should be
89// encoded as a single comma-delimited value. Including the "space" option
90// similarly encodes the value as a single space-delimited string. Including
91// the "semicolon" option will encode the value as a semicolon-delimited string.
92// Including the "brackets" option signals that the multiple URL values should
93// have "[]" appended to the value name. "numbered" will append a number to
94// the end of each incidence of the value name, example:
95// name0=value0&name1=value1, etc.
96//
97// Anonymous struct fields are usually encoded as if their inner exported
98// fields were fields in the outer struct, subject to the standard Go
99// visibility rules. An anonymous struct field with a name given in its URL
100// tag is treated as having that name, rather than being anonymous.
101//
102// Non-nil pointer values are encoded as the value pointed to.
103//
104// Nested structs are encoded including parent fields in value names for
105// scoping. e.g:
106//
107// "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO"
108//
109// All other values are encoded using their default string representation.
110//
111// Multiple fields that encode to the same URL parameter name will be included
112// as multiple URL values of the same name.
113func Values(v interface{}) (url.Values, error) {
114 values := make(url.Values)
115 val := reflect.ValueOf(v)
116 for val.Kind() == reflect.Ptr {
117 if val.IsNil() {
118 return values, nil
119 }
120 val = val.Elem()
121 }
122
123 if v == nil {
124 return values, nil
125 }
126
127 if val.Kind() != reflect.Struct {
128 return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind())
129 }
130
131 err := reflectValue(values, val, "")
132 return values, err
133}
134
135// reflectValue populates the values parameter from the struct fields in val.
136// Embedded structs are followed recursively (using the rules defined in the
137// Values function documentation) breadth-first.
138func reflectValue(values url.Values, val reflect.Value, scope string) error {
139 var embedded []reflect.Value
140
141 typ := val.Type()
142 for i := 0; i < typ.NumField(); i++ {
143 sf := typ.Field(i)
144 if sf.PkgPath != "" && !sf.Anonymous { // unexported
145 continue
146 }
147
148 sv := val.Field(i)
149 tag := sf.Tag.Get("url")
150 if tag == "-" {
151 continue
152 }
153 name, opts := parseTag(tag)
154 if name == "" {
155 if sf.Anonymous && sv.Kind() == reflect.Struct {
156 // save embedded struct for later processing
157 embedded = append(embedded, sv)
158 continue
159 }
160
161 name = sf.Name
162 }
163
164 if scope != "" {
165 name = scope + "[" + name + "]"
166 }
167
168 if opts.Contains("omitempty") && isEmptyValue(sv) {
169 continue
170 }
171
172 if sv.Type().Implements(encoderType) {
173 if !reflect.Indirect(sv).IsValid() {
174 sv = reflect.New(sv.Type().Elem())
175 }
176
177 m := sv.Interface().(Encoder)
178 if err := m.EncodeValues(name, &values); err != nil {
179 return err
180 }
181 continue
182 }
183
184 if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array {
185 var del byte
186 if opts.Contains("comma") {
187 del = ','
188 } else if opts.Contains("space") {
189 del = ' '
190 } else if opts.Contains("semicolon") {
191 del = ';'
192 } else if opts.Contains("brackets") {
193 name = name + "[]"
194 }
195
196 if del != 0 {
197 s := new(bytes.Buffer)
198 first := true
199 for i := 0; i < sv.Len(); i++ {
200 if first {
201 first = false
202 } else {
203 s.WriteByte(del)
204 }
205 s.WriteString(valueString(sv.Index(i), opts))
206 }
207 values.Add(name, s.String())
208 } else {
209 for i := 0; i < sv.Len(); i++ {
210 k := name
211 if opts.Contains("numbered") {
212 k = fmt.Sprintf("%s%d", name, i)
213 }
214 values.Add(k, valueString(sv.Index(i), opts))
215 }
216 }
217 continue
218 }
219
220 for sv.Kind() == reflect.Ptr {
221 if sv.IsNil() {
222 break
223 }
224 sv = sv.Elem()
225 }
226
227 if sv.Type() == timeType {
228 values.Add(name, valueString(sv, opts))
229 continue
230 }
231
232 if sv.Kind() == reflect.Struct {
233 reflectValue(values, sv, name)
234 continue
235 }
236
237 values.Add(name, valueString(sv, opts))
238 }
239
240 for _, f := range embedded {
241 if err := reflectValue(values, f, scope); err != nil {
242 return err
243 }
244 }
245
246 return nil
247}
248
249// valueString returns the string representation of a value.
250func valueString(v reflect.Value, opts tagOptions) string {
251 for v.Kind() == reflect.Ptr {
252 if v.IsNil() {
253 return ""
254 }
255 v = v.Elem()
256 }
257
258 if v.Kind() == reflect.Bool && opts.Contains("int") {
259 if v.Bool() {
260 return "1"
261 }
262 return "0"
263 }
264
265 if v.Type() == timeType {
266 t := v.Interface().(time.Time)
267 if opts.Contains("unix") {
268 return strconv.FormatInt(t.Unix(), 10)
269 }
270 return t.Format(time.RFC3339)
271 }
272
273 return fmt.Sprint(v.Interface())
274}
275
276// isEmptyValue checks if a value should be considered empty for the purposes
277// of omitting fields with the "omitempty" option.
278func isEmptyValue(v reflect.Value) bool {
279 switch v.Kind() {
280 case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
281 return v.Len() == 0
282 case reflect.Bool:
283 return !v.Bool()
284 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
285 return v.Int() == 0
286 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
287 return v.Uint() == 0
288 case reflect.Float32, reflect.Float64:
289 return v.Float() == 0
290 case reflect.Interface, reflect.Ptr:
291 return v.IsNil()
292 }
293
294 if v.Type() == timeType {
295 return v.Interface().(time.Time).IsZero()
296 }
297
298 return false
299}
300
301// tagOptions is the string following a comma in a struct field's "url" tag, or
302// the empty string. It does not include the leading comma.
303type tagOptions []string
304
305// parseTag splits a struct field's url tag into its name and comma-separated
306// options.
307func parseTag(tag string) (string, tagOptions) {
308 s := strings.Split(tag, ",")
309 return s[0], s[1:]
310}
311
312// Contains checks whether the tagOptions contains the specified option.
313func (o tagOptions) Contains(option string) bool {
314 for _, s := range o {
315 if s == option {
316 return true
317 }
318 }
319 return false
320}