From f611eb38b3875cc3bd991ca91c51d06446afa14c Mon Sep 17 00:00:00 2001 From: Travis Parker Date: Tue, 24 Jan 2017 08:28:13 -0800 Subject: [PATCH] Map support (#73) * support maps with "key1:val1,key2:val2" syntax * quote the invalid string in error msgs * document map support in the README closes #70 --- README.md | 25 ++++++++++++---- envconfig.go | 21 +++++++++++++ envconfig_test.go | 17 +++++++++++ testdata/custom.txt | 1 + testdata/default_list.txt | 5 ++++ testdata/default_table.txt | 61 +++++++++++++++++++------------------- testdata/fault.txt | 1 + usage.go | 6 ++++ 8 files changed, 101 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 09de74b..b6c65a8 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ export MYAPP_USER=Kelsey export MYAPP_RATE="0.5" export MYAPP_TIMEOUT="3m" export MYAPP_USERS="rob,ken,robert" +export MYAPP_COLORCODES="red:1,green:2,blue:3" ``` Write some code: @@ -37,12 +38,13 @@ import ( ) type Specification struct { - Debug bool - Port int - User string - Users []string - Rate float32 - Timeout time.Duration + Debug bool + Port int + User string + Users []string + Rate float32 + Timeout time.Duration + ColorCodes map[string]int } func main() { @@ -61,6 +63,11 @@ func main() { for _, u := range s.Users { fmt.Printf(" %s\n", u) } + + fmt.Println("Color codes:") + for k, v := range s.ColorCodes { + fmt.Printf(" %s: %d\n", k, v) + } } ``` @@ -76,6 +83,10 @@ Users: rob ken robert +Color codes: + red: 1 + green: 2 + blue: 3 ``` ## Struct Tag Support @@ -145,6 +156,8 @@ envconfig supports supports these struct field types: * int8, int16, int32, int64 * bool * float32, float64 + * slices of any supported type + * maps (keys and values of any supported type) * [encoding.TextUnmarshaler](https://golang.org/pkg/encoding/#TextUnmarshaler) Embedded structs using these fields are also supported. diff --git a/envconfig.go b/envconfig.go index 3ad5e7d..892d746 100644 --- a/envconfig.go +++ b/envconfig.go @@ -265,6 +265,27 @@ func processField(value string, field reflect.Value) error { } } field.Set(sl) + case reflect.Map: + pairs := strings.Split(value, ",") + mp := reflect.MakeMap(typ) + for _, pair := range pairs { + kvpair := strings.Split(pair, ":") + if len(kvpair) != 2 { + return fmt.Errorf("invalid map item: %q", pair) + } + k := reflect.New(typ.Key()).Elem() + err := processField(kvpair[0], k) + if err != nil { + return err + } + v := reflect.New(typ.Elem()).Elem() + err = processField(kvpair[1], v) + if err != nil { + return err + } + mp.SetMapIndex(k, v) + } + field.Set(mp) } return nil diff --git a/envconfig_test.go b/envconfig_test.go index 03b6621..e754058 100644 --- a/envconfig_test.go +++ b/envconfig_test.go @@ -32,6 +32,7 @@ type Specification struct { Timeout time.Duration AdminUsers []string MagicNumbers []int + ColorCodes map[string]int MultiWordVar string MultiWordVarWithAutoSplit uint32 `split_words:"true"` SomePointer *string @@ -77,6 +78,7 @@ func TestProcess(t *testing.T) { os.Setenv("ENV_CONFIG_TIMEOUT", "2m") os.Setenv("ENV_CONFIG_ADMINUSERS", "John,Adam,Will") os.Setenv("ENV_CONFIG_MAGICNUMBERS", "5,10,20") + os.Setenv("ENV_CONFIG_COLORCODES", "red:1,green:2,blue:3") os.Setenv("SERVICE_HOST", "127.0.0.1") os.Setenv("ENV_CONFIG_TTL", "30") os.Setenv("ENV_CONFIG_REQUIREDVAR", "foo") @@ -130,6 +132,21 @@ func TestProcess(t *testing.T) { t.Errorf("expected empty string, got %#v", s.Ignored) } + if len(s.ColorCodes) != 3 || + s.ColorCodes["red"] != 1 || + s.ColorCodes["green"] != 2 || + s.ColorCodes["blue"] != 3 { + t.Errorf( + "expected %#v, got %#v", + map[string]int{ + "red": 1, + "green": 2, + "blue": 3, + }, + s.ColorCodes, + ) + } + if s.NestedSpecification.Property != "iamnested" { t.Errorf("expected '%s' string, got %#v", "iamnested", s.NestedSpecification.Property) } diff --git a/testdata/custom.txt b/testdata/custom.txt index 47edd22..243e82c 100644 --- a/testdata/custom.txt +++ b/testdata/custom.txt @@ -11,6 +11,7 @@ ENV_CONFIG_TTL= ENV_CONFIG_TIMEOUT= ENV_CONFIG_ADMINUSERS= ENV_CONFIG_MAGICNUMBERS= +ENV_CONFIG_COLORCODES= ENV_CONFIG_MULTIWORDVAR= ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT= ENV_CONFIG_SOMEPOINTER= diff --git a/testdata/default_list.txt b/testdata/default_list.txt index 7d4f8a4..bc29211 100644 --- a/testdata/default_list.txt +++ b/testdata/default_list.txt @@ -66,6 +66,11 @@ ENV_CONFIG_MAGICNUMBERS ..[type]........Comma-separated.list.of.Integer ..[default]..... ..[required].... +ENV_CONFIG_COLORCODES +..[description]. +..[type]........Comma-separated.list.of.String:Integer.pairs +..[default]..... +..[required].... ENV_CONFIG_MULTIWORDVAR ..[description]. ..[type]........String diff --git a/testdata/default_table.txt b/testdata/default_table.txt index a1e560a..f3cf945 100644 --- a/testdata/default_table.txt +++ b/testdata/default_table.txt @@ -1,33 +1,34 @@ This.application.is.configured.via.the.environment..The.following.environment variables.can.be.used: -KEY..............................................TYPE...............................DEFAULT...........REQUIRED....DESCRIPTION -ENV_CONFIG_ENABLED...............................True.or.False....................................................some.embedded.value -ENV_CONFIG_EMBEDDEDPORT..........................Integer.......................................................... -ENV_CONFIG_MULTIWORDVAR..........................String........................................................... -ENV_CONFIG_MULTI_WITH_DIFFERENT_ALT..............String........................................................... -ENV_CONFIG_EMBEDDED_WITH_ALT.....................String........................................................... -ENV_CONFIG_DEBUG.................................True.or.False.................................................... -ENV_CONFIG_PORT..................................Integer.......................................................... -ENV_CONFIG_RATE..................................Float............................................................ -ENV_CONFIG_USER..................................String........................................................... -ENV_CONFIG_TTL...................................Unsigned.Integer................................................. -ENV_CONFIG_TIMEOUT...............................Duration......................................................... -ENV_CONFIG_ADMINUSERS............................Comma-separated.list.of.String................................... -ENV_CONFIG_MAGICNUMBERS..........................Comma-separated.list.of.Integer.................................. -ENV_CONFIG_MULTIWORDVAR..........................String........................................................... -ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT........Unsigned.Integer................................................. -ENV_CONFIG_SOMEPOINTER...........................String........................................................... -ENV_CONFIG_SOMEPOINTERWITHDEFAULT................String.............................foo2baz.......................foorbar.is.the.word -ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT...............String...........................................................what.alt -ENV_CONFIG_MULTI_WORD_VAR_WITH_LOWER_CASE_ALT....String........................................................... -ENV_CONFIG_SERVICE_HOST..........................String........................................................... -ENV_CONFIG_DEFAULTVAR............................String.............................foobar........................ -ENV_CONFIG_REQUIREDVAR...........................String...............................................true........ -ENV_CONFIG_BROKER................................String.............................127.0.0.1..................... -ENV_CONFIG_REQUIREDDEFAULT.......................String.............................foo2bar...........true........ -ENV_CONFIG_OUTER_INNER...........................String........................................................... -ENV_CONFIG_OUTER_PROPERTYWITHDEFAULT.............String.............................fuzzybydefault................ -ENV_CONFIG_AFTERNESTED...........................String........................................................... -ENV_CONFIG_HONOR.................................HonorDecodeInStruct.............................................. -ENV_CONFIG_DATETIME..............................Time............................................................. +KEY..............................................TYPE............................................DEFAULT...........REQUIRED....DESCRIPTION +ENV_CONFIG_ENABLED...............................True.or.False.................................................................some.embedded.value +ENV_CONFIG_EMBEDDEDPORT..........................Integer....................................................................... +ENV_CONFIG_MULTIWORDVAR..........................String........................................................................ +ENV_CONFIG_MULTI_WITH_DIFFERENT_ALT..............String........................................................................ +ENV_CONFIG_EMBEDDED_WITH_ALT.....................String........................................................................ +ENV_CONFIG_DEBUG.................................True.or.False................................................................. +ENV_CONFIG_PORT..................................Integer....................................................................... +ENV_CONFIG_RATE..................................Float......................................................................... +ENV_CONFIG_USER..................................String........................................................................ +ENV_CONFIG_TTL...................................Unsigned.Integer.............................................................. +ENV_CONFIG_TIMEOUT...............................Duration...................................................................... +ENV_CONFIG_ADMINUSERS............................Comma-separated.list.of.String................................................ +ENV_CONFIG_MAGICNUMBERS..........................Comma-separated.list.of.Integer............................................... +ENV_CONFIG_COLORCODES............................Comma-separated.list.of.String:Integer.pairs.................................. +ENV_CONFIG_MULTIWORDVAR..........................String........................................................................ +ENV_CONFIG_MULTI_WORD_VAR_WITH_AUTO_SPLIT........Unsigned.Integer.............................................................. +ENV_CONFIG_SOMEPOINTER...........................String........................................................................ +ENV_CONFIG_SOMEPOINTERWITHDEFAULT................String..........................................foo2baz.......................foorbar.is.the.word +ENV_CONFIG_MULTI_WORD_VAR_WITH_ALT...............String........................................................................what.alt +ENV_CONFIG_MULTI_WORD_VAR_WITH_LOWER_CASE_ALT....String........................................................................ +ENV_CONFIG_SERVICE_HOST..........................String........................................................................ +ENV_CONFIG_DEFAULTVAR............................String..........................................foobar........................ +ENV_CONFIG_REQUIREDVAR...........................String............................................................true........ +ENV_CONFIG_BROKER................................String..........................................127.0.0.1..................... +ENV_CONFIG_REQUIREDDEFAULT.......................String..........................................foo2bar...........true........ +ENV_CONFIG_OUTER_INNER...........................String........................................................................ +ENV_CONFIG_OUTER_PROPERTYWITHDEFAULT.............String..........................................fuzzybydefault................ +ENV_CONFIG_AFTERNESTED...........................String........................................................................ +ENV_CONFIG_HONOR.................................HonorDecodeInStruct........................................................... +ENV_CONFIG_DATETIME..............................Time.......................................................................... diff --git a/testdata/fault.txt b/testdata/fault.txt index 20783e3..30e28ce 100644 --- a/testdata/fault.txt +++ b/testdata/fault.txt @@ -27,3 +27,4 @@ {.Key} {.Key} {.Key} +{.Key} diff --git a/usage.go b/usage.go index 4870237..1846353 100644 --- a/usage.go +++ b/usage.go @@ -56,6 +56,12 @@ func toTypeDescription(t reflect.Type) string { switch t.Kind() { case reflect.Array, reflect.Slice: return fmt.Sprintf("Comma-separated list of %s", toTypeDescription(t.Elem())) + case reflect.Map: + return fmt.Sprintf( + "Comma-separated list of %s:%s pairs", + toTypeDescription(t.Key()), + toTypeDescription(t.Elem()), + ) case reflect.Ptr: return toTypeDescription(t.Elem()) case reflect.Struct: