Documentation
¶
Overview ¶
Package oneenv reads .env files and decodes them straight into Go structs, with zero external dependencies.
It combines what most projects reach for two libraries to do — a dotenv file parser and an environment-to-struct decoder — behind a single, fast, cached API.
Quick start ¶
type Config struct {
Port int `env:"PORT" default:"8080"`
Host string `env:"HOST,required"`
Timeout time.Duration `env:"TIMEOUT" default:"5s"`
Tags []string `env:"TAGS" separator:","`
DB DBConfig `envPrefix:"DB_"`
}
cfg, err := oneenv.Parse[Config](oneenv.WithFiles(".env", ".env.local"))
Resolution order ¶
Each field is resolved with the priority: process environment, then .env file value, then the `default` tag. WithOverride flips file values above the environment.
Options ¶
Behavior is tuned with functional options: WithFiles, WithPrefix, WithOverride, WithExpand, WithRequired, WithTagKey, WithLookuper, WithTypeParser, WithMutator, WithValidator and WithContext. The zero configuration is valid, and later options win over earlier ones.
Tags ¶
env:"NAME" bind to env key NAME env:"NAME,required" error if unset from every source env:"NAME,notEmpty" error if present but empty env:"NAME,file" read the value from the file at the resolved path env:"NAME,init" allocate a nil pointer/slice/map even when unset env:"NAME,unset" drop the variable from the environment after reading env:"-" skip the field default:"value" fallback when unset separator:";" element delimiter for slices and maps (default ",") envSeparator:";" alias for separator (caarlos0/env compatibility) layout:"2006-01-02" time.Time parse layout (default RFC3339) envPrefix:"DB_" prefix applied to a nested struct's keys desc:"..." description surfaced by Usage
Marshal and Usage ¶
Marshal renders a struct back into .env bytes, and Usage writes a table of the variables a struct consumes for --help output.
Performance ¶
The parser scans source bytes once without regexp, and struct schemas are analyzed once per type and cached, so repeated Load calls avoid reflection overhead. All exported functions are safe for concurrent use.
Index ¶
- Variables
- func Load(v any, opts ...Option) error
- func LoadContext(ctx context.Context, v any, opts ...Option) error
- func LoadEnv(filenames ...string) error
- func Marshal(v any, opts ...Option) ([]byte, error)
- func MarshalMap(v any, opts ...Option) (map[string]string, error)
- func Overload(filenames ...string) error
- func Parse[T any](opts ...Option) (*T, error)
- func ParseContext[T any](ctx context.Context, opts ...Option) (*T, error)
- func Read(filenames ...string) (map[string]string, error)
- func Unmarshal(data []byte, v any, opts ...Option) error
- func Usage[T any](w io.Writer, opts ...Option) error
- type FieldError
- type Lookuper
- type MapLookuper
- type Mutator
- type OSLookuper
- type Option
- func WithContext(ctx context.Context) Option
- func WithExpand() Option
- func WithFiles(names ...string) Option
- func WithLookuper(l Lookuper) Option
- func WithMutator(m Mutator) Option
- func WithOverride() Option
- func WithPrefix(prefix string) Option
- func WithRequired() Option
- func WithTagKey(key string) Option
- func WithTypeParser[T any](fn func(string) (T, error)) Option
- func WithValidator(fn func(v any) error) Option
- type ParseError
- type PrefixLookuper
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotAStruct is returned when the decode target is not a non-nil pointer // to a struct. ErrNotAStruct = errors.New("oneenv: target must be a non-nil pointer to struct") // ErrRequired is returned (wrapped in a FieldError) when a field marked // required has no value from any source. ErrRequired = errors.New("oneenv: required variable is not set") // ErrUnsupportedType is returned (wrapped in a FieldError) when a struct // field has a type that oneenv cannot decode into. ErrUnsupportedType = errors.New("oneenv: unsupported field type") // ErrEmpty is returned (wrapped in a FieldError) when a field marked // ",notEmpty" resolves to an empty value. ErrEmpty = errors.New("oneenv: variable is set but empty") // ErrSecretFile is returned (wrapped in a FieldError) when a field marked // ",file" names a path that cannot be read. ErrSecretFile = errors.New("oneenv: cannot read secret file") )
Sentinel errors returned by the package. Compare against them with errors.Is.
Functions ¶
func Load ¶
Load reads the configured .env files (default ".env"), merges them with the process environment, and decodes the result into the struct pointed to by v.
v must be a non-nil pointer to a struct. Values are resolved per field with the priority: process environment > .env file > `default` tag. Every field error is collected and returned joined via errors.Join.
Load is safe for concurrent use.
func LoadContext ¶
LoadContext behaves like Load but threads ctx through to any mutators registered with WithMutator.
func LoadEnv ¶
LoadEnv parses the given .env files and sets each variable into the process environment via os.Setenv. Existing variables are preserved (call with WithOverride semantics is not available here; use Overload for that).
func Marshal ¶
Marshal renders v as .env-formatted bytes (KEY=value lines, sorted by key). It is the inverse of Unmarshal for the fields oneenv understands. Values that contain whitespace, quotes or newlines are double-quoted and escaped.
func MarshalMap ¶
MarshalMap renders v into a flat key/value map, applying any configured prefix so the keys match what Load would look up.
func Overload ¶
Overload behaves like LoadEnv but overwrites variables that already exist in the process environment.
func Parse ¶
Parse is the generic convenience form of Load: it allocates a T, decodes into it, and returns the pointer.
cfg, err := oneenv.Parse[Config](oneenv.WithPrefix("APP_"))
Example ¶
package main
import (
"fmt"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Name string `env:"NAME"`
}
cfg, err := oneenv.Parse[Config](
oneenv.WithFiles(),
oneenv.WithLookuper(oneenv.MapLookuper{"NAME": "myapp"}),
)
if err != nil {
panic(err)
}
fmt.Println(cfg.Name)
}
Output: myapp
func ParseContext ¶
ParseContext is the generic convenience form of LoadContext.
func Read ¶
Read parses one or more .env files and returns the merged key/value map, without writing anything to the process environment. Later files override earlier keys.
func Unmarshal ¶
Unmarshal decodes .env-formatted bytes directly into v, without touching any file or the process environment. Options such as WithExpand and WithPrefix still apply.
Example ¶
package main
import (
"fmt"
"time"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Host string `env:"HOST" default:"localhost"`
Port int `env:"PORT" default:"8080"`
Timeout time.Duration `env:"TIMEOUT" default:"5s"`
}
src := []byte("HOST=example.com\nPORT=9090")
var cfg Config
if err := oneenv.Unmarshal(src, &cfg); err != nil {
panic(err)
}
fmt.Printf("%s:%d timeout=%s\n", cfg.Host, cfg.Port, cfg.Timeout)
}
Output: example.com:9090 timeout=5s
Types ¶
type FieldError ¶
type FieldError struct {
Field string // Go struct field path, e.g. "DB.Port"
Key string // env key, e.g. "DB_PORT"
Err error // underlying cause
}
FieldError associates a decoding failure with the struct field and env key that produced it. Retrieve it from a Load error with errors.As.
func (*FieldError) Error ¶
func (e *FieldError) Error() string
func (*FieldError) Unwrap ¶
func (e *FieldError) Unwrap() error
type Lookuper ¶
type Lookuper interface {
// Lookup returns the value for key and whether it was present.
Lookup(key string) (value string, ok bool)
}
Lookuper is the source of environment values consulted by the decoder. The decoder never touches os.Getenv directly; everything flows through a Lookuper, which makes tests hermetic and parallel-safe.
type MapLookuper ¶
MapLookuper reads from an in-memory map. Handy in tests.
type Mutator ¶
Mutator transforms a raw value after lookup and before it is decoded into a field. Mutators are applied in registration order; each receives the output of the previous one. A non-nil error aborts that field's decoding.
type OSLookuper ¶
type OSLookuper struct{}
OSLookuper reads from the process environment via os.LookupEnv.
type Option ¶
type Option func(*config)
Option configures a Load, Parse or Read call. Options follow the functional options pattern: each Option is a function that mutates an internal config. The zero config is valid and sensible, options are applied in order, and a later option overrides an earlier one (last-wins).
func WithContext ¶
WithContext sets the context passed to mutators. Defaults to context.Background.
func WithExpand ¶
func WithExpand() Option
WithExpand enables ${VAR} and $VAR expansion inside values.
Example ¶
package main
import (
"fmt"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
URL string `env:"URL"`
}
src := []byte("HOST=db\nURL=postgres://${HOST}:5432")
var cfg Config
if err := oneenv.Unmarshal(src, &cfg, oneenv.WithExpand()); err != nil {
panic(err)
}
fmt.Println(cfg.URL)
}
Output: postgres://db:5432
func WithFiles ¶
WithFiles sets the .env files to read, replacing the default ".env". Files are read in order; later files override earlier keys.
func WithLookuper ¶
WithLookuper replaces the environment source used during decoding. Defaults to OSLookuper. Pass a MapLookuper for hermetic tests.
func WithMutator ¶
WithMutator registers a Mutator applied to every field's raw value before it is decoded. Multiple mutators run in registration order.
func WithOverride ¶
func WithOverride() Option
WithOverride lets values from .env files overwrite variables that already exist in the process environment. By default existing variables win.
func WithPrefix ¶
WithPrefix restricts environment lookups to keys carrying the given prefix. For example WithPrefix("APP_") maps a field tagged env:"PORT" to APP_PORT.
func WithRequired ¶
func WithRequired() Option
WithRequired treats every field as required, as if each carried the ",required" tag option.
func WithTagKey ¶
WithTagKey overrides the struct tag key used for field names (default "env").
func WithTypeParser ¶
WithTypeParser registers a custom parser for a specific type T. Whenever a field of type T is decoded, fn is used instead of the built-in setter. This works for named types, structs, or any type not otherwise supported.
oneenv.WithTypeParser(func(s string) (net.IP, error) {
return net.ParseIP(s), nil
})
Registering any type parser disables the shared schema cache for that call, so prefer registering parsers once and reusing the option set.
func WithValidator ¶
WithValidator registers a function called with the fully decoded target once decoding succeeds. Returning an error fails the Load. This keeps oneenv dependency-free while letting callers plug in any validation library.
type ParseError ¶
type ParseError struct {
File string // file name, empty for in-memory sources
Line int // 1-based line number
Msg string // human readable message
}
ParseError describes a syntax error in a .env source, with position.
func (*ParseError) Error ¶
func (e *ParseError) Error() string
type PrefixLookuper ¶
PrefixLookuper strips a fixed prefix off every key before delegating.