Add dynamic sequence identity selectors
This commit is contained in:
@@ -5,8 +5,37 @@ type sequenceRegistration struct {
|
||||
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
|
||||
@@ -26,8 +55,9 @@ func DefinitionWithSequenceRegistrations[T any](definition Definition[T], paths
|
||||
}
|
||||
|
||||
func compileSequencePolicy(registrations []sequenceRegistration) (sequencePolicy, []Diagnostic) {
|
||||
policy := sequencePolicy{identityFields: make(map[string]string, len(registrations))}
|
||||
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 {
|
||||
@@ -37,18 +67,211 @@ func compileSequencePolicy(registrations []sequenceRegistration) (sequencePolicy
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
key := path.String()
|
||||
if current, ok := policy.identityFields[key]; ok {
|
||||
if current != registration.identityField {
|
||||
diagnostics = append(diagnostics, Diagnostic{
|
||||
Code: CodeOptionsInvalid, Severity: SeverityError,
|
||||
Path: path, Message: "sequence registrations at one path must use the same identity field",
|
||||
})
|
||||
}
|
||||
continue
|
||||
selector := make(sequenceIdentitySelector, len(path.segments))
|
||||
for index, segment := range path.segments {
|
||||
selector[index] = sequenceSelectorSegment{kind: sequenceSelectorLiteral, key: segment.key}
|
||||
}
|
||||
policy.identityFields[key] = registration.identityField
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user