Skip to content

Commit

Permalink
Options to set defaults for zero values only
Browse files Browse the repository at this point in the history
  • Loading branch information
dnovikoff committed Oct 22, 2024
1 parent e55230b commit 1d07485
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 21 deletions.
53 changes: 32 additions & 21 deletions env.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,15 @@ type Options struct {
// variable names conventions.
UseFieldNameByDefault bool

// SetDefaultsForZeroValuesOnly defines whether to set defaults for zero values
// If the `env` variable for the value is not set
// and `envDefault` is set
// and the value is not a zero value for the the type
// and SetDefaultsForZeroValuesOnly=true
// the value from `envDefault` will be ignored
// Usefull for mixing default values from `envDefault` and struct initialization
SetDefaultsForZeroValuesOnly bool

// Custom parse functions for different types.
FuncMap map[reflect.Type]ParserFunc

Expand Down Expand Up @@ -219,31 +228,33 @@ func customOptions(opt Options) Options {

func optionsWithSliceEnvPrefix(opts Options, index int) Options {
return Options{
Environment: opts.Environment,
TagName: opts.TagName,
PrefixTagName: opts.PrefixTagName,
DefaultValueTagName: opts.DefaultValueTagName,
RequiredIfNoDef: opts.RequiredIfNoDef,
OnSet: opts.OnSet,
Prefix: fmt.Sprintf("%s%d_", opts.Prefix, index),
UseFieldNameByDefault: opts.UseFieldNameByDefault,
FuncMap: opts.FuncMap,
rawEnvVars: opts.rawEnvVars,
Environment: opts.Environment,
TagName: opts.TagName,
PrefixTagName: opts.PrefixTagName,
DefaultValueTagName: opts.DefaultValueTagName,
RequiredIfNoDef: opts.RequiredIfNoDef,
OnSet: opts.OnSet,
Prefix: fmt.Sprintf("%s%d_", opts.Prefix, index),
UseFieldNameByDefault: opts.UseFieldNameByDefault,
SetDefaultsForZeroValuesOnly: opts.SetDefaultsForZeroValuesOnly,
FuncMap: opts.FuncMap,
rawEnvVars: opts.rawEnvVars,
}
}

func optionsWithEnvPrefix(field reflect.StructField, opts Options) Options {
return Options{
Environment: opts.Environment,
TagName: opts.TagName,
PrefixTagName: opts.PrefixTagName,
DefaultValueTagName: opts.DefaultValueTagName,
RequiredIfNoDef: opts.RequiredIfNoDef,
OnSet: opts.OnSet,
Prefix: opts.Prefix + field.Tag.Get(opts.PrefixTagName),
UseFieldNameByDefault: opts.UseFieldNameByDefault,
FuncMap: opts.FuncMap,
rawEnvVars: opts.rawEnvVars,
Environment: opts.Environment,
TagName: opts.TagName,
PrefixTagName: opts.PrefixTagName,
DefaultValueTagName: opts.DefaultValueTagName,
RequiredIfNoDef: opts.RequiredIfNoDef,
OnSet: opts.OnSet,
Prefix: opts.Prefix + field.Tag.Get(opts.PrefixTagName),
UseFieldNameByDefault: opts.UseFieldNameByDefault,
SetDefaultsForZeroValuesOnly: opts.SetDefaultsForZeroValuesOnly,
FuncMap: opts.FuncMap,
rawEnvVars: opts.rawEnvVars,
}
}

Expand Down Expand Up @@ -497,7 +508,7 @@ func setField(refField reflect.Value, refTypeField reflect.StructField, opts Opt
return err
}

if value != "" {
if value != "" && (!opts.SetDefaultsForZeroValuesOnly || refField.IsZero()) {
return set(refField, refTypeField, value, opts.FuncMap)
}

Expand Down
57 changes: 57 additions & 0 deletions env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,63 @@ func TestParseWithOptionsRenamedDefault(t *testing.T) {
isEqual(t, "foo", cfg.Str)
}

func TestSetDefaultsForZeroValuesOnly(t *testing.T) {
type config struct {
Str string `env:"STR" envDefault:"foo"`
Int int `env:"INT" envDefault:"42"`
Url url.URL `env:"URL" envDefault:"https://github.com/caarlos0"`
}
defUrl, err := url.Parse("https://github.com/caarlos0")
isNoErr(t, err)

u, err := url.Parse("https://localhost/foo")
isNoErr(t, err)

for _, tc := range []struct {
Name string
Options Options
Expected config
}{
{
Name: "true",
Options: Options{SetDefaultsForZeroValuesOnly: true},
Expected: config{
Str: "isSet",
Int: 1,
Url: *u,
},
},
{
Name: "false",
Options: Options{SetDefaultsForZeroValuesOnly: false},
Expected: config{
Str: "foo",
Int: 42,
Url: *defUrl,
},
},
{
Name: "default",
Options: Options{},
Expected: config{
Str: "foo",
Int: 42,
Url: *defUrl,
},
},
} {
t.Run(tc.Name, func(t *testing.T) {
cfg := &config{
Str: "isSet",
Int: 1,
Url: *u,
}
isNoErr(t, ParseWithOptions(cfg, tc.Options))
isEqual(t, tc.Expected, *cfg)
})
}
}

func TestParseWithOptionsRenamedPrefix(t *testing.T) {
type Config struct {
Str string `env:"STR"`
Expand Down

0 comments on commit 1d07485

Please sign in to comment.