env

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Aug 2, 2026 License: MIT Imports: 16 Imported by: 0

README

env

CI OpenSSF Scorecard Go Reference Go Report Card

Blazing-fast, zero-allocation environment configuration for Go.
Blazing-fast load. Contract-grade audit.

github.com/gopherust-io/env parses environment variables into typed structs using compile-time code generation. No reflection at runtime. No external dependencies. One os.Environ() pass, then direct field assignment. Config Doctor turns the same struct into an enforceable env contract—typos, orphans, silent defaults, unmarked secrets.

Quick links: Architecture · Getting started · Examples · Changelog · Scorecard

  caarlos0/env   12,622 ns/op   244 allocs
  goenv           6,023 ns/op     8 allocs
  viper           3,596 ns/op    70 allocs
  envconfig       3,068 ns/op    81 allocs
  cleanenv        2,737 ns/op    57 allocs
  stdlib            140 ns/op     0 allocs
  env                74 ns/op     0 allocs

Benchmark note: Small (10-field) medians from the project bench suite on identical fixtures; directional. Full matrix → Performance. Re-run: make bench-remote VERSION=v0.6.0.

New here?docs/GETTING_STARTED.md (copy-paste guide, CI, troubleshooting)


Start in 3 steps

1. Install

go get github.com/gopherust-io/env@latest
go install github.com/gopherust-io/env/cmd/envgen@latest

2. Struct + generate

package config

//go:generate envgen -type Config -output config_env_gen.go

type Config struct {
    Port  int    `env:"PORT" default:"8080"`
    Debug bool   `env:"DEBUG"`
    Host  string `env:"HOST" default:"localhost"`
}
go generate ./...
# or: envgen -type Config
# list structs: envgen -list

3. Load

cfg, err := config.LoadConfig()
if err != nil {
    log.Fatal(err)
}
log.Printf("%+v", cfg.Masked()) // safe if you use sensitive:"true"

Optional local .env: _ = env.LoadDotEnv(".env") before LoadConfig().

Full-featured example: examples/basic. Minimal: examples/minimal.

When not to use env

  • You need dynamic/untyped runtime schemas from arbitrary keys.
  • Your config changes shape frequently and code generation is not acceptable.
  • You prefer convenience over strict, explicit typed parsing and compile-time setup.

For those cases, reflection-based config loaders can be a better fit.


Cheatsheet

I want to… Do this
List struct names envgen -list
Regenerate loaders go generate ./...
Load config LoadConfig()
Audit env contract envgen doctor -type Config / AuditConfig
Reload after env change ReloadConfig(&cfg)
Log without secrets cfg.Masked()
Skip codegen (dev only) reflectenv.Parse(&cfg)
Nested fields DB Database `prefix:"DB_"`
${VAR} in values `expand:"true"` on field

How it works

flowchart LR
    subgraph compile [Compile time]
        Struct[Config struct]
        Envgen[envgen]
        Gen[config_env_gen.go]
        Struct --> Envgen --> Gen
    end
    subgraph runtime [Runtime]
        Snap[EnvSnapshot]
        Load[LoadConfig]
        Snap --> Load
    end
    Gen --> Load
  1. Define a struct with env tags.
  2. go generate runs envgenLoadConfig, ReloadConfig, Masked(), AuditConfig.
  3. LoadConfig() indexes the environment once and assigns fields with zero reflection.

Config Doctor

The Config struct is the contract. envgen doctor (and generated AuditConfig) checks a snapshot against that schema without touching the zero-alloc load path.

envgen doctor -type Config -env-file .env -mode prod
error: DB_HST is not a known key; did you mean DB_HOST?
warn:  Host (HOST): unset; using default "localhost"
error: DB.Host (DB_HOST): required but unset
Finding Meaning
Typo Unknown key within edit distance of a schema key
Orphan Unknown key under a known nested prefix
Silent default Unset field falling back to default (error in -mode prod)
Missing required Required field unset
Unmarked secret Value looks like a token but field lacks sensitive:"true"
rep := config.AuditConfigWithOptions(snap, env.AuditOptions{Mode: env.AuditModeProd})
if err := rep.Err(); err != nil {
    log.Fatal(err)
}

Try the planted typos in examples/basic/.env.doctor:

go run ./cmd/envgen doctor -dir ./examples/basic -type Config \
  -env-file ./examples/basic/.env.doctor -mode prod -prefix DB_

Flags: -mode dev|prod, -format text|json, -strict-unknown, -all-unknown, -prefix, -env-file.


Struct tags

Tag Description
env:"KEY" Environment variable name
default:"..." Value when unset
required:"true" Error if unset and no default
prefix:"FOO_" Prefix for nested struct fields
sep:"," Slice separator (default ,)
kvsep:":" Map key/value separator (default :)
layout:"..." time.Time parse layout (default RFC3339)
expand:"true" Expand ${VAR} and $VAR in values
sensitive:"true" Redact in Masked()
env:"-" Skip field

Nested prefixes compose: prefix:"DB_" + env:"HOST"DB_HOST.


Generated API

Function Description
LoadConfig() Parse env into Config
ReloadConfig(cfg *Config) Re-parse env in-place
LoadConfigFrom(snap) Parse from a custom snapshot
MustLoadConfig() Panics on error
(Config) Masked() Copy with sensitive fields redacted
AuditConfig(snap) Contract audit (typos, orphans, …)
AuditConfigFromEnviron() Audit against process env
AuditConfigWithOptions(snap, opts) Audit with mode / strict flags

Errors are collected in one pass:

env: DB.Host (DB_HOST): required; Port (PORT): parse: strconv.Atoi: parsing "abc": invalid syntax

Local development (.env)

_ = env.LoadDotEnv(".env")
cfg, err := config.LoadConfig()

LoadDotEnv fills unset variables from a file and refreshes the snapshot. Existing process variables are preserved.

Read-only merge without touching os.Environ():

snap, err := env.SnapshotWithDotEnv(".env")
cfg, err := config.LoadConfigFrom(snap)

Variable expansion

BaseURL string `env:"BASE_URL" default:"${NATS_URL}/api" expand:"true"`

Supports ${VAR} and $VAR syntax.


Hot reload

cfg, _ := config.LoadConfig()
os.Setenv("PORT", "9090")
_ = config.ReloadConfig(&cfg)

Cross-package nested structs

import "myapp/internal/db"

type Config struct {
    DB db.Database `prefix:"DB_"`
}

Reflection fallback (opt-in)

import "github.com/gopherust-io/env/reflectenv"

var cfg Config
reflectenv.Parse(&cfg)

Slower than codegen — use envgen in production.


Custom types

type Mode string

func (m *Mode) UnmarshalEnv(key, value string) error {
    switch value {
    case "dev", "staging", "prod":
        *m = Mode(value)
        return nil
    default:
        return fmt.Errorf("unknown mode %q", value)
    }
}

Migration from caarlos0/env

caarlos0/env env
env.Parse(&cfg) LoadConfig()
envDefault:"8080" default:"8080"
envPrefix:"DB_" prefix:"DB_"
env:"HOST,required" env:"HOST" required:"true"

Performance

Codegen load is faster and zero-alloc vs reflection loaders on identical fixtures.

Library Approach Benchmark
env (this) Codegen, no reflection *Envgen
stdlib Hand-written LookupEnv + strconv *Stdlib
cleanenv Reflection *Cleanenv
envconfig Reflection *Envconfig
viper AutomaticEnv + mapstructure *Viper
goenv Low-allocation reflection *Goenv
caarlos0/env Reflection *Carl
Small config (10 fields)
Library ns/op allocs/op vs env
env 74 0
stdlib 140 0 ~2×
cleanenv 2,737 57 ~37×
envconfig 3,068 81 ~42×
viper 3,596 70 ~49×
goenv 6,023 8 ~82×
caarlos0/env 12,622 244 ~172×
Scaling (env)
Fixture env allocs
Small (10) 74 ns 0
Medium (50) 402 ns 0
Large (100) 977 ns 0

Platform: darwin/arm64 (Apple M4 Pro). Medians of -count=10 from bench/. Directional; re-run on your hardware:

make bench-remote VERSION=v0.6.0

Details and Medium/Large matrices: bench/README.md. Sample output: bench/results.sample.txt.


Runtime API

snap := env.Snapshot()
snap.Lookup("PORT")
env.ParseInt("8080")
env.LoadDotEnv(".env")
env.Reload()

Compatibility and stability

  • Supported Go version: follow go.mod in this repository.
  • Public generated API (LoadConfig, ReloadConfig, Masked) is stable across patch releases.
  • Breaking changes are called out in CHANGELOG.md.

Changelog

See CHANGELOG.md.

Contributing · Security

License

MIT — see LICENSE.

Documentation

Overview

Package env is a codegen-first environment variable parser for Go. Use cmd/envgen to generate type-specific loaders with zero runtime reflection.

Index

Constants

View Source
const MaxDotEnvBytes = 1 << 20 // 1 MiB

MaxDotEnvBytes is the maximum .env file size accepted by ParseDotEnvFile.

View Source
const SensitiveMask = "***"

SensitiveMask replaces sensitive fields in generated Masked() output.

Variables

This section is empty.

Functions

func AppendParse

func AppendParse(errs *[]FieldError, field, key, value string, parseErr error)

AppendParse records a parse failure. Prefer AppendParseSensitive for sensitive fields.

func AppendParseSensitive added in v0.6.0

func AppendParseSensitive(errs *[]FieldError, field, key, value string, parseErr error)

AppendParseSensitive records a parse failure without retaining or printing the raw value.

func AppendRequired

func AppendRequired(errs *[]FieldError, field, key string)

func BytesToString added in v0.5.0

func BytesToString(b []byte) string

BytesToString returns a string view of b without copying.

func ClosestKey added in v0.6.0

func ClosestKey(key string, keys []string) (string, int)

ClosestKey returns the schema key with minimal Levenshtein distance to key. Candidates whose length differs by more than the typo budget are skipped. Distance is -1 when no candidate remains.

func Expand added in v0.2.0

func Expand(s string, snap *EnvSnapshot) string

Expand replaces ${VAR} and $VAR references using values from snap.

func IsEmpty added in v0.5.0

func IsEmpty(s string) bool

IsEmpty reports whether s is empty.

func LoadDotEnv added in v0.2.0

func LoadDotEnv(path string) error

LoadDotEnv loads variables from path into the process environment. Existing variables are not overwritten. The cached snapshot is refreshed. path is treated as a trusted filesystem path.

func LooksLikeSecret added in v0.6.0

func LooksLikeSecret(value string) bool

LooksLikeSecret reports whether value resembles an API token or secret.

func NewError

func NewError(fields []FieldError) error

NewError returns nil when fields is empty.

func ParseBool

func ParseBool(s string) (bool, error)

func ParseDotEnv added in v0.2.0

func ParseDotEnv(data []byte) (map[string]string, error)

ParseDotEnv parses dotenv content. Supports # comments, export prefix, and quoted values.

func ParseDotEnvFile added in v0.2.0

func ParseDotEnvFile(path string) (map[string]string, error)

ParseDotEnvFile reads KEY=VALUE pairs from a dotenv file.

func ParseDuration

func ParseDuration(s string) (time.Duration, error)

func ParseFloat32

func ParseFloat32(s string) (float32, error)

func ParseFloat64

func ParseFloat64(s string) (float64, error)

func ParseInt

func ParseInt(s string) (int, error)

func ParseInt8

func ParseInt8(s string) (int8, error)

func ParseInt16

func ParseInt16(s string) (int16, error)

func ParseInt32

func ParseInt32(s string) (int32, error)

func ParseInt64

func ParseInt64(s string) (int64, error)

func ParseIntSlice

func ParseIntSlice(s, sep string) ([]int, error)

func ParseString

func ParseString(s string) (string, error)

func ParseStringMap

func ParseStringMap(s, sep, kvSep string) (map[string]string, error)

func ParseStringSlice

func ParseStringSlice(s, sep string) ([]string, error)

func ParseTime added in v0.2.0

func ParseTime(s, layout string) (time.Time, error)

func ParseUint

func ParseUint(s string) (uint, error)

func ParseUint8

func ParseUint8(s string) (uint8, error)

func ParseUint16

func ParseUint16(s string) (uint16, error)

func ParseUint32

func ParseUint32(s string) (uint32, error)

func ParseUint64

func ParseUint64(s string) (uint64, error)

func Reload added in v0.4.0

func Reload()

Reload refreshes the cached snapshot from os.Environ(). Generated ReloadConfig calls this before re-parsing.

func ResetSnapshot

func ResetSnapshot()

ResetSnapshot rebuilds the cached snapshot from the current process environment. It always stores a fresh snapshot and does not share work with Snapshot's first-build flight, so a concurrent first Snapshot cannot overwrite a newer reload.

func StringToBytes added in v0.5.0

func StringToBytes(s string) []byte

StringToBytes returns a read-only view of s as a []byte without copying.

Types

type AuditMode added in v0.6.0

type AuditMode int

AuditMode controls which warnings become errors.

const (
	// AuditModeDev keeps silent defaults and unmarked secrets as warnings.
	AuditModeDev AuditMode = iota
	// AuditModeProd elevates silent defaults and unmarked secrets to errors.
	AuditModeProd
)

type AuditOptions added in v0.6.0

type AuditOptions struct {
	Mode          AuditMode
	StrictUnknown bool // orphans are errors (default: warnings)
	AllUnknown    bool // report unknown keys that are neither orphans nor typos
	// contains filtered or unexported fields
}

AuditOptions tunes contract checks.

type AuditReport added in v0.6.0

type AuditReport struct {
	Findings []Finding
}

AuditReport collects findings from one audit pass.

func Audit added in v0.6.0

func Audit(snap *EnvSnapshot, schema []SchemaField, opts AuditOptions) AuditReport

Audit checks snap against schema: typos, orphans, silent defaults, required gaps, unmarked secrets.

func (AuditReport) Err added in v0.6.0

func (r AuditReport) Err() error

Err returns a non-nil error when any finding has SeverityError.

func (AuditReport) HasErrors added in v0.6.0

func (r AuditReport) HasErrors() bool

HasErrors reports whether any finding is SeverityError.

type EnvSnapshot

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

EnvSnapshot holds an indexed view of environment variables.

func FromEnviron

func FromEnviron(environ []string) *EnvSnapshot

func FromMap

func FromMap(vars map[string]string) *EnvSnapshot

FromMap copies vars into a new snapshot. Callers may mutate the input map afterward.

func Snapshot

func Snapshot() *EnvSnapshot

Snapshot returns a cached process environment index. The index is built once from os.Environ() until ResetSnapshot is called. Concurrent first callers share a single FromEnviron via singleflight.

func SnapshotWithDotEnv added in v0.2.0

func SnapshotWithDotEnv(paths ...string) (*EnvSnapshot, error)

SnapshotWithDotEnv builds a snapshot from dotenv files overlaid with os.Environ(). Process environment values take precedence over file values.

func (*EnvSnapshot) Len

func (s *EnvSnapshot) Len() int

func (*EnvSnapshot) Lookup

func (s *EnvSnapshot) Lookup(key string) (string, bool)

func (*EnvSnapshot) Range added in v0.6.0

func (s *EnvSnapshot) Range(fn func(key, value string) bool)

Range calls fn for each key/value. If fn returns false, iteration stops.

type Error

type Error struct {
	Fields []FieldError
	// contains filtered or unexported fields
}

Error collects every field error from one parse pass.

func (*Error) Error

func (e *Error) Error() string

type FieldError

type FieldError struct {
	Err       error
	Field     string
	EnvKey    string
	Op        string
	Value     string
	Sensitive bool
	// contains filtered or unexported fields
}

FieldError is a single field-level configuration error.

func (FieldError) Error

func (e FieldError) Error() string

type Finding added in v0.6.0

type Finding struct {
	Key      string
	Field    string
	Message  string
	Suggest  string
	Kind     FindingKind
	Severity FindingSeverity
}

Finding is one contract violation or hygiene warning.

type FindingKind added in v0.6.0

type FindingKind int

FindingKind classifies an audit finding.

const (
	FindingTypo FindingKind = iota
	FindingOrphan
	FindingSilentDefault
	FindingMissingRequired
	FindingUnmarkedSecret
)

func (FindingKind) String added in v0.6.0

func (k FindingKind) String() string

type FindingSeverity added in v0.6.0

type FindingSeverity int

FindingSeverity is error or warning.

const (
	SeverityWarning FindingSeverity = iota
	SeverityError
)

func (FindingSeverity) String added in v0.6.0

func (s FindingSeverity) String() string

type SchemaField added in v0.6.0

type SchemaField struct {
	Key        string
	FieldPath  string
	Default    string
	Prefix     string
	Required   bool
	HasDefault bool
	Sensitive  bool
	// contains filtered or unexported fields
}

SchemaField describes one env key known to a generated (or CLI) schema.

type Unmarshaler

type Unmarshaler interface {
	UnmarshalEnv(key, value string) error
}

Unmarshaler parses a custom type from a raw environment value.

Directories

Path Synopsis
cmd
envgen command
examples
basic/cmd command
internal
tag

Jump to

Keyboard shortcuts

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