Skip to content

Commit

Permalink
feat: support prefixes for nested structs (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
bunyk authored Mar 13, 2024
1 parent f33e5de commit 625998f
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 1 deletion.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,48 @@ fmt.Println(cfg.Port) // 8080
See the `strconv.Parse*` functions for the parsing rules.
User-defined types can be used by implementing the `encoding.TextUnmarshaler` interface.

### Nested structs

Nested struct of any depth level are supported,
allowing grouping of related environment variables.

```go
os.Setenv("HTTP_PORT", "8080")

var cfg struct {
HTTP struct {
Port int `env:"HTTP_PORT"`
}
}
if err := env.Load(&cfg, nil); err != nil {
fmt.Println(err)
}

fmt.Println(cfg.HTTP.Port) // 8080
```

A nested struct can have the optional `env:"PREFIX"` tag.
In this case, the environment variables declared by its fields are prefixed with PREFIX.
This rule is applied recursively to all nested structs.

```go
os.Setenv("DB_HOST", "localhost")
os.Setenv("DB_PORT", "5432")

var cfg struct {
DB struct {
Host string `env:"HOST"`
Port int `env:"PORT"`
} `env:"DB_"`
}
if err := env.Load(&cfg, nil); err != nil {
fmt.Println(err)
}

fmt.Println(cfg.DB.Host) // localhost
fmt.Println(cfg.DB.Port) // 5432
```

### Default values

Default values can be specified using the `default:"VALUE"` struct tag:
Expand Down
17 changes: 16 additions & 1 deletion env.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ func (e *NotSetError) Error() string {
// See the [strconv].Parse* functions for the parsing rules.
// User-defined types can be used by implementing the [encoding.TextUnmarshaler] interface.
//
// Nested struct of any depth level are supported,
// allowing grouping of related environment variables.
// A nested struct can have the optional `env:"PREFIX"` tag.
// In this case, the environment variables declared by its fields are prefixed with PREFIX.
// This rule is applied recursively to all nested structs.
//
// Default values can be specified using the `default:"VALUE"` struct tag.
//
// The name of an environment variable can be followed by comma-separated options:
Expand Down Expand Up @@ -116,7 +122,16 @@ func parseVars(v reflect.Value) []Var {

// special case: a nested struct, parse its fields recursively.
if kindOf(field, reflect.Struct) && !implements(field, unmarshalerIface) {
vars = append(vars, parseVars(field)...)
var prefix string
sf := v.Type().Field(i)
value, ok := sf.Tag.Lookup("env")
if ok {
prefix = value
}
for _, v := range parseVars(field) {
v.Name = prefix + v.Name
vars = append(vars, v)
}
continue
}

Expand Down
21 changes: 21 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,27 @@ func ExampleLoad_nestedStruct() {
// Output: 8080
}

func ExampleLoad_nestedStructPrefixed() {
os.Setenv("DB_HOST", "localhost")
os.Setenv("DB_PORT", "5432")

var cfg struct {
DB struct {
Host string `env:"HOST"`
Port int `env:"PORT"`
} `env:"DB_"`
}
if err := env.Load(&cfg, nil); err != nil {
fmt.Println(err)
}

fmt.Println(cfg.DB.Host)
fmt.Println(cfg.DB.Port)
// Output:
// localhost
// 5432
}

func ExampleLoad_required() {
os.Unsetenv("PORT")

Expand Down

0 comments on commit 625998f

Please sign in to comment.