env

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 6, 2026 License: MIT Imports: 7 Imported by: 0

README

go-env-parser

Explicit, batch-validated parsing of environment variables into typed Go values.

go get github.com/cblauvelt/go-env-parser

Why

Most env-parsing libraries either bind struct tags (implicit, hard to grep) or return an error per call (fail on the first problem). This library does neither:

  • Every variable is read with an explicit function call — the mapping between env var and field is visible at the call site.
  • A Parser accumulates errors as it goes. One Err() call at the end reports every missing or malformed variable in a single message.
  • Values are read through a Lookuper interface, so tests inject a MapLookuper directly instead of reaching for os.Setenv / t.Setenv.

Quick start

import env "github.com/cblauvelt/go-env-parser"

p := env.New()
cfg := Config{
    DBHost:  p.RequiredString("DB_HOST"),
    DBPort:  p.Int("DB_PORT", 5432),
    Debug:   p.Bool("DEBUG", false),
    Timeout: p.Duration("TIMEOUT", 30*time.Second),
}
if err := p.Err(); err != nil {
    log.Fatal(err) // all problems reported in one message
}

If DB_HOST is unset and DB_PORT contains "abc" (a typo), the error from p.Err() names both problems:

env "DB_HOST" is required but not set
env "DB_PORT": invalid int value "abc": ...

Required vs defaulted accessors

Every type has two families:

Family Behavior on absent/malformed
String, Int, Bool, … Returns def. Never records an error. Malformed present values fire the bad-default hook.
RequiredString, RequiredInt, RequiredBool, … Records a missing or parse error. Returns the zero value of T.

Supported types

Scalars
Method Type
String / RequiredString string
Int / RequiredInt int
Int32 / RequiredInt32 int32
Int64 / RequiredInt64 int64
Uint / RequiredUint uint
Uint64 / RequiredUint64 uint64
Float64 / RequiredFloat64 float64
Bool / RequiredBool bool (accepts 1/0, true/false, t/f, TRUE/FALSE, True/False)
Duration / RequiredDuration time.Duration (e.g. "300ms", "1.5h")
Time / RequiredTime time.Time (RFC 3339)
TimeLayout / RequiredTimeLayout time.Time (custom layout)
Collections
// []string — comma-separated, each element trimmed, empty elements dropped
origins := p.CSV("CORS_ORIGINS", []string{"*"})

// alternate separator
tags := p.CSVSep("TAGS", ":", nil)

// required: error if the key is absent (an empty value is not an error)
hosts := p.RequiredCSV("ALLOWED_HOSTS")

// []int
ports := p.IntSlice("PORTS", []int{8080})
ports := p.RequiredIntSlice("PORTS")
Validators

OneOf — enum membership:

level := p.OneOf("LOG_LEVEL", "info", "debug", "info", "warn", "error")
level := p.RequiredOneOf("LOG_LEVEL", "debug", "info", "warn", "error")

Validated[T] — generic escape hatch for any parse+validate logic:

parseURL := func(s string) (*url.URL, error) {
    u, err := url.Parse(s)
    if err != nil || u.Scheme == "" {
        return nil, fmt.Errorf("must be an absolute URL")
    }
    return u, nil
}

u := env.RequiredValidated(p, "WEBHOOK_URL", parseURL)
u := env.Validated(p, "WEBHOOK_URL", defaultURL, parseURL)

Options

WithEmptyAsUnset

Treat KEY="" the same as an absent key. Off by default, so explicit-empty values are preserved.

p := env.New(env.WithEmptyAsUnset())
Bad-Default Hook

Called when a defaulted accessor finds a present-but-malformed value. Useful for logging typos that would otherwise be silently replaced by the default.

p := env.New(env.WithBadDefaultHook(func(key, raw string, err error) {
    slog.Warn("env var malformed, using default", "key", key, "raw", raw, "err", err)
}))

Testing

Inject a MapLookuper so tests never touch the real environment:

func TestLoadConfig(t *testing.T) {
    p := env.From(env.MapLookuper{
        "DB_HOST": "localhost",
        "DB_PORT": "5432",
        "DEBUG":   "true",
    })
    cfg, err := LoadConfig(p)
    // ...
}

A function that accepts a *env.Parser can be tested this way without os.Setenv, t.Setenv, or any global state.

Porting from explicit os.Getenv calls

Before:

var missing []string
dbURL := func(key string) string {
    v := os.Getenv(key)
    if v == "" { missing = append(missing, key) }
    return v
}("DB_URL")
port, _ := strconv.Atoi(os.Getenv("DB_PORT"))
if port == 0 { port = 5432 }
if len(missing) > 0 {
    return fmt.Errorf("missing: %s", strings.Join(missing, ", "))
}

After:

p := env.New()
dbURL := p.RequiredString("DB_URL")
port  := p.Int("DB_PORT", 5432)
if err := p.Err(); err != nil {
    return err
}

Documentation

Overview

Package env provides explicit, batch-validated parsing of environment variables into typed Go values.

Unlike struct-tag binding libraries, every variable is read with an explicit function call, keeping the mapping between env vars and fields easy to read and grep. A Parser accumulates errors as it goes so a single Err() call reports every missing or malformed variable in one pass, rather than failing on the first one.

Values are read through a Lookuper, which decouples parsing from the process environment. The default source is the OS environment; tests and layered configuration can inject a MapLookuper instead, avoiding global state such as os.Setenv / t.Setenv.

Index

Constants

View Source
const DefaultSeparator = ","

DefaultSeparator is the delimiter used by CSV and IntSlice when no explicit separator is supplied.

Variables

This section is empty.

Functions

func RequiredValidated

func RequiredValidated[T any](p *Parser, key string, parse func(string) (T, error)) T

RequiredValidated returns the value of key converted by parse. If key is unset or parse returns an error, it records an error and returns the zero value of T.

func Validated

func Validated[T any](p *Parser, key string, def T, parse func(string) (T, error)) T

Validated returns the value of key converted by parse, or def if key is unset or parse returns an error. A parse error on a present value fires the bad-default hook. It never records an error.

parse may both convert and validate: returning a non-nil error rejects the value. This is the generic escape hatch for types and rules the built-in accessors do not cover (e.g. *url.URL, net.IP, a bounded int).

It is a package-level function rather than a method because Go methods cannot declare their own type parameters.

Types

type Lookuper

type Lookuper interface {
	// Lookup returns the value for key and whether key was set. A set key with
	// an empty value returns ("", true).
	Lookup(key string) (value string, ok bool)
}

Lookuper resolves an environment variable key to its raw string value and reports whether the key was present at all. Distinguishing "present but empty" from "absent" is intentional: some callers treat KEY="" as a meaningful explicit value, others as unset (see WithEmptyAsUnset).

type MapLookuper

type MapLookuper map[string]string

MapLookuper reads from an in-memory map. A nil or missing entry is reported as unset. It is intended for tests and for composing layered configuration without touching the real environment.

func (MapLookuper) Lookup

func (m MapLookuper) Lookup(key string) (string, bool)

Lookup implements Lookuper against the backing map.

type OSLookuper

type OSLookuper struct{}

OSLookuper reads from the process environment via os.LookupEnv. It is the default source used by New.

func (OSLookuper) Lookup

func (OSLookuper) Lookup(key string) (string, bool)

Lookup implements Lookuper against the OS environment.

type Option

type Option func(*Parser)

Option configures a Parser.

func WithBadDefaultHook

func WithBadDefaultHook(fn func(key, raw string, err error)) Option

WithBadDefaultHook registers a callback invoked when a defaulted accessor finds a present value that fails to parse. The accessor still returns its default; the hook lets callers log the typo instead of silently swallowing it. fn is called with the key, the raw value, and the parse error.

func WithEmptyAsUnset

func WithEmptyAsUnset() Option

WithEmptyAsUnset makes a set-but-empty variable (KEY="") be treated as if the variable were absent: required accessors report it missing, and defaulted accessors return their default. This restores the semantics of callers that consider an empty value meaningless.

type Parser

type Parser struct {
	// contains filtered or unexported fields
}

Parser reads environment variables through a Lookuper and accumulates any errors encountered. Required accessors that fail append an error; defaulted accessors never append an error but report malformed values through the onBadDefault hook before falling back to the supplied default.

The zero value is not usable; construct a Parser with New or From.

A Parser is not safe for concurrent use. Typical usage builds a config struct from a single goroutine at startup and then checks Err once.

func From

func From(src Lookuper, opts ...Option) *Parser

From returns a Parser that reads from src. A nil src defaults to OSLookuper.

func New

func New(opts ...Option) *Parser

New returns a Parser that reads from the OS environment.

func (*Parser) Bool

func (p *Parser) Bool(key string, def bool) bool

Bool returns the value of key parsed as a bool, or def if key is unset or malformed. A malformed value fires the bad-default hook. Accepted values are those of strconv.ParseBool: 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False.

func (*Parser) CSV

func (p *Parser) CSV(key string, def []string) []string

CSV returns the value of key split on DefaultSeparator, with each element trimmed of surrounding whitespace and empty elements dropped. It returns def if key is unset. If key is set but yields no non-empty elements, an empty (non-nil) slice is returned rather than def — a set value always overrides the default. It never records an error.

func (*Parser) CSVSep

func (p *Parser) CSVSep(key, sep string, def []string) []string

CSVSep behaves like CSV but splits on the supplied separator.

func (*Parser) Duration

func (p *Parser) Duration(key string, def time.Duration) time.Duration

Duration returns the value of key parsed as a time.Duration (e.g. "300ms", "1.5h", "2h45m"), or def if key is unset or malformed. A malformed value fires the bad-default hook.

func (*Parser) Err

func (p *Parser) Err() error

Err returns the accumulated errors joined into a single error, or nil if no required accessor has failed. Call it once after reading all variables.

func (*Parser) Float64

func (p *Parser) Float64(key string, def float64) float64

Float64 returns the value of key parsed as a float64, or def if key is unset or malformed. A malformed value fires the bad-default hook.

func (*Parser) Int

func (p *Parser) Int(key string, def int) int

Int returns the value of key parsed as a base-10 int, or def if key is unset or malformed. A malformed value invokes the bad-default hook (if set) and is otherwise silently replaced by def.

func (*Parser) Int32

func (p *Parser) Int32(key string, def int32) int32

Int32 returns the value of key parsed as a base-10 int32, or def if key is unset or malformed (including overflow). A malformed value fires the bad-default hook.

func (*Parser) Int64

func (p *Parser) Int64(key string, def int64) int64

Int64 returns the value of key parsed as a base-10 int64, or def if key is unset or malformed. A malformed value fires the bad-default hook.

func (*Parser) IntSlice

func (p *Parser) IntSlice(key string, def []int) []int

IntSlice returns the value of key split on DefaultSeparator with each element parsed as a base-10 int. It returns def if key is unset. A malformed element fires the bad-default hook and causes def to be returned. It never records an error.

func (*Parser) OneOf

func (p *Parser) OneOf(key, def string, allowed ...string) string

OneOf returns the value of key if it is one of allowed, otherwise def. It returns def when key is unset. When key is set to a value outside allowed, the bad-default hook fires and def is returned. It never records an error.

def is not required to be a member of allowed; callers are responsible for supplying a sensible default.

func (*Parser) RequiredBool

func (p *Parser) RequiredBool(key string) bool

RequiredBool returns the value of key parsed as a bool. If key is unset or malformed it records an error and returns false.

func (*Parser) RequiredCSV

func (p *Parser) RequiredCSV(key string) []string

RequiredCSV returns the value of key split like CSV. If key is unset it records a missing error and returns nil. A key that is set but yields no non-empty elements is not an error and returns an empty slice.

func (*Parser) RequiredCSVSep

func (p *Parser) RequiredCSVSep(key, sep string) []string

RequiredCSVSep behaves like RequiredCSV but splits on the supplied separator.

func (*Parser) RequiredDuration

func (p *Parser) RequiredDuration(key string) time.Duration

RequiredDuration returns the value of key parsed as a time.Duration. If key is unset or malformed it records an error and returns 0.

func (*Parser) RequiredFloat64

func (p *Parser) RequiredFloat64(key string) float64

RequiredFloat64 returns the value of key parsed as a float64. If key is unset or malformed it records an error and returns 0.

func (*Parser) RequiredInt

func (p *Parser) RequiredInt(key string) int

RequiredInt returns the value of key parsed as a base-10 int. If key is unset or malformed it records an error and returns 0.

func (*Parser) RequiredInt32

func (p *Parser) RequiredInt32(key string) int32

RequiredInt32 returns the value of key parsed as a base-10 int32. If key is unset or malformed (including overflow) it records an error and returns 0.

func (*Parser) RequiredInt64

func (p *Parser) RequiredInt64(key string) int64

RequiredInt64 returns the value of key parsed as a base-10 int64. If key is unset or malformed it records an error and returns 0.

func (*Parser) RequiredIntSlice

func (p *Parser) RequiredIntSlice(key string) []int

RequiredIntSlice returns the value of key split on DefaultSeparator with each element parsed as a base-10 int. If key is unset, or any element fails to parse, it records an error and returns nil.

func (*Parser) RequiredOneOf

func (p *Parser) RequiredOneOf(key string, allowed ...string) string

RequiredOneOf returns the value of key if it is one of allowed. If key is unset it records a missing error; if key is set to a value outside allowed it records a validation error. On failure it returns "".

func (*Parser) RequiredString

func (p *Parser) RequiredString(key string) string

RequiredString returns the value of key. If key is unset it records a missing error and returns "".

func (*Parser) RequiredTime

func (p *Parser) RequiredTime(key string) time.Time

RequiredTime returns the value of key parsed as a time.Time using RFC 3339. If key is unset or malformed it records an error and returns the zero time.

func (*Parser) RequiredTimeLayout

func (p *Parser) RequiredTimeLayout(key, layout string) time.Time

RequiredTimeLayout returns the value of key parsed as a time.Time using the supplied reference layout. If key is unset or malformed it records an error and returns the zero time.

func (*Parser) RequiredUint

func (p *Parser) RequiredUint(key string) uint

RequiredUint returns the value of key parsed as a base-10 unsigned int. If key is unset or malformed it records an error and returns 0.

func (*Parser) RequiredUint64

func (p *Parser) RequiredUint64(key string) uint64

RequiredUint64 returns the value of key parsed as a base-10 uint64. If key is unset or malformed it records an error and returns 0.

func (*Parser) String

func (p *Parser) String(key, def string) string

String returns the value of key, or def if key is unset (or empty when WithEmptyAsUnset is in effect). It never records an error.

func (*Parser) Time

func (p *Parser) Time(key string, def time.Time) time.Time

Time returns the value of key parsed as a time.Time using RFC 3339, or def if key is unset or malformed. A malformed value fires the bad-default hook. Use TimeLayout for a non-RFC3339 layout.

func (*Parser) TimeLayout

func (p *Parser) TimeLayout(key, layout string, def time.Time) time.Time

TimeLayout returns the value of key parsed as a time.Time using the supplied reference layout (see the time package), or def if key is unset or malformed.

func (*Parser) Uint

func (p *Parser) Uint(key string, def uint) uint

Uint returns the value of key parsed as a base-10 unsigned int, or def if key is unset or malformed (including negative or overflow). A malformed value fires the bad-default hook.

func (*Parser) Uint64

func (p *Parser) Uint64(key string, def uint64) uint64

Uint64 returns the value of key parsed as a base-10 uint64, or def if key is unset or malformed. A malformed value fires the bad-default hook.

Jump to

Keyboard shortcuts

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