// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package query import ( "fmt" "net/url" "reflect" "testing" "time" ) type Nested struct { A SubNested `url:"a"` B *SubNested `url:"b"` Ptr *SubNested `url:"ptr,omitempty"` } type SubNested struct { Value string `url:"value"` } func TestValues_types(t *testing.T) { str := "string" strPtr := &str timeVal := time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC) tests := []struct { in interface{} want url.Values }{ { // basic primitives struct { A string B int C uint D float32 E bool }{}, url.Values{ "A": {""}, "B": {"0"}, "C": {"0"}, "D": {"0"}, "E": {"false"}, }, }, { // pointers struct { A *string B *int C **string D *time.Time }{ A: strPtr, C: &strPtr, D: &timeVal, }, url.Values{ "A": {str}, "B": {""}, "C": {str}, "D": {"2000-01-01T12:34:56Z"}, }, }, { // slices and arrays struct { A []string B []string `url:",comma"` C []string `url:",space"` D [2]string E [2]string `url:",comma"` F [2]string `url:",space"` G []*string `url:",space"` H []bool `url:",int,space"` I []string `url:",brackets"` J []string `url:",semicolon"` K []string `url:",numbered"` }{ A: []string{"a", "b"}, B: []string{"a", "b"}, C: []string{"a", "b"}, D: [2]string{"a", "b"}, E: [2]string{"a", "b"}, F: [2]string{"a", "b"}, G: []*string{&str, &str}, H: []bool{true, false}, I: []string{"a", "b"}, J: []string{"a", "b"}, K: []string{"a", "b"}, }, url.Values{ "A": {"a", "b"}, "B": {"a,b"}, "C": {"a b"}, "D": {"a", "b"}, "E": {"a,b"}, "F": {"a b"}, "G": {"string string"}, "H": {"1 0"}, "I[]": {"a", "b"}, "J": {"a;b"}, "K0": {"a"}, "K1": {"b"}, }, }, { // other types struct { A time.Time B time.Time `url:",unix"` C bool `url:",int"` D bool `url:",int"` }{ A: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC), B: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC), C: true, D: false, }, url.Values{ "A": {"2000-01-01T12:34:56Z"}, "B": {"946730096"}, "C": {"1"}, "D": {"0"}, }, }, { struct { Nest Nested `url:"nest"` }{ Nested{ A: SubNested{ Value: "that", }, }, }, url.Values{ "nest[a][value]": {"that"}, "nest[b]": {""}, }, }, { struct { Nest Nested `url:"nest"` }{ Nested{ Ptr: &SubNested{ Value: "that", }, }, }, url.Values{ "nest[a][value]": {""}, "nest[b]": {""}, "nest[ptr][value]": {"that"}, }, }, { nil, url.Values{}, }, } for i, tt := range tests { v, err := Values(tt.in) if err != nil { t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err) } if !reflect.DeepEqual(tt.want, v) { t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want) } } } func TestValues_omitEmpty(t *testing.T) { str := "" s := struct { a string A string B string `url:",omitempty"` C string `url:"-"` D string `url:"omitempty"` // actually named omitempty, not an option E *string `url:",omitempty"` }{E: &str} v, err := Values(s) if err != nil { t.Errorf("Values(%q) returned error: %v", s, err) } want := url.Values{ "A": {""}, "omitempty": {""}, "E": {""}, // E is included because the pointer is not empty, even though the string being pointed to is } if !reflect.DeepEqual(want, v) { t.Errorf("Values(%q) returned %v, want %v", s, v, want) } } type A struct { B } type B struct { C string } type D struct { B C string } type e struct { B C string } type F struct { e } func TestValues_embeddedStructs(t *testing.T) { tests := []struct { in interface{} want url.Values }{ { A{B{C: "foo"}}, url.Values{"C": {"foo"}}, }, { D{B: B{C: "bar"}, C: "foo"}, url.Values{"C": {"foo", "bar"}}, }, { F{e{B: B{C: "bar"}, C: "foo"}}, // With unexported embed url.Values{"C": {"foo", "bar"}}, }, } for i, tt := range tests { v, err := Values(tt.in) if err != nil { t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err) } if !reflect.DeepEqual(tt.want, v) { t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want) } } } func TestValues_invalidInput(t *testing.T) { _, err := Values("") if err == nil { t.Errorf("expected Values() to return an error on invalid input") } } type EncodedArgs []string func (m EncodedArgs) EncodeValues(key string, v *url.Values) error { for i, arg := range m { v.Set(fmt.Sprintf("%s.%d", key, i), arg) } return nil } func TestValues_Marshaler(t *testing.T) { s := struct { Args EncodedArgs `url:"arg"` }{[]string{"a", "b", "c"}} v, err := Values(s) if err != nil { t.Errorf("Values(%q) returned error: %v", s, err) } want := url.Values{ "arg.0": {"a"}, "arg.1": {"b"}, "arg.2": {"c"}, } if !reflect.DeepEqual(want, v) { t.Errorf("Values(%q) returned %v, want %v", s, v, want) } } func TestValues_MarshalerWithNilPointer(t *testing.T) { s := struct { Args *EncodedArgs `url:"arg"` }{} v, err := Values(s) if err != nil { t.Errorf("Values(%q) returned error: %v", s, err) } want := url.Values{} if !reflect.DeepEqual(want, v) { t.Errorf("Values(%q) returned %v, want %v", s, v, want) } } func TestTagParsing(t *testing.T) { name, opts := parseTag("field,foobar,foo") if name != "field" { t.Fatalf("name = %q, want field", name) } for _, tt := range []struct { opt string want bool }{ {"foobar", true}, {"foo", true}, {"bar", false}, {"field", false}, } { if opts.Contains(tt.opt) != tt.want { t.Errorf("Contains(%q) = %v", tt.opt, !tt.want) } } }