243 lines
8.3 KiB
Go
243 lines
8.3 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) 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
|
|
}
|