1
2
3
4
5 package query
6
7 import (
8 "errors"
9 "fmt"
10 "net/url"
11 "reflect"
12 "testing"
13 "time"
14
15 "github.com/google/go-cmp/cmp"
16 )
17
18
19 func testValue(t *testing.T, input interface{}, want url.Values) {
20 v, err := Values(input)
21 if err != nil {
22 t.Errorf("Values(%q) returned error: %v", input, err)
23 }
24 if diff := cmp.Diff(want, v); diff != "" {
25 t.Errorf("Values(%#v) mismatch:\n%s", input, diff)
26 }
27 }
28
29 func TestValues_BasicTypes(t *testing.T) {
30 tests := []struct {
31 input interface{}
32 want url.Values
33 }{
34
35 {struct{ V string }{}, url.Values{"V": {""}}},
36 {struct{ V int }{}, url.Values{"V": {"0"}}},
37 {struct{ V uint }{}, url.Values{"V": {"0"}}},
38 {struct{ V float32 }{}, url.Values{"V": {"0"}}},
39 {struct{ V bool }{}, url.Values{"V": {"false"}}},
40
41
42 {struct{ V string }{"v"}, url.Values{"V": {"v"}}},
43 {struct{ V int }{1}, url.Values{"V": {"1"}}},
44 {struct{ V uint }{1}, url.Values{"V": {"1"}}},
45 {struct{ V float32 }{0.1}, url.Values{"V": {"0.1"}}},
46 {struct{ V bool }{true}, url.Values{"V": {"true"}}},
47
48
49 {
50 struct {
51 V bool `url:",int"`
52 }{false},
53 url.Values{"V": {"0"}},
54 },
55 {
56 struct {
57 V bool `url:",int"`
58 }{true},
59 url.Values{"V": {"1"}},
60 },
61
62
63 {
64 struct {
65 V time.Time
66 }{time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC)},
67 url.Values{"V": {"2000-01-01T12:34:56Z"}},
68 },
69 {
70 struct {
71 V time.Time `url:",unix"`
72 }{time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC)},
73 url.Values{"V": {"946730096"}},
74 },
75 {
76 struct {
77 V time.Time `url:",unixmilli"`
78 }{time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC)},
79 url.Values{"V": {"946730096000"}},
80 },
81 {
82 struct {
83 V time.Time `url:",unixnano"`
84 }{time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC)},
85 url.Values{"V": {"946730096000000000"}},
86 },
87 {
88 struct {
89 V time.Time `layout:"2006-01-02"`
90 }{time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC)},
91 url.Values{"V": {"2000-01-01"}},
92 },
93 }
94
95 for _, tt := range tests {
96 testValue(t, tt.input, tt.want)
97 }
98 }
99
100 func TestValues_Pointers(t *testing.T) {
101 str := "s"
102 strPtr := &str
103
104 tests := []struct {
105 input interface{}
106 want url.Values
107 }{
108
109 {struct{ V *string }{}, url.Values{"V": {""}}},
110 {struct{ V *int }{}, url.Values{"V": {""}}},
111
112
113 {struct{ V *string }{&str}, url.Values{"V": {"s"}}},
114 {struct{ V **string }{&strPtr}, url.Values{"V": {"s"}}},
115
116
117 {struct{ V []*string }{}, url.Values{}},
118 {struct{ V []*string }{[]*string{&str, &str}}, url.Values{"V": {"s", "s"}}},
119
120
121 {struct{ V *[]string }{}, url.Values{"V": {""}}},
122 {struct{ V *[]string }{&[]string{"a", "b"}}, url.Values{"V": {"a", "b"}}},
123
124
125 {(*struct{})(nil), url.Values{}},
126 {&struct{}{}, url.Values{}},
127 {&struct{ V string }{}, url.Values{"V": {""}}},
128 {&struct{ V string }{"v"}, url.Values{"V": {"v"}}},
129 }
130
131 for _, tt := range tests {
132 testValue(t, tt.input, tt.want)
133 }
134 }
135
136 func TestValues_Slices(t *testing.T) {
137 tests := []struct {
138 input interface{}
139 want url.Values
140 }{
141
142 {
143 struct{ V []string }{},
144 url.Values{},
145 },
146 {
147 struct{ V []string }{[]string{"a", "b"}},
148 url.Values{"V": {"a", "b"}},
149 },
150 {
151 struct {
152 V []string `url:",comma"`
153 }{[]string{"a", "b"}},
154 url.Values{"V": {"a,b"}},
155 },
156 {
157 struct {
158 V []string `url:",space"`
159 }{[]string{"a", "b"}},
160 url.Values{"V": {"a b"}},
161 },
162 {
163 struct {
164 V []string `url:",semicolon"`
165 }{[]string{"a", "b"}},
166 url.Values{"V": {"a;b"}},
167 },
168 {
169 struct {
170 V []string `url:",brackets"`
171 }{[]string{"a", "b"}},
172 url.Values{"V[]": {"a", "b"}},
173 },
174 {
175 struct {
176 V []string `url:",numbered"`
177 }{[]string{"a", "b"}},
178 url.Values{"V0": {"a"}, "V1": {"b"}},
179 },
180
181
182 {
183 struct{ V [2]string }{},
184 url.Values{"V": {"", ""}},
185 },
186 {
187 struct{ V [2]string }{[2]string{"a", "b"}},
188 url.Values{"V": {"a", "b"}},
189 },
190 {
191 struct {
192 V [2]string `url:",comma"`
193 }{[2]string{"a", "b"}},
194 url.Values{"V": {"a,b"}},
195 },
196 {
197 struct {
198 V [2]string `url:",space"`
199 }{[2]string{"a", "b"}},
200 url.Values{"V": {"a b"}},
201 },
202 {
203 struct {
204 V [2]string `url:",semicolon"`
205 }{[2]string{"a", "b"}},
206 url.Values{"V": {"a;b"}},
207 },
208 {
209 struct {
210 V [2]string `url:",brackets"`
211 }{[2]string{"a", "b"}},
212 url.Values{"V[]": {"a", "b"}},
213 },
214 {
215 struct {
216 V [2]string `url:",numbered"`
217 }{[2]string{"a", "b"}},
218 url.Values{"V0": {"a"}, "V1": {"b"}},
219 },
220
221
222 {
223 struct {
224 V []string `del:","`
225 }{[]string{"a", "b"}},
226 url.Values{"V": {"a,b"}},
227 },
228 {
229 struct {
230 V []string `del:"|"`
231 }{[]string{"a", "b"}},
232 url.Values{"V": {"a|b"}},
233 },
234 {
235 struct {
236 V []string `del:"🥑"`
237 }{[]string{"a", "b"}},
238 url.Values{"V": {"a🥑b"}},
239 },
240
241
242 {
243 struct {
244 V []bool `url:",space,int"`
245 }{[]bool{true, false}},
246 url.Values{"V": {"1 0"}},
247 },
248 }
249
250 for _, tt := range tests {
251 testValue(t, tt.input, tt.want)
252 }
253 }
254
255 func TestValues_NestedTypes(t *testing.T) {
256 type SubNested struct {
257 Value string `url:"value"`
258 }
259
260 type Nested struct {
261 A SubNested `url:"a"`
262 B *SubNested `url:"b"`
263 Ptr *SubNested `url:"ptr,omitempty"`
264 }
265
266 tests := []struct {
267 input interface{}
268 want url.Values
269 }{
270 {
271 struct {
272 Nest Nested `url:"nest"`
273 }{
274 Nested{
275 A: SubNested{
276 Value: "v",
277 },
278 },
279 },
280 url.Values{
281 "nest[a][value]": {"v"},
282 "nest[b]": {""},
283 },
284 },
285 {
286 struct {
287 Nest Nested `url:"nest"`
288 }{
289 Nested{
290 Ptr: &SubNested{
291 Value: "v",
292 },
293 },
294 },
295 url.Values{
296 "nest[a][value]": {""},
297 "nest[b]": {""},
298 "nest[ptr][value]": {"v"},
299 },
300 },
301 {
302 nil,
303 url.Values{},
304 },
305 }
306
307 for _, tt := range tests {
308 testValue(t, tt.input, tt.want)
309 }
310 }
311
312 func TestValues_OmitEmpty(t *testing.T) {
313 str := ""
314
315 tests := []struct {
316 input interface{}
317 want url.Values
318 }{
319 {struct{ v string }{}, url.Values{}},
320 {
321 struct {
322 V string `url:",omitempty"`
323 }{},
324 url.Values{},
325 },
326 {
327 struct {
328 V string `url:"-"`
329 }{},
330 url.Values{},
331 },
332 {
333 struct {
334 V string `url:"omitempty"`
335 }{},
336 url.Values{"omitempty": {""}},
337 },
338 {
339
340 struct {
341 V *string `url:",omitempty"`
342 }{&str},
343 url.Values{"V": {""}},
344 },
345 }
346
347 for _, tt := range tests {
348 testValue(t, tt.input, tt.want)
349 }
350 }
351
352 func TestValues_EmbeddedStructs(t *testing.T) {
353 type Inner struct {
354 V string
355 }
356 type Outer struct {
357 Inner
358 }
359 type OuterPtr struct {
360 *Inner
361 }
362 type Mixed struct {
363 Inner
364 V string
365 }
366 type unexported struct {
367 Inner
368 V string
369 }
370 type Exported struct {
371 unexported
372 }
373
374 tests := []struct {
375 input interface{}
376 want url.Values
377 }{
378 {
379 Outer{Inner{V: "a"}},
380 url.Values{"V": {"a"}},
381 },
382 {
383 OuterPtr{&Inner{V: "a"}},
384 url.Values{"V": {"a"}},
385 },
386 {
387 Mixed{Inner: Inner{V: "a"}, V: "b"},
388 url.Values{"V": {"b", "a"}},
389 },
390 {
391
392 Exported{
393 unexported{
394 Inner: Inner{V: "bar"},
395 V: "foo",
396 },
397 },
398 url.Values{"V": {"foo", "bar"}},
399 },
400 }
401
402 for _, tt := range tests {
403 testValue(t, tt.input, tt.want)
404 }
405 }
406
407 func TestValues_InvalidInput(t *testing.T) {
408 _, err := Values("")
409 if err == nil {
410 t.Errorf("expected Values() to return an error on invalid input")
411 }
412 }
413
414
415 type customEncodedStrings []string
416
417
418
419 func (m customEncodedStrings) EncodeValues(key string, v *url.Values) error {
420 for i, arg := range m {
421 if arg == "err" {
422 return errors.New("encoding error")
423 }
424 v.Set(fmt.Sprintf("%s.%d", key, i), arg)
425 }
426 return nil
427 }
428
429 func TestValues_CustomEncodingSlice(t *testing.T) {
430 tests := []struct {
431 input interface{}
432 want url.Values
433 }{
434 {
435 struct {
436 V customEncodedStrings `url:"v"`
437 }{},
438 url.Values{},
439 },
440 {
441 struct {
442 V customEncodedStrings `url:"v"`
443 }{[]string{"a", "b"}},
444 url.Values{"v.0": {"a"}, "v.1": {"b"}},
445 },
446
447
448 {
449 struct {
450 V *customEncodedStrings `url:"v"`
451 }{},
452 url.Values{},
453 },
454 {
455 struct {
456 V *customEncodedStrings `url:"v"`
457 }{(*customEncodedStrings)(&[]string{"a", "b"})},
458 url.Values{"v.0": {"a"}, "v.1": {"b"}},
459 },
460 }
461
462 for _, tt := range tests {
463 testValue(t, tt.input, tt.want)
464 }
465 }
466
467
468
469 func TestValues_CustomEncoding_Error(t *testing.T) {
470 type st struct {
471 V customEncodedStrings
472 }
473 tests := []struct {
474 input interface{}
475 }{
476 {
477 st{[]string{"err"}},
478 },
479 {
480 struct{ S st }{st{[]string{"err"}}},
481 },
482 {
483 struct{ st }{st{[]string{"err"}}},
484 },
485 }
486 for _, tt := range tests {
487 _, err := Values(tt.input)
488 if err == nil {
489 t.Errorf("Values(%q) did not return expected encoding error", tt.input)
490 }
491 }
492 }
493
494
495 type customEncodedInt int
496
497
498 func (m customEncodedInt) EncodeValues(key string, v *url.Values) error {
499 v.Set(key, fmt.Sprintf("_%d", m))
500 return nil
501 }
502
503 func TestValues_CustomEncodingInt(t *testing.T) {
504 var zero customEncodedInt = 0
505 var one customEncodedInt = 1
506 tests := []struct {
507 input interface{}
508 want url.Values
509 }{
510 {
511 struct {
512 V customEncodedInt `url:"v"`
513 }{},
514 url.Values{"v": {"_0"}},
515 },
516 {
517 struct {
518 V customEncodedInt `url:"v,omitempty"`
519 }{zero},
520 url.Values{},
521 },
522 {
523 struct {
524 V customEncodedInt `url:"v"`
525 }{one},
526 url.Values{"v": {"_1"}},
527 },
528
529
530 {
531 struct {
532 V *customEncodedInt `url:"v"`
533 }{},
534 url.Values{"v": {"_0"}},
535 },
536 {
537 struct {
538 V *customEncodedInt `url:"v,omitempty"`
539 }{},
540 url.Values{},
541 },
542 {
543 struct {
544 V *customEncodedInt `url:"v,omitempty"`
545 }{&zero},
546 url.Values{"v": {"_0"}},
547 },
548 {
549 struct {
550 V *customEncodedInt `url:"v"`
551 }{&one},
552 url.Values{"v": {"_1"}},
553 },
554 }
555
556 for _, tt := range tests {
557 testValue(t, tt.input, tt.want)
558 }
559 }
560
561
562
563 type customEncodedIntPtr int
564
565
566
567 func (m *customEncodedIntPtr) EncodeValues(key string, v *url.Values) error {
568 if m == nil {
569 v.Set(key, "undefined")
570 } else {
571 v.Set(key, fmt.Sprintf("_%d", *m))
572 }
573 return nil
574 }
575
576
577
578 func TestValues_CustomEncodingPointer(t *testing.T) {
579 var zero customEncodedIntPtr = 0
580 var one customEncodedIntPtr = 1
581 tests := []struct {
582 input interface{}
583 want url.Values
584 }{
585
586
587 {
588 struct {
589 V customEncodedIntPtr `url:"v"`
590 }{},
591 url.Values{"v": {"0"}},
592 },
593 {
594 struct {
595 V customEncodedIntPtr `url:"v,omitempty"`
596 }{},
597 url.Values{},
598 },
599 {
600 struct {
601 V customEncodedIntPtr `url:"v"`
602 }{one},
603 url.Values{"v": {"1"}},
604 },
605
606
607 {
608 struct {
609 V *customEncodedIntPtr `url:"v"`
610 }{},
611 url.Values{"v": {"undefined"}},
612 },
613 {
614 struct {
615 V *customEncodedIntPtr `url:"v,omitempty"`
616 }{},
617 url.Values{},
618 },
619 {
620 struct {
621 V *customEncodedIntPtr `url:"v"`
622 }{&zero},
623 url.Values{"v": {"_0"}},
624 },
625 {
626 struct {
627 V *customEncodedIntPtr `url:"v,omitempty"`
628 }{&zero},
629 url.Values{"v": {"_0"}},
630 },
631 {
632 struct {
633 V *customEncodedIntPtr `url:"v"`
634 }{&one},
635 url.Values{"v": {"_1"}},
636 },
637 }
638
639 for _, tt := range tests {
640 testValue(t, tt.input, tt.want)
641 }
642 }
643
644 func TestIsEmptyValue(t *testing.T) {
645 str := "string"
646 tests := []struct {
647 value interface{}
648 empty bool
649 }{
650
651 {[]int{}, true},
652 {[]int{0}, false},
653 {[0]int{}, true},
654 {[3]int{}, false},
655 {[3]int{1}, false},
656 {map[string]string{}, true},
657 {map[string]string{"a": "b"}, false},
658
659
660 {"", true},
661 {" ", false},
662 {"a", false},
663
664
665 {true, false},
666 {false, true},
667
668
669 {(int)(0), true}, {(int)(1), false}, {(int)(-1), false},
670 {(int8)(0), true}, {(int8)(1), false}, {(int8)(-1), false},
671 {(int16)(0), true}, {(int16)(1), false}, {(int16)(-1), false},
672 {(int32)(0), true}, {(int32)(1), false}, {(int32)(-1), false},
673 {(int64)(0), true}, {(int64)(1), false}, {(int64)(-1), false},
674 {(uint)(0), true}, {(uint)(1), false},
675 {(uint8)(0), true}, {(uint8)(1), false},
676 {(uint16)(0), true}, {(uint16)(1), false},
677 {(uint32)(0), true}, {(uint32)(1), false},
678 {(uint64)(0), true}, {(uint64)(1), false},
679
680
681 {(float32)(0), true}, {(float32)(0.0), true}, {(float32)(0.1), false},
682 {(float64)(0), true}, {(float64)(0.0), true}, {(float64)(0.1), false},
683
684
685 {(*int)(nil), true},
686 {new([]int), false},
687 {&str, false},
688
689
690 {time.Time{}, true},
691 {time.Now(), false},
692
693
694 {(*struct{ int })(nil), true},
695 {struct{ int }{}, false},
696 {struct{ int }{0}, false},
697 {struct{ int }{1}, false},
698 }
699
700 for _, tt := range tests {
701 got := isEmptyValue(reflect.ValueOf(tt.value))
702 want := tt.empty
703 if got != want {
704 t.Errorf("isEmptyValue(%v) returned %t; want %t", tt.value, got, want)
705 }
706 }
707 }
708
709 func TestParseTag(t *testing.T) {
710 name, opts := parseTag("field,foobar,foo")
711 if name != "field" {
712 t.Fatalf("name = %q, want field", name)
713 }
714 for _, tt := range []struct {
715 opt string
716 want bool
717 }{
718 {"foobar", true},
719 {"foo", true},
720 {"bar", false},
721 {"field", false},
722 } {
723 if opts.Contains(tt.opt) != tt.want {
724 t.Errorf("Contains(%q) = %v", tt.opt, !tt.want)
725 }
726 }
727 }
728
View as plain text