Skip to content

Commit

Permalink
Merge branch 'main' into expand
Browse files Browse the repository at this point in the history
  • Loading branch information
caarlos0 committed Jun 20, 2023
2 parents 2012f6e + 390412e commit 6bac575
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 41 deletions.
88 changes: 51 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
[![Coverage Status](https://img.shields.io/codecov/c/gh/caarlos0/env.svg?logo=codecov&style=for-the-badge)](https://codecov.io/gh/caarlos0/env)
[![](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=for-the-badge)](https://pkg.go.dev/github.com/caarlos0/env/v8)

A simple and zero-dependencies library to parse environment variables into structs.
A simple and zero-dependencies library to parse environment variables into
`struct`s.

## Example

Expand Down Expand Up @@ -61,7 +62,6 @@ $ PRODUCTION=true HOSTS="host1:host2:host3" DURATION=1s go run main.go
- _Unexported fields_ are **ignored**


## Supported types and defaults

Out of the box all built-in types are supported, plus a few others that
Expand Down Expand Up @@ -122,8 +122,10 @@ for more info.

Env supports by default anything that implements the `TextUnmarshaler` interface.
That includes things like `time.Time` for example.
The upside is that depending on the format you need, you don't need to change anything.
The downside is that if you do need time in another format, you'll need to create your own type.
The upside is that depending on the format you need, you don't need to change
anything.
The downside is that if you do need time in another format, you'll need to
create your own type.

Its fairly straightforward:

Expand All @@ -145,15 +147,23 @@ And then you can parse `Config` with `env.Parse`.

## Required fields

The `env` tag option `required` (e.g., `env:"tagKey,required"`) can be added to ensure that some environment variable is set.
In the example above, an error is returned if the `config` struct is changed to:
The `env` tag option `required` (e.g., `env:"tagKey,required"`) can be added to
ensure that some environment variable is set. In the example above, an error is
returned if the `config` struct is changed to:

```go
type config struct {
SecretKey string `env:"SECRET_KEY,required"`
}
```

> **Warning**
>
> Note that being set is not the same as being empty.
> If the variable is set, but empty, the field will have its type's default
> value.
> This also means that custom parser funcs will not be invoked.
## Expand vars

If you set the `expand` option, environment variables (either in `${var}` or
Expand All @@ -168,11 +178,11 @@ type config struct {

This also works with `envDefault`.


## Not Empty fields

While `required` demands the environment variable to be set, it doesn't check its value.
If you want to make sure the environment is set and not empty, you need to use the `notEmpty` tag option instead (`env:"SOME_ENV,notEmpty"`).
While `required` demands the environment variable to be set, it doesn't check
its value. If you want to make sure the environment is set and not empty, you
need to use the `notEmpty` tag option instead (`env:"SOME_ENV,notEmpty"`).

Example:

Expand All @@ -198,9 +208,9 @@ type config struct {
## From file

The `env` tag option `file` (e.g., `env:"tagKey,file"`) can be added
to in order to indicate that the value of the variable shall be loaded from a file. The path of that file is given
by the environment variable associated with it
Example below
in order to indicate that the value of the variable shall be loaded from a
file.
The path of that file is given by the environment variable associated with it:

```go
package main
Expand Down Expand Up @@ -249,7 +259,6 @@ It will use the field name as environment variable name.

Here's an example:


```go
package main

Expand All @@ -268,10 +277,10 @@ type Config struct {

func main() {
cfg := &Config{}
opts := &env.Options{UseFieldNameByDefault: true}
opts := env.Options{UseFieldNameByDefault: true}

// Load env vars.
if err := env.Parse(cfg, opts); err != nil {
if err := env.ParseWithOptions(cfg, opts); err != nil {
log.Fatal(err)
}

Expand All @@ -282,9 +291,11 @@ func main() {

### Environment

By setting the `Options.Environment` map you can tell `Parse` to add those `keys` and `values`
as env vars before parsing is done. These envs are stored in the map and never actually set by `os.Setenv`.
This option effectively makes `env` ignore the OS environment variables: only the ones provided in the option are used.
By setting the `Options.Environment` map you can tell `Parse` to add those
`keys` and `values` as `env` vars before parsing is done.
These `envs` are stored in the map and never actually set by `os.Setenv`.
This option effectively makes `env` ignore the OS environment variables: only
the ones provided in the option are used.

This can make your testing scenarios a bit more clean and easy to handle.

Expand All @@ -304,12 +315,12 @@ type Config struct {

func main() {
cfg := &Config{}
opts := &env.Options{Environment: map[string]string{
opts := env.Options{Environment: map[string]string{
"PASSWORD": "MY_PASSWORD",
}}

// Load env vars.
if err := env.Parse(cfg, opts); err != nil {
if err := env.ParseWithOptions(cfg, opts); err != nil {
log.Fatal(err)
}

Expand All @@ -320,10 +331,11 @@ func main() {

### Changing default tag name

You can change what tag name to use for setting the env vars by setting the `Options.TagName`
variable.
You can change what tag name to use for setting the env vars by setting the
`Options.TagName` variable.

For example

```go
package main

Expand All @@ -340,10 +352,10 @@ type Config struct {

func main() {
cfg := &Config{}
opts := &env.Options{TagName: "json"}
opts := env.Options{TagName: "json"}

// Load env vars.
if err := env.Parse(cfg, opts); err != nil {
if err := env.ParseWithOptions(cfg, opts); err != nil {
log.Fatal(err)
}

Expand Down Expand Up @@ -380,21 +392,19 @@ type ComplexConfig struct {
}

func main() {
cfg := ComplexConfig{}
if err := Parse(&cfg, Options{
cfg := &ComplexConfig{}
opts := env.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 {
if err := env.ParseWithOptions(cfg, opts); err != nil {
log.Fatal(err)
}

Expand All @@ -405,7 +415,8 @@ func main() {

### On set hooks

You might want to listen to value sets and, for example, log something or do some other kind of logic.
You might want to listen to value sets and, for example, log something or do
some other kind of logic.
You can do this by passing a `OnSet` option:

```go
Expand All @@ -425,14 +436,14 @@ type Config struct {

func main() {
cfg := &Config{}
opts := &env.Options{
opts := env.Options{
OnSet: func(tag string, value interface{}, isDefault bool) {
fmt.Printf("Set %s to %v (default? %v)\n", tag, value, isDefault)
},
}

// Load env vars.
if err := env.Parse(cfg, opts); err != nil {
if err := env.ParseWithOptions(cfg, opts); err != nil {
log.Fatal(err)
}

Expand All @@ -443,7 +454,8 @@ func main() {

## Making all fields to required

You can make all fields that don't have a default value be required by setting the `RequiredIfNoDef: true` in the `Options`.
You can make all fields that don't have a default value be required by setting
the `RequiredIfNoDef: true` in the `Options`.

For example

Expand All @@ -464,10 +476,10 @@ type Config struct {

func main() {
cfg := &Config{}
opts := &env.Options{RequiredIfNoDef: true}
opts := env.Options{RequiredIfNoDef: true}

// Load env vars.
if err := env.Parse(cfg, opts); err != nil {
if err := env.ParseWithOptions(cfg, opts); err != nil {
log.Fatal(err)
}

Expand All @@ -478,8 +490,10 @@ 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`.
Default values defined as struct tags will overwrite existing values during 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
package main
Expand Down
4 changes: 3 additions & 1 deletion env.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,9 @@ func get(field reflect.StructField, opts Options) (val string, err error) {
}

if opts.OnSet != nil {
opts.OnSet(key, val, isDefault)
if ownKey != "" {
opts.OnSet(key, val, isDefault)
}
}
return val, err
}
Expand Down
6 changes: 5 additions & 1 deletion env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,8 @@ func TestHook(t *testing.T) {
type config struct {
Something string `env:"SOMETHING" envDefault:"important"`
Another string `env:"ANOTHER"`
Nope string
Inner struct{} `envPrefix:"FOO_"`
}

cfg := &config{}
Expand Down Expand Up @@ -1326,6 +1328,8 @@ func ExampleParse_onSet() {
Home string `env:"HOME,required"`
Port int `env:"PORT" envDefault:"3000"`
IsProduction bool `env:"PRODUCTION"`
NoEnvTag bool
Inner struct{} `envPrefix:"INNER_"`
}
os.Setenv("HOME", "/tmp/fakehome")
var cfg config
Expand All @@ -1340,7 +1344,7 @@ func ExampleParse_onSet() {
// Output: Set HOME to /tmp/fakehome (default? false)
// Set PORT to 3000 (default? true)
// Set PRODUCTION to (default? false)
// {Home:/tmp/fakehome Port:3000 IsProduction:false}
// {Home:/tmp/fakehome Port:3000 IsProduction:false NoEnvTag:false Inner:{}}
}

func ExampleParse_defaults() {
Expand Down
3 changes: 1 addition & 2 deletions env_unix.go → env_tomap.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
//go:build !windows

package env

Expand Down
2 changes: 2 additions & 0 deletions env_unix_test.go → env_tomap_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !windows

package env

import "testing"
Expand Down
2 changes: 2 additions & 0 deletions env_windows.go → env_tomap_windows.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build windows

package env

import "strings"
Expand Down
2 changes: 2 additions & 0 deletions env_windows_test.go → env_tomap_windows_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build windows

package env

import "testing"
Expand Down

0 comments on commit 6bac575

Please sign in to comment.