From 2cf397a58a36021e20b385fa64839ba446fbe4fd Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Tue, 9 Nov 2021 10:19:05 -0300 Subject: [PATCH] feat:prefix envs (#201) * feat: allow to prefix envs * chore: fumpt --- README.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- env.go | 16 ++++++++++++++-- env_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e4ed791..a028100 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,56 @@ func main() { } ``` +### Prefixes + +You can prefix sub-structs env tags, as well as a whole `env.Parse` call. + +Here's an example flexing it a bit: + +```go +package main + +import ( + "fmt" + "log" + + "github.com/caarlos0/env/v6" +) + +type Config struct { + Home string `env:"HOME"` +} + +type ComplexConfig struct { + Foo Config `envPrefix:"FOO_"` + Clean Config + Bar Config `envPrefix:"BAR_"` + Blah string `env:"BLAH"` +} + +func main() { + cfg := ComplexConfig{} + if err := Parse(&cfg, Options{ + Prefix: "T_", + Environment: map[string]string{ + "T_FOO_HOME": "/foo", + "T_BAR_HOME": "/bar", + "T_BLAH": "blahhh", + "T_HOME": "/clean", + }, + }); err != nil { + log.Fatal(err) + } + + // Load env vars. + if err := env.Parse(cfg, opts); err != nil { + log.Fatal(err) + } + + // Print the loaded data. + fmt.Printf("%+v\n", cfg.envData) +} +``` ### On set hooks @@ -372,7 +422,7 @@ func main() { ## Defaults from code -You may define default value also in code, by initialising the config data before it's filled by `env.Parse`. +You may define default value also in code, by initialising the config data before it's filled by `env.Parse`. Default values defined as struct tags will overwrite existing values during Parse. ```go diff --git a/env.go b/env.go index d1ba9ee..53e36c4 100644 --- a/env.go +++ b/env.go @@ -111,6 +111,9 @@ type Options struct { // OnSet allows to run a function when a value is set OnSet OnSetFn + // Prefix define a prefix for each key + Prefix string + // Sets to true if we have already configured once. configured bool } @@ -142,6 +145,9 @@ func configure(opts []Options) []Options { if item.OnSet != nil { opt.OnSet = item.OnSet } + if item.Prefix != "" { + opt.Prefix = item.Prefix + } opt.RequiredIfNoDef = item.RequiredIfNoDef } @@ -218,7 +224,12 @@ func doParse(ref reflect.Value, funcMap map[reflect.Type]ParserFunc, opts []Opti } if value == "" { if reflect.Struct == refField.Kind() { - if err := doParse(refField, funcMap, opts); err != nil { + subOpts := make([]Options, len(opts)) + copy(subOpts, opts) + if prefix := refType.Field(i).Tag.Get("envPrefix"); prefix != "" { + subOpts[0].Prefix += prefix + } + if err := doParse(refField, funcMap, subOpts); err != nil { return err } } @@ -239,7 +250,9 @@ func get(field reflect.StructField, opts []Options) (val string, err error) { var notEmpty bool required := opts[0].RequiredIfNoDef + prefix := opts[0].Prefix key, tags := parseKeyForOption(field.Tag.Get(getTagName(opts))) + key = prefix + key for _, tag := range tags { switch tag { case "": @@ -256,7 +269,6 @@ func get(field reflect.StructField, opts []Options) (val string, err error) { return "", fmt.Errorf("env: tag option %q not supported", tag) } } - expand := strings.EqualFold(field.Tag.Get("envExpand"), "true") defaultValue, defExists := field.Tag.Lookup("envDefault") val, exists, isDefault = getOr(key, defaultValue, defExists, getEnvironment(opts)) diff --git a/env_test.go b/env_test.go index 4dc8fb0..3e04dd3 100644 --- a/env_test.go +++ b/env_test.go @@ -1422,6 +1422,52 @@ func TestRequiredIfNoDefOption(t *testing.T) { }) } +func TestPrefix(t *testing.T) { + is := is.New(t) + type Config struct { + Home string `env:"HOME"` + } + type ComplexConfig struct { + Foo Config `envPrefix:"FOO_"` + Bar Config `envPrefix:"BAR_"` + Clean Config + } + cfg := ComplexConfig{} + err := Parse(&cfg, Options{Environment: map[string]string{"FOO_HOME": "/foo", "BAR_HOME": "/bar", "HOME": "/clean"}}) + is.NoErr(err) + is.Equal("/foo", cfg.Foo.Home) + is.Equal("/bar", cfg.Bar.Home) + is.Equal("/clean", cfg.Clean.Home) +} + +func TestComplePrefix(t *testing.T) { + is := is.New(t) + type Config struct { + Home string `env:"HOME"` + } + type ComplexConfig struct { + Foo Config `envPrefix:"FOO_"` + Clean Config + Bar Config `envPrefix:"BAR_"` + Blah string `env:"BLAH"` + } + cfg := ComplexConfig{} + err := Parse(&cfg, Options{ + Prefix: "T_", + Environment: map[string]string{ + "T_FOO_HOME": "/foo", + "T_BAR_HOME": "/bar", + "T_BLAH": "blahhh", + "T_HOME": "/clean", + }, + }) + is.NoErr(err) + is.Equal("/foo", cfg.Foo.Home) + is.Equal("/bar", cfg.Bar.Home) + is.Equal("/clean", cfg.Clean.Home) + is.Equal("blahhh", cfg.Blah) +} + func isErrorWithMessage(tb testing.TB, err error, msg string) { tb.Helper()