github.com/hashicorp/vault/sdk@v0.13.0/framework/field_data_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package framework
     5  
     6  import (
     7  	"encoding/json"
     8  	"net/http"
     9  	"reflect"
    10  	"testing"
    11  	"time"
    12  )
    13  
    14  func TestFieldDataGet(t *testing.T) {
    15  	cases := map[string]struct {
    16  		Schema      map[string]*FieldSchema
    17  		Raw         map[string]interface{}
    18  		Key         string
    19  		Value       interface{}
    20  		ExpectError bool
    21  	}{
    22  		"string type, string value": {
    23  			map[string]*FieldSchema{
    24  				"foo": {Type: TypeString},
    25  			},
    26  			map[string]interface{}{
    27  				"foo": "bar",
    28  			},
    29  			"foo",
    30  			"bar",
    31  			false,
    32  		},
    33  
    34  		"string type, int value": {
    35  			map[string]*FieldSchema{
    36  				"foo": {Type: TypeString},
    37  			},
    38  			map[string]interface{}{
    39  				"foo": 42,
    40  			},
    41  			"foo",
    42  			"42",
    43  			false,
    44  		},
    45  
    46  		"string type, unset value": {
    47  			map[string]*FieldSchema{
    48  				"foo": {Type: TypeString},
    49  			},
    50  			map[string]interface{}{},
    51  			"foo",
    52  			"",
    53  			false,
    54  		},
    55  
    56  		"string type, unset value with default": {
    57  			map[string]*FieldSchema{
    58  				"foo": {
    59  					Type:    TypeString,
    60  					Default: "bar",
    61  				},
    62  			},
    63  			map[string]interface{}{},
    64  			"foo",
    65  			"bar",
    66  			false,
    67  		},
    68  
    69  		"lowercase string type, lowercase string value": {
    70  			map[string]*FieldSchema{
    71  				"foo": {Type: TypeLowerCaseString},
    72  			},
    73  			map[string]interface{}{
    74  				"foo": "bar",
    75  			},
    76  			"foo",
    77  			"bar",
    78  			false,
    79  		},
    80  
    81  		"lowercase string type, mixed-case string value": {
    82  			map[string]*FieldSchema{
    83  				"foo": {Type: TypeLowerCaseString},
    84  			},
    85  			map[string]interface{}{
    86  				"foo": "BaR",
    87  			},
    88  			"foo",
    89  			"bar",
    90  			false,
    91  		},
    92  
    93  		"lowercase string type, int value": {
    94  			map[string]*FieldSchema{
    95  				"foo": {Type: TypeLowerCaseString},
    96  			},
    97  			map[string]interface{}{
    98  				"foo": 42,
    99  			},
   100  			"foo",
   101  			"42",
   102  			false,
   103  		},
   104  
   105  		"lowercase string type, unset value": {
   106  			map[string]*FieldSchema{
   107  				"foo": {Type: TypeLowerCaseString},
   108  			},
   109  			map[string]interface{}{},
   110  			"foo",
   111  			"",
   112  			false,
   113  		},
   114  
   115  		"lowercase string type, unset value with lowercase default": {
   116  			map[string]*FieldSchema{
   117  				"foo": {
   118  					Type:    TypeLowerCaseString,
   119  					Default: "bar",
   120  				},
   121  			},
   122  			map[string]interface{}{},
   123  			"foo",
   124  			"bar",
   125  			false,
   126  		},
   127  
   128  		"int type, int value": {
   129  			map[string]*FieldSchema{
   130  				"foo": {Type: TypeInt},
   131  			},
   132  			map[string]interface{}{
   133  				"foo": 42,
   134  			},
   135  			"foo",
   136  			42,
   137  			false,
   138  		},
   139  
   140  		"bool type, bool value": {
   141  			map[string]*FieldSchema{
   142  				"foo": {Type: TypeBool},
   143  			},
   144  			map[string]interface{}{
   145  				"foo": false,
   146  			},
   147  			"foo",
   148  			false,
   149  			false,
   150  		},
   151  
   152  		"map type, map value": {
   153  			map[string]*FieldSchema{
   154  				"foo": {Type: TypeMap},
   155  			},
   156  			map[string]interface{}{
   157  				"foo": map[string]interface{}{
   158  					"child": true,
   159  				},
   160  			},
   161  			"foo",
   162  			map[string]interface{}{
   163  				"child": true,
   164  			},
   165  			false,
   166  		},
   167  
   168  		"duration type, string value": {
   169  			map[string]*FieldSchema{
   170  				"foo": {Type: TypeDurationSecond},
   171  			},
   172  			map[string]interface{}{
   173  				"foo": "42",
   174  			},
   175  			"foo",
   176  			42,
   177  			false,
   178  		},
   179  
   180  		"duration type, string duration value": {
   181  			map[string]*FieldSchema{
   182  				"foo": {Type: TypeDurationSecond},
   183  			},
   184  			map[string]interface{}{
   185  				"foo": "42m",
   186  			},
   187  			"foo",
   188  			2520,
   189  			false,
   190  		},
   191  
   192  		"duration type, int value": {
   193  			map[string]*FieldSchema{
   194  				"foo": {Type: TypeDurationSecond},
   195  			},
   196  			map[string]interface{}{
   197  				"foo": 42,
   198  			},
   199  			"foo",
   200  			42,
   201  			false,
   202  		},
   203  
   204  		"duration type, float value": {
   205  			map[string]*FieldSchema{
   206  				"foo": {Type: TypeDurationSecond},
   207  			},
   208  			map[string]interface{}{
   209  				"foo": 42.0,
   210  			},
   211  			"foo",
   212  			42,
   213  			false,
   214  		},
   215  
   216  		"duration type, nil value": {
   217  			map[string]*FieldSchema{
   218  				"foo": {Type: TypeDurationSecond},
   219  			},
   220  			map[string]interface{}{
   221  				"foo": nil,
   222  			},
   223  			"foo",
   224  			0,
   225  			false,
   226  		},
   227  
   228  		"duration type, 0 value": {
   229  			map[string]*FieldSchema{
   230  				"foo": {Type: TypeDurationSecond},
   231  			},
   232  			map[string]interface{}{
   233  				"foo": 0,
   234  			},
   235  			"foo",
   236  			0,
   237  			false,
   238  		},
   239  
   240  		"signed duration type, positive string value": {
   241  			map[string]*FieldSchema{
   242  				"foo": {Type: TypeSignedDurationSecond},
   243  			},
   244  			map[string]interface{}{
   245  				"foo": "42",
   246  			},
   247  			"foo",
   248  			42,
   249  			false,
   250  		},
   251  
   252  		"signed duration type, positive string duration value": {
   253  			map[string]*FieldSchema{
   254  				"foo": {Type: TypeSignedDurationSecond},
   255  			},
   256  			map[string]interface{}{
   257  				"foo": "42m",
   258  			},
   259  			"foo",
   260  			2520,
   261  			false,
   262  		},
   263  
   264  		"signed duration type, positive int value": {
   265  			map[string]*FieldSchema{
   266  				"foo": {Type: TypeSignedDurationSecond},
   267  			},
   268  			map[string]interface{}{
   269  				"foo": 42,
   270  			},
   271  			"foo",
   272  			42,
   273  			false,
   274  		},
   275  
   276  		"signed duration type, positive float value": {
   277  			map[string]*FieldSchema{
   278  				"foo": {Type: TypeSignedDurationSecond},
   279  			},
   280  			map[string]interface{}{
   281  				"foo": 42.0,
   282  			},
   283  			"foo",
   284  			42,
   285  			false,
   286  		},
   287  
   288  		"signed duration type, negative string value": {
   289  			map[string]*FieldSchema{
   290  				"foo": {Type: TypeSignedDurationSecond},
   291  			},
   292  			map[string]interface{}{
   293  				"foo": "-42",
   294  			},
   295  			"foo",
   296  			-42,
   297  			false,
   298  		},
   299  
   300  		"signed duration type, negative string duration value": {
   301  			map[string]*FieldSchema{
   302  				"foo": {Type: TypeSignedDurationSecond},
   303  			},
   304  			map[string]interface{}{
   305  				"foo": "-42m",
   306  			},
   307  			"foo",
   308  			-2520,
   309  			false,
   310  		},
   311  
   312  		"signed duration type, negative int value": {
   313  			map[string]*FieldSchema{
   314  				"foo": {Type: TypeSignedDurationSecond},
   315  			},
   316  			map[string]interface{}{
   317  				"foo": -42,
   318  			},
   319  			"foo",
   320  			-42,
   321  			false,
   322  		},
   323  
   324  		"signed duration type, negative float value": {
   325  			map[string]*FieldSchema{
   326  				"foo": {Type: TypeSignedDurationSecond},
   327  			},
   328  			map[string]interface{}{
   329  				"foo": -42.0,
   330  			},
   331  			"foo",
   332  			-42,
   333  			false,
   334  		},
   335  
   336  		"signed duration type, nil value": {
   337  			map[string]*FieldSchema{
   338  				"foo": {Type: TypeSignedDurationSecond},
   339  			},
   340  			map[string]interface{}{
   341  				"foo": nil,
   342  			},
   343  			"foo",
   344  			0,
   345  			false,
   346  		},
   347  
   348  		"signed duration type, 0 value": {
   349  			map[string]*FieldSchema{
   350  				"foo": {Type: TypeSignedDurationSecond},
   351  			},
   352  			map[string]interface{}{
   353  				"foo": 0,
   354  			},
   355  			"foo",
   356  			0,
   357  			false,
   358  		},
   359  
   360  		"slice type, empty slice": {
   361  			map[string]*FieldSchema{
   362  				"foo": {Type: TypeSlice},
   363  			},
   364  			map[string]interface{}{
   365  				"foo": []interface{}{},
   366  			},
   367  			"foo",
   368  			[]interface{}{},
   369  			false,
   370  		},
   371  
   372  		"slice type, filled, mixed slice": {
   373  			map[string]*FieldSchema{
   374  				"foo": {Type: TypeSlice},
   375  			},
   376  			map[string]interface{}{
   377  				"foo": []interface{}{123, "abc"},
   378  			},
   379  			"foo",
   380  			[]interface{}{123, "abc"},
   381  			false,
   382  		},
   383  
   384  		"string slice type, filled slice": {
   385  			map[string]*FieldSchema{
   386  				"foo": {Type: TypeStringSlice},
   387  			},
   388  			map[string]interface{}{
   389  				"foo": []interface{}{123, "abc"},
   390  			},
   391  			"foo",
   392  			[]string{"123", "abc"},
   393  			false,
   394  		},
   395  
   396  		"string slice type, single value": {
   397  			map[string]*FieldSchema{
   398  				"foo": {Type: TypeStringSlice},
   399  			},
   400  			map[string]interface{}{
   401  				"foo": "abc",
   402  			},
   403  			"foo",
   404  			[]string{"abc"},
   405  			false,
   406  		},
   407  
   408  		"string slice type, empty string": {
   409  			map[string]*FieldSchema{
   410  				"foo": {Type: TypeStringSlice},
   411  			},
   412  			map[string]interface{}{
   413  				"foo": "",
   414  			},
   415  			"foo",
   416  			[]string{},
   417  			false,
   418  		},
   419  
   420  		"comma string slice type, empty string": {
   421  			map[string]*FieldSchema{
   422  				"foo": {Type: TypeCommaStringSlice},
   423  			},
   424  			map[string]interface{}{
   425  				"foo": "",
   426  			},
   427  			"foo",
   428  			[]string{},
   429  			false,
   430  		},
   431  
   432  		"comma string slice type, comma string with one value": {
   433  			map[string]*FieldSchema{
   434  				"foo": {Type: TypeCommaStringSlice},
   435  			},
   436  			map[string]interface{}{
   437  				"foo": "value1",
   438  			},
   439  			"foo",
   440  			[]string{"value1"},
   441  			false,
   442  		},
   443  
   444  		"comma string slice type, comma string with multi value": {
   445  			map[string]*FieldSchema{
   446  				"foo": {Type: TypeCommaStringSlice},
   447  			},
   448  			map[string]interface{}{
   449  				"foo": "value1,value2,value3",
   450  			},
   451  			"foo",
   452  			[]string{"value1", "value2", "value3"},
   453  			false,
   454  		},
   455  
   456  		"comma string slice type, nil string slice value": {
   457  			map[string]*FieldSchema{
   458  				"foo": {Type: TypeCommaStringSlice},
   459  			},
   460  			map[string]interface{}{
   461  				"foo": "",
   462  			},
   463  			"foo",
   464  			[]string{},
   465  			false,
   466  		},
   467  
   468  		"comma string slice type, string slice with one value": {
   469  			map[string]*FieldSchema{
   470  				"foo": {Type: TypeCommaStringSlice},
   471  			},
   472  			map[string]interface{}{
   473  				"foo": []interface{}{"value1"},
   474  			},
   475  			"foo",
   476  			[]string{"value1"},
   477  			false,
   478  		},
   479  
   480  		"comma string slice type, string slice with multi value": {
   481  			map[string]*FieldSchema{
   482  				"foo": {Type: TypeCommaStringSlice},
   483  			},
   484  			map[string]interface{}{
   485  				"foo": []interface{}{"value1", "value2", "value3"},
   486  			},
   487  			"foo",
   488  			[]string{"value1", "value2", "value3"},
   489  			false,
   490  		},
   491  
   492  		"comma string slice type, empty string slice value": {
   493  			map[string]*FieldSchema{
   494  				"foo": {Type: TypeCommaStringSlice},
   495  			},
   496  			map[string]interface{}{
   497  				"foo": []interface{}{},
   498  			},
   499  			"foo",
   500  			[]string{},
   501  			false,
   502  		},
   503  
   504  		"comma int slice type, comma int with one value": {
   505  			map[string]*FieldSchema{
   506  				"foo": {Type: TypeCommaIntSlice},
   507  			},
   508  			map[string]interface{}{
   509  				"foo": 1,
   510  			},
   511  			"foo",
   512  			[]int{1},
   513  			false,
   514  		},
   515  
   516  		"comma int slice type, comma int with multi value slice": {
   517  			map[string]*FieldSchema{
   518  				"foo": {Type: TypeCommaIntSlice},
   519  			},
   520  			map[string]interface{}{
   521  				"foo": []int{1, 2, 3},
   522  			},
   523  			"foo",
   524  			[]int{1, 2, 3},
   525  			false,
   526  		},
   527  
   528  		"comma int slice type, comma int with multi value": {
   529  			map[string]*FieldSchema{
   530  				"foo": {Type: TypeCommaIntSlice},
   531  			},
   532  			map[string]interface{}{
   533  				"foo": "1,2,3",
   534  			},
   535  			"foo",
   536  			[]int{1, 2, 3},
   537  			false,
   538  		},
   539  
   540  		"comma int slice type, nil int slice value": {
   541  			map[string]*FieldSchema{
   542  				"foo": {Type: TypeCommaIntSlice},
   543  			},
   544  			map[string]interface{}{
   545  				"foo": "",
   546  			},
   547  			"foo",
   548  			[]int{},
   549  			false,
   550  		},
   551  
   552  		"comma int slice type, int slice with one value": {
   553  			map[string]*FieldSchema{
   554  				"foo": {Type: TypeCommaIntSlice},
   555  			},
   556  			map[string]interface{}{
   557  				"foo": []interface{}{"1"},
   558  			},
   559  			"foo",
   560  			[]int{1},
   561  			false,
   562  		},
   563  
   564  		"comma int slice type, int slice with multi value strings": {
   565  			map[string]*FieldSchema{
   566  				"foo": {Type: TypeCommaIntSlice},
   567  			},
   568  			map[string]interface{}{
   569  				"foo": []interface{}{"1", "2", "3"},
   570  			},
   571  			"foo",
   572  			[]int{1, 2, 3},
   573  			false,
   574  		},
   575  
   576  		"comma int slice type, int slice with multi value": {
   577  			map[string]*FieldSchema{
   578  				"foo": {Type: TypeCommaIntSlice},
   579  			},
   580  			map[string]interface{}{
   581  				"foo": []interface{}{1, 2, 3},
   582  			},
   583  			"foo",
   584  			[]int{1, 2, 3},
   585  			false,
   586  		},
   587  
   588  		"comma int slice type, empty int slice value": {
   589  			map[string]*FieldSchema{
   590  				"foo": {Type: TypeCommaIntSlice},
   591  			},
   592  			map[string]interface{}{
   593  				"foo": []interface{}{},
   594  			},
   595  			"foo",
   596  			[]int{},
   597  			false,
   598  		},
   599  
   600  		"comma int slice type, json number": {
   601  			map[string]*FieldSchema{
   602  				"foo": {Type: TypeCommaIntSlice},
   603  			},
   604  			map[string]interface{}{
   605  				"foo": json.Number("1"),
   606  			},
   607  			"foo",
   608  			[]int{1},
   609  			false,
   610  		},
   611  
   612  		"name string type, valid string": {
   613  			map[string]*FieldSchema{
   614  				"foo": {Type: TypeNameString},
   615  			},
   616  			map[string]interface{}{
   617  				"foo": "bar",
   618  			},
   619  			"foo",
   620  			"bar",
   621  			false,
   622  		},
   623  
   624  		"name string type, valid value with special characters": {
   625  			map[string]*FieldSchema{
   626  				"foo": {Type: TypeNameString},
   627  			},
   628  			map[string]interface{}{
   629  				"foo": "bar.baz-bay123",
   630  			},
   631  			"foo",
   632  			"bar.baz-bay123",
   633  			false,
   634  		},
   635  
   636  		"keypair type, valid value map type": {
   637  			map[string]*FieldSchema{
   638  				"foo": {Type: TypeKVPairs},
   639  			},
   640  			map[string]interface{}{
   641  				"foo": map[string]interface{}{
   642  					"key1": "value1",
   643  					"key2": "value2",
   644  					"key3": 1,
   645  				},
   646  			},
   647  			"foo",
   648  			map[string]string{
   649  				"key1": "value1",
   650  				"key2": "value2",
   651  				"key3": "1",
   652  			},
   653  			false,
   654  		},
   655  
   656  		"keypair type, list of equal sign delim key pairs type": {
   657  			map[string]*FieldSchema{
   658  				"foo": {Type: TypeKVPairs},
   659  			},
   660  			map[string]interface{}{
   661  				"foo": []interface{}{"key1=value1", "key2=value2", "key3=1"},
   662  			},
   663  			"foo",
   664  			map[string]string{
   665  				"key1": "value1",
   666  				"key2": "value2",
   667  				"key3": "1",
   668  			},
   669  			false,
   670  		},
   671  
   672  		"keypair type, single equal sign delim value": {
   673  			map[string]*FieldSchema{
   674  				"foo": {Type: TypeKVPairs},
   675  			},
   676  			map[string]interface{}{
   677  				"foo": "key1=value1",
   678  			},
   679  			"foo",
   680  			map[string]string{
   681  				"key1": "value1",
   682  			},
   683  			false,
   684  		},
   685  
   686  		"type header, keypair string array": {
   687  			map[string]*FieldSchema{
   688  				"foo": {Type: TypeHeader},
   689  			},
   690  			map[string]interface{}{
   691  				"foo": []interface{}{"key1:value1", "key2:value2", "key3:1"},
   692  			},
   693  			"foo",
   694  			http.Header{
   695  				"Key1": []string{"value1"},
   696  				"Key2": []string{"value2"},
   697  				"Key3": []string{"1"},
   698  			},
   699  			false,
   700  		},
   701  
   702  		"type header, b64 string": {
   703  			map[string]*FieldSchema{
   704  				"foo": {Type: TypeHeader},
   705  			},
   706  			map[string]interface{}{
   707  				"foo": "eyJDb250ZW50LUxlbmd0aCI6IFsiNDMiXSwgIlVzZXItQWdlbnQiOiBbImF3cy1zZGstZ28vMS40LjEyIChnbzEuNy4xOyBsaW51eDsgYW1kNjQpIl0sICJYLVZhdWx0LUFXU0lBTS1TZXJ2ZXItSWQiOiBbInZhdWx0LmV4YW1wbGUuY29tIl0sICJYLUFtei1EYXRlIjogWyIyMDE2MDkzMFQwNDMxMjFaIl0sICJDb250ZW50LVR5cGUiOiBbImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZDsgY2hhcnNldD11dGYtOCJdLCAiQXV0aG9yaXphdGlvbiI6IFsiQVdTNC1ITUFDLVNIQTI1NiBDcmVkZW50aWFsPWZvby8yMDE2MDkzMC91cy1lYXN0LTEvc3RzL2F3czRfcmVxdWVzdCwgU2lnbmVkSGVhZGVycz1jb250ZW50LWxlbmd0aDtjb250ZW50LXR5cGU7aG9zdDt4LWFtei1kYXRlO3gtdmF1bHQtc2VydmVyLCBTaWduYXR1cmU9YTY5ZmQ3NTBhMzQ0NWM0ZTU1M2UxYjNlNzlkM2RhOTBlZWY1NDA0N2YxZWI0ZWZlOGZmYmM5YzQyOGMyNjU1YiJdLCAiRm9vIjogNDJ9",
   708  			},
   709  			"foo",
   710  			http.Header{
   711  				"Content-Length":           []string{"43"},
   712  				"User-Agent":               []string{"aws-sdk-go/1.4.12 (go1.7.1; linux; amd64)"},
   713  				"X-Vault-Awsiam-Server-Id": []string{"vault.example.com"},
   714  				"X-Amz-Date":               []string{"20160930T043121Z"},
   715  				"Content-Type":             []string{"application/x-www-form-urlencoded; charset=utf-8"},
   716  				"Authorization":            []string{"AWS4-HMAC-SHA256 Credential=foo/20160930/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-server, Signature=a69fd750a3445c4e553e1b3e79d3da90eef54047f1eb4efe8ffbc9c428c2655b"},
   717  				"Foo":                      []string{"42"},
   718  			},
   719  			false,
   720  		},
   721  
   722  		"type header, json string": {
   723  			map[string]*FieldSchema{
   724  				"foo": {Type: TypeHeader},
   725  			},
   726  			map[string]interface{}{
   727  				"foo": `{"hello":"world","bonjour":["monde","dieu"], "Guten Tag": 42, "你好": ["10", 20, 3.14]}`,
   728  			},
   729  			"foo",
   730  			http.Header{
   731  				"Hello":     []string{"world"},
   732  				"Bonjour":   []string{"monde", "dieu"},
   733  				"Guten Tag": []string{"42"},
   734  				"你好":        []string{"10", "20", "3.14"},
   735  			},
   736  			false,
   737  		},
   738  
   739  		"type header, keypair string array with dupe key": {
   740  			map[string]*FieldSchema{
   741  				"foo": {Type: TypeHeader},
   742  			},
   743  			map[string]interface{}{
   744  				"foo": []interface{}{"key1:value1", "key2:value2", "key3:1", "key3:true"},
   745  			},
   746  			"foo",
   747  			http.Header{
   748  				"Key1": []string{"value1"},
   749  				"Key2": []string{"value2"},
   750  				"Key3": []string{"1", "true"},
   751  			},
   752  			false,
   753  		},
   754  
   755  		"type header, map string slice": {
   756  			map[string]*FieldSchema{
   757  				"foo": {Type: TypeHeader},
   758  			},
   759  			map[string]interface{}{
   760  				"foo": map[string][]string{
   761  					"key1": {"value1"},
   762  					"key2": {"value2"},
   763  					"key3": {"1"},
   764  				},
   765  			},
   766  			"foo",
   767  			http.Header{
   768  				"Key1": []string{"value1"},
   769  				"Key2": []string{"value2"},
   770  				"Key3": []string{"1"},
   771  			},
   772  			false,
   773  		},
   774  
   775  		"name string type, not supplied": {
   776  			map[string]*FieldSchema{
   777  				"foo": {Type: TypeNameString},
   778  			},
   779  			map[string]interface{}{},
   780  			"foo",
   781  			"",
   782  			false,
   783  		},
   784  
   785  		"string type, not supplied": {
   786  			map[string]*FieldSchema{
   787  				"foo": {Type: TypeString},
   788  			},
   789  			map[string]interface{}{},
   790  			"foo",
   791  			"",
   792  			false,
   793  		},
   794  
   795  		"type int, not supplied": {
   796  			map[string]*FieldSchema{
   797  				"foo": {Type: TypeInt},
   798  			},
   799  			map[string]interface{}{},
   800  			"foo",
   801  			0,
   802  			false,
   803  		},
   804  
   805  		"type bool, not supplied": {
   806  			map[string]*FieldSchema{
   807  				"foo": {Type: TypeBool},
   808  			},
   809  			map[string]interface{}{},
   810  			"foo",
   811  			false,
   812  			false,
   813  		},
   814  
   815  		"type map, not supplied": {
   816  			map[string]*FieldSchema{
   817  				"foo": {Type: TypeMap},
   818  			},
   819  			map[string]interface{}{},
   820  			"foo",
   821  			map[string]interface{}{},
   822  			false,
   823  		},
   824  
   825  		"type duration second, not supplied": {
   826  			map[string]*FieldSchema{
   827  				"foo": {Type: TypeDurationSecond},
   828  			},
   829  			map[string]interface{}{},
   830  			"foo",
   831  			0,
   832  			false,
   833  		},
   834  
   835  		"type signed duration second, not supplied": {
   836  			map[string]*FieldSchema{
   837  				"foo": {Type: TypeSignedDurationSecond},
   838  			},
   839  			map[string]interface{}{},
   840  			"foo",
   841  			0,
   842  			false,
   843  		},
   844  
   845  		"type slice, not supplied": {
   846  			map[string]*FieldSchema{
   847  				"foo": {Type: TypeSlice},
   848  			},
   849  			map[string]interface{}{},
   850  			"foo",
   851  			[]interface{}{},
   852  			false,
   853  		},
   854  
   855  		"type string slice, not supplied": {
   856  			map[string]*FieldSchema{
   857  				"foo": {Type: TypeStringSlice},
   858  			},
   859  			map[string]interface{}{},
   860  			"foo",
   861  			[]string{},
   862  			false,
   863  		},
   864  
   865  		"type comma string slice, not supplied": {
   866  			map[string]*FieldSchema{
   867  				"foo": {Type: TypeCommaStringSlice},
   868  			},
   869  			map[string]interface{}{},
   870  			"foo",
   871  			[]string{},
   872  			false,
   873  		},
   874  
   875  		"comma string slice type, single JSON number value": {
   876  			map[string]*FieldSchema{
   877  				"foo": {Type: TypeCommaStringSlice},
   878  			},
   879  			map[string]interface{}{
   880  				"foo": json.Number("123"),
   881  			},
   882  			"foo",
   883  			[]string{"123"},
   884  			false,
   885  		},
   886  
   887  		"type kv pair, not supplied": {
   888  			map[string]*FieldSchema{
   889  				"foo": {Type: TypeKVPairs},
   890  			},
   891  			map[string]interface{}{},
   892  			"foo",
   893  			map[string]string{},
   894  			false,
   895  		},
   896  
   897  		"type header, not supplied": {
   898  			map[string]*FieldSchema{
   899  				"foo": {Type: TypeHeader},
   900  			},
   901  			map[string]interface{}{},
   902  			"foo",
   903  			http.Header{},
   904  			false,
   905  		},
   906  
   907  		"float type, positive with decimals, as string": {
   908  			map[string]*FieldSchema{
   909  				"foo": {Type: TypeFloat},
   910  			},
   911  			map[string]interface{}{
   912  				"foo": "1234567.891234567",
   913  			},
   914  			"foo",
   915  			1234567.891234567,
   916  			false,
   917  		},
   918  
   919  		"float type, negative with decimals, as string": {
   920  			map[string]*FieldSchema{
   921  				"foo": {Type: TypeFloat},
   922  			},
   923  			map[string]interface{}{
   924  				"foo": "-1234567.891234567",
   925  			},
   926  			"foo",
   927  			-1234567.891234567,
   928  			false,
   929  		},
   930  
   931  		"float type, positive without decimals": {
   932  			map[string]*FieldSchema{
   933  				"foo": {Type: TypeFloat},
   934  			},
   935  			map[string]interface{}{
   936  				"foo": 1234567,
   937  			},
   938  			"foo",
   939  			1234567.0,
   940  			false,
   941  		},
   942  
   943  		"type float, not supplied": {
   944  			map[string]*FieldSchema{
   945  				"foo": {Type: TypeFloat},
   946  			},
   947  			map[string]interface{}{},
   948  			"foo",
   949  			0.0,
   950  			false,
   951  		},
   952  
   953  		"type float, invalid value": {
   954  			map[string]*FieldSchema{
   955  				"foo": {Type: TypeFloat},
   956  			},
   957  			map[string]interface{}{
   958  				"foo": "invalid0.0",
   959  			},
   960  			"foo",
   961  			0.0,
   962  			true,
   963  		},
   964  
   965  		"type time, not supplied": {
   966  			map[string]*FieldSchema{
   967  				"foo": {Type: TypeTime},
   968  			},
   969  			map[string]interface{}{},
   970  			"foo",
   971  			time.Time{},
   972  			false,
   973  		},
   974  		"type time, string value": {
   975  			map[string]*FieldSchema{
   976  				"foo": {Type: TypeTime},
   977  			},
   978  			map[string]interface{}{
   979  				"foo": "2021-12-11T09:08:07Z",
   980  			},
   981  			"foo",
   982  			// Comparison uses DeepEqual() so better match exactly,
   983  			// can't have a different location.
   984  			time.Date(2021, 12, 11, 9, 8, 7, 0, time.UTC),
   985  			false,
   986  		},
   987  		"type time, invalid value": {
   988  			map[string]*FieldSchema{
   989  				"foo": {Type: TypeTime},
   990  			},
   991  			map[string]interface{}{
   992  				"foo": "2021-13-11T09:08:07+02:00",
   993  			},
   994  			"foo",
   995  			time.Time{},
   996  			true,
   997  		},
   998  	}
   999  
  1000  	for name, tc := range cases {
  1001  		name, tc := name, tc
  1002  		t.Run(name, func(t *testing.T) {
  1003  			t.Parallel()
  1004  			data := &FieldData{
  1005  				Raw:    tc.Raw,
  1006  				Schema: tc.Schema,
  1007  			}
  1008  
  1009  			err := data.Validate()
  1010  			switch {
  1011  			case tc.ExpectError && err == nil:
  1012  				t.Fatalf("expected error")
  1013  			case tc.ExpectError && err != nil:
  1014  				return
  1015  			case !tc.ExpectError && err != nil:
  1016  				t.Fatal(err)
  1017  			default:
  1018  				// Continue if !tc.ExpectError && err == nil
  1019  			}
  1020  
  1021  			actual := data.Get(tc.Key)
  1022  			if !reflect.DeepEqual(actual, tc.Value) {
  1023  				t.Fatalf("Expected: %#v\nGot: %#v", tc.Value, actual)
  1024  			}
  1025  		})
  1026  	}
  1027  }
  1028  
  1029  func TestFieldDataGet_Error(t *testing.T) {
  1030  	cases := map[string]struct {
  1031  		Schema map[string]*FieldSchema
  1032  		Raw    map[string]interface{}
  1033  		Key    string
  1034  	}{
  1035  		"name string type, invalid value with invalid characters": {
  1036  			map[string]*FieldSchema{
  1037  				"foo": {Type: TypeNameString},
  1038  			},
  1039  			map[string]interface{}{
  1040  				"foo": "bar baz",
  1041  			},
  1042  			"foo",
  1043  		},
  1044  		"name string type, invalid value with special characters at beginning": {
  1045  			map[string]*FieldSchema{
  1046  				"foo": {Type: TypeNameString},
  1047  			},
  1048  			map[string]interface{}{
  1049  				"foo": ".barbaz",
  1050  			},
  1051  			"foo",
  1052  		},
  1053  		"name string type, invalid value with special characters at end": {
  1054  			map[string]*FieldSchema{
  1055  				"foo": {Type: TypeNameString},
  1056  			},
  1057  			map[string]interface{}{
  1058  				"foo": "barbaz-",
  1059  			},
  1060  			"foo",
  1061  		},
  1062  		"name string type, empty string": {
  1063  			map[string]*FieldSchema{
  1064  				"foo": {Type: TypeNameString},
  1065  			},
  1066  			map[string]interface{}{
  1067  				"foo": "",
  1068  			},
  1069  			"foo",
  1070  		},
  1071  		"keypair type, csv version empty key name": {
  1072  			map[string]*FieldSchema{
  1073  				"foo": {Type: TypeKVPairs},
  1074  			},
  1075  			map[string]interface{}{
  1076  				"foo": []interface{}{"=value1", "key2=value2", "key3=1"},
  1077  			},
  1078  			"foo",
  1079  		},
  1080  		"duration type, negative string value": {
  1081  			map[string]*FieldSchema{
  1082  				"foo": {Type: TypeDurationSecond},
  1083  			},
  1084  			map[string]interface{}{
  1085  				"foo": "-42",
  1086  			},
  1087  			"foo",
  1088  		},
  1089  		"duration type, negative string duration value": {
  1090  			map[string]*FieldSchema{
  1091  				"foo": {Type: TypeDurationSecond},
  1092  			},
  1093  			map[string]interface{}{
  1094  				"foo": "-42m",
  1095  			},
  1096  			"foo",
  1097  		},
  1098  		"duration type, negative int value": {
  1099  			map[string]*FieldSchema{
  1100  				"foo": {Type: TypeDurationSecond},
  1101  			},
  1102  			map[string]interface{}{
  1103  				"foo": -42,
  1104  			},
  1105  			"foo",
  1106  		},
  1107  		"duration type, negative float value": {
  1108  			map[string]*FieldSchema{
  1109  				"foo": {Type: TypeDurationSecond},
  1110  			},
  1111  			map[string]interface{}{
  1112  				"foo": -42.0,
  1113  			},
  1114  			"foo",
  1115  		},
  1116  	}
  1117  
  1118  	for name, tc := range cases {
  1119  		name, tc := name, tc
  1120  		t.Run(name, func(t *testing.T) {
  1121  			t.Parallel()
  1122  			data := &FieldData{
  1123  				Raw:    tc.Raw,
  1124  				Schema: tc.Schema,
  1125  			}
  1126  
  1127  			got, _, err := data.GetOkErr(tc.Key)
  1128  			if err == nil {
  1129  				t.Fatalf("error expected, none received, got result: %#v", got)
  1130  			}
  1131  		})
  1132  	}
  1133  }
  1134  
  1135  func TestFieldDataGetFirst(t *testing.T) {
  1136  	data := &FieldData{
  1137  		Raw: map[string]interface{}{
  1138  			"foo":  "bar",
  1139  			"fizz": "buzz",
  1140  		},
  1141  		Schema: map[string]*FieldSchema{
  1142  			"foo":  {Type: TypeNameString},
  1143  			"fizz": {Type: TypeNameString},
  1144  		},
  1145  	}
  1146  
  1147  	result, ok := data.GetFirst("foo", "fizz")
  1148  	if !ok {
  1149  		t.Fatal("should have found value for foo")
  1150  	}
  1151  	if result.(string) != "bar" {
  1152  		t.Fatal("should have gotten bar for foo")
  1153  	}
  1154  
  1155  	result, ok = data.GetFirst("fizz", "foo")
  1156  	if !ok {
  1157  		t.Fatal("should have found value for fizz")
  1158  	}
  1159  	if result.(string) != "buzz" {
  1160  		t.Fatal("should have gotten buzz for fizz")
  1161  	}
  1162  
  1163  	_, ok = data.GetFirst("cats")
  1164  	if ok {
  1165  		t.Fatal("shouldn't have gotten anything for cats")
  1166  	}
  1167  }
  1168  
  1169  func TestValidateStrict(t *testing.T) {
  1170  	cases := map[string]struct {
  1171  		Schema      map[string]*FieldSchema
  1172  		Raw         map[string]interface{}
  1173  		ExpectError bool
  1174  	}{
  1175  		"string type, string value": {
  1176  			map[string]*FieldSchema{
  1177  				"foo": {Type: TypeString},
  1178  			},
  1179  			map[string]interface{}{
  1180  				"foo": "bar",
  1181  			},
  1182  			false,
  1183  		},
  1184  
  1185  		"string type, int value": {
  1186  			map[string]*FieldSchema{
  1187  				"foo": {Type: TypeString},
  1188  			},
  1189  			map[string]interface{}{
  1190  				"foo": 42,
  1191  			},
  1192  			false,
  1193  		},
  1194  
  1195  		"string type, unset value": {
  1196  			map[string]*FieldSchema{
  1197  				"foo": {Type: TypeString},
  1198  			},
  1199  			map[string]interface{}{},
  1200  			false,
  1201  		},
  1202  
  1203  		"string type, unset required value": {
  1204  			map[string]*FieldSchema{
  1205  				"foo": {
  1206  					Type:     TypeString,
  1207  					Required: true,
  1208  				},
  1209  			},
  1210  			map[string]interface{}{},
  1211  			true,
  1212  		},
  1213  
  1214  		"value not in schema": {
  1215  			map[string]*FieldSchema{
  1216  				"foo": {
  1217  					Type:     TypeString,
  1218  					Required: true,
  1219  				},
  1220  			},
  1221  			map[string]interface{}{
  1222  				"foo": 42,
  1223  				"bar": 43,
  1224  			},
  1225  			true,
  1226  		},
  1227  
  1228  		"value not in schema, empty schema": {
  1229  			map[string]*FieldSchema{},
  1230  			map[string]interface{}{
  1231  				"foo": 42,
  1232  				"bar": 43,
  1233  			},
  1234  			true,
  1235  		},
  1236  
  1237  		"value not in schema, nil schema": {
  1238  			nil,
  1239  			map[string]interface{}{
  1240  				"foo": 42,
  1241  				"bar": 43,
  1242  			},
  1243  			false,
  1244  		},
  1245  
  1246  		"type time, invalid value": {
  1247  			map[string]*FieldSchema{
  1248  				"foo": {Type: TypeTime},
  1249  			},
  1250  			map[string]interface{}{
  1251  				"foo": "2021-13-11T09:08:07+02:00",
  1252  			},
  1253  			true,
  1254  		},
  1255  	}
  1256  
  1257  	for name, tc := range cases {
  1258  		name, tc := name, tc
  1259  		t.Run(name, func(t *testing.T) {
  1260  			t.Parallel()
  1261  
  1262  			data := &FieldData{
  1263  				Raw:    tc.Raw,
  1264  				Schema: tc.Schema,
  1265  			}
  1266  
  1267  			err := data.ValidateStrict()
  1268  
  1269  			if err == nil && tc.ExpectError == true {
  1270  				t.Fatalf("expected an error, got nil")
  1271  			}
  1272  			if err != nil && tc.ExpectError == false {
  1273  				t.Fatalf("unexpected error: %v", err)
  1274  			}
  1275  		})
  1276  	}
  1277  }