311 lines
12 KiB
Go
311 lines
12 KiB
Go
package engine
|
|
|
|
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
|
|
|
func projectInheritanceCollectionPolicies(scope collectionPolicyScope, prefix Path) collectionPolicyScope {
|
|
projected := collectionPolicyScope{}
|
|
for _, entry := range scope.collections {
|
|
policy := cloneCollectionPolicyForInheritance(entry.policy)
|
|
stripInheritanceModes(&policy)
|
|
projected.set(appendDestination(prefix, entry.destination), policy)
|
|
}
|
|
return projected
|
|
}
|
|
|
|
func projectCurrentInheritanceCollectionPolicies(scope collectionPolicyScope, prefix Path) collectionPolicyScope {
|
|
projected := collectionPolicyScope{}
|
|
for _, entry := range scope.collections {
|
|
projected.set(appendDestination(prefix, entry.destination), cloneCollectionPolicyForInheritance(entry.policy))
|
|
}
|
|
return projected
|
|
}
|
|
|
|
func projectInheritancePolicyRoots(target *collectionPolicyScope, scope collectionPolicyScope, prefix Path) {
|
|
for _, entry := range scope.collections {
|
|
target.set(appendDestination(prefix, entry.destination), cloneCollectionPolicyForInheritance(entry.policy))
|
|
}
|
|
}
|
|
|
|
func stripInheritanceModes(policy *collectionPolicy) {
|
|
if policy == nil {
|
|
return
|
|
}
|
|
policy.explicitMode = 0
|
|
policy.explicitModePresent = false
|
|
policy.modeLocation = nil
|
|
for owner, nested := range policy.recordPolicies {
|
|
for key, entry := range nested.collections {
|
|
stripInheritanceModes(&entry.policy)
|
|
nested.collections[key] = entry
|
|
}
|
|
policy.recordPolicies[owner] = nested
|
|
}
|
|
}
|
|
|
|
func inheritanceDeclarationsAt(path Path, policy collectionPolicy) []sequenceDeclarationSpec {
|
|
if !policy.identityPresent && !policy.explicitModePresent {
|
|
return nil
|
|
}
|
|
return []sequenceDeclarationSpec{{
|
|
destination: path.clone(),
|
|
identityFields: cloneStrings(policy.identityFields), identityPresent: policy.identityPresent,
|
|
identityLocation: cloneLocation(policy.identityLocation),
|
|
mode: policy.explicitMode, modePresent: policy.explicitModePresent,
|
|
modeLocation: cloneLocation(policy.modeLocation),
|
|
}}
|
|
}
|
|
|
|
func inheritanceCollectionResolution(
|
|
path Path,
|
|
lowerScope, higherScope collectionPolicyScope,
|
|
applicationPolicy sequencePolicy,
|
|
incoming *document.Node,
|
|
) (collectionPolicyResolution, collectionPolicy, collectionPolicy, bool, []Diagnostic) {
|
|
lower, hasLower := lowerScope.at(path)
|
|
higher, hasHigher := higherScope.at(path)
|
|
applicationIdentity, registered := applicationPolicy.identityFieldsAt(path)
|
|
if !hasLower && !hasHigher && !registered {
|
|
return collectionPolicyResolution{}, collectionPolicy{}, collectionPolicy{}, false, nil
|
|
}
|
|
lowerForResolution := cloneCollectionPolicy(lower)
|
|
// The parent policy is copied to a different sibling destination. Its YAML
|
|
// identity follows the inherited collection, but its explicit mode does not.
|
|
lowerForResolution.explicitMode = 0
|
|
lowerForResolution.explicitModePresent = false
|
|
lowerForResolution.modeLocation = nil
|
|
if incoming != nil && higher.explicitModePresent && higher.explicitMode == sequenceModeReplace {
|
|
// Actual replacement discards the parent's YAML identity before
|
|
// agreement. A mode-only declaration still governs retained records.
|
|
lowerForResolution = collectionPolicy{}
|
|
}
|
|
var lowerPointer *collectionPolicy
|
|
if hasLower {
|
|
lowerPointer = &lowerForResolution
|
|
}
|
|
resolution, diagnostics := resolveCollectionPolicy(
|
|
path, inheritanceDeclarationsAt(path, higher), applicationIdentity, lowerPointer,
|
|
)
|
|
return resolution, lower, higher, true, diagnostics
|
|
}
|
|
|
|
func mergeInheritanceCollectionPolicies(
|
|
parent, child collectionPolicyScope,
|
|
parentRootPath Path,
|
|
parentValue, childValue *document.Node,
|
|
applicationPolicy sequencePolicy,
|
|
replaced collectionReplacementPaths,
|
|
) (collectionPolicyScope, []Diagnostic) {
|
|
result := cloneCollectionPolicyScopeForInheritance(parent)
|
|
paths := make(map[string]Path, len(parent.collections)+len(child.collections))
|
|
for key, entry := range parent.collections {
|
|
paths[key] = entry.destination.clone()
|
|
}
|
|
for key, entry := range child.collections {
|
|
paths[key] = entry.destination.clone()
|
|
}
|
|
var lookup *documentLookup
|
|
if len(paths) != 0 {
|
|
lookup = buildDocumentLookup(parentValue, childValue)
|
|
}
|
|
var diagnostics []Diagnostic
|
|
for _, path := range paths {
|
|
relative, inside := relativeHistoryPath(parentRootPath, path)
|
|
var retained, incoming *document.Node
|
|
if inside {
|
|
if !replaced.contains(path, false) {
|
|
retained, _ = documentAtPathWithLookup(parentValue, relative, lookup)
|
|
}
|
|
incoming, _ = documentAtPathWithLookup(childValue, relative, lookup)
|
|
}
|
|
resolution, lower, _, active, issues := inheritanceCollectionResolution(path, parent, child, applicationPolicy, incoming)
|
|
if len(issues) != 0 {
|
|
diagnostics = append(diagnostics, issues...)
|
|
continue
|
|
}
|
|
if !active {
|
|
continue
|
|
}
|
|
var lowerPointer *collectionPolicy
|
|
if _, found := parent.at(path); found {
|
|
lower.explicitMode = 0
|
|
lower.explicitModePresent = false
|
|
lower.modeLocation = nil
|
|
lowerPointer = &lower
|
|
}
|
|
policy := resolution.policy
|
|
if incoming == nil || resolution.operation != sequenceModeReplace {
|
|
var foundDiagnostics []Diagnostic
|
|
policy, foundDiagnostics = collectionPolicyWithoutValue(lowerPointer, resolution.policy, retained, path)
|
|
if len(foundDiagnostics) != 0 {
|
|
diagnostics = append(diagnostics, foundDiagnostics...)
|
|
continue
|
|
}
|
|
}
|
|
if higher, ok := child.at(path); ok {
|
|
policy = inheritanceRecordPoliciesFromHigher(policy, higher)
|
|
}
|
|
result.set(path, policy)
|
|
}
|
|
return result, canonicalDiagnostics(diagnostics)
|
|
}
|
|
|
|
func inheritanceRecordPoliciesFromHigher(policy, higher collectionPolicy) collectionPolicy {
|
|
if len(higher.recordOrder) == 0 {
|
|
return policy
|
|
}
|
|
policy.recordOrder = nil
|
|
policy.recordPolicies = nil
|
|
for index := range higher.recordOrder {
|
|
if record, found := higher.recordPolicyAt(index); found {
|
|
policy.setRecordPolicyAt(index, currentCollectionRecordPolicy(record))
|
|
}
|
|
}
|
|
return policy
|
|
}
|
|
|
|
func inheritedCollectionRecordPolicy(record collectionRecordPolicy) collectionRecordPolicy {
|
|
cloned := cloneCollectionPolicyForInheritance(collectionPolicy{
|
|
recordOrder: []*collectionRecordOwner{record.owner},
|
|
recordPolicies: map[*collectionRecordOwner]collectionPolicyScope{record.owner: record.scope},
|
|
})
|
|
if len(cloned.recordOrder) == 0 {
|
|
return newCollectionRecordPolicy()
|
|
}
|
|
owner := cloned.recordOrder[0]
|
|
scope := cloned.recordPolicies[owner]
|
|
for key, entry := range scope.collections {
|
|
stripInheritanceModes(&entry.policy)
|
|
scope.collections[key] = entry
|
|
}
|
|
return collectionRecordPolicy{owner: owner, scope: scope}
|
|
}
|
|
|
|
func currentCollectionRecordPolicy(record collectionRecordPolicy) collectionRecordPolicy {
|
|
cloned := cloneCollectionPolicyForInheritance(collectionPolicy{
|
|
recordOrder: []*collectionRecordOwner{record.owner},
|
|
recordPolicies: map[*collectionRecordOwner]collectionPolicyScope{record.owner: record.scope},
|
|
})
|
|
if len(cloned.recordOrder) == 0 {
|
|
return newCollectionRecordPolicy()
|
|
}
|
|
owner := cloned.recordOrder[0]
|
|
return collectionRecordPolicy{owner: owner, scope: cloned.recordPolicies[owner]}
|
|
}
|
|
|
|
func applyInheritanceSequencePolicy(
|
|
path Path,
|
|
combined sequenceCombinationResult,
|
|
resolution collectionPolicyResolution,
|
|
lowerScope, higherScope collectionPolicyScope,
|
|
) collectionPolicy {
|
|
policy := cloneCollectionPolicyForInheritance(resolution.policy)
|
|
policy.recordOrder = nil
|
|
policy.recordPolicies = nil
|
|
if combined.value == nil || combined.value.Kind != document.Sequence {
|
|
return policy
|
|
}
|
|
|
|
lowerPolicy, hasLower := lowerScope.at(path)
|
|
higherPolicy, hasHigher := higherScope.at(path)
|
|
lowerProjection := newSequenceItemProjectionIndex(combined.lowerItems)
|
|
higherProjection := newSequenceItemProjectionIndex(combined.higherItems)
|
|
for outputIndex := range combined.value.Sequence {
|
|
if higherIndex, exists := higherProjection.inputForOutput(outputIndex); exists {
|
|
if record, found := higherPolicy.recordPolicyAt(higherIndex); hasHigher && found {
|
|
policy.setRecordPolicyAt(outputIndex, currentCollectionRecordPolicy(record))
|
|
} else {
|
|
policy.setRecordPolicyAt(outputIndex, replacedCollectionRecordPolicy())
|
|
}
|
|
continue
|
|
}
|
|
if lowerIndex, exists := lowerProjection.inputForOutput(outputIndex); exists && hasLower {
|
|
if record, found := lowerPolicy.recordPolicyAt(lowerIndex); found {
|
|
policy.setRecordPolicyAt(outputIndex, inheritedCollectionRecordPolicy(record))
|
|
}
|
|
}
|
|
}
|
|
return policy
|
|
}
|
|
|
|
func applyInheritanceSequenceOrigins(
|
|
origins, incomingOrigins map[string]Origin,
|
|
path Path,
|
|
lower, higher *document.Node,
|
|
combined sequenceCombinationResult,
|
|
operation sequenceMode,
|
|
) []inheritanceReplacement {
|
|
var replacements []inheritanceReplacement
|
|
if operation == sequenceModeReplace {
|
|
if lower != nil {
|
|
displaced := displacedOrigins(lower, origins, path)
|
|
replacements = append(replacements, inheritanceReplacement{
|
|
path: path.clone(), before: lower, after: higher,
|
|
previous: displaced, effective: cloneOrigin(incomingOrigins[path.String()]),
|
|
})
|
|
deleteOriginSubtree(origins, path, lower)
|
|
}
|
|
mergeInheritanceOrigin(origins, path, incomingOrigins[path.String()])
|
|
if higher != nil && higher.Kind != document.Sequence {
|
|
copyInheritanceSubtree(origins, incomingOrigins, higher, path)
|
|
}
|
|
for _, projection := range combined.higherItems {
|
|
if higher != nil && higher.Kind == document.Sequence && projection.inputIndex >= 0 && projection.inputIndex < len(higher.Sequence) {
|
|
copyInheritanceSubtreeAt(origins, incomingOrigins, higher.Sequence[projection.inputIndex], path.Index(projection.inputIndex), path.Index(projection.outputIndex))
|
|
}
|
|
}
|
|
return replacements
|
|
}
|
|
|
|
mergeInheritanceOrigin(origins, path, incomingOrigins[path.String()])
|
|
for _, replacement := range combined.replacements {
|
|
if lower == nil || lower.Kind != document.Sequence || replacement.lowerIndex < 0 || replacement.lowerIndex >= len(lower.Sequence) ||
|
|
higher == nil || higher.Kind != document.Sequence || replacement.higherIndex < 0 || replacement.higherIndex >= len(higher.Sequence) {
|
|
continue
|
|
}
|
|
lowerPath := path.Index(replacement.lowerIndex)
|
|
higherPath := path.Index(replacement.higherIndex)
|
|
outputPath := path.Index(replacement.outputIndex)
|
|
previous := displacedOrigins(lower.Sequence[replacement.lowerIndex], origins, lowerPath)
|
|
deleteOriginSubtree(origins, lowerPath, lower.Sequence[replacement.lowerIndex])
|
|
copyInheritanceSubtreeAt(origins, incomingOrigins, higher.Sequence[replacement.higherIndex], higherPath, outputPath)
|
|
replacements = append(replacements, inheritanceReplacement{
|
|
path: outputPath, before: lower.Sequence[replacement.lowerIndex], after: higher.Sequence[replacement.higherIndex],
|
|
previous: previous, effective: cloneOrigin(incomingOrigins[higherPath.String()]),
|
|
})
|
|
}
|
|
for _, projection := range combined.higherItems {
|
|
if higher != nil && higher.Kind == document.Sequence && projection.inputIndex >= 0 && projection.inputIndex < len(higher.Sequence) {
|
|
copyInheritanceSubtreeAt(origins, incomingOrigins, higher.Sequence[projection.inputIndex], path.Index(projection.inputIndex), path.Index(projection.outputIndex))
|
|
}
|
|
}
|
|
return replacements
|
|
}
|
|
|
|
func validateRegisteredInheritanceSequences(root *document.Node, rootPath Path, applicationPolicy sequencePolicy) []Diagnostic {
|
|
var diagnostics []Diagnostic
|
|
var visit func(*document.Node, Path)
|
|
visit = func(node *document.Node, path Path) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
if node.Kind == document.Sequence {
|
|
if fields, registered := applicationPolicy.identityFieldsAt(path); registered {
|
|
if _, issues := validateSequenceIdentity(node, path, fields, sequenceCombinationIdentityCodes); len(issues) != 0 {
|
|
diagnostics = append(diagnostics, issues...)
|
|
}
|
|
}
|
|
for index, item := range node.Sequence {
|
|
visit(item, path.Index(index))
|
|
}
|
|
return
|
|
}
|
|
if node.Kind == document.Mapping {
|
|
for _, entry := range node.Mapping {
|
|
visit(entry.Value, path.Key(entry.Key))
|
|
}
|
|
}
|
|
}
|
|
visit(root, rootPath)
|
|
return canonicalDiagnostics(diagnostics)
|
|
}
|