diff --git a/envconf.go b/envconf.go index f8ffa45..ce38c6a 100644 --- a/envconf.go +++ b/envconf.go @@ -17,41 +17,68 @@ import ( // 2. Duplicated field tags will be assigned with the same environment variable. func Load(prefix string, out interface{}) { val := reflect.ValueOf(out).Elem() - loadStruct(strings.ToUpper(prefix), &val) + loader := loader{ + usedKeys: map[string]struct{}{}, + } + loader.loadStruct(strings.ToUpper(prefix), &val) } var stringSliceType = reflect.TypeOf([]string{}) -func loadField(name string, out *reflect.Value) { +type loader struct { + usedKeys map[string]struct{} +} + +func (l *loader) useKey(name string) error { + if _, ok := l.usedKeys[name]; ok { + return fmt.Errorf("Duplicated key %v", name) + } + l.usedKeys[name] = struct{}{} + return nil +} + +func (l *loader) loadField(name string, out *reflect.Value) { + kind := out.Kind() + if kind == reflect.Struct { + l.loadStruct(name, out) + return + } + + if err := l.useKey(name); err != nil { + panic(err) + } + switch out.Kind() { case reflect.String: - loadString(name, out) + l.loadString(name, out) + return case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - loadInt(name, out) + l.loadInt(name, out) + return case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - loadUint(name, out) + l.loadUint(name, out) + return case reflect.Bool: - loadBool(name, out) - - case reflect.Struct: - loadStruct(name, out) + l.loadBool(name, out) + return case reflect.Slice: sliceType := out.Type() switch sliceType { case stringSliceType: - loadStringSlice(name, out) + l.loadStringSlice(name, out) + return default: panic(fmt.Errorf("slice type %v on %v is not supported", sliceType, name)) @@ -62,7 +89,7 @@ func loadField(name string, out *reflect.Value) { } } -func loadStruct(prefix string, out *reflect.Value) { +func (l *loader) loadStruct(prefix string, out *reflect.Value) { t := out.Type() for i := 0; i < t.NumField(); i++ { field := t.Field(i) @@ -98,11 +125,11 @@ func loadStruct(prefix string, out *reflect.Value) { } fval := out.Field(i) - loadField(name, &fval) + l.loadField(name, &fval) } } -func loadString(name string, out *reflect.Value) { +func (l *loader) loadString(name string, out *reflect.Value) { data, found := syscall.Getenv(name) if !found { return @@ -111,7 +138,7 @@ func loadString(name string, out *reflect.Value) { out.SetString(data) } -func loadInt(name string, out *reflect.Value) { +func (l *loader) loadInt(name string, out *reflect.Value) { data, found := syscall.Getenv(name) if !found { return @@ -125,7 +152,7 @@ func loadInt(name string, out *reflect.Value) { out.SetInt(d) } -func loadUint(name string, out *reflect.Value) { +func (l *loader) loadUint(name string, out *reflect.Value) { data, found := syscall.Getenv(name) if !found { return @@ -139,7 +166,7 @@ func loadUint(name string, out *reflect.Value) { out.SetUint(d) } -func loadBool(name string, out *reflect.Value) { +func (l *loader) loadBool(name string, out *reflect.Value) { data, found := syscall.Getenv(name) if !found { return @@ -155,7 +182,7 @@ func loadBool(name string, out *reflect.Value) { out.SetBool(isTrue) } -func loadStringSlice(name string, out *reflect.Value) { +func (l *loader) loadStringSlice(name string, out *reflect.Value) { data, found := syscall.Getenv(name) if !found { return diff --git a/envconf_test.go b/envconf_test.go index cc6fbd7..963053a 100644 --- a/envconf_test.go +++ b/envconf_test.go @@ -254,3 +254,48 @@ func TestNoTag(t *testing.T) { assertEqual(t, "StringInEmbeddedStructure", "a-z", config.StringInEmbeddedStructure) assertEqual(t, "IntInEmbeddedStructure", 19, config.IntInEmbeddedStructure) } + +func TestDuplicatedKeys(t *testing.T) { + defer assertPanic(t) + os.Setenv("TEST_DUPLICATED_STRING", "duplication") + + config := struct { + Str string `env:"duplicated_string"` + Struct struct { + Str string `env:"string"` + } `env:"duplicated"` + }{} + Load("TEST", &config) +} +func TestDuplicatedKeysBetweenInlineStructs(t *testing.T) { + defer assertPanic(t) + os.Setenv("TEST_DUPLICATED_STRING", "duplication") + + type em1 struct { + Str1 string `env:"duplicated_string"` + } + type em2 struct { + Str2 string `env:"duplicated_string"` + } + config := struct { + em1 `env:",inline"` + em2 `env:",inline"` + }{} + Load("TEST", &config) +} +func TestDuplicatedKeysBetweenStructs(t *testing.T) { + defer assertPanic(t) + os.Setenv("TEST_DUPLICATED_STRING", "duplication") + + type em1 struct { + Str1 string `env:"duplicated_string"` + } + type em2 struct { + Str2 string `env:"string"` + } + config := struct { + EM1 em1 `env:"em"` + EM2 em2 `env:"em_duplicated"` + }{} + Load("TEST", &config) +}