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, WithEnvFiles, WithEnvVar, WithPrefix, WithOverride, WithExpand, WithRequired, WithTagKey, WithLookuper, WithTypeParser, WithMutator, WithValidator and WithContext. The zero configuration is valid, and later options win over earlier ones.
Environment-aware file cascade ¶
WithEnvFiles layers files by the active environment, like Rails and Next.js: on top of each base file it also reads <base>.local, <base>.<env> and <base>.<env>.local (all optional, later files winning). The environment name comes from APP_ENV then GO_ENV, configurable with WithEnvVar.
Secrets ¶
The ",secret" tag option (or env-secret:"true") marks a field sensitive: Redacted and RedactedMap render its value as a mask while Marshal keeps the real value. Alternatively wrap a field in Secret[T], whose String, GoString and JSON forms are always masked, so it never leaks through logging.
Slices of structs ¶
A []Struct field is decoded from indexed keys. A field tagged env:"SERVER" reads SERVER_0_HOST, SERVER_1_HOST, ... one element per index, stopping at the first index with no keys present.
Hot reload ¶
The subpackage oneenv/watch re-decodes a struct whenever a watched .env file changes, using native OS notifications — inotify (Linux), kqueue (BSD/macOS) and ReadDirectoryChangesW (Windows), with modification-time polling as a fallback on other platforms — still with zero external dependencies.
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:"NAME,secret" mask the value in Redacted output env:"-" skip the field default:"value" fallback when unset separator:";" element delimiter for slices and maps (default ",") envSeparator:";" alias for separator 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
Every configuration tag also has an env-* spelling (env-default, env-separator, env-description, env-layout, env-prefix, env-required, env-notempty, env-file, env-init, env-unset and env-secret). When both spellings are present the env-* form takes priority and the native tag is the fallback.
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.
Example ¶
Example shows the common case: parse configuration straight into a struct.
package main
import (
"fmt"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Port int `env:"PORT" default:"8080"`
Host string `env:"HOST,required"`
Tags []string `env:"TAGS" separator:","`
}
cfg, err := oneenv.Parse[Config](
oneenv.WithLookuper(oneenv.MapLookuper{"HOST": "localhost", "TAGS": "a,b,c"}),
)
if err != nil {
panic(err)
}
fmt.Printf("%s:%d %v\n", cfg.Host, cfg.Port, cfg.Tags)
}
Output: localhost:8080 [a b c]
Index ¶
- Variables
- func Example[T any](w io.Writer, opts ...Option) error
- func FilesFor(opts ...Option) []string
- 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 Redacted(v any, opts ...Option) ([]byte, error)
- func RedactedMap(v any, opts ...Option) (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 WithEnvFiles() Option
- func WithEnvVar(names ...string) 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
- type Secret
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 Example ¶ added in v1.1.0
Example writes a ready-to-fill .env.example file for the environment variables that a struct of type T consumes. Each key is preceded by a comment carrying its description, Go type, and whether it is required; the value is the default when one is declared, and empty otherwise. Secret fields never leak their default value.
oneenv.Example[Config](os.Stdout)
func FilesFor ¶ added in v1.1.0
FilesFor returns the ordered list of .env file paths that Load would read for the given options, including the full environment-aware cascade when WithEnvFiles is set. It lets tooling (such as oneenv/watch) discover which files to observe without reimplementing the resolution rules.
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.
Example ¶
ExampleMarshal renders a struct back into .env bytes.
package main
import (
"fmt"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Host string `env:"HOST"`
Port int `env:"PORT"`
}
data, err := oneenv.Marshal(Config{Host: "localhost", Port: 5432})
if err != nil {
panic(err)
}
fmt.Print(string(data))
}
Output: HOST=localhost PORT=5432
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
Example (Defaults) ¶
ExampleParse_defaults shows how the `default` tag fills a field when no source provides a value. Anything present in the environment or file wins over the default.
package main
import (
"fmt"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Host string `env:"HOST" default:"localhost"`
Port int `env:"PORT" default:"8080"`
}
cfg, err := oneenv.Parse[Config](
oneenv.WithLookuper(oneenv.MapLookuper{"PORT": "9090"}), // HOST falls back
)
if err != nil {
panic(err)
}
fmt.Printf("%s:%d\n", cfg.Host, cfg.Port)
}
Output: localhost:9090
Example (EnvTags) ¶
ExampleParse_envTags shows the env-* tag aliases, which work alongside the native tags and take priority when both are present.
package main
import (
"fmt"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Port int `env:"PORT" env-default:"8080"`
Tags []string `env:"TAGS" env-separator:";"`
}
cfg, err := oneenv.Parse[Config](
oneenv.WithLookuper(oneenv.MapLookuper{"TAGS": "a;b;c"}),
)
if err != nil {
panic(err)
}
fmt.Printf("%d %v\n", cfg.Port, cfg.Tags)
}
Output: 8080 [a b c]
Example (ErrorHandling) ¶
ExampleParse_errorHandling shows how a single Parse reports every problem, each retrievable as a typed *FieldError.
package main
import (
"errors"
"fmt"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Host string `env:"HOST,required"`
}
_, err := oneenv.Parse[Config](oneenv.WithLookuper(oneenv.MapLookuper{}))
var fe *oneenv.FieldError
if errors.As(err, &fe) {
fmt.Printf("%s is required\n", fe.Key)
}
}
Output: HOST is required
Example (Nested) ¶
ExampleParse_nested decodes a nested struct. The envPrefix tag prefixes every key inside it, so DB.Host reads from DB_HOST.
package main
import (
"fmt"
"github.com/bakhod1r/oneenv"
)
func main() {
type DB struct {
Host string `env:"HOST"`
Port int `env:"PORT"`
}
type Config struct {
Name string `env:"NAME"`
DB DB `envPrefix:"DB_"`
}
cfg, err := oneenv.Parse[Config](oneenv.WithLookuper(oneenv.MapLookuper{
"NAME": "api",
"DB_HOST": "db.internal",
"DB_PORT": "5432",
}))
if err != nil {
panic(err)
}
fmt.Printf("%s -> %s:%d\n", cfg.Name, cfg.DB.Host, cfg.DB.Port)
}
Output: api -> db.internal:5432
Example (SliceOfStructs) ¶
ExampleParse_sliceOfStructs decodes a repeated struct from indexed keys.
package main
import (
"fmt"
"github.com/bakhod1r/oneenv"
)
func main() {
type Server struct {
Host string `env:"HOST"`
Port int `env:"PORT"`
}
type Config struct {
Servers []Server `env:"SERVER"`
}
cfg, err := oneenv.Parse[Config](oneenv.WithLookuper(oneenv.MapLookuper{
"SERVER_0_HOST": "a", "SERVER_0_PORT": "1",
"SERVER_1_HOST": "b", "SERVER_1_PORT": "2",
}))
if err != nil {
panic(err)
}
fmt.Printf("%s:%d %s:%d\n", cfg.Servers[0].Host, cfg.Servers[0].Port, cfg.Servers[1].Host, cfg.Servers[1].Port)
}
Output: a:1 b:2
Example (Time) ¶
ExampleParse_time parses a time.Time with a custom layout.
package main
import (
"fmt"
"time"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Release time.Time `env:"RELEASE" layout:"2006-01-02"`
}
cfg, err := oneenv.Parse[Config](
oneenv.WithLookuper(oneenv.MapLookuper{"RELEASE": "2026-07-18"}),
)
if err != nil {
panic(err)
}
fmt.Println(cfg.Release.Year())
}
Output: 2026
Example (Types) ¶
ExampleParse_types shows the built-in support for durations, booleans, slices and maps. Slices split on the separator; maps take key:value pairs.
package main
import (
"fmt"
"time"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Debug bool `env:"DEBUG"`
Timeout time.Duration `env:"TIMEOUT"`
Tags []string `env:"TAGS" separator:","`
Limits map[string]int `env:"LIMITS" separator:","` // key:value,key:value
}
cfg, err := oneenv.Parse[Config](oneenv.WithLookuper(oneenv.MapLookuper{
"DEBUG": "true",
"TIMEOUT": "1m30s",
"TAGS": "web,api,worker",
"LIMITS": "cpu:8,mem:16",
}))
if err != nil {
panic(err)
}
fmt.Printf("debug=%t timeout=%s tags=%v cpu=%d\n",
cfg.Debug, cfg.Timeout, cfg.Tags, cfg.Limits["cpu"])
}
Output: debug=true timeout=1m30s tags=[web api worker] cpu=8
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.
Example ¶
ExampleRead parses a .env file into a plain map without touching the process environment or a struct.
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/bakhod1r/oneenv"
)
func main() {
dir, _ := os.MkdirTemp("", "oneenv")
defer os.RemoveAll(dir)
path := filepath.Join(dir, ".env")
_ = os.WriteFile(path, []byte("HOST=localhost\nPORT=5432\n"), 0o600)
m, err := oneenv.Read(path)
if err != nil {
panic(err)
}
fmt.Printf("%s:%s\n", m["HOST"], m["PORT"])
}
Output: localhost:5432
func Redacted ¶ added in v1.1.0
Redacted renders v as .env-formatted bytes like Marshal, but replaces the value of every field marked ",secret" (or env-secret:"true") with a mask. Use it to log or print a configuration without leaking sensitive values.
Example ¶
ExampleRedacted renders a struct to .env bytes with ",secret" fields masked, so a configuration can be logged without leaking sensitive values.
package main
import (
"fmt"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Host string `env:"HOST"`
Password string `env:"PASSWORD,secret"`
}
cfg := Config{Host: "db", Password: "hunter2"}
data, err := oneenv.Redacted(cfg)
if err != nil {
panic(err)
}
fmt.Print(string(data))
}
Output: HOST=db PASSWORD=****
func RedactedMap ¶ added in v1.1.0
RedactedMap is MarshalMap with secret field values masked.
Example ¶
ExampleRedactedMap masks secret fields while returning a map, handy for structured logging.
package main
import (
"fmt"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
User string `env:"USER"`
Password string `env:"PASSWORD,secret"`
}
m, err := oneenv.RedactedMap(Config{User: "admin", Password: "hunter2"})
if err != nil {
panic(err)
}
fmt.Printf("%s / %s\n", m["USER"], m["PASSWORD"])
}
Output: admin / ****
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
func Usage ¶
Usage writes a human-readable table of the environment variables that a struct of type T consumes: the key, Go type, whether it is required, its default, and the "desc" tag. It is handy for --help output.
oneenv.Usage[Config](os.Stdout)
Example ¶
ExampleUsage prints a table of the variables a struct consumes, handy for a --help flag.
package main
import (
"os"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Port int `env:"PORT" default:"8080" desc:"listen port"`
Host string `env:"HOST,required" desc:"bind address"`
}
_ = oneenv.Usage[Config](os.Stdout)
}
Output: KEY TYPE REQUIRED DEFAULT DESCRIPTION PORT int no 8080 listen port HOST string yes bind address
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 WithEnvFiles ¶ added in v1.1.0
func WithEnvFiles() Option
WithEnvFiles enables the dotenv-style, environment-aware file cascade. On top of each configured base file (default ".env"), oneenv also reads, in increasing priority: "<base>.local", "<base>.<env>" and "<base>.<env>.local", where <env> is the active environment name (see WithEnvVar). Every file in the cascade is optional. This mirrors the layered .env convention used by Rails, Next.js and dotenv-cli.
Example ¶
ExampleWithEnvFiles shows the environment-aware cascade: on top of the base file it also reads <base>.<env>, so with APP_ENV=production the production override wins.
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/bakhod1r/oneenv"
)
func main() {
dir, _ := os.MkdirTemp("", "oneenv")
defer os.RemoveAll(dir)
base := filepath.Join(dir, ".env")
_ = os.WriteFile(base, []byte("HOST=localhost\nPORT=8080\n"), 0o600)
_ = os.WriteFile(base+".production", []byte("HOST=prod.internal\n"), 0o600)
type Config struct {
Host string `env:"HOST"`
Port int `env:"PORT"`
}
cfg, err := oneenv.Parse[Config](
oneenv.WithFiles(base),
oneenv.WithEnvFiles(),
oneenv.WithLookuper(oneenv.MapLookuper{"APP_ENV": "production"}),
)
if err != nil {
panic(err)
}
fmt.Printf("%s:%d\n", cfg.Host, cfg.Port)
}
Output: prod.internal:8080
func WithEnvVar ¶ added in v1.1.0
WithEnvVar sets which environment variables name the active environment for WithEnvFiles, consulted in order (first non-empty wins). Defaults to APP_ENV then GO_ENV.
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.
Example ¶
ExampleWithMutator transforms every value before it is decoded.
package main
import (
"context"
"fmt"
"strings"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Name string `env:"NAME"`
}
cfg, err := oneenv.ParseContext[Config](context.Background(),
oneenv.WithLookuper(oneenv.MapLookuper{"NAME": "app"}),
oneenv.WithMutator(func(_ context.Context, _, v string) (string, error) {
return strings.ToUpper(v), nil
}),
)
if err != nil {
panic(err)
}
fmt.Println(cfg.Name)
}
Output: APP
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.
Example ¶
ExampleWithPrefix restricts every lookup to a prefix, so env:"PORT" reads from APP_PORT. Handy when one process hosts several components.
package main
import (
"fmt"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Port int `env:"PORT"`
}
cfg, err := oneenv.Parse[Config](
oneenv.WithPrefix("APP_"),
oneenv.WithLookuper(oneenv.MapLookuper{"APP_PORT": "9090"}),
)
if err != nil {
panic(err)
}
fmt.Println(cfg.Port)
}
Output: 9090
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.
Example ¶
ExampleWithTypeParser registers a parser for a type that has no built-in support (here net.IP). It also applies inside slices, maps and pointers.
package main
import (
"fmt"
"net"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Addr net.IP `env:"ADDR"`
}
cfg, err := oneenv.Parse[Config](
oneenv.WithLookuper(oneenv.MapLookuper{"ADDR": "10.0.0.1"}),
oneenv.WithTypeParser(func(s string) (net.IP, error) { return net.ParseIP(s), nil }),
)
if err != nil {
panic(err)
}
fmt.Println(cfg.Addr)
}
Output: 10.0.0.1
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.
Example ¶
ExampleWithValidator runs a validation callback on the decoded struct, letting you plug in any validator without adding a dependency.
package main
import (
"errors"
"fmt"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
Port int `env:"PORT"`
}
_, err := oneenv.Parse[Config](
oneenv.WithLookuper(oneenv.MapLookuper{"PORT": "70000"}),
oneenv.WithValidator(func(v any) error {
if v.(*Config).Port > 65535 {
return errors.New("port out of range")
}
return nil
}),
)
fmt.Println(err)
}
Output: port out of range
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.
type Secret ¶ added in v1.1.0
type Secret[T any] struct { // contains filtered or unexported fields }
Secret wraps a sensitive configuration value of type T. It decodes exactly like a bare T (reusing oneenv's setters), but its String, GoString and MarshalJSON representations are masked, so a Secret never leaks through fmt, log, %v/%+v/%#v or encoding/json. Retrieve the real value with Value.
type Config struct {
APIKey oneenv.Secret[string] `env:"API_KEY"`
}
fmt.Println(cfg.APIKey) // ****
client.Use(cfg.APIKey.Value()) // the real key
Example ¶
ExampleSecret wraps a sensitive value so it never prints in the clear, while the real value stays available through Value.
package main
import (
"fmt"
"github.com/bakhod1r/oneenv"
)
func main() {
type Config struct {
APIKey oneenv.Secret[string] `env:"API_KEY"`
}
cfg, err := oneenv.Parse[Config](
oneenv.WithLookuper(oneenv.MapLookuper{"API_KEY": "s3cr3t"}),
)
if err != nil {
panic(err)
}
fmt.Printf("masked=%v real=%s\n", cfg.APIKey, cfg.APIKey.Value())
}
Output: masked=**** real=s3cr3t
func (Secret[T]) MarshalJSON ¶ added in v1.1.0
MarshalJSON masks the value so it never leaks through encoding/json.
func (Secret[T]) MarshalText ¶ added in v1.1.0
MarshalText renders the real underlying value, so Marshal round-trips a Secret back to its plaintext form in a .env file. Use Redacted to mask it.
func (*Secret[T]) UnmarshalText ¶ added in v1.1.0
UnmarshalText decodes raw into the underlying T using oneenv's built-in setter machinery, so Secret[T] supports every type oneenv can decode.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
oneenv
command
Command oneenv is a small CLI around the oneenv package.
|
Command oneenv is a small CLI around the oneenv package. |
|
examples
|
|
|
basic
command
Example: load a .env file straight into a struct.
|
Example: load a .env file straight into a struct. |
|
Package watch adds hot-reloading to oneenv: it re-decodes your configuration whenever a watched .env file changes on disk.
|
Package watch adds hot-reloading to oneenv: it re-decodes your configuration whenever a watched .env file changes on disk. |