Apply collection policies during sibling inheritance
This commit is contained in:
293
internal/engine/inheritance_collection.go
Normal file
293
internal/engine/inheritance_collection.go
Normal file
@@ -0,0 +1,293 @@
|
||||
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,
|
||||
) (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
|
||||
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 *document.Node,
|
||||
applicationPolicy sequencePolicy,
|
||||
) (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 diagnostics []Diagnostic
|
||||
for _, path := range paths {
|
||||
resolution, lower, _, active, issues := inheritanceCollectionResolution(path, parent, child, applicationPolicy)
|
||||
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
|
||||
}
|
||||
relative, inside := relativeHistoryPath(parentRootPath, path)
|
||||
var retained *document.Node
|
||||
if inside {
|
||||
retained, _ = documentAtPath(parentValue, relative)
|
||||
}
|
||||
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,
|
||||
result *collectionPolicyScope,
|
||||
) collectionPolicy {
|
||||
policy := cloneCollectionPolicyForInheritance(resolution.policy)
|
||||
policy.recordOrder = nil
|
||||
policy.recordPolicies = nil
|
||||
if combined.value == nil || combined.value.Kind != document.Sequence {
|
||||
result.retireCollectionPoliciesAtOrBelow(path)
|
||||
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(
|
||||
result *inheritanceScope,
|
||||
origins, incomingOrigins map[string]Origin,
|
||||
path Path,
|
||||
lower, higher *document.Node,
|
||||
combined sequenceCombinationResult,
|
||||
operation sequenceMode,
|
||||
) {
|
||||
if operation == sequenceModeReplace {
|
||||
if lower != nil {
|
||||
displaced := displacedOrigins(lower, origins, path)
|
||||
result.replacements = append(result.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) {
|
||||
copyInheritanceSubtree(origins, incomingOrigins, higher.Sequence[projection.inputIndex], path.Index(projection.outputIndex))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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])
|
||||
copyInheritanceSubtree(origins, incomingOrigins, higher.Sequence[replacement.higherIndex], outputPath)
|
||||
result.replacements = append(result.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) {
|
||||
copyInheritanceSubtree(origins, incomingOrigins, higher.Sequence[projection.inputIndex], path.Index(projection.outputIndex))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user