env

package module
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Dec 23, 2023 License: MPL-2.0 Imports: 9 Imported by: 1

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

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]

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 for each environment variable via 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)
}
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) method:

type config struct{ ... }

func (config) Usage(vars []env.Var, w io.Writer) {
    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.

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("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)
}
Output:

8080
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)

Usage writes a usage message documenting all defined environment variables to the given io.Writer. An optional usage string can be added for each environment variable via the `usage:"STRING"` struct tag. The format of the message can be customized by implementing the Usage([]env.Var, io.Writer) 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)
	}

}
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 // The names of the missing environment variables.
}

NotSetError is returned when environment variables are marked as required but 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.
}

Options are the options for the Load function.

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 an environment variable parsed from the struct field.

Directories

Path Synopsis
internal
assert
Package assert provides common assertions to use with the standard testing package.
Package assert provides common assertions to use with 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