Skip to content

Commit

Permalink
feat:prefix envs (#201)
Browse files Browse the repository at this point in the history
* feat: allow to prefix envs

* chore: fumpt
  • Loading branch information
caarlos0 committed Nov 9, 2021
1 parent adc0f63 commit 2cf397a
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 3 deletions.
52 changes: 51 additions & 1 deletion README.md
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
16 changes: 14 additions & 2 deletions env.go
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
}
Expand All @@ -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 "":
Expand All @@ -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))
Expand Down
46 changes: 46 additions & 0 deletions env_test.go
Expand Up @@ -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()

Expand Down

0 comments on commit 2cf397a

Please sign in to comment.