97 lines
4.1 KiB
Go
97 lines
4.1 KiB
Go
package configkit
|
|
|
|
import "gitea.maximumdirect.net/eric/ConfigKit/internal/engine"
|
|
|
|
// Input selects one source, its application destination, whether its imports are
|
|
// enabled, and any explicit filesystem context. A zero Source is invalid; use
|
|
// NoSource() for an omitted main input. Sources keep their documented reuse and
|
|
// reader ownership rules.
|
|
type Input struct {
|
|
Source Source
|
|
// Destination establishes the mapping path for Source. Imports inherit this
|
|
// path or append a relative destination declared in YAML. Its zero value
|
|
// keeps root placement.
|
|
Destination Path
|
|
// PlacementMode defaults to mapping placement. Sequence-item placement wraps
|
|
// the source mapping as one record; unknown values and an empty destination
|
|
// fail option validation before acquisition.
|
|
PlacementMode PlacementMode
|
|
Imports bool
|
|
Filesystem *FilesystemContext
|
|
}
|
|
|
|
// Underlay contains inputs that must compose strictly within one tier. Tiers
|
|
// appear in lowest-to-highest precedence order; an empty tier is a no-op.
|
|
type Underlay struct{ Inputs []Input }
|
|
|
|
// LayeredOptions selects lower-priority tiers and final application rules.
|
|
// An active Overlay requires an explicit OverlayFilesystem; an inactive overlay
|
|
// ignores it. Overrides remain explicit CLI records, not general mappings.
|
|
// SourceVersionMode applies one declaration policy to main, underlay, import,
|
|
// and selected overlay documents. Enabled modes interpret the version path
|
|
// source-locally, consume accepted non-main declarations, and protect main's
|
|
// destination-prefixed path. The zero value preserves existing behavior; see
|
|
// the shared source-version contract in docs/config.md.
|
|
type LayeredOptions[T any] struct {
|
|
Definition Definition[T]
|
|
SourceVersionMode SourceVersionMode
|
|
Underlays []Underlay
|
|
Overlay string
|
|
OverlayFilesystem *FilesystemContext
|
|
Overrides []Override
|
|
}
|
|
|
|
// LoadLayered acquires the main input first and strictly composes enabled
|
|
// imports with each input's tier. It strictly composes contributions within
|
|
// each underlay, then recursively combines underlays in listed order beneath
|
|
// main, a selected overlay, and CLI overrides. Defaults, decoding, resolution,
|
|
// and validation run once after sibling `extends` expansion of the assembled
|
|
// input. Source IDs follow acquisition order, not precedence order. Zero or
|
|
// one options value is accepted; any error
|
|
// returns a nil result.
|
|
func LoadLayered[T any](main Input, supplied ...LayeredOptions[T]) (*Result[T], error) {
|
|
options := make([]engine.LayeredOptions[T], len(supplied))
|
|
for index := range supplied {
|
|
options[index] = toEngineLayeredOptions(supplied[index])
|
|
}
|
|
loaded, err := engine.LoadLayered(toEngineInput(main), secretBinding(), options...)
|
|
return fromEngineLoaded(loaded, err)
|
|
}
|
|
|
|
func toEngineInput(input Input) engine.Input {
|
|
result := engine.Input{
|
|
Source: input.Source.toEngine(), Imports: input.Imports,
|
|
Filesystem: toEngineFilesystemContext(input.Filesystem), Destination: toEnginePath(input.Destination),
|
|
}
|
|
return engine.InputWithPlacement(result, uint8(input.PlacementMode))
|
|
}
|
|
|
|
func toEngineLayeredOptions[T any](options LayeredOptions[T]) engine.LayeredOptions[T] {
|
|
result := engine.LayeredOptions[T]{
|
|
Definition: toEngineDefinition(options.Definition),
|
|
SourceVersionMode: engine.SourceVersionModeFromFacade(uint8(options.SourceVersionMode)),
|
|
Overlay: options.Overlay,
|
|
OverlayFilesystem: toEngineFilesystemContext(options.OverlayFilesystem),
|
|
}
|
|
if options.Underlays != nil {
|
|
result.Underlays = make([]engine.Underlay, len(options.Underlays))
|
|
for index, group := range options.Underlays {
|
|
if group.Inputs != nil {
|
|
result.Underlays[index].Inputs = make([]engine.Input, len(group.Inputs))
|
|
for inputIndex, input := range group.Inputs {
|
|
result.Underlays[index].Inputs[inputIndex] = toEngineInput(input)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if options.Overrides != nil {
|
|
result.Overrides = make([]engine.Override, len(options.Overrides))
|
|
for index, override := range options.Overrides {
|
|
result.Overrides[index] = engine.Override{
|
|
Path: toEnginePath(override.Path), Value: override.Value, Flag: override.Flag,
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|