json_p3 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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]