...

Source file src/github.com/google/go-querystring/query/encode_test.go

Documentation: github.com/google/go-querystring/query

     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
     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  // test that Values(input) matches want.  If not, report an error on t.
    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  		// zero values
    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  		// simple non-zero values
    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  		// bool-specific options
    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  		// time values
    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  		// nil pointers (zero values)
   109  		{struct{ V *string }{}, url.Values{"V": {""}}},
   110  		{struct{ V *int }{}, url.Values{"V": {""}}},
   111  
   112  		// non-zero pointer values
   113  		{struct{ V *string }{&str}, url.Values{"V": {"s"}}},
   114  		{struct{ V **string }{&strPtr}, url.Values{"V": {"s"}}},
   115  
   116  		// slices of pointer values
   117  		{struct{ V []*string }{}, url.Values{}},
   118  		{struct{ V []*string }{[]*string{&str, &str}}, url.Values{"V": {"s", "s"}}},
   119  
   120  		// pointer to slice
   121  		{struct{ V *[]string }{}, url.Values{"V": {""}}},
   122  		{struct{ V *[]string }{&[]string{"a", "b"}}, url.Values{"V": {"a", "b"}}},
   123  
   124  		// pointer values for the input struct itself
   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  		// slices of strings
   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  		// arrays of strings
   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  		// custom delimiters
   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  		// slice of bools with additional options
   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{}}, // non-exported field
   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"` // actually named omitempty
   335  			}{},
   336  			url.Values{"omitempty": {""}},
   337  		},
   338  		{
   339  			// include value for a non-nil pointer to an empty value
   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  			// values from unexported embed are still included
   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  // customEncodedStrings is a slice of strings with a custom URL encoding
   415  type customEncodedStrings []string
   416  
   417  // EncodeValues using key name of the form "{key}.N" where N increments with
   418  // each value.  A value of "err" will return an error.
   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  		// pointers to custom encoded types
   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  // One of the few ways reflectValues will return an error is if a custom
   468  // encoder returns an error.  Test all of the various ways that can happen.
   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  		{ // struct field
   480  			struct{ S st }{st{[]string{"err"}}},
   481  		},
   482  		{ // embedded struct
   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  // customEncodedInt is an int with a custom URL encoding
   495  type customEncodedInt int
   496  
   497  // EncodeValues encodes values with leading underscores
   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  		// pointers to custom encoded types
   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  // customEncodedInt is an int with a custom URL encoding defined on its pointer
   562  // value.
   563  type customEncodedIntPtr int
   564  
   565  // EncodeValues encodes a 0 as false, 1 as true, and nil as unknown.  All other
   566  // values cause an error.
   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  // Test behavior when encoding is defined for a pointer of a custom type.
   577  // Custom type should be able to encode values for nil pointers.
   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  		// non-pointer values do not get the custom encoding because
   586  		// they don't implement the encoder interface.
   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  		// pointers to custom encoded types.
   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  		// slices, arrays, and maps
   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  		// strings
   660  		{"", true},
   661  		{" ", false},
   662  		{"a", false},
   663  
   664  		// bool
   665  		{true, false},
   666  		{false, true},
   667  
   668  		// ints of various types
   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  		// floats
   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  		// pointers
   685  		{(*int)(nil), true},
   686  		{new([]int), false},
   687  		{&str, false},
   688  
   689  		// time
   690  		{time.Time{}, true},
   691  		{time.Now(), false},
   692  
   693  		// unknown type - always false unless a nil pointer, which are always empty.
   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