json_p3 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,449 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "English"
4
+ require_relative "errors"
5
+
6
+ module JSONP3
7
+ # Base class for all JSON Patch operations
8
+ class Op
9
+ # Return the name of the patch operation.
10
+ def name
11
+ raise "JSON Patch operations must implement #name"
12
+ end
13
+
14
+ # Apply the patch operation to _value_.
15
+ def apply(_value, _index)
16
+ raise "JSON Patch operations must implement apply(value, index)"
17
+ end
18
+
19
+ # Return a JSON-like representation of this patch operation.
20
+ def to_h
21
+ raise "JSON Patch operations must implement #to_h"
22
+ end
23
+ end
24
+
25
+ # The JSON Patch _add_ operation.
26
+ class OpAdd < Op
27
+ # @param pointer [JSONPointer]
28
+ # @param value [JSON-like value]
29
+ def initialize(pointer, value)
30
+ super()
31
+ @pointer = pointer
32
+ @value = value
33
+ end
34
+
35
+ def name
36
+ "add"
37
+ end
38
+
39
+ def apply(value, index)
40
+ parent, obj = @pointer.resolve_with_parent(value)
41
+ return @value if parent == JSONP3::JSONPointer::UNDEFINED && @pointer.tokens.empty?
42
+
43
+ if parent == JSONP3::JSONPointer::UNDEFINED
44
+ raise JSONPatchError,
45
+ "no such property or item '#{@pointer.parent}' (#{name}:#{index})"
46
+ end
47
+
48
+ target = @pointer.tokens.last
49
+ if parent.is_a?(Array)
50
+ if obj == JSONP3::JSONPointer::UNDEFINED
51
+ raise JSONPatchError, "index out of range (#{name}:#{index})" unless target == "-"
52
+
53
+ parent << @value
54
+ else
55
+ parent.insert(target.to_i, @value)
56
+ end
57
+ elsif parent.is_a?(Hash)
58
+ parent[target] = @value
59
+ else
60
+ raise JSONPatchError, "unexpected operation on #{parent.class} (#{name}:#{index})"
61
+ end
62
+
63
+ value
64
+ end
65
+
66
+ def to_h
67
+ { "op" => name, "path" => @pointer.to_s, "value" => @value }
68
+ end
69
+ end
70
+
71
+ # The JSON Patch _remove_ operation.
72
+ class OpRemove < Op
73
+ # @param pointer [JSONPointer]
74
+ def initialize(pointer)
75
+ super()
76
+ @pointer = pointer
77
+ end
78
+
79
+ def name
80
+ "remove"
81
+ end
82
+
83
+ def apply(value, index)
84
+ parent, obj = @pointer.resolve_with_parent(value)
85
+
86
+ if parent == JSONP3::JSONPointer::UNDEFINED && @pointer.tokens.empty?
87
+ raise JSONPatchError,
88
+ "can't remove root (#{name}:#{index})"
89
+ end
90
+
91
+ if parent == JSONP3::JSONPointer::UNDEFINED
92
+ raise JSONPatchError,
93
+ "no such property or item '#{@pointer.parent}' (#{name}:#{index})"
94
+ end
95
+
96
+ target = @pointer.tokens.last
97
+ if target == JSONP3::JSONPointer::UNDEFINED
98
+ raise JSONPatchError,
99
+ "unexpected operation (#{name}:#{index})"
100
+ end
101
+
102
+ if parent.is_a?(Array)
103
+ raise JSONPatchError, "no item to remove (#{name}:#{index})" if obj == JSONP3::JSONPointer::UNDEFINED
104
+
105
+ parent.delete_at(target.to_i)
106
+ elsif parent.is_a?(Hash)
107
+ raise JSONPatchError, "no property to remove (#{name}:#{index})" if obj == JSONP3::JSONPointer::UNDEFINED
108
+
109
+ parent.delete(target)
110
+ else
111
+ raise JSONPatchError, "unexpected operation on #{parent.class} (#{name}:#{index})"
112
+ end
113
+
114
+ value
115
+ end
116
+
117
+ def to_h
118
+ { "op" => name, "path" => @pointer.to_s }
119
+ end
120
+ end
121
+
122
+ # The JSON Patch _replace_ operation.
123
+ class OpReplace < Op
124
+ # @param pointer [JSONPointer]
125
+ # @param value [JSON-like value]
126
+ def initialize(pointer, value)
127
+ super()
128
+ @pointer = pointer
129
+ @value = value
130
+ end
131
+
132
+ def name
133
+ "replace"
134
+ end
135
+
136
+ def apply(value, index)
137
+ parent, obj = @pointer.resolve_with_parent(value)
138
+ return @value if parent == JSONP3::JSONPointer::UNDEFINED && @pointer.tokens.empty?
139
+
140
+ if parent == JSONP3::JSONPointer::UNDEFINED
141
+ raise JSONPatchError,
142
+ "no such property or item '#{@pointer.parent}' (#{name}:#{index})"
143
+ end
144
+
145
+ target = @pointer.tokens.last
146
+ if target == JSONP3::JSONPointer::UNDEFINED
147
+ raise JSONPatchError,
148
+ "unexpected operation (#{name}:#{index})"
149
+ end
150
+
151
+ if parent.is_a?(Array)
152
+ raise JSONPatchError, "no item to replace (#{name}:#{index})" if obj == JSONP3::JSONPointer::UNDEFINED
153
+
154
+ parent[target.to_i] = @value
155
+ elsif parent.is_a?(Hash)
156
+ raise JSONPatchError, "no property to replace (#{name}:#{index})" if obj == JSONP3::JSONPointer::UNDEFINED
157
+
158
+ parent[target] = @value
159
+ else
160
+ raise JSONPatchError, "unexpected operation on #{parent.class} (#{name}:#{index})"
161
+ end
162
+
163
+ value
164
+ end
165
+
166
+ def to_h
167
+ { "op" => name, "path" => @pointer.to_s, "value" => @value }
168
+ end
169
+ end
170
+
171
+ # The JSON Patch _move_ operation.
172
+ class OpMove < Op
173
+ # @param from [JSONPointer]
174
+ # @param pointer [JSONPointer]
175
+ def initialize(from, pointer)
176
+ super()
177
+ @from = from
178
+ @pointer = pointer
179
+ end
180
+
181
+ def name
182
+ "move"
183
+ end
184
+
185
+ def apply(value, index)
186
+ if @pointer.relative_to?(@from)
187
+ raise JSONPatchError,
188
+ "can't move object to one of its children (#{name}:#{index})"
189
+ end
190
+
191
+ # Grab the source value.
192
+ source_parent, source_obj = @from.resolve_with_parent(value)
193
+ if source_obj == JSONP3::JSONPointer::UNDEFINED
194
+ raise JSONPatchError,
195
+ "source object does not exist (#{name}:#{index})"
196
+ end
197
+
198
+ source_target = @from.tokens.last
199
+ if source_target == JSONP3::JSONPointer::UNDEFINED
200
+ raise JSONPatchError,
201
+ "unexpected operation (#{name}:#{index})"
202
+ end
203
+
204
+ # Delete the target value from the source location.
205
+ if source_parent.is_a?(Array)
206
+ source_parent.delete_at(source_target.to_i)
207
+ elsif source_parent.is_a?(Hash)
208
+ source_parent.delete(source_target)
209
+ end
210
+
211
+ # Find the parent of the destination pointer.
212
+ dest_parent, _dest_obj = @pointer.resolve_with_parent(value)
213
+ return source_obj if dest_parent == JSONP3::JSONPointer::UNDEFINED
214
+
215
+ dest_target = @pointer.tokens.last
216
+ if dest_target == JSONP3::JSONPointer::UNDEFINED
217
+ raise JSONPatchError,
218
+ "unexpected operation (#{name}:#{index})"
219
+ end
220
+
221
+ # Write the source value to the destination.
222
+ if dest_parent.is_a?(Array)
223
+ if dest_target == "-"
224
+ dest_parent << source_obj
225
+ else
226
+ dest_parent[dest_target.to_i] = source_obj
227
+ end
228
+ elsif dest_parent.is_a?(Hash)
229
+ dest_parent[dest_target] = source_obj
230
+ end
231
+
232
+ value
233
+ end
234
+
235
+ def to_h
236
+ { "op" => name, "from" => @from.to_s, "path" => @pointer.to_s }
237
+ end
238
+ end
239
+
240
+ # The JSON Patch _copy_ operation.
241
+ class OpCopy < Op
242
+ # @param from [JSONPointer]
243
+ # @param pointer [JSONPointer]
244
+ def initialize(from, pointer)
245
+ super()
246
+ @from = from
247
+ @pointer = pointer
248
+ end
249
+
250
+ def name
251
+ "copy"
252
+ end
253
+
254
+ def apply(value, index)
255
+ # Grab the source value.
256
+ _source_parent, source_obj = @from.resolve_with_parent(value)
257
+ if source_obj == JSONP3::JSONPointer::UNDEFINED
258
+ raise JSONPatchError,
259
+ "source object does not exist (#{name}:#{index})"
260
+ end
261
+
262
+ # Find the parent of the destination pointer.
263
+ dest_parent, _dest_obj = @pointer.resolve_with_parent(value)
264
+ return deep_copy(source_obj) if dest_parent == JSONP3::JSONPointer::UNDEFINED
265
+
266
+ dest_target = @pointer.tokens.last
267
+ if dest_target == JSONP3::JSONPointer::UNDEFINED
268
+ raise JSONPatchError,
269
+ "unexpected operation (#{name}:#{index})"
270
+ end
271
+
272
+ # Write the source value to the destination.
273
+ if dest_parent.is_a?(Array)
274
+ if dest_target == "-"
275
+ dest_parent << source_obj
276
+ else
277
+ dest_parent.insert(dest_target.to_i, deep_copy(source_obj))
278
+ end
279
+ elsif dest_parent.is_a?(Hash)
280
+ dest_parent[dest_target] = deep_copy(source_obj)
281
+ else
282
+ raise JSONPatchError, "unexpected operation on #{dest_parent.class} (#{name}:#{index})"
283
+ end
284
+
285
+ value
286
+ end
287
+
288
+ def to_h
289
+ { "op" => name, "from" => @from.to_s, "path" => @pointer.to_s }
290
+ end
291
+
292
+ private
293
+
294
+ def deep_copy(obj)
295
+ Marshal.load(Marshal.dump(obj))
296
+ end
297
+ end
298
+
299
+ # The JSON Patch _test_ operation.
300
+ class OpTest < Op
301
+ # @param pointer [JSONPointer]
302
+ # @param value [JSON-like value]
303
+ def initialize(pointer, value)
304
+ super()
305
+ @pointer = pointer
306
+ @value = value
307
+ end
308
+
309
+ def name
310
+ "test"
311
+ end
312
+
313
+ def apply(value, index)
314
+ obj = @pointer.resolve(value)
315
+ raise JSONPatchTestFailure, "test failed (#{name}:#{index})" if obj != @value
316
+
317
+ value
318
+ end
319
+
320
+ def to_h
321
+ { "op" => name, "path" => @pointer.to_s, "value" => @value }
322
+ end
323
+ end
324
+
325
+ # A JSON Patch containing zero or more patch operations.
326
+ class JSONPatch
327
+ # @param ops [Array<Op | Hash<String, untyped>>?]
328
+ def initialize(ops = nil)
329
+ @ops = []
330
+ build(ops) unless ops.nil?
331
+ end
332
+
333
+ # @param pointer [String | JSONPointer]
334
+ # @param value [JSON-like value]
335
+ # @return [self]
336
+ def add(pointer, value)
337
+ @ops.push(OpAdd.new(ensure_pointer(pointer, :add, @ops.length), value))
338
+ self
339
+ end
340
+
341
+ # @param pointer [String | JSONPointer]
342
+ # @return [self]
343
+ def remove(pointer)
344
+ @ops.push(OpRemove.new(ensure_pointer(pointer, :remove, @ops.length)))
345
+ self
346
+ end
347
+
348
+ # @param pointer [String | JSONPointer]
349
+ # @param value [JSON-like value]
350
+ # @return [self]
351
+ def replace(pointer, value)
352
+ @ops.push(OpReplace.new(ensure_pointer(pointer, :replace, @ops.length), value))
353
+ self
354
+ end
355
+
356
+ # @param from [String | JSONPointer]
357
+ # @param pointer [String | JSONPointer]
358
+ # @return [self]
359
+ def move(from, pointer)
360
+ @ops.push(OpMove.new(
361
+ ensure_pointer(from, :move, @ops.length),
362
+ ensure_pointer(pointer, :move, @ops.length)
363
+ ))
364
+ self
365
+ end
366
+
367
+ # @param from [String | JSONPointer]
368
+ # @param pointer [String | JSONPointer]
369
+ # @return [self]
370
+ def copy(from, pointer)
371
+ @ops.push(OpCopy.new(
372
+ ensure_pointer(from, :copy, @ops.length),
373
+ ensure_pointer(pointer, :copy, @ops.length)
374
+ ))
375
+ self
376
+ end
377
+
378
+ # @param pointer [String | JSONPointer]
379
+ # @param value [JSON-like value]
380
+ # @return [self]
381
+ def test(pointer, value)
382
+ @ops.push(OpTest.new(ensure_pointer(pointer, :test, @ops.length), value))
383
+ self
384
+ end
385
+
386
+ # Apply this patch to JSON-like value _value_.
387
+ def apply(value)
388
+ @ops.each_with_index { |op, i| value = op.apply(value, i) }
389
+ value
390
+ end
391
+
392
+ def to_a
393
+ @ops.map(&:to_h)
394
+ end
395
+
396
+ private
397
+
398
+ # @param ops [Array<Op | Hash<String, untyped>>?]
399
+ # @return void
400
+ def build(ops)
401
+ ops.each_with_index do |obj, i|
402
+ if obj.is_a?(Op)
403
+ @ops << obj
404
+ next
405
+ end
406
+
407
+ case obj["op"]
408
+ when "add"
409
+ add(op_pointer(obj, "path", "add", i), op_value(obj, "value", "add", i))
410
+ when "remove"
411
+ remove(op_pointer(obj, "path", "remove", i))
412
+ when "replace"
413
+ replace(op_pointer(obj, "path", "replace", i), op_value(obj, "value", "replace", i))
414
+ when "move"
415
+ move(op_pointer(obj, "from", "move", i), op_pointer(obj, "path", "move", i))
416
+ when "copy"
417
+ copy(op_pointer(obj, "from", "copy", i), op_pointer(obj, "path", "copy", i))
418
+ when "test"
419
+ test(op_pointer(obj, "path", "test", i), op_value(obj, "value", "test", i))
420
+ else
421
+ raise JSONPatchError,
422
+ "expected 'op' to be one of 'add', 'remove', 'replace', 'move', 'copy' or 'test' (#{obj["op"]}:#{i})"
423
+ end
424
+ end
425
+ end
426
+
427
+ def op_pointer(obj, key, op, index)
428
+ raise JSONPatchError, "missing property '#{key}' (#{op}:#{index})" unless obj.key?(key)
429
+
430
+ JSONP3::JSONPointer.new(obj[key])
431
+ rescue JSONPointerError
432
+ raise JSONPatchError, "#{$ERROR_INFO} (#{op}:#{index})"
433
+ end
434
+
435
+ def op_value(obj, key, op, index)
436
+ raise JSONPatchError, "missing property '#{key}' (#{op}:#{index})" unless obj.key?(key)
437
+
438
+ obj[key]
439
+ end
440
+
441
+ def ensure_pointer(pointer, op, index)
442
+ return pointer unless pointer.is_a?(String)
443
+
444
+ JSONP3::JSONPointer.new(pointer)
445
+ rescue JSONPointerError
446
+ raise JSONPatchError, "#{$ERROR_INFO} (#{op}:#{index})"
447
+ end
448
+ end
449
+ end
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "errors"
4
+
5
+ module JSONP3
6
+ # Identify a single value in JSON-like data, as per RFC 6901.
7
+ class JSONPointer
8
+ RE_INT = /\A(0|[1-9][0-9]*)\z/
9
+ UNDEFINED = :__undefined
10
+
11
+ attr_reader :tokens
12
+
13
+ # Encode an array of strings and integers into a JSON Pointer.
14
+ # @param tokens [Array<String | Integer> | nil]
15
+ # @return [String]
16
+ def self.encode(tokens)
17
+ return "" if tokens.nil? || tokens.empty?
18
+
19
+ encoded = tokens.map do |token|
20
+ token.is_a?(Integer) ? token.to_s : token.gsub("~", "~0").gsub("/", "~1")
21
+ end
22
+
23
+ "/#{encoded.join("/")}"
24
+ end
25
+
26
+ # @param pointer [String]
27
+ def initialize(pointer)
28
+ @tokens = parse(pointer)
29
+ @pointer = JSONPointer.encode(@tokens)
30
+ end
31
+
32
+ # Resolve this pointer against JSON-like data _value_.
33
+ # @param value [Object]
34
+ # @param default [Object] the value to return if this pointer can not be
35
+ # resolved against _value_.
36
+ def resolve(value, default: UNDEFINED)
37
+ item = value
38
+
39
+ @tokens.each do |token|
40
+ item = get_item(item, token)
41
+ return default if item == UNDEFINED
42
+ end
43
+
44
+ item
45
+ end
46
+
47
+ # Resolve this pointer against _value_, returning the resolved object and its
48
+ # parent object.
49
+ #
50
+ # @param value [Object]
51
+ # @return [Array<Object>] an array with exactly two elements, one or both of
52
+ # which could be undefined.
53
+ def resolve_with_parent(value)
54
+ return [UNDEFINED, resolve(value)] if @tokens.empty?
55
+
56
+ parent = value
57
+ (@tokens[...-1] || raise).each do |token|
58
+ parent = get_item(parent, token)
59
+ break if parent == UNDEFINED
60
+ end
61
+
62
+ [parent, get_item(parent, @tokens.last)]
63
+ end
64
+
65
+ # Return true if this pointer is relative to _pointer_.
66
+ # @param pointer [JSONPointer]
67
+ # @return [bool]
68
+ def relative_to?(pointer)
69
+ pointer.tokens.length < @tokens.length && @tokens[...pointer.tokens.length] == pointer.tokens
70
+ end
71
+
72
+ # @param parts [String]
73
+ # @return [JSONPointer]
74
+ def join(*parts)
75
+ pointer = self
76
+ parts.each do |part|
77
+ pointer = pointer._join(part)
78
+ end
79
+ pointer
80
+ end
81
+
82
+ # Return _true_ if this pointer can be resolved against _value_, even if the resolved
83
+ # value is false or nil.
84
+ # @param value [Object]
85
+ def exist?(value)
86
+ resolve(value) != UNDEFINED
87
+ end
88
+
89
+ # Return this pointer's parent as a new pointer. If this pointer points to the
90
+ # document root, self is returned.
91
+ def parent
92
+ return self if @tokens.empty?
93
+
94
+ JSONPointer.new(JSONPointer.encode((@tokens[...-1] || raise)))
95
+ end
96
+
97
+ # Return a new pointer relative to this pointer using Relative JSON Pointer syntax.
98
+ # @param rel [String | RelativeJSONPointer]
99
+ # @return [JSONPointer]
100
+ def to(rel)
101
+ p = rel.is_a?(String) ? RelativeJSONPointer.new(rel) : rel
102
+ p.to(self)
103
+ end
104
+
105
+ def to_s
106
+ @pointer
107
+ end
108
+
109
+ protected
110
+
111
+ # @param pointer [String]
112
+ # @return [Array<String | Integer>]
113
+ def parse(pointer)
114
+ if pointer.length.positive? && !pointer.start_with?("/")
115
+ raise JSONPointerSyntaxError,
116
+ "pointers must start with a slash or be the empty string"
117
+ end
118
+
119
+ return [] if pointer.empty?
120
+ return [""] if pointer == "/"
121
+
122
+ (pointer[1..] || raise).split("/", -1).map do |token|
123
+ token.match?(/\A(?:0|[1-9][0-9]*)\z/) ? Integer(token) : token.gsub("~1", "/").gsub("~0", "~")
124
+ end
125
+ end
126
+
127
+ # @param value [Object]
128
+ # @param token [String | Integer]
129
+ # @return [Object] the "fetched" object from _value_ or UNDEFINED.
130
+ def get_item(value, token)
131
+ if value.is_a?(Array)
132
+ if token.is_a?(String) && token.start_with?("#")
133
+ maybe_index = token[1..] || raise
134
+ return maybe_index.to_i if RE_INT.match?(maybe_index)
135
+ end
136
+
137
+ return UNDEFINED unless token.is_a?(Integer)
138
+ return UNDEFINED if token.negative? || token >= value.length
139
+
140
+ value[token]
141
+ elsif value.is_a?(Hash)
142
+ return value[token] if value.key?(token)
143
+
144
+ # Handle "#" from relative JSON pointer
145
+ return token[1..] if token.is_a?(String) && token.start_with?("#") && value.key?(token[1..])
146
+
147
+ # Token might be an integer. Force it to a string and try again.
148
+ string_token = token.to_s
149
+ value.key?(string_token) ? value[string_token] : UNDEFINED
150
+ else
151
+ UNDEFINED
152
+ end
153
+ end
154
+
155
+ # Like `#parse`, but assumes there's no leading slash.
156
+ # @param pointer [String]
157
+ # @return [Array<String | Integer>]
158
+ def _parse(pointer)
159
+ return [] if pointer.empty?
160
+
161
+ pointer.split("/", -1).map do |token|
162
+ token.match?(/\A(?:0|[1-9][0-9]*)\z/) ? Integer(token) : token.gsub("~1", "/").gsub("~0", "~")
163
+ end
164
+ end
165
+
166
+ def _join(other)
167
+ raise JSONPointerTypeError, "unsupported join part" unless other.is_a?(String)
168
+
169
+ part = other.lstrip
170
+ part.start_with?("/") ? JSONPointer.new(part) : JSONPointer.new(JSONPointer.encode(@tokens + _parse(part)))
171
+ end
172
+ end
173
+
174
+ # A relative JSON Pointer.
175
+ # See https://datatracker.ietf.org/doc/html/draft-hha-relative-json-pointer
176
+ class RelativeJSONPointer
177
+ RE_RELATIVE_POINTER = /\A(?<ORIGIN>\d+)(?<INDEX_G>(?<SIGN>[+-])(?<INDEX>\d))?(?<POINTER>.*)\z/m
178
+ RE_INT = /\A(0|[1-9][0-9]*)\z/
179
+
180
+ # @param rel [String]
181
+ def initialize(rel)
182
+ match = RE_RELATIVE_POINTER.match(rel)
183
+
184
+ raise JSONPointerSyntaxError, "failed to parse relative pointer" if match.nil?
185
+
186
+ @origin = parse_int(match[:ORIGIN] || raise)
187
+ @index = 0
188
+
189
+ if match[:INDEX_G]
190
+ @index = parse_int(match[:INDEX] || raise)
191
+ raise JSONPointerSyntaxError, "index offset can't be zero" if @index.zero?
192
+
193
+ @index = -@index if match[:SIGN] == "-"
194
+ end
195
+
196
+ @pointer = match[:POINTER] == "#" ? "#" : JSONPointer.new(match[:POINTER] || raise)
197
+ end
198
+
199
+ def to_s
200
+ sign = @index.positive? ? "+" : ""
201
+ index = @index.zero? ? "" : "#{sign}#{@index}"
202
+ "#{@origin}#{index}#{@pointer}"
203
+ end
204
+
205
+ # Return a new JSON Pointer by applying this relative pointer to _pointer_.
206
+ # @param pointer [String | JSONPointer]
207
+ # @return [JSONPointer]
208
+ def to(pointer)
209
+ p = pointer.is_a?(String) ? JSONPointer.new(pointer) : pointer
210
+
211
+ raise JSONPointerIndexError, "origin (#{@origin}) exceeds root (#{p.tokens.length})" if @origin > p.tokens.length
212
+
213
+ tokens = @origin < 1 ? p.tokens[0..] || raise : p.tokens[0...-@origin] || raise
214
+ tokens[-1] = (tokens[-1] || raise) + @index if @index != 0 && tokens.length.positive? && tokens[-1].is_a?(Integer)
215
+
216
+ if @pointer == "#"
217
+ tokens[-1] = "##{tokens[-1]}"
218
+ else
219
+ tokens.concat(@pointer.tokens) # steep:ignore
220
+ end
221
+
222
+ JSONPointer.new(JSONPointer.encode(tokens))
223
+ end
224
+
225
+ private
226
+
227
+ # @param token [String]
228
+ # @return [Integer]
229
+ def parse_int(token)
230
+ raise JSONPointerSyntaxError, "unexpected leading zero" if token.start_with?("0") && token.length > 1
231
+ raise JSONPointerSyntaxError, "expected an integer, found '#{token}'" unless RE_INT.match?(token)
232
+
233
+ token.to_i
234
+ end
235
+ end
236
+ end
@@ -21,7 +21,7 @@ module JSONP3
21
21
  # The child selection segment.
22
22
  class ChildSegment < Segment
23
23
  def resolve(nodes)
24
- rv = []
24
+ rv = [] # : Array[JSONPathNode]
25
25
  nodes.each do |node|
26
26
  @selectors.each do |selector|
27
27
  rv.concat selector.resolve(node)
@@ -50,7 +50,7 @@ module JSONP3
50
50
  # The recursive descent segment
51
51
  class RecursiveDescentSegment < Segment
52
52
  def resolve(nodes)
53
- rv = []
53
+ rv = [] # : Array[JSONPathNode]
54
54
  nodes.each do |node|
55
55
  visit(node).each do |descendant|
56
56
  @selectors.each do |selector|
@@ -79,7 +79,7 @@ module JSONP3
79
79
 
80
80
  protected
81
81
 
82
- def visit(node, depth = 1) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
82
+ def visit(node, depth = 1)
83
83
  raise JSONPathRecursionError.new("recursion limit exceeded", @token) if depth > @env.class::MAX_RECURSION_DEPTH
84
84
 
85
85
  rv = [node]