package engine import ( "sort" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) type collectionPolicyScope struct { collections map[string]collectionPolicyEntry } type collectionPolicyEntry struct { destination Path policy collectionPolicy } type collectionRecordPolicy struct { owner *collectionRecordOwner scope collectionPolicyScope } // The nonzero field ensures simultaneously live record owners have distinct // addresses. The token is per-record ownership, not a source or identity key. type collectionRecordOwner struct { marker byte } type projectedCollectionPolicy struct { path Path policy collectionPolicy } func newCollectionRecordPolicy() collectionRecordPolicy { return collectionRecordPolicy{owner: &collectionRecordOwner{marker: 1}} } func retainedCollectionRecordPolicy(record collectionRecordPolicy) collectionRecordPolicy { return collectionRecordPolicy{ owner: record.owner, scope: cloneCollectionPolicyScope(record.scope), } } func replacedCollectionRecordPolicy() collectionRecordPolicy { return newCollectionRecordPolicy() } func (policy *collectionPolicy) setRecordPolicy(record collectionRecordPolicy) { if record.owner == nil { record.owner = newCollectionRecordPolicy().owner } if policy.recordPolicies == nil { policy.recordPolicies = make(map[*collectionRecordOwner]collectionPolicyScope) } if _, exists := policy.recordPolicies[record.owner]; !exists { policy.recordOrder = append(policy.recordOrder, record.owner) } policy.recordPolicies[record.owner] = cloneCollectionPolicyScope(record.scope) } func (policy *collectionPolicy) setRecordPolicyAt(index int, record collectionRecordPolicy) { if index < 0 { return } if record.owner == nil { record.owner = newCollectionRecordPolicy().owner } if policy.recordPolicies == nil { policy.recordPolicies = make(map[*collectionRecordOwner]collectionPolicyScope) } for len(policy.recordOrder) <= index { owner := newCollectionRecordPolicy().owner policy.recordOrder = append(policy.recordOrder, owner) policy.recordPolicies[owner] = collectionPolicyScope{} } delete(policy.recordPolicies, policy.recordOrder[index]) policy.recordOrder[index] = record.owner policy.recordPolicies[record.owner] = cloneCollectionPolicyScope(record.scope) } func (policy collectionPolicy) recordPolicyAt(index int) (collectionRecordPolicy, bool) { if index < 0 || index >= len(policy.recordOrder) { return collectionRecordPolicy{}, false } owner := policy.recordOrder[index] scope, ok := policy.recordPolicies[owner] if !ok { return collectionRecordPolicy{}, false } return collectionRecordPolicy{owner: owner, scope: cloneCollectionPolicyScope(scope)}, true } func collectionPolicyForReplacement(lower *collectionPolicy, current collectionPolicy) collectionPolicy { result := cloneCollectionPolicy(current) if !current.explicitModePresent && lower != nil && lower.explicitModePresent { result.explicitMode = lower.explicitMode result.explicitModePresent = true result.modeLocation = cloneLocation(lower.modeLocation) } return result } // collectionPolicyWithoutValue keeps identity and record ownership because no // collection value was replaced. A key-only declaration can remain pending // when no sequence is present, or validate the retained sequence immediately. func collectionPolicyWithoutValue( lower *collectionPolicy, current collectionPolicy, retainedValue *document.Node, path Path, ) (collectionPolicy, []Diagnostic) { result := collectionPolicy{} if lower != nil { result = cloneCollectionPolicy(*lower) } if lower != nil && lower.identityPresent && current.identityPresent && !equalSequenceIdentityFields(lower.identityFields, current.identityFields) { return collectionPolicy{}, []Diagnostic{collectionPolicyConflictDiagnostic( path, current.identityLocation, lower.identityLocation, "collection identity declaration conflicts with retained identity", )} } if current.identityPresent { result.identityFields = cloneStrings(current.identityFields) result.identityPresent = true result.identityLocation = cloneLocation(current.identityLocation) } if current.explicitModePresent { result.explicitMode = current.explicitMode result.explicitModePresent = true result.modeLocation = cloneLocation(current.modeLocation) } if current.identityPresent && retainedValue != nil && retainedValue.Kind == document.Sequence { _, diagnostics := validateSequenceIdentity(retainedValue, path, result.identityFields, sequenceIdentityDiagnosticCodes{ invalid: CodeSequenceInvalid, conflict: CodeSequenceConflict, }) if len(diagnostics) != 0 { return collectionPolicy{}, diagnostics } } return result, nil } func (scope *collectionPolicyScope) set(destination Path, policy collectionPolicy) { if scope.collections == nil { scope.collections = make(map[string]collectionPolicyEntry) } scope.collections[destination.String()] = collectionPolicyEntry{ destination: destination.clone(), policy: cloneCollectionPolicy(policy), } } func (scope collectionPolicyScope) at(destination Path) (collectionPolicy, bool) { entry, ok := scope.collections[destination.String()] if !ok { return collectionPolicy{}, false } return cloneCollectionPolicy(entry.policy), true } // retireCollectionPoliciesAtOrBelow clears YAML identity and record-owned state // below a replaced ancestor while retaining independently scoped explicit modes. func (scope *collectionPolicyScope) retireCollectionPoliciesAtOrBelow(ancestor Path) { if scope == nil { return } for key, entry := range scope.collections { if !pathHasPrefix(entry.destination, ancestor) { continue } clearCollectionIdentity(&entry.policy) scope.collections[key] = entry } } func projectCollectionPolicyScope(scope collectionPolicyScope, prefix Path) []projectedCollectionPolicy { keys := make([]string, 0, len(scope.collections)) for key := range scope.collections { keys = append(keys, key) } sort.Strings(keys) var projected []projectedCollectionPolicy for _, key := range keys { entry := scope.collections[key] concrete := appendDestination(prefix, entry.destination) projected = append(projected, projectedCollectionPolicy{path: concrete, policy: cloneCollectionPolicy(entry.policy)}) for index, owner := range entry.policy.recordOrder { if recordScope, ok := entry.policy.recordPolicies[owner]; ok { projected = append(projected, projectCollectionPolicyScope(recordScope, concrete.Index(index))...) } } } return projected } func cloneCollectionPolicyScope(scope collectionPolicyScope) collectionPolicyScope { if scope.collections == nil { return collectionPolicyScope{} } cloned := collectionPolicyScope{collections: make(map[string]collectionPolicyEntry, len(scope.collections))} for key, entry := range scope.collections { cloned.collections[key] = collectionPolicyEntry{ destination: entry.destination.clone(), policy: cloneCollectionPolicy(entry.policy), } } return cloned } func cloneCollectionPolicyScopeForInheritance(scope collectionPolicyScope) collectionPolicyScope { if scope.collections == nil { return collectionPolicyScope{} } cloned := collectionPolicyScope{collections: make(map[string]collectionPolicyEntry, len(scope.collections))} for key, entry := range scope.collections { cloned.collections[key] = collectionPolicyEntry{ destination: entry.destination.clone(), policy: cloneCollectionPolicyForInheritance(entry.policy), } } return cloned } func cloneCollectionPolicy(policy collectionPolicy) collectionPolicy { cloned := policy cloned.identityFields = cloneStrings(policy.identityFields) cloned.identityLocation = cloneLocation(policy.identityLocation) cloned.modeLocation = cloneLocation(policy.modeLocation) if policy.recordOrder != nil { cloned.recordOrder = append([]*collectionRecordOwner(nil), policy.recordOrder...) } if policy.recordPolicies != nil { cloned.recordPolicies = make(map[*collectionRecordOwner]collectionPolicyScope, len(policy.recordPolicies)) for owner, scope := range policy.recordPolicies { cloned.recordPolicies[owner] = cloneCollectionPolicyScope(scope) } } return cloned } func cloneCollectionPolicyForInheritance(policy collectionPolicy) collectionPolicy { cloned := policy cloned.identityFields = cloneStrings(policy.identityFields) cloned.identityLocation = cloneLocation(policy.identityLocation) cloned.modeLocation = cloneLocation(policy.modeLocation) if policy.recordOrder != nil { cloned.recordOrder = make([]*collectionRecordOwner, 0, len(policy.recordOrder)) } if policy.recordPolicies != nil { cloned.recordPolicies = make(map[*collectionRecordOwner]collectionPolicyScope, len(policy.recordPolicies)) for _, sourceOwner := range policy.recordOrder { owner := newCollectionRecordPolicy().owner cloned.recordOrder = append(cloned.recordOrder, owner) if sourceScope, ok := policy.recordPolicies[sourceOwner]; ok { cloned.recordPolicies[owner] = cloneCollectionPolicyScopeForInheritance(sourceScope) } } } return cloned }