env

package module
v0.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 2, 2024 License: MPL-2.0 Imports: 9 Imported by: 2

README ΒΆ

logo

πŸ” Load environment variables into a config struct

awesome-go checks pkg.go.dev goreportcard codecov

πŸ“Œ About

This package is made for apps that store config in environment variables. Its purpose is to replace fragmented os.Getenv calls in main.go with a single struct definition, which simplifies config management and improves code readability.

πŸš€ Features

πŸ“¦ Install

Go 1.20+

go get go-simpler.org/env

πŸ“‹ Usage

Load is the main function of the package. It loads environment variables into the given struct.

The struct fields must have the env:"VAR" struct tag, where VAR is the name of the corresponding environment variable. Unexported fields are ignored.

os.Setenv("PORT", "8080")

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

fmt.Println(cfg.Port) // 8080

Supported types

  • int (any kind)
  • float (any kind)
  • bool
  • string
  • time.Duration
  • encoding.TextUnmarshaler
  • slices of any type above
  • nested structs of any depth

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.

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

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

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

If a nested struct has the optional env:"PREFIX" tag, the environment variables declared by its fields are prefixed with PREFIX.

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.

os.Unsetenv("PORT")

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

fmt.Println(cfg.Port) // 8080

Required

Use the required option to mark an environment variable as required. If it is not set, an error of type NotSetError is returned.

os.Unsetenv("PORT")

var cfg struct {
    Port int `env:"PORT,required"`
}
if err := env.Load(&cfg, nil); err != nil {
    var notSetErr *env.NotSetError
    if errors.As(err, &notSetErr) {
        fmt.Println(notSetErr) // env: PORT is required but not set
    }
}

Expand

Use the expand option to automatically expand the value of an environment variable using os.Expand.

os.Setenv("PORT", "8080")
os.Setenv("ADDR", "localhost:${PORT}")

var cfg struct {
    Addr string `env:"ADDR,expand"`
}
if err := env.Load(&cfg, nil); err != nil {
    fmt.Println(err)
}

fmt.Println(cfg.Addr) // localhost:8080

Slice separator

Space is the default separator used to parse slice values. It can be changed with Options.SliceSep.

os.Setenv("PORTS", "8080,8081,8082")

var cfg struct {
    Ports []int `env:"PORTS"`
}
if err := env.Load(&cfg, &env.Options{SliceSep: ","}); err != nil {
    fmt.Println(err)
}

fmt.Println(cfg.Ports) // [8080 8081 8082]

Name separator

By default, environment variable names are concatenated from nested struct tags as is. If Options.NameSep is not empty, it is used as the separator.

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, &env.Options{NameSep: "_"}); err != nil {
    fmt.Println(err)
}

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

Source

By default, Load retrieves environment variables directly from OS. To use a different source, pass an implementation of the Source interface via Options.Source.

type Source interface {
    LookupEnv(key string) (value string, ok bool)
}

Here's an example of using Map, a Source implementation useful in tests.

m := env.Map{"PORT": "8080"}

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

fmt.Println(cfg.Port) // 8080

Usage message

The Usage function prints a usage message documenting all defined environment variables. An optional usage string can be added to environment variables with the usage:"STRING" struct tag.

os.Unsetenv("DB_HOST")
os.Unsetenv("DB_PORT")

var cfg struct {
    DB struct {
        Host string `env:"DB_HOST,required" usage:"database host"`
        Port int    `env:"DB_PORT,required" usage:"database port"`
    }
    HTTPPort int `env:"HTTP_PORT" default:"8080" usage:"http server port"`
}
if err := env.Load(&cfg, nil); err != nil {
    fmt.Println(err)
    fmt.Println("Usage:")
    env.Usage(&cfg, os.Stdout, nil)
}
Usage:
  DB_HOST    string  required      database host
  DB_PORT    int     required      database port
  HTTP_PORT  int     default 8080  http server port

The format of the message can be customized by implementing the Usage([]env.Var, io.Writer, *env.Options) method.

type Config struct{ ... }

func (Config) Usage(vars []env.Var, w io.Writer, opts *env.Options) {
    for v := range vars {
        // write to w.
    }
}

Documentation ΒΆ

Overview ΒΆ

Package env implements loading environment variables into a config struct.

Index ΒΆ

Examples ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

This section is empty.

Functions ΒΆ

func Load ΒΆ

func Load(cfg any, opts *Options) error

Load loads environment variables into the given struct. cfg must be a non-nil struct pointer, otherwise Load panics. If opts is nil, the default Options are used.

The struct fields must have the `env:"VAR"` struct tag, where VAR is the name of the corresponding environment variable. Unexported fields are ignored.

The following types are supported:

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. If a nested struct has the optional `env:"PREFIX"` tag, the environment variables declared by its fields are prefixed with PREFIX.

Default values can be specified using the `default:"VALUE"` struct tag.

The name of an environment variable can be followed by comma-separated options:

  • required: marks the environment variable as required
  • expand: expands the value of the environment variable using os.Expand
Example ΒΆ
package main

import (
	"fmt"
	"os"

	"go-simpler.org/env"
)

func main() {
	os.Setenv("PORT", "8080")

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

	fmt.Println(cfg.Port)
}
Output:

8080
Example (DefaultValue) ΒΆ
package main

import (
	"fmt"
	"os"

	"go-simpler.org/env"
)

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

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

	fmt.Println(cfg.Port)
}
Output:

8080
Example (Expand) ΒΆ
package main

import (
	"fmt"
	"os"

	"go-simpler.org/env"
)

func main() {
	os.Setenv("PORT", "8080")
	os.Setenv("ADDR", "localhost:${PORT}")

	var cfg struct {
		Addr string `env:"ADDR,expand"`
	}
	if err := env.Load(&cfg, nil); err != nil {
		fmt.Println(err)
	}

	fmt.Println(cfg.Addr)
}
Output:

localhost:8080
Example (NestedStruct) ΒΆ
package main

import (
	"fmt"
	"os"

	"go-simpler.org/env"
)

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

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

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

localhost
5432
Example (NestedStructWithPrefix) ΒΆ
package main

import (
	"fmt"
	"os"

	"go-simpler.org/env"
)

func main() {
	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
Example (NestedStructWithPrefixAndSeparator) ΒΆ
package main

import (
	"fmt"
	"os"

	"go-simpler.org/env"
)

func main() {
	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, &env.Options{NameSep: "_"}); err != nil {
		fmt.Println(err)
	}

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

localhost
5432
Example (Required) ΒΆ
package main

import (
	"errors"
	"fmt"
	"os"

	"go-simpler.org/env"
)

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

	var cfg struct {
		Port int `env:"PORT,required"`
	}
	if err := env.Load(&cfg, nil); err != nil {
		var notSetErr *env.NotSetError
		if errors.As(err, &notSetErr) {
			fmt.Println(notSetErr)
		}
	}

}
Output:

env: PORT is required but not set
Example (SliceSeparator) ΒΆ
package main

import (
	"fmt"
	"os"

	"go-simpler.org/env"
)

func main() {
	os.Setenv("PORTS", "8080,8081,8082")

	var cfg struct {
		Ports []int `env:"PORTS"`
	}
	if err := env.Load(&cfg, &env.Options{SliceSep: ","}); err != nil {
		fmt.Println(err)
	}

	fmt.Println(cfg.Ports)
}
Output:

[8080 8081 8082]
Example (Source) ΒΆ
package main

import (
	"fmt"

	"go-simpler.org/env"
)

func main() {
	m := env.Map{"PORT": "8080"}

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

	fmt.Println(cfg.Port)
}
Output:

8080

func Usage ΒΆ

func Usage(cfg any, w io.Writer, opts *Options)

Usage writes a usage message documenting all defined environment variables to the given io.Writer. The caller must pass the same Options to both Load and Usage, or nil. An optional usage string can be added to environment variables with the `usage:"STRING"` struct tag. The format of the message can be customized by implementing the Usage([]env.Var, io.Writer, *env.Options) method on the cfg's type.

Example ΒΆ
package main

import (
	"fmt"
	"os"

	"go-simpler.org/env"
)

func main() {
	os.Unsetenv("DB_HOST")
	os.Unsetenv("DB_PORT")

	var cfg struct {
		DB struct {
			Host string `env:"DB_HOST,required" usage:"database host"`
			Port int    `env:"DB_PORT,required" usage:"database port"`
		}
		HTTPPort int `env:"HTTP_PORT" default:"8080" usage:"http server port"`
	}
	if err := env.Load(&cfg, nil); err != nil {
		fmt.Println(err)
		fmt.Println("Usage:")
		env.Usage(&cfg, os.Stdout, nil)
	}

}
Output:

env: DB_HOST DB_PORT are required but not set
Usage:
  DB_HOST    string  required      database host
  DB_PORT    int     required      database port
  HTTP_PORT  int     default 8080  http server port

Types ΒΆ

type Map ΒΆ

type Map map[string]string

Map is a Source implementation useful in tests.

func (Map) LookupEnv ΒΆ

func (m Map) LookupEnv(key string) (string, bool)

LookupEnv implements the Source interface.

type NotSetError ΒΆ

type NotSetError struct {
	Names []string
}

NotSetError is returned when required environment variables are not set.

func (*NotSetError) Error ΒΆ

func (e *NotSetError) Error() string

Error implements the error interface.

type Options ΒΆ added in v0.10.0

type Options struct {
	Source   Source // The source of environment variables. The default is [OS].
	SliceSep string // The separator used to parse slice values. The default is space.
	NameSep  string // The separator used to concatenate environment variable names from nested struct tags. The default is an empty string.
}

Options are the options for the Load and Usage functions.

type Source ΒΆ added in v0.10.0

type Source interface {
	// LookupEnv retrieves the value of the environment variable named by the key.
	LookupEnv(key string) (value string, ok bool)
}

Source represents a source of environment variables.

var OS Source = sourceFunc(os.LookupEnv)

OS is the main Source that uses os.LookupEnv.

type Var ΒΆ

type Var struct {
	Name     string       // The name of the variable.
	Type     reflect.Type // The type of the variable.
	Usage    string       // The usage string parsed from the `usage` tag (if exists).
	Default  string       // The default value of the variable. Empty, if the variable is required.
	Required bool         // True, if the variable is marked as required.
	Expand   bool         // True, if the variable is marked to be expanded with [os.Expand].
	// contains filtered or unexported fields
}

Var holds the information about the environment variable parsed from a struct field.

Directories ΒΆ

Path Synopsis
internal
assert
Package assert implements assertions for the standard testing package.
Package assert implements assertions for the standard testing package.
assert/EF
Package EF provides type aliases for the parent [assert] package.
Package EF provides type aliases for the parent [assert] package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL