config
Thread-safe configuration from TOML files, environment variables, command-line arguments and registered defaults. Requires Go 1.27.1. The only runtime dependency is lixenwraith/toml.
package main
import (
"log"
"time"
"github.com/lixenwraith/config"
)
type Settings struct {
Port uint16 `toml:"port"`
Timeout time.Duration `toml:"timeout"`
}
func main() {
initial := Settings{Port: 8080, Timeout: 5 * time.Second}
cfg, err := config.NewBuilder().
WithTarget(&initial).
WithFile("config.toml").
WithEnvPrefix("APP_").
WithArgs([]string{"--port=9090"}).
Build()
if err != nil { log.Fatal(err) }
latest, err := cfg.AsStruct()
if err != nil { log.Fatal(err) }
log.Printf("%+v", latest.(*Settings))
}
# Service settings
timeout = "10s"
port = 8080
Precedence is CLI > environment > file > default. Register paths with Register
or structs with RegisterStruct; defaults define the types used to validate new
values. LoadFile, LoadEnv and LoadCLI replace their complete source. A failed
source load keeps its previous values. Removing an environment variable, CLI flag
or file key removes the old override on the next load.
Get returns a detached raw source value: environment and CLI values remain
strings, TOML integers are int64. GetTyped[T], Scan, ScanSource, ScanTyped[T]
and ScanMap provide checked conversions. Errors leave destinations unchanged;
missing struct fields retain their existing values. AsStruct returns a detached
snapshot on every call. Build fills WithTarget once; subsequent reloads never
mutate that caller-owned object.
Migration
- Only TOML files and
tomltags are supported. JSON/YAML parsers, format constants and alternate tag modes are removed. Existing format/tag option methods accept onlytoml(autoremains an alias for TOML for file options). - Numeric conversion rejects overflow, negative unsigned inputs, fractional integer conversion, non-finite floats and integer-to-float precision loss. Float64-to-float32 rounding within range remains supported. Full uint64 is supported in memory; saving a value above MaxInt64 fails without changing the destination file. Use an application-level string representation if needed.
- Strings can represent decimal numbers, booleans, durations, RFC3339
timestamps, IP addresses, CIDR networks and URLs. For a slice or array path,
environment and command-line text splits at commas; a string from a file,
SetorScanMapis one element, since those have native arrays and a pattern may hold a comma. Boolean to string conversion usestrue/false. Conversion errors name the path, never the value. There are no user decode hooks or automaticencoding.TextUnmarshalercalls. - Setters and source loads reject values incompatible with registered defaults immediately. Check their errors. Untyped nil defaults leave the value type open; typed nil pointers retain their declared type. Unsupported values, non-finite floats and cyclic/deep graphs are rejected. Configuration value depth is 128.
- All public values, defaults, options and clones own their mutable containers.
Existing references returned from
AsStructare snapshots, not live objects. - Saves standardize formatting and retain loaded comments as a preamble. Comment positions are not preserved. Existing file permissions are retained; new files use 0600. Only registered paths are saved, so unknown file keys are omitted.
- Paths use dot-separated ASCII key segments. Numeric-only segments and overlapping
registrations such as
serverplusserver.portare rejected. Embedded fields are not promoted. Recursive struct schemas are unsupported.
Watching
changes := cfg.WatchWithOptions(config.DefaultWatchOptions())
defer cfg.StopAutoUpdate()
for event := range changes {
// Events are hints; always read the latest complete state.
log.Print(event)
latest, err := cfg.AsStruct()
if err != nil { log.Print(err); continue }
_ = latest.(*Settings)
}
The watcher fingerprints file contents, waits for a quiet debounce interval, then validates and publishes. Invalid edits, timeouts, deletion and a permission change, reported once before the next edit applies, retain the last valid state. Cancelled, expired or superseded reloads cannot publish. Stop closes subscriptions synchronously; a new Watch call can restart watching. Notifications are buffered and best effort.
Documentation
- Quick start, builder, access
- Files and saving, environment, CLI
- Live reconfiguration, validation
- Architecture, audit evidence
Run go test -race ./... and go vet ./.... CI also executes the 386 suite,
cross-compiles for Windows and runs bounded numeric conversion fuzzing.
BSD-3-Clause; see LICENSE.