294 lines
8.9 KiB
Go
294 lines
8.9 KiB
Go
package engine
|
|
|
|
import "fmt"
|
|
|
|
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
|
|
invalidPath *Path
|
|
}
|
|
|
|
// SequenceIdentitySelectorSegment carries one root-owned selector segment into
|
|
// an engine definition. This transport type remains behind the internal package
|
|
// boundary.
|
|
type SequenceIdentitySelectorSegment struct {
|
|
Key string
|
|
AnyMappingKey bool
|
|
}
|
|
|
|
// SequenceIdentityRegistrationInput carries detached root-owned registration
|
|
// data into an engine definition without exposing its compiled policy. A
|
|
// non-nil ExactPath selects exact-path registration; otherwise Selector is used.
|
|
type SequenceIdentityRegistrationInput struct {
|
|
ExactPath *Path
|
|
Selector []SequenceIdentitySelectorSegment
|
|
IdentityFields []string
|
|
}
|
|
|
|
type sequenceSelectorTrie struct {
|
|
literals map[string]*sequenceSelectorTrie
|
|
wildcard *sequenceSelectorTrie
|
|
fields []string
|
|
terminal bool
|
|
}
|
|
|
|
type sequencePolicy struct {
|
|
selectorTrie *sequenceSelectorTrie
|
|
}
|
|
|
|
// DefinitionWithSequenceIdentityRegistrations attaches facade-owned
|
|
// registrations to a detached engine definition without exposing compiled
|
|
// engine policy.
|
|
func DefinitionWithSequenceIdentityRegistrations[T any](definition Definition[T], registrations []SequenceIdentityRegistrationInput) Definition[T] {
|
|
if registrations == nil {
|
|
definition.sequenceIdentityRegistrations = nil
|
|
return definition
|
|
}
|
|
definition.sequenceIdentityRegistrations = make([]sequenceIdentityRegistration, len(registrations))
|
|
for index, registration := range registrations {
|
|
compiled := sequenceIdentityRegistration{identityFields: append([]string(nil), registration.IdentityFields...)}
|
|
if registration.ExactPath != nil {
|
|
path, err := normalizeDestination(registration.ExactPath.clone())
|
|
if err != nil {
|
|
invalid := registration.ExactPath.clone()
|
|
compiled.invalidPath = &invalid
|
|
} else {
|
|
compiled.selector = make(sequenceIdentitySelector, len(path.segments))
|
|
for segmentIndex, segment := range path.segments {
|
|
compiled.selector[segmentIndex] = sequenceSelectorSegment{kind: sequenceSelectorLiteral, key: segment.key}
|
|
}
|
|
}
|
|
} else {
|
|
compiled.selector = make(sequenceIdentitySelector, len(registration.Selector))
|
|
for segmentIndex, segment := range registration.Selector {
|
|
kind := sequenceSelectorLiteral
|
|
if segment.AnyMappingKey {
|
|
kind = sequenceSelectorAnyMappingKey
|
|
}
|
|
compiled.selector[segmentIndex] = sequenceSelectorSegment{kind: kind, key: segment.Key}
|
|
}
|
|
}
|
|
definition.sequenceIdentityRegistrations[index] = compiled
|
|
}
|
|
return definition
|
|
}
|
|
|
|
func compileSequenceIdentityPolicy(registrations []sequenceIdentityRegistration) (sequencePolicy, []Diagnostic) {
|
|
policy := sequencePolicy{}
|
|
type positionedRegistration struct {
|
|
registration sequenceIdentityRegistration
|
|
position int
|
|
}
|
|
compiled := make([]positionedRegistration, 0, len(registrations))
|
|
var diagnostics []Diagnostic
|
|
for position, registration := range registrations {
|
|
selector := cloneSequenceIdentitySelector(registration.selector)
|
|
if registration.invalidPath != nil {
|
|
diagnostics = append(diagnostics, Diagnostic{
|
|
Code: CodeOptionsInvalid, Severity: SeverityError, Path: registration.invalidPath.clone(),
|
|
Message: "sequence registration path must contain only non-reserved mapping keys",
|
|
})
|
|
continue
|
|
}
|
|
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, positionedRegistration{
|
|
registration: sequenceIdentityRegistration{selector: selector, identityFields: fields},
|
|
position: position + 1,
|
|
})
|
|
}
|
|
|
|
for leftIndex := 0; leftIndex < len(compiled); leftIndex++ {
|
|
for rightIndex := leftIndex + 1; rightIndex < len(compiled); rightIndex++ {
|
|
left, right := compiled[leftIndex].registration, compiled[rightIndex].registration
|
|
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: fmt.Sprintf("sequence registrations %d and %d overlap with conflicting identity fields", compiled[leftIndex].position, compiled[rightIndex].position),
|
|
})
|
|
}
|
|
}
|
|
if len(diagnostics) != 0 {
|
|
return policy, canonicalDiagnostics(diagnostics)
|
|
}
|
|
|
|
unique := make([]sequenceIdentityRegistration, 0, len(compiled))
|
|
for _, positioned := range compiled {
|
|
registration := positioned.registration
|
|
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 {
|
|
return nil, false
|
|
}
|
|
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
|
|
}
|