diff --git a/README.md b/README.md index c51467e..de02bd4 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Define a struct with `env` struct tags: ```go type Config struct { Hostname string `env:"SERVER_HOSTNAME,default=localhost"` - Port uint16 `env:"SERVER_PORT,default=8080"` + Port uint16 `env:"HTTP_PORT;SERVER_PORT,default=8080"` AWS struct { ID string `env:"AWS_ACCESS_KEY_ID"` @@ -30,8 +30,8 @@ type Config struct { } ``` -Fields *must be exported* (i.e. begin with a capital letter) in order -for `envdecode` to work with them. An error will be returned if a +Fields _must be exported_ (i.e. begin with a capital letter) in order +for `envdecode` to work with them. An error will be returned if a struct with no exported fields is decoded (including one that contains no `env` tags at all). Default values may be provided by appending ",default=value" to the @@ -57,16 +57,16 @@ All parse errors will fail fast and return an error in this mode. ## Supported types -* Structs (and pointer to structs) -* Slices of below defined types, separated by semicolon -* `bool` -* `float32`, `float64` -* `int`, `int8`, `int16`, `int32`, `int64` -* `uint`, `uint8`, `uint16`, `uint32`, `uint64` -* `string` -* `time.Duration`, using the [`time.ParseDuration()` format](http://golang.org/pkg/time/#ParseDuration) -* `*url.URL`, using [`url.Parse()`](https://godoc.org/net/url#Parse) -* Types those implement a `Decoder` interface +- Structs (and pointer to structs) +- Slices of below defined types, separated by semicolon +- `bool` +- `float32`, `float64` +- `int`, `int8`, `int16`, `int32`, `int64` +- `uint`, `uint8`, `uint16`, `uint32`, `uint64` +- `string` +- `time.Duration`, using the [`time.ParseDuration()` format](http://golang.org/pkg/time/#ParseDuration) +- `*url.URL`, using [`url.Parse()`](https://godoc.org/net/url#Parse) +- Types those implement a `Decoder` interface ## Custom `Decoder` diff --git a/envdecode.go b/envdecode.go index c8c0a65..d97d698 100644 --- a/envdecode.go +++ b/envdecode.go @@ -146,7 +146,15 @@ func decode(target interface{}, strict bool) (int, error) { } parts := strings.Split(tag, ",") - env := os.Getenv(parts[0]) + overrides := strings.Split(parts[0], `;`) + + var env string + for _, override := range overrides { + v := os.Getenv(override) + if v != "" { + env = v + } + } required := false hasDefault := false diff --git a/envdecode_test.go b/envdecode_test.go index 6c340c9..bf6fd33 100644 --- a/envdecode_test.go +++ b/envdecode_test.go @@ -78,6 +78,10 @@ type testConfigRequiredDefault struct { RequiredDefault string `env:"TEST_REQUIRED_DEFAULT,required,default=test"` } +type testConfigOverride struct { + OverrideString string `env:"TEST_OVERRIDE_A;TEST_OVERRIDE_B,default=override_default"` +} + type testNoExportedFields struct { aString string `env:"TEST_STRING"` anInt64 int64 `env:"TEST_INT64"` @@ -318,6 +322,40 @@ func TestDecode(t *testing.T) { if err != nil { t.Fatal(err) } + + var tco testConfigOverride + err = Decode(&tco) + if err != nil { + t.Fatal(err) + } + + if tco.OverrideString != "override_default" { + t.Fatalf(`Expected "override_default" but got %s`, tco.OverrideString) + } + + os.Setenv("TEST_OVERRIDE_A", "override_a") + + tco = testConfigOverride{} + err = Decode(&tco) + if err != nil { + t.Fatal(err) + } + + if tco.OverrideString != "override_a" { + t.Fatalf(`Expected "override_a" but got %s`, tco.OverrideString) + } + + os.Setenv("TEST_OVERRIDE_B", "override_b") + + tco = testConfigOverride{} + err = Decode(&tco) + if err != nil { + t.Fatal(err) + } + + if tco.OverrideString != "override_b" { + t.Fatalf(`Expected "override_b" but got %s`, tco.OverrideString) + } } func TestDecodeErrors(t *testing.T) {