diff --git a/.travis.yml b/.travis.yml index b737167..ad5c8b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,7 @@ language: go go: - - 1.5 - - 1.6 - - 1.7 - - 1.8 - - 1.9 - - "1.10" - - "1.11" - - "1.12" - - tip + - "1.12" + - "1.13" + - "1.14" + - tip diff --git a/envconfig.go b/envconfig.go index 9cdec8f..85bbbf0 100644 --- a/envconfig.go +++ b/envconfig.go @@ -148,9 +148,11 @@ func readStruct(value reflect.Value, ctx *context) (nonNil bool, err error) { for i := 0; i < value.NumField(); i++ { field := value.Field(i) - name := value.Type().Field(i).Name + fieldType := field.Type() + fieldInfo := value.Type().Field(i) + name := fieldInfo.Name - tag := parseTag(value.Type().Field(i).Tag.Get("envconfig")) + tag := parseTag(fieldInfo.Tag.Get("envconfig")) if tag.skip || !field.CanSet() { if !field.CanSet() && !ctx.allowUnexported { return false, ErrUnexportedField @@ -161,8 +163,8 @@ func readStruct(value reflect.Value, ctx *context) (nonNil bool, err error) { parents = ctx.parents doRead: - switch field.Kind() { - case reflect.Ptr: + switch { + case field.Kind() == reflect.Ptr && !isUnmarshaler(fieldType): // it's a pointer, create a new value and restart the switch if field.IsNil() { field.Set(reflect.New(field.Type().Elem())) @@ -170,7 +172,7 @@ func readStruct(value reflect.Value, ctx *context) (nonNil bool, err error) { } field = field.Elem() goto doRead - case reflect.Struct: + case field.Kind() == reflect.Struct && !isUnmarshaler(fieldType): var nonNilIn bool nonNilIn, err = readStruct(field, &context{ name: combineName(ctx.name, name), @@ -267,8 +269,8 @@ func setSliceField(value reflect.Value, str string, ctx *context) error { } var ( - durationType = reflect.TypeOf(new(time.Duration)).Elem() - unmarshalerType = reflect.TypeOf(new(Unmarshaler)).Elem() + durationType = reflect.TypeOf((*time.Duration)(nil)).Elem() + unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem() ) func isDurationField(t reflect.Type) bool { @@ -323,7 +325,15 @@ func parseValue(v reflect.Value, str string, ctx *context) (err error) { } func parseWithUnmarshaler(v reflect.Value, str string) error { - var u = v.Addr().Interface().(Unmarshaler) + var u Unmarshaler + if v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + u = v.Interface().(Unmarshaler) + } else { + u = v.Addr().Interface().(Unmarshaler) + } return u.Unmarshal(str) } diff --git a/go.mod b/go.mod index 526374d..26119b0 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,12 @@ module github.com/vrischmann/envconfig + +go 1.12 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/stretchr/testify v1.6.1 + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c26bc2b --- /dev/null +++ b/go.sum @@ -0,0 +1,25 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/structunmarshaler_test.go b/structunmarshaler_test.go new file mode 100644 index 0000000..af1e1d7 --- /dev/null +++ b/structunmarshaler_test.go @@ -0,0 +1,48 @@ +package envconfig_test + +import ( + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/vrischmann/envconfig" +) + +type dateUnmarshaller time.Time + +func (d *dateUnmarshaller) Unmarshal(s string) error { + t, err := time.ParseInLocation("2006-01-02", s, time.UTC) + if err != nil { + return err + } + *d = dateUnmarshaller(t) + return nil +} + +func TestParseStructWithUnmarshaler(t *testing.T) { + test := func(conf *struct{ TestEnvDate *dateUnmarshaller }) { + if assert.NoError(t, envconfig.Init(&conf)) { + assert.Equal(t, time.Date(2020, time.June, 20, 0, 0, 0, 0, time.UTC), time.Time(*conf.TestEnvDate)) + } + } + + os.Setenv("TEST_ENV_DATE", "2020-06-20") + + t.Run("val", func(t *testing.T) { + conf := struct { + TestEnvDate dateUnmarshaller + }{} + if assert.NoError(t, envconfig.Init(&conf)) { + assert.Equal(t, time.Date(2020, time.June, 20, 0, 0, 0, 0, time.UTC), time.Time(conf.TestEnvDate)) + } + }) + + t.Run("nil", func(t *testing.T) { + test(&struct{ TestEnvDate *dateUnmarshaller }{}) + }) + + t.Run("nonnil", func(t *testing.T) { + test(&struct{ TestEnvDate *dateUnmarshaller }{TestEnvDate: new(dateUnmarshaller)}) + }) +}