From 9ac65ccc08ec62c9e40c7c0320e2cf22c50eee67 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Fri, 9 Oct 2026 03:23:08 +0000 Subject: [PATCH] Compare canonical paths by token spelling --- docs/roadmap/implementation.md | 2 +- internal/engine/metadata.go | 14 ++--- internal/engine/path.go | 39 +++++++++++++ internal/engine/path_test.go | 100 +++++++++++++++++++++++++++++++++ internal/engine/resolution.go | 12 ++-- 5 files changed, 151 insertions(+), 16 deletions(-) diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index f65fe73..83f93d2 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -135,7 +135,7 @@ Roadmap candidate 9. Owner: `internal/engine/inheritance_load.go`. Validate: `go test ./internal/engine -run 'Inheritance|Inherited'` and the everyday check. Inputs without inheritance still need a linear scan. -## Stage 4: Compare path tokens without whole-pointer formatting +## Stage 4: Compare path tokens without whole-pointer formatting — Complete Roadmap candidate 2. Owners: engine `path.go`, `resolution.go`, `metadata.go`. diff --git a/internal/engine/metadata.go b/internal/engine/metadata.go index 57c7115..f12a61b 100644 --- a/internal/engine/metadata.go +++ b/internal/engine/metadata.go @@ -41,7 +41,7 @@ func (m Metadata) effectiveField(path Path) (FieldInfo, bool) { } pointer := path.String() candidate, ok := m.effectivePaths[pointer] - if !ok || !matchesPathShapeWithPointer(path, candidate, pointer) { + if !ok || !matchesPathShape(path, candidate) { return FieldInfo{}, false } info, ok := m.fields[pointer] @@ -89,18 +89,14 @@ func (m Metadata) HistoricalPaths() []Path { // Textual pointers are lookup keys, but explicit key/index kinds remain part of // the query. Candidate paths are constructed from actual document containers. func matchesPathShape(query, candidate Path) bool { - if !query.valid() { - return false - } - return matchesPathShapeWithPointer(query, candidate, query.String()) -} - -func matchesPathShapeWithPointer(query, candidate Path, pointer string) bool { - if pointer != candidate.String() || len(query.segments) != len(candidate.segments) { + if !query.valid() || len(query.segments) != len(candidate.segments) { return false } for i, segment := range query.segments { actual := candidate.segments[i] + if !pathTokenSpellingEqual(segment, actual) { + return false + } if actual.kind == pathSegmentIndex { index, ok := schemaIndex(segment) if !ok || index != actual.index { diff --git a/internal/engine/path.go b/internal/engine/path.go index a956dab..a4bef1b 100644 --- a/internal/engine/path.go +++ b/internal/engine/path.go @@ -128,6 +128,45 @@ func clonePathSegments(segments []pathSegment, extra int) []pathSegment { return cloned } +func pathTokenSpellingEqual(left, right pathSegment) bool { + switch { + case left.kind == pathSegmentIndex && right.kind == pathSegmentIndex: + return left.index == right.index + case left.kind == pathSegmentIndex: + return pathIndexSpellingEqual(left.index, right.key) + case right.kind == pathSegmentIndex: + return pathIndexSpellingEqual(right.index, left.key) + default: + return left.key == right.key + } +} + +func pathIndexSpellingEqual(index int, token string) bool { + var scratch [20]byte + spelling := strconv.AppendInt(scratch[:0], int64(index), 10) + if len(spelling) != len(token) { + return false + } + for i, character := range spelling { + if character != token[i] { + return false + } + } + return true +} + +func pathSpellingEqual(left, right Path) bool { + if len(left.segments) != len(right.segments) { + return false + } + for i, segment := range left.segments { + if !pathTokenSpellingEqual(segment, right.segments[i]) { + return false + } + } + return true +} + func escapePathToken(token string) string { token = strings.ReplaceAll(token, "~", "~0") return strings.ReplaceAll(token, "/", "~1") diff --git a/internal/engine/path_test.go b/internal/engine/path_test.go index 9995a69..9d4ab88 100644 --- a/internal/engine/path_test.go +++ b/internal/engine/path_test.go @@ -48,6 +48,106 @@ func TestPathRoundTripsSemanticKeys(t *testing.T) { } } +func TestPathContainmentMatchesPointerSpelling(t *testing.T) { + parse := func(pointer string) Path { + path, err := ParsePath(pointer) + if err != nil { + t.Fatalf("ParsePath(%q) error = %v", pointer, err) + } + return path + } + maximum := int(^uint(0) >> 1) + minimum := -maximum - 1 + paths := []Path{ + {}, + Keys(""), + Keys("a"), + Keys("ab"), + Keys("a", "b"), + Keys("a/b"), + Keys("a~1b"), + Keys("~"), + Keys("0"), + Keys("01"), + Keys("+1"), + Keys("items").Index(0), + Keys("items").Index(maximum), + Keys("items").Index(minimum), + parse("/"), + parse("/a~1b"), + parse("/a~01b"), + parse("/~0"), + parse("/0"), + parse("/01"), + parse("/+1"), + parse("/items/0"), + parse("/items/" + strconv.Itoa(maximum)), + parse("/items/" + strconv.Itoa(minimum)), + Path{}.Index(-1), + } + for _, container := range paths { + for _, descendant := range paths { + got := pathContains(container, descendant) + want := pathContainsByPointer(container, descendant) + if got != want { + t.Errorf("pathContains(%q, %q) = %v, want %v", container.String(), descendant.String(), got, want) + } + } + } +} + +func TestAppendUniquePathMatchesPointerSpelling(t *testing.T) { + parse := func(pointer string) Path { + path, err := ParsePath(pointer) + if err != nil { + t.Fatalf("ParsePath(%q) error = %v", pointer, err) + } + return path + } + paths := []Path{ + Keys("a/b"), parse("/a~1b"), + Keys("a~1b"), parse("/a~01b"), + Keys("items").Index(0), parse("/items/0"), Keys("items", "0"), + Keys("items", "01"), parse("/items/01"), + Path{}.Index(-1), Keys("-1"), + Keys("a", "b"), Keys("a/b"), + } + var got, want []Path + for _, path := range paths { + got = appendUniquePath(got, path) + want = appendUniquePathByPointer(want, path) + } + if len(got) != len(want) { + t.Fatalf("unique path counts = %d, want %d", len(got), len(want)) + } + for i := range got { + if got[i].String() != want[i].String() { + t.Fatalf("unique path %d = %q, want %q", i, got[i].String(), want[i].String()) + } + } +} + +func pathContainsByPointer(container, descendant Path) bool { + if !container.valid() || !descendant.valid() || len(container.segments) > len(descendant.segments) { + return false + } + prefix := container.String() + if prefix == "" { + return true + } + value := descendant.String() + return value == prefix || len(value) > len(prefix) && value[:len(prefix)] == prefix && value[len(prefix)] == '/' +} + +func appendUniquePathByPointer(paths []Path, path Path) []Path { + for _, existing := range paths { + if existing.String() == path.String() { + return paths + } + } + return append(paths, path.clone()) +} + func TestPathPreservesSegmentIntentAndImmutability(t *testing.T) { t.Parallel() diff --git a/internal/engine/resolution.go b/internal/engine/resolution.go index 464bb35..12cb1c9 100644 --- a/internal/engine/resolution.go +++ b/internal/engine/resolution.go @@ -337,12 +337,12 @@ func pathContains(container, descendant Path) bool { if !container.valid() || !descendant.valid() || len(container.segments) > len(descendant.segments) { return false } - if container.String() == "" { - return true + for i, segment := range container.segments { + if !pathTokenSpellingEqual(segment, descendant.segments[i]) { + return false + } } - prefix := container.String() - value := descendant.String() - return value == prefix || strings.HasPrefix(value, prefix+"/") + return true } func appendUniqueString(values []string, value string) []string { @@ -354,7 +354,7 @@ func appendUniqueString(values []string, value string) []string { func appendUniquePath(paths []Path, path Path) []Path { for _, existing := range paths { - if existing.String() == path.String() { + if pathSpellingEqual(existing, path) { return paths } }