Skip to content

Commit 2226b2e

Browse files
committed
Add Pointer method to NormalizedPath
1 parent 8e8e0ab commit 2226b2e

File tree

4 files changed

+104
-19
lines changed

4 files changed

+104
-19
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ All notable changes to this project will be documented in this file. It uses the
77
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html
88
"Semantic Versioning 2.0.0"
99

10+
## [v0.4.0] — Unreleased
11+
12+
### ⚡ Improvements
13+
14+
* Added the `Pointer` method to `NormalizedPath`. It returns an [RFC 9535
15+
JSON Pointer] string representation of the normalized path.
16+
17+
[v0.4.0]: https://github.com/theory/jsonpath/compare/v0.3.0...v0.4.0
18+
[RFC 9535 JSON Pointer]: https://www.rfc-editor.org/rfc/rfc9535#name-normalized-paths
19+
1020
## [v0.3.0] — 2024-12-28
1121

1222
### ⚡ Improvements

spec/normalized.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,17 @@ import (
88
// NormalSelector represents a single selector in a normalized path.
99
// Implemented by [Name] and [Index].
1010
type NormalSelector interface {
11-
// writeNormalizedTo writes n to buf formatted as a [normalized path] element.
11+
// writeNormalizedTo writes the selector to buf formatted as a [normalized
12+
// path] element.
1213
//
1314
// [normalized path]: https://www.rfc-editor.org/rfc/rfc9535#section-2.7
1415
writeNormalizedTo(buf *strings.Builder)
16+
17+
// writePointerTo writes the selector to buf formatted as a [JSON Pointer]
18+
// reference token.
19+
//
20+
// [JSON Pointer]: https://www.rfc-editor.org/rfc/rfc6901
21+
writePointerTo(buf *strings.Builder)
1522
}
1623

1724
// NormalizedPath represents a normalized path identifying a single value in a
@@ -30,6 +37,18 @@ func (np NormalizedPath) String() string {
3037
return buf.String()
3138
}
3239

40+
// Pointer returns an [RFC 6901 JSON Pointer] string representation of np.
41+
//
42+
// [RFC 6901 JSON Pointer]: https://www.rfc-editor.org/rfc/rfc6901
43+
func (np NormalizedPath) Pointer() string {
44+
buf := new(strings.Builder)
45+
for _, e := range np {
46+
buf.WriteRune('/')
47+
e.writePointerTo(buf)
48+
}
49+
return buf.String()
50+
}
51+
3352
// Compare compares np to np2 and returns -1 if np is less than np2, 1 if it's
3453
// greater than np2, and 0 if they're equal. Indexes are always considered
3554
// less than names.

spec/normalized_test.go

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,49 +16,66 @@ func TestNormalSelector(t *testing.T) {
1616
for _, tc := range []struct {
1717
name string
1818
elem NormalSelector
19-
exp string
19+
str string
20+
ptr string
2021
}{
2122
{
2223
name: "object_value",
2324
elem: Name("a"),
24-
exp: `['a']`,
25+
str: `['a']`,
26+
ptr: `a`,
2527
},
2628
{
2729
name: "array_index",
2830
elem: Index(1),
29-
exp: `[1]`,
31+
str: `[1]`,
32+
ptr: `1`,
3033
},
3134
{
3235
name: "escape_apostrophes",
3336
elem: Name("'hi'"),
34-
exp: `['\'hi\'']`,
37+
str: `['\'hi\'']`,
38+
ptr: "'hi'",
3539
},
3640
{
3741
name: "escapes",
3842
elem: Name("'\b\f\n\r\t\\'"),
39-
exp: `['\'\b\f\n\r\t\\\'']`,
43+
str: `['\'\b\f\n\r\t\\\'']`,
44+
ptr: "'\b\f\n\r\t\\'",
4045
},
4146
{
4247
name: "escape_vertical_unicode",
4348
elem: Name("\u000B"),
44-
exp: `['\u000b']`,
49+
str: `['\u000b']`,
50+
ptr: "\u000B",
4551
},
4652
{
4753
name: "escape_unicode_null",
4854
elem: Name("\u0000"),
49-
exp: `['\u0000']`,
55+
str: `['\u0000']`,
56+
ptr: "\u0000",
5057
},
5158
{
5259
name: "escape_unicode_runes",
5360
elem: Name("\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u000e\u000F"),
54-
exp: `['\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u000e\u000f']`,
61+
str: `['\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u000e\u000f']`,
62+
ptr: "\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u000e\u000F",
63+
},
64+
{
65+
name: "escape_pointer",
66+
elem: Name("this / ~that"),
67+
str: `['this / ~that']`,
68+
ptr: "this ~1 ~0that",
5569
},
5670
} {
5771
t.Run(tc.name, func(t *testing.T) {
5872
t.Parallel()
5973
buf := new(strings.Builder)
6074
tc.elem.writeNormalizedTo(buf)
61-
a.Equal(tc.exp, buf.String())
75+
a.Equal(tc.str, buf.String())
76+
buf.Reset()
77+
tc.elem.writePointerTo(buf)
78+
a.Equal(tc.ptr, buf.String())
6279
})
6380
}
6481
}
@@ -70,42 +87,62 @@ func TestNormalizedPath(t *testing.T) {
7087
for _, tc := range []struct {
7188
name string
7289
path NormalizedPath
73-
exp string
90+
str string
91+
ptr string
7492
}{
93+
{
94+
name: "empty_path",
95+
path: NormalizedPath{},
96+
str: "$",
97+
ptr: "",
98+
},
7599
{
76100
name: "object_value",
77101
path: NormalizedPath{Name("a")},
78-
exp: "$['a']",
102+
str: "$['a']",
103+
ptr: "/a",
79104
},
80105
{
81106
name: "array_index",
82107
path: NormalizedPath{Index(1)},
83-
exp: "$[1]",
108+
str: "$[1]",
109+
ptr: "/1",
84110
},
85111
{
86112
name: "neg_for_len_5",
87113
path: NormalizedPath{Index(2)},
88-
exp: "$[2]",
114+
str: "$[2]",
115+
ptr: "/2",
89116
},
90117
{
91118
name: "nested_structure",
92119
path: NormalizedPath{Name("a"), Name("b"), Index(1)},
93-
exp: "$['a']['b'][1]",
120+
str: "$['a']['b'][1]",
121+
ptr: "/a/b/1",
94122
},
95123
{
96124
name: "unicode_escape",
97125
path: NormalizedPath{Name("\u000B")},
98-
exp: `$['\u000b']`,
126+
str: `$['\u000b']`,
127+
ptr: "/\u000b",
99128
},
100129
{
101130
name: "unicode_character",
102131
path: NormalizedPath{Name("\u0061")},
103-
exp: "$['a']",
132+
str: "$['a']",
133+
ptr: "/a",
134+
},
135+
{
136+
name: "nested_structure_pointer_stuff",
137+
path: NormalizedPath{Name("a~x"), Name("b/2"), Index(1)},
138+
str: "$['a~x']['b/2'][1]",
139+
ptr: "/a~0x/b~12/1",
104140
},
105141
} {
106142
t.Run(tc.name, func(t *testing.T) {
107143
t.Parallel()
108-
a.Equal(tc.exp, tc.path.String())
144+
a.Equal(tc.str, tc.path.String())
145+
a.Equal(tc.ptr, tc.path.Pointer())
109146
})
110147
}
111148
}

spec/selector.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func (n Name) SelectLocated(input, _ any, parent NormalizedPath) []*LocatedNode
7777
}
7878

7979
// writeNormalizedTo writes n to buf formatted as a [normalized path] element.
80-
// Implements [NormalSelector].
80+
// Defined by [NormalSelector].
8181
//
8282
// [normalized path]: https://www.rfc-editor.org/rfc/rfc9535#section-2.7
8383
func (n Name) writeNormalizedTo(buf *strings.Builder) {
@@ -109,6 +109,17 @@ func (n Name) writeNormalizedTo(buf *strings.Builder) {
109109
buf.WriteString("']")
110110
}
111111

112+
// writePointerTo writes n to buf formatted as a [JSON Pointer] reference
113+
// token. Defined by [NormalSelector].
114+
//
115+
// [JSON Pointer]: https://www.rfc-editor.org/rfc/rfc6901
116+
func (n Name) writePointerTo(buf *strings.Builder) {
117+
buf.WriteString(strings.ReplaceAll(
118+
strings.ReplaceAll(string(n), "~", "~0"),
119+
"/", "~1",
120+
))
121+
}
122+
112123
// WildcardSelector is the underlying nil value used by [Wildcard].
113124
type WildcardSelector struct{}
114125

@@ -226,6 +237,14 @@ func (i Index) writeNormalizedTo(buf *strings.Builder) {
226237
buf.WriteRune(']')
227238
}
228239

240+
// writePointerTo writes n to buf formatted as a [JSON Pointer] reference
241+
// token. Defined by [NormalSelector].
242+
//
243+
// [JSON Pointer]: https://www.rfc-editor.org/rfc/rfc6901
244+
func (i Index) writePointerTo(buf *strings.Builder) {
245+
buf.WriteString(strconv.FormatInt(int64(i), 10))
246+
}
247+
229248
// SliceSelector is a slice selector, e.g., [0:100:5].
230249
type SliceSelector struct {
231250
// Start of the slice; defaults to 0.

0 commit comments

Comments
 (0)