schemurai 3.1.0-aarch64-linux-gnu
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +395 -0
- data/lib/schemurai/backend.rb +34 -0
- 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 +106 -0
- data/lib/schemurai/dialect_keywords.rb +91 -0
- data/lib/schemurai/dialects/draft2019_09.rb +27 -0
- data/lib/schemurai/dialects/draft2020_12.rb +27 -0
- data/lib/schemurai/dialects/draft7.rb +27 -0
- data/lib/schemurai/ecma_regexp.rb +46 -0
- data/lib/schemurai/error_message.rb +102 -0
- data/lib/schemurai/evaluation.rb +63 -0
- data/lib/schemurai/evaluation_limits.rb +65 -0
- data/lib/schemurai/evaluation_pool.rb +66 -0
- data/lib/schemurai/evaluator.rb +861 -0
- data/lib/schemurai/formats.rb +403 -0
- data/lib/schemurai/json_pointer.rb +11 -0
- data/lib/schemurai/limit_budget.rb +78 -0
- data/lib/schemurai/limit_exceeded.rb +18 -0
- data/lib/schemurai/limited_evaluator.rb +43 -0
- data/lib/schemurai/meta_schemas/draft2019_09.rb +149 -0
- data/lib/schemurai/meta_schemas/draft2020_12.rb +149 -0
- data/lib/schemurai/meta_schemas/draft7.rb +121 -0
- data/lib/schemurai/meta_schemas.rb +28 -0
- data/lib/schemurai/native/4.0/schemurai_native.so +0 -0
- data/lib/schemurai/native/diagnostics.rb +53 -0
- data/lib/schemurai/native/evaluator.rb +87 -0
- data/lib/schemurai/native/null_numeric.rb +23 -0
- data/lib/schemurai/number_checks.rb +128 -0
- data/lib/schemurai/schema_domain.rb +95 -0
- data/lib/schemurai/schema_graph.rb +538 -0
- data/lib/schemurai/schema_node.rb +55 -0
- data/lib/schemurai/string_checks.rb +166 -0
- data/lib/schemurai/type_checks.rb +59 -0
- data/lib/schemurai/unique_items.rb +68 -0
- data/lib/schemurai/value_equality.rb +72 -0
- data/lib/schemurai/version.rb +6 -0
- data/lib/schemurai/vm/compiler.rb +410 -0
- data/lib/schemurai/vm/evaluator.rb +663 -0
- data/lib/schemurai/vm/limited_evaluator.rb +27 -0
- data/lib/schemurai.rb +307 -0
- data/lib/schemurai_native.rb +4 -0
- data/sig/schemurai.rbs +108 -0
- metadata +121 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../evaluation"
|
|
4
|
+
require_relative "../error_message"
|
|
5
|
+
|
|
6
|
+
module Schemurai
|
|
7
|
+
module Internal
|
|
8
|
+
module ContainerChecks
|
|
9
|
+
module ObjectDiagnostics
|
|
10
|
+
MISSING_SEGMENT = Object.new.freeze
|
|
11
|
+
|
|
12
|
+
private def check_object(rules, value, prior_evaluation)
|
|
13
|
+
evaluation = nil
|
|
14
|
+
if rules.max_properties && value.length > rules.max_properties
|
|
15
|
+
return Evaluation.invalid unless collecting_object_errors?
|
|
16
|
+
|
|
17
|
+
object_error("maxProperties", rules, value)
|
|
18
|
+
end
|
|
19
|
+
if rules.min_properties && value.length < rules.min_properties
|
|
20
|
+
return Evaluation.invalid unless collecting_object_errors?
|
|
21
|
+
|
|
22
|
+
object_error("minProperties", rules, value)
|
|
23
|
+
end
|
|
24
|
+
if (required = rules.required)
|
|
25
|
+
required.each do |name|
|
|
26
|
+
object_budget&.checkpoint
|
|
27
|
+
next if value.key?(name)
|
|
28
|
+
return Evaluation.invalid unless collecting_object_errors?
|
|
29
|
+
|
|
30
|
+
object_error("required", rules, value, name)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
properties = rules.properties
|
|
35
|
+
patterns = rules.patterns
|
|
36
|
+
value.each do |name, property_value|
|
|
37
|
+
object_budget&.checkpoint
|
|
38
|
+
matched = false
|
|
39
|
+
if (child = properties[name])
|
|
40
|
+
matched = true
|
|
41
|
+
valid = evaluate_child_at(child, property_value, name, "properties", name)
|
|
42
|
+
return Evaluation.invalid if !valid && !collecting_object_errors?
|
|
43
|
+
(evaluation ||= tracked_evaluation).record_property(name)
|
|
44
|
+
end
|
|
45
|
+
if patterns
|
|
46
|
+
patterns.each do |pattern, child|
|
|
47
|
+
object_budget&.checkpoint
|
|
48
|
+
next unless ecma_regexp(pattern).match?(name)
|
|
49
|
+
matched = true
|
|
50
|
+
valid = evaluate_child_at(child, property_value, name, "patternProperties", pattern)
|
|
51
|
+
return Evaluation.invalid if !valid && !collecting_object_errors?
|
|
52
|
+
(evaluation ||= tracked_evaluation).record_property(name)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
if !matched && (additional = rules.additional)
|
|
56
|
+
valid = evaluate_child_at(additional, property_value, name, "additionalProperties")
|
|
57
|
+
return Evaluation.invalid if !valid && !collecting_object_errors?
|
|
58
|
+
(evaluation ||= tracked_evaluation).record_property(name)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if (property_names = rules.property_names)
|
|
63
|
+
value.each_key do |name|
|
|
64
|
+
object_budget&.checkpoint
|
|
65
|
+
valid = evaluate_child_at(property_names, name, name, "propertyNames")
|
|
66
|
+
return Evaluation.invalid if !valid && !collecting_object_errors?
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
if (dependencies = rules.dependencies)
|
|
70
|
+
dependencies.each do |name, dependency|
|
|
71
|
+
object_budget&.checkpoint
|
|
72
|
+
next unless value.key?(name)
|
|
73
|
+
if dependency.is_a?(Array)
|
|
74
|
+
dependency.each do |required_name|
|
|
75
|
+
object_budget&.checkpoint
|
|
76
|
+
unless value.key?(required_name)
|
|
77
|
+
return Evaluation.invalid unless collecting_object_errors?
|
|
78
|
+
|
|
79
|
+
object_error("dependencies", rules, value, name, required_name)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
result = evaluate_at(dependency, value, MISSING_SEGMENT, "dependencies", name)
|
|
84
|
+
if result.valid? && !result.evaluated_properties.empty?
|
|
85
|
+
result.evaluated_properties.each do |property|
|
|
86
|
+
object_budget&.checkpoint
|
|
87
|
+
(evaluation ||= tracked_evaluation).record_property(property)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
if (dependent_required = rules.dependent_required)
|
|
94
|
+
dependent_required.each do |name, required_names|
|
|
95
|
+
object_budget&.checkpoint
|
|
96
|
+
next unless value.key?(name)
|
|
97
|
+
required_names.each do |required_name|
|
|
98
|
+
object_budget&.checkpoint
|
|
99
|
+
unless value.key?(required_name)
|
|
100
|
+
return Evaluation.invalid unless collecting_object_errors?
|
|
101
|
+
|
|
102
|
+
object_error("dependentRequired", rules, value, name, required_name)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
if (dependent_schemas = rules.dependent_schemas)
|
|
108
|
+
dependent_schemas.each do |name, child|
|
|
109
|
+
object_budget&.checkpoint
|
|
110
|
+
next unless value.key?(name)
|
|
111
|
+
result = evaluate_at(child, value, MISSING_SEGMENT, "dependentSchemas", name)
|
|
112
|
+
if result.valid? && !result.evaluated_properties.empty?
|
|
113
|
+
result.evaluated_properties.each do |property|
|
|
114
|
+
object_budget&.checkpoint
|
|
115
|
+
(evaluation ||= tracked_evaluation).record_property(property)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
if (unevaluated = rules.unevaluated)
|
|
121
|
+
prior_properties = prior_evaluation.evaluated_properties
|
|
122
|
+
value.each_key do |name|
|
|
123
|
+
object_budget&.checkpoint
|
|
124
|
+
next if prior_properties.include?(name) || evaluation&.evaluated_properties&.include?(name)
|
|
125
|
+
|
|
126
|
+
valid = evaluate_child_at(unevaluated, value[name], name, "unevaluatedProperties")
|
|
127
|
+
return Evaluation.invalid if !valid && !collecting_object_errors?
|
|
128
|
+
(evaluation ||= tracked_evaluation).record_property(name)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
return Evaluation.valid unless evaluation
|
|
132
|
+
|
|
133
|
+
evaluation
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
private def object_budget = @budget
|
|
137
|
+
private def collecting_object_errors? = @errors
|
|
138
|
+
|
|
139
|
+
private def object_error(keyword, rules, value, name = nil, required_name = nil)
|
|
140
|
+
add_error(keyword) do
|
|
141
|
+
case keyword
|
|
142
|
+
when "maxProperties" then ErrorMessage.size(keyword, rules.max_properties, value.length)
|
|
143
|
+
when "minProperties" then ErrorMessage.size(keyword, rules.min_properties, value.length)
|
|
144
|
+
when "required" then ErrorMessage.required(name)
|
|
145
|
+
when "dependencies", "dependentRequired" then ErrorMessage.dependent_required(name, required_name)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "string_checks"
|
|
4
|
+
require_relative "evaluation_pool"
|
|
5
|
+
require_relative "container_checks/array_diagnostics"
|
|
6
|
+
require_relative "container_checks/object_diagnostics"
|
|
7
|
+
|
|
8
|
+
require_relative "error_message"
|
|
9
|
+
require_relative "value_equality"
|
|
10
|
+
|
|
11
|
+
module Schemurai
|
|
12
|
+
module Internal
|
|
13
|
+
module ContainerChecks
|
|
14
|
+
module Properties
|
|
15
|
+
module_function def merge(left, left_keys, left_items, right, right_keys, right_items)
|
|
16
|
+
materialize(left, left_keys, left_items).merge(materialize(right, right_keys, right_items))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module_function def materialize(buffer, properties, items)
|
|
20
|
+
return buffer if buffer
|
|
21
|
+
return Evaluation.valid if properties.empty? && items.empty?
|
|
22
|
+
|
|
23
|
+
result = EvaluationBuffer.new
|
|
24
|
+
properties.each { |key| result.record_property(key) }
|
|
25
|
+
items.each { |index| result.record_item(index) }
|
|
26
|
+
result
|
|
27
|
+
end
|
|
28
|
+
private_class_method :materialize
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
module Bounds
|
|
32
|
+
private def valid_container_bounds?(length, maximum, minimum)
|
|
33
|
+
return false if maximum && length > maximum
|
|
34
|
+
return false if minimum && length < minimum
|
|
35
|
+
|
|
36
|
+
true
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
module Required
|
|
41
|
+
private def valid_required_keys?(required, value)
|
|
42
|
+
return true unless required
|
|
43
|
+
|
|
44
|
+
all_keys_present?(required, value)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private def all_keys_present?(names, value)
|
|
48
|
+
names.all? do |name|
|
|
49
|
+
@budget&.checkpoint
|
|
50
|
+
value.key?(name)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private def valid_dependency_keys?(entries, value)
|
|
55
|
+
return true unless entries
|
|
56
|
+
|
|
57
|
+
entries.each do |name, names|
|
|
58
|
+
@budget&.checkpoint
|
|
59
|
+
next unless value.key?(name)
|
|
60
|
+
return false unless all_keys_present?(names, value)
|
|
61
|
+
end
|
|
62
|
+
true
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
module Contains
|
|
67
|
+
private def valid_contains?(rules, value, &block)
|
|
68
|
+
if rules.count_contains
|
|
69
|
+
matches = value.count(&block)
|
|
70
|
+
return false if matches < rules.min_contains || matches > rules.max_contains
|
|
71
|
+
else
|
|
72
|
+
return false unless value.any?(&block)
|
|
73
|
+
end
|
|
74
|
+
true
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
module ArrayChildren
|
|
79
|
+
include Contains
|
|
80
|
+
|
|
81
|
+
private def valid_array_children?(rules, value, length)
|
|
82
|
+
if (prefix_items = rules.prefix_items)
|
|
83
|
+
prefix_items.each_with_index do |child, index|
|
|
84
|
+
@budget&.checkpoint
|
|
85
|
+
break if index >= length
|
|
86
|
+
return false unless yield(child, value[index])
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
items = rules.items
|
|
91
|
+
if rules.items_list
|
|
92
|
+
items.each_with_index do |child, index|
|
|
93
|
+
@budget&.checkpoint
|
|
94
|
+
break if index >= length
|
|
95
|
+
return false unless yield(child, value[index])
|
|
96
|
+
end
|
|
97
|
+
if length > items.length && (additional = rules.additional)
|
|
98
|
+
index = items.length
|
|
99
|
+
while index < length
|
|
100
|
+
@budget&.checkpoint
|
|
101
|
+
return false unless yield(additional, value[index])
|
|
102
|
+
index += 1
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
elsif items
|
|
106
|
+
start = rules.prefix_items&.length || 0
|
|
107
|
+
index = start
|
|
108
|
+
while index < length
|
|
109
|
+
@budget&.checkpoint
|
|
110
|
+
return false unless yield(items, value[index])
|
|
111
|
+
index += 1
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
if (contains = rules.contains)
|
|
116
|
+
return false unless valid_contains?(rules, value) { |item| yield(contains, item) }
|
|
117
|
+
end
|
|
118
|
+
true
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
module ObjectChildren
|
|
123
|
+
include Required
|
|
124
|
+
|
|
125
|
+
private def child_budget = @budget
|
|
126
|
+
|
|
127
|
+
private def valid_object_children?(rules, value)
|
|
128
|
+
properties = rules.properties
|
|
129
|
+
patterns = rules.patterns
|
|
130
|
+
additional = rules.additional
|
|
131
|
+
unless properties.empty? && patterns.nil? && additional.nil?
|
|
132
|
+
value.each do |name, property_value|
|
|
133
|
+
child_budget&.checkpoint
|
|
134
|
+
matched = false
|
|
135
|
+
if (child = properties[name])
|
|
136
|
+
matched = true
|
|
137
|
+
return false unless yield(child, property_value)
|
|
138
|
+
end
|
|
139
|
+
if patterns
|
|
140
|
+
patterns.each do |pattern, child|
|
|
141
|
+
child_budget&.checkpoint
|
|
142
|
+
next unless ecma_regexp(pattern).match?(name)
|
|
143
|
+
matched = true
|
|
144
|
+
return false unless yield(child, property_value)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
return false if !matched && additional && !yield(additional, property_value)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
if (property_names = rules.property_names)
|
|
152
|
+
value.each_key { |name| return false unless yield(property_names, name) }
|
|
153
|
+
end
|
|
154
|
+
if (dependencies = rules.dependencies)
|
|
155
|
+
dependencies.each do |name, dependency|
|
|
156
|
+
child_budget&.checkpoint
|
|
157
|
+
next unless value.key?(name)
|
|
158
|
+
if dependency.is_a?(Array)
|
|
159
|
+
return false unless all_keys_present?(dependency, value)
|
|
160
|
+
else
|
|
161
|
+
return false unless yield(dependency, value)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
return false unless valid_dependency_keys?(rules.dependent_required, value)
|
|
166
|
+
if (dependent_schemas = rules.dependent_schemas)
|
|
167
|
+
dependent_schemas.each do |name, child|
|
|
168
|
+
child_budget&.checkpoint
|
|
169
|
+
next unless value.key?(name)
|
|
170
|
+
return false unless yield(child, value)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
true
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Frames preserve Ruby container operations. Compiled child schema
|
|
178
|
+
# execution remains with the native traversal.
|
|
179
|
+
class Frame
|
|
180
|
+
include Bounds
|
|
181
|
+
|
|
182
|
+
def initialize(rules, value, budget, required)
|
|
183
|
+
@rules = rules
|
|
184
|
+
@value = value
|
|
185
|
+
@budget = budget
|
|
186
|
+
@required = required != 0
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def valid?
|
|
190
|
+
return !@required unless container?
|
|
191
|
+
|
|
192
|
+
length = @value.length
|
|
193
|
+
return false unless valid_container_bounds?(length, maximum, minimum)
|
|
194
|
+
|
|
195
|
+
valid_tail?(length)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def check(step)
|
|
199
|
+
case step
|
|
200
|
+
when 2
|
|
201
|
+
violated = @value.length > maximum
|
|
202
|
+
violated ? false : true
|
|
203
|
+
when 3
|
|
204
|
+
violated = @value.length < minimum
|
|
205
|
+
violated ? false : true
|
|
206
|
+
when 4 then unique?
|
|
207
|
+
when 6
|
|
208
|
+
@value.each do |name, _value|
|
|
209
|
+
@budget&.checkpoint
|
|
210
|
+
@rules.properties[name]
|
|
211
|
+
end
|
|
212
|
+
true
|
|
213
|
+
when 5
|
|
214
|
+
# The VM evaluates this gate even when no additional schema exists.
|
|
215
|
+
_additional = @value.length > @rules.items.length
|
|
216
|
+
true
|
|
217
|
+
else raise "unknown container assertion"
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def message(keyword, _rescued)
|
|
222
|
+
case keyword
|
|
223
|
+
when "type" then ErrorMessage.type(type_name, @value)
|
|
224
|
+
when "maxItems", "maxProperties" then ErrorMessage.size(keyword, maximum, @value.length)
|
|
225
|
+
when "minItems", "minProperties" then ErrorMessage.size(keyword, minimum, @value.length)
|
|
226
|
+
when "uniqueItems" then ErrorMessage.unique_items
|
|
227
|
+
else raise "unknown container diagnostic"
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
private def valid_tail?(_length) = true
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
class ArrayFrame < Frame
|
|
235
|
+
include ValueEquality
|
|
236
|
+
include UniqueItems
|
|
237
|
+
|
|
238
|
+
def container? = @value.is_a?(Array)
|
|
239
|
+
|
|
240
|
+
private def type_name = "array"
|
|
241
|
+
private def maximum = @rules.max_items
|
|
242
|
+
private def minimum = @rules.min_items
|
|
243
|
+
private def unique?
|
|
244
|
+
return true unless @rules.unique
|
|
245
|
+
|
|
246
|
+
unique_items?(@value)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
private def valid_tail?(length)
|
|
250
|
+
return false unless unique?
|
|
251
|
+
|
|
252
|
+
length > @rules.items.length if @rules.items_list
|
|
253
|
+
true
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
class LimitedArrayFrame < ArrayFrame
|
|
258
|
+
include ValueEquality::Checkpoints
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
module ChildCallback
|
|
262
|
+
def initialize(rules, value, budget, required, callback)
|
|
263
|
+
super(rules, value, budget, required)
|
|
264
|
+
@callback = callback
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
class CallbackArrayFrame < ArrayFrame
|
|
269
|
+
include ChildCallback
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
class ContainsFrame < CallbackArrayFrame
|
|
273
|
+
include Contains
|
|
274
|
+
|
|
275
|
+
private def valid_tail?(length)
|
|
276
|
+
return false unless super
|
|
277
|
+
|
|
278
|
+
valid_contains?(@rules, @value) { |item| @callback.call(item) }
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
class LimitedContainsFrame < ContainsFrame
|
|
283
|
+
include ValueEquality::Checkpoints
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
class ArrayChildrenFrame < CallbackArrayFrame
|
|
287
|
+
include ArrayChildren
|
|
288
|
+
|
|
289
|
+
private def valid_tail?(length)
|
|
290
|
+
return false unless unique?
|
|
291
|
+
|
|
292
|
+
valid_array_children?(@rules, @value, length) { |child, item| @callback.evaluate_child(child, item) }
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
class LimitedArrayChildrenFrame < ArrayChildrenFrame
|
|
297
|
+
include ValueEquality::Checkpoints
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
class ArrayReportItems
|
|
301
|
+
def initialize(callback, prior)
|
|
302
|
+
@callback = callback
|
|
303
|
+
@prior = prior
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def valid? = true
|
|
307
|
+
def evaluated_items = self
|
|
308
|
+
def include?(index) = @callback.include_item?(index, @prior)
|
|
309
|
+
|
|
310
|
+
def record_item(index)
|
|
311
|
+
@callback.record_item(index)
|
|
312
|
+
self
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
class ArrayReportFrame < ArrayFrame
|
|
317
|
+
include ArrayDiagnostics
|
|
318
|
+
include ChildCallback
|
|
319
|
+
|
|
320
|
+
def collect
|
|
321
|
+
@failed = false
|
|
322
|
+
unless container?
|
|
323
|
+
if @required && collecting_array_errors?
|
|
324
|
+
@callback.error("type", self)
|
|
325
|
+
end
|
|
326
|
+
return @callback.finish(!@required, nil)
|
|
327
|
+
end
|
|
328
|
+
result = check_array(@rules, @value, ArrayReportItems.new(@callback, true))
|
|
329
|
+
@callback.finish(result.valid? && !@failed, @evaluation)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def message(keyword, rescued)
|
|
333
|
+
return ErrorMessage.contains(@matched_count, @rules.min_contains, @rules.max_contains) if keyword == "contains"
|
|
334
|
+
|
|
335
|
+
super
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
private def collecting_array_errors? = !@callback.silent?
|
|
339
|
+
|
|
340
|
+
private def array_error(keyword, _rules, _value, actual = nil)
|
|
341
|
+
@failed = true
|
|
342
|
+
@matched_count = actual
|
|
343
|
+
@callback.error(keyword, self)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
private def tracked_evaluation = (@evaluation ||= @callback.evaluation_buffer)
|
|
347
|
+
|
|
348
|
+
private def evaluate_child_at(program, instance, index, keyword, child_segment = nil)
|
|
349
|
+
valid = @callback.evaluate(program, instance, index, keyword, child_segment)
|
|
350
|
+
@failed = true unless valid
|
|
351
|
+
valid
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
private def trial(program, instance)
|
|
355
|
+
@callback.trial(program, instance) ? Evaluation.valid : Evaluation.invalid
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
class LimitedArrayReportFrame < ArrayReportFrame
|
|
360
|
+
include ValueEquality::Checkpoints
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
class ObjectFrame < Frame
|
|
364
|
+
include Required
|
|
365
|
+
|
|
366
|
+
def container? = @value.is_a?(Hash)
|
|
367
|
+
|
|
368
|
+
def check(step)
|
|
369
|
+
return super if step < 7
|
|
370
|
+
|
|
371
|
+
if step == 7
|
|
372
|
+
return true unless @rules.required
|
|
373
|
+
|
|
374
|
+
# Preserve errors from malformed required operands before iteration.
|
|
375
|
+
@rules.required.each { break }
|
|
376
|
+
return true
|
|
377
|
+
end
|
|
378
|
+
index = step - 8
|
|
379
|
+
count = iteration_length(@rules.required)
|
|
380
|
+
if index < count
|
|
381
|
+
return check_required_name(iteration_name(@rules.required, index))
|
|
382
|
+
end
|
|
383
|
+
check_dependency(index - count)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def message(keyword, rescued)
|
|
387
|
+
return ErrorMessage.required(@required_name) if keyword == "required"
|
|
388
|
+
if keyword == "dependencies" || keyword == "dependentRequired"
|
|
389
|
+
return ErrorMessage.dependent_required(@dependency_name, @required_name)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
super
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
private def type_name = "object"
|
|
396
|
+
private def maximum = @rules.max_properties
|
|
397
|
+
private def minimum = @rules.min_properties
|
|
398
|
+
private def valid_tail?(_length)
|
|
399
|
+
valid_required_keys?(@rules.required, @value) &&
|
|
400
|
+
valid_dependency_keys?(@rules.dependencies, @value) &&
|
|
401
|
+
valid_dependency_keys?(@rules.dependent_required, @value)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
private def iteration_length(names)
|
|
405
|
+
(Array === names || Hash === names) ? names.length : 0
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
private def iteration_name(names, at)
|
|
409
|
+
return names[at] if Array === names
|
|
410
|
+
|
|
411
|
+
names.each_with_index { |name, index| return name if index == at }
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
private def dependency_steps
|
|
415
|
+
@dependency_steps ||= [@rules.dependencies, @rules.dependent_required].flat_map do |entries|
|
|
416
|
+
(entries || []).flat_map do |name, names|
|
|
417
|
+
[[:trigger, name], [:prepare, names]] + Array.new(iteration_length(names)) { |index| [:key, names, index] }
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
private def check_dependency(index)
|
|
423
|
+
kind, operand, at = dependency_steps.fetch(index)
|
|
424
|
+
case kind
|
|
425
|
+
when :trigger
|
|
426
|
+
@dependency_name = operand
|
|
427
|
+
@budget&.checkpoint
|
|
428
|
+
@value.key?(operand) || false
|
|
429
|
+
when :prepare
|
|
430
|
+
operand.each { break }
|
|
431
|
+
true
|
|
432
|
+
when :key then check_required_name(iteration_name(operand, at))
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
private def check_required_name(name)
|
|
436
|
+
@required_name = name
|
|
437
|
+
@budget&.checkpoint
|
|
438
|
+
@value.key?(name) || false
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
class ObjectReportLocations
|
|
443
|
+
def initialize(properties = Set.new)
|
|
444
|
+
@properties = properties
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def valid? = true
|
|
448
|
+
def evaluated_properties = @properties
|
|
449
|
+
|
|
450
|
+
def record_property(name)
|
|
451
|
+
@properties.add(name)
|
|
452
|
+
self
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
class ObjectReportPrior
|
|
457
|
+
def initialize(callback)
|
|
458
|
+
@callback = callback
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def evaluated_properties = @callback.properties(true)
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
class ObjectReportFrame < ObjectFrame
|
|
465
|
+
include ObjectDiagnostics
|
|
466
|
+
include ChildCallback
|
|
467
|
+
include StringChecks::Regexps
|
|
468
|
+
|
|
469
|
+
def collect
|
|
470
|
+
@failed = false
|
|
471
|
+
unless container?
|
|
472
|
+
@callback.object_error("type", self) if @required && collecting_object_errors?
|
|
473
|
+
return @callback.finish_object(!@required, nil)
|
|
474
|
+
end
|
|
475
|
+
result = check_object(@rules, @value, ObjectReportPrior.new(@callback))
|
|
476
|
+
@callback.finish_object(result.valid? && !@failed, @evaluation)
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
private def collecting_object_errors? = !@callback.silent?
|
|
480
|
+
private def object_budget = @callback.invocation_budget
|
|
481
|
+
private def regexp_cache = (@regexps ||= @callback.regexp_cache)
|
|
482
|
+
private def tracked_evaluation = (@evaluation ||= @callback.evaluation_buffer)
|
|
483
|
+
|
|
484
|
+
private def object_error(keyword, _rules, _value, name = nil, required_name = nil)
|
|
485
|
+
@failed = true
|
|
486
|
+
@dependency_name = name
|
|
487
|
+
@required_name = (keyword == "required") ? name : required_name
|
|
488
|
+
@callback.object_error(keyword, self)
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
private def evaluate_child_at(program, instance, segment, keyword, child = ObjectDiagnostics::MISSING_SEGMENT)
|
|
492
|
+
flags = child.equal?(ObjectDiagnostics::MISSING_SEGMENT) ? 0 : 2
|
|
493
|
+
valid = @callback.evaluate_object(program, instance, segment, keyword, child, flags, false)
|
|
494
|
+
@failed = true unless valid
|
|
495
|
+
valid
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
private def evaluate_at(program, instance, segment, keyword, child)
|
|
499
|
+
flags = segment.equal?(ObjectDiagnostics::MISSING_SEGMENT) ? 3 : 2
|
|
500
|
+
valid, properties = @callback.evaluate_object(program, instance, segment, keyword, child, flags, true)
|
|
501
|
+
@failed = true unless valid
|
|
502
|
+
valid ? ObjectReportLocations.new(properties) : Evaluation.invalid
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
class LimitedObjectReportFrame < ObjectReportFrame
|
|
507
|
+
include StringChecks::TimedRegexps
|
|
508
|
+
|
|
509
|
+
private def regexp_budget = object_budget
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
class ObjectChildrenFrame < ObjectFrame
|
|
513
|
+
include ChildCallback
|
|
514
|
+
include ObjectChildren
|
|
515
|
+
include StringChecks::Regexps
|
|
516
|
+
|
|
517
|
+
private def child_budget = @callback.invocation_budget
|
|
518
|
+
private def regexp_cache = (@regexps ||= @callback.regexp_cache)
|
|
519
|
+
|
|
520
|
+
private def valid_tail?(_length)
|
|
521
|
+
return false unless valid_required_keys?(@rules.required, @value)
|
|
522
|
+
|
|
523
|
+
valid_object_children?(@rules, @value) { |child, item| @callback.evaluate_child(child, item) }
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
class LimitedObjectChildrenFrame < ObjectChildrenFrame
|
|
528
|
+
include StringChecks::TimedRegexps
|
|
529
|
+
|
|
530
|
+
private def regexp_budget = child_budget
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
end
|