Skip to content

Commit

Permalink
Merge branch 'feature/panic-when-keys-are-duplicated' into 'master'
Browse files Browse the repository at this point in the history
feat: panic if detecting duplicated keys

See merge request backend/envconf!7
  • Loading branch information
komod committed Aug 3, 2020
2 parents d563962 + 7d6a7cf commit a9f4731
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 17 deletions.
61 changes: 44 additions & 17 deletions envconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
45 changes: 45 additions & 0 deletions envconf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit a9f4731

Please sign in to comment.