Files
ConfigKit/internal/engine/inheritance_collection.go

294 lines
11 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,
) (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) {
copyInheritanceSubtreeAt(origins, incomingOrigins, higher.Sequence[projection.inputIndex], path.Index(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])
copyInheritanceSubtreeAt(origins, incomingOrigins, higher.Sequence[replacement.higherIndex], higherPath, 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) {
copyInheritanceSubtreeAt(origins, incomingOrigins, higher.Sequence[projection.inputIndex], path.Index(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)
}