-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathpointer.go
237 lines (214 loc) · 5.5 KB
/
pointer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
// Copyright 2016-2020 Olivier Mengué. All rights reserved.
// Use of this source code is governed by the Apache 2.0 license that
// can be found in the LICENSE file.
package jsonptr
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"strings"
)
// Pointer represents a mutable parsed JSON Pointer.
//
// The Go representation is a array of non-encoded path elements. This
// allows to use type conversion from/to a []string.
type Pointer []string
// Parse parses a JSON pointer from its text representation.
func Parse(pointer string) (Pointer, error) {
if pointer == "" {
return nil, nil
}
if pointer[0] != '/' {
return nil, ErrSyntax
}
ptr := strings.Split(pointer[1:], "/")
// Optimize for the common case
if strings.IndexByte(pointer, '~') == -1 {
return ptr, nil
}
for i, part := range ptr {
var err error
if ptr[i], err = UnescapeString(part); err != nil {
// TODO return the full prefix
return nil, err
}
}
return ptr, nil
}
// MustParse wraps Parse and panics in case of error.
func MustParse(pointer string) Pointer {
ptr, err := Parse(pointer)
if err != nil {
panic(fmt.Errorf("%q: %v", pointer, err))
}
return ptr
}
// MarshalText implements [encoding.TextUnmarshaler].
func (ptr *Pointer) UnmarshalText(text []byte) error {
if len(text) == 0 {
*ptr = nil
return nil
}
if text[0] != '/' {
return ErrSyntax
}
var p Pointer
t := text[1:]
for {
i := bytes.IndexByte(t, '/')
if i < 0 {
break
}
part, err := Unescape(t[:i])
if err != nil {
return syntaxError(string(text[:len(text)-len(t)-i-1]))
}
p = append(p, string(part))
t = t[i+1:]
}
part, err := Unescape(t)
if err != nil {
return syntaxError(string(text))
}
*ptr = append(p, string(part))
return nil
}
// String returns a JSON Pointer string, escaping components when necessary:
// '~' is replaced by "~0", '/' by "~1"
func (ptr Pointer) String() string {
if len(ptr) == 0 {
return ""
}
dst := make([]byte, 0, 8*len(ptr))
for _, part := range ptr {
dst = AppendEscape(append(dst, '/'), part)
}
return string(dst)
}
// MarshalText implements [encoding.TextMarshaler].
func (ptr Pointer) MarshalText() (text []byte, err error) {
if len(ptr) == 0 {
return nil, nil
}
dst := make([]byte, 0, 8*len(ptr))
for _, part := range ptr {
dst = AppendEscape(append(dst, '/'), part)
}
return dst, nil
}
// Grow allows to prepare space for growth (before use of [Pointer.Property]/[Pointer.Index]).
func (ptr *Pointer) Grow(n int) {
if cap(*ptr) >= len(*ptr)+n {
return
}
*ptr = append(make(Pointer, 0, len(*ptr)+n), *ptr...)
}
// Copy returns a new, independant, copy of the pointer.
func (ptr Pointer) Copy() Pointer {
return append(Pointer(nil), ptr...)
}
// IsRoot returns true if the pointer is at root (empty).
func (ptr Pointer) IsRoot() bool {
return len(ptr) == 0
}
// Up removes the last element of the pointer.
// The pointer is returned for chaining.
//
// Panics if already at root.
func (ptr *Pointer) Up() *Pointer {
if ptr.IsRoot() {
panic(ErrRoot)
}
*ptr = (*ptr)[:len(*ptr)-1]
return ptr
}
// Pop removes the last element of the pointer and returns it.
//
// Panics if already at root.
func (ptr *Pointer) Pop() string {
last := len(*ptr) - 1
if last < 0 {
panic(ErrRoot)
}
prop := (*ptr)[last]
*ptr = (*ptr)[:last]
return prop
}
// Property moves the pointer deeper, following a property name.
// The pointer is returned for chaining.
func (ptr *Pointer) Property(name string) *Pointer {
*ptr = append(*ptr, name)
return ptr
}
// Index moves the pointer deeper, following an array index.
// The pointer is returned for chaining.
func (ptr *Pointer) Index(index int) *Pointer {
var prop string
if index < 0 {
prop = "-"
} else {
prop = strconv.Itoa(index)
}
return ptr.Property(prop)
}
// LeafName returns the name of the last part of the pointer.
func (ptr Pointer) LeafName() string {
return ptr[len(ptr)-1]
}
// LeafIndex returns the last part of the pointer as an array index.
// -1 is returned for "-".
func (ptr Pointer) LeafIndex() (int, error) {
return arrayIndex(ptr.LeafName())
}
// In returns the value from doc pointed by ptr.
//
// doc may be a deserialized document, or a [encoding/json.RawMessage].
func (ptr Pointer) In(doc interface{}) (interface{}, error) {
for i, key := range ptr {
switch here := (doc).(type) {
case map[string]interface{}:
var ok bool
if doc, ok = here[key]; !ok {
return nil, propertyError(ptr[:i+1].String())
}
case []interface{}:
n, err := arrayIndex(key)
if err != nil || n < 0 || n >= len(here) {
return nil, indexError(ptr[:i+1].String())
}
doc = here[n]
case JSONDecoder:
v, err := getJSON(here, ptr[i:].String())
if err != nil {
err.rebase(ptr[:i].String())
}
return v, err
case json.RawMessage:
v, err := getRaw(here, ptr[i:].String())
if err != nil {
err.rebase(ptr[:i].String())
}
return v, err
default:
// We report the error at the upper level
return nil, docError(ptr[:i-1].String(), doc)
}
}
doc, err := getLeaf(doc)
if err != nil {
err.rebase(ptr.String())
}
return doc, err
}
// Set changes a value in document pdoc at location pointed by ptr.
func (ptr Pointer) Set(pdoc *interface{}, value interface{}) error {
// TODO Make an optimised implementation
return Set(pdoc, ptr.String(), value)
}
// Delete removes an object property or an array element (and shifts remaining ones).
// It can't be applied on root.
func (ptr Pointer) Delete(pdoc *interface{}) (interface{}, error) {
// TODO Make an optimised implementation
return Delete(pdoc, ptr.String())
}