sxcli.dev/conf/engine
import "sxcli.dev/conf/engine"
Package engine implements the configuration machinery of the sxcli framework: schema extraction from tagged config structs, the lenient and strict argument parsers, environment lookup, config file discovery and transcoding, and source merging with in-place struct filling. Like the other internal packages it is framework-ignorant: config structs arrive as named Sections built by the caller, and format providers arrive through a structural interface that the root package's ConfigFormatProvider satisfies implicitly.
Index#
- Constants
- func HasPositionals(cfgPtr reflect.Type) bool
- func OpenPinned(path string) (io.ReadCloser, error)
- func ParseFromVersions(c *fail.Collector, entries []string) (map[string]uint32, *uint32)
- func PeekCore(c *fail.Collector, appletID string, src Sources, core []Contribution)
- func ProbeFields(cfgPtr any) map[string]ProbedField
- func ProbeType(cfgPtr reflect.Type) map[string]ProbedField
- func StatRegular(path string) (int64, error)
- func ValidateConfigType(id string, cfgPtr reflect.Type) error
- type Contribution
- type Core
- type Field
- type FieldMeta
- type Files
- type HelpSection
- type Loaded
- type Location
- type Meta
- type ProbedField
- type Provider
- type Schema
- func NewSchema(c *fail.Collector, appletID string, core []Contribution, sections []Section, suppress []string) *Schema
- func (s *Schema) Apply(c *fail.Collector, files *Files, src Sources) Loaded
- func (s *Schema) HelpSections() []HelpSection
- func (s *Schema) MarshalIndent() ([]byte, error)
- func (s *Schema) PositionalFields() ([]*Field, *Field)
- func (s *Schema) UpgradeFile(c *fail.Collector, path string, from map[string]uint32, bare *uint32, src Sources)
- func (s *Schema) Value(f *Field) any
- func (s *Schema) WriteHelp(w io.Writer)
- func (s *Schema) WriteMerged(stdout io.Writer, target string, src Sources) error
- type Section
- type Sources
- type Step
- type ValueHint
Constants#
CoreID is the reserved service id of the framework core: the name of its config section, of the virtual root the resolver expands from, and of the synthesized introspection entry. The root package derives its reserved-id constant from this one.
const CoreID = "core"
DefaultMaxSize is the config file size cap applied when Sources does not set one: 1 MiB covers any sane configuration.
const DefaultMaxSize = 1 << 20
func HasPositionalssrc#
func HasPositionals(cfgPtr reflect.Type) bool
HasPositionals reports whether a config struct TYPE declares any pos-tagged field — the commit-time probe behind "positionals are applet-only".
func OpenPinnedsrc#
func OpenPinned(path string) (io.ReadCloser, error)
OpenPinned opens a binary-companion config file refusing a symlink at the final path component (O_NOFOLLOW, enforced atomically by the kernel — no check-then-open race): the companion must be a regular file that really lives next to the real binary.
func ParseFromVersionssrc#
func ParseFromVersions(c *fail.Collector, entries []string) (map[string]uint32, *uint32)
ParseFromVersions parses --from-version entries: repeatable "section=N" pairs plus at most one bare "N" (the single-section sugar). Violations land in c; both doors share this grammar.
func PeekCoresrc#
func PeekCore(c *fail.Collector, appletID string, src Sources, core []Contribution)
PeekCore is the first pipeline pass: it leniently fills the composite core from environment and arguments only — no file can be located before --config is known. File-sourced core values arrive later via Files.ApplyCore. The caller owns the contribution structs and must hand over pristine ones.
func ProbeFieldssrc#
func ProbeFields(cfgPtr any) map[string]ProbedField
ProbeFields returns the settable fields of a config struct keyed by go field name ("A.B" for nested), for registration-time metadata validation. Extraction violations are ignored here; ValidateConfigType reports them.
func ProbeTypesrc#
func ProbeType(cfgPtr reflect.Type) map[string]ProbedField
ProbeType is ProbeFields without an instance: the settable fields of a config struct TYPE (a pointer-to-struct type), for type-level metadata validation at the registration commit, where no instance exists yet. Probed Values are zero; the value-level default-in-domain check runs later, at Build, over ProbeFields of the real instance.
func StatRegularsrc#
func StatRegular(path string) (int64, error)
StatRegular probes one config source: a config file must resolve to a regular file. os.Stat follows symlinks, so a symlink to a regular file passes (symlink-overlay distros keep working) — it is the resolved target that must be regular. FIFOs are refused here, before any open could block on them; devices and directories get a clean startup error instead of downstream read weirdness.
func ValidateConfigTypesrc#
func ValidateConfigType(id string, cfgPtr reflect.Type) error
ValidateConfigType validates the tags and field types of a config struct TYPE (a pointer-to-struct type) at the registration commit, where no instance exists yet — including the Version mandate: the version key must exist in files BEFORE any migration needs it, so it is required from a schema's first release. (The factory default, an instance-level fact, is checked when the schema is built.)
type Contributionsrc#
Contribution is one flat struct claiming keys under the composite core section. The section name is fixed — that absence of a name is what distinguishes it from a Section. Contributors share the "core" namespace in every source; a json key claimed twice is a violation.
type Contribution struct { Ptr any // pointer to the struct; nil contributes nothing Meta *Meta // its own field metadata, nil when none declared }
func CoreContribsrc#
func CoreContrib(core *Core) Contribution
CoreContrib pairs the engine's own Core with its metadata: the first contribution of every composite core, by convention — first so its short forms win.
type Coresrc#
Core is the engine's own configuration — the machinery knobs only, living under the reserved section name "core". All three are run-scoped (dump:"-"): excluded from --write-config output AND refused loudly from config files — a file setting them would be self-triggering (every run becoming help output, or a config write to an attacker-chosen path). writeConfig and help additionally carry env:"-": an inherited APPLETID_HELP=true would be the same persistent denial — they are argument-only. config keeps its env door (a legitimate deployment pattern; the pointed-at file still passes every gate). Anything else under "core" — the framework's service controls, say — arrives as a further Contribution.
type Core struct { Config string `json:"config" conf:"config,c" dump:"-" usage:"path of the configuration file, replaces the location search"` WriteConfig bool `json:"writeConfig" conf:"write-config" dump:"-" env:"-" usage:"write the merged configuration to the --config target (or stdout) and exit"` Help bool `json:"help" conf:"help,h" dump:"-" env:"-" usage:"print the applet's argument schema and exit"` ValidateConfig bool `json:"validateConfig" conf:"validate-config" dump:"-" env:"-" usage:"run every configuration check, report the violations and exit"` }
type Fieldsrc#
Field is one settable config struct field.
type Field struct { ServiceID string Name string // go field name path for error messages, e.g. "Rotation.MaxAge" Path []int // reflect index path into the config struct JSONPath []string // json object path inside the service's section Long string // long argument name; "" = file-only Short string // single-character short form; "" = none EnvName string // resolved environment variable name; "" = not env-settable NoEnv bool // env:"-": no environment variable, not even derived Usage string Type reflect.Type IsSlice bool Transient bool // dump:"-": run-scoped — excluded from --write-config output AND refused from config files Allowed []any // closed value domain from registration metadata; values are of the field's type (element type for slices) Doc string Hint ValueHint // advisory value denotation from registration metadata // contains filtered or unexported fields }
type FieldMetasrc#
FieldMeta annotates one config field. Allowed values are already converted to the field's own type.
type FieldMeta struct { Allowed []any Doc string Hint ValueHint }
type Filessrc#
Files is the parsed content of every loaded config file: one service-id → raw section map per file, in merge order (later files override earlier ones), plus the providers that transcoded them.
type Files struct { Used []Provider // contains filtered or unexported fields }
func LoadFilessrc#
func LoadFiles(c *fail.Collector, src Sources, explicit string) *Files
LoadFiles discovers, transcodes and parses the configuration files of one invocation. explicit is the resolved --config path: when non-empty it replaces the location search entirely and must exist. Otherwise every base path in src.Locations is probed with ".json" plus every registered provider extension; more than one existing candidate at the same location is ambiguous and a startup violation, as is a file whose extension no provider handles.
Existence and size are probed via Stat before any file is opened: an oversized config is never opened, read or parsed.
func (*Files) ApplyCoresrc#
func (f *Files) ApplyCore(c *fail.Collector, appletID string, src Sources, core []Contribution)
ApplyCore fills the composite core in full precedence order once the files are loaded: file sections, then environment, then arguments. These are the values the closure resolution must use — a control list in a config file is only visible here. The caller owns the contribution structs and must hand over pristine ones (slice values already filled by a peek would double up).
type HelpSectionsrc#
HelpSection is one service's schema for help rendering.
type HelpSection struct { ID string Fields []*Field }
type Loadedsrc#
Loaded is the outcome of a strict Schema.Apply.
type Loaded struct { // Positionals holds the raw trailing tokens ONLY when the active // config declares no pos fields; any declaration makes the schema // own the tail entirely (assignment, counting, violations). Positionals []string }
type Locationsrc#
Location is one config file search location: a base path without extension. A pinned location is security-sensitive — the binary companion — and its candidates are opened through Sources.OpenPinned, which must refuse symlinks so the file really lives at Base's directory.
type Location struct { Base string Pinned bool }
func CompanionLocationsrc#
func CompanionLocation(name string) (Location, bool)
CompanionLocation returns the pinned binary-companion location, or false when the real binary path cannot be resolved.
func ProductionLocationssrc#
func ProductionLocations(name string) []Location
ProductionLocations returns the full config search of one name: companion, system, user, in merge order. Callers with tier policy (the front door's Suppress) compose the tier constructors instead.
func SystemLocationsrc#
func SystemLocation(name string) Location
SystemLocation returns the system-wide location (/etc on unix, %ProgramData% on windows).
func UserLocationsrc#
func UserLocation(name string) (Location, bool)
UserLocation returns the per-user location (the XDG config dir), or false when it cannot be resolved.
type Metasrc#
Meta is the internal, normalized form of a service's registration metadata (the root package's Metadata, validated and converted by its metadata check).
type Meta struct { Description string Fields map[string]FieldMeta // keyed by go field name, "A.B" for nested }
type ProbedFieldsrc#
ProbedField describes one settable config field for registration-time metadata validation: its type (element type for slices), slice-ness and current (default) value.
type ProbedField struct { Type reflect.Type IsSlice bool Value reflect.Value }
type Providersrc#
Provider is the structural twin of the root package's ConfigFormatProvider, redeclared here to avoid an import cycle; root provider instances satisfy it without adaptation.
type Provider interface { Extensions() []string ToJSON(in io.Reader) (io.Reader, error) FromJSON(in io.Reader) (io.Reader, error) }
type Schemasrc#
Schema is the full argument/env/file schema of one invocation: the core plus every closure member owning a config struct.
type Schema struct { // contains filtered or unexported fields }
func NewSchemasrc#
func NewSchema(c *fail.Collector, appletID string, core []Contribution, sections []Section, suppress []string) *Schema
NewSchema builds the full schema of one invocation: the core config first (so its short forms win), then every member owning a config struct. Core fields whose long name appears in suppress are removed from the schema entirely: the argument becomes unknown, the env var is never consulted, and the file key turns into an unknown-key violation. Duplicate long argument names and duplicate explicit env names across the schema are violations; short-form collisions are resolved first-come-first-served.
func (*Schema) Applysrc#
func (s *Schema) Apply(c *fail.Collector, files *Files, src Sources) Loaded
Apply is the strict pipeline pass over the full schema: files, then environment, then arguments, unknown argument = violation. It fills every member's config struct in place and returns the trailing positionals.
func (*Schema) HelpSectionssrc#
func (s *Schema) HelpSections() []HelpSection
HelpSections returns the schema's services and their fields for help rendering, the core first.
func (*Schema) MarshalIndentsrc#
func (s *Schema) MarshalIndent() ([]byte, error)
MarshalIndent serializes the merged configuration of every schema member — the exact values the config structs hold — as the core's native JSON, service sections keyed by id. Empty values (zero scalars, empty slices) are skipped, and sections or nested objects they would leave empty are omitted entirely, so a default-heavy configuration dumps small. Consequence, documented: a field explicitly set to its zero value is indistinguishable from an unset one and falls back to its default when the dump is loaded.
func (*Schema) PositionalFieldssrc#
func (s *Schema) PositionalFields() ([]*Field, *Field)
PositionalFields exposes the active config's declared positionals: the indexed fields in order, and the trailing collector (nil when absent) — what help renderers and tooling enumerate.
func (*Schema) UpgradeFilesrc#
func (s *Schema) UpgradeFile(c *fail.Collector, path string, from map[string]uint32, bare *uint32, src Sources)
UpgradeFile is the pure file transform behind --upgrade-config: it reads ONE file, migrates every schema-owned section to its current version, and writes the file back in its own format. No merge, no other sources, no defaults injection. Sections the schema does not own — a shared file serves other tools — pass through verbatim.
A section carrying a version key migrates by it; a contradicting from entry is an error. A versionless section requires its version asserted via from ("section" → N) or the bare assertion (legal only when exactly one versionless owned section exists). The assertion declares the section a COMPLETE version-N document — the tool warns which top-level keys were absent from the input, since the chain materializes them.
func (*Schema) Valuesrc#
func (s *Schema) Value(f *Field) any
Value returns one field's current (merged) value, rendered like the --write-config output: durations as unit-suffixed strings.
func (*Schema) WriteHelpsrc#
func (s *Schema) WriteHelp(w io.Writer)
WriteHelp renders the schema grouped by section: every argument with its short form, usage and environment name against the current (merged) values. Plain text — the framework renders its own help through its translation seam; this is the engine's untranslated canonical form.
func (*Schema) WriteMergedsrc#
func (s *Schema) WriteMerged(stdout io.Writer, target string, src Sources) error
WriteMerged serves --write-config: the merged configuration goes to stdout as json when target is empty, else to the target file in the format its extension names (a registered format provider transcodes the native json).
type Sectionsrc#
Section is one named contributor to a schema: a config struct under its operator-facing section name. The framework maps its accepted services to sections; a standalone caller builds them directly. The engine never sees anything richer — this is the whole seam.
type Section struct { Name string // section name: config-file key, env prefix Ptr any // pointer to the config struct; nil contributes nothing Meta *Meta // field metadata, nil when none declared Steps []Step // migration chain, oldest first; empty = never evolved }
type Sourcessrc#
Sources carries every external input of configuration loading, all injectable for hermetic tests.
type Sources struct { Args []string // argv without the binary name and applet selector LookupEnv func(string) (string, bool) // os.LookupEnv in production Locations []Location // search locations in merge order Stat func(string) (int64, error) // file size probe; missing files must report fs.ErrNotExist Lstat func(string) error // pinned-location cross-check: nil when something occupies the path ITSELF (e.g. a dangling symlink Stat cannot see) Open func(string) (io.ReadCloser, error) // os.Open in production; missing files must report fs.ErrNotExist OpenPinned func(string) (io.ReadCloser, error) // symlink-refusing opener (O_NOFOLLOW-style) for pinned locations Providers []Provider // registered format providers, registration order SuppressCore []string // long names of core fields the binary suppressed (fw.Suppress) MaxSize int64 // config file size cap in bytes; <=0 means the 1 MiB default }
func ProductionSourcessrc#
func ProductionSources(name string) Sources
ProductionSources assembles the real-world Sources of one name: os argument vector and environment, the standard search locations with the hardening stack (regular files only, pinned symlink refusal).
type Stepsrc#
Step is one erased link of a section's migration chain: it converts the config document of one schema version to the next. Build steps with NewStep (or the front door's Step) — the generic constructor is what keeps the conversion typed.
type Step struct { // contains filtered or unexported fields }
func NewStepsrc#
func NewStep[From, To any](from uint32, fn func(From) To) Step
NewStep declares the conversion from schema version `from` to the next: fn receives the strictly-parsed old document and returns its successor. The chain's link types are verified when the schema is built; the erased call can never mismatch at migration time.
type ValueHintsrc#
ValueHint is the advisory declaration of what a field's value denotes. Unlike Allowed it is never enforced — a hinted file may not exist yet (--config with --write-config creates it); it travels the schema so tooling (completion, documentation) can act on it. The root package re-exports the constants under the same names.
type ValueHint int
const ( HintNone ValueHint = iota HintFile HintDirectory HintServiceID )