Files
ConfigKit/internal/engine/collection_policy_state.go

263 lines
9.0 KiB
Go

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