Documentation
¶
Overview ¶
Package config turns files and environment variables into the configuration structs the rest of this module is built around, and turns them back into files again.
Every package here with a config subpackage tags its fields the caarlos0/env way — `env:`, `envPrefix:`, `envDefault:`, `env:",init"` — so an application composing several of them has one big struct whose leaves are already described. This package is the application-agnostic place those tags get mounted onto a value. It knows nothing about any particular configuration, and it is not itself one of the config subpackages: nothing in it selects an implementation.
Loading ¶
cfg, err := config.LoadFromYAMLFile[AppConfig](ctx, "config/production.yaml")
The LoadFrom* functions are one function over four sources — a YAML, JSON or TOML file, a .env file, or the environment alone — and all of them finish the same way, by overlaying environment variables onto whatever the file produced. That ordering is the whole point of the layering: the file is what a deployment checks in, and the environment is what a deployment overrides per replica, per region, or per incident, including the secrets that must not be in the file.
The overlay has one edge worth knowing before choosing tags, and it belongs to caarlos0/env rather than to this package: a field carrying an envDefault whose variable is unset is written back to that default, overwriting what the file supplied. A field the file should win is a field with no envDefault. LoadFromJSONFile carries the long form.
WithPrefix scopes the overlay to one application's variables. WithOnSet reports each assignment as it happens along with whether the value came from the environment or from an envDefault — the difference between a setting a deployment chose and one it inherited, which the loaded struct cannot show.
Validate is separate, and deliberately not called by the LoadFrom* functions. It runs the ozzo ValidatableWithContext a config implements, if it implements one, and it is a caller's line rather than a step — a config assembled from several LoadFrom* calls is validated once it is whole, not four times on the way there.
Rendering ¶
config.RenderYAMLFiles(ctx, []config.Environment[AppConfig]{
{Name: "production", Path: "config/production.yaml", Config: prod},
{Name: "staging", Path: "config/staging.yaml", Config: staging},
})
The same three formats go the other way. A service whose per-environment files are hand-maintained text has no compiler between an edit and a deployment; one that builds those files from Go values gets the type checking and the validation before the file exists, and the checked-in file becomes a projection of an object rather than a second source of truth. Encoding runs through github.com/primandproper/primitives-go/encoding, so what is written is what the LoadFrom* side reads.
The variables themselves ¶
github.com/primandproper/primitives-go/config/envvars answers what the overlay can actually read. The `env:` tags reachable from a configuration struct are a closed, derivable set, and nothing at runtime knows it — a variable one underscore off its tag is not read, the file value stands, and the process comes up healthy and wrong. That package derives the set and emits it as Go constants, so a deployment manifest is built from identifiers a compiler checks.
Index ¶
- func ApplyEnvironmentVariables(cfg any, opts ...Option) error
- func LoadFromDotEnvFile[T any](path string, opts ...Option) (*T, error)
- func LoadFromEnvironment[T any](opts ...Option) (*T, error)
- func LoadFromJSONFile[T any](ctx context.Context, path string, opts ...Option) (*T, error)
- func LoadFromTOMLFile[T any](ctx context.Context, path string, opts ...Option) (*T, error)
- func LoadFromYAMLFile[T any](ctx context.Context, path string, opts ...Option) (*T, error)
- func RenderJSONFiles[T any](ctx context.Context, envs []Environment[T], opts ...RenderOption) error
- func RenderTOMLFiles[T any](ctx context.Context, envs []Environment[T], opts ...RenderOption) error
- func RenderYAMLFiles[T any](ctx context.Context, envs []Environment[T], opts ...RenderOption) error
- func ResolveDotEnvPath(baseDir, filename string) (string, error)
- func Validate(ctx context.Context, cfg any) error
- type Environment
- type OnSetFunc
- type Option
- type RenderOption
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ApplyEnvironmentVariables ¶
ApplyEnvironmentVariables populates cfg (a non-nil pointer to a struct) from environment variables using the caarlos0/env struct tags, following that library's standard semantics:
- A field whose env var is set is assigned that value, overriding any value the field already held (e.g. one decoded from a file).
- A field with an envDefault whose env var is unset is assigned the default, which likewise overrides any pre-existing value. Because of this, when layering env vars on top of a decoded config, a field carrying an envDefault always ends up at either its env value or its default — never a value that came only from the file.
- A field with no env var and no envDefault is left untouched.
func LoadFromDotEnvFile ¶
LoadFromDotEnvFile loads the .env file at path into the process environment and then builds a *T from the environment. godotenv does not override variables already present in the process, so real environment values still take precedence over values in the file.
func LoadFromEnvironment ¶
LoadFromEnvironment builds a *T populated entirely from environment variables.
func LoadFromJSONFile ¶
LoadFromJSONFile decodes the JSON file at path into a *T, then overlays environment variables on top of it via ApplyEnvironmentVariables. A set env var takes precedence over the file value. Note the caarlos0/env caveat documented on ApplyEnvironmentVariables: a field carrying an envDefault whose env var is unset is reset to that default even if the file supplied a value, so give such fields their env var (or no envDefault) when the file should win.
func LoadFromTOMLFile ¶
LoadFromTOMLFile behaves like LoadFromJSONFile but decodes a TOML file. TOML keys map to struct fields by their `toml:` tag, falling back to a case-insensitive field-name match.
func LoadFromYAMLFile ¶
LoadFromYAMLFile behaves like LoadFromJSONFile but decodes a YAML file. YAML keys map to struct fields by their `yaml:` tag, falling back to the lower-cased field name.
func RenderJSONFiles ¶
func RenderJSONFiles[T any](ctx context.Context, envs []Environment[T], opts ...RenderOption) error
RenderJSONFiles writes each environment's Config to its Path as indented JSON, and is the inverse of LoadFromJSONFile: what it writes, that function reads back into an equal *T.
The one thing that breaks the round trip is the caarlos0/env caveat LoadFromJSONFile already documents. The overlay runs after the decode, so a field carrying an envDefault whose env var is unset comes back as that default no matter what was rendered. That is a property of loading, not of rendering — the file on disk holds the value it was given either way — but a test asserting Render-then-Load equality has to account for it.
Nothing is written until every environment has been validated and marshaled, so a config that fails either fails the whole call rather than landing a broken file next to updated ones. Validation goes through Validate, which is a no-op for a T that does not implement ozzo-validation's ValidatableWithContext: there is a compiler behind the file regardless, but only a validatable T gets the second guarantee.
The output is stable across runs — struct fields render in declaration order and encoding/json sorts map keys — and ends in exactly one newline.
func RenderTOMLFiles ¶
func RenderTOMLFiles[T any](ctx context.Context, envs []Environment[T], opts ...RenderOption) error
RenderTOMLFiles behaves like RenderJSONFiles but writes TOML, inverting LoadFromTOMLFile. Fields render under their `toml:` tag.
func RenderYAMLFiles ¶
func RenderYAMLFiles[T any](ctx context.Context, envs []Environment[T], opts ...RenderOption) error
RenderYAMLFiles behaves like RenderJSONFiles but writes YAML, inverting LoadFromYAMLFile. Fields render under their `yaml:` tag.
func ResolveDotEnvPath ¶
ResolveDotEnvPath joins baseDir and filename and returns the result if that file exists. A missing file yields "" (and a nil error) so callers can treat "no .env present" as "skip loading" rather than a failure; any other stat error is returned.
func Validate ¶
Validate runs cfg's context-aware validation if cfg implements ozzo-validation's ValidatableWithContext; otherwise it is a no-op. It is a convenience for validating a freshly loaded config, particularly one built solely from the environment where there is no file baseline to fall back on.
Types ¶
type Environment ¶
type Environment[T any] struct { // Config is this environment's configuration object. It must not be nil. Config *T // Name labels the environment in error messages (e.g. "localdev"). It must // not be empty — every failure the render functions report names the // environment it came from, and an unnamed one makes those unreadable. Name string // Path is where the rendered file is written. Parent directories are // created as needed. It must not be empty, and no two environments in one // call may resolve to the same path. Path string }
Environment pairs one named configuration object with the path it renders to. It is the unit the render functions operate on: the Config is built in Go, so the file on disk is a projection of a real, compiled, validated object rather than hand-maintained text that drifts from the struct it is decoded into.
type OnSetFunc ¶
OnSetFunc is invoked for each field the parser populates from the environment. It mirrors caarlos0/env's OnSet hook and is handy for debug logging which variables were applied. Wire it to any logger via WithOnSet.
type Option ¶
type Option func(*options)
Option configures how environment variables are applied.
func WithOnSet ¶
WithOnSet registers a hook invoked for each field populated from the environment. Passing nil is a no-op.
func WithPrefix ¶
WithPrefix sets a prefix prepended to every env var key the parser reads (e.g. "MYAPP_"). Nested envPrefix struct tags are appended after it.
type RenderOption ¶
type RenderOption func(*renderOptions)
RenderOption configures how the render functions write their files.
It is deliberately not Option. Option configures the environment-variable overlay the loaders apply on the way in, and neither WithPrefix nor WithOnSet means anything on the way out; sharing the type would buy symmetry at the price of accepting two options and silently ignoring them.
func WithDirMode ¶
func WithDirMode(mode fs.FileMode) RenderOption
WithDirMode sets the mode parent directories are created with, overriding the owner-and-group default.
func WithFileMode ¶
func WithFileMode(mode fs.FileMode) RenderOption
WithFileMode sets the mode rendered files are created with, overriding the owner-only default. It has no effect on a file that already exists.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package cfgnorm holds the normalization a config performs on itself before its own validation runs.
|
Package cfgnorm holds the normalization a config performs on itself before its own validation runs. |
|
Package envvars derives the closed set of environment variables that can override a configuration struct, and writes it out as Go constants.
|
Package envvars derives the closed set of environment variables that can override a configuration struct, and writes it out as Go constants. |
|
Package injection holds the samber/do helpers shared by this module's do.Provide registrations.
|
Package injection holds the samber/do helpers shared by this module's do.Provide registrations. |