Activate sibling inheritance in configuration loading
This commit is contained in:
53
internal/engine/inheritance_load.go
Normal file
53
internal/engine/inheritance_load.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package engine
|
||||
|
||||
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||
|
||||
func containsInheritanceDirective(root *document.Node) bool {
|
||||
found := false
|
||||
walkDocument(root, Path{}, func(path Path, _ *document.Node) {
|
||||
if len(path.segments) != 0 && path.segments[len(path.segments)-1].kind == pathSegmentKey &&
|
||||
path.segments[len(path.segments)-1].key == inheritanceKey {
|
||||
found = true
|
||||
}
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
func inheritanceVersionDiagnostic(rule *VersionRule, expanded *inheritanceScope) *Diagnostic {
|
||||
if rule == nil || expanded == nil {
|
||||
return nil
|
||||
}
|
||||
for _, operation := range expanded.operations {
|
||||
var location Location
|
||||
var affected bool
|
||||
if operation.control != nil {
|
||||
affected = pathsIntersect(operation.control.path, rule.Path)
|
||||
if len(operation.control.locations) != 0 {
|
||||
location = operation.control.locations[0]
|
||||
}
|
||||
} else if operation.transfer != nil {
|
||||
if relative, within := relativeHistoryPath(operation.transfer.to, rule.Path); within {
|
||||
_, affected = documentAtPath(operation.parent, relative)
|
||||
} else if pathContains(rule.Path, operation.transfer.to) {
|
||||
affected = true
|
||||
}
|
||||
if len(operation.transfer.locations) != 0 {
|
||||
location = operation.transfer.locations[0]
|
||||
}
|
||||
}
|
||||
if affected {
|
||||
return &Diagnostic{
|
||||
Code: CodeInheritanceVersion, Severity: SeverityError, Path: rule.Path.clone(),
|
||||
Message: "inheritance cannot change the schema-version path", Primary: &location,
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func inheritanceOperations(expanded *inheritanceScope) []inheritanceOperation {
|
||||
if expanded == nil {
|
||||
return nil
|
||||
}
|
||||
return expanded.operations
|
||||
}
|
||||
@@ -195,6 +195,25 @@ func executeLoadRequest[T any](request loadRequest[T], secrets SecretBinding, lo
|
||||
mergeResourceContexts(&input.contexts, contexts)
|
||||
}
|
||||
}
|
||||
if input.document != nil && containsInheritanceDirective(input.document) {
|
||||
composition, problems := compositionForInput(input)
|
||||
if len(problems) != 0 {
|
||||
accumulator.add(problems...)
|
||||
return nil, accumulator.loadError()
|
||||
}
|
||||
expanded, problems := expandInheritanceWithOrigins(composition.root, composition.origins, defaultProjectionLimits())
|
||||
if len(problems) != 0 {
|
||||
accumulator.add(problems...)
|
||||
return nil, accumulator.loadError()
|
||||
}
|
||||
if problem := inheritanceVersionDiagnostic(request.definition.Version, expanded); problem != nil {
|
||||
accumulator.add(*problem)
|
||||
return nil, accumulator.loadError()
|
||||
}
|
||||
input.composition = composition
|
||||
input.inheritance = expanded
|
||||
input.document = expanded.root
|
||||
}
|
||||
|
||||
decoded, diagnostics := decodeLoadInput(schema, request.definition, input)
|
||||
accumulator.add(diagnostics...)
|
||||
@@ -206,7 +225,7 @@ func executeLoadRequest[T any](request loadRequest[T], secrets SecretBinding, lo
|
||||
if hasErrors(diagnostics) {
|
||||
return nil, accumulator.loadError()
|
||||
}
|
||||
if !historyNodesWithinLimit(decoded.baseline, decoded.final, resolved.final, resolved.applications, resolved.snapshots) {
|
||||
if !historyNodesWithinLimit(decoded.baseline, decoded.final, resolved.final, resolved.applications, resolved.snapshots, input.inheritance) {
|
||||
accumulator.add(Diagnostic{Code: CodeExplainLimitExceeded, Severity: SeverityError, Message: "retained configuration history exceeds the node limit"})
|
||||
return nil, accumulator.loadError()
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ type assembledInput struct {
|
||||
parsed parsedSource
|
||||
document *document.Node
|
||||
composition *composedDocument
|
||||
inheritance *inheritanceScope
|
||||
sources []SourceInfo
|
||||
contexts map[string]ResourceContext
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ type resolvedLoad[T any] struct {
|
||||
final *document.Node
|
||||
snapshots []resolutionSnapshot
|
||||
applications []historyApplication
|
||||
inheritance *inheritanceScope
|
||||
contexts map[string]ResourceContext
|
||||
}
|
||||
|
||||
@@ -35,7 +36,9 @@ func decodeLoadInput[T any](schema *schemaDescriptor, definition Definition[T],
|
||||
}
|
||||
return decodedConfiguration{}, diagnostics
|
||||
}
|
||||
if input.composition != nil {
|
||||
if input.inheritance != nil {
|
||||
applyCompositionOrigins(&decoded, input.inheritance.origins)
|
||||
} else if input.composition != nil {
|
||||
applyCompositionOrigins(&decoded, input.composition.origins)
|
||||
}
|
||||
|
||||
@@ -46,7 +49,9 @@ func resolveLoadInput[T any](schema *schemaDescriptor, definition Definition[T],
|
||||
var diagnostics []Diagnostic
|
||||
configuration := decoded.value.Interface().(T)
|
||||
metadata := metadataFromDecoded(decoded)
|
||||
if input.composition != nil {
|
||||
if input.inheritance != nil {
|
||||
applyInheritanceMetadata(&metadata, input.composition.root, input.composition.origins, input.composition.overlays, input.inheritance)
|
||||
} else if input.composition != nil {
|
||||
applyOverlayMetadata(&metadata, input.composition.overlays)
|
||||
}
|
||||
final := decoded.final
|
||||
@@ -68,7 +73,7 @@ func resolveLoadInput[T any](schema *schemaDescriptor, definition Definition[T],
|
||||
} else if input.document != nil {
|
||||
applications = []historyApplication{{layer: LayerBase, value: input.document}}
|
||||
}
|
||||
return resolvedLoad[T]{configuration: configuration, decoded: decoded, metadata: metadata, final: final, snapshots: resolutionSnapshots, applications: applications, contexts: input.contexts}, diagnostics
|
||||
return resolvedLoad[T]{configuration: configuration, decoded: decoded, metadata: metadata, final: final, snapshots: resolutionSnapshots, applications: applications, inheritance: input.inheritance, contexts: input.contexts}, diagnostics
|
||||
}
|
||||
|
||||
func detachLoadConfiguration[T any](schema *schemaDescriptor, configuration T, mode secretCloneMode) (T, *Diagnostic) {
|
||||
@@ -104,6 +109,7 @@ func publishLoadResult[T any](schema *schemaDescriptor, definition Definition[T]
|
||||
final: final,
|
||||
resolution: resolutionSnapshots,
|
||||
applications: applications,
|
||||
inheritance: inheritanceOperations(resolved.inheritance),
|
||||
secrets: secrets,
|
||||
secretReferences: cloneSecretReferences(references),
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ func (m Metadata) Lookup(path Path) (FieldInfo, bool) {
|
||||
return detached, true
|
||||
}
|
||||
|
||||
// IsExplicit reports whether a surviving input contribution explicitly defined
|
||||
// path.
|
||||
// IsExplicit reports whether a surviving input contribution, including one
|
||||
// inherited from a sibling, explicitly defined path.
|
||||
func (m Metadata) IsExplicit(path Path) bool {
|
||||
info, ok := m.Lookup(path)
|
||||
return ok && info.Explicit
|
||||
|
||||
@@ -41,7 +41,7 @@ type resolutionResult struct {
|
||||
diagnostics []Diagnostic
|
||||
}
|
||||
|
||||
// IsExplicit reports whether surviving input explicitly defined path.
|
||||
// IsExplicit reports whether surviving direct or inherited input defined path.
|
||||
func (r *Resolution) IsExplicit(path Path) bool {
|
||||
state := r.activeState()
|
||||
if !state.validPath(path) {
|
||||
|
||||
Reference in New Issue
Block a user