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 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 while protecting main's destination-prefixed path. 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 { return engine.Input{ Source: input.Source.toEngine(), Imports: input.Imports, Filesystem: toEngineFilesystemContext(input.Filesystem), Destination: toEnginePath(input.Destination), } } 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 }