Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions internal/binding/json_pointer.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ func ReadJSONPointer(data interface{}, pointer string) (interface{}, error) {

for i, raw := range segments {
// RFC 6901 unescaping: ~1 → /, ~0 → ~ (order matters).
key := strings.ReplaceAll(raw, "~1", "/")
key = strings.ReplaceAll(key, "~0", "~")
key, err := decodeJSONPointerSegment(raw)
if err != nil {
return nil, fmt.Errorf("json pointer %q: segment %q: %w", pointer, raw, err)
}

m, ok := current.(map[string]interface{})
if !ok {
Expand All @@ -53,3 +55,26 @@ func ReadJSONPointer(data interface{}, pointer string) (interface{}, error) {

return current, nil
}

func decodeJSONPointerSegment(raw string) (string, error) {
var out strings.Builder
for i := 0; i < len(raw); i++ {
if raw[i] != '~' {
out.WriteByte(raw[i])
continue
}
if i+1 >= len(raw) {
return "", fmt.Errorf("invalid escape: ~ must be followed by 0 or 1")
}
switch raw[i+1] {
case '0':
out.WriteByte('~')
case '1':
out.WriteByte('/')
default:
return "", fmt.Errorf("invalid escape: ~%c must be ~0 or ~1", raw[i+1])
}
i++
}
return out.String(), nil
}
35 changes: 35 additions & 0 deletions internal/binding/json_pointer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,41 @@ func TestReadJSONPointer_RFC6901_Escaping(t *testing.T) {
}
}

func TestReadJSONPointer_InvalidEscape(t *testing.T) {
data := map[string]interface{}{
"a~2b": "literal",
"a~": "literal",
}
tests := []struct {
name string
pointer string
want string
}{
{
name: "unsupported escape code",
pointer: "/a~2b",
want: `json pointer "/a~2b": segment "a~2b": invalid escape: ~2 must be ~0 or ~1`,
},
{
name: "dangling tilde",
pointer: "/a~",
want: `json pointer "/a~": segment "a~": invalid escape: ~ must be followed by 0 or 1`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := ReadJSONPointer(data, tt.pointer)
if err == nil {
t.Fatal("expected error for invalid escape, got nil")
}
if err.Error() != tt.want {
t.Errorf("error = %q, want %q", err.Error(), tt.want)
}
})
}
}

func TestReadJSONPointer_InvalidFormat(t *testing.T) {
data := map[string]interface{}{"key": "val"}
_, err := ReadJSONPointer(data, "no-leading-slash")
Expand Down
Loading