aboutsummaryrefslogtreecommitdiff
path: root/internal/path/path.go
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2026-06-19 20:40:51 +0200
committerStefan Majewsky <majewsky@gmx.net>2026-06-19 20:40:51 +0200
commit76fdf49e4f3bd0048da2bc4697cac2f2b2cf752a (patch)
tree16aa5bd6bb8fb4a479c6e8afbbb7068d0ee8ae86 /internal/path/path.go
parent71d31d73e7dc3b9cdb3c53b4f343b1928a9e858b (diff)
downloadgo-gg-76fdf49e4f3bd0048da2bc4697cac2f2b2cf752a.tar.gz
jsonmatch: factor out type Path for reuse in assert.Equal() implementation
Diffstat (limited to 'internal/path/path.go')
-rw-r--r--internal/path/path.go74
1 files changed, 74 insertions, 0 deletions
diff --git a/internal/path/path.go b/internal/path/path.go
new file mode 100644
index 0000000..30a94d2
--- /dev/null
+++ b/internal/path/path.go
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: 2026 Stefan Majewsky <majewsky@gmx.net>
+// SPDX-License-Identifier: Apache-2.0
+
+package path
+
+import (
+ "encoding/json"
+ "strconv"
+ "strings"
+
+ . "go.xyrillian.de/gg/option"
+)
+
+// Path is used to identify the current location within a nested data structure
+// while recursing through it. For example, when comparing
+//
+// actual = { "foo": { "bar": [ 5, 23 ] } }
+// expected = { "foo": { "bar": [ 5, 42 ] } }
+//
+// we would generate a diff at the path {"foo", "bar", 1}.
+// Since diffs are usually rare, we only build Pointer strings
+// out of these paths when we really need them.
+// During recursion, Path holds a sequence of path elements,
+// most of which are constants to keep allocations to a minimum.
+//
+// # Warning
+//
+// Because the same Path slice is heavily reused across nested function calls,
+// it is not safe to store references to the Path slice during such a recursion.
+type Path []Element
+
+// Path preallocates a decently sized path buffer for use in a recursion.
+func NewPath() Path {
+ return make([]Element, 0, 32)
+}
+
+// Element occurs in type [Path]. Only one of both fields is set per instance.
+type Element struct {
+ Key Option[string]
+ Index int
+}
+
+// KeyElement is a shorthand for constructing an Element with the Key field set.
+func KeyElement(key string) Element { return Element{Some(key), 0} }
+
+// IndexElement is a shorthand for constructing an Element with the Index field set.
+func IndexElement(idx int) Element { return Element{None[string](), idx} }
+
+// AsJSONPointer serializes p as a JSON pointer (RFC 6901).
+func (p Path) AsJSONPointer() string {
+ if len(p) == 0 {
+ return ""
+ }
+ fragments := make([]string, len(p)+1)
+ fragments[0] = ""
+ for idx, elem := range p {
+ if key, ok := elem.Key.Unpack(); ok {
+ fragments[idx+1] = keyIntoPointerFragment(key)
+ } else {
+ fragments[idx+1] = strconv.Itoa(elem.Index)
+ }
+ }
+ return strings.Join(fragments, "/")
+}
+
+func keyIntoPointerFragment(key string) string {
+ buf, _ := json.Marshal(key)
+ s := string(buf)
+ s = strings.TrimPrefix(s, "\"")
+ s = strings.TrimSuffix(s, "\"")
+ s = strings.ReplaceAll(s, "~", "~0")
+ s = strings.ReplaceAll(s, "/", "~1")
+ return s
+}