From da4a7a10d4b2bd754d005b6fc8d5382a0e4db68b Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 27 Jul 2022 13:11:10 -0400 Subject: [PATCH] Propagate noinit from parent fields Prior to this commit, only the first-level parent fields were propagated for `noinit`. This fixes the bug to always carry noinit through to all nested fields in a struct. --- envconfig.go | 10 +++++----- envconfig_test.go | 31 ++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/envconfig.go b/envconfig.go index 145d285..e908296 100644 --- a/envconfig.go +++ b/envconfig.go @@ -286,17 +286,18 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo if opts.NoInit && ef.Kind() != reflect.Ptr { return fmt.Errorf("%s: %w", tf.Name, ErrNoInitNotPtr) } + shouldNotInit := opts.NoInit || parentNoInit isNilStructPtr := false setNilStruct := func(v reflect.Value) { origin := e.Field(i) if isNilStructPtr { empty := reflect.New(origin.Type().Elem()).Interface() + // If a struct (after traversal) equals to the empty value, it means // nothing was changed in any sub-fields. With the noinit opt, we skip - // setting the empty value to the original struct pointer (aka. keep it - // nil). - if !reflect.DeepEqual(v.Interface(), empty) || (!opts.NoInit && !parentNoInit) { + // setting the empty value to the original struct pointer (keep it nil). + if !reflect.DeepEqual(v.Interface(), empty) || !shouldNotInit { origin.Set(v) } } @@ -314,7 +315,6 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo isNilStructPtr = true // Use an empty struct of the type so we can traverse. ef = reflect.New(ef.Type().Elem()).Elem() - } else { ef = ef.Elem() } @@ -351,7 +351,7 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo plu = PrefixLookuper(opts.Prefix, l) } - if err := processWith(ctx, ef.Interface(), plu, opts.NoInit, fns...); err != nil { + if err := processWith(ctx, ef.Interface(), plu, shouldNotInit, fns...); err != nil { return fmt.Errorf("%s: %w", tf.Name, err) } diff --git a/envconfig_test.go b/envconfig_test.go index c28ef6e..872c1ce 100644 --- a/envconfig_test.go +++ b/envconfig_test.go @@ -1942,7 +1942,7 @@ func TestProcessWith(t *testing.T) { lookuper: MapLookuper(nil), }, { - name: "noinit/init_when_sub_sub_fields_unset", + name: "noinit/no_init_when_sub_sub_fields_unset", input: &struct { Lepton *Lepton `env:",noinit"` }{}, @@ -1954,6 +1954,35 @@ func TestProcessWith(t *testing.T) { }, lookuper: MapLookuper(nil), }, + { + name: "noinit/no_init_from_parent", + input: &struct { + Electron *Electron `env:"FIELD,noinit"` + }{}, + exp: &struct { + Electron *Electron `env:"FIELD,noinit"` + }{ + Electron: nil, + }, + lookuper: MapLookuper(nil), + }, + { + name: "noinit/no_init_when_decoder", + input: &struct { + Parent *struct { + Field url.URL + } `env:",noinit"` + }{}, + exp: &struct { + Parent *struct { + Field url.URL + } `env:",noinit"` + }{ + // Parent should be nil. + Parent: nil, + }, + lookuper: MapLookuper(nil), + }, { name: "noinit/init_when_sub_fields_set", input: &struct {