Skip to content

Latest commit

 

History

History
182 lines (132 loc) · 5.56 KB

README.md

File metadata and controls

182 lines (132 loc) · 5.56 KB

Minimal Reflection-Based Environment Management for Go

codecov Go Reference

minienv is a minimal libary that makes it easy to work with environment variables in Go. It is heavily inspired by netflix/go-env and Pythons pydantic/BaseSettings and combines reading from .env files and reflection based parsing of environment variables.

Add it with the following command:

go get github.com/yannickalex07/minienv

Getting Started

Using minienv is quite simple, just create a struct and annotate it with env:"" tags:

type Environment struct {
    Port int `env:"PORT"`
}

var e Environment
if err := minienv.Load(&e); err != nil {
    // handle error
}

print(e.Port) // will equal to whatever the PORT env variable is set to

Optional Values

By default every value is required, so if no matching env variables was found or no default is specified, the load will fail with an error. This can be changed by declaring a certain field as optional in the tag:

type Environment struct {
    Port int `env:"PORT,optional"`
}

var e Environment
if err := minienv.Load(&e); err != nil {
    // handle error
}

print(e.Port) // will be the default value of PORT was not set

Default Values

Minienv allows you to specify default values that will be used if no value was found in the environment or specified through a fallback like WithFile() or WithFallbackValues().

type Environment struct {
    Port int `env:"PORT,default=8080"`
}

var e Environment

// `WithFile()` with no arguments will look for a `.env` file in the current directory
err := minienv.Load(&e) 
if err != nil {
    // handle error
}

print(e.Port) // will be 8080 if PORT is not set in the environment

Reading .env-Files

minienv additionally supports loading variables from .env files by using the WithFile(...) option:

type Environment struct {
    Port int `env:"PORT"`
}

var e Environment

// `WithFile()` with no arguments will look for a `.env` file in the current directory
err := minienv.Load(&e, minienv.WithFile(false)) 
if err != nil {
    // handle error
}

Alternatively you can specify one or multiple explicit files:

type Environment struct {
    Port int `env:"PORT"`
}

var e Environment

err := minienv.Load(&e, minienv.WithFile(true, "database.env", "extra.env"))
if err != nil {
    // handle error
}

The first argument controls if the files are required to be there or not. false indicates that the load will just continue if the file / files were not found, a true on the other hand would raise an error if a file was not found of couldn't be parsed.

Precedence Order: Values from .env-files have a lower precedence than environment variables, therefore if a key exists in the environment and in a .env-file, there value in the environment takes precedence. Also, if a key exists in multiple .env-files, the last value takes precedence.

Advanced Usage

The following features are more advanced, however some of them might still be useful.

Additional Fallback Values

Another option that minienv provides is to supply custom fallback values that might be sourced from somewhere completely else:

type Environment struct {
    Port int `env:"PORT"`
}

values := map[string]string{
    "PORT": "12345"
}

var e Environment
err := minienv.Load(&e, minienv.WithFallbackValues(values))
if err != nil {
    // handle error
}

Specifying a Custom Prefix

Another option allows you to set a prefix that will be used during environment lookup:

type Environment struct {
    Port int `env:"PORT"`
}

var e Environment
err := minienv.Load(&e, minienv.WithPrefix("APP_")) // will cause a lookup for APP_PORT
if err != nil {
    // handle error
}

This prefix is also applied to keys from .env-files as well as additional fallback values, however only if the key does not already contain the prefix.

Custom Error Parsing

If Minienv encounters any issues during loading, it will raise an error to the enduser. These errors are wrapped in custom error objects that allow you to react to them more precisely.

If the input to the Load()-function itself is invalid, Minienv will raise the predefined ErrInvalidInput-error:

var e Environment
err := minienv.Load(e) // e is not a pointer here, therefore invalid
if err == minienv.ErrInvalidInput {
    // do something...
}

Additionally if Minienv fails to load a value into a certain field, for example due to a type mismatch, it will raise an error of type LoadError:

var e Environment
err := minienv.Load(&e)
if err != nil {
    loadErr = err.(minienv.LoadError)
    // handle load error
}

The LoadError additionally exposes the affected field that failed together with the underlying error.