aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/google/go-querystring/query
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/google/go-querystring/query')
-rw-r--r--vendor/github.com/google/go-querystring/query/encode.go320
-rw-r--r--vendor/github.com/google/go-querystring/query/encode_test.go328
2 files changed, 648 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}
diff --git a/vendor/github.com/google/go-querystring/query/encode_test.go b/vendor/github.com/google/go-querystring/query/encode_test.go
new file mode 100644
index 0000000..77bea5a
--- /dev/null
+++ b/vendor/github.com/google/go-querystring/query/encode_test.go
@@ -0,0 +1,328 @@
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
5package query
6
7import (
8 "fmt"
9 "net/url"
10 "reflect"
11 "testing"
12 "time"
13)
14
15type Nested struct {
16 A SubNested `url:"a"`
17 B *SubNested `url:"b"`
18 Ptr *SubNested `url:"ptr,omitempty"`
19}
20
21type SubNested struct {
22 Value string `url:"value"`
23}
24
25func TestValues_types(t *testing.T) {
26 str := "string"
27 strPtr := &str
28 timeVal := time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC)
29
30 tests := []struct {
31 in interface{}
32 want url.Values
33 }{
34 {
35 // basic primitives
36 struct {
37 A string
38 B int
39 C uint
40 D float32
41 E bool
42 }{},
43 url.Values{
44 "A": {""},
45 "B": {"0"},
46 "C": {"0"},
47 "D": {"0"},
48 "E": {"false"},
49 },
50 },
51 {
52 // pointers
53 struct {
54 A *string
55 B *int
56 C **string
57 D *time.Time
58 }{
59 A: strPtr,
60 C: &strPtr,
61 D: &timeVal,
62 },
63 url.Values{
64 "A": {str},
65 "B": {""},
66 "C": {str},
67 "D": {"2000-01-01T12:34:56Z"},
68 },
69 },
70 {
71 // slices and arrays
72 struct {
73 A []string
74 B []string `url:",comma"`
75 C []string `url:",space"`
76 D [2]string
77 E [2]string `url:",comma"`
78 F [2]string `url:",space"`
79 G []*string `url:",space"`
80 H []bool `url:",int,space"`
81 I []string `url:",brackets"`
82 J []string `url:",semicolon"`
83 K []string `url:",numbered"`
84 }{
85 A: []string{"a", "b"},
86 B: []string{"a", "b"},
87 C: []string{"a", "b"},
88 D: [2]string{"a", "b"},
89 E: [2]string{"a", "b"},
90 F: [2]string{"a", "b"},
91 G: []*string{&str, &str},
92 H: []bool{true, false},
93 I: []string{"a", "b"},
94 J: []string{"a", "b"},
95 K: []string{"a", "b"},
96 },
97 url.Values{
98 "A": {"a", "b"},
99 "B": {"a,b"},
100 "C": {"a b"},
101 "D": {"a", "b"},
102 "E": {"a,b"},
103 "F": {"a b"},
104 "G": {"string string"},
105 "H": {"1 0"},
106 "I[]": {"a", "b"},
107 "J": {"a;b"},
108 "K0": {"a"},
109 "K1": {"b"},
110 },
111 },
112 {
113 // other types
114 struct {
115 A time.Time
116 B time.Time `url:",unix"`
117 C bool `url:",int"`
118 D bool `url:",int"`
119 }{
120 A: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC),
121 B: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC),
122 C: true,
123 D: false,
124 },
125 url.Values{
126 "A": {"2000-01-01T12:34:56Z"},
127 "B": {"946730096"},
128 "C": {"1"},
129 "D": {"0"},
130 },
131 },
132 {
133 struct {
134 Nest Nested `url:"nest"`
135 }{
136 Nested{
137 A: SubNested{
138 Value: "that",
139 },
140 },
141 },
142 url.Values{
143 "nest[a][value]": {"that"},
144 "nest[b]": {""},
145 },
146 },
147 {
148 struct {
149 Nest Nested `url:"nest"`
150 }{
151 Nested{
152 Ptr: &SubNested{
153 Value: "that",
154 },
155 },
156 },
157 url.Values{
158 "nest[a][value]": {""},
159 "nest[b]": {""},
160 "nest[ptr][value]": {"that"},
161 },
162 },
163 {
164 nil,
165 url.Values{},
166 },
167 }
168
169 for i, tt := range tests {
170 v, err := Values(tt.in)
171 if err != nil {
172 t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err)
173 }
174
175 if !reflect.DeepEqual(tt.want, v) {
176 t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want)
177 }
178 }
179}
180
181func TestValues_omitEmpty(t *testing.T) {
182 str := ""
183 s := struct {
184 a string
185 A string
186 B string `url:",omitempty"`
187 C string `url:"-"`
188 D string `url:"omitempty"` // actually named omitempty, not an option
189 E *string `url:",omitempty"`
190 }{E: &str}
191
192 v, err := Values(s)
193 if err != nil {
194 t.Errorf("Values(%v) returned error: %v", s, err)
195 }
196
197 want := url.Values{
198 "A": {""},
199 "omitempty": {""},
200 "E": {""}, // E is included because the pointer is not empty, even though the string being pointed to is
201 }
202 if !reflect.DeepEqual(want, v) {
203 t.Errorf("Values(%v) returned %v, want %v", s, v, want)
204 }
205}
206
207type A struct {
208 B
209}
210
211type B struct {
212 C string
213}
214
215type D struct {
216 B
217 C string
218}
219
220type e struct {
221 B
222 C string
223}
224
225type F struct {
226 e
227}
228
229func TestValues_embeddedStructs(t *testing.T) {
230 tests := []struct {
231 in interface{}
232 want url.Values
233 }{
234 {
235 A{B{C: "foo"}},
236 url.Values{"C": {"foo"}},
237 },
238 {
239 D{B: B{C: "bar"}, C: "foo"},
240 url.Values{"C": {"foo", "bar"}},
241 },
242 {
243 F{e{B: B{C: "bar"}, C: "foo"}}, // With unexported embed
244 url.Values{"C": {"foo", "bar"}},
245 },
246 }
247
248 for i, tt := range tests {
249 v, err := Values(tt.in)
250 if err != nil {
251 t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err)
252 }
253
254 if !reflect.DeepEqual(tt.want, v) {
255 t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want)
256 }
257 }
258}
259
260func TestValues_invalidInput(t *testing.T) {
261 _, err := Values("")
262 if err == nil {
263 t.Errorf("expected Values() to return an error on invalid input")
264 }
265}
266
267type EncodedArgs []string
268
269func (m EncodedArgs) EncodeValues(key string, v *url.Values) error {
270 for i, arg := range m {
271 v.Set(fmt.Sprintf("%s.%d", key, i), arg)
272 }
273 return nil
274}
275
276func TestValues_Marshaler(t *testing.T) {
277 s := struct {
278 Args EncodedArgs `url:"arg"`
279 }{[]string{"a", "b", "c"}}
280 v, err := Values(s)
281 if err != nil {
282 t.Errorf("Values(%q) returned error: %v", s, err)
283 }
284
285 want := url.Values{
286 "arg.0": {"a"},
287 "arg.1": {"b"},
288 "arg.2": {"c"},
289 }
290 if !reflect.DeepEqual(want, v) {
291 t.Errorf("Values(%q) returned %v, want %v", s, v, want)
292 }
293}
294
295func TestValues_MarshalerWithNilPointer(t *testing.T) {
296 s := struct {
297 Args *EncodedArgs `url:"arg"`
298 }{}
299 v, err := Values(s)
300 if err != nil {
301 t.Errorf("Values(%v) returned error: %v", s, err)
302 }
303
304 want := url.Values{}
305 if !reflect.DeepEqual(want, v) {
306 t.Errorf("Values(%v) returned %v, want %v", s, v, want)
307 }
308}
309
310func TestTagParsing(t *testing.T) {
311 name, opts := parseTag("field,foobar,foo")
312 if name != "field" {
313 t.Fatalf("name = %q, want field", name)
314 }
315 for _, tt := range []struct {
316 opt string
317 want bool
318 }{
319 {"foobar", true},
320 {"foo", true},
321 {"bar", false},
322 {"field", false},
323 } {
324 if opts.Contains(tt.opt) != tt.want {
325 t.Errorf("Contains(%q) = %v", tt.opt, !tt.want)
326 }
327 }
328}