json_p3 0.2.1 → 0.3.0

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,441 @@
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
+ dest_parent[dest_target.to_i] = source_obj
224
+ elsif dest_parent.is_a?(Hash)
225
+ dest_parent[dest_target] = source_obj
226
+ end
227
+
228
+ value
229
+ end
230
+
231
+ def to_h
232
+ { "op" => name, "from" => @from.to_s, "path" => @pointer.to_s }
233
+ end
234
+ end
235
+
236
+ # The JSON Patch _copy_ operation.
237
+ class OpCopy < Op
238
+ # @param from [JSONPointer]
239
+ # @param pointer [JSONPointer]
240
+ def initialize(from, pointer)
241
+ super()
242
+ @from = from
243
+ @pointer = pointer
244
+ end
245
+
246
+ def name
247
+ "copy"
248
+ end
249
+
250
+ def apply(value, index)
251
+ # Grab the source value.
252
+ _source_parent, source_obj = @from.resolve_with_parent(value)
253
+ if source_obj == JSONP3::JSONPointer::UNDEFINED
254
+ raise JSONPatchError,
255
+ "source object does not exist (#{name}:#{index})"
256
+ end
257
+
258
+ # Find the parent of the destination pointer.
259
+ dest_parent, _dest_obj = @pointer.resolve_with_parent(value)
260
+ return deep_copy(source_obj) if dest_parent == JSONP3::JSONPointer::UNDEFINED
261
+
262
+ dest_target = @pointer.tokens.last
263
+ if dest_target == JSONP3::JSONPointer::UNDEFINED
264
+ raise JSONPatchError,
265
+ "unexpected operation (#{name}:#{index})"
266
+ end
267
+
268
+ # Write the source value to the destination.
269
+ if dest_parent.is_a?(Array)
270
+ dest_parent.insert(dest_target.to_i, deep_copy(source_obj))
271
+ elsif dest_parent.is_a?(Hash)
272
+ dest_parent[dest_target] = deep_copy(source_obj)
273
+ else
274
+ raise JSONPatchError, "unexpected operation on #{dest_parent.class} (#{name}:#{index})"
275
+ end
276
+
277
+ value
278
+ end
279
+
280
+ def to_h
281
+ { "op" => name, "from" => @from.to_s, "path" => @pointer.to_s }
282
+ end
283
+
284
+ private
285
+
286
+ def deep_copy(obj)
287
+ Marshal.load(Marshal.dump(obj))
288
+ end
289
+ end
290
+
291
+ # The JSON Patch _test_ operation.
292
+ class OpTest < Op
293
+ # @param pointer [JSONPointer]
294
+ # @param value [JSON-like value]
295
+ def initialize(pointer, value)
296
+ super()
297
+ @pointer = pointer
298
+ @value = value
299
+ end
300
+
301
+ def name
302
+ "test"
303
+ end
304
+
305
+ def apply(value, index)
306
+ obj = @pointer.resolve(value)
307
+ raise JSONPatchTestFailure, "test failed (#{name}:#{index})" if obj != @value
308
+
309
+ value
310
+ end
311
+
312
+ def to_h
313
+ { "op" => name, "path" => @pointer.to_s, "value" => @value }
314
+ end
315
+ end
316
+
317
+ # A JSON Patch containing zero or more patch operations.
318
+ class JSONPatch
319
+ # @param ops [Array<Op | Hash<String, untyped>>?]
320
+ def initialize(ops = nil)
321
+ @ops = []
322
+ build(ops) unless ops.nil?
323
+ end
324
+
325
+ # @param pointer [String | JSONPointer]
326
+ # @param value [JSON-like value]
327
+ # @return [self]
328
+ def add(pointer, value)
329
+ @ops.push(OpAdd.new(ensure_pointer(pointer, :add, @ops.length), value))
330
+ self
331
+ end
332
+
333
+ # @param pointer [String | JSONPointer]
334
+ # @return [self]
335
+ def remove(pointer)
336
+ @ops.push(OpRemove.new(ensure_pointer(pointer, :remove, @ops.length)))
337
+ self
338
+ end
339
+
340
+ # @param pointer [String | JSONPointer]
341
+ # @param value [JSON-like value]
342
+ # @return [self]
343
+ def replace(pointer, value)
344
+ @ops.push(OpReplace.new(ensure_pointer(pointer, :replace, @ops.length), value))
345
+ self
346
+ end
347
+
348
+ # @param from [String | JSONPointer]
349
+ # @param pointer [String | JSONPointer]
350
+ # @return [self]
351
+ def move(from, pointer)
352
+ @ops.push(OpMove.new(
353
+ ensure_pointer(from, :move, @ops.length),
354
+ ensure_pointer(pointer, :move, @ops.length)
355
+ ))
356
+ self
357
+ end
358
+
359
+ # @param from [String | JSONPointer]
360
+ # @param pointer [String | JSONPointer]
361
+ # @return [self]
362
+ def copy(from, pointer)
363
+ @ops.push(OpCopy.new(
364
+ ensure_pointer(from, :copy, @ops.length),
365
+ ensure_pointer(pointer, :copy, @ops.length)
366
+ ))
367
+ self
368
+ end
369
+
370
+ # @param pointer [String | JSONPointer]
371
+ # @param value [JSON-like value]
372
+ # @return [self]
373
+ def test(pointer, value)
374
+ @ops.push(OpTest.new(ensure_pointer(pointer, :test, @ops.length), value))
375
+ self
376
+ end
377
+
378
+ # Apply this patch to JSON-like value _value_.
379
+ def apply(value)
380
+ @ops.each_with_index { |op, i| value = op.apply(value, i) }
381
+ value
382
+ end
383
+
384
+ def to_a
385
+ @ops.map(&:to_h)
386
+ end
387
+
388
+ private
389
+
390
+ # @param ops [Array<Op | Hash<String, untyped>>?]
391
+ # @return void
392
+ def build(ops)
393
+ ops.each_with_index do |obj, i|
394
+ if obj.is_a?(Op)
395
+ @ops << obj
396
+ next
397
+ end
398
+
399
+ case obj["op"]
400
+ when "add"
401
+ add(op_pointer(obj, "path", "add", i), op_value(obj, "value", "add", i))
402
+ when "remove"
403
+ remove(op_pointer(obj, "path", "remove", i))
404
+ when "replace"
405
+ replace(op_pointer(obj, "path", "replace", i), op_value(obj, "value", "replace", i))
406
+ when "move"
407
+ move(op_pointer(obj, "from", "move", i), op_pointer(obj, "path", "move", i))
408
+ when "copy"
409
+ copy(op_pointer(obj, "from", "copy", i), op_pointer(obj, "path", "copy", i))
410
+ when "test"
411
+ test(op_pointer(obj, "path", "test", i), op_value(obj, "value", "test", i))
412
+ else
413
+ raise JSONPatchError,
414
+ "expected 'op' to be one of 'add', 'remove', 'replace', 'move', 'copy' or 'test' (#{obj["op"]}:#{i})"
415
+ end
416
+ end
417
+ end
418
+
419
+ def op_pointer(obj, key, op, index)
420
+ raise JSONPatchError, "missing property '#{key}' (#{op}:#{index})" unless obj.key?(key)
421
+
422
+ JSONP3::JSONPointer.new(obj[key])
423
+ rescue JSONPointerError
424
+ raise JSONPatchError, "#{$ERROR_INFO} (#{op}:#{index})"
425
+ end
426
+
427
+ def op_value(obj, key, op, index)
428
+ raise JSONPatchError, "missing property '#{key}' (#{op}:#{index})" unless obj.key?(key)
429
+
430
+ obj[key]
431
+ end
432
+
433
+ def ensure_pointer(pointer, op, index)
434
+ return pointer unless pointer.is_a?(String)
435
+
436
+ JSONP3::JSONPointer.new(pointer)
437
+ rescue JSONPointerError
438
+ raise JSONPatchError, "#{$ERROR_INFO} (#{op}:#{index})"
439
+ end
440
+ end
441
+ 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]