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( 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) }