Identify conflicting sequence registration positions
This commit is contained in:
@@ -319,7 +319,13 @@ Add ordinary/layered parity and warning-plus-later-error regressions.
|
|||||||
|
|
||||||
## 9. Identify Conflicting Application Registrations In Diagnostics — P2
|
## 9. Identify Conflicting Application Registrations In Diagnostics — P2
|
||||||
|
|
||||||
Status: [ ] Open.
|
Status: [x] Complete.
|
||||||
|
|
||||||
|
Completion: definition validation retains original one-based registration
|
||||||
|
positions and names both positions in each overlap diagnostic. No input path or
|
||||||
|
new public diagnostic structure is invented. A mixed-selector regression verifies
|
||||||
|
multiple conflicts even after an earlier invalid registration is skipped.
|
||||||
|
Verified registration/facade coverage through `./scripts/check.sh`.
|
||||||
|
|
||||||
Owner: [`compileSequenceIdentityPolicy`](../../internal/engine/sequence_registration.go),
|
Owner: [`compileSequenceIdentityPolicy`](../../internal/engine/sequence_registration.go),
|
||||||
lines 119–128.
|
lines 119–128.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package engine
|
package engine
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
type sequenceSelectorSegmentKind uint8
|
type sequenceSelectorSegmentKind uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -87,9 +89,13 @@ func DefinitionWithSequenceIdentityRegistrations[T any](definition Definition[T]
|
|||||||
|
|
||||||
func compileSequenceIdentityPolicy(registrations []sequenceIdentityRegistration) (sequencePolicy, []Diagnostic) {
|
func compileSequenceIdentityPolicy(registrations []sequenceIdentityRegistration) (sequencePolicy, []Diagnostic) {
|
||||||
policy := sequencePolicy{}
|
policy := sequencePolicy{}
|
||||||
compiled := make([]sequenceIdentityRegistration, 0, len(registrations))
|
type positionedRegistration struct {
|
||||||
|
registration sequenceIdentityRegistration
|
||||||
|
position int
|
||||||
|
}
|
||||||
|
compiled := make([]positionedRegistration, 0, len(registrations))
|
||||||
var diagnostics []Diagnostic
|
var diagnostics []Diagnostic
|
||||||
for _, registration := range registrations {
|
for position, registration := range registrations {
|
||||||
selector := cloneSequenceIdentitySelector(registration.selector)
|
selector := cloneSequenceIdentitySelector(registration.selector)
|
||||||
if registration.invalidPath != nil {
|
if registration.invalidPath != nil {
|
||||||
diagnostics = append(diagnostics, Diagnostic{
|
diagnostics = append(diagnostics, Diagnostic{
|
||||||
@@ -113,18 +119,21 @@ func compileSequenceIdentityPolicy(registrations []sequenceIdentityRegistration)
|
|||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
compiled = append(compiled, sequenceIdentityRegistration{selector: selector, identityFields: fields})
|
compiled = append(compiled, positionedRegistration{
|
||||||
|
registration: sequenceIdentityRegistration{selector: selector, identityFields: fields},
|
||||||
|
position: position + 1,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for leftIndex := 0; leftIndex < len(compiled); leftIndex++ {
|
for leftIndex := 0; leftIndex < len(compiled); leftIndex++ {
|
||||||
for rightIndex := leftIndex + 1; rightIndex < len(compiled); rightIndex++ {
|
for rightIndex := leftIndex + 1; rightIndex < len(compiled); rightIndex++ {
|
||||||
left, right := compiled[leftIndex], compiled[rightIndex]
|
left, right := compiled[leftIndex].registration, compiled[rightIndex].registration
|
||||||
if !sequenceIdentitySelectorsOverlap(left.selector, right.selector) || equalSequenceIdentityFields(left.identityFields, right.identityFields) {
|
if !sequenceIdentitySelectorsOverlap(left.selector, right.selector) || equalSequenceIdentityFields(left.identityFields, right.identityFields) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
diagnostics = append(diagnostics, Diagnostic{
|
diagnostics = append(diagnostics, Diagnostic{
|
||||||
Code: CodeOptionsInvalid, Severity: SeverityError, Path: selectorDiagnosticPath(left.selector),
|
Code: CodeOptionsInvalid, Severity: SeverityError, Path: selectorDiagnosticPath(left.selector),
|
||||||
Message: "overlapping sequence registrations must use the same identity fields",
|
Message: fmt.Sprintf("sequence registrations %d and %d overlap with conflicting identity fields", compiled[leftIndex].position, compiled[rightIndex].position),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +142,8 @@ func compileSequenceIdentityPolicy(registrations []sequenceIdentityRegistration)
|
|||||||
}
|
}
|
||||||
|
|
||||||
unique := make([]sequenceIdentityRegistration, 0, len(compiled))
|
unique := make([]sequenceIdentityRegistration, 0, len(compiled))
|
||||||
for _, registration := range compiled {
|
for _, positioned := range compiled {
|
||||||
|
registration := positioned.registration
|
||||||
duplicate := false
|
duplicate := false
|
||||||
for _, existing := range unique {
|
for _, existing := range unique {
|
||||||
if sequenceIdentitySelectorsEqual(existing.selector, registration.selector) && equalSequenceIdentityFields(existing.identityFields, registration.identityFields) {
|
if sequenceIdentitySelectorsEqual(existing.selector, registration.selector) && equalSequenceIdentityFields(existing.identityFields, registration.identityFields) {
|
||||||
|
|||||||
@@ -178,6 +178,34 @@ func TestCompileSequenceIdentityPolicyRejectsConflictingOverlapsWithoutInput(t *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSequenceOverlapDiagnosticsIdentifyOriginalRegistrationPositions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
_, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{
|
||||||
|
{selector: sequenceIdentitySelector{literalSequenceSelector("invalid")}},
|
||||||
|
{selector: sequenceIdentitySelector{literalSequenceSelector("unrelated")}, identityFields: []string{"id"}},
|
||||||
|
{selector: sequenceIdentitySelector{literalSequenceSelector("collections"), anyMappingSequenceSelector(), literalSequenceSelector("items")}, identityFields: []string{"id"}},
|
||||||
|
{selector: sequenceIdentitySelector{literalSequenceSelector("collections"), literalSequenceSelector("foo"), literalSequenceSelector("items")}, identityFields: []string{"name"}},
|
||||||
|
{selector: sequenceIdentitySelector{literalSequenceSelector("collections"), literalSequenceSelector("bar"), literalSequenceSelector("items")}, identityFields: []string{"code"}},
|
||||||
|
})
|
||||||
|
if len(diagnostics) != 3 {
|
||||||
|
t.Fatalf("diagnostics = %#v, want the invalid registration and two conflicts", diagnostics)
|
||||||
|
}
|
||||||
|
for _, pair := range []string{"3 and 4", "3 and 5"} {
|
||||||
|
found := false
|
||||||
|
for _, diagnostic := range diagnostics {
|
||||||
|
if strings.Contains(diagnostic.Message, pair) {
|
||||||
|
found = true
|
||||||
|
if diagnostic.Code != CodeOptionsInvalid || diagnostic.Path.String() != "" || len(diagnostic.Related) != 0 {
|
||||||
|
t.Fatalf("definition overlap invented input coordinates: %#v", diagnostic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("conflicting registration positions %s not identified: %#v", pair, diagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCompileSequenceIdentityPolicyAllowsDisjointSelectorsAndRoot(t *testing.T) {
|
func TestCompileSequenceIdentityPolicyAllowsDisjointSelectorsAndRoot(t *testing.T) {
|
||||||
policy, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{
|
policy, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{
|
||||||
{selector: sequenceIdentitySelector{}, identityFields: []string{"id"}},
|
{selector: sequenceIdentitySelector{}, identityFields: []string{"id"}},
|
||||||
|
|||||||
Reference in New Issue
Block a user