Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track pointer initialization for zero values #73

Merged
merged 1 commit into from Aug 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 4 additions & 2 deletions envconfig.go
Expand Up @@ -304,6 +304,7 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo
}

// Initialize pointer structs.
pointerWasSet := false
for ef.Kind() == reflect.Ptr {
if ef.IsNil() {
if ef.Type().Elem().Kind() != reflect.Struct {
Expand All @@ -316,6 +317,7 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo
// Use an empty struct of the type so we can traverse.
ef = reflect.New(ef.Type().Elem()).Elem()
} else {
pointerWasSet = true
ef = ef.Elem()
}
}
Expand Down Expand Up @@ -370,7 +372,7 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo

// The field already has a non-zero value and overwrite is false, do not
// overwrite.
if !ef.IsZero() && !opts.Overwrite {
if (pointerWasSet || !ef.IsZero()) && !opts.Overwrite {
continue
}

Expand All @@ -382,7 +384,7 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo
// If the field already has a non-zero value and there was no value directly
// specified, do not overwrite the existing field. We only want to overwrite
// when the envvar was provided directly.
if !ef.IsZero() && !found {
if (pointerWasSet || !ef.IsZero()) && !found {
continue
}

Expand Down
166 changes: 159 additions & 7 deletions envconfig_test.go
Expand Up @@ -214,6 +214,18 @@ type Button struct {

type Base64ByteSlice []Base64Bytes

func boolPtr(b bool) *bool {
return &b
}

func stringPtr(s string) *string {
return &s
}

func intPtr(i int) *int {
return &i
}

func TestProcessWith(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -1445,35 +1457,49 @@ func TestProcessWith(t *testing.T) {

// Pointer pointers
{
name: "string_pointer",
name: "pointer_string",
input: &struct {
Field *string `env:"FIELD"`
}{},
exp: &struct {
Field *string `env:"FIELD"`
}{
Field: func() *string { s := "foo"; return &s }(),
Field: stringPtr("foo"),
},
lookuper: MapLookuper(map[string]string{
"FIELD": "foo",
}),
},
{
name: "string_pointer_pointer",
name: "pointer_pointer_string",
input: &struct {
Field **string `env:"FIELD"`
}{},
exp: &struct {
Field **string `env:"FIELD"`
}{
Field: func() **string { s := "foo"; ptr := &s; return &ptr }(),
Field: func() **string { ptr := stringPtr("foo"); return &ptr }(),
},
lookuper: MapLookuper(map[string]string{
"FIELD": "foo",
}),
},
{
name: "map_pointer",
name: "pointer_int",
input: &struct {
Field *int `env:"FIELD"`
}{},
exp: &struct {
Field *int `env:"FIELD"`
}{
Field: intPtr(5),
},
lookuper: MapLookuper(map[string]string{
"FIELD": "5",
}),
},
{
name: "pointer_map",
input: &struct {
Field *map[string]string `env:"FIELD"`
}{},
Expand All @@ -1490,7 +1516,7 @@ func TestProcessWith(t *testing.T) {
}),
},
{
name: "slice_pointer",
name: "pointer_slice",
input: &struct {
Field *[]string `env:"FIELD"`
}{},
Expand All @@ -1507,7 +1533,21 @@ func TestProcessWith(t *testing.T) {
}),
},
{
name: "bool_pointer",
name: "pointer_bool",
input: &struct {
Field *bool `env:"FIELD"`
}{},
exp: &struct {
Field *bool `env:"FIELD"`
}{
Field: boolPtr(true),
},
lookuper: MapLookuper(map[string]string{
"FIELD": "true",
}),
},
{
name: "pointer_bool_noinit",
input: &struct {
Field *bool `env:"FIELD,noinit"`
}{},
Expand All @@ -1518,6 +1558,118 @@ func TestProcessWith(t *testing.T) {
},
lookuper: MapLookuper(nil),
},
{
name: "pointer_bool_default_field_set_env_unset",
input: &struct {
Field *bool `env:"FIELD,default=true"`
}{
Field: boolPtr(false),
},
exp: &struct {
Field *bool `env:"FIELD,default=true"`
}{
Field: boolPtr(false),
},
lookuper: MapLookuper(nil),
},
{
name: "pointer_bool_default_field_set_env_set",
input: &struct {
Field *bool `env:"FIELD,default=true"`
}{
Field: boolPtr(false),
},
exp: &struct {
Field *bool `env:"FIELD,default=true"`
}{
Field: boolPtr(false),
},
lookuper: MapLookuper(map[string]string{
"FIELD": "true",
}),
},
{
name: "pointer_bool_default_field_unset_env_set",
input: &struct {
Field *bool `env:"FIELD,default=true"`
}{},
exp: &struct {
Field *bool `env:"FIELD,default=true"`
}{
Field: boolPtr(false),
},
lookuper: MapLookuper(map[string]string{
"FIELD": "false",
}),
},
{
name: "pointer_bool_default_field_unset_env_unset",
input: &struct {
Field *bool `env:"FIELD,default=true"`
}{},
exp: &struct {
Field *bool `env:"FIELD,default=true"`
}{
Field: boolPtr(true),
},
lookuper: MapLookuper(nil),
},
{
name: "pointer_bool_default_overwrite_field_set_env_unset",
input: &struct {
Field *bool `env:"FIELD,overwrite,default=true"`
}{
Field: boolPtr(false),
},
exp: &struct {
Field *bool `env:"FIELD,overwrite,default=true"`
}{
Field: boolPtr(false),
},
lookuper: MapLookuper(nil),
},
{
name: "pointer_bool_default_overwrite_field_set_env_set",
input: &struct {
Field *bool `env:"FIELD,overwrite,default=true"`
}{
Field: boolPtr(false),
},
exp: &struct {
Field *bool `env:"FIELD,overwrite,default=true"`
}{
Field: boolPtr(true),
},
lookuper: MapLookuper(map[string]string{
"FIELD": "true",
}),
},
{
name: "pointer_bool_default_overwrite_field_unset_env_set",
input: &struct {
Field *bool `env:"FIELD,overwrite,default=true"`
}{},
exp: &struct {
Field *bool `env:"FIELD,overwrite,default=true"`
}{
Field: boolPtr(false),
},
lookuper: MapLookuper(map[string]string{
"FIELD": "false",
}),
},
{
name: "pointer_bool_default_overwrite_field_unset_env_unset",
input: &struct {
Field *bool `env:"FIELD,overwrite,default=true"`
}{},
exp: &struct {
Field *bool `env:"FIELD,overwrite,default=true"`
}{
Field: boolPtr(true),
},
lookuper: MapLookuper(nil),
},

// Marshallers
{
Expand Down