Track collection policy ownership
This commit is contained in:
242
internal/engine/collection_policy_state.go
Normal file
242
internal/engine/collection_policy_state.go
Normal file
@@ -0,0 +1,242 @@
|
||||
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) 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
|
||||
}
|
||||
entry.policy.identityFields = nil
|
||||
entry.policy.identityPresent = false
|
||||
entry.policy.identityLocation = nil
|
||||
entry.policy.recordOrder = nil
|
||||
entry.policy.recordPolicies = nil
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user