schemurai 2.0.0 → 2.2.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.
- checksums.yaml +4 -4
- data/README.md +40 -2
- data/lib/schemurai/error_message.rb +102 -0
- data/lib/schemurai/evaluation.rb +19 -2
- data/lib/schemurai/evaluator.rb +24 -21
- 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 +523 -221
- data/lib/schemurai.rb +6 -3
- data/sig/schemurai.rbs +157 -0
- metadata +4 -2
|
@@ -3,12 +3,54 @@
|
|
|
3
3
|
require "base64"
|
|
4
4
|
require "json"
|
|
5
5
|
require_relative "../evaluation"
|
|
6
|
+
require_relative "../error_message"
|
|
6
7
|
require_relative "compiler"
|
|
7
8
|
|
|
8
9
|
module Schemurai
|
|
9
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
|
+
|
|
10
51
|
class Evaluator
|
|
11
52
|
MISSING_SEGMENT = Object.new.freeze
|
|
53
|
+
DECIMAL_CACHE_LIMIT = 16
|
|
12
54
|
|
|
13
55
|
def backend = :vm
|
|
14
56
|
|
|
@@ -20,6 +62,14 @@ module Schemurai
|
|
|
20
62
|
@validate_format = format
|
|
21
63
|
@regexps = nil
|
|
22
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
|
|
23
73
|
end
|
|
24
74
|
|
|
25
75
|
def validate(instance)
|
|
@@ -33,31 +83,52 @@ module Schemurai
|
|
|
33
83
|
|
|
34
84
|
def valid?(instance)
|
|
35
85
|
@errors = nil
|
|
36
|
-
|
|
86
|
+
@error_count = 0
|
|
87
|
+
@dynamic_scope&.clear
|
|
88
|
+
@instance_path = nil
|
|
89
|
+
@schema_path = nil
|
|
90
|
+
reset_evaluation_pool
|
|
37
91
|
evaluate_valid(@root, instance)
|
|
38
92
|
end
|
|
39
93
|
|
|
40
94
|
private def prepare_evaluation(paths:)
|
|
41
95
|
@error_count = 0
|
|
42
|
-
@dynamic_scope
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
45
107
|
end
|
|
46
108
|
|
|
47
109
|
private def evaluate_valid(program, instance)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
52
123
|
end
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
124
|
+
instruction_index = 1
|
|
125
|
+
while (opcode = code[instruction_index])
|
|
126
|
+
operand = code[instruction_index + 1]
|
|
56
127
|
case opcode
|
|
57
128
|
when :boolean
|
|
58
129
|
return operand
|
|
59
130
|
when :ref
|
|
60
|
-
target =
|
|
131
|
+
target = reference_target(program, operand)
|
|
61
132
|
return false unless valid_reference?(program, target, instance)
|
|
62
133
|
when :recursive_ref
|
|
63
134
|
target = recursive_target(program, operand)
|
|
@@ -74,11 +145,22 @@ module Schemurai
|
|
|
74
145
|
when :type_array
|
|
75
146
|
return false unless instance.is_a?(Array)
|
|
76
147
|
when :type_number
|
|
77
|
-
return false unless
|
|
148
|
+
return false unless instance.is_a?(Numeric) && !instance.is_a?(Complex)
|
|
78
149
|
when :type_integer
|
|
79
|
-
return false unless
|
|
150
|
+
return false unless instance.is_a?(Numeric) && !instance.is_a?(Complex) && instance.finite? && instance.to_i == instance
|
|
80
151
|
when :type_string
|
|
81
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)
|
|
82
164
|
when :types
|
|
83
165
|
return false if (operand.mask & instance_type_mask(instance, integer: (operand.mask & TYPE_INTEGER) != 0)).zero?
|
|
84
166
|
when :enum
|
|
@@ -100,8 +182,8 @@ module Schemurai
|
|
|
100
182
|
return false if evaluate_valid(operand, instance)
|
|
101
183
|
when :conditional
|
|
102
184
|
if evaluate_valid(operand.condition, instance)
|
|
103
|
-
return false if operand.
|
|
104
|
-
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)
|
|
105
187
|
return false
|
|
106
188
|
end
|
|
107
189
|
when :number
|
|
@@ -115,6 +197,7 @@ module Schemurai
|
|
|
115
197
|
else
|
|
116
198
|
raise "unknown VM instruction #{opcode.inspect}"
|
|
117
199
|
end
|
|
200
|
+
instruction_index += 2
|
|
118
201
|
end
|
|
119
202
|
true
|
|
120
203
|
rescue ResolutionError
|
|
@@ -123,16 +206,38 @@ module Schemurai
|
|
|
123
206
|
leave_scope if entered_scope
|
|
124
207
|
end
|
|
125
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
|
+
|
|
126
220
|
private def evaluate(program, instance)
|
|
127
221
|
before = @error_count
|
|
128
222
|
evaluation = Evaluation.valid
|
|
129
|
-
|
|
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
|
|
130
233
|
|
|
131
|
-
|
|
234
|
+
instruction_index = 1
|
|
235
|
+
while (opcode = code[instruction_index])
|
|
236
|
+
operand = code[instruction_index + 1]
|
|
132
237
|
case opcode
|
|
133
238
|
when :boolean
|
|
134
239
|
if operand == false
|
|
135
|
-
add_error("falseSchema",
|
|
240
|
+
add_error("falseSchema", append_keyword: false) { Internal::ErrorMessage.false_schema }
|
|
136
241
|
evaluation = Evaluation.invalid
|
|
137
242
|
end
|
|
138
243
|
when :ref
|
|
@@ -142,70 +247,108 @@ module Schemurai
|
|
|
142
247
|
when :dynamic_ref
|
|
143
248
|
evaluation = evaluation.merge(evaluate_dynamic_ref(program, operand, instance))
|
|
144
249
|
when :type_null
|
|
145
|
-
check_compiled_type("null", instance.nil
|
|
250
|
+
check_compiled_type("null", instance.nil?, instance)
|
|
146
251
|
when :type_boolean
|
|
147
|
-
check_compiled_type("boolean", instance == true || instance == false)
|
|
252
|
+
check_compiled_type("boolean", instance == true || instance == false, instance)
|
|
148
253
|
when :type_object
|
|
149
|
-
check_compiled_type("object", instance.is_a?(Hash))
|
|
254
|
+
check_compiled_type("object", instance.is_a?(Hash), instance)
|
|
150
255
|
when :type_array
|
|
151
|
-
check_compiled_type("array", instance.is_a?(Array))
|
|
256
|
+
check_compiled_type("array", instance.is_a?(Array), instance)
|
|
152
257
|
when :type_number
|
|
153
|
-
check_compiled_type("number", number?(instance))
|
|
258
|
+
check_compiled_type("number", number?(instance), instance)
|
|
154
259
|
when :type_integer
|
|
155
|
-
check_compiled_type("integer", integer?(instance))
|
|
260
|
+
check_compiled_type("integer", integer?(instance), instance)
|
|
156
261
|
when :type_string
|
|
157
|
-
check_compiled_type("string", instance.is_a?(String))
|
|
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
|
|
158
299
|
when :types
|
|
159
300
|
check_compiled_types(operand, instance)
|
|
160
301
|
when :enum
|
|
161
|
-
add_error("enum"
|
|
302
|
+
add_error("enum") { Internal::ErrorMessage.enum } unless operand.any? { |candidate| json_equal?(candidate, instance) }
|
|
162
303
|
when :const
|
|
163
|
-
add_error("const"
|
|
304
|
+
add_error("const") { Internal::ErrorMessage.const } unless json_equal?(operand, instance)
|
|
164
305
|
when :allOf, :anyOf, :oneOf, :not, :conditional
|
|
165
|
-
|
|
306
|
+
result = check_combiner(opcode, operand, instance)
|
|
307
|
+
return Evaluation.invalid if !@errors && (!result.valid? || @error_count != before)
|
|
308
|
+
evaluation = evaluation.merge(result)
|
|
166
309
|
when :number
|
|
167
310
|
check_number(operand, instance) if instance.is_a?(Numeric) && !instance.is_a?(Complex)
|
|
168
311
|
when :string
|
|
169
312
|
check_string(operand, instance) if instance.is_a?(String)
|
|
170
313
|
when :array
|
|
171
|
-
|
|
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
|
|
172
319
|
when :object
|
|
173
|
-
|
|
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
|
|
174
325
|
else
|
|
175
326
|
raise "unknown VM instruction #{opcode.inspect}"
|
|
176
327
|
end
|
|
328
|
+
instruction_index += 2
|
|
177
329
|
end
|
|
178
330
|
(@error_count == before) ? evaluation : Evaluation.invalid
|
|
179
331
|
ensure
|
|
180
332
|
leave_scope if entered_scope
|
|
181
333
|
end
|
|
182
334
|
|
|
183
|
-
private def enter_scope(program)
|
|
184
|
-
return false unless program.tracks_dynamic_scope?
|
|
185
|
-
return false if @dynamic_scope&.last.equal?(program.node.resource)
|
|
186
|
-
|
|
187
|
-
(@dynamic_scope ||= []) << program.node.resource
|
|
188
|
-
true
|
|
189
|
-
end
|
|
190
|
-
|
|
191
335
|
private def leave_scope
|
|
192
336
|
@dynamic_scope.pop
|
|
193
337
|
end
|
|
194
338
|
|
|
195
339
|
private def recursive_target(program, rules)
|
|
196
|
-
target =
|
|
340
|
+
target = reference_target(program, rules)
|
|
197
341
|
return target unless rules.fragment == "" && target.recursive_anchor?
|
|
198
342
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
compiled
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
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
|
|
205
348
|
end
|
|
206
349
|
|
|
207
350
|
private def dynamic_target(program, rules)
|
|
208
|
-
target =
|
|
351
|
+
target = reference_target(program, rules)
|
|
209
352
|
fragment = rules.fragment
|
|
210
353
|
return target if fragment.nil? || fragment.empty? || fragment.start_with?("/")
|
|
211
354
|
|
|
@@ -220,18 +363,22 @@ module Schemurai
|
|
|
220
363
|
|
|
221
364
|
private def valid_reference?(source, target, instance)
|
|
222
365
|
instances = active_instances(source)
|
|
223
|
-
|
|
224
|
-
return true if instances[instance_id]
|
|
366
|
+
return true if instances.key?(instance)
|
|
225
367
|
|
|
226
|
-
instances[
|
|
368
|
+
instances[instance] = true
|
|
227
369
|
activated = true
|
|
228
370
|
evaluate_valid(target, instance)
|
|
229
371
|
ensure
|
|
230
|
-
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) }
|
|
231
378
|
end
|
|
232
379
|
|
|
233
380
|
private def evaluate_ref(source, rules, instance)
|
|
234
|
-
target =
|
|
381
|
+
target = reference_target(source, rules)
|
|
235
382
|
evaluate_reference(source, target, instance, "$ref")
|
|
236
383
|
rescue ResolutionError => error
|
|
237
384
|
unresolved_reference("$ref", error)
|
|
@@ -251,14 +398,13 @@ module Schemurai
|
|
|
251
398
|
|
|
252
399
|
private def evaluate_reference(source, target, instance, keyword)
|
|
253
400
|
instances = active_instances(source)
|
|
254
|
-
|
|
255
|
-
return Evaluation.valid if instances[instance_id]
|
|
401
|
+
return Evaluation.valid if instances.key?(instance)
|
|
256
402
|
|
|
257
|
-
instances[
|
|
403
|
+
instances[instance] = true
|
|
258
404
|
activated = true
|
|
259
405
|
evaluate_at(target, instance, MISSING_SEGMENT, keyword)
|
|
260
406
|
ensure
|
|
261
|
-
instances&.delete(
|
|
407
|
+
instances&.delete(instance) if activated
|
|
262
408
|
end
|
|
263
409
|
|
|
264
410
|
private def unresolved_reference(keyword, error)
|
|
@@ -267,19 +413,19 @@ module Schemurai
|
|
|
267
413
|
end
|
|
268
414
|
|
|
269
415
|
private def active_instances(program)
|
|
270
|
-
active = (@active ||= {})
|
|
271
|
-
active[program
|
|
416
|
+
active = (@active ||= {}.compare_by_identity)
|
|
417
|
+
active[program] ||= {}.compare_by_identity
|
|
272
418
|
end
|
|
273
419
|
|
|
274
|
-
private def check_compiled_type(name, valid)
|
|
275
|
-
add_error("type"
|
|
420
|
+
private def check_compiled_type(name, valid, value)
|
|
421
|
+
add_error("type") { Internal::ErrorMessage.type(name, value) } unless valid
|
|
276
422
|
end
|
|
277
423
|
|
|
278
424
|
private def check_compiled_types(rules, value)
|
|
279
425
|
instance_mask = instance_type_mask(value, integer: (rules.mask & TYPE_INTEGER) != 0)
|
|
280
426
|
return unless (rules.mask & instance_mask).zero?
|
|
281
427
|
|
|
282
|
-
add_error("type"
|
|
428
|
+
add_error("type") { Internal::ErrorMessage.type(rules.names, value) }
|
|
283
429
|
end
|
|
284
430
|
|
|
285
431
|
private def instance_type_mask(value, integer:)
|
|
@@ -300,47 +446,63 @@ module Schemurai
|
|
|
300
446
|
|
|
301
447
|
private def valid_number?(rules, value)
|
|
302
448
|
mask = rules.mask
|
|
303
|
-
actual = decimal(value)
|
|
449
|
+
actual = (mask & MULTIPLE_OF).zero? ? value : decimal(value)
|
|
304
450
|
return false if (mask & MAXIMUM) != 0 && actual > rules.maximum
|
|
305
451
|
return false if (mask & MINIMUM) != 0 && actual < rules.minimum
|
|
306
452
|
return false if (mask & EXCLUSIVE_MAXIMUM) != 0 && actual >= rules.exclusive_maximum
|
|
307
453
|
return false if (mask & EXCLUSIVE_MINIMUM) != 0 && actual <= rules.exclusive_minimum
|
|
308
454
|
return true if (mask & MULTIPLE_OF).zero?
|
|
309
455
|
|
|
310
|
-
|
|
456
|
+
valid_multiple?(rules, value)
|
|
311
457
|
end
|
|
312
458
|
|
|
313
459
|
private def check_number(rules, value)
|
|
314
460
|
mask = rules.mask
|
|
315
|
-
actual = decimal(value)
|
|
461
|
+
actual = (mask & MULTIPLE_OF).zero? ? value : decimal(value)
|
|
316
462
|
if (mask & MAXIMUM) != 0 && actual > rules.maximum
|
|
317
|
-
add_error("maximum"
|
|
463
|
+
add_error("maximum") { Internal::ErrorMessage.numeric_limit("maximum", rules.maximum) }
|
|
318
464
|
end
|
|
319
465
|
if (mask & MINIMUM) != 0 && actual < rules.minimum
|
|
320
|
-
add_error("minimum"
|
|
466
|
+
add_error("minimum") { Internal::ErrorMessage.numeric_limit("minimum", rules.minimum) }
|
|
321
467
|
end
|
|
322
468
|
if (mask & EXCLUSIVE_MAXIMUM) != 0 && actual >= rules.exclusive_maximum
|
|
323
|
-
add_error("exclusiveMaximum"
|
|
469
|
+
add_error("exclusiveMaximum") do
|
|
470
|
+
Internal::ErrorMessage.numeric_limit("exclusiveMaximum", rules.exclusive_maximum)
|
|
471
|
+
end
|
|
324
472
|
end
|
|
325
473
|
if (mask & EXCLUSIVE_MINIMUM) != 0 && actual <= rules.exclusive_minimum
|
|
326
|
-
add_error("exclusiveMinimum"
|
|
474
|
+
add_error("exclusiveMinimum") do
|
|
475
|
+
Internal::ErrorMessage.numeric_limit("exclusiveMinimum", rules.exclusive_minimum)
|
|
476
|
+
end
|
|
327
477
|
end
|
|
328
478
|
return if (mask & MULTIPLE_OF).zero?
|
|
329
479
|
|
|
330
480
|
divisor = rules.multiple_of
|
|
331
|
-
valid =
|
|
332
|
-
add_error("multipleOf"
|
|
481
|
+
valid = valid_multiple?(rules, value)
|
|
482
|
+
add_error("multipleOf") { Internal::ErrorMessage.multiple_of(divisor) } unless valid
|
|
483
|
+
end
|
|
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?
|
|
333
493
|
end
|
|
334
494
|
|
|
335
495
|
private def valid_string?(rules, value)
|
|
336
496
|
length = value.length
|
|
337
|
-
return false if rules.
|
|
338
|
-
return false if rules.
|
|
339
|
-
return false if rules.
|
|
340
|
-
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)
|
|
341
501
|
return false unless rules.format.call(value)
|
|
342
502
|
end
|
|
343
|
-
|
|
503
|
+
if @validate_content && (rules.decode_base64 || rules.parse_json)
|
|
504
|
+
return valid_content?(rules, value)
|
|
505
|
+
end
|
|
344
506
|
|
|
345
507
|
true
|
|
346
508
|
rescue RegexpError, IPAddr::InvalidAddressError
|
|
@@ -349,25 +511,21 @@ module Schemurai
|
|
|
349
511
|
|
|
350
512
|
private def check_string(rules, value)
|
|
351
513
|
length = value.length
|
|
352
|
-
if rules.
|
|
353
|
-
add_error("maxLength"
|
|
514
|
+
if rules.max_length && length > rules.max_length
|
|
515
|
+
add_error("maxLength") { Internal::ErrorMessage.size("maxLength", rules.max_length, length) }
|
|
354
516
|
end
|
|
355
|
-
if rules.
|
|
356
|
-
add_error("minLength"
|
|
517
|
+
if rules.min_length && length < rules.min_length
|
|
518
|
+
add_error("minLength") { Internal::ErrorMessage.size("minLength", rules.min_length, length) }
|
|
357
519
|
end
|
|
358
|
-
if rules.
|
|
359
|
-
add_error("pattern"
|
|
520
|
+
if rules.pattern && !ecma_regexp(rules.pattern).match?(value)
|
|
521
|
+
add_error("pattern") { Internal::ErrorMessage.pattern(rules.pattern) }
|
|
360
522
|
end
|
|
361
|
-
if
|
|
362
|
-
add_error("format"
|
|
523
|
+
if rules.format && (@validate_format || rules.format_assertion) && !rules.format.call(value)
|
|
524
|
+
add_error("format") { Internal::ErrorMessage.format(rules.format.name) }
|
|
363
525
|
end
|
|
364
|
-
check_content(rules, value) if @validate_content
|
|
526
|
+
check_content(rules, value) if @validate_content && (rules.decode_base64 || rules.parse_json)
|
|
365
527
|
rescue RegexpError
|
|
366
|
-
add_error("pattern"
|
|
367
|
-
end
|
|
368
|
-
|
|
369
|
-
private def format_asserted?(rules)
|
|
370
|
-
(@validate_format || rules.format_assertion) && !rules.format.nil?
|
|
528
|
+
add_error("pattern") { Internal::ErrorMessage.invalid_pattern(rules.pattern) }
|
|
371
529
|
end
|
|
372
530
|
|
|
373
531
|
private def valid_content?(rules, value)
|
|
@@ -385,16 +543,24 @@ module Schemurai
|
|
|
385
543
|
JSON.parse(decoded)
|
|
386
544
|
rescue ArgumentError, JSON::ParserError
|
|
387
545
|
keyword = rules.decode_base64 ? "contentEncoding" : "contentMediaType"
|
|
388
|
-
add_error(keyword
|
|
546
|
+
add_error(keyword) do
|
|
547
|
+
(keyword == "contentEncoding") ? Internal::ErrorMessage.content_encoding : Internal::ErrorMessage.content_media_type
|
|
548
|
+
end
|
|
389
549
|
end
|
|
390
550
|
|
|
391
551
|
private def valid_array?(rules, value)
|
|
392
552
|
length = value.length
|
|
393
|
-
return false if rules.
|
|
394
|
-
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
|
|
395
555
|
if rules.unique
|
|
396
|
-
|
|
397
|
-
|
|
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
|
|
398
564
|
end
|
|
399
565
|
end
|
|
400
566
|
|
|
@@ -412,14 +578,18 @@ module Schemurai
|
|
|
412
578
|
return false unless evaluate_valid(child, value[index])
|
|
413
579
|
end
|
|
414
580
|
if length > items.length && (additional = rules.additional)
|
|
415
|
-
|
|
581
|
+
index = items.length
|
|
582
|
+
while index < length
|
|
416
583
|
return false unless evaluate_valid(additional, value[index])
|
|
584
|
+
index += 1
|
|
417
585
|
end
|
|
418
586
|
end
|
|
419
587
|
elsif items
|
|
420
588
|
start = rules.prefix_items&.length || 0
|
|
421
|
-
|
|
589
|
+
index = start
|
|
590
|
+
while index < length
|
|
422
591
|
return false unless evaluate_valid(items, value[index])
|
|
592
|
+
index += 1
|
|
423
593
|
end
|
|
424
594
|
end
|
|
425
595
|
|
|
@@ -435,25 +605,44 @@ module Schemurai
|
|
|
435
605
|
end
|
|
436
606
|
|
|
437
607
|
private def check_array(rules, value, prior_evaluation)
|
|
438
|
-
|
|
439
|
-
if rules.
|
|
440
|
-
|
|
608
|
+
evaluation = nil
|
|
609
|
+
if rules.max_items && value.length > rules.max_items
|
|
610
|
+
return Evaluation.invalid unless @errors
|
|
611
|
+
|
|
612
|
+
add_error("maxItems") { Internal::ErrorMessage.size("maxItems", rules.max_items, value.length) }
|
|
441
613
|
end
|
|
442
|
-
if rules.
|
|
443
|
-
|
|
614
|
+
if rules.min_items && value.length < rules.min_items
|
|
615
|
+
return Evaluation.invalid unless @errors
|
|
616
|
+
|
|
617
|
+
add_error("minItems") { Internal::ErrorMessage.size("minItems", rules.min_items, value.length) }
|
|
444
618
|
end
|
|
445
619
|
if rules.unique
|
|
446
|
-
duplicate =
|
|
447
|
-
|
|
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 }
|
|
448
637
|
end
|
|
449
|
-
add_error("uniqueItems", "array items are not unique") if duplicate
|
|
450
638
|
end
|
|
451
639
|
|
|
452
640
|
if (prefix_items = rules.prefix_items)
|
|
453
641
|
prefix_items.each_with_index do |child, index|
|
|
454
642
|
break if index >= value.length
|
|
455
|
-
|
|
456
|
-
|
|
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)
|
|
457
646
|
end
|
|
458
647
|
end
|
|
459
648
|
|
|
@@ -461,101 +650,144 @@ module Schemurai
|
|
|
461
650
|
if rules.items_list
|
|
462
651
|
items.each_with_index do |child, index|
|
|
463
652
|
break if index >= value.length
|
|
464
|
-
|
|
465
|
-
|
|
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)
|
|
466
656
|
end
|
|
467
657
|
if value.length > items.length && (additional = rules.additional)
|
|
468
658
|
(items.length...value.length).each do |index|
|
|
469
|
-
|
|
470
|
-
|
|
659
|
+
valid = evaluate_child_at(additional, value[index], index, "additionalItems")
|
|
660
|
+
return Evaluation.invalid if !valid && !@errors
|
|
661
|
+
(evaluation ||= tracked_evaluation).record_item(index)
|
|
471
662
|
end
|
|
472
663
|
end
|
|
473
664
|
elsif items
|
|
474
665
|
start = rules.prefix_items&.length || 0
|
|
475
666
|
(start...value.length).each do |index|
|
|
476
|
-
|
|
477
|
-
|
|
667
|
+
valid = evaluate_child_at(items, value[index], index, "items")
|
|
668
|
+
return Evaluation.invalid if !valid && !@errors
|
|
669
|
+
(evaluation ||= tracked_evaluation).record_item(index)
|
|
478
670
|
end
|
|
479
671
|
end
|
|
480
672
|
|
|
481
673
|
if (contains = rules.contains)
|
|
482
|
-
matched =
|
|
483
|
-
|
|
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
|
|
484
679
|
end
|
|
485
|
-
|
|
486
|
-
|
|
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
|
+
|
|
684
|
+
add_error("contains") do
|
|
685
|
+
Internal::ErrorMessage.contains(matched_count, rules.min_contains, rules.max_contains)
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
if matched
|
|
689
|
+
matched.each { |index| (evaluation ||= tracked_evaluation).record_item(index) }
|
|
487
690
|
end
|
|
488
|
-
evaluated.concat(matched)
|
|
489
691
|
end
|
|
490
692
|
|
|
491
|
-
combined = prior_evaluation.evaluated_items | evaluated
|
|
492
693
|
if (unevaluated = rules.unevaluated)
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
|
496
705
|
end
|
|
497
706
|
end
|
|
498
|
-
Evaluation.valid
|
|
707
|
+
return Evaluation.valid unless evaluation
|
|
708
|
+
|
|
709
|
+
evaluation
|
|
499
710
|
end
|
|
500
711
|
|
|
501
712
|
private def valid_object?(rules, value)
|
|
502
713
|
length = value.length
|
|
503
|
-
return false if rules.
|
|
504
|
-
return false if rules.
|
|
505
|
-
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) }
|
|
506
717
|
return false
|
|
507
718
|
end
|
|
508
719
|
|
|
509
720
|
properties = rules.properties
|
|
510
721
|
patterns = rules.patterns
|
|
511
722
|
additional = rules.additional
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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)
|
|
522
738
|
end
|
|
523
|
-
return false if !matched && additional && !evaluate_valid(additional, property_value)
|
|
524
739
|
end
|
|
525
740
|
|
|
526
741
|
if (property_names = rules.property_names)
|
|
527
742
|
value.each_key { |name| return false unless evaluate_valid(property_names, name) }
|
|
528
743
|
end
|
|
529
|
-
rules.dependencies
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
|
535
752
|
end
|
|
536
753
|
end
|
|
537
|
-
rules.dependent_required
|
|
538
|
-
|
|
539
|
-
|
|
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
|
|
540
759
|
end
|
|
541
|
-
rules.dependent_schemas
|
|
542
|
-
|
|
543
|
-
|
|
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
|
|
544
765
|
end
|
|
545
766
|
true
|
|
546
767
|
end
|
|
547
768
|
|
|
548
769
|
private def check_object(rules, value, prior_evaluation)
|
|
549
|
-
|
|
550
|
-
if rules.
|
|
551
|
-
|
|
770
|
+
evaluation = nil
|
|
771
|
+
if rules.max_properties && value.length > rules.max_properties
|
|
772
|
+
return Evaluation.invalid unless @errors
|
|
773
|
+
|
|
774
|
+
add_error("maxProperties") do
|
|
775
|
+
Internal::ErrorMessage.size("maxProperties", rules.max_properties, value.length)
|
|
776
|
+
end
|
|
552
777
|
end
|
|
553
|
-
if rules.
|
|
554
|
-
|
|
778
|
+
if rules.min_properties && value.length < rules.min_properties
|
|
779
|
+
return Evaluation.invalid unless @errors
|
|
780
|
+
|
|
781
|
+
add_error("minProperties") do
|
|
782
|
+
Internal::ErrorMessage.size("minProperties", rules.min_properties, value.length)
|
|
783
|
+
end
|
|
555
784
|
end
|
|
556
785
|
if (required = rules.required)
|
|
557
786
|
required.each do |name|
|
|
558
|
-
|
|
787
|
+
next if value.key?(name)
|
|
788
|
+
return Evaluation.invalid unless @errors
|
|
789
|
+
|
|
790
|
+
add_error("required") { Internal::ErrorMessage.required(name) }
|
|
559
791
|
end
|
|
560
792
|
end
|
|
561
793
|
|
|
@@ -565,96 +797,138 @@ module Schemurai
|
|
|
565
797
|
matched = false
|
|
566
798
|
if (child = properties[name])
|
|
567
799
|
matched = true
|
|
568
|
-
|
|
569
|
-
|
|
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)
|
|
570
803
|
end
|
|
571
|
-
patterns
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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
|
|
576
812
|
end
|
|
577
813
|
if !matched && (additional = rules.additional)
|
|
578
|
-
|
|
579
|
-
|
|
814
|
+
valid = evaluate_child_at(additional, property_value, name, "additionalProperties")
|
|
815
|
+
return Evaluation.invalid if !valid && !@errors
|
|
816
|
+
(evaluation ||= tracked_evaluation).record_property(name)
|
|
580
817
|
end
|
|
581
818
|
end
|
|
582
819
|
|
|
583
820
|
if (property_names = rules.property_names)
|
|
584
|
-
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
|
|
585
825
|
end
|
|
586
|
-
rules.dependencies
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
dependency.
|
|
590
|
-
|
|
591
|
-
|
|
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
|
|
592
843
|
end
|
|
593
844
|
end
|
|
594
|
-
else
|
|
595
|
-
result = evaluate_at(dependency, value, MISSING_SEGMENT, "dependencies", name)
|
|
596
|
-
evaluated.concat(result.evaluated_properties) if result.valid?
|
|
597
845
|
end
|
|
598
846
|
end
|
|
599
|
-
rules.dependent_required
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|
|
604
856
|
end
|
|
605
857
|
end
|
|
606
858
|
end
|
|
607
|
-
rules.dependent_schemas
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
|
611
869
|
end
|
|
612
|
-
|
|
613
|
-
combined = prior_evaluation.evaluated_properties | evaluated
|
|
614
870
|
if (unevaluated = rules.unevaluated)
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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)
|
|
618
878
|
end
|
|
619
879
|
end
|
|
620
|
-
Evaluation.valid
|
|
880
|
+
return Evaluation.valid unless evaluation
|
|
881
|
+
|
|
882
|
+
evaluation
|
|
621
883
|
end
|
|
622
884
|
|
|
623
885
|
private def check_combiner(opcode, operand, value)
|
|
624
886
|
evaluation = Evaluation.valid
|
|
625
887
|
case opcode
|
|
626
888
|
when :allOf
|
|
627
|
-
|
|
628
|
-
|
|
889
|
+
index = 0
|
|
890
|
+
while index < operand.length
|
|
891
|
+
evaluation = evaluation.merge(evaluate_at(operand[index], value, MISSING_SEGMENT, "allOf", index))
|
|
892
|
+
index += 1
|
|
629
893
|
end
|
|
630
894
|
when :anyOf
|
|
631
|
-
matches =
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
|
639
904
|
end
|
|
905
|
+
add_error("anyOf") { Internal::ErrorMessage.any_of } if matches.zero?
|
|
640
906
|
when :oneOf
|
|
641
|
-
matches =
|
|
642
|
-
|
|
643
|
-
|
|
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
|
|
644
917
|
end
|
|
645
|
-
if matches
|
|
646
|
-
evaluation = evaluation.merge(
|
|
918
|
+
if matches == 1
|
|
919
|
+
evaluation = evaluation.merge(matched_evaluation)
|
|
647
920
|
else
|
|
648
|
-
add_error("oneOf"
|
|
921
|
+
add_error("oneOf") { Internal::ErrorMessage.one_of(matches) }
|
|
649
922
|
end
|
|
650
923
|
when :not
|
|
651
|
-
add_error("not"
|
|
924
|
+
add_error("not") { Internal::ErrorMessage.not } if trial_at(operand, value, MISSING_SEGMENT, "not").valid?
|
|
652
925
|
when :conditional
|
|
653
926
|
condition = trial_at(operand.condition, value, MISSING_SEGMENT, "if")
|
|
654
|
-
|
|
655
|
-
|
|
927
|
+
condition_valid = condition.valid?
|
|
928
|
+
evaluation = evaluation.merge(condition) if condition_valid
|
|
929
|
+
if condition_valid && operand.then_branch
|
|
656
930
|
evaluation = evaluation.merge(evaluate_at(operand.then_branch, value, MISSING_SEGMENT, "then"))
|
|
657
|
-
elsif !
|
|
931
|
+
elsif !condition_valid && operand.else_branch
|
|
658
932
|
evaluation = evaluation.merge(evaluate_at(operand.else_branch, value, MISSING_SEGMENT, "else"))
|
|
659
933
|
end
|
|
660
934
|
end
|
|
@@ -673,6 +947,20 @@ module Schemurai
|
|
|
673
947
|
end
|
|
674
948
|
|
|
675
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)
|
|
676
964
|
@instance_path << instance_segment unless instance_segment.equal?(MISSING_SEGMENT)
|
|
677
965
|
@schema_path << schema_segment
|
|
678
966
|
@schema_path << child_segment unless child_segment.equal?(MISSING_SEGMENT)
|
|
@@ -684,6 +972,12 @@ module Schemurai
|
|
|
684
972
|
end
|
|
685
973
|
|
|
686
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)
|
|
687
981
|
@instance_path << instance_segment unless instance_segment.equal?(MISSING_SEGMENT)
|
|
688
982
|
@schema_path << schema_segment
|
|
689
983
|
@schema_path << child_segment unless child_segment.equal?(MISSING_SEGMENT)
|
|
@@ -694,7 +988,7 @@ module Schemurai
|
|
|
694
988
|
@instance_path.pop unless instance_segment.equal?(MISSING_SEGMENT)
|
|
695
989
|
end
|
|
696
990
|
|
|
697
|
-
private def add_error(keyword, message, append_keyword: true)
|
|
991
|
+
private def add_error(keyword, message = nil, append_keyword: true)
|
|
698
992
|
@error_count += 1
|
|
699
993
|
return false unless @errors
|
|
700
994
|
|
|
@@ -703,7 +997,7 @@ module Schemurai
|
|
|
703
997
|
keyword: keyword,
|
|
704
998
|
instance_path: pointer(@instance_path),
|
|
705
999
|
schema_path: pointer(@schema_path, final_segment),
|
|
706
|
-
message: message
|
|
1000
|
+
message: message || yield
|
|
707
1001
|
)
|
|
708
1002
|
false
|
|
709
1003
|
end
|
|
@@ -713,30 +1007,38 @@ module Schemurai
|
|
|
713
1007
|
end
|
|
714
1008
|
|
|
715
1009
|
private def json_equal?(left, right)
|
|
716
|
-
|
|
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
|
+
|
|
717
1015
|
case left
|
|
718
1016
|
when Hash
|
|
719
1017
|
left.length == right.length && left.all? do |key, value|
|
|
720
1018
|
right.key?(key) && json_equal?(value, right[key])
|
|
721
1019
|
end
|
|
722
1020
|
when Array
|
|
723
|
-
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
|
|
724
1029
|
else
|
|
725
1030
|
left == right
|
|
726
1031
|
end
|
|
727
1032
|
end
|
|
728
1033
|
|
|
729
|
-
private def json_kind(value)
|
|
730
|
-
return :number if number?(value)
|
|
731
|
-
return :boolean if value == true || value == false
|
|
732
|
-
|
|
733
|
-
value.class
|
|
734
|
-
end
|
|
735
|
-
|
|
736
1034
|
private def decimal(value)
|
|
737
1035
|
return value if value.is_a?(Integer) || value.is_a?(Rational)
|
|
738
1036
|
|
|
739
|
-
|
|
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)
|
|
740
1042
|
end
|
|
741
1043
|
|
|
742
1044
|
private def ecma_regexp(pattern)
|
|
@@ -790,7 +1092,7 @@ module Schemurai
|
|
|
790
1092
|
pointer << "/" << segment.to_s.gsub("~", "~0").gsub("/", "~1")
|
|
791
1093
|
end
|
|
792
1094
|
|
|
793
|
-
private_constant :MISSING_SEGMENT
|
|
1095
|
+
private_constant :DECIMAL_CACHE_LIMIT, :MISSING_SEGMENT
|
|
794
1096
|
end
|
|
795
1097
|
end
|
|
796
1098
|
|