Files
ConfigKit/internal/engine/sequence_registration.go

278 lines
8.3 KiB
Go

package engine
type sequenceRegistration struct {
path Path
identityField string
}
type sequenceSelectorSegmentKind uint8
const (
sequenceSelectorLiteral sequenceSelectorSegmentKind = iota + 1
sequenceSelectorAnyMappingKey
)
type sequenceSelectorSegment struct {
kind sequenceSelectorSegmentKind
key string
}
type sequenceIdentitySelector []sequenceSelectorSegment
type sequenceIdentityRegistration struct {
selector sequenceIdentitySelector
identityFields []string
}
type sequenceSelectorTrie struct {
literals map[string]*sequenceSelectorTrie
wildcard *sequenceSelectorTrie
fields []string
terminal bool
}
type sequencePolicy struct {
// identityFields preserves the old exact-path view for existing internal
// callers while selector matching is introduced behind the same policy.
identityFields map[string]string
selectorTrie *sequenceSelectorTrie
}
// DefinitionWithSequenceRegistrations attaches facade-owned registrations to a
// detached engine definition without exposing the registration representation.
func DefinitionWithSequenceRegistrations[T any](definition Definition[T], paths []Path, identityFields []string) Definition[T] {
if paths == nil {
definition.sequenceRegistrations = nil
return definition
}
definition.sequenceRegistrations = make([]sequenceRegistration, len(paths))
for index := range paths {
definition.sequenceRegistrations[index] = sequenceRegistration{
path: paths[index].clone(), identityField: identityFields[index],
}
}
return definition
}
func compileSequencePolicy(registrations []sequenceRegistration) (sequencePolicy, []Diagnostic) {
common := make([]sequenceIdentityRegistration, 0, len(registrations))
var diagnostics []Diagnostic
legacyPaths := make(map[string]string, len(registrations))
for _, registration := range registrations {
path, err := normalizeDestination(registration.path)
if err != nil {
diagnostics = append(diagnostics, Diagnostic{
Code: CodeOptionsInvalid, Severity: SeverityError,
Path: registration.path.clone(), Message: "sequence registration path must contain only non-reserved mapping keys",
})
continue
}
selector := make(sequenceIdentitySelector, len(path.segments))
for index, segment := range path.segments {
selector[index] = sequenceSelectorSegment{kind: sequenceSelectorLiteral, key: segment.key}
}
common = append(common, sequenceIdentityRegistration{
selector: selector, identityFields: []string{registration.identityField},
})
legacyPaths[path.String()] = registration.identityField
}
policy, compiledDiagnostics := compileSequenceIdentityPolicy(common)
policy.identityFields = legacyPaths
diagnostics = append(diagnostics, compiledDiagnostics...)
return policy, canonicalDiagnostics(diagnostics)
}
func compileSequenceIdentityPolicy(registrations []sequenceIdentityRegistration) (sequencePolicy, []Diagnostic) {
policy := sequencePolicy{}
compiled := make([]sequenceIdentityRegistration, 0, len(registrations))
var diagnostics []Diagnostic
for _, registration := range registrations {
selector := cloneSequenceIdentitySelector(registration.selector)
fields, err := normalizeSequenceIdentityFields(registration.identityFields)
if err != nil {
diagnostics = append(diagnostics, Diagnostic{
Code: CodeOptionsInvalid, Severity: SeverityError, Path: selectorDiagnosticPath(selector),
Message: "sequence registration requires at least one identity field",
})
continue
}
if err := validateSequenceIdentitySelector(selector); err != nil {
diagnostics = append(diagnostics, Diagnostic{
Code: CodeOptionsInvalid, Severity: SeverityError, Path: selectorDiagnosticPath(selector),
Message: "sequence registration selector must contain only valid mapping segments",
})
continue
}
compiled = append(compiled, sequenceIdentityRegistration{selector: selector, identityFields: fields})
}
for leftIndex := 0; leftIndex < len(compiled); leftIndex++ {
for rightIndex := leftIndex + 1; rightIndex < len(compiled); rightIndex++ {
left, right := compiled[leftIndex], compiled[rightIndex]
if !sequenceIdentitySelectorsOverlap(left.selector, right.selector) || equalSequenceIdentityFields(left.identityFields, right.identityFields) {
continue
}
diagnostics = append(diagnostics, Diagnostic{
Code: CodeOptionsInvalid, Severity: SeverityError, Path: selectorDiagnosticPath(left.selector),
Message: "overlapping sequence registrations must use the same identity fields",
})
}
}
if len(diagnostics) != 0 {
return policy, canonicalDiagnostics(diagnostics)
}
unique := make([]sequenceIdentityRegistration, 0, len(compiled))
for _, registration := range compiled {
duplicate := false
for _, existing := range unique {
if sequenceIdentitySelectorsEqual(existing.selector, registration.selector) && equalSequenceIdentityFields(existing.identityFields, registration.identityFields) {
duplicate = true
break
}
}
if duplicate {
continue
}
unique = append(unique, registration)
}
for _, registration := range unique {
policy.addRegistration(registration)
}
return policy, nil
}
func (p *sequencePolicy) addRegistration(registration sequenceIdentityRegistration) {
if p.selectorTrie == nil {
p.selectorTrie = &sequenceSelectorTrie{}
}
node := p.selectorTrie
for _, segment := range registration.selector {
if segment.kind == sequenceSelectorAnyMappingKey {
if node.wildcard == nil {
node.wildcard = &sequenceSelectorTrie{}
}
node = node.wildcard
continue
}
if node.literals == nil {
node.literals = make(map[string]*sequenceSelectorTrie)
}
if node.literals[segment.key] == nil {
node.literals[segment.key] = &sequenceSelectorTrie{}
}
node = node.literals[segment.key]
}
node.fields = append([]string(nil), registration.identityFields...)
node.terminal = true
}
func (p sequencePolicy) identityFieldsAt(path Path) ([]string, bool) {
if !path.valid() {
return nil, false
}
if p.selectorTrie == nil {
field, ok := p.identityFields[path.String()]
if !ok {
return nil, false
}
return []string{field}, true
}
current := []*sequenceSelectorTrie{p.selectorTrie}
for _, segment := range path.segments {
if segment.kind != pathSegmentKey && segment.kind != pathSegmentParsed {
return nil, false
}
next := make([]*sequenceSelectorTrie, 0, len(current)*2)
for _, node := range current {
if literal := node.literals[segment.key]; literal != nil {
next = append(next, literal)
}
if node.wildcard != nil {
next = append(next, node.wildcard)
}
}
if len(next) == 0 {
return nil, false
}
current = next
}
for _, node := range current {
if node.terminal {
return append([]string(nil), node.fields...), true
}
}
return nil, false
}
func (p sequencePolicy) registered(path Path) bool {
_, ok := p.identityFieldsAt(path)
return ok
}
func validateSequenceIdentitySelector(selector sequenceIdentitySelector) error {
for _, segment := range selector {
switch segment.kind {
case sequenceSelectorLiteral:
if _, err := normalizeDestination(Keys(segment.key)); err != nil {
return err
}
case sequenceSelectorAnyMappingKey:
default:
return errInvalidDestination
}
}
return nil
}
func sequenceIdentitySelectorsEqual(left, right sequenceIdentitySelector) bool {
if len(left) != len(right) {
return false
}
for index := range left {
if left[index] != right[index] {
return false
}
}
return true
}
func sequenceIdentitySelectorsOverlap(left, right sequenceIdentitySelector) bool {
if len(left) != len(right) {
return false
}
for index := range left {
if left[index].kind == sequenceSelectorLiteral && right[index].kind == sequenceSelectorLiteral && left[index].key != right[index].key {
return false
}
}
return true
}
func equalSequenceIdentityFields(left, right []string) bool {
if len(left) != len(right) {
return false
}
for index := range left {
if left[index] != right[index] {
return false
}
}
return true
}
func cloneSequenceIdentitySelector(selector sequenceIdentitySelector) sequenceIdentitySelector {
return append(sequenceIdentitySelector(nil), selector...)
}
func selectorDiagnosticPath(selector sequenceIdentitySelector) Path {
path := Path{}
for _, segment := range selector {
if segment.kind != sequenceSelectorLiteral {
return Path{}
}
path = path.Key(segment.key)
}
return path
}