schemurai 2.1.0 → 2.2.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.
- checksums.yaml +4 -4
- data/README.md +4 -2
- data/lib/schemurai/evaluation.rb +19 -2
- data/lib/schemurai/schema_domain.rb +27 -19
- data/lib/schemurai/schema_graph.rb +70 -37
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/compiler.rb +166 -129
- data/lib/schemurai/vm/evaluator.rb +479 -190
- data/sig/schemurai.rbs +100 -0
- metadata +3 -2
|
@@ -8,8 +8,49 @@ require_relative "compiler"
|
|
|
8
8
|
|
|
9
9
|
module Schemurai
|
|
10
10
|
module VM
|
|
11
|
+
class EvaluationBuffer
|
|
12
|
+
attr_reader :evaluated_properties, :evaluated_items
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@evaluated_properties = []
|
|
16
|
+
@evaluated_items = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def valid? = true
|
|
20
|
+
|
|
21
|
+
def reset
|
|
22
|
+
@evaluated_properties.clear
|
|
23
|
+
@evaluated_items.clear
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def record_property(name)
|
|
28
|
+
@evaluated_properties << name unless @evaluated_properties.include?(name)
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def record_item(index)
|
|
33
|
+
@evaluated_items << index unless @evaluated_items.include?(index)
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def merge(other)
|
|
38
|
+
return Evaluation.invalid unless other.valid?
|
|
39
|
+
|
|
40
|
+
merge_locations(@evaluated_properties, other.evaluated_properties)
|
|
41
|
+
merge_locations(@evaluated_items, other.evaluated_items)
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private def merge_locations(target, locations)
|
|
46
|
+
locations.each { |location| target << location unless target.include?(location) }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
private_constant :EvaluationBuffer
|
|
50
|
+
|
|
11
51
|
class Evaluator
|
|
12
52
|
MISSING_SEGMENT = Object.new.freeze
|
|
53
|
+
DECIMAL_CACHE_LIMIT = 16
|
|
13
54
|
|
|
14
55
|
def backend = :vm
|
|
15
56
|
|
|
@@ -21,6 +62,14 @@ module Schemurai
|
|
|
21
62
|
@validate_format = format
|
|
22
63
|
@regexps = nil
|
|
23
64
|
@active = nil
|
|
65
|
+
@resolved_references = nil
|
|
66
|
+
@decimals = nil
|
|
67
|
+
@multiple_results = nil
|
|
68
|
+
@dynamic_scope = nil
|
|
69
|
+
@instance_path_buffer = nil
|
|
70
|
+
@schema_path_buffer = nil
|
|
71
|
+
@evaluation_pool = nil
|
|
72
|
+
@evaluation_pool_index = 0
|
|
24
73
|
end
|
|
25
74
|
|
|
26
75
|
def validate(instance)
|
|
@@ -34,31 +83,52 @@ module Schemurai
|
|
|
34
83
|
|
|
35
84
|
def valid?(instance)
|
|
36
85
|
@errors = nil
|
|
37
|
-
|
|
86
|
+
@error_count = 0
|
|
87
|
+
@dynamic_scope&.clear
|
|
88
|
+
@instance_path = nil
|
|
89
|
+
@schema_path = nil
|
|
90
|
+
reset_evaluation_pool
|
|
38
91
|
evaluate_valid(@root, instance)
|
|
39
92
|
end
|
|
40
93
|
|
|
41
94
|
private def prepare_evaluation(paths:)
|
|
42
95
|
@error_count = 0
|
|
43
|
-
@dynamic_scope
|
|
44
|
-
|
|
45
|
-
|
|
96
|
+
@dynamic_scope&.clear
|
|
97
|
+
reset_evaluation_pool
|
|
98
|
+
if paths
|
|
99
|
+
(@instance_path_buffer ||= []).clear
|
|
100
|
+
(@schema_path_buffer ||= []).clear
|
|
101
|
+
@instance_path = @instance_path_buffer
|
|
102
|
+
@schema_path = @schema_path_buffer
|
|
103
|
+
else
|
|
104
|
+
@instance_path = nil
|
|
105
|
+
@schema_path = nil
|
|
106
|
+
end
|
|
46
107
|
end
|
|
47
108
|
|
|
48
109
|
private def evaluate_valid(program, instance)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
110
|
+
code = program.code
|
|
111
|
+
flags = code[0]
|
|
112
|
+
entered_scope = false
|
|
113
|
+
unless flags.zero?
|
|
114
|
+
return evaluate(program, instance).valid? if (flags & TRACKS_EVALUATION) != 0
|
|
115
|
+
|
|
116
|
+
if (flags & TRACKS_DYNAMIC_SCOPE) != 0
|
|
117
|
+
resource = program.node.resource
|
|
118
|
+
unless @dynamic_scope&.last.equal?(resource)
|
|
119
|
+
(@dynamic_scope ||= []) << resource
|
|
120
|
+
entered_scope = true
|
|
121
|
+
end
|
|
122
|
+
end
|
|
53
123
|
end
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
124
|
+
instruction_index = 1
|
|
125
|
+
while (opcode = code[instruction_index])
|
|
126
|
+
operand = code[instruction_index + 1]
|
|
57
127
|
case opcode
|
|
58
128
|
when :boolean
|
|
59
129
|
return operand
|
|
60
130
|
when :ref
|
|
61
|
-
target =
|
|
131
|
+
target = reference_target(program, operand)
|
|
62
132
|
return false unless valid_reference?(program, target, instance)
|
|
63
133
|
when :recursive_ref
|
|
64
134
|
target = recursive_target(program, operand)
|
|
@@ -75,11 +145,22 @@ module Schemurai
|
|
|
75
145
|
when :type_array
|
|
76
146
|
return false unless instance.is_a?(Array)
|
|
77
147
|
when :type_number
|
|
78
|
-
return false unless
|
|
148
|
+
return false unless instance.is_a?(Numeric) && !instance.is_a?(Complex)
|
|
79
149
|
when :type_integer
|
|
80
|
-
return false unless
|
|
150
|
+
return false unless instance.is_a?(Numeric) && !instance.is_a?(Complex) && instance.finite? && instance.to_i == instance
|
|
81
151
|
when :type_string
|
|
82
152
|
return false unless instance.is_a?(String)
|
|
153
|
+
when :typed_number
|
|
154
|
+
return false unless instance.is_a?(Numeric) && !instance.is_a?(Complex) && valid_number?(operand, instance)
|
|
155
|
+
when :typed_integer
|
|
156
|
+
return false unless instance.is_a?(Numeric) && !instance.is_a?(Complex) && instance.finite? &&
|
|
157
|
+
instance.to_i == instance && valid_number?(operand, instance)
|
|
158
|
+
when :typed_string
|
|
159
|
+
return false unless instance.is_a?(String) && valid_string?(operand, instance)
|
|
160
|
+
when :typed_array
|
|
161
|
+
return false unless instance.is_a?(Array) && valid_array?(operand, instance)
|
|
162
|
+
when :typed_object
|
|
163
|
+
return false unless instance.is_a?(Hash) && valid_object?(operand, instance)
|
|
83
164
|
when :types
|
|
84
165
|
return false if (operand.mask & instance_type_mask(instance, integer: (operand.mask & TYPE_INTEGER) != 0)).zero?
|
|
85
166
|
when :enum
|
|
@@ -101,8 +182,8 @@ module Schemurai
|
|
|
101
182
|
return false if evaluate_valid(operand, instance)
|
|
102
183
|
when :conditional
|
|
103
184
|
if evaluate_valid(operand.condition, instance)
|
|
104
|
-
return false if operand.
|
|
105
|
-
elsif operand.
|
|
185
|
+
return false if operand.then_branch && !evaluate_valid(operand.then_branch, instance)
|
|
186
|
+
elsif operand.else_branch && !evaluate_valid(operand.else_branch, instance)
|
|
106
187
|
return false
|
|
107
188
|
end
|
|
108
189
|
when :number
|
|
@@ -116,6 +197,7 @@ module Schemurai
|
|
|
116
197
|
else
|
|
117
198
|
raise "unknown VM instruction #{opcode.inspect}"
|
|
118
199
|
end
|
|
200
|
+
instruction_index += 2
|
|
119
201
|
end
|
|
120
202
|
true
|
|
121
203
|
rescue ResolutionError
|
|
@@ -124,12 +206,34 @@ module Schemurai
|
|
|
124
206
|
leave_scope if entered_scope
|
|
125
207
|
end
|
|
126
208
|
|
|
209
|
+
private def reset_evaluation_pool
|
|
210
|
+
@evaluation_pool_index = 0
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
private def tracked_evaluation
|
|
214
|
+
pool = (@evaluation_pool ||= [])
|
|
215
|
+
index = @evaluation_pool_index
|
|
216
|
+
@evaluation_pool_index += 1
|
|
217
|
+
(pool[index] ||= EvaluationBuffer.new).reset
|
|
218
|
+
end
|
|
219
|
+
|
|
127
220
|
private def evaluate(program, instance)
|
|
128
221
|
before = @error_count
|
|
129
222
|
evaluation = Evaluation.valid
|
|
130
|
-
|
|
223
|
+
code = program.code
|
|
224
|
+
flags = code[0]
|
|
225
|
+
entered_scope = false
|
|
226
|
+
if (flags & TRACKS_DYNAMIC_SCOPE) != 0
|
|
227
|
+
resource = program.node.resource
|
|
228
|
+
unless @dynamic_scope&.last.equal?(resource)
|
|
229
|
+
(@dynamic_scope ||= []) << resource
|
|
230
|
+
entered_scope = true
|
|
231
|
+
end
|
|
232
|
+
end
|
|
131
233
|
|
|
132
|
-
|
|
234
|
+
instruction_index = 1
|
|
235
|
+
while (opcode = code[instruction_index])
|
|
236
|
+
operand = code[instruction_index + 1]
|
|
133
237
|
case opcode
|
|
134
238
|
when :boolean
|
|
135
239
|
if operand == false
|
|
@@ -156,6 +260,42 @@ module Schemurai
|
|
|
156
260
|
check_compiled_type("integer", integer?(instance), instance)
|
|
157
261
|
when :type_string
|
|
158
262
|
check_compiled_type("string", instance.is_a?(String), instance)
|
|
263
|
+
when :typed_number
|
|
264
|
+
if number?(instance)
|
|
265
|
+
check_number(operand, instance)
|
|
266
|
+
else
|
|
267
|
+
check_compiled_type("number", false, instance)
|
|
268
|
+
end
|
|
269
|
+
when :typed_integer
|
|
270
|
+
if instance.is_a?(Numeric) && !instance.is_a?(Complex)
|
|
271
|
+
integer = instance.finite? && instance.to_i == instance
|
|
272
|
+
check_compiled_type("integer", integer, instance)
|
|
273
|
+
check_number(operand, instance)
|
|
274
|
+
else
|
|
275
|
+
check_compiled_type("integer", false, instance)
|
|
276
|
+
end
|
|
277
|
+
when :typed_string
|
|
278
|
+
if instance.is_a?(String)
|
|
279
|
+
check_string(operand, instance)
|
|
280
|
+
else
|
|
281
|
+
check_compiled_type("string", false, instance)
|
|
282
|
+
end
|
|
283
|
+
when :typed_array
|
|
284
|
+
if instance.is_a?(Array)
|
|
285
|
+
result = check_array(operand, instance, evaluation)
|
|
286
|
+
return Evaluation.invalid if !@errors && (!result.valid? || @error_count != before)
|
|
287
|
+
evaluation = evaluation.merge(result)
|
|
288
|
+
else
|
|
289
|
+
check_compiled_type("array", false, instance)
|
|
290
|
+
end
|
|
291
|
+
when :typed_object
|
|
292
|
+
if instance.is_a?(Hash)
|
|
293
|
+
result = check_object(operand, instance, evaluation)
|
|
294
|
+
return Evaluation.invalid if !@errors && (!result.valid? || @error_count != before)
|
|
295
|
+
evaluation = evaluation.merge(result)
|
|
296
|
+
else
|
|
297
|
+
check_compiled_type("object", false, instance)
|
|
298
|
+
end
|
|
159
299
|
when :types
|
|
160
300
|
check_compiled_types(operand, instance)
|
|
161
301
|
when :enum
|
|
@@ -163,50 +303,52 @@ module Schemurai
|
|
|
163
303
|
when :const
|
|
164
304
|
add_error("const") { Internal::ErrorMessage.const } unless json_equal?(operand, instance)
|
|
165
305
|
when :allOf, :anyOf, :oneOf, :not, :conditional
|
|
166
|
-
|
|
306
|
+
result = check_combiner(opcode, operand, instance)
|
|
307
|
+
return Evaluation.invalid if !@errors && (!result.valid? || @error_count != before)
|
|
308
|
+
evaluation = evaluation.merge(result)
|
|
167
309
|
when :number
|
|
168
310
|
check_number(operand, instance) if instance.is_a?(Numeric) && !instance.is_a?(Complex)
|
|
169
311
|
when :string
|
|
170
312
|
check_string(operand, instance) if instance.is_a?(String)
|
|
171
313
|
when :array
|
|
172
|
-
|
|
314
|
+
if instance.is_a?(Array)
|
|
315
|
+
result = check_array(operand, instance, evaluation)
|
|
316
|
+
return Evaluation.invalid if !@errors && (!result.valid? || @error_count != before)
|
|
317
|
+
evaluation = evaluation.merge(result)
|
|
318
|
+
end
|
|
173
319
|
when :object
|
|
174
|
-
|
|
320
|
+
if instance.is_a?(Hash)
|
|
321
|
+
result = check_object(operand, instance, evaluation)
|
|
322
|
+
return Evaluation.invalid if !@errors && (!result.valid? || @error_count != before)
|
|
323
|
+
evaluation = evaluation.merge(result)
|
|
324
|
+
end
|
|
175
325
|
else
|
|
176
326
|
raise "unknown VM instruction #{opcode.inspect}"
|
|
177
327
|
end
|
|
328
|
+
instruction_index += 2
|
|
178
329
|
end
|
|
179
330
|
(@error_count == before) ? evaluation : Evaluation.invalid
|
|
180
331
|
ensure
|
|
181
332
|
leave_scope if entered_scope
|
|
182
333
|
end
|
|
183
334
|
|
|
184
|
-
private def enter_scope(program)
|
|
185
|
-
return false unless program.tracks_dynamic_scope?
|
|
186
|
-
return false if @dynamic_scope&.last.equal?(program.node.resource)
|
|
187
|
-
|
|
188
|
-
(@dynamic_scope ||= []) << program.node.resource
|
|
189
|
-
true
|
|
190
|
-
end
|
|
191
|
-
|
|
192
335
|
private def leave_scope
|
|
193
336
|
@dynamic_scope.pop
|
|
194
337
|
end
|
|
195
338
|
|
|
196
339
|
private def recursive_target(program, rules)
|
|
197
|
-
target =
|
|
340
|
+
target = reference_target(program, rules)
|
|
198
341
|
return target unless rules.fragment == "" && target.recursive_anchor?
|
|
199
342
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
compiled
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
dynamic || target
|
|
343
|
+
@dynamic_scope&.each do |resource|
|
|
344
|
+
compiled = @compiler.compile(resource.root)
|
|
345
|
+
return compiled if compiled.recursive_anchor?
|
|
346
|
+
end
|
|
347
|
+
target
|
|
206
348
|
end
|
|
207
349
|
|
|
208
350
|
private def dynamic_target(program, rules)
|
|
209
|
-
target =
|
|
351
|
+
target = reference_target(program, rules)
|
|
210
352
|
fragment = rules.fragment
|
|
211
353
|
return target if fragment.nil? || fragment.empty? || fragment.start_with?("/")
|
|
212
354
|
|
|
@@ -221,18 +363,22 @@ module Schemurai
|
|
|
221
363
|
|
|
222
364
|
private def valid_reference?(source, target, instance)
|
|
223
365
|
instances = active_instances(source)
|
|
224
|
-
|
|
225
|
-
return true if instances[instance_id]
|
|
366
|
+
return true if instances.key?(instance)
|
|
226
367
|
|
|
227
|
-
instances[
|
|
368
|
+
instances[instance] = true
|
|
228
369
|
activated = true
|
|
229
370
|
evaluate_valid(target, instance)
|
|
230
371
|
ensure
|
|
231
|
-
instances&.delete(
|
|
372
|
+
instances&.delete(instance) if activated
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
private def reference_target(program, rules)
|
|
376
|
+
references = (@resolved_references ||= {}.compare_by_identity)
|
|
377
|
+
references.fetch(rules) { references[rules] = @compiler.resolve(program, rules.value) }
|
|
232
378
|
end
|
|
233
379
|
|
|
234
380
|
private def evaluate_ref(source, rules, instance)
|
|
235
|
-
target =
|
|
381
|
+
target = reference_target(source, rules)
|
|
236
382
|
evaluate_reference(source, target, instance, "$ref")
|
|
237
383
|
rescue ResolutionError => error
|
|
238
384
|
unresolved_reference("$ref", error)
|
|
@@ -252,14 +398,13 @@ module Schemurai
|
|
|
252
398
|
|
|
253
399
|
private def evaluate_reference(source, target, instance, keyword)
|
|
254
400
|
instances = active_instances(source)
|
|
255
|
-
|
|
256
|
-
return Evaluation.valid if instances[instance_id]
|
|
401
|
+
return Evaluation.valid if instances.key?(instance)
|
|
257
402
|
|
|
258
|
-
instances[
|
|
403
|
+
instances[instance] = true
|
|
259
404
|
activated = true
|
|
260
405
|
evaluate_at(target, instance, MISSING_SEGMENT, keyword)
|
|
261
406
|
ensure
|
|
262
|
-
instances&.delete(
|
|
407
|
+
instances&.delete(instance) if activated
|
|
263
408
|
end
|
|
264
409
|
|
|
265
410
|
private def unresolved_reference(keyword, error)
|
|
@@ -268,8 +413,8 @@ module Schemurai
|
|
|
268
413
|
end
|
|
269
414
|
|
|
270
415
|
private def active_instances(program)
|
|
271
|
-
active = (@active ||= {})
|
|
272
|
-
active[program
|
|
416
|
+
active = (@active ||= {}.compare_by_identity)
|
|
417
|
+
active[program] ||= {}.compare_by_identity
|
|
273
418
|
end
|
|
274
419
|
|
|
275
420
|
private def check_compiled_type(name, valid, value)
|
|
@@ -301,19 +446,19 @@ module Schemurai
|
|
|
301
446
|
|
|
302
447
|
private def valid_number?(rules, value)
|
|
303
448
|
mask = rules.mask
|
|
304
|
-
actual = decimal(value)
|
|
449
|
+
actual = (mask & MULTIPLE_OF).zero? ? value : decimal(value)
|
|
305
450
|
return false if (mask & MAXIMUM) != 0 && actual > rules.maximum
|
|
306
451
|
return false if (mask & MINIMUM) != 0 && actual < rules.minimum
|
|
307
452
|
return false if (mask & EXCLUSIVE_MAXIMUM) != 0 && actual >= rules.exclusive_maximum
|
|
308
453
|
return false if (mask & EXCLUSIVE_MINIMUM) != 0 && actual <= rules.exclusive_minimum
|
|
309
454
|
return true if (mask & MULTIPLE_OF).zero?
|
|
310
455
|
|
|
311
|
-
|
|
456
|
+
valid_multiple?(rules, value)
|
|
312
457
|
end
|
|
313
458
|
|
|
314
459
|
private def check_number(rules, value)
|
|
315
460
|
mask = rules.mask
|
|
316
|
-
actual = decimal(value)
|
|
461
|
+
actual = (mask & MULTIPLE_OF).zero? ? value : decimal(value)
|
|
317
462
|
if (mask & MAXIMUM) != 0 && actual > rules.maximum
|
|
318
463
|
add_error("maximum") { Internal::ErrorMessage.numeric_limit("maximum", rules.maximum) }
|
|
319
464
|
end
|
|
@@ -333,19 +478,31 @@ module Schemurai
|
|
|
333
478
|
return if (mask & MULTIPLE_OF).zero?
|
|
334
479
|
|
|
335
480
|
divisor = rules.multiple_of
|
|
336
|
-
valid =
|
|
481
|
+
valid = valid_multiple?(rules, value)
|
|
337
482
|
add_error("multipleOf") { Internal::ErrorMessage.multiple_of(divisor) } unless valid
|
|
338
483
|
end
|
|
339
484
|
|
|
485
|
+
private def valid_multiple?(rules, value)
|
|
486
|
+
results = (@multiple_results ||= {}.compare_by_identity)
|
|
487
|
+
values = (results[rules] ||= {})
|
|
488
|
+
return values[value] if values.key?(value)
|
|
489
|
+
|
|
490
|
+
values.clear if values.length >= DECIMAL_CACHE_LIMIT
|
|
491
|
+
divisor = rules.multiple_of
|
|
492
|
+
values[value] = divisor.positive? && decimal(value).remainder(divisor).zero?
|
|
493
|
+
end
|
|
494
|
+
|
|
340
495
|
private def valid_string?(rules, value)
|
|
341
496
|
length = value.length
|
|
342
|
-
return false if rules.
|
|
343
|
-
return false if rules.
|
|
344
|
-
return false if rules.
|
|
345
|
-
if
|
|
497
|
+
return false if rules.max_length && length > rules.max_length
|
|
498
|
+
return false if rules.min_length && length < rules.min_length
|
|
499
|
+
return false if rules.pattern && !ecma_regexp(rules.pattern).match?(value)
|
|
500
|
+
if rules.format && (@validate_format || rules.format_assertion)
|
|
346
501
|
return false unless rules.format.call(value)
|
|
347
502
|
end
|
|
348
|
-
|
|
503
|
+
if @validate_content && (rules.decode_base64 || rules.parse_json)
|
|
504
|
+
return valid_content?(rules, value)
|
|
505
|
+
end
|
|
349
506
|
|
|
350
507
|
true
|
|
351
508
|
rescue RegexpError, IPAddr::InvalidAddressError
|
|
@@ -354,27 +511,23 @@ module Schemurai
|
|
|
354
511
|
|
|
355
512
|
private def check_string(rules, value)
|
|
356
513
|
length = value.length
|
|
357
|
-
if rules.
|
|
514
|
+
if rules.max_length && length > rules.max_length
|
|
358
515
|
add_error("maxLength") { Internal::ErrorMessage.size("maxLength", rules.max_length, length) }
|
|
359
516
|
end
|
|
360
|
-
if rules.
|
|
517
|
+
if rules.min_length && length < rules.min_length
|
|
361
518
|
add_error("minLength") { Internal::ErrorMessage.size("minLength", rules.min_length, length) }
|
|
362
519
|
end
|
|
363
|
-
if rules.
|
|
520
|
+
if rules.pattern && !ecma_regexp(rules.pattern).match?(value)
|
|
364
521
|
add_error("pattern") { Internal::ErrorMessage.pattern(rules.pattern) }
|
|
365
522
|
end
|
|
366
|
-
if
|
|
523
|
+
if rules.format && (@validate_format || rules.format_assertion) && !rules.format.call(value)
|
|
367
524
|
add_error("format") { Internal::ErrorMessage.format(rules.format.name) }
|
|
368
525
|
end
|
|
369
|
-
check_content(rules, value) if @validate_content
|
|
526
|
+
check_content(rules, value) if @validate_content && (rules.decode_base64 || rules.parse_json)
|
|
370
527
|
rescue RegexpError
|
|
371
528
|
add_error("pattern") { Internal::ErrorMessage.invalid_pattern(rules.pattern) }
|
|
372
529
|
end
|
|
373
530
|
|
|
374
|
-
private def format_asserted?(rules)
|
|
375
|
-
(@validate_format || rules.format_assertion) && !rules.format.nil?
|
|
376
|
-
end
|
|
377
|
-
|
|
378
531
|
private def valid_content?(rules, value)
|
|
379
532
|
decoded = rules.decode_base64 ? Base64.strict_decode64(value) : value
|
|
380
533
|
JSON.parse(decoded) if rules.parse_json
|
|
@@ -397,11 +550,17 @@ module Schemurai
|
|
|
397
550
|
|
|
398
551
|
private def valid_array?(rules, value)
|
|
399
552
|
length = value.length
|
|
400
|
-
return false if rules.
|
|
401
|
-
return false if rules.
|
|
553
|
+
return false if rules.max_items && length > rules.max_items
|
|
554
|
+
return false if rules.min_items && length < rules.min_items
|
|
402
555
|
if rules.unique
|
|
403
|
-
|
|
404
|
-
|
|
556
|
+
index = 1
|
|
557
|
+
while index < length
|
|
558
|
+
previous_index = 0
|
|
559
|
+
while previous_index < index
|
|
560
|
+
return false if json_equal?(value[previous_index], value[index])
|
|
561
|
+
previous_index += 1
|
|
562
|
+
end
|
|
563
|
+
index += 1
|
|
405
564
|
end
|
|
406
565
|
end
|
|
407
566
|
|
|
@@ -419,14 +578,18 @@ module Schemurai
|
|
|
419
578
|
return false unless evaluate_valid(child, value[index])
|
|
420
579
|
end
|
|
421
580
|
if length > items.length && (additional = rules.additional)
|
|
422
|
-
|
|
581
|
+
index = items.length
|
|
582
|
+
while index < length
|
|
423
583
|
return false unless evaluate_valid(additional, value[index])
|
|
584
|
+
index += 1
|
|
424
585
|
end
|
|
425
586
|
end
|
|
426
587
|
elsif items
|
|
427
588
|
start = rules.prefix_items&.length || 0
|
|
428
|
-
|
|
589
|
+
index = start
|
|
590
|
+
while index < length
|
|
429
591
|
return false unless evaluate_valid(items, value[index])
|
|
592
|
+
index += 1
|
|
430
593
|
end
|
|
431
594
|
end
|
|
432
595
|
|
|
@@ -442,25 +605,44 @@ module Schemurai
|
|
|
442
605
|
end
|
|
443
606
|
|
|
444
607
|
private def check_array(rules, value, prior_evaluation)
|
|
445
|
-
|
|
446
|
-
if rules.
|
|
608
|
+
evaluation = nil
|
|
609
|
+
if rules.max_items && value.length > rules.max_items
|
|
610
|
+
return Evaluation.invalid unless @errors
|
|
611
|
+
|
|
447
612
|
add_error("maxItems") { Internal::ErrorMessage.size("maxItems", rules.max_items, value.length) }
|
|
448
613
|
end
|
|
449
|
-
if rules.
|
|
614
|
+
if rules.min_items && value.length < rules.min_items
|
|
615
|
+
return Evaluation.invalid unless @errors
|
|
616
|
+
|
|
450
617
|
add_error("minItems") { Internal::ErrorMessage.size("minItems", rules.min_items, value.length) }
|
|
451
618
|
end
|
|
452
619
|
if rules.unique
|
|
453
|
-
duplicate =
|
|
454
|
-
|
|
620
|
+
duplicate = false
|
|
621
|
+
index = 1
|
|
622
|
+
while index < value.length && !duplicate
|
|
623
|
+
previous_index = 0
|
|
624
|
+
while previous_index < index
|
|
625
|
+
if json_equal?(value[previous_index], value[index])
|
|
626
|
+
duplicate = true
|
|
627
|
+
break
|
|
628
|
+
end
|
|
629
|
+
previous_index += 1
|
|
630
|
+
end
|
|
631
|
+
index += 1
|
|
632
|
+
end
|
|
633
|
+
if duplicate
|
|
634
|
+
return Evaluation.invalid unless @errors
|
|
635
|
+
|
|
636
|
+
add_error("uniqueItems") { Internal::ErrorMessage.unique_items }
|
|
455
637
|
end
|
|
456
|
-
add_error("uniqueItems") { Internal::ErrorMessage.unique_items } if duplicate
|
|
457
638
|
end
|
|
458
639
|
|
|
459
640
|
if (prefix_items = rules.prefix_items)
|
|
460
641
|
prefix_items.each_with_index do |child, index|
|
|
461
642
|
break if index >= value.length
|
|
462
|
-
|
|
463
|
-
|
|
643
|
+
valid = evaluate_child_at(child, value[index], index, "prefixItems", index)
|
|
644
|
+
return Evaluation.invalid if !valid && !@errors
|
|
645
|
+
(evaluation ||= tracked_evaluation).record_item(index)
|
|
464
646
|
end
|
|
465
647
|
end
|
|
466
648
|
|
|
@@ -468,107 +650,144 @@ module Schemurai
|
|
|
468
650
|
if rules.items_list
|
|
469
651
|
items.each_with_index do |child, index|
|
|
470
652
|
break if index >= value.length
|
|
471
|
-
|
|
472
|
-
|
|
653
|
+
valid = evaluate_child_at(child, value[index], index, "items", index)
|
|
654
|
+
return Evaluation.invalid if !valid && !@errors
|
|
655
|
+
(evaluation ||= tracked_evaluation).record_item(index)
|
|
473
656
|
end
|
|
474
657
|
if value.length > items.length && (additional = rules.additional)
|
|
475
658
|
(items.length...value.length).each do |index|
|
|
476
|
-
|
|
477
|
-
|
|
659
|
+
valid = evaluate_child_at(additional, value[index], index, "additionalItems")
|
|
660
|
+
return Evaluation.invalid if !valid && !@errors
|
|
661
|
+
(evaluation ||= tracked_evaluation).record_item(index)
|
|
478
662
|
end
|
|
479
663
|
end
|
|
480
664
|
elsif items
|
|
481
665
|
start = rules.prefix_items&.length || 0
|
|
482
666
|
(start...value.length).each do |index|
|
|
483
|
-
|
|
484
|
-
|
|
667
|
+
valid = evaluate_child_at(items, value[index], index, "items")
|
|
668
|
+
return Evaluation.invalid if !valid && !@errors
|
|
669
|
+
(evaluation ||= tracked_evaluation).record_item(index)
|
|
485
670
|
end
|
|
486
671
|
end
|
|
487
672
|
|
|
488
673
|
if (contains = rules.contains)
|
|
489
|
-
matched =
|
|
490
|
-
|
|
674
|
+
matched = nil
|
|
675
|
+
index = 0
|
|
676
|
+
while index < value.length
|
|
677
|
+
(matched ||= []) << index if trial_at(contains, value[index], index, "contains").valid?
|
|
678
|
+
index += 1
|
|
491
679
|
end
|
|
492
|
-
|
|
680
|
+
matched_count = matched ? matched.length : 0
|
|
681
|
+
unless matched_count.between?(rules.min_contains, rules.max_contains)
|
|
682
|
+
return Evaluation.invalid unless @errors
|
|
683
|
+
|
|
493
684
|
add_error("contains") do
|
|
494
|
-
Internal::ErrorMessage.contains(
|
|
685
|
+
Internal::ErrorMessage.contains(matched_count, rules.min_contains, rules.max_contains)
|
|
495
686
|
end
|
|
496
687
|
end
|
|
497
|
-
|
|
688
|
+
if matched
|
|
689
|
+
matched.each { |index| (evaluation ||= tracked_evaluation).record_item(index) }
|
|
690
|
+
end
|
|
498
691
|
end
|
|
499
692
|
|
|
500
|
-
combined = prior_evaluation.evaluated_items | evaluated
|
|
501
693
|
if (unevaluated = rules.unevaluated)
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
694
|
+
prior_items = prior_evaluation.evaluated_items
|
|
695
|
+
index = 0
|
|
696
|
+
while index < value.length
|
|
697
|
+
if prior_items.include?(index) || evaluation&.evaluated_items&.include?(index)
|
|
698
|
+
index += 1
|
|
699
|
+
next
|
|
700
|
+
end
|
|
701
|
+
valid = evaluate_child_at(unevaluated, value[index], index, "unevaluatedItems")
|
|
702
|
+
return Evaluation.invalid if !valid && !@errors
|
|
703
|
+
(evaluation ||= tracked_evaluation).record_item(index)
|
|
704
|
+
index += 1
|
|
505
705
|
end
|
|
506
706
|
end
|
|
507
|
-
Evaluation.valid
|
|
707
|
+
return Evaluation.valid unless evaluation
|
|
708
|
+
|
|
709
|
+
evaluation
|
|
508
710
|
end
|
|
509
711
|
|
|
510
712
|
private def valid_object?(rules, value)
|
|
511
713
|
length = value.length
|
|
512
|
-
return false if rules.
|
|
513
|
-
return false if rules.
|
|
514
|
-
if rules.
|
|
714
|
+
return false if rules.max_properties && length > rules.max_properties
|
|
715
|
+
return false if rules.min_properties && length < rules.min_properties
|
|
716
|
+
if rules.required && !rules.required.all? { |name| value.key?(name) }
|
|
515
717
|
return false
|
|
516
718
|
end
|
|
517
719
|
|
|
518
720
|
properties = rules.properties
|
|
519
721
|
patterns = rules.patterns
|
|
520
722
|
additional = rules.additional
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
723
|
+
unless properties.empty? && patterns.nil? && additional.nil?
|
|
724
|
+
value.each do |name, property_value|
|
|
725
|
+
matched = false
|
|
726
|
+
if (child = properties[name])
|
|
727
|
+
matched = true
|
|
728
|
+
return false unless evaluate_valid(child, property_value)
|
|
729
|
+
end
|
|
730
|
+
if patterns
|
|
731
|
+
patterns.each do |pattern, child|
|
|
732
|
+
next unless ecma_regexp(pattern).match?(name)
|
|
733
|
+
matched = true
|
|
734
|
+
return false unless evaluate_valid(child, property_value)
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
return false if !matched && additional && !evaluate_valid(additional, property_value)
|
|
531
738
|
end
|
|
532
|
-
return false if !matched && additional && !evaluate_valid(additional, property_value)
|
|
533
739
|
end
|
|
534
740
|
|
|
535
741
|
if (property_names = rules.property_names)
|
|
536
742
|
value.each_key { |name| return false unless evaluate_valid(property_names, name) }
|
|
537
743
|
end
|
|
538
|
-
rules.dependencies
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
744
|
+
if (dependencies = rules.dependencies)
|
|
745
|
+
dependencies.each do |name, dependency|
|
|
746
|
+
next unless value.key?(name)
|
|
747
|
+
if dependency.is_a?(Array)
|
|
748
|
+
return false unless dependency.all? { |required_name| value.key?(required_name) }
|
|
749
|
+
else
|
|
750
|
+
return false unless evaluate_valid(dependency, value)
|
|
751
|
+
end
|
|
544
752
|
end
|
|
545
753
|
end
|
|
546
|
-
rules.dependent_required
|
|
547
|
-
|
|
548
|
-
|
|
754
|
+
if (dependent_required = rules.dependent_required)
|
|
755
|
+
dependent_required.each do |name, required_names|
|
|
756
|
+
next unless value.key?(name)
|
|
757
|
+
return false unless required_names.all? { |required_name| value.key?(required_name) }
|
|
758
|
+
end
|
|
549
759
|
end
|
|
550
|
-
rules.dependent_schemas
|
|
551
|
-
|
|
552
|
-
|
|
760
|
+
if (dependent_schemas = rules.dependent_schemas)
|
|
761
|
+
dependent_schemas.each do |name, child|
|
|
762
|
+
next unless value.key?(name)
|
|
763
|
+
return false unless evaluate_valid(child, value)
|
|
764
|
+
end
|
|
553
765
|
end
|
|
554
766
|
true
|
|
555
767
|
end
|
|
556
768
|
|
|
557
769
|
private def check_object(rules, value, prior_evaluation)
|
|
558
|
-
|
|
559
|
-
if rules.
|
|
770
|
+
evaluation = nil
|
|
771
|
+
if rules.max_properties && value.length > rules.max_properties
|
|
772
|
+
return Evaluation.invalid unless @errors
|
|
773
|
+
|
|
560
774
|
add_error("maxProperties") do
|
|
561
775
|
Internal::ErrorMessage.size("maxProperties", rules.max_properties, value.length)
|
|
562
776
|
end
|
|
563
777
|
end
|
|
564
|
-
if rules.
|
|
778
|
+
if rules.min_properties && value.length < rules.min_properties
|
|
779
|
+
return Evaluation.invalid unless @errors
|
|
780
|
+
|
|
565
781
|
add_error("minProperties") do
|
|
566
782
|
Internal::ErrorMessage.size("minProperties", rules.min_properties, value.length)
|
|
567
783
|
end
|
|
568
784
|
end
|
|
569
785
|
if (required = rules.required)
|
|
570
786
|
required.each do |name|
|
|
571
|
-
|
|
787
|
+
next if value.key?(name)
|
|
788
|
+
return Evaluation.invalid unless @errors
|
|
789
|
+
|
|
790
|
+
add_error("required") { Internal::ErrorMessage.required(name) }
|
|
572
791
|
end
|
|
573
792
|
end
|
|
574
793
|
|
|
@@ -578,96 +797,138 @@ module Schemurai
|
|
|
578
797
|
matched = false
|
|
579
798
|
if (child = properties[name])
|
|
580
799
|
matched = true
|
|
581
|
-
|
|
582
|
-
|
|
800
|
+
valid = evaluate_child_at(child, property_value, name, "properties", name)
|
|
801
|
+
return Evaluation.invalid if !valid && !@errors
|
|
802
|
+
(evaluation ||= tracked_evaluation).record_property(name)
|
|
583
803
|
end
|
|
584
|
-
patterns
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
804
|
+
if patterns
|
|
805
|
+
patterns.each do |pattern, child|
|
|
806
|
+
next unless ecma_regexp(pattern).match?(name)
|
|
807
|
+
matched = true
|
|
808
|
+
valid = evaluate_child_at(child, property_value, name, "patternProperties", pattern)
|
|
809
|
+
return Evaluation.invalid if !valid && !@errors
|
|
810
|
+
(evaluation ||= tracked_evaluation).record_property(name)
|
|
811
|
+
end
|
|
589
812
|
end
|
|
590
813
|
if !matched && (additional = rules.additional)
|
|
591
|
-
|
|
592
|
-
|
|
814
|
+
valid = evaluate_child_at(additional, property_value, name, "additionalProperties")
|
|
815
|
+
return Evaluation.invalid if !valid && !@errors
|
|
816
|
+
(evaluation ||= tracked_evaluation).record_property(name)
|
|
593
817
|
end
|
|
594
818
|
end
|
|
595
819
|
|
|
596
820
|
if (property_names = rules.property_names)
|
|
597
|
-
value.each_key
|
|
821
|
+
value.each_key do |name|
|
|
822
|
+
valid = evaluate_child_at(property_names, name, name, "propertyNames")
|
|
823
|
+
return Evaluation.invalid if !valid && !@errors
|
|
824
|
+
end
|
|
598
825
|
end
|
|
599
|
-
rules.dependencies
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
dependency.
|
|
603
|
-
|
|
604
|
-
|
|
826
|
+
if (dependencies = rules.dependencies)
|
|
827
|
+
dependencies.each do |name, dependency|
|
|
828
|
+
next unless value.key?(name)
|
|
829
|
+
if dependency.is_a?(Array)
|
|
830
|
+
dependency.each do |required_name|
|
|
831
|
+
unless value.key?(required_name)
|
|
832
|
+
return Evaluation.invalid unless @errors
|
|
833
|
+
|
|
834
|
+
add_error("dependencies") { Internal::ErrorMessage.dependent_required(name, required_name) }
|
|
835
|
+
end
|
|
836
|
+
end
|
|
837
|
+
else
|
|
838
|
+
result = evaluate_at(dependency, value, MISSING_SEGMENT, "dependencies", name)
|
|
839
|
+
if result.valid? && !result.evaluated_properties.empty?
|
|
840
|
+
result.evaluated_properties.each do |property|
|
|
841
|
+
(evaluation ||= tracked_evaluation).record_property(property)
|
|
842
|
+
end
|
|
605
843
|
end
|
|
606
844
|
end
|
|
607
|
-
else
|
|
608
|
-
result = evaluate_at(dependency, value, MISSING_SEGMENT, "dependencies", name)
|
|
609
|
-
evaluated.concat(result.evaluated_properties) if result.valid?
|
|
610
845
|
end
|
|
611
846
|
end
|
|
612
|
-
rules.dependent_required
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
847
|
+
if (dependent_required = rules.dependent_required)
|
|
848
|
+
dependent_required.each do |name, required_names|
|
|
849
|
+
next unless value.key?(name)
|
|
850
|
+
required_names.each do |required_name|
|
|
851
|
+
unless value.key?(required_name)
|
|
852
|
+
return Evaluation.invalid unless @errors
|
|
853
|
+
|
|
854
|
+
add_error("dependentRequired") { Internal::ErrorMessage.dependent_required(name, required_name) }
|
|
855
|
+
end
|
|
617
856
|
end
|
|
618
857
|
end
|
|
619
858
|
end
|
|
620
|
-
rules.dependent_schemas
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
859
|
+
if (dependent_schemas = rules.dependent_schemas)
|
|
860
|
+
dependent_schemas.each do |name, child|
|
|
861
|
+
next unless value.key?(name)
|
|
862
|
+
result = evaluate_at(child, value, MISSING_SEGMENT, "dependentSchemas", name)
|
|
863
|
+
if result.valid? && !result.evaluated_properties.empty?
|
|
864
|
+
result.evaluated_properties.each do |property|
|
|
865
|
+
(evaluation ||= tracked_evaluation).record_property(property)
|
|
866
|
+
end
|
|
867
|
+
end
|
|
868
|
+
end
|
|
624
869
|
end
|
|
625
|
-
|
|
626
|
-
combined = prior_evaluation.evaluated_properties | evaluated
|
|
627
870
|
if (unevaluated = rules.unevaluated)
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
871
|
+
prior_properties = prior_evaluation.evaluated_properties
|
|
872
|
+
value.each_key do |name|
|
|
873
|
+
next if prior_properties.include?(name) || evaluation&.evaluated_properties&.include?(name)
|
|
874
|
+
|
|
875
|
+
valid = evaluate_child_at(unevaluated, value[name], name, "unevaluatedProperties")
|
|
876
|
+
return Evaluation.invalid if !valid && !@errors
|
|
877
|
+
(evaluation ||= tracked_evaluation).record_property(name)
|
|
631
878
|
end
|
|
632
879
|
end
|
|
633
|
-
Evaluation.valid
|
|
880
|
+
return Evaluation.valid unless evaluation
|
|
881
|
+
|
|
882
|
+
evaluation
|
|
634
883
|
end
|
|
635
884
|
|
|
636
885
|
private def check_combiner(opcode, operand, value)
|
|
637
886
|
evaluation = Evaluation.valid
|
|
638
887
|
case opcode
|
|
639
888
|
when :allOf
|
|
640
|
-
|
|
641
|
-
|
|
889
|
+
index = 0
|
|
890
|
+
while index < operand.length
|
|
891
|
+
evaluation = evaluation.merge(evaluate_at(operand[index], value, MISSING_SEGMENT, "allOf", index))
|
|
892
|
+
index += 1
|
|
642
893
|
end
|
|
643
894
|
when :anyOf
|
|
644
|
-
matches =
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
895
|
+
matches = 0
|
|
896
|
+
index = 0
|
|
897
|
+
while index < operand.length
|
|
898
|
+
result = trial_at(operand[index], value, MISSING_SEGMENT, "anyOf", index)
|
|
899
|
+
if result.valid?
|
|
900
|
+
matches += 1
|
|
901
|
+
evaluation = evaluation.merge(result)
|
|
902
|
+
end
|
|
903
|
+
index += 1
|
|
652
904
|
end
|
|
905
|
+
add_error("anyOf") { Internal::ErrorMessage.any_of } if matches.zero?
|
|
653
906
|
when :oneOf
|
|
654
|
-
matches =
|
|
655
|
-
|
|
656
|
-
|
|
907
|
+
matches = 0
|
|
908
|
+
matched_evaluation = nil
|
|
909
|
+
index = 0
|
|
910
|
+
while index < operand.length
|
|
911
|
+
result = trial_at(operand[index], value, MISSING_SEGMENT, "oneOf", index)
|
|
912
|
+
if result.valid?
|
|
913
|
+
matches += 1
|
|
914
|
+
matched_evaluation = result
|
|
915
|
+
end
|
|
916
|
+
index += 1
|
|
657
917
|
end
|
|
658
|
-
if matches
|
|
659
|
-
evaluation = evaluation.merge(
|
|
918
|
+
if matches == 1
|
|
919
|
+
evaluation = evaluation.merge(matched_evaluation)
|
|
660
920
|
else
|
|
661
|
-
add_error("oneOf") { Internal::ErrorMessage.one_of(matches
|
|
921
|
+
add_error("oneOf") { Internal::ErrorMessage.one_of(matches) }
|
|
662
922
|
end
|
|
663
923
|
when :not
|
|
664
924
|
add_error("not") { Internal::ErrorMessage.not } if trial_at(operand, value, MISSING_SEGMENT, "not").valid?
|
|
665
925
|
when :conditional
|
|
666
926
|
condition = trial_at(operand.condition, value, MISSING_SEGMENT, "if")
|
|
667
|
-
|
|
668
|
-
|
|
927
|
+
condition_valid = condition.valid?
|
|
928
|
+
evaluation = evaluation.merge(condition) if condition_valid
|
|
929
|
+
if condition_valid && operand.then_branch
|
|
669
930
|
evaluation = evaluation.merge(evaluate_at(operand.then_branch, value, MISSING_SEGMENT, "then"))
|
|
670
|
-
elsif !
|
|
931
|
+
elsif !condition_valid && operand.else_branch
|
|
671
932
|
evaluation = evaluation.merge(evaluate_at(operand.else_branch, value, MISSING_SEGMENT, "else"))
|
|
672
933
|
end
|
|
673
934
|
end
|
|
@@ -686,6 +947,20 @@ module Schemurai
|
|
|
686
947
|
end
|
|
687
948
|
|
|
688
949
|
private def evaluate_at(program, instance, instance_segment, schema_segment, child_segment = MISSING_SEGMENT)
|
|
950
|
+
return evaluate(program, instance) unless @instance_path
|
|
951
|
+
|
|
952
|
+
evaluate_at_with_path(program, instance, instance_segment, schema_segment, child_segment)
|
|
953
|
+
end
|
|
954
|
+
|
|
955
|
+
private def evaluate_child_at(program, instance, instance_segment, schema_segment, child_segment = MISSING_SEGMENT)
|
|
956
|
+
return evaluate_at(program, instance, instance_segment, schema_segment, child_segment).valid? if @errors
|
|
957
|
+
|
|
958
|
+
valid = evaluate_valid(program, instance)
|
|
959
|
+
@error_count += 1 unless valid
|
|
960
|
+
valid
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
private def evaluate_at_with_path(program, instance, instance_segment, schema_segment, child_segment)
|
|
689
964
|
@instance_path << instance_segment unless instance_segment.equal?(MISSING_SEGMENT)
|
|
690
965
|
@schema_path << schema_segment
|
|
691
966
|
@schema_path << child_segment unless child_segment.equal?(MISSING_SEGMENT)
|
|
@@ -697,6 +972,12 @@ module Schemurai
|
|
|
697
972
|
end
|
|
698
973
|
|
|
699
974
|
private def trial_at(program, instance, instance_segment, schema_segment, child_segment = MISSING_SEGMENT)
|
|
975
|
+
return trial(program, instance) unless @instance_path
|
|
976
|
+
|
|
977
|
+
trial_at_with_path(program, instance, instance_segment, schema_segment, child_segment)
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
private def trial_at_with_path(program, instance, instance_segment, schema_segment, child_segment)
|
|
700
981
|
@instance_path << instance_segment unless instance_segment.equal?(MISSING_SEGMENT)
|
|
701
982
|
@schema_path << schema_segment
|
|
702
983
|
@schema_path << child_segment unless child_segment.equal?(MISSING_SEGMENT)
|
|
@@ -726,30 +1007,38 @@ module Schemurai
|
|
|
726
1007
|
end
|
|
727
1008
|
|
|
728
1009
|
private def json_equal?(left, right)
|
|
729
|
-
|
|
1010
|
+
if left.is_a?(Numeric) && !left.is_a?(Complex)
|
|
1011
|
+
return right.is_a?(Numeric) && !right.is_a?(Complex) && left == right
|
|
1012
|
+
end
|
|
1013
|
+
return false unless left.instance_of?(right.class)
|
|
1014
|
+
|
|
730
1015
|
case left
|
|
731
1016
|
when Hash
|
|
732
1017
|
left.length == right.length && left.all? do |key, value|
|
|
733
1018
|
right.key?(key) && json_equal?(value, right[key])
|
|
734
1019
|
end
|
|
735
1020
|
when Array
|
|
736
|
-
left.length == right.length
|
|
1021
|
+
return false unless left.length == right.length
|
|
1022
|
+
|
|
1023
|
+
index = 0
|
|
1024
|
+
while index < left.length
|
|
1025
|
+
return false unless json_equal?(left[index], right[index])
|
|
1026
|
+
index += 1
|
|
1027
|
+
end
|
|
1028
|
+
true
|
|
737
1029
|
else
|
|
738
1030
|
left == right
|
|
739
1031
|
end
|
|
740
1032
|
end
|
|
741
1033
|
|
|
742
|
-
private def json_kind(value)
|
|
743
|
-
return :number if number?(value)
|
|
744
|
-
return :boolean if value == true || value == false
|
|
745
|
-
|
|
746
|
-
value.class
|
|
747
|
-
end
|
|
748
|
-
|
|
749
1034
|
private def decimal(value)
|
|
750
1035
|
return value if value.is_a?(Integer) || value.is_a?(Rational)
|
|
751
1036
|
|
|
752
|
-
|
|
1037
|
+
decimals = (@decimals ||= {})
|
|
1038
|
+
return decimals[value] if decimals.key?(value)
|
|
1039
|
+
|
|
1040
|
+
decimals.clear if decimals.length >= DECIMAL_CACHE_LIMIT
|
|
1041
|
+
decimals[value] = Rational(value.to_s)
|
|
753
1042
|
end
|
|
754
1043
|
|
|
755
1044
|
private def ecma_regexp(pattern)
|
|
@@ -803,7 +1092,7 @@ module Schemurai
|
|
|
803
1092
|
pointer << "/" << segment.to_s.gsub("~", "~0").gsub("/", "~1")
|
|
804
1093
|
end
|
|
805
1094
|
|
|
806
|
-
private_constant :MISSING_SEGMENT
|
|
1095
|
+
private_constant :DECIMAL_CACHE_LIMIT, :MISSING_SEGMENT
|
|
807
1096
|
end
|
|
808
1097
|
end
|
|
809
1098
|
|