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

enhancement: Expand with default values #285

Merged
merged 5 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions env.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,25 @@

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

// Used internally. maps the env variable key to its resolved string value. (for env var expansion)
rawEnvVars map[string]string
}

func (opts *Options) getRawEnv(s string) string {
val := opts.rawEnvVars[s]
if val == "" {
return opts.Environment[s]
}
return val
}

func defaultOptions() Options {
return Options{
TagName: "env",
Environment: toMap(os.Environ()),
FuncMap: defaultTypeParsers(),
rawEnvVars: make(map[string]string),
}
}

Expand All @@ -143,6 +155,9 @@
if opt.FuncMap == nil {
opt.FuncMap = map[reflect.Type]ParserFunc{}
}
if opt.rawEnvVars == nil {
opt.rawEnvVars = defOpts.rawEnvVars
}
for k, v := range defOpts.FuncMap {
if _, exists := opt.FuncMap[k]; !exists {
opt.FuncMap[k] = v
Expand All @@ -152,6 +167,10 @@
}

func optionsWithEnvPrefix(field reflect.StructField, opts Options) Options {
rawEnvVars := opts.rawEnvVars
if rawEnvVars == nil {
rawEnvVars = make(map[string]string)
caarlos0 marked this conversation as resolved.
Show resolved Hide resolved
}

Check warning on line 173 in env.go

View check run for this annotation

Codecov / codecov/patch

env.go#L172-L173

Added lines #L172 - L173 were not covered by tests
return Options{
Environment: opts.Environment,
TagName: opts.TagName,
Expand All @@ -160,6 +179,7 @@
Prefix: opts.Prefix + field.Tag.Get("envPrefix"),
UseFieldNameByDefault: opts.UseFieldNameByDefault,
FuncMap: opts.FuncMap,
rawEnvVars: rawEnvVars,
}
}

Expand Down Expand Up @@ -350,9 +370,11 @@
val, exists, isDefault = getOr(fieldParams.Key, fieldParams.DefaultValue, fieldParams.HasDefaultValue, opts.Environment)

if fieldParams.Expand {
val = os.ExpandEnv(val)
val = os.Expand(val, opts.getRawEnv)
}

opts.rawEnvVars[fieldParams.OwnKey] = val

if fieldParams.Unset {
defer os.Unsetenv(fieldParams.Key)
}
Expand Down Expand Up @@ -392,7 +414,7 @@
return string(b), err
}

func getOr(key, defaultValue string, defExists bool, envs map[string]string) (string, bool, bool) {
func getOr(key, defaultValue string, defExists bool, envs map[string]string) (val string, exists bool, isDefault bool) {
value, exists := envs[key]
switch {
case (!exists || key == "") && defExists:
Expand Down
32 changes: 32 additions & 0 deletions env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,38 @@ func TestParseExpandOption(t *testing.T) {
isEqual(t, "def1", cfg.Default)
}

func TestParseExpandWithDefaultOption(t *testing.T) {
type config struct {
Host string `env:"HOST" envDefault:"localhost"`
Port int `env:"PORT,expand" envDefault:"3000"`
OtherPort int `env:"OTHER_PORT" envDefault:"4000"`
CompoundDefault string `env:"HOST_PORT,expand" envDefault:"${HOST}:${PORT}"`
SimpleDefault string `env:"DEFAULT,expand" envDefault:"def1"`
MixedDefault string `env:"MIXED_DEFAULT,expand" envDefault:"$USER@${HOST}:${OTHER_PORT}"`
OverrideDefault string `env:"OVERRIDE_DEFAULT,expand" envDefault:"$THIS_SHOULD_NOT_BE_USED"`
NoDefault string `env:"NO_DEFAULT,expand"`
}

t.Setenv("OTHER_PORT", "5000")
t.Setenv("USER", "jhon")
t.Setenv("THIS_IS_USED", "this is used instead")
t.Setenv("OVERRIDE_DEFAULT", "msg: ${THIS_IS_USED}")
t.Setenv("NO_DEFAULT", "$PORT:$OTHER_PORT")

cfg := config{}
err := Parse(&cfg)

isNoErr(t, err)
isEqual(t, "localhost", cfg.Host)
isEqual(t, 3000, cfg.Port)
isEqual(t, 5000, cfg.OtherPort)
isEqual(t, "localhost:3000", cfg.CompoundDefault)
isEqual(t, "def1", cfg.SimpleDefault)
isEqual(t, "jhon@localhost:5000", cfg.MixedDefault)
isEqual(t, "msg: this is used instead", cfg.OverrideDefault)
isEqual(t, "3000:5000", cfg.NoDefault)
}

func TestParseUnsetRequireOptions(t *testing.T) {
type config struct {
Password string `env:"PASSWORD,unset,required"`
Expand Down