Skip to content

go-simpler/env

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

57 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

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. } }

Contributors