@@ -16,6 +16,16 @@ type testConfig struct {
1616 FloatValue float64 `env:"CONFIGSTRUCT_FLOAT" cli:"floatValue" usage:"float value"`
1717}
1818
19+ type endpoint struct {
20+ User string `json:"user" yaml:"user"`
21+ Pass string `json:"pass" yaml:"pass"`
22+ URL string `json:"url" yaml:"url"`
23+ }
24+
25+ type endpointConfig struct {
26+ Endpoints []endpoint `env:"CONFIGSTRUCT_ENDPOINTS" cli:"endpoints" yaml:"endpoints"`
27+ }
28+
1929func TestParse (t * testing.T ) {
2030 t .Run ("valid cli fields" , func (t * testing.T ) {
2131 cliArgs := []string {"command" , "-hostname=localhost" , "-port=8080" , "-debug=true" , "-floatValue=100.5" }
@@ -247,6 +257,166 @@ func TestParse(t *testing.T) {
247257 assert .Equal (t , tmpFile , conf .ConfigPath )
248258 })
249259
260+ t .Run ("env json array for struct slice" , func (t * testing.T ) {
261+ os .Clearenv ()
262+ os .Setenv ("CONFIGSTRUCT_ENDPOINTS" , `[{"user":"u1","pass":"p1","url":"https://a"},{"user":"u2","pass":"p2","url":"https://b"}]` )
263+
264+ cliArgs := []string {"command" }
265+ flagSet := flag .NewFlagSet (cliArgs [0 ], flag .ExitOnError )
266+ conf := endpointConfig {}
267+
268+ err := ParseWithFlagSet (flagSet , cliArgs , & conf )
269+ assert .NoError (t , err )
270+ assert .Len (t , conf .Endpoints , 2 )
271+ assert .Equal (t , "u1" , conf .Endpoints [0 ].User )
272+ assert .Equal (t , "https://b" , conf .Endpoints [1 ].URL )
273+ })
274+
275+ t .Run ("env json object for struct slice" , func (t * testing.T ) {
276+ os .Clearenv ()
277+ os .Setenv ("CONFIGSTRUCT_ENDPOINTS" , `{"user":"u1","pass":"p1","url":"https://a"}` )
278+
279+ cliArgs := []string {"command" }
280+ flagSet := flag .NewFlagSet (cliArgs [0 ], flag .ExitOnError )
281+ conf := endpointConfig {}
282+
283+ err := ParseWithFlagSet (flagSet , cliArgs , & conf )
284+ assert .NoError (t , err )
285+ assert .Len (t , conf .Endpoints , 1 )
286+ assert .Equal (t , "u1" , conf .Endpoints [0 ].User )
287+ assert .Equal (t , "https://a" , conf .Endpoints [0 ].URL )
288+ })
289+
290+ t .Run ("cli repeated json objects for struct slice" , func (t * testing.T ) {
291+ os .Clearenv ()
292+ cliArgs := []string {
293+ "command" ,
294+ `-endpoints={"user":"u1","pass":"p1","url":"https://a"}` ,
295+ `-endpoints={"user":"u2","pass":"p2","url":"https://b"}` ,
296+ }
297+ flagSet := flag .NewFlagSet (cliArgs [0 ], flag .ExitOnError )
298+ conf := endpointConfig {
299+ Endpoints : []endpoint {{User : "default" , Pass : "default" , URL : "https://default" }},
300+ }
301+
302+ err := ParseWithFlagSet (flagSet , cliArgs , & conf )
303+ assert .NoError (t , err )
304+ assert .Len (t , conf .Endpoints , 2 )
305+ assert .Equal (t , "u1" , conf .Endpoints [0 ].User )
306+ assert .Equal (t , "u2" , conf .Endpoints [1 ].User )
307+ })
308+
309+ t .Run ("cli single json array for struct slice" , func (t * testing.T ) {
310+ os .Clearenv ()
311+ cliArgs := []string {
312+ "command" ,
313+ `-endpoints=[{"user":"u1","pass":"p1","url":"https://a"},{"user":"u2","pass":"p2","url":"https://b"}]` ,
314+ }
315+ flagSet := flag .NewFlagSet (cliArgs [0 ], flag .ExitOnError )
316+ conf := endpointConfig {}
317+
318+ err := ParseWithFlagSet (flagSet , cliArgs , & conf )
319+ assert .NoError (t , err )
320+ assert .Len (t , conf .Endpoints , 2 )
321+ assert .Equal (t , "u1" , conf .Endpoints [0 ].User )
322+ assert .Equal (t , "u2" , conf .Endpoints [1 ].User )
323+ })
324+
325+ t .Run ("default precedence cli over env for struct slice" , func (t * testing.T ) {
326+ os .Clearenv ()
327+ os .Setenv ("CONFIGSTRUCT_ENDPOINTS" , `[{"user":"env","pass":"env","url":"https://env"}]` )
328+
329+ cliArgs := []string {
330+ "command" ,
331+ `-endpoints={"user":"cli","pass":"cli","url":"https://cli"}` ,
332+ }
333+ flagSet := flag .NewFlagSet (cliArgs [0 ], flag .ExitOnError )
334+ conf := endpointConfig {}
335+
336+ err := ParseWithFlagSet (flagSet , cliArgs , & conf )
337+ assert .NoError (t , err )
338+ assert .Len (t , conf .Endpoints , 1 )
339+ assert .Equal (t , "cli" , conf .Endpoints [0 ].User )
340+ })
341+
342+ t .Run ("env precedence over cli for struct slice" , func (t * testing.T ) {
343+ os .Clearenv ()
344+ os .Setenv ("CONFIGSTRUCT_ENDPOINTS" , `[{"user":"env","pass":"env","url":"https://env"}]` )
345+
346+ cliArgs := []string {
347+ "command" ,
348+ `-endpoints={"user":"cli","pass":"cli","url":"https://cli"}` ,
349+ }
350+ flagSet := flag .NewFlagSet (cliArgs [0 ], flag .ExitOnError )
351+ conf := endpointConfig {}
352+
353+ err := ParseWithFlagSet (flagSet , cliArgs , & conf , WithPrecedenceEnv ())
354+ assert .NoError (t , err )
355+ assert .Len (t , conf .Endpoints , 1 )
356+ assert .Equal (t , "env" , conf .Endpoints [0 ].User )
357+ })
358+
359+ t .Run ("yaml struct slice overridden by cli" , func (t * testing.T ) {
360+ type Config struct {
361+ Endpoints []endpoint `yaml:"endpoints" cli:"endpoints"`
362+ }
363+
364+ tmpFile := "test_slice_yaml_override.yaml"
365+ defer os .Remove (tmpFile )
366+
367+ err := Save (tmpFile , & Config {
368+ Endpoints : []endpoint {{User : "yaml" , Pass : "yaml" , URL : "https://yaml" }},
369+ })
370+ assert .NoError (t , err )
371+
372+ conf := Config {}
373+ cliArgs := []string {
374+ "command" ,
375+ `-endpoints={"user":"cli","pass":"cli","url":"https://cli"}` ,
376+ }
377+ flagSet := flag .NewFlagSet (cliArgs [0 ], flag .ExitOnError )
378+ err = ParseWithFlagSet (flagSet , cliArgs , & conf , WithYamlConfig (tmpFile ))
379+ assert .NoError (t , err )
380+ assert .Len (t , conf .Endpoints , 1 )
381+ assert .Equal (t , "cli" , conf .Endpoints [0 ].User )
382+ })
383+
384+ t .Run ("invalid env json returns error for struct slice" , func (t * testing.T ) {
385+ os .Clearenv ()
386+ os .Setenv ("CONFIGSTRUCT_ENDPOINTS" , `{"user":` )
387+
388+ cliArgs := []string {"command" }
389+ flagSet := flag .NewFlagSet (cliArgs [0 ], flag .ExitOnError )
390+ conf := endpointConfig {}
391+
392+ err := ParseWithFlagSet (flagSet , cliArgs , & conf )
393+ assert .Error (t , err )
394+ })
395+
396+ t .Run ("invalid cli json returns error for struct slice" , func (t * testing.T ) {
397+ os .Clearenv ()
398+ cliArgs := []string {"command" , `-endpoints={"user":}` }
399+ flagSet := flag .NewFlagSet (cliArgs [0 ], flag .ContinueOnError )
400+ conf := endpointConfig {}
401+
402+ err := ParseWithFlagSet (flagSet , cliArgs , & conf )
403+ assert .Error (t , err )
404+ })
405+
406+ t .Run ("struct slice keeps defaults when no input is set" , func (t * testing.T ) {
407+ os .Clearenv ()
408+ cliArgs := []string {"command" }
409+ flagSet := flag .NewFlagSet (cliArgs [0 ], flag .ExitOnError )
410+ conf := endpointConfig {
411+ Endpoints : []endpoint {{User : "default" , Pass : "default" , URL : "https://default" }},
412+ }
413+
414+ err := ParseWithFlagSet (flagSet , cliArgs , & conf )
415+ assert .NoError (t , err )
416+ assert .Len (t , conf .Endpoints , 1 )
417+ assert .Equal (t , "default" , conf .Endpoints [0 ].User )
418+ })
419+
250420}
251421
252422// Example for using `configstruct` with default values.
0 commit comments