schemurai 3.0.0 → 3.0.2
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 +6 -4
- data/ext/schemurai_native/Cargo.lock +55 -1
- data/ext/schemurai_native/Cargo.toml +4 -1
- data/ext/schemurai_native/extconf.rb +1 -1
- data/ext/schemurai_native/glue.c +1679 -62
- data/ext/schemurai_native/protocol.def +45 -0
- data/ext/schemurai_native/src/budget.rs +49 -0
- data/ext/schemurai_native/src/container.rs +460 -0
- data/ext/schemurai_native/src/content.rs +74 -27
- data/ext/schemurai_native/src/diagnostics/array.rs +392 -0
- data/ext/schemurai_native/src/diagnostics/object.rs +218 -0
- data/ext/schemurai_native/src/diagnostics.rs +1087 -307
- data/ext/schemurai_native/src/enumeration.rs +6 -1
- data/ext/schemurai_native/src/evaluation.rs +49 -20
- data/ext/schemurai_native/src/format.rs +126 -14
- data/ext/schemurai_native/src/graph.rs +37 -0
- data/ext/schemurai_native/src/instance_graph.rs +90 -0
- data/ext/schemurai_native/src/integer_scan.rs +132 -0
- data/ext/schemurai_native/src/lib.rs +917 -238
- data/ext/schemurai_native/src/memo.rs +11 -38
- data/ext/schemurai_native/src/number/arbitrary.rs +154 -0
- data/ext/schemurai_native/src/number.rs +250 -59
- data/ext/schemurai_native/src/pattern.rs +147 -109
- data/ext/schemurai_native/src/pointer.rs +97 -0
- data/ext/schemurai_native/src/runtime/properties.rs +162 -0
- data/ext/schemurai_native/src/runtime/reports.rs +266 -0
- data/ext/schemurai_native/src/runtime/scopes.rs +430 -0
- data/ext/schemurai_native/src/runtime.rs +1020 -0
- data/ext/schemurai_native/src/scope.rs +60 -16
- data/ext/schemurai_native/src/string.rs +68 -0
- data/ext/schemurai_native/src/tracking.rs +244 -174
- data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
- data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
- data/lib/schemurai/container_checks.rb +534 -0
- data/lib/schemurai/decimal.rb +11 -0
- data/lib/schemurai/dialect.rb +0 -12
- data/lib/schemurai/ecma_regexp.rb +46 -0
- data/lib/schemurai/evaluation_pool.rb +66 -0
- data/lib/schemurai/evaluator.rb +9 -48
- data/lib/schemurai/json_pointer.rb +11 -0
- data/lib/schemurai/native/diagnostics.rb +4 -3
- data/lib/schemurai/native/evaluator.rb +38 -10
- data/lib/schemurai/native/null_numeric.rb +23 -0
- data/lib/schemurai/number_checks.rb +128 -0
- data/lib/schemurai/string_checks.rb +166 -0
- data/lib/schemurai/type_checks.rb +59 -0
- data/lib/schemurai/value_equality.rb +72 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/compiler.rb +18 -26
- data/lib/schemurai/vm/evaluator.rb +87 -545
- data/lib/schemurai/vm/limited_evaluator.rb +7 -22
- metadata +25 -2
|
@@ -1,84 +1,62 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../decimal"
|
|
3
4
|
require "base64"
|
|
4
5
|
require "json"
|
|
5
|
-
|
|
6
|
+
require_relative "../evaluation_pool"
|
|
6
7
|
require_relative "../evaluation"
|
|
7
8
|
require_relative "../error_message"
|
|
8
9
|
require_relative "../unique_items"
|
|
10
|
+
require_relative "../value_equality"
|
|
11
|
+
require_relative "../container_checks"
|
|
12
|
+
require_relative "../ecma_regexp"
|
|
13
|
+
require_relative "../json_pointer"
|
|
9
14
|
require_relative "compiler"
|
|
15
|
+
require_relative "../string_checks"
|
|
10
16
|
|
|
11
17
|
module Schemurai
|
|
12
18
|
module VM
|
|
13
|
-
|
|
14
|
-
attr_reader :evaluated_properties, :evaluated_items
|
|
15
|
-
|
|
16
|
-
def initialize
|
|
17
|
-
# Applicators can overlap. Array membership makes collecting and merging
|
|
18
|
-
# their annotations quadratic in the number of instance locations.
|
|
19
|
-
@evaluated_properties = Set.new
|
|
20
|
-
@evaluated_items = Set.new
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def valid? = true
|
|
24
|
-
|
|
25
|
-
def reset
|
|
26
|
-
@evaluated_properties.clear
|
|
27
|
-
@evaluated_items.clear
|
|
28
|
-
self
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def record_property(name)
|
|
32
|
-
@evaluated_properties.add(name)
|
|
33
|
-
self
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def record_item(index)
|
|
37
|
-
@evaluated_items.add(index)
|
|
38
|
-
self
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def merge(other)
|
|
42
|
-
return Evaluation.invalid unless other.valid?
|
|
43
|
-
|
|
44
|
-
properties = other.evaluated_properties
|
|
45
|
-
items = other.evaluated_items
|
|
46
|
-
@evaluated_properties.merge(properties) unless properties.empty?
|
|
47
|
-
@evaluated_items.merge(items) unless items.empty?
|
|
48
|
-
self
|
|
49
|
-
end
|
|
50
|
-
end
|
|
19
|
+
EvaluationBuffer = Internal::EvaluationBuffer
|
|
51
20
|
private_constant :EvaluationBuffer
|
|
52
21
|
|
|
53
22
|
class Evaluator
|
|
54
23
|
include Internal::UniqueItems
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
24
|
+
include Internal::ValueEquality
|
|
25
|
+
include Internal::ContainerChecks::Bounds
|
|
26
|
+
include Internal::ContainerChecks::ObjectChildren
|
|
27
|
+
include Internal::ContainerChecks::ArrayChildren
|
|
28
|
+
include Internal::ContainerChecks::ArrayDiagnostics
|
|
29
|
+
include Internal::ContainerChecks::ObjectDiagnostics
|
|
30
|
+
include Internal::NumberChecks::Evaluation
|
|
31
|
+
include Internal::StringChecks::Evaluation
|
|
32
|
+
include Internal::StringChecks::Regexps
|
|
33
|
+
|
|
34
|
+
MISSING_SEGMENT = Internal::ContainerChecks::ObjectDiagnostics::MISSING_SEGMENT
|
|
58
35
|
|
|
59
36
|
def backend = :vm
|
|
60
37
|
|
|
61
|
-
def initialize(graph, compiler, root, content: false, format: false)
|
|
38
|
+
def initialize(graph, compiler, root, content: false, format: false, number_state: nil, regexps: nil, dynamic_scope: nil, active_state: nil, native_references: nil, native_reports: nil)
|
|
62
39
|
@graph = graph
|
|
63
40
|
@compiler = compiler
|
|
64
41
|
@root = root
|
|
65
42
|
@validate_content = content
|
|
66
43
|
@validate_format = format
|
|
67
|
-
@regexps =
|
|
68
|
-
@active =
|
|
44
|
+
@regexps = regexps
|
|
45
|
+
@active = active_state
|
|
46
|
+
@native_reports = native_reports
|
|
47
|
+
@native_references = native_references
|
|
69
48
|
@resolved_references = nil
|
|
70
|
-
@
|
|
71
|
-
@
|
|
72
|
-
@dynamic_scope = nil
|
|
49
|
+
@number_state = number_state
|
|
50
|
+
@dynamic_scope = dynamic_scope
|
|
73
51
|
@instance_path_buffer = nil
|
|
74
52
|
@schema_path_buffer = nil
|
|
75
|
-
@evaluation_pool =
|
|
76
|
-
@evaluation_pool_index = 0
|
|
53
|
+
@evaluation_pool = native_reports ? native_reports[1] : Internal::EvaluationPool.new
|
|
77
54
|
end
|
|
78
55
|
|
|
79
56
|
def validate(instance)
|
|
80
57
|
@errors = []
|
|
81
58
|
prepare_evaluation(paths: true)
|
|
59
|
+
@native_reports[0] = self if @native_reports
|
|
82
60
|
evaluate(@root, instance)
|
|
83
61
|
Result.new(@errors)
|
|
84
62
|
ensure
|
|
@@ -92,9 +70,37 @@ module Schemurai
|
|
|
92
70
|
@instance_path = nil
|
|
93
71
|
@schema_path = nil
|
|
94
72
|
reset_evaluation_pool
|
|
73
|
+
@native_reports[0] = self if @native_reports
|
|
95
74
|
evaluate_valid(@root, instance)
|
|
96
75
|
end
|
|
97
76
|
|
|
77
|
+
# Data exchange for retained native blocks during a compatibility traversal.
|
|
78
|
+
# These methods expose collection state, never programs or instructions.
|
|
79
|
+
private def native_report_context
|
|
80
|
+
[@errors, @error_count || 0, @instance_path, @schema_path]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private def native_clear_collection(paths = false)
|
|
84
|
+
@errors = nil
|
|
85
|
+
@error_count = 0
|
|
86
|
+
if paths
|
|
87
|
+
(@instance_path_buffer ||= []).clear
|
|
88
|
+
(@schema_path_buffer ||= []).clear
|
|
89
|
+
@instance_path = @instance_path_buffer
|
|
90
|
+
@schema_path = @schema_path_buffer
|
|
91
|
+
else
|
|
92
|
+
@instance_path = nil
|
|
93
|
+
@schema_path = nil
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private def native_append_report(errors, count, reset)
|
|
98
|
+
native_clear_collection(reset == 2) unless reset.zero?
|
|
99
|
+
@error_count += count
|
|
100
|
+
@errors.concat(errors) if @errors
|
|
101
|
+
@native_reports[0] = self
|
|
102
|
+
end
|
|
103
|
+
|
|
98
104
|
private def prepare_evaluation(paths:)
|
|
99
105
|
@error_count = 0
|
|
100
106
|
@dynamic_scope&.clear
|
|
@@ -142,19 +148,19 @@ module Schemurai
|
|
|
142
148
|
target = dynamic_target(program, operand)
|
|
143
149
|
return false unless valid_reference?(program, target, instance)
|
|
144
150
|
when :type_null
|
|
145
|
-
return false unless
|
|
151
|
+
return false unless Internal::TypeChecks.single?(instance, TYPE_NULL)
|
|
146
152
|
when :type_boolean
|
|
147
|
-
return false unless instance
|
|
153
|
+
return false unless Internal::TypeChecks.single?(instance, TYPE_BOOLEAN)
|
|
148
154
|
when :type_object
|
|
149
|
-
return false unless
|
|
155
|
+
return false unless Internal::TypeChecks.single?(instance, TYPE_OBJECT)
|
|
150
156
|
when :type_array
|
|
151
|
-
return false unless
|
|
157
|
+
return false unless Internal::TypeChecks.single?(instance, TYPE_ARRAY)
|
|
152
158
|
when :type_number
|
|
153
|
-
return false unless
|
|
159
|
+
return false unless Internal::TypeChecks.single?(instance, TYPE_NUMBER)
|
|
154
160
|
when :type_integer
|
|
155
|
-
return false unless
|
|
161
|
+
return false unless Internal::TypeChecks.single?(instance, TYPE_INTEGER)
|
|
156
162
|
when :type_string
|
|
157
|
-
return false unless
|
|
163
|
+
return false unless Internal::TypeChecks.single?(instance, TYPE_STRING)
|
|
158
164
|
when :typed_number
|
|
159
165
|
return false unless instance.is_a?(Numeric) && !instance.is_a?(Complex) && valid_number?(operand, instance)
|
|
160
166
|
when :typed_integer
|
|
@@ -213,14 +219,11 @@ module Schemurai
|
|
|
213
219
|
end
|
|
214
220
|
|
|
215
221
|
private def reset_evaluation_pool
|
|
216
|
-
@
|
|
222
|
+
@evaluation_pool.reset
|
|
217
223
|
end
|
|
218
224
|
|
|
219
225
|
private def tracked_evaluation
|
|
220
|
-
|
|
221
|
-
index = @evaluation_pool_index
|
|
222
|
-
@evaluation_pool_index += 1
|
|
223
|
-
(pool[index] ||= EvaluationBuffer.new).reset
|
|
226
|
+
@evaluation_pool.acquire
|
|
224
227
|
end
|
|
225
228
|
|
|
226
229
|
private def evaluate(program, instance)
|
|
@@ -254,21 +257,21 @@ module Schemurai
|
|
|
254
257
|
when :dynamic_ref
|
|
255
258
|
evaluation = evaluation.merge(evaluate_dynamic_ref(program, operand, instance))
|
|
256
259
|
when :type_null
|
|
257
|
-
check_compiled_type("null", instance
|
|
260
|
+
check_compiled_type("null", Internal::TypeChecks.single?(instance, TYPE_NULL), instance)
|
|
258
261
|
when :type_boolean
|
|
259
|
-
check_compiled_type("boolean", instance
|
|
262
|
+
check_compiled_type("boolean", Internal::TypeChecks.single?(instance, TYPE_BOOLEAN), instance)
|
|
260
263
|
when :type_object
|
|
261
|
-
check_compiled_type("object",
|
|
264
|
+
check_compiled_type("object", Internal::TypeChecks.single?(instance, TYPE_OBJECT), instance)
|
|
262
265
|
when :type_array
|
|
263
|
-
check_compiled_type("array",
|
|
266
|
+
check_compiled_type("array", Internal::TypeChecks.single?(instance, TYPE_ARRAY), instance)
|
|
264
267
|
when :type_number
|
|
265
|
-
check_compiled_type("number",
|
|
268
|
+
check_compiled_type("number", Internal::TypeChecks.single?(instance, TYPE_NUMBER), instance)
|
|
266
269
|
when :type_integer
|
|
267
|
-
check_compiled_type("integer",
|
|
270
|
+
check_compiled_type("integer", Internal::TypeChecks.single?(instance, TYPE_INTEGER), instance)
|
|
268
271
|
when :type_string
|
|
269
|
-
check_compiled_type("string",
|
|
272
|
+
check_compiled_type("string", Internal::TypeChecks.single?(instance, TYPE_STRING), instance)
|
|
270
273
|
when :typed_number
|
|
271
|
-
if
|
|
274
|
+
if instance.is_a?(Numeric) && !instance.is_a?(Complex)
|
|
272
275
|
check_number(operand, instance)
|
|
273
276
|
else
|
|
274
277
|
check_compiled_type("number", false, instance)
|
|
@@ -372,7 +375,7 @@ module Schemurai
|
|
|
372
375
|
|
|
373
376
|
private def valid_reference?(source, target, instance)
|
|
374
377
|
instances = active_instances(source)
|
|
375
|
-
return true if instances.key?(instance)
|
|
378
|
+
return true if instances.key?(instance) || @native_references&.reference_active?(source, instance)
|
|
376
379
|
|
|
377
380
|
instances[instance] = true
|
|
378
381
|
activated = true
|
|
@@ -407,7 +410,7 @@ module Schemurai
|
|
|
407
410
|
|
|
408
411
|
private def evaluate_reference(source, target, instance, keyword)
|
|
409
412
|
instances = active_instances(source)
|
|
410
|
-
return Evaluation.valid if instances.key?(instance)
|
|
413
|
+
return Evaluation.valid if instances.key?(instance) || @native_references&.reference_active?(source, instance)
|
|
411
414
|
|
|
412
415
|
instances[instance] = true
|
|
413
416
|
activated = true
|
|
@@ -438,36 +441,16 @@ module Schemurai
|
|
|
438
441
|
end
|
|
439
442
|
|
|
440
443
|
private def instance_type_mask(value, integer:)
|
|
441
|
-
|
|
442
|
-
return TYPE_BOOLEAN if value == true || value == false
|
|
443
|
-
return TYPE_OBJECT if value.is_a?(Hash)
|
|
444
|
-
return TYPE_ARRAY if value.is_a?(Array)
|
|
445
|
-
return TYPE_STRING if value.is_a?(String)
|
|
446
|
-
return 0 unless number?(value)
|
|
447
|
-
return TYPE_NUMBER unless integer
|
|
448
|
-
|
|
449
|
-
(value.finite? && value.to_i == value) ? TYPE_NUMBER | TYPE_INTEGER : TYPE_NUMBER
|
|
444
|
+
Internal::TypeChecks.instance_mask(value, integer: integer)
|
|
450
445
|
end
|
|
451
446
|
|
|
452
447
|
private def integer?(value)
|
|
453
|
-
|
|
454
|
-
end
|
|
455
|
-
|
|
456
|
-
private def valid_number?(rules, value)
|
|
457
|
-
mask = rules.mask
|
|
458
|
-
actual = (mask & MULTIPLE_OF).zero? ? value : decimal(value)
|
|
459
|
-
return false if (mask & MAXIMUM) != 0 && actual > rules.maximum
|
|
460
|
-
return false if (mask & MINIMUM) != 0 && actual < rules.minimum
|
|
461
|
-
return false if (mask & EXCLUSIVE_MAXIMUM) != 0 && actual >= rules.exclusive_maximum
|
|
462
|
-
return false if (mask & EXCLUSIVE_MINIMUM) != 0 && actual <= rules.exclusive_minimum
|
|
463
|
-
return true if (mask & MULTIPLE_OF).zero?
|
|
464
|
-
|
|
465
|
-
valid_multiple?(rules, value)
|
|
448
|
+
Internal::TypeChecks.integer?(value)
|
|
466
449
|
end
|
|
467
450
|
|
|
468
451
|
private def check_number(rules, value)
|
|
469
452
|
mask = rules.mask
|
|
470
|
-
actual = (mask & MULTIPLE_OF).zero? ? value : decimal(value)
|
|
453
|
+
actual = (mask & MULTIPLE_OF).zero? ? value : number_state.decimal(value)
|
|
471
454
|
if (mask & MAXIMUM) != 0 && actual > rules.maximum
|
|
472
455
|
add_error("maximum") { Internal::ErrorMessage.numeric_limit("maximum", rules.maximum) }
|
|
473
456
|
end
|
|
@@ -491,33 +474,6 @@ module Schemurai
|
|
|
491
474
|
add_error("multipleOf") { Internal::ErrorMessage.multiple_of(divisor) } unless valid
|
|
492
475
|
end
|
|
493
476
|
|
|
494
|
-
private def valid_multiple?(rules, value)
|
|
495
|
-
results = (@multiple_results ||= {}.compare_by_identity)
|
|
496
|
-
values = (results[rules] ||= {})
|
|
497
|
-
return values[value] if values.key?(value)
|
|
498
|
-
|
|
499
|
-
values.clear if values.length >= DECIMAL_CACHE_LIMIT
|
|
500
|
-
divisor = rules.multiple_of
|
|
501
|
-
values[value] = divisor.positive? && decimal(value).remainder(divisor).zero?
|
|
502
|
-
end
|
|
503
|
-
|
|
504
|
-
private def valid_string?(rules, value)
|
|
505
|
-
length = value.length
|
|
506
|
-
return false if rules.max_length && length > rules.max_length
|
|
507
|
-
return false if rules.min_length && length < rules.min_length
|
|
508
|
-
return false if rules.pattern && !ecma_regexp(rules.pattern).match?(value)
|
|
509
|
-
if rules.format && (@validate_format || rules.format_assertion)
|
|
510
|
-
return false unless rules.format.call(value)
|
|
511
|
-
end
|
|
512
|
-
if @validate_content && (rules.decode_base64 || rules.parse_json)
|
|
513
|
-
return valid_content?(rules, value)
|
|
514
|
-
end
|
|
515
|
-
|
|
516
|
-
true
|
|
517
|
-
rescue RegexpError, IPAddr::InvalidAddressError
|
|
518
|
-
false
|
|
519
|
-
end
|
|
520
|
-
|
|
521
477
|
private def check_string(rules, value)
|
|
522
478
|
length = value.length
|
|
523
479
|
if rules.max_length && length > rules.max_length
|
|
@@ -537,14 +493,6 @@ module Schemurai
|
|
|
537
493
|
add_error("pattern") { Internal::ErrorMessage.invalid_pattern(rules.pattern) }
|
|
538
494
|
end
|
|
539
495
|
|
|
540
|
-
private def valid_content?(rules, value)
|
|
541
|
-
decoded = rules.decode_base64 ? Base64.strict_decode64(value) : value
|
|
542
|
-
JSON.parse(decoded) if rules.parse_json
|
|
543
|
-
true
|
|
544
|
-
rescue ArgumentError, JSON::ParserError
|
|
545
|
-
false
|
|
546
|
-
end
|
|
547
|
-
|
|
548
496
|
private def check_content(rules, value)
|
|
549
497
|
decoded = rules.decode_base64 ? Base64.strict_decode64(value) : value
|
|
550
498
|
return unless rules.parse_json
|
|
@@ -559,347 +507,18 @@ module Schemurai
|
|
|
559
507
|
|
|
560
508
|
private def valid_array?(rules, value)
|
|
561
509
|
length = value.length
|
|
562
|
-
return false
|
|
563
|
-
return false if rules.min_items && length < rules.min_items
|
|
510
|
+
return false unless valid_container_bounds?(length, rules.max_items, rules.min_items)
|
|
564
511
|
return false if rules.unique && !unique_items?(value)
|
|
565
512
|
|
|
566
|
-
|
|
567
|
-
prefix_items.each_with_index do |child, index|
|
|
568
|
-
@budget&.checkpoint
|
|
569
|
-
break if index >= length
|
|
570
|
-
return false unless evaluate_valid(child, value[index])
|
|
571
|
-
end
|
|
572
|
-
end
|
|
573
|
-
|
|
574
|
-
items = rules.items
|
|
575
|
-
if rules.items_list
|
|
576
|
-
items.each_with_index do |child, index|
|
|
577
|
-
@budget&.checkpoint
|
|
578
|
-
break if index >= length
|
|
579
|
-
return false unless evaluate_valid(child, value[index])
|
|
580
|
-
end
|
|
581
|
-
if length > items.length && (additional = rules.additional)
|
|
582
|
-
index = items.length
|
|
583
|
-
while index < length
|
|
584
|
-
@budget&.checkpoint
|
|
585
|
-
return false unless evaluate_valid(additional, value[index])
|
|
586
|
-
index += 1
|
|
587
|
-
end
|
|
588
|
-
end
|
|
589
|
-
elsif items
|
|
590
|
-
start = rules.prefix_items&.length || 0
|
|
591
|
-
index = start
|
|
592
|
-
while index < length
|
|
593
|
-
@budget&.checkpoint
|
|
594
|
-
return false unless evaluate_valid(items, value[index])
|
|
595
|
-
index += 1
|
|
596
|
-
end
|
|
597
|
-
end
|
|
598
|
-
|
|
599
|
-
if (contains = rules.contains)
|
|
600
|
-
if rules.count_contains
|
|
601
|
-
matches = value.count { |item| evaluate_valid(contains, item) }
|
|
602
|
-
return false if matches < rules.min_contains || matches > rules.max_contains
|
|
603
|
-
else
|
|
604
|
-
return false unless value.any? { |item| evaluate_valid(contains, item) }
|
|
605
|
-
end
|
|
606
|
-
end
|
|
607
|
-
true
|
|
608
|
-
end
|
|
609
|
-
|
|
610
|
-
private def check_array(rules, value, prior_evaluation)
|
|
611
|
-
evaluation = nil
|
|
612
|
-
if rules.max_items && value.length > rules.max_items
|
|
613
|
-
return Evaluation.invalid unless @errors
|
|
614
|
-
|
|
615
|
-
add_error("maxItems") { Internal::ErrorMessage.size("maxItems", rules.max_items, value.length) }
|
|
616
|
-
end
|
|
617
|
-
if rules.min_items && value.length < rules.min_items
|
|
618
|
-
return Evaluation.invalid unless @errors
|
|
619
|
-
|
|
620
|
-
add_error("minItems") { Internal::ErrorMessage.size("minItems", rules.min_items, value.length) }
|
|
621
|
-
end
|
|
622
|
-
if rules.unique && !unique_items?(value)
|
|
623
|
-
return Evaluation.invalid unless @errors
|
|
624
|
-
|
|
625
|
-
add_error("uniqueItems") { Internal::ErrorMessage.unique_items }
|
|
626
|
-
end
|
|
627
|
-
|
|
628
|
-
if (prefix_items = rules.prefix_items)
|
|
629
|
-
prefix_items.each_with_index do |child, index|
|
|
630
|
-
@budget&.checkpoint
|
|
631
|
-
break if index >= value.length
|
|
632
|
-
valid = evaluate_child_at(child, value[index], index, "prefixItems", index)
|
|
633
|
-
return Evaluation.invalid if !valid && !@errors
|
|
634
|
-
(evaluation ||= tracked_evaluation).record_item(index)
|
|
635
|
-
end
|
|
636
|
-
end
|
|
637
|
-
|
|
638
|
-
items = rules.items
|
|
639
|
-
if rules.items_list
|
|
640
|
-
items.each_with_index do |child, index|
|
|
641
|
-
@budget&.checkpoint
|
|
642
|
-
break if index >= value.length
|
|
643
|
-
valid = evaluate_child_at(child, value[index], index, "items", index)
|
|
644
|
-
return Evaluation.invalid if !valid && !@errors
|
|
645
|
-
(evaluation ||= tracked_evaluation).record_item(index)
|
|
646
|
-
end
|
|
647
|
-
if value.length > items.length && (additional = rules.additional)
|
|
648
|
-
(items.length...value.length).each do |index|
|
|
649
|
-
@budget&.checkpoint
|
|
650
|
-
valid = evaluate_child_at(additional, value[index], index, "additionalItems")
|
|
651
|
-
return Evaluation.invalid if !valid && !@errors
|
|
652
|
-
(evaluation ||= tracked_evaluation).record_item(index)
|
|
653
|
-
end
|
|
654
|
-
end
|
|
655
|
-
elsif items
|
|
656
|
-
start = rules.prefix_items&.length || 0
|
|
657
|
-
(start...value.length).each do |index|
|
|
658
|
-
@budget&.checkpoint
|
|
659
|
-
valid = evaluate_child_at(items, value[index], index, "items")
|
|
660
|
-
return Evaluation.invalid if !valid && !@errors
|
|
661
|
-
(evaluation ||= tracked_evaluation).record_item(index)
|
|
662
|
-
end
|
|
663
|
-
end
|
|
664
|
-
|
|
665
|
-
if (contains = rules.contains)
|
|
666
|
-
matched = nil
|
|
667
|
-
index = 0
|
|
668
|
-
while index < value.length
|
|
669
|
-
@budget&.checkpoint
|
|
670
|
-
(matched ||= []) << index if trial(contains, value[index]).valid?
|
|
671
|
-
index += 1
|
|
672
|
-
end
|
|
673
|
-
matched_count = matched ? matched.length : 0
|
|
674
|
-
unless matched_count.between?(rules.min_contains, rules.max_contains)
|
|
675
|
-
return Evaluation.invalid unless @errors
|
|
676
|
-
|
|
677
|
-
add_error("contains") do
|
|
678
|
-
Internal::ErrorMessage.contains(matched_count, rules.min_contains, rules.max_contains)
|
|
679
|
-
end
|
|
680
|
-
end
|
|
681
|
-
if matched
|
|
682
|
-
matched.each { |index| (evaluation ||= tracked_evaluation).record_item(index) }
|
|
683
|
-
end
|
|
684
|
-
end
|
|
685
|
-
|
|
686
|
-
if (unevaluated = rules.unevaluated)
|
|
687
|
-
prior_items = prior_evaluation.evaluated_items
|
|
688
|
-
index = 0
|
|
689
|
-
while index < value.length
|
|
690
|
-
@budget&.checkpoint
|
|
691
|
-
if prior_items.include?(index) || evaluation&.evaluated_items&.include?(index)
|
|
692
|
-
index += 1
|
|
693
|
-
next
|
|
694
|
-
end
|
|
695
|
-
valid = evaluate_child_at(unevaluated, value[index], index, "unevaluatedItems")
|
|
696
|
-
return Evaluation.invalid if !valid && !@errors
|
|
697
|
-
(evaluation ||= tracked_evaluation).record_item(index)
|
|
698
|
-
index += 1
|
|
699
|
-
end
|
|
700
|
-
end
|
|
701
|
-
return Evaluation.valid unless evaluation
|
|
702
|
-
|
|
703
|
-
evaluation
|
|
513
|
+
valid_array_children?(rules, value, length) { |child, item| evaluate_valid(child, item) }
|
|
704
514
|
end
|
|
705
515
|
|
|
706
516
|
private def valid_object?(rules, value)
|
|
707
517
|
length = value.length
|
|
708
|
-
return false
|
|
709
|
-
return false
|
|
710
|
-
if rules.required && !rules.required.all? do |name|
|
|
711
|
-
@budget&.checkpoint
|
|
712
|
-
value.key?(name)
|
|
713
|
-
end
|
|
714
|
-
return false
|
|
715
|
-
end
|
|
716
|
-
|
|
717
|
-
properties = rules.properties
|
|
718
|
-
patterns = rules.patterns
|
|
719
|
-
additional = rules.additional
|
|
720
|
-
unless properties.empty? && patterns.nil? && additional.nil?
|
|
721
|
-
value.each do |name, property_value|
|
|
722
|
-
@budget&.checkpoint
|
|
723
|
-
matched = false
|
|
724
|
-
if (child = properties[name])
|
|
725
|
-
matched = true
|
|
726
|
-
return false unless evaluate_valid(child, property_value)
|
|
727
|
-
end
|
|
728
|
-
if patterns
|
|
729
|
-
patterns.each do |pattern, child|
|
|
730
|
-
@budget&.checkpoint
|
|
731
|
-
next unless ecma_regexp(pattern).match?(name)
|
|
732
|
-
matched = true
|
|
733
|
-
return false unless evaluate_valid(child, property_value)
|
|
734
|
-
end
|
|
735
|
-
end
|
|
736
|
-
return false if !matched && additional && !evaluate_valid(additional, property_value)
|
|
737
|
-
end
|
|
738
|
-
end
|
|
739
|
-
|
|
740
|
-
if (property_names = rules.property_names)
|
|
741
|
-
value.each_key { |name| return false unless evaluate_valid(property_names, name) }
|
|
742
|
-
end
|
|
743
|
-
if (dependencies = rules.dependencies)
|
|
744
|
-
dependencies.each do |name, dependency|
|
|
745
|
-
@budget&.checkpoint
|
|
746
|
-
next unless value.key?(name)
|
|
747
|
-
if dependency.is_a?(Array)
|
|
748
|
-
return false unless dependency.all? do |required_name|
|
|
749
|
-
@budget&.checkpoint
|
|
750
|
-
value.key?(required_name)
|
|
751
|
-
end
|
|
752
|
-
else
|
|
753
|
-
return false unless evaluate_valid(dependency, value)
|
|
754
|
-
end
|
|
755
|
-
end
|
|
756
|
-
end
|
|
757
|
-
if (dependent_required = rules.dependent_required)
|
|
758
|
-
dependent_required.each do |name, required_names|
|
|
759
|
-
@budget&.checkpoint
|
|
760
|
-
next unless value.key?(name)
|
|
761
|
-
return false unless required_names.all? do |required_name|
|
|
762
|
-
@budget&.checkpoint
|
|
763
|
-
value.key?(required_name)
|
|
764
|
-
end
|
|
765
|
-
end
|
|
766
|
-
end
|
|
767
|
-
if (dependent_schemas = rules.dependent_schemas)
|
|
768
|
-
dependent_schemas.each do |name, child|
|
|
769
|
-
@budget&.checkpoint
|
|
770
|
-
next unless value.key?(name)
|
|
771
|
-
return false unless evaluate_valid(child, value)
|
|
772
|
-
end
|
|
773
|
-
end
|
|
774
|
-
true
|
|
775
|
-
end
|
|
776
|
-
|
|
777
|
-
private def check_object(rules, value, prior_evaluation)
|
|
778
|
-
evaluation = nil
|
|
779
|
-
if rules.max_properties && value.length > rules.max_properties
|
|
780
|
-
return Evaluation.invalid unless @errors
|
|
781
|
-
|
|
782
|
-
add_error("maxProperties") do
|
|
783
|
-
Internal::ErrorMessage.size("maxProperties", rules.max_properties, value.length)
|
|
784
|
-
end
|
|
785
|
-
end
|
|
786
|
-
if rules.min_properties && value.length < rules.min_properties
|
|
787
|
-
return Evaluation.invalid unless @errors
|
|
788
|
-
|
|
789
|
-
add_error("minProperties") do
|
|
790
|
-
Internal::ErrorMessage.size("minProperties", rules.min_properties, value.length)
|
|
791
|
-
end
|
|
792
|
-
end
|
|
793
|
-
if (required = rules.required)
|
|
794
|
-
required.each do |name|
|
|
795
|
-
@budget&.checkpoint
|
|
796
|
-
next if value.key?(name)
|
|
797
|
-
return Evaluation.invalid unless @errors
|
|
798
|
-
|
|
799
|
-
add_error("required") { Internal::ErrorMessage.required(name) }
|
|
800
|
-
end
|
|
801
|
-
end
|
|
802
|
-
|
|
803
|
-
properties = rules.properties
|
|
804
|
-
patterns = rules.patterns
|
|
805
|
-
value.each do |name, property_value|
|
|
806
|
-
@budget&.checkpoint
|
|
807
|
-
matched = false
|
|
808
|
-
if (child = properties[name])
|
|
809
|
-
matched = true
|
|
810
|
-
valid = evaluate_child_at(child, property_value, name, "properties", name)
|
|
811
|
-
return Evaluation.invalid if !valid && !@errors
|
|
812
|
-
(evaluation ||= tracked_evaluation).record_property(name)
|
|
813
|
-
end
|
|
814
|
-
if patterns
|
|
815
|
-
patterns.each do |pattern, child|
|
|
816
|
-
@budget&.checkpoint
|
|
817
|
-
next unless ecma_regexp(pattern).match?(name)
|
|
818
|
-
matched = true
|
|
819
|
-
valid = evaluate_child_at(child, property_value, name, "patternProperties", pattern)
|
|
820
|
-
return Evaluation.invalid if !valid && !@errors
|
|
821
|
-
(evaluation ||= tracked_evaluation).record_property(name)
|
|
822
|
-
end
|
|
823
|
-
end
|
|
824
|
-
if !matched && (additional = rules.additional)
|
|
825
|
-
valid = evaluate_child_at(additional, property_value, name, "additionalProperties")
|
|
826
|
-
return Evaluation.invalid if !valid && !@errors
|
|
827
|
-
(evaluation ||= tracked_evaluation).record_property(name)
|
|
828
|
-
end
|
|
829
|
-
end
|
|
830
|
-
|
|
831
|
-
if (property_names = rules.property_names)
|
|
832
|
-
value.each_key do |name|
|
|
833
|
-
@budget&.checkpoint
|
|
834
|
-
valid = evaluate_child_at(property_names, name, name, "propertyNames")
|
|
835
|
-
return Evaluation.invalid if !valid && !@errors
|
|
836
|
-
end
|
|
837
|
-
end
|
|
838
|
-
if (dependencies = rules.dependencies)
|
|
839
|
-
dependencies.each do |name, dependency|
|
|
840
|
-
@budget&.checkpoint
|
|
841
|
-
next unless value.key?(name)
|
|
842
|
-
if dependency.is_a?(Array)
|
|
843
|
-
dependency.each do |required_name|
|
|
844
|
-
@budget&.checkpoint
|
|
845
|
-
unless value.key?(required_name)
|
|
846
|
-
return Evaluation.invalid unless @errors
|
|
847
|
-
|
|
848
|
-
add_error("dependencies") { Internal::ErrorMessage.dependent_required(name, required_name) }
|
|
849
|
-
end
|
|
850
|
-
end
|
|
851
|
-
else
|
|
852
|
-
result = evaluate_at(dependency, value, MISSING_SEGMENT, "dependencies", name)
|
|
853
|
-
if result.valid? && !result.evaluated_properties.empty?
|
|
854
|
-
result.evaluated_properties.each do |property|
|
|
855
|
-
@budget&.checkpoint
|
|
856
|
-
(evaluation ||= tracked_evaluation).record_property(property)
|
|
857
|
-
end
|
|
858
|
-
end
|
|
859
|
-
end
|
|
860
|
-
end
|
|
861
|
-
end
|
|
862
|
-
if (dependent_required = rules.dependent_required)
|
|
863
|
-
dependent_required.each do |name, required_names|
|
|
864
|
-
@budget&.checkpoint
|
|
865
|
-
next unless value.key?(name)
|
|
866
|
-
required_names.each do |required_name|
|
|
867
|
-
@budget&.checkpoint
|
|
868
|
-
unless value.key?(required_name)
|
|
869
|
-
return Evaluation.invalid unless @errors
|
|
870
|
-
|
|
871
|
-
add_error("dependentRequired") { Internal::ErrorMessage.dependent_required(name, required_name) }
|
|
872
|
-
end
|
|
873
|
-
end
|
|
874
|
-
end
|
|
875
|
-
end
|
|
876
|
-
if (dependent_schemas = rules.dependent_schemas)
|
|
877
|
-
dependent_schemas.each do |name, child|
|
|
878
|
-
@budget&.checkpoint
|
|
879
|
-
next unless value.key?(name)
|
|
880
|
-
result = evaluate_at(child, value, MISSING_SEGMENT, "dependentSchemas", name)
|
|
881
|
-
if result.valid? && !result.evaluated_properties.empty?
|
|
882
|
-
result.evaluated_properties.each do |property|
|
|
883
|
-
@budget&.checkpoint
|
|
884
|
-
(evaluation ||= tracked_evaluation).record_property(property)
|
|
885
|
-
end
|
|
886
|
-
end
|
|
887
|
-
end
|
|
888
|
-
end
|
|
889
|
-
if (unevaluated = rules.unevaluated)
|
|
890
|
-
prior_properties = prior_evaluation.evaluated_properties
|
|
891
|
-
value.each_key do |name|
|
|
892
|
-
@budget&.checkpoint
|
|
893
|
-
next if prior_properties.include?(name) || evaluation&.evaluated_properties&.include?(name)
|
|
894
|
-
|
|
895
|
-
valid = evaluate_child_at(unevaluated, value[name], name, "unevaluatedProperties")
|
|
896
|
-
return Evaluation.invalid if !valid && !@errors
|
|
897
|
-
(evaluation ||= tracked_evaluation).record_property(name)
|
|
898
|
-
end
|
|
899
|
-
end
|
|
900
|
-
return Evaluation.valid unless evaluation
|
|
518
|
+
return false unless valid_container_bounds?(length, rules.max_properties, rules.min_properties)
|
|
519
|
+
return false unless valid_required_keys?(rules.required, value)
|
|
901
520
|
|
|
902
|
-
|
|
521
|
+
valid_object_children?(rules, value) { |child, item| evaluate_valid(child, item) }
|
|
903
522
|
end
|
|
904
523
|
|
|
905
524
|
private def check_combiner(opcode, operand, value)
|
|
@@ -975,6 +594,7 @@ module Schemurai
|
|
|
975
594
|
ensure
|
|
976
595
|
@errors = saved_errors
|
|
977
596
|
@error_count = saved_count
|
|
597
|
+
@native_reports[0] = self if @native_reports
|
|
978
598
|
if saved_instance_path
|
|
979
599
|
@instance_path = saved_instance_path
|
|
980
600
|
@schema_path = saved_schema_path
|
|
@@ -1021,85 +641,7 @@ module Schemurai
|
|
|
1021
641
|
end
|
|
1022
642
|
|
|
1023
643
|
private def number?(value)
|
|
1024
|
-
|
|
1025
|
-
end
|
|
1026
|
-
|
|
1027
|
-
private def json_equal?(left, right)
|
|
1028
|
-
if left.is_a?(Numeric) && !left.is_a?(Complex)
|
|
1029
|
-
return right.is_a?(Numeric) && !right.is_a?(Complex) && left == right
|
|
1030
|
-
end
|
|
1031
|
-
return false unless left.instance_of?(right.class)
|
|
1032
|
-
|
|
1033
|
-
case left
|
|
1034
|
-
when Hash
|
|
1035
|
-
left.length == right.length && left.all? do |key, value|
|
|
1036
|
-
@budget&.checkpoint
|
|
1037
|
-
right.key?(key) && json_equal?(value, right[key])
|
|
1038
|
-
end
|
|
1039
|
-
when Array
|
|
1040
|
-
return false unless left.length == right.length
|
|
1041
|
-
|
|
1042
|
-
index = 0
|
|
1043
|
-
while index < left.length
|
|
1044
|
-
@budget&.checkpoint
|
|
1045
|
-
return false unless json_equal?(left[index], right[index])
|
|
1046
|
-
index += 1
|
|
1047
|
-
end
|
|
1048
|
-
true
|
|
1049
|
-
else
|
|
1050
|
-
left == right
|
|
1051
|
-
end
|
|
1052
|
-
end
|
|
1053
|
-
|
|
1054
|
-
private def decimal(value)
|
|
1055
|
-
return value if value.is_a?(Integer) || value.is_a?(Rational)
|
|
1056
|
-
|
|
1057
|
-
decimals = (@decimals ||= {})
|
|
1058
|
-
return decimals[value] if decimals.key?(value)
|
|
1059
|
-
|
|
1060
|
-
decimals.clear if decimals.length >= DECIMAL_CACHE_LIMIT
|
|
1061
|
-
decimals[value] = Rational(value.to_s)
|
|
1062
|
-
end
|
|
1063
|
-
|
|
1064
|
-
private def ecma_regexp(pattern)
|
|
1065
|
-
regexps = (@regexps ||= {})
|
|
1066
|
-
return regexps[pattern] if regexps.key?(pattern)
|
|
1067
|
-
|
|
1068
|
-
whitespace = "\\u0009-\\u000D\\u0020\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF"
|
|
1069
|
-
translated = +""
|
|
1070
|
-
escaped = false
|
|
1071
|
-
in_class = false
|
|
1072
|
-
pattern.each_char do |character|
|
|
1073
|
-
@budget&.checkpoint
|
|
1074
|
-
if escaped
|
|
1075
|
-
translated << case character
|
|
1076
|
-
when "d" then in_class ? "0-9" : "[0-9]"
|
|
1077
|
-
when "D" then in_class ? "^0-9" : "[^0-9]"
|
|
1078
|
-
when "w" then in_class ? "A-Za-z0-9_" : "[A-Za-z0-9_]"
|
|
1079
|
-
when "W" then in_class ? "^A-Za-z0-9_" : "[^A-Za-z0-9_]"
|
|
1080
|
-
when "s" then in_class ? whitespace : "[#{whitespace}]"
|
|
1081
|
-
when "S" then in_class ? "^#{whitespace}" : "[^#{whitespace}]"
|
|
1082
|
-
else "\\#{character}"
|
|
1083
|
-
end
|
|
1084
|
-
escaped = false
|
|
1085
|
-
elsif character == "\\"
|
|
1086
|
-
escaped = true
|
|
1087
|
-
elsif character == "["
|
|
1088
|
-
in_class = true
|
|
1089
|
-
translated << character
|
|
1090
|
-
elsif character == "]"
|
|
1091
|
-
in_class = false
|
|
1092
|
-
translated << character
|
|
1093
|
-
elsif character == "^" && !in_class
|
|
1094
|
-
translated << "\\A"
|
|
1095
|
-
elsif character == "$" && !in_class
|
|
1096
|
-
translated << "\\z"
|
|
1097
|
-
else
|
|
1098
|
-
translated << character
|
|
1099
|
-
end
|
|
1100
|
-
end
|
|
1101
|
-
translated << "\\" if escaped
|
|
1102
|
-
regexps[pattern] = Regexp.new(translated)
|
|
644
|
+
Internal::TypeChecks.number?(value)
|
|
1103
645
|
end
|
|
1104
646
|
|
|
1105
647
|
private def pointer(path, final_segment = MISSING_SEGMENT)
|
|
@@ -1110,10 +652,10 @@ module Schemurai
|
|
|
1110
652
|
end
|
|
1111
653
|
|
|
1112
654
|
private def append_pointer_segment(pointer, segment)
|
|
1113
|
-
pointer << "/" <<
|
|
655
|
+
pointer << "/" << Internal::JsonPointer.escape(segment)
|
|
1114
656
|
end
|
|
1115
657
|
|
|
1116
|
-
private_constant :
|
|
658
|
+
private_constant :MISSING_SEGMENT
|
|
1117
659
|
end
|
|
1118
660
|
end
|
|
1119
661
|
|