diff --git a/env.go b/env.go index c928b66d..aa6e8ecf 100644 --- a/env.go +++ b/env.go @@ -156,6 +156,12 @@ type Options struct { // Prefix define a prefix for every key. Prefix string + // AutoPrefix automatically uses the parsed field key for prefix only when no prefix defined + AutoPrefix bool + + // PrefixSeparator automatically uses provided prefix for nested key lookups + PrefixSeparator string + // UseFieldNameByDefault defines whether or not `env` should use the field // name by default if the `env` key is missing. // Note that the field name will be "converted" to conform with environment @@ -249,6 +255,8 @@ func optionsWithSliceEnvPrefix(opts Options, index int) Options { RequiredIfNoDef: opts.RequiredIfNoDef, OnSet: opts.OnSet, Prefix: fmt.Sprintf("%s%d_", opts.Prefix, index), + AutoPrefix: opts.AutoPrefix, + PrefixSeparator: opts.PrefixSeparator, UseFieldNameByDefault: opts.UseFieldNameByDefault, SetDefaultsForZeroValuesOnly: opts.SetDefaultsForZeroValuesOnly, FuncMap: opts.FuncMap, @@ -264,7 +272,9 @@ func optionsWithEnvPrefix(field reflect.StructField, opts Options) Options { DefaultValueTagName: opts.DefaultValueTagName, RequiredIfNoDef: opts.RequiredIfNoDef, OnSet: opts.OnSet, - Prefix: opts.Prefix + field.Tag.Get(opts.PrefixTagName), + Prefix: opts.Prefix + parseOwnPrefix(field, opts), + AutoPrefix: opts.AutoPrefix, + PrefixSeparator: opts.PrefixSeparator, UseFieldNameByDefault: opts.UseFieldNameByDefault, SetDefaultsForZeroValuesOnly: opts.SetDefaultsForZeroValuesOnly, FuncMap: opts.FuncMap, @@ -438,8 +448,12 @@ func isSliceOfStructs(refTypeField reflect.StructField) bool { } func doParseSlice(ref reflect.Value, processField processFieldFn, opts Options) error { - if opts.Prefix != "" && !strings.HasSuffix(opts.Prefix, string(underscore)) { - opts.Prefix += string(underscore) + var separator = string(underscore) + if opts.PrefixSeparator != "" { + separator = opts.PrefixSeparator + } + if opts.Prefix != "" && !strings.HasSuffix(opts.Prefix, separator) { + opts.Prefix += separator } var environments []string @@ -453,7 +467,7 @@ func doParseSlice(ref reflect.Value, processField processFieldFn, opts Options) counter := 0 for finished := false; !finished; { finished = true - prefix := fmt.Sprintf("%s%d%c", opts.Prefix, counter, underscore) + prefix := opts.Prefix + strconv.Itoa(counter) + separator for _, variable := range environments { if strings.HasPrefix(variable, prefix) { counter++ @@ -550,11 +564,28 @@ type FieldParams struct { Ignored bool } -func parseFieldParams(field reflect.StructField, opts Options) (FieldParams, error) { +func parseOwnPrefix(field reflect.StructField, opts Options) string { + var ownPrefix string + ownPrefix = field.Tag.Get(opts.PrefixTagName) + if ownPrefix == "" && opts.AutoPrefix { + ownPrefix, _ = doParseKeyForOption(field, opts) + } + if opts.PrefixSeparator != "" && !strings.HasSuffix(ownPrefix, opts.PrefixSeparator) { + ownPrefix += opts.PrefixSeparator + } + return ownPrefix +} + +func doParseKeyForOption(field reflect.StructField, opts Options) (string, []string) { ownKey, tags := parseKeyForOption(field.Tag.Get(opts.TagName)) if ownKey == "" && opts.UseFieldNameByDefault { ownKey = toEnvName(field.Name) } + return ownKey, tags +} + +func parseFieldParams(field reflect.StructField, opts Options) (FieldParams, error) { + ownKey, tags := doParseKeyForOption(field, opts) defaultValue, hasDefaultValue := field.Tag.Lookup(opts.DefaultValueTagName) diff --git a/env_test.go b/env_test.go index 9a348f83..7e400833 100644 --- a/env_test.go +++ b/env_test.go @@ -2411,3 +2411,30 @@ func TestEnvBleed(t *testing.T) { isEqual(t, "", cfg.Foo) }) } + +func TestAutoPrefix(t *testing.T) { + type Test struct { + Str string `env:"TEST"` + } + type ComplexConfig struct { + Foo *Test `env:"FOO,init"` + Bar Test `envPrefix:"BAR"` + Baz Test `envPrefix:"NOT_FOO_"` + List []Test + Clean *Test + } + + t.Setenv("FOO_TEST", "kek") + t.Setenv("BAR_TEST", "pep") + t.Setenv("NOT_FOO_TEST", "lel") + t.Setenv("LIST_0_TEST", "mem1") + t.Setenv("LIST_1_TEST", "mem2") + + cfg := ComplexConfig{} + isNoErr(t, ParseWithOptions(&cfg, Options{AutoPrefix: true, PrefixSeparator: "_", UseFieldNameByDefault: true})) + isEqual(t, "kek", cfg.Foo.Str) + isEqual(t, "pep", cfg.Bar.Str) + isEqual(t, "lel", cfg.Baz.Str) + isEqual(t, "mem1", cfg.List[0].Str) + isEqual(t, "mem2", cfg.List[1].Str) +}