go.uber.org/yarpc@v1.72.1/internal/config/mapdecode_test.go (about)

     1  // Copyright (c) 2022 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package config
    22  
    23  import (
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  	"go.uber.org/yarpc/internal/interpolate"
    30  )
    31  
    32  func TestInterpolateHook(t *testing.T) {
    33  	type someStruct struct {
    34  		SomeString   string        `config:",interpolate"`
    35  		SomeInt      int           `config:",interpolate"`
    36  		SomeFloat    float32       `config:",interpolate"`
    37  		SomeDuration time.Duration `config:",interpolate"`
    38  
    39  		Key string `config:"name,interpolate"`
    40  
    41  		// We don't support interpolation for lists and maps yet.
    42  		SomeMap  map[string]string `config:",interpolate"`
    43  		SomeList []string          `config:",interpolate"`
    44  
    45  		// Uninterpolated fields
    46  		AnotherString string
    47  	}
    48  
    49  	tests := []struct {
    50  		desc string
    51  		give interface{}
    52  		env  map[string]string
    53  
    54  		want       someStruct
    55  		wantErrors []string
    56  	}{
    57  		{
    58  			desc: "bad string",
    59  			give: map[string]interface{}{
    60  				"someString": "hello ${NAME:world",
    61  			},
    62  			wantErrors: []string{`failed to parse "hello ${NAME:world" for interpolation`},
    63  		},
    64  		{
    65  			desc: "bad string uninterpolated field",
    66  			give: map[string]interface{}{
    67  				"anotherString": "hello ${NAME:world",
    68  			},
    69  			want: someStruct{AnotherString: "hello ${NAME:world"},
    70  		},
    71  		{
    72  			desc: "missing variable",
    73  			give: map[string]interface{}{"someString": "hello ${NAME}"},
    74  			wantErrors: []string{
    75  				`failed to render "hello ${NAME}" with environment variables`,
    76  			},
    77  		},
    78  		{
    79  			desc: "string",
    80  			give: map[string]interface{}{
    81  				"someString": "hello ${NAME:world}",
    82  			},
    83  			env:  map[string]string{"NAME": "foo"},
    84  			want: someStruct{SomeString: "hello foo"},
    85  		},
    86  		{
    87  			desc: "string default",
    88  			give: map[string]interface{}{
    89  				"someString": "hello ${NAME:world}",
    90  			},
    91  			want: someStruct{SomeString: "hello world"},
    92  		},
    93  		{
    94  			desc: "int",
    95  			give: map[string]interface{}{"someInt": "12${FOO:}5"},
    96  			env:  map[string]string{"FOO": "34"},
    97  			want: someStruct{SomeInt: 12345},
    98  		},
    99  		{
   100  			desc: "int default",
   101  			give: map[string]interface{}{"someInt": "12${FOO:}5"},
   102  			want: someStruct{SomeInt: 125},
   103  		},
   104  		{
   105  			desc: "float",
   106  			give: map[string]interface{}{"SOMEFLOAT": "12${BAR:}.${BAZ:0}"},
   107  			env:  map[string]string{"BAR": "34", "BAZ": "56"},
   108  			want: someStruct{SomeFloat: 1234.56},
   109  		},
   110  		{
   111  			desc: "float default",
   112  			give: map[string]interface{}{"SOMEFLOAT": "12${BAR:}.${BAZ:0}"},
   113  			want: someStruct{SomeFloat: 12},
   114  		},
   115  		{
   116  			desc: "duration",
   117  			give: map[string]interface{}{"someDuration": "5${UNIT:s}"},
   118  			env:  map[string]string{"UNIT": "m"},
   119  			want: someStruct{SomeDuration: 5 * time.Minute},
   120  		},
   121  		{
   122  			desc: "duration default",
   123  			give: map[string]interface{}{"someDuration": "5${UNIT:s}"},
   124  			want: someStruct{SomeDuration: 5 * time.Second},
   125  		},
   126  		{
   127  			desc: "name override",
   128  			give: map[string]interface{}{"name": "foo ${BAR:baz}"},
   129  			env:  map[string]string{"BAR": "foo"},
   130  			want: someStruct{Key: "foo foo"},
   131  		},
   132  		{
   133  			desc: "name override default",
   134  			give: map[string]interface{}{"name": "foo ${BAR:baz}"},
   135  			want: someStruct{Key: "foo baz"},
   136  		},
   137  		{
   138  			desc: "uninterpolated field",
   139  			give: map[string]interface{}{"AnotherString": "hello ${NAME}"},
   140  			env:  map[string]string{"NAME": "world"},
   141  			want: someStruct{AnotherString: "hello ${NAME}"},
   142  		},
   143  		{
   144  			desc: "map",
   145  			give: map[string]interface{}{
   146  				"someMap": map[string]interface{}{
   147  					"foo": "${BAR}",
   148  					"baz": "${QUX}",
   149  				},
   150  			},
   151  			env: map[string]string{
   152  				"BAR": "foo",
   153  				"QUX": "baz",
   154  			},
   155  			want: someStruct{
   156  				SomeMap: map[string]string{
   157  					"foo": "${BAR}",
   158  					"baz": "${QUX}",
   159  				},
   160  			},
   161  		},
   162  		{
   163  			desc: "list",
   164  			give: map[string]interface{}{
   165  				"someList": []interface{}{
   166  					"foo",
   167  					"${BAR}",
   168  					"baz",
   169  					"${QUX}",
   170  				},
   171  			},
   172  			env: map[string]string{
   173  				"BAR": "foo",
   174  				"QUX": "baz",
   175  			},
   176  			want: someStruct{
   177  				SomeList: []string{
   178  					"foo",
   179  					"${BAR}",
   180  					"baz",
   181  					"${QUX}",
   182  				},
   183  			},
   184  		},
   185  	}
   186  
   187  	for _, tt := range tests {
   188  		t.Run(tt.desc, func(t *testing.T) {
   189  			var dest someStruct
   190  			err := DecodeInto(&dest, tt.give, InterpolateWith(mapVariableResolver(tt.env)))
   191  
   192  			if len(tt.wantErrors) > 0 {
   193  				require.Error(t, err)
   194  				for _, msg := range tt.wantErrors {
   195  					assert.Contains(t, err.Error(), msg)
   196  				}
   197  				return
   198  			}
   199  
   200  			require.NoError(t, err)
   201  			assert.Equal(t, tt.want, dest)
   202  		})
   203  	}
   204  }
   205  
   206  func mapVariableResolver(m map[string]string) interpolate.VariableResolver {
   207  	return func(name string) (value string, ok bool) {
   208  		value, ok = m[name]
   209  		return
   210  	}
   211  }