Files
ConfigKit/internal/engine/collection_retirement.go

112 lines
3.7 KiB
Go

package engine
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
// A true entry replaces the collection itself as well as its descendants;
// false records ordinary ancestor replacement without selecting a collection
// operation at that exact path.
type collectionReplacementPaths map[string]bool
type lowerCollectionPolicyView struct {
scope collectionPolicyScope
replacements collectionReplacementPaths
}
func (view lowerCollectionPolicyView) at(path Path) (collectionPolicy, bool) {
entry, found := view.scope.collections[path.String()]
if !found {
return collectionPolicy{}, false
}
policy := entry.policy
if view.replacements.contains(path, true) {
clearCollectionIdentity(&policy)
}
return policy, true
}
func (paths collectionReplacementPaths) contains(path Path, includeExact bool) bool {
for length := 0; length <= len(path.segments); length++ {
prefix := Path{segments: path.segments[:length]}
inclusive, found := paths[prefix.String()]
if found && (length < len(path.segments) || includeExact && inclusive) {
return true
}
}
return false
}
// lowerCollectionPoliciesForClaims makes replacement eligibility independent of
// declaration order. It removes only old YAML identity/record state; new pending
// declarations and application constraints are reconciled afterward.
func lowerCollectionPoliciesForClaims(
lower collectionPolicyScope,
claims []compositionClaim,
declarations map[string][]sequenceDeclarationSpec,
application sequencePolicy,
) (collectionPolicyScope, collectionReplacementPaths) {
replaced := collectionReplacementPathsForClaims(lower, claims, declarations, application)
// This materialization is used when inheritance creates an independently
// owned local scope. Strict tier resolution uses lowerCollectionPolicyView
// instead and keeps only its sparse current-scope result.
result := cloneCollectionPolicyScope(lower)
for key, entry := range result.collections {
if replaced.contains(entry.destination, true) {
clearCollectionIdentity(&entry.policy)
result.collections[key] = entry
}
}
return result, replaced
}
func collectionReplacementPathsForClaims(
lower collectionPolicyScope,
claims []compositionClaim,
declarations map[string][]sequenceDeclarationSpec,
application sequencePolicy,
) collectionReplacementPaths {
byPath := make(map[string]compositionClaim, len(claims))
rootPath := ""
if len(claims) != 0 {
rootPath = claims[0].path.String()
}
for _, claim := range claims {
byPath[claim.path.String()] = claim
}
replaced := make(collectionReplacementPaths)
for key, claim := range byPath {
old, hasOld := lower.collections[key]
current := declarations[key]
if claim.kind == document.Mapping && (key == rootPath || !hasOld && len(current) == 0) {
continue // Ordinary mapping combination preserves descendants.
}
mode := sequenceMode(0)
identity := old.policy.identityPresent
for _, declaration := range current {
identity = identity || declaration.identityPresent
if mode == 0 && declaration.modePresent {
mode = declaration.mode
}
}
retainedMode := sequenceMode(0)
if old.policy.explicitModePresent {
retainedMode = old.policy.explicitMode
}
_, registered := application.identityFieldsAt(claim.path)
mode = collectionOperationMode(mode, retainedMode, identity || registered)
if mode == sequenceModeReplace {
replaced[key] = true
} else if claim.kind != document.Mapping && claim.kind != document.Sequence {
replaced[key] = false
}
}
return replaced
}
func clearCollectionIdentity(policy *collectionPolicy) {
policy.identityFields = nil
policy.identityPresent = false
policy.identityLocation = nil
policy.recordOrder = nil
policy.recordPolicies = nil
}