gostructor

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 18 Imported by: 3

README

gostructor

CI Coverage Go Report Card Go Reference

gostructor fills the fields of a Go struct from any mix of configuration sources — environment variables, files, HashiCorp Vault, or plain defaults — driven by two small struct tags, in the priority order you choose via the source list.

type Config struct {
    Host  string `cfg:"host" gos:"default:0.0.0.0"`
    Port  int    `cfg:"port" gos:"default:8080"`
    Debug bool   `cfg:"debug" gos:"default:false"`
}

cfg, err := gostructor.Configure(&Config{})

Two tags, cleanly separated:

  • cfgrouting & naming: cfg:"base_name,source:override,...". The base name is what each source looks up (the env source reads HOST from host); a source:override pins a specific key for one source.
  • gosbehavior: gos:"default:8080,secret,optional,sep:;" — a literal default, a masked-secret flag, an unresolved-is-ok flag, and a slice separator.

One field, several possible sources, resolved with a priority you control (the order of the source list) — rather than merging every source into one map and unmarshalling it once. A configured field that no source can resolve is a hard error unless you mark it gos:"optional": surprises are errors.

v1.0 replaces the old cf_* tag family with just cfg + gos, makes priority the source order, and splits YAML/TOML/HOCON/Vault into their own zero-dependency-core modules. Upgrading from v0.x? See docs/migration.md.

Install

go get github.com/goreflect/gostructor

That alone gets you the env, default, JSON, and INI sources with no dependencies beyond the Go standard library. Add whichever of these you need:

go get github.com/goreflect/gostructor/yaml    # yaml source
go get github.com/goreflect/gostructor/toml    # toml source
go get github.com/goreflect/gostructor/hocon   # hocon source
go get github.com/goreflect/gostructor/vault   # vault source

Live-config and remote sources (each its own module, all optional):

go get github.com/goreflect/gostructor/watch        # fsnotify file source (hot reload)
go get github.com/goreflect/gostructor/git          # git repo as config, ref = version
go get github.com/goreflect/gostructor/consul       # Consul KV
go get github.com/goreflect/gostructor/etcd         # etcd v3
go get github.com/goreflect/gostructor/springcloud  # Spring Cloud Config Server
go get github.com/goreflect/gostructor/snapshot     # durable last-known-good store

Requires Go 1.24+.

Quick start

package main

import (
    "fmt"
    "log"

    "github.com/goreflect/gostructor"
)

type Config struct {
    Host string `cfg:"host" gos:"default:0.0.0.0"` // env HOST, else default
    Port int    `cfg:"port" gos:"default:8080"`    // env PORT, else default
}

func main() {
    cfg, err := gostructor.Configure(&Config{})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%+v\n", cfg)
}

Configure reads the cfg/gos tags, tries each field's sources in list order (the default list is Env, Default, so an env var beats the default), and returns the same pointer you passed in. A field with no gostructor tags is left untouched; a configured field that no source can resolve is a hard error (unless it's gos:"optional"), so misconfiguration fails loudly instead of shipping a zero value.

Features & live demos

Each feature has a short page under docs/features that pairs an explanation with a runnable command and its real output — copy the command, run it against your checkout, and see the same thing.

Feature What you get Run it
Two-tag configuration Fill a struct from cfg (routing) + gos (behavior). go run ./examples/basic
Priority = source order The same struct resolving differently per environment, from the WithSources order alone. go run ./examples/priority
Sources env, default, JSON, INI (core) + YAML/TOML/HOCON/Vault (modules). go run ./examples/filesources
Field types Durations, slices, time.Time/net.IP, named types, pointers, maps, structs — strict conversion. go run ./examples/types
Hooks Validate and transform each resolved value before it lands. go run ./examples/hooks
Error taxonomy A closed set of typed errors, classified with errors.Is/errors.As. go run ./examples/errors
Observability & masking A focused resolution trace, provenance map, masked secrets. go run ./examples/observability
Full service config ~30 fields, nested sub-structs, JSON + env + defaults at once. go run ./examples/webservice
Live reload Watch re-fills the struct on change; transactional last-known-good; git/consul/etcd/springcloud/vault adapters. cd examples/hotreload-file && go run .

Supported sources

The source name is what you target in a per-source override (cfg:"port,env:DB_PORT") and what appears in the resolution trace. Non-core sources are opt-in via WithSources, so a bare cfg name never triggers an unexpected file load.

Source Reads from Module Requires
default Literal gos:"default:..." value core
env Environment variable core
json JSON file core GOSTRUCTOR_JSON=...
ini INI file core GOSTRUCTOR_INI=...
keyvalue Key/value file (.env/.properties) core GOSTRUCTOR_KEYVALUE=...
yaml YAML file gostructor/yaml GOSTRUCTOR_YAML=...
toml TOML file gostructor/toml GOSTRUCTOR_TOML=...
hocon HOCON file gostructor/hocon GOSTRUCTOR_HOCON=...
vault HashiCorp Vault secret gostructor/vault VAULT_ADDR, VAULT_TOKEN
map In-memory map (gostructor.Map) core

Live / remote sources — each implements Watchable, so gostructor.Watch re-fills the struct when the backing data changes (see docs/live-reload.md):

Source Reads from Module Live via
file Config file on disk (any format) gostructor/watch fsnotify
git File in a git repo, any format (ref = version) gostructor/git poll for drift + SetVersion
consul Consul KV prefix gostructor/consul blocking queries
etcd etcd v3 key prefix gostructor/etcd native watch API
springcloud Spring Cloud Config Server gostructor/springcloud poll
vault HashiCorp Vault secret gostructor/vault poll (secret rotation)

Each has a fully reproducible, docker-compose example under examples/. Full addressing rules, per-source key derivation, and priority: docs/sources.md.

The file and git sources are format-agnostic: they default to JSON but read any format the library supports via a pluggable decoder — pass gostructor.DecodeINI / gostructor.DecodeKeyValue (zero-dep) or yaml.Decode / toml.Decode / hocon.Decode as the source's Decoder.

Documentation

The docs/ directory holds the deeper reference — dip in when you need it:

  • configuration.md — the Configure API, WithSources, the full cfg/gos tag grammar, hooks, and logging.
  • sources.md — every source in detail, priority, and writing your own Source.
  • field-types.md — supported field types and strict conversion rules.
  • observability.md — the resolution trace (ConfigureWithReport) and secret masking.
  • limitations.md — known limitations and the error taxonomy.
  • migration.md — v1.0 changes and migrating from v0.x.

Roadmap

See ROADMAP.md for the full plan. Headlines:

  • Observability & masking — ✅ shipped: a focused resolution trace (ConfigureWithReport). See docs/observability.md.
  • Hot reload — ✅ shipped: a Watchable source interface and a Watch helper that re-fills the struct (transactionally, last-known-good) when a backing source changes. See docs/live-reload.md.
  • Git as source of truth — ✅ shipped (gostructor/git): a branch/tag as a config version, snapshotted and polled for drift, switchable at runtime.
  • Config-server adapters — ✅ shipped: Consul (gostructor/consul), etcd (gostructor/etcd), Spring Cloud Config (gostructor/springcloud), and a now-Watchable Vault — each a Source module with a docker-compose example.
  • Ergonomics — in-memory override source (gostructor.Map) shipped; self-documenting config, whole-struct validation, and custom time layouts next.

Development

make build   # go build ./... in every module (core, yaml, toml, hocon, vault, examples/multisource)
make vet
make test    # go test ./... -race -cover in every module
make all     # build + vet + test

Test coverage (statements, library packages, excluding the runnable examples/): ~89% aggregate. Per module: core 86%, yaml 79%, toml 85%, hocon 84%, vault 87%, with the internal convert/ini/structplan parsers each above 89%. Reproduce with:

go test -coverpkg=$(go list ./... | grep -v /examples/ | paste -sd, -) \
    $(go list ./... | grep -v /examples/) -cover

Each submodule's go.mod carries a local replace directive pointing at ../ so it builds against your working copy of core; that line is a no-op for anyone importing the module normally, since Go only applies replace directives from the main module of a build.

Runnable usage examples live in examples/.

Contributing

See CONTRIBUTING.md.

License

MIT

Documentation

Overview

Package gostructor fills the fields of a Go struct from a mix of configuration sources (environment variables, files, secret stores), driven by two struct tags: cfg for routing/naming and gos for behavior. See the README for the full guide. This file holds the engine: the Configure entry point and the resolution loop that walks a cached structplan.Plan and asks each Source, in slice order, for a value.

Sources are tried in the order passed to WithSources; the first that reports found=true wins. There is no per-field priority tag and no global selector: the slice order decides priority.

Index

Constants

View Source
const (
	SourceEnv     = "env"
	SourceJSON    = "json"
	SourceINI     = "ini"
	SourceDefault = "default"
)

Source names for the core module's built-in sources. These strings are what a cfg tag targets in a per-source override, e.g. `cfg:"port,env:DB_PORT"`.

View Source
const INIFileEnvVar = "GOSTRUCTOR_INI"

INIFileEnvVar names the environment variable INI reads its file path from, unless a path was given explicitly to INIFile.

View Source
const JSONFileEnvVar = "GOSTRUCTOR_JSON"

JSONFileEnvVar names the environment variable JSON reads its file path from, unless a path was given explicitly to JSONFile.

View Source
const KeyValueFileEnvVar = "GOSTRUCTOR_KEYVALUE"

KeyValueFileEnvVar names the environment variable KeyValue reads its file path from, unless a path was given explicitly to KeyValueFile.

View Source
const SourceKeyValue = "keyvalue"

SourceKeyValue is the key/value source's identity and its cfg per-source override key, e.g. `cfg:"host,keyvalue:DATABASE_HOST"`.

View Source
const SourceMap = "map"

SourceMap is the default identity of the in-memory Map source.

Variables

View Source
var ErrFieldNotResolved = errors.New("no configured source produced a value")

ErrFieldNotResolved is the sentinel wrapped by *NotResolvedError: a field carried at least one recognized source tag, but no configured source produced a value for it. Match it with errors.Is:

if errors.Is(err, gostructor.ErrFieldNotResolved) { ... }
View Source
var ErrInvalidTarget = errors.New("gostructor: target must be a non-nil pointer to a struct")

ErrInvalidTarget is returned, wrapped, when Configure is called with a target that is not a non-nil pointer to a struct. Match it with errors.Is.

Functions

func Configure added in v1.1.0

func Configure[T any](target *T, opts ...Option) (*T, error)

Configure fills target's fields in place and returns target for chaining. With no options it uses a minimal default source list: Env then Default. Add file and secret sources (JSON(), INI(), yaml.New(), vault.New(), ...) via WithSources; they are opt-in, so a bare cfg base name never triggers an unexpected file load.

func DecodeINI added in v1.1.0

func DecodeINI(raw []byte) (map[string]any, error)

DecodeINI parses an INI document into a nested map: global keys at the top level, each `[section]` a nested map. Values are strings.

func DecodeJSON added in v1.1.0

func DecodeJSON(raw []byte) (map[string]any, error)

DecodeJSON parses JSON into a nested map, keeping numbers exact (json.Number) so large integers survive rather than being rounded through float64 — the same decoding the JSON source uses.

func DecodeKeyValue added in v1.1.0

func DecodeKeyValue(raw []byte) (map[string]any, error)

DecodeKeyValue parses a flat `.env`/`.properties` file (see parseKeyValue) into a flat map of string values. It backs the KeyValue source and is reusable as a git/watch Decoder.

func Identity added in v1.1.0

func Identity(base string) string

Identity returns base unchanged. It is the naming strategy for file sources (JSON, YAML, TOML, HOCON, INI), whose keys are the base names as written; nested paths are given explicitly via a per-source override.

func LookupKey added in v1.1.0

func LookupKey(field FieldContext, sourceName string, data map[string]any) (any, bool)

LookupKey resolves field against an already-decoded, possibly nested map[string]any using the field's per-source override for sourceName (or its base name) as a dot-separated path ("server.host"). It returns found=false when the field has no key for this source or the path is absent/nil.

It is exported so remote map-backed sources in separate modules (git, Spring Cloud Config) resolve keys exactly like the built-in file sources.

func PollWatch added in v1.1.0

func PollWatch(ctx context.Context, interval time.Duration, fingerprint func(context.Context) (string, error), onChange func(), log *slog.Logger) error

PollWatch helps a Watchable source implement Watch by polling a cheap fingerprint (a commit SHA, a KV modify-index, a secret version) on an interval and calling onChange when it differs from the previous poll. It blocks until ctx is cancelled. A fingerprint error is logged (if log is non-nil) and retried on the next tick rather than ending the watch. A non-positive interval defaults to 30s. The first poll only establishes the baseline and does not fire onChange.

func ScreamingSnake added in v1.1.0

func ScreamingSnake(base string) string

ScreamingSnake converts a base name to SCREAMING_SNAKE_CASE, the naming strategy for environment variables: "port" -> "PORT", "maxConns" -> "MAX_CONNS", "HTTPServer" -> "HTTP_SERVER". Hyphens, dots and spaces become underscores; camelCase and acronym boundaries get an underscore inserted.

func Watch added in v1.1.0

func Watch[T any](ctx context.Context, target *T, onReload func(*T, error), opts ...Option) error

Watch fills target once, then keeps it live: whenever a Watchable source reports a change, it re-fills a fresh *T (same path as the initial fill) and delivers the result to onReload. It blocks until ctx is cancelled.

Reloads are transactional: a fresh copy is filled and validated before being published, so a failed reload leaves the last-known-good config serving and reports the error via onReload(nil, err) instead of crashing. onReload is called with the initial fill too, so callers can publish it (typically an atomic.Pointer swap) with the same code used for reloads.

A failed *initial* fill is fatal: Watch returns the error without calling onReload, like Configure. WithDebounce coalesces a burst of signals into one reload. With no Watchable source, Watch fills once and blocks until ctx ends.

Types

type Attempt added in v1.1.0

type Attempt struct {
	Source string // e.g. "env"
	Status string // not-found | used | skipped | error
	Detail string // the key looked up (env var, file key) or an error summary
}

Attempt records a single source's turn at resolving a field.

type ConvertError added in v1.1.0

type ConvertError struct {
	Field  string       // struct field name being filled
	Value  any          // the raw value a source produced
	Target reflect.Type // the field's Go type
	Cause  error        // the underlying conversion failure, if any
}

ConvertError reports a failure to convert a resolved raw value into a struct field's Go type: a fractional float into an int, an out-of-range number, a malformed duration string, and so on. It carries the offending value and target type, and unwraps to the underlying cause (a *strconv.NumError, a time.ParseDuration error, a TextUnmarshaler error) for a further errors.As.

func (*ConvertError) Error added in v1.1.0

func (e *ConvertError) Error() string

func (*ConvertError) FieldName added in v1.1.0

func (e *ConvertError) FieldName() string

FieldName reports the struct field this error concerns.

func (*ConvertError) Unwrap added in v1.1.0

func (e *ConvertError) Unwrap() error

type Decoder added in v1.1.0

type Decoder func([]byte) (map[string]any, error)

A Decoder turns a configuration file's raw bytes into a nested map, the shape LookupKey addresses. It is the pluggable format layer for the remote, file-shaped sources (gostructor/git, gostructor/watch).

The core ships zero-dependency decoders — DecodeJSON, DecodeINI, DecodeKeyValue. The yaml/toml/hocon modules expose their own, so a format's decoder pulls in only that format's dependency.

type FieldContext added in v1.1.0

type FieldContext struct {
	reflect.StructField
	// contains filtered or unexported fields
}

FieldContext describes the struct field being resolved, exposing its parsed cfg (base name and per-source overrides) and gos (defaults, flags) metadata so a Source never has to parse struct tags itself.

func (FieldContext) Base added in v1.1.0

func (f FieldContext) Base() string

Base returns the field's base name (the first cfg element), from which each source derives its key when there is no explicit override.

func (FieldContext) Flag added in v1.1.0

func (f FieldContext) Flag(name string) bool

Flag reports whether a bare gos flag is present, as in `gos:"secret"` -> Flag("secret") == true.

func (FieldContext) IsSecret added in v1.1.0

func (f FieldContext) IsSecret() bool

IsSecret reports whether the field carries the gos secret flag; its value is masked everywhere it would otherwise print (report, trace, ConvertError).

func (FieldContext) Meta added in v1.1.0

func (f FieldContext) Meta(key string) (string, bool)

Meta returns a gos meta value, as in `gos:"default:8080"` -> Meta("default") == ("8080", true).

func (FieldContext) Optional added in v1.1.0

func (f FieldContext) Optional() bool

Optional reports whether the field carries the gos optional flag; an optional field left unresolved by every source is not an error.

func (FieldContext) Override added in v1.1.0

func (f FieldContext) Override(source string) (string, bool)

Override returns the explicit key a cfg tag set for source, as in `cfg:"port,env:SERVER_PORT"` -> Override("env") == ("SERVER_PORT", true).

func (FieldContext) Separator added in v1.1.0

func (f FieldContext) Separator() string

Separator returns the string used to split a flat value into slice elements: the gos sep meta if set, otherwise a comma.

func (FieldContext) SourceKey added in v1.1.0

func (f FieldContext) SourceKey(source string, naming func(base string) string) string

SourceKey returns the key source should look up for this field: the explicit per-source override if present, otherwise naming(Base()). It returns "" when there is no override and no base name, signalling the source does not apply.

func (FieldContext) TagValue added in v1.1.0

func (f FieldContext) TagValue(tagName string) string

TagValue is a convenience for field.Tag.Get(tagName), for the rare source that still wants a raw struct tag value.

type FieldError added in v1.1.0

type FieldError interface {
	error
	FieldName() string
}

FieldError is implemented by every Configure error scoped to one struct field: NotResolvedError, SourceError, ConvertError, and HookError. It lets a caller recover which field failed without switching on the concrete type:

var fe gostructor.FieldError
if errors.As(err, &fe) { ... fe.FieldName() ... }

type FieldResolution added in v1.1.0

type FieldResolution struct {
	Field    string    // struct field name
	Type     string    // Go type, e.g. "int", "time.Duration"
	Attempts []Attempt // every source considered, in the order tried
	Winner   string    // source name that produced the value, or "" if none
	Raw      any       // value the winning source returned (masked if secret)
	Value    any       // converted, field-typed value    (masked if secret)
	Outcome  string    // resolved | default | unresolved | error | skipped
	IsSecret bool      // field carries gos:"secret"
}

FieldResolution records the resolution of a single struct field: every source considered, the winner, and the raw and converted values.

type Hook added in v1.1.0

type Hook func(field FieldContext, value any) (any, error)

Hook runs after a value has been resolved for a field but before it is set on the struct, letting you validate or transform it. Returning an error aborts Configure with that error.

type HookError added in v1.1.0

type HookError struct {
	Field string // struct field name the hook ran on
	Cause error  // the hook's error, or a type-mismatch description
}

HookError reports that a WithHook callback rejected or failed to transform a field's value. A hook that returns a non-nil error produces a HookError wrapping it; a hook that returns a value of the wrong type produces a HookError whose Cause explains the type mismatch. It unwraps to that cause.

func (*HookError) Error added in v1.1.0

func (e *HookError) Error() string

func (*HookError) FieldName added in v1.1.0

func (e *HookError) FieldName() string

FieldName reports the struct field this error concerns.

func (*HookError) Unwrap added in v1.1.0

func (e *HookError) Unwrap() error

type Masker added in v1.1.0

type Masker func(field FieldContext, value any) string

Masker renders a sensitive field's value for display. It is called for any field carrying the gos secret flag before its value reaches a log, the resolution report, or an error message. The default fully redacts.

type NotResolvedError added in v1.1.0

type NotResolvedError struct {
	Field   string   // struct field name
	Sources []string // the source names tried, in order
}

NotResolvedError reports that a configured field produced no value from any source. Sources lists the source names tried, in order, so the message can say precisely what was looked at (e.g. tried env, json). It wraps ErrFieldNotResolved.

func (*NotResolvedError) Error added in v1.1.0

func (e *NotResolvedError) Error() string

func (*NotResolvedError) FieldName added in v1.1.0

func (e *NotResolvedError) FieldName() string

FieldName reports the struct field this error concerns.

func (*NotResolvedError) Unwrap added in v1.1.0

func (e *NotResolvedError) Unwrap() error

type Option added in v1.1.0

type Option func(*config)

Option configures a single Configure call.

func WithDebounce added in v1.1.0

func WithDebounce(d time.Duration) Option

WithDebounce sets how long Watch waits for a source's change signals to go quiet before running one reload, collapsing a burst (an editor's several writes for one save, a config server's batch of key events) into a single re-fill. Zero disables debouncing (every signal reloads immediately). It has no effect on a plain Configure call.

func WithHook added in v1.1.0

func WithHook(h Hook) Option

WithHook registers a hook run after each field is resolved, in the order added. Use it for validation (return an error to reject a value) or transformation (return a different value).

func WithLogger added in v1.1.0

func WithLogger(l *slog.Logger) Option

WithLogger sets the logger Configure uses for diagnostic output. The default is a no-op logger; pass slog.Default() (or your own) to see what gostructor is doing.

func WithMasker added in v1.1.0

func WithMasker(m Masker) Option

WithMasker overrides how secret fields (gos:"secret") are rendered for display. The default fully redacts ("••••••"); pass your own to, say, reveal the last four characters. The masker is only ever called for fields carrying the gos secret flag.

func WithSources added in v1.1.0

func WithSources(sources ...Source) Option

WithSources replaces the default source list with an explicit, ordered one. Sources are tried in the order given; the first one that reports found=true for a field wins. This slice order is how priority is expressed: put the source that should win first. Use this to bring in external sources such as yaml.New() or vault.New(), since the core module only ships Env, JSON, INI, and Default.

func WithTrace added in v1.1.0

func WithTrace() Option

WithTrace makes Configure log the full resolution report at debug level through the configured logger (see WithLogger) once resolution completes. It has no effect without a logger. To capture the report programmatically instead, use ConfigureWithReport.

func WithValidate added in v1.1.0

func WithValidate[T any](fn func(*T) error) Option

WithValidate registers a whole-struct validation run during Watch after a reload has filled a fresh copy but before it is published. Returning an error rejects that reload: the previously good config keeps serving and the error is delivered to onReload rather than swapping in a broken value. It complements per-field WithHook (which runs during every fill) with a check that can see the whole struct at once. It has no effect on a plain Configure call; validate there is the caller's own concern.

type Report added in v1.1.0

type Report struct {
	// Type is the configured struct's Go type, e.g. "main.Config".
	Type string
	// Fields holds one FieldResolution per struct field walked, in struct
	// order (nested fields flattened, matching resolution order).
	Fields []FieldResolution
}

Report is a per-field account of how Configure resolved a struct: which sources it tried for each field, in what order, which won, and what value came out. Obtain one from ConfigureWithReport; the plain Configure builds no report, so it is free when unused.

Report is the machine view. String() renders a focused summary of the actionable data: the primary source, the defaults, and an Overrides & Secrets section. Secret fields (gos:"secret") are masked throughout.

func ConfigureWithReport added in v1.1.0

func ConfigureWithReport[T any](target *T, opts ...Option) (*T, *Report, error)

ConfigureWithReport is Configure plus a resolution trace: for every field it records which sources were tried, in what order, which won, and the raw and converted values (secrets masked). Report is the machine view; its String() renders a human summary. On error the report is returned as far as resolution got.

func (*Report) PrimarySource added in v1.1.0

func (r *Report) PrimarySource() string

PrimarySource reports the source that resolved the most non-secret fields, i.e. the environment's main configuration origin (typically a YAML/JSON/TOML file). The Default source is never the primary. It returns "" when no non-default source won a non-secret field.

func (*Report) Provenance added in v1.1.0

func (r *Report) Provenance() map[string]string

Provenance projects the report down to the "field -> winning source name" map: the dependency view of how this environment assembled the config. Fields that resolved from no source (unresolved or unconfigured) are omitted.

func (*Report) String added in v1.1.0

func (r *Report) String() string

String renders a focused resolution trace: a one-line summary, a one-liner for the primary source and for defaults, then an Overrides & Secrets section listing only the fields that were resolved by a non-primary, non-default source (an override) or that are secret. Fields resolved straight from the primary source or from a plain default are summarized by count, not listed.

Configuring main.Config: 5 fields
[Primary Source] json (loaded 3 fields)
[Defaults] applied for 1 field

Overrides & Secrets:
  Port     int    ⇐ env (override; json had it too)
  Password string ⇐ vault  •••••••• (secret)

type Source added in v1.1.0

type Source interface {
	// Name is the source's short identity, e.g. "env". It doubles as the
	// per-source override key in a cfg tag.
	Name() string
	// Resolve returns the raw value for field, or found=false if this
	// source has nothing to contribute for it.
	Resolve(field FieldContext) (value any, found bool, err error)
}

Source resolves one struct field's value from a single configuration origin: an environment variable, a parsed file, a secret store, and so on.

Resolve returns found=false when nothing in this source applies to the field (for example, the field has no key for this source), so the engine falls through to the next source instead of treating it as an error.

Name is the source's short identity ("env", "json", "vault", ...), and the key used to target it from a cfg per-source override such as `cfg:"port,env:SERVER_PORT"`.

Sources outside the core module (gostructor/yaml, gostructor/toml, gostructor/hocon, gostructor/vault, or your own) implement this interface to plug into Configure via WithSources.

func Default added in v1.1.0

func Default() Source

Default resolves fields from the literal value on the gos default meta, `gos:"default:8080"`. It has no external dependency and always applies when the meta is present, so it's typically placed last in a source list as the fallback. For a slice/array field the value is split on the field's separator (gos sep, default comma).

func Env added in v1.1.0

func Env() Source

Env resolves fields from environment variables. The variable name is the field's base name in SCREAMING_SNAKE_CASE (`cfg:"port"` -> PORT), unless the cfg tag overrides it (`cfg:"port,env:DB_PORT_LEGACY"` -> DB_PORT_LEGACY).

func INI added in v1.1.0

func INI() Source

INI resolves fields from the INI file named by the GOSTRUCTOR_INI environment variable. The key is the field's base name in the global, section-less part of the file (`cfg:"port"`); a value inside a section is addressed by overriding with "section#key" (`cfg:"port,ini:server#port"`).

func INIFile added in v1.1.0

func INIFile(path string) Source

INIFile is like INI but reads from path instead of the GOSTRUCTOR_INI environment variable.

func JSON added in v1.1.0

func JSON() Source

JSON resolves fields from the JSON file named by the GOSTRUCTOR_JSON environment variable. The key is the field's base name (`cfg:"host"` -> the top-level "host" key); a nested value is addressed by overriding the key with a dotted path (`cfg:"host,json:server.host"` -> "host" inside "server"). A bare object key like `cfg:"server"` addresses the whole nested object, for map[string]T destination fields.

func JSONFile added in v1.1.0

func JSONFile(path string) Source

JSONFile is like JSON but reads from path instead of the GOSTRUCTOR_JSON environment variable.

func KeyValue added in v1.1.0

func KeyValue() Source

KeyValue resolves fields from a flat key/value file (the `.env`/`.properties` shape: `KEY=VALUE` lines, `#`/`;` comments, optional `export`, quoted values) named by the GOSTRUCTOR_KEYVALUE environment variable. A field's key is its base name as written (`cfg:"host"` -> the `host` key); pin a different key with a per-source override (`cfg:"host,keyvalue:DB_HOST"`). Values are strings; a slice/array field's value is split on the field separator.

func KeyValueFile added in v1.1.0

func KeyValueFile(path string) Source

KeyValueFile is like KeyValue but reads from path instead of the GOSTRUCTOR_KEYVALUE environment variable.

func Map added in v1.1.0

func Map(name string, data map[string]any) Source

Map returns a Source backed by an in-memory map, resolving fields with the same nested-key addressing as the JSON source (see LookupKey). It is handy as a highest-priority override in tests and programmatic setups — put it first in WithSources. name is the source identity and cfg override key; empty defaults to SourceMap.

type SourceError added in v1.1.0

type SourceError struct {
	Field  string // struct field name being resolved
	Source string // the failing source's name, e.g. "json"
	Cause  error  // the error the Source returned
}

SourceError reports that a configured Source failed while resolving a field: a JSON file that doesn't exist or parse, an unreachable Vault server, and so on. Source names the failing source (e.g. "json") so you can tell which of several sources went wrong. It unwraps to the error the Source returned.

func (*SourceError) Error added in v1.1.0

func (e *SourceError) Error() string

func (*SourceError) FieldName added in v1.1.0

func (e *SourceError) FieldName() string

FieldName reports the struct field this error concerns.

func (*SourceError) Unwrap added in v1.1.0

func (e *SourceError) Unwrap() error

type Watchable added in v1.1.0

type Watchable interface {
	Watch(ctx context.Context, onChange func()) error
}

Watchable is an optional interface a Source may implement to signal that its backing data can change while the program runs. Watch (see watch.go) subscribes to every configured source that implements it and re-fills the target struct whenever any of them reports a change.

Watch (the method) blocks until ctx is cancelled, calling onChange each time the underlying data changes. It must honour ctx — returning ctx.Err() once cancelled — and return any fatal error otherwise. onChange may be invoked from any goroutine and must be safe to call repeatedly; the Watch driver coalesces bursts through WithDebounce, so a source need not debounce itself.

A source that caches a snapshot for Resolve should refresh that snapshot *before* calling onChange, so the re-fill triggered by onChange reads the new data rather than the old.

Directories

Path Synopsis
examples
basic command
Command basic shows the minimum gostructor setup: env vars (named from each field via the cfg tag) with gos default fallbacks, using only the core module.
Command basic shows the minimum gostructor setup: env vars (named from each field via the cfg tag) with gos default fallbacks, using only the core module.
errors command
Command errors tours gostructor's typed error taxonomy: it triggers each failure mode and classifies it with errors.Is / errors.As, so a caller can tell what went wrong and whose fault it is.
Command errors tours gostructor's typed error taxonomy: it triggers each failure mode and classifies it with errors.Is / errors.As, so a caller can tell what went wrong and whose fault it is.
filesources command
Command filesources shows the two core file sources, JSON and INI, used together on one struct with env and a gos default in the same priority chain.
Command filesources shows the two core file sources, JSON and INI, used together on one struct with env and a gos default in the same priority chain.
hooks command
Command hooks shows WithHook for two jobs: transforming a resolved value (normalise a string) and validating one (reject an out-of-range port), both operating on the already-converted, field-typed value.
Command hooks shows WithHook for two jobs: transforming a resolved value (normalise a string) and validating one (reject an out-of-range port), both operating on the already-converted, field-typed value.
observability command
Command observability shows gostructor's focused resolution trace: a summary line, the primary source, the defaults count, and an Overrides & Secrets section listing only the fields that were overridden away from the primary config or that are secret (masked).
Command observability shows gostructor's focused resolution trace: a summary line, the primary source, the defaults count, and an Overrides & Secrets section listing only the fields that were overridden away from the primary config or that are secret (masked).
priority command
Command priority shows how gostructor expresses priority: the SAME struct resolves DIFFERENTLY purely from the ORDER of sources passed to WithSources.
Command priority shows how gostructor expresses priority: the SAME struct resolves DIFFERENTLY purely from the ORDER of sources passed to WithSources.
types command
Command types shows the range of field types gostructor fills from a single structured source (a JSON file here): durations, slices and arrays, TextUnmarshaler types (time.Time, net.IP), named scalar types, pointers, maps, and slices/maps of structs, all with strict, lossless conversion.
Command types shows the range of field types gostructor fills from a single structured source (a JSON file here): durations, slices and arrays, TextUnmarshaler types (time.Time, net.IP), named scalar types, pointers, maps, and slices/maps of structs, all with strict, lossless conversion.
webservice command
Command webservice is a production-style microservice configuration assembled from three layers:
Command webservice is a production-style microservice configuration assembled from three layers:
internal
convert
Package convert turns the loosely-typed values a config source parses (strings, float64s from JSON, []interface{} from YAML) into the concrete Go types of the struct fields gostructor is filling.
Package convert turns the loosely-typed values a config source parses (strings, float64s from JSON, []interface{} from YAML) into the concrete Go types of the struct fields gostructor is filling.
format/ini
Package ini is a small, hand-written INI parser covering the practical subset gostructor needs: `[section]` headers, `key = value` or `key: value` pairs, `;`/`#` line comments, and quoted values.
Package ini is a small, hand-written INI parser covering the practical subset gostructor needs: `[section]` headers, `key = value` or `key: value` pairs, `;`/`#` line comments, and quoted values.
structplan
Package structplan builds and caches, per reflect.Type, the flattened list of settable fields on a struct, each with its config metadata already parsed from the two struct tags gostructor understands:
Package structplan builds and caches, per reflect.Type, the flattened list of settable fields on a struct, each with its config metadata already parsed from the two struct tags gostructor understands:
watch module

Jump to

Keyboard shortcuts

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