From cd5d3e4aab20f86867daa507a1b57d1ab416f389 Mon Sep 17 00:00:00 2001 From: urso Date: Wed, 22 Dec 2021 15:22:57 +0100 Subject: [PATCH] Allow structs without public fields to implement Setter If a struct does not provide a public field that might be set by cleanenv, then the struct was ignored. This did make it impossible to implement a custom struct with SetValue. The change checks if a field implements SetValue. If SetValue is implemented we stop recursing into the type to looks for other fields that might be populated via cleanenv. --- cleanenv.go | 28 ++++++++++++++++++++-------- example_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/cleanenv.go b/cleanenv.go index 3b03d74..1491e08 100644 --- a/cleanenv.go +++ b/cleanenv.go @@ -238,15 +238,27 @@ func readStructMetadata(cfgRoot interface{}) ([]structMeta, error) { // process nested structure (except of time.Time) if fld := s.Field(idx); fld.Kind() == reflect.Struct { - // add structure to parsing stack - if fld.Type() != reflect.TypeOf(time.Time{}) { - prefix, _ := fType.Tag.Lookup(TagEnvPrefix) - cfgStack = append(cfgStack, cfgNode{fld.Addr().Interface(), sPrefix + prefix}) - continue + // check if type implements a custom setter + canSet := false + if fld.CanInterface() { + if _, ok := fld.Interface().(Setter); ok { + canSet = true + } else if _, ok := fld.Addr().Interface().(Setter); ok { + canSet = true + } } - // process time.Time - if l, ok := fType.Tag.Lookup(TagEnvLayout); ok { - layout = &l + + if !canSet { + // add structure to parsing stack + if fld.Type() != reflect.TypeOf(time.Time{}) { + prefix, _ := fType.Tag.Lookup(TagEnvPrefix) + cfgStack = append(cfgStack, cfgNode{fld.Addr().Interface(), sPrefix + prefix}) + continue + } + // process time.Time + if l, ok := fType.Tag.Lookup(TagEnvLayout); ok { + layout = &l + } } } diff --git a/example_test.go b/example_test.go index bc5473e..3b91886 100644 --- a/example_test.go +++ b/example_test.go @@ -1,6 +1,7 @@ package cleanenv_test import ( + "errors" "flag" "fmt" "os" @@ -195,6 +196,36 @@ func Example_setter() { //Output: {Default:test1 Custom:my field is: test2} } +// MyCustomStruct is an example type with a custom setter on a struct with +// private fields only. +type MyCustomStruct struct { + priv string +} + +func (cs *MyCustomStruct) SetValue(s string) error { + if s == "" { + return errors.New("empty value") + } + cs.priv = s + return nil +} + +func (cs *MyCustomStruct) String() string { + return cs.priv +} + +func Example_structsetter() { + type config struct { + Custom MyCustomStruct `env:"CUSTOM"` + } + + var cfg config + os.Setenv("CUSTOM", "test") + cleanenv.ReadEnv(&cfg) + fmt.Println(cfg.Custom.String()) + //Output: test +} + // ConfigUpdate is a type with a custom updater type ConfigUpdate struct { Default string `env:"DEFAULT"`