senko 0.1.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 +7 -0
- data/Gemfile +13 -0
- data/LICENSE +21 -0
- data/README.md +235 -0
- data/Rakefile +56 -0
- data/ext/senko/extconf.rb +5 -0
- data/ext/senko/instructions.c +1 -0
- data/ext/senko/instructions.h +12 -0
- data/ext/senko/senko.c +17 -0
- data/ext/senko/validator.c +107 -0
- data/ext/senko/validator.h +14 -0
- data/lib/senko/cache.rb +57 -0
- data/lib/senko/code_generator.rb +270 -0
- data/lib/senko/compiler/instruction.rb +69 -0
- data/lib/senko/compiler/optimizer.rb +80 -0
- data/lib/senko/compiler/ref_resolver.rb +409 -0
- data/lib/senko/compiler.rb +991 -0
- data/lib/senko/dialect.rb +59 -0
- data/lib/senko/errors.rb +41 -0
- data/lib/senko/format.rb +327 -0
- data/lib/senko/native.rb +11 -0
- data/lib/senko/result.rb +65 -0
- data/lib/senko/schema.rb +58 -0
- data/lib/senko/validator.rb +1391 -0
- data/lib/senko/version.rb +5 -0
- data/lib/senko/vocabulary.rb +25 -0
- data/lib/senko.rb +171 -0
- data/sig/senko.rbs +45 -0
- metadata +170 -0
|
@@ -0,0 +1,1391 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bigdecimal'
|
|
4
|
+
require 'set'
|
|
5
|
+
|
|
6
|
+
require_relative 'compiler/instruction'
|
|
7
|
+
require_relative 'errors'
|
|
8
|
+
require_relative 'format'
|
|
9
|
+
require_relative 'result'
|
|
10
|
+
|
|
11
|
+
module Senko
|
|
12
|
+
class Validator
|
|
13
|
+
MESSAGES = {
|
|
14
|
+
false_schema: 'boolean schema false does not allow any value',
|
|
15
|
+
type: 'expected %{expected}, got %{actual}',
|
|
16
|
+
required: "missing required property '%{property}'",
|
|
17
|
+
minimum: 'value must be >= %{minimum}',
|
|
18
|
+
exclusive_minimum: 'value must be > %{minimum}',
|
|
19
|
+
maximum: 'value must be <= %{maximum}',
|
|
20
|
+
exclusive_maximum: 'value must be < %{maximum}',
|
|
21
|
+
min_length: 'string length must be >= %{min_length}',
|
|
22
|
+
max_length: 'string length must be <= %{max_length}',
|
|
23
|
+
pattern: "string does not match pattern '%{pattern}'",
|
|
24
|
+
min_items: 'array length must be >= %{min_items}',
|
|
25
|
+
max_items: 'array length must be <= %{max_items}',
|
|
26
|
+
unique_items: 'array items must be unique',
|
|
27
|
+
min_contains: 'array must contain at least %{min_contains} matching item(s)',
|
|
28
|
+
max_contains: 'array must contain at most %{max_contains} matching item(s)',
|
|
29
|
+
min_properties: 'object must have >= %{min_properties} properties',
|
|
30
|
+
max_properties: 'object must have <= %{max_properties} properties',
|
|
31
|
+
enum: 'value must be one of: %{values}',
|
|
32
|
+
const: 'value must be %{const}',
|
|
33
|
+
multiple_of: 'value must be a multiple of %{multiple_of}',
|
|
34
|
+
format: "value does not match format '%{format}'",
|
|
35
|
+
not: 'value should not match the schema',
|
|
36
|
+
one_of_none: 'value must match exactly one schema in oneOf (matched none)',
|
|
37
|
+
one_of_multiple: 'value must match exactly one schema in oneOf (matched %{count})',
|
|
38
|
+
any_of: 'value must match at least one schema in anyOf',
|
|
39
|
+
discriminator: "value does not match discriminator property '%{property}'",
|
|
40
|
+
additional_properties: "unexpected property '%{property}'",
|
|
41
|
+
property_names: "property name '%{property}' is invalid",
|
|
42
|
+
dependent_required: "property '%{property}' requires property '%{dependency}'",
|
|
43
|
+
unevaluated_properties: "unevaluated property '%{property}'",
|
|
44
|
+
unevaluated_items: 'unevaluated item at index %{index}',
|
|
45
|
+
custom_keyword: "value failed custom keyword '%{keyword}'"
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
class EvaluationContext
|
|
49
|
+
EMPTY_EVALUATED = {}.freeze
|
|
50
|
+
|
|
51
|
+
def initialize(dynamic_scopes = [])
|
|
52
|
+
@dynamic_scopes = dynamic_scopes.dup
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def evaluated_properties_by_location
|
|
56
|
+
@evaluated_properties_by_location || EMPTY_EVALUATED
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def evaluated_items_by_location
|
|
60
|
+
@evaluated_items_by_location || EMPTY_EVALUATED
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def mark_property(location, name)
|
|
64
|
+
evaluated_properties_for_write[location][name.to_s] = true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def track_evaluation?
|
|
68
|
+
true
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def mark_item(location, index)
|
|
72
|
+
evaluated_items_for_write[location][index] = true
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def property_evaluated?(location, name)
|
|
76
|
+
return false unless @evaluated_properties_by_location
|
|
77
|
+
|
|
78
|
+
properties = @evaluated_properties_by_location.fetch(location, nil)
|
|
79
|
+
properties ? properties.key?(name.to_s) : false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def item_evaluated?(location, index)
|
|
83
|
+
return false unless @evaluated_items_by_location
|
|
84
|
+
|
|
85
|
+
items = @evaluated_items_by_location.fetch(location, nil)
|
|
86
|
+
items ? items.key?(index) : false
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def evaluation_empty?
|
|
90
|
+
(!@evaluated_properties_by_location || @evaluated_properties_by_location.empty?) &&
|
|
91
|
+
(!@evaluated_items_by_location || @evaluated_items_by_location.empty?)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def push_dynamic_scope(anchors)
|
|
95
|
+
@dynamic_scopes << anchors
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def pop_dynamic_scope
|
|
99
|
+
@dynamic_scopes.pop
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def dynamic_target(anchor)
|
|
103
|
+
scope = @dynamic_scopes.find { |candidate| candidate.key?(anchor) }
|
|
104
|
+
scope&.fetch(anchor)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def fork(track_evaluation: true)
|
|
108
|
+
track_evaluation ? self.class.new(@dynamic_scopes) : NoopEvaluationContext.new(@dynamic_scopes)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def merge(child)
|
|
112
|
+
child.evaluated_properties_by_location.each do |location, properties|
|
|
113
|
+
evaluated_properties_for_write[location].update(properties)
|
|
114
|
+
end
|
|
115
|
+
child.evaluated_items_by_location.each do |location, items|
|
|
116
|
+
evaluated_items_for_write[location].update(items)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def evaluated_properties_for_write
|
|
123
|
+
@evaluated_properties_by_location ||= Hash.new { |hash, location| hash[location] = {} }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def evaluated_items_for_write
|
|
127
|
+
@evaluated_items_by_location ||= Hash.new { |hash, location| hash[location] = {} }
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
class NoopEvaluationContext
|
|
132
|
+
def initialize(dynamic_scopes = [])
|
|
133
|
+
@dynamic_scopes = dynamic_scopes.dup
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def mark_property(_location, _name); end
|
|
137
|
+
|
|
138
|
+
def track_evaluation?
|
|
139
|
+
false
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def mark_item(_location, _index); end
|
|
143
|
+
|
|
144
|
+
def property_evaluated?(_location, _name)
|
|
145
|
+
false
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def item_evaluated?(_location, _index)
|
|
149
|
+
false
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def evaluation_empty?
|
|
153
|
+
true
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def push_dynamic_scope(anchors)
|
|
157
|
+
@dynamic_scopes << anchors
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def pop_dynamic_scope
|
|
161
|
+
@dynamic_scopes.pop
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def dynamic_target(anchor)
|
|
165
|
+
scope = @dynamic_scopes.find { |candidate| candidate.key?(anchor) }
|
|
166
|
+
scope&.fetch(anchor)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def fork(track_evaluation: false)
|
|
170
|
+
track_evaluation ? EvaluationContext.new(@dynamic_scopes) : self.class.new(@dynamic_scopes)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def merge(_child); end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
class << self
|
|
177
|
+
def requires_evaluation_tracking?(instructions, seen = {})
|
|
178
|
+
return false unless instructions.is_a?(Array)
|
|
179
|
+
return false if seen[instructions.object_id]
|
|
180
|
+
|
|
181
|
+
seen[instructions.object_id] = true
|
|
182
|
+
instructions.any? { |instruction| instruction_requires_evaluation_tracking?(instruction, seen) }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private
|
|
186
|
+
|
|
187
|
+
def instruction_requires_evaluation_tracking?(instruction, seen)
|
|
188
|
+
return true if %i[unevaluated_properties unevaluated_items].include?(instruction.op)
|
|
189
|
+
return true if instruction.op == :dynamic_ref && instruction.payload[:dynamic]
|
|
190
|
+
|
|
191
|
+
nested_instruction_sets(instruction).any? do |child_instructions|
|
|
192
|
+
requires_evaluation_tracking?(child_instructions, seen)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def nested_instruction_sets(instruction)
|
|
197
|
+
payload = instruction.payload
|
|
198
|
+
|
|
199
|
+
case instruction.op
|
|
200
|
+
when :prefix_items, :all_of, :any_of, :one_of
|
|
201
|
+
payload[:schemas]
|
|
202
|
+
when :properties, :dependent_schemas, :discriminator
|
|
203
|
+
payload[:schemas]&.values || payload[:mapping]&.values || []
|
|
204
|
+
when :pattern_properties
|
|
205
|
+
payload[:patterns].map { |entry| entry[:schema] }
|
|
206
|
+
when :items, :contains, :property_names, :not, :additional_properties
|
|
207
|
+
[payload[:schema]].grep(Array)
|
|
208
|
+
when :if_then_else
|
|
209
|
+
[payload[:if_schema], payload[:then_schema], payload[:else_schema]].compact
|
|
210
|
+
when :ref, :dynamic_ref
|
|
211
|
+
[payload[:instructions]]
|
|
212
|
+
when :dynamic_scope
|
|
213
|
+
payload[:anchors].values
|
|
214
|
+
else
|
|
215
|
+
[]
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def initialize(options = {})
|
|
221
|
+
@options = options
|
|
222
|
+
@custom_formats = options[:custom_formats] || {}
|
|
223
|
+
@messages = MESSAGES.merge(symbolized_messages(options[:messages] || {}))
|
|
224
|
+
@tracking_cache = {}
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def validate(instructions, data, fail_fast: false)
|
|
228
|
+
result = Result.new(fail_fast: fail_fast)
|
|
229
|
+
validate_instructions(instructions, data, result, '', EvaluationContext.new)
|
|
230
|
+
result
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def valid?(instructions, data, track_evaluation: self.class.requires_evaluation_tracking?(instructions))
|
|
234
|
+
context = track_evaluation ? EvaluationContext.new : NoopEvaluationContext.new
|
|
235
|
+
valid_instructions?(instructions, data, '', context)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
private
|
|
239
|
+
|
|
240
|
+
def validate_instructions(instructions, data, result, instance_location, context)
|
|
241
|
+
start_error_count = result.errors.length
|
|
242
|
+
pushed_scopes = push_dynamic_scopes(instructions, context)
|
|
243
|
+
|
|
244
|
+
begin
|
|
245
|
+
instructions.each do |instruction|
|
|
246
|
+
next if instruction.op == :dynamic_scope
|
|
247
|
+
|
|
248
|
+
execute(instruction, data, result, instance_location, context)
|
|
249
|
+
return false if result.fail_fast? && result.errors.length > start_error_count
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
result.errors.length == start_error_count
|
|
253
|
+
ensure
|
|
254
|
+
pushed_scopes.times { context.pop_dynamic_scope }
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def execute(instruction, data, result, instance_location, context)
|
|
259
|
+
case instruction.op
|
|
260
|
+
when :false_schema then add_error(result, instruction, data, :false_schema, {}, instance_location)
|
|
261
|
+
when :type then execute_type(instruction, data, result, instance_location)
|
|
262
|
+
when :enum then execute_enum(instruction, data, result, instance_location)
|
|
263
|
+
when :const then execute_const(instruction, data, result, instance_location)
|
|
264
|
+
when :multiple_of then execute_multiple_of(instruction, data, result, instance_location)
|
|
265
|
+
when :maximum then execute_maximum(instruction, data, result, instance_location)
|
|
266
|
+
when :minimum then execute_minimum(instruction, data, result, instance_location)
|
|
267
|
+
when :max_length then execute_max_length(instruction, data, result, instance_location)
|
|
268
|
+
when :min_length then execute_min_length(instruction, data, result, instance_location)
|
|
269
|
+
when :pattern then execute_pattern(instruction, data, result, instance_location)
|
|
270
|
+
when :format then execute_format(instruction, data, result, instance_location)
|
|
271
|
+
when :max_items then execute_max_items(instruction, data, result, instance_location)
|
|
272
|
+
when :min_items then execute_min_items(instruction, data, result, instance_location)
|
|
273
|
+
when :unique_items then execute_unique_items(instruction, data, result, instance_location)
|
|
274
|
+
when :prefix_items then execute_prefix_items(instruction, data, result, instance_location, context)
|
|
275
|
+
when :items then execute_items(instruction, data, result, instance_location, context)
|
|
276
|
+
when :contains then execute_contains(instruction, data, result, instance_location, context)
|
|
277
|
+
when :max_properties then execute_max_properties(instruction, data, result, instance_location)
|
|
278
|
+
when :min_properties then execute_min_properties(instruction, data, result, instance_location)
|
|
279
|
+
when :required then execute_required(instruction, data, result, instance_location)
|
|
280
|
+
when :properties then execute_properties(instruction, data, result, instance_location, context)
|
|
281
|
+
when :pattern_properties then execute_pattern_properties(instruction, data, result, instance_location, context)
|
|
282
|
+
when :additional_properties then execute_additional_properties(instruction, data, result, instance_location,
|
|
283
|
+
context)
|
|
284
|
+
when :property_names then execute_property_names(instruction, data, result, instance_location, context)
|
|
285
|
+
when :dependent_required then execute_dependent_required(instruction, data, result, instance_location)
|
|
286
|
+
when :dependent_schemas then execute_dependent_schemas(instruction, data, result, instance_location, context)
|
|
287
|
+
when :all_of then execute_all_of(instruction, data, result, instance_location, context)
|
|
288
|
+
when :any_of then execute_any_of(instruction, data, result, instance_location, context)
|
|
289
|
+
when :one_of then execute_one_of(instruction, data, result, instance_location, context)
|
|
290
|
+
when :not then execute_not(instruction, data, result, instance_location, context)
|
|
291
|
+
when :if_then_else then execute_if_then_else(instruction, data, result, instance_location, context)
|
|
292
|
+
when :discriminator then execute_discriminator(instruction, data, result, instance_location, context)
|
|
293
|
+
when :ref then validate_instructions(instruction.payload[:instructions], data, result, instance_location, context)
|
|
294
|
+
when :dynamic_ref then execute_dynamic_ref(instruction, data, result, instance_location, context)
|
|
295
|
+
when :unevaluated_properties then execute_unevaluated_properties(instruction, data, result, instance_location,
|
|
296
|
+
context)
|
|
297
|
+
when :unevaluated_items then execute_unevaluated_items(instruction, data, result, instance_location, context)
|
|
298
|
+
when :custom_keyword then execute_custom_keyword(instruction, data, result, instance_location)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def valid_instructions?(instructions, data, instance_location, context)
|
|
303
|
+
unless dynamic_scope_prefix?(instructions)
|
|
304
|
+
return valid_unscoped_instructions?(instructions, data, instance_location,
|
|
305
|
+
context)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
valid_scoped_instructions?(instructions, data, instance_location, context)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def valid_unscoped_instructions?(instructions, data, instance_location, context)
|
|
312
|
+
instructions.each do |instruction|
|
|
313
|
+
return false unless valid_instruction?(instruction, data, instance_location, context)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
true
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def valid_scoped_instructions?(instructions, data, instance_location, context)
|
|
320
|
+
pushed_scopes = push_dynamic_scopes(instructions, context)
|
|
321
|
+
|
|
322
|
+
begin
|
|
323
|
+
instructions.each do |instruction|
|
|
324
|
+
next if instruction.op == :dynamic_scope
|
|
325
|
+
|
|
326
|
+
return false unless valid_instruction?(instruction, data, instance_location, context)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
true
|
|
330
|
+
ensure
|
|
331
|
+
pushed_scopes.times { context.pop_dynamic_scope }
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def dynamic_scope_prefix?(instructions)
|
|
336
|
+
!instructions.empty? && instructions.first.op == :dynamic_scope
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def valid_instruction?(instruction, data, instance_location, context)
|
|
340
|
+
payload = instruction.payload
|
|
341
|
+
|
|
342
|
+
case instruction.op
|
|
343
|
+
when :false_schema
|
|
344
|
+
false
|
|
345
|
+
when :type
|
|
346
|
+
type_mask_for(data).anybits?(payload[:mask])
|
|
347
|
+
when :enum
|
|
348
|
+
payload[:values].any? { |value| json_equal?(value, data) }
|
|
349
|
+
when :const
|
|
350
|
+
json_equal?(payload[:value], data)
|
|
351
|
+
when :multiple_of
|
|
352
|
+
!numeric_instance?(data) || multiple_of?(data, payload[:factor])
|
|
353
|
+
when :maximum
|
|
354
|
+
return true unless numeric_instance?(data)
|
|
355
|
+
|
|
356
|
+
payload[:exclusive] ? native_numeric_lt?(data, payload[:limit]) : native_numeric_lte?(data, payload[:limit])
|
|
357
|
+
when :minimum
|
|
358
|
+
return true unless numeric_instance?(data)
|
|
359
|
+
|
|
360
|
+
payload[:exclusive] ? native_numeric_gt?(data, payload[:limit]) : native_numeric_gte?(data, payload[:limit])
|
|
361
|
+
when :max_length
|
|
362
|
+
!data.is_a?(String) || string_length(data) <= payload[:limit]
|
|
363
|
+
when :min_length
|
|
364
|
+
!data.is_a?(String) || string_length(data) >= payload[:limit]
|
|
365
|
+
when :pattern
|
|
366
|
+
!data.is_a?(String) || data.match?(payload[:pattern])
|
|
367
|
+
when :format
|
|
368
|
+
!data.is_a?(String) || !payload[:assertion] || Senko::Format.valid?(payload[:format], data, @custom_formats)
|
|
369
|
+
when :max_items
|
|
370
|
+
!data.is_a?(Array) || data.length <= payload[:limit]
|
|
371
|
+
when :min_items
|
|
372
|
+
!data.is_a?(Array) || data.length >= payload[:limit]
|
|
373
|
+
when :unique_items
|
|
374
|
+
!data.is_a?(Array) || unique_items?(data)
|
|
375
|
+
when :prefix_items
|
|
376
|
+
valid_prefix_items?(payload, data, instance_location, context)
|
|
377
|
+
when :items
|
|
378
|
+
valid_items?(payload, data, instance_location, context)
|
|
379
|
+
when :contains
|
|
380
|
+
valid_contains?(payload, data, instance_location, context)
|
|
381
|
+
when :max_properties
|
|
382
|
+
!data.is_a?(Hash) || data.length <= payload[:limit]
|
|
383
|
+
when :min_properties
|
|
384
|
+
!data.is_a?(Hash) || data.length >= payload[:limit]
|
|
385
|
+
when :required
|
|
386
|
+
!data.is_a?(Hash) || payload[:keys].all? { |key| data.key?(key) }
|
|
387
|
+
when :properties
|
|
388
|
+
valid_properties?(payload, data, instance_location, context)
|
|
389
|
+
when :pattern_properties
|
|
390
|
+
valid_pattern_properties?(payload, data, instance_location, context)
|
|
391
|
+
when :additional_properties
|
|
392
|
+
valid_additional_properties?(payload, data, instance_location, context)
|
|
393
|
+
when :property_names
|
|
394
|
+
valid_property_names?(payload, data, instance_location, context)
|
|
395
|
+
when :dependent_required
|
|
396
|
+
valid_dependent_required?(payload, data)
|
|
397
|
+
when :dependent_schemas
|
|
398
|
+
valid_dependent_schemas?(payload, data, instance_location, context)
|
|
399
|
+
when :all_of
|
|
400
|
+
valid_all_of?(payload, data, instance_location, context)
|
|
401
|
+
when :any_of
|
|
402
|
+
valid_any_of?(payload, data, instance_location, context)
|
|
403
|
+
when :one_of
|
|
404
|
+
valid_one_of?(payload, data, instance_location, context)
|
|
405
|
+
when :not
|
|
406
|
+
child_context = context.track_evaluation? ? context.fork : context
|
|
407
|
+
!valid_instructions?(payload[:schema], data, instance_location, child_context)
|
|
408
|
+
when :if_then_else
|
|
409
|
+
valid_if_then_else?(payload, data, instance_location, context)
|
|
410
|
+
when :discriminator
|
|
411
|
+
valid_discriminator?(payload, data, instance_location, context)
|
|
412
|
+
when :ref
|
|
413
|
+
valid_instructions?(payload[:instructions], data, instance_location, context)
|
|
414
|
+
when :dynamic_ref
|
|
415
|
+
valid_dynamic_ref?(payload, data, instance_location, context)
|
|
416
|
+
when :unevaluated_properties
|
|
417
|
+
valid_unevaluated_properties?(payload, data, instance_location, context)
|
|
418
|
+
when :unevaluated_items
|
|
419
|
+
valid_unevaluated_items?(payload, data, instance_location, context)
|
|
420
|
+
when :custom_keyword
|
|
421
|
+
custom_keyword_result(instruction, data) == true
|
|
422
|
+
else
|
|
423
|
+
true
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def valid_prefix_items?(payload, data, instance_location, context)
|
|
428
|
+
return true unless data.is_a?(Array)
|
|
429
|
+
|
|
430
|
+
payload[:schemas].each_with_index do |schema, index|
|
|
431
|
+
break if index >= data.length
|
|
432
|
+
|
|
433
|
+
child_location = child_instance_location(context, instance_location, index)
|
|
434
|
+
return false unless valid_child_instance_schema?(schema, data[index], child_location, context)
|
|
435
|
+
|
|
436
|
+
context.mark_item(instance_location, index)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
true
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def valid_items?(payload, data, instance_location, context)
|
|
443
|
+
return true unless data.is_a?(Array)
|
|
444
|
+
|
|
445
|
+
data.each_with_index do |item, index|
|
|
446
|
+
next if index < payload[:start_index]
|
|
447
|
+
|
|
448
|
+
child_location = child_instance_location(context, instance_location, index)
|
|
449
|
+
return false unless valid_child_instance_schema?(payload[:schema], item, child_location, context)
|
|
450
|
+
|
|
451
|
+
context.mark_item(instance_location, index)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
true
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def valid_contains?(payload, data, instance_location, context)
|
|
458
|
+
return true unless data.is_a?(Array)
|
|
459
|
+
|
|
460
|
+
count = 0
|
|
461
|
+
data.each_with_index do |item, index|
|
|
462
|
+
child_location = child_instance_location(context, instance_location, index)
|
|
463
|
+
child_context = context.track_evaluation? ? context.fork : context
|
|
464
|
+
next unless valid_instructions?(payload[:schema], item, child_location, child_context)
|
|
465
|
+
|
|
466
|
+
count += 1
|
|
467
|
+
context.mark_item(instance_location, index)
|
|
468
|
+
context.merge(child_context) if context.track_evaluation?
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
count >= payload[:min] && (!payload[:max] || count <= payload[:max])
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def valid_properties?(payload, data, instance_location, context)
|
|
475
|
+
return true unless data.is_a?(Hash)
|
|
476
|
+
|
|
477
|
+
payload[:schemas].each do |key, schema|
|
|
478
|
+
next unless data.key?(key)
|
|
479
|
+
|
|
480
|
+
child_location = child_instance_location(context, instance_location, key)
|
|
481
|
+
return false unless valid_child_instance_schema?(schema, data[key], child_location, context)
|
|
482
|
+
|
|
483
|
+
context.mark_property(instance_location, key)
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
true
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def valid_pattern_properties?(payload, data, instance_location, context)
|
|
490
|
+
return true unless data.is_a?(Hash)
|
|
491
|
+
|
|
492
|
+
data.each do |key, value|
|
|
493
|
+
payload[:patterns].each do |entry|
|
|
494
|
+
next unless key.match?(entry[:pattern])
|
|
495
|
+
|
|
496
|
+
child_location = child_instance_location(context, instance_location, key)
|
|
497
|
+
return false unless valid_child_instance_schema?(entry[:schema], value, child_location, context)
|
|
498
|
+
|
|
499
|
+
context.mark_property(instance_location, key)
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
true
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def valid_additional_properties?(payload, data, instance_location, context)
|
|
507
|
+
return true unless data.is_a?(Hash)
|
|
508
|
+
|
|
509
|
+
data.each do |key, value|
|
|
510
|
+
next if payload[:known].include?(key)
|
|
511
|
+
next if payload[:patterns].any? { |pattern| key.match?(pattern) }
|
|
512
|
+
return false if payload[:schema] == false
|
|
513
|
+
|
|
514
|
+
child_location = child_instance_location(context, instance_location, key)
|
|
515
|
+
return false unless valid_child_instance_schema?(payload[:schema], value, child_location, context)
|
|
516
|
+
|
|
517
|
+
context.mark_property(instance_location, key)
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
true
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def valid_property_names?(payload, data, instance_location, context)
|
|
524
|
+
return true unless data.is_a?(Hash)
|
|
525
|
+
|
|
526
|
+
data.each_key do |key|
|
|
527
|
+
child_location = child_instance_location(context, instance_location, key)
|
|
528
|
+
child_context = context.track_evaluation? ? context.fork : context
|
|
529
|
+
return false unless valid_instructions?(payload[:schema], key, child_location, child_context)
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
true
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
def valid_dependent_required?(payload, data)
|
|
536
|
+
return true unless data.is_a?(Hash)
|
|
537
|
+
|
|
538
|
+
payload[:requirements].each do |property, dependencies|
|
|
539
|
+
next unless data.key?(property)
|
|
540
|
+
|
|
541
|
+
dependencies.each do |dependency|
|
|
542
|
+
return false unless data.key?(dependency)
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
true
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def valid_dependent_schemas?(payload, data, instance_location, context)
|
|
550
|
+
return true unless data.is_a?(Hash)
|
|
551
|
+
|
|
552
|
+
payload[:schemas].each do |property, schema|
|
|
553
|
+
next unless data.key?(property)
|
|
554
|
+
|
|
555
|
+
return false unless valid_child_schema?(schema, data, instance_location, context)
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
true
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def valid_all_of?(payload, data, instance_location, context)
|
|
562
|
+
if payload[:schemas].length == 1 && context.evaluation_empty?
|
|
563
|
+
return valid_instructions?(payload[:schemas].first, data, instance_location, context)
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
payload[:schemas].each do |schema|
|
|
567
|
+
return false unless valid_child_schema?(schema, data, instance_location, context)
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
true
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
def valid_any_of?(payload, data, instance_location, context)
|
|
574
|
+
unless context.track_evaluation?
|
|
575
|
+
return payload[:schemas].any? { |schema| valid_instructions?(schema, data, instance_location, context) }
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
valid_contexts = []
|
|
579
|
+
|
|
580
|
+
payload[:schemas].each do |schema|
|
|
581
|
+
child_context = context.fork
|
|
582
|
+
valid_contexts << child_context if valid_instructions?(schema, data, instance_location, child_context)
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
return false if valid_contexts.empty?
|
|
586
|
+
|
|
587
|
+
valid_contexts.each { |child_context| context.merge(child_context) }
|
|
588
|
+
true
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
def valid_one_of?(payload, data, instance_location, context)
|
|
592
|
+
unless context.track_evaluation?
|
|
593
|
+
matches = 0
|
|
594
|
+
payload[:schemas].each do |schema|
|
|
595
|
+
next unless valid_instructions?(schema, data, instance_location, context)
|
|
596
|
+
|
|
597
|
+
matches += 1
|
|
598
|
+
return false if matches > 1
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
return matches == 1
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
match_context = nil
|
|
605
|
+
|
|
606
|
+
payload[:schemas].each do |schema|
|
|
607
|
+
child_context = context.fork
|
|
608
|
+
next unless valid_instructions?(schema, data, instance_location, child_context)
|
|
609
|
+
return false if match_context
|
|
610
|
+
|
|
611
|
+
match_context = child_context
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
return false unless match_context
|
|
615
|
+
|
|
616
|
+
context.merge(match_context)
|
|
617
|
+
true
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
def valid_if_then_else?(payload, data, instance_location, context)
|
|
621
|
+
condition_context = context.track_evaluation? ? context.fork : context
|
|
622
|
+
condition_passed = valid_instructions?(payload[:if_schema], data, instance_location, condition_context)
|
|
623
|
+
selected_schema = condition_passed ? payload[:then_schema] : payload[:else_schema]
|
|
624
|
+
|
|
625
|
+
unless selected_schema
|
|
626
|
+
context.merge(condition_context) if condition_passed
|
|
627
|
+
return true
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
return false unless valid_child_schema?(selected_schema, data, instance_location, context)
|
|
631
|
+
|
|
632
|
+
context.merge(condition_context) if condition_passed
|
|
633
|
+
true
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
def valid_discriminator?(payload, data, instance_location, context)
|
|
637
|
+
property = payload[:property]
|
|
638
|
+
return false unless data.is_a?(Hash) && data.key?(property)
|
|
639
|
+
|
|
640
|
+
schema = payload[:mapping][data[property]] || payload[:mapping][data[property].to_s]
|
|
641
|
+
return false unless schema
|
|
642
|
+
|
|
643
|
+
return false unless valid_child_schema?(schema, data, instance_location, context)
|
|
644
|
+
|
|
645
|
+
true
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
def valid_dynamic_ref?(payload, data, instance_location, context)
|
|
649
|
+
target = (context.dynamic_target(payload[:anchor]) if payload[:dynamic] && payload[:anchor])
|
|
650
|
+
target ||= payload[:instructions]
|
|
651
|
+
|
|
652
|
+
valid_instructions?(target, data, instance_location, context)
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def valid_unevaluated_properties?(payload, data, instance_location, context)
|
|
656
|
+
return true unless data.is_a?(Hash)
|
|
657
|
+
|
|
658
|
+
data.each do |key, value|
|
|
659
|
+
next if context.property_evaluated?(instance_location, key)
|
|
660
|
+
return false if payload[:schema] == false
|
|
661
|
+
|
|
662
|
+
return false unless valid_child_instance_schema?(payload[:schema], value,
|
|
663
|
+
join_instance(instance_location, key), context)
|
|
664
|
+
|
|
665
|
+
context.mark_property(instance_location, key)
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
true
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
def valid_unevaluated_items?(payload, data, instance_location, context)
|
|
672
|
+
return true unless data.is_a?(Array)
|
|
673
|
+
|
|
674
|
+
data.each_with_index do |item, index|
|
|
675
|
+
next if context.item_evaluated?(instance_location, index)
|
|
676
|
+
return false if payload[:schema] == false
|
|
677
|
+
|
|
678
|
+
return false unless valid_child_instance_schema?(payload[:schema], item,
|
|
679
|
+
join_instance(instance_location, index), context)
|
|
680
|
+
|
|
681
|
+
context.mark_item(instance_location, index)
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
true
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
def push_dynamic_scopes(instructions, context)
|
|
688
|
+
pushed = 0
|
|
689
|
+
instructions.each do |instruction|
|
|
690
|
+
break unless instruction.op == :dynamic_scope
|
|
691
|
+
|
|
692
|
+
context.push_dynamic_scope(instruction.payload[:anchors])
|
|
693
|
+
pushed += 1
|
|
694
|
+
end
|
|
695
|
+
pushed
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
def execute_type(instruction, data, result, instance_location)
|
|
699
|
+
return if type_mask_for(data).anybits?(instruction.payload[:mask])
|
|
700
|
+
|
|
701
|
+
add_error(
|
|
702
|
+
result,
|
|
703
|
+
instruction,
|
|
704
|
+
data,
|
|
705
|
+
:type,
|
|
706
|
+
{ expected: instruction.payload[:expected].join(' or '), actual: actual_type(data) },
|
|
707
|
+
instance_location
|
|
708
|
+
)
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
def execute_enum(instruction, data, result, instance_location)
|
|
712
|
+
return if instruction.payload[:values].any? { |value| json_equal?(value, data) }
|
|
713
|
+
|
|
714
|
+
add_error(result, instruction, data, :enum, { values: instruction.payload[:values].inspect }, instance_location)
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
def execute_const(instruction, data, result, instance_location)
|
|
718
|
+
return if json_equal?(instruction.payload[:value], data)
|
|
719
|
+
|
|
720
|
+
add_error(result, instruction, data, :const, { const: instruction.payload[:value].inspect }, instance_location)
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
def execute_multiple_of(instruction, data, result, instance_location)
|
|
724
|
+
return unless numeric_instance?(data)
|
|
725
|
+
return if multiple_of?(data, instruction.payload[:factor])
|
|
726
|
+
|
|
727
|
+
add_error(result, instruction, data, :multiple_of, { multiple_of: instruction.payload[:factor] },
|
|
728
|
+
instance_location)
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
def execute_maximum(instruction, data, result, instance_location)
|
|
732
|
+
return unless numeric_instance?(data)
|
|
733
|
+
|
|
734
|
+
limit = instruction.payload[:limit]
|
|
735
|
+
exclusive = instruction.payload[:exclusive]
|
|
736
|
+
return if exclusive ? native_numeric_lt?(data, limit) : native_numeric_lte?(data, limit)
|
|
737
|
+
|
|
738
|
+
key = exclusive ? :exclusive_maximum : :maximum
|
|
739
|
+
add_error(result, instruction, data, key, { maximum: limit }, instance_location)
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
def execute_minimum(instruction, data, result, instance_location)
|
|
743
|
+
return unless numeric_instance?(data)
|
|
744
|
+
|
|
745
|
+
limit = instruction.payload[:limit]
|
|
746
|
+
exclusive = instruction.payload[:exclusive]
|
|
747
|
+
return if exclusive ? native_numeric_gt?(data, limit) : native_numeric_gte?(data, limit)
|
|
748
|
+
|
|
749
|
+
key = exclusive ? :exclusive_minimum : :minimum
|
|
750
|
+
add_error(result, instruction, data, key, { minimum: limit }, instance_location)
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
def execute_max_length(instruction, data, result, instance_location)
|
|
754
|
+
return unless data.is_a?(String)
|
|
755
|
+
return if string_length(data) <= instruction.payload[:limit]
|
|
756
|
+
|
|
757
|
+
add_error(result, instruction, data, :max_length, { max_length: instruction.payload[:limit] }, instance_location)
|
|
758
|
+
end
|
|
759
|
+
|
|
760
|
+
def execute_min_length(instruction, data, result, instance_location)
|
|
761
|
+
return unless data.is_a?(String)
|
|
762
|
+
return if string_length(data) >= instruction.payload[:limit]
|
|
763
|
+
|
|
764
|
+
add_error(result, instruction, data, :min_length, { min_length: instruction.payload[:limit] }, instance_location)
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
def execute_pattern(instruction, data, result, instance_location)
|
|
768
|
+
return unless data.is_a?(String)
|
|
769
|
+
return if data.match?(instruction.payload[:pattern])
|
|
770
|
+
|
|
771
|
+
add_error(result, instruction, data, :pattern, { pattern: instruction.payload[:source] }, instance_location)
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
def execute_format(instruction, data, result, instance_location)
|
|
775
|
+
return unless data.is_a?(String)
|
|
776
|
+
|
|
777
|
+
if instruction.payload[:assertion]
|
|
778
|
+
return if Senko::Format.valid?(instruction.payload[:format], data, @custom_formats)
|
|
779
|
+
|
|
780
|
+
add_error(result, instruction, data, :format, { format: instruction.payload[:format] }, instance_location)
|
|
781
|
+
else
|
|
782
|
+
result.add_annotation(
|
|
783
|
+
'keywordLocation' => instruction.keyword_location,
|
|
784
|
+
'instanceLocation' => instance_location,
|
|
785
|
+
'annotation' => instruction.payload[:format]
|
|
786
|
+
)
|
|
787
|
+
end
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
def execute_max_items(instruction, data, result, instance_location)
|
|
791
|
+
return unless data.is_a?(Array)
|
|
792
|
+
return if data.length <= instruction.payload[:limit]
|
|
793
|
+
|
|
794
|
+
add_error(result, instruction, data, :max_items, { max_items: instruction.payload[:limit] }, instance_location)
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
def execute_min_items(instruction, data, result, instance_location)
|
|
798
|
+
return unless data.is_a?(Array)
|
|
799
|
+
return if data.length >= instruction.payload[:limit]
|
|
800
|
+
|
|
801
|
+
add_error(result, instruction, data, :min_items, { min_items: instruction.payload[:limit] }, instance_location)
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
def execute_unique_items(instruction, data, result, instance_location)
|
|
805
|
+
return unless data.is_a?(Array)
|
|
806
|
+
return if unique_items?(data)
|
|
807
|
+
|
|
808
|
+
add_error(result, instruction, data, :unique_items, {}, instance_location)
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
def execute_prefix_items(instruction, data, result, instance_location, context)
|
|
812
|
+
return unless data.is_a?(Array)
|
|
813
|
+
|
|
814
|
+
instruction.payload[:schemas].each_with_index do |schema, index|
|
|
815
|
+
break if index >= data.length
|
|
816
|
+
|
|
817
|
+
child_context = context.fork
|
|
818
|
+
before = result.errors.length
|
|
819
|
+
valid = validate_instructions(schema, data[index], result, join_instance(instance_location, index),
|
|
820
|
+
child_context)
|
|
821
|
+
if valid
|
|
822
|
+
context.mark_item(instance_location, index)
|
|
823
|
+
context.merge(child_context)
|
|
824
|
+
end
|
|
825
|
+
return if result.fail_fast? && result.errors.length > before
|
|
826
|
+
end
|
|
827
|
+
end
|
|
828
|
+
|
|
829
|
+
def execute_items(instruction, data, result, instance_location, context)
|
|
830
|
+
return unless data.is_a?(Array)
|
|
831
|
+
|
|
832
|
+
data.each_with_index do |item, index|
|
|
833
|
+
next if index < instruction.payload[:start_index]
|
|
834
|
+
|
|
835
|
+
child_context = context.fork
|
|
836
|
+
before = result.errors.length
|
|
837
|
+
valid = validate_instructions(instruction.payload[:schema], item, result,
|
|
838
|
+
join_instance(instance_location, index), child_context)
|
|
839
|
+
if valid
|
|
840
|
+
context.mark_item(instance_location, index)
|
|
841
|
+
context.merge(child_context)
|
|
842
|
+
end
|
|
843
|
+
return if result.fail_fast? && result.errors.length > before
|
|
844
|
+
end
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
def execute_contains(instruction, data, result, instance_location, context)
|
|
848
|
+
return unless data.is_a?(Array)
|
|
849
|
+
|
|
850
|
+
count = 0
|
|
851
|
+
data.each_with_index do |item, index|
|
|
852
|
+
child_context = context.fork
|
|
853
|
+
next unless schema_valid?(instruction.payload[:schema], item, join_instance(instance_location, index),
|
|
854
|
+
child_context)
|
|
855
|
+
|
|
856
|
+
count += 1
|
|
857
|
+
context.mark_item(instance_location, index)
|
|
858
|
+
context.merge(child_context)
|
|
859
|
+
end
|
|
860
|
+
|
|
861
|
+
min = instruction.payload[:min]
|
|
862
|
+
max = instruction.payload[:max]
|
|
863
|
+
|
|
864
|
+
if count < min
|
|
865
|
+
add_error(result, instruction, data, :min_contains, { min_contains: min }, instance_location)
|
|
866
|
+
elsif max && count > max
|
|
867
|
+
add_error(result, instruction, data, :max_contains, { max_contains: max }, instance_location)
|
|
868
|
+
end
|
|
869
|
+
end
|
|
870
|
+
|
|
871
|
+
def execute_max_properties(instruction, data, result, instance_location)
|
|
872
|
+
return unless data.is_a?(Hash)
|
|
873
|
+
return if data.length <= instruction.payload[:limit]
|
|
874
|
+
|
|
875
|
+
add_error(result, instruction, data, :max_properties, { max_properties: instruction.payload[:limit] },
|
|
876
|
+
instance_location)
|
|
877
|
+
end
|
|
878
|
+
|
|
879
|
+
def execute_min_properties(instruction, data, result, instance_location)
|
|
880
|
+
return unless data.is_a?(Hash)
|
|
881
|
+
return if data.length >= instruction.payload[:limit]
|
|
882
|
+
|
|
883
|
+
add_error(result, instruction, data, :min_properties, { min_properties: instruction.payload[:limit] },
|
|
884
|
+
instance_location)
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
def execute_required(instruction, data, result, instance_location)
|
|
888
|
+
return unless data.is_a?(Hash)
|
|
889
|
+
|
|
890
|
+
instruction.payload[:keys].each do |key|
|
|
891
|
+
next if data.key?(key)
|
|
892
|
+
|
|
893
|
+
add_error(result, instruction, data, :required, { property: key }, instance_location)
|
|
894
|
+
return if result.fail_fast?
|
|
895
|
+
end
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
def execute_properties(instruction, data, result, instance_location, context)
|
|
899
|
+
return unless data.is_a?(Hash)
|
|
900
|
+
|
|
901
|
+
instruction.payload[:schemas].each do |key, schema|
|
|
902
|
+
next unless data.key?(key)
|
|
903
|
+
|
|
904
|
+
child_context = context.fork
|
|
905
|
+
before = result.errors.length
|
|
906
|
+
valid = validate_instructions(schema, data[key], result, join_instance(instance_location, key), child_context)
|
|
907
|
+
if valid
|
|
908
|
+
context.mark_property(instance_location, key)
|
|
909
|
+
context.merge(child_context)
|
|
910
|
+
end
|
|
911
|
+
return if result.fail_fast? && result.errors.length > before
|
|
912
|
+
end
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
def execute_pattern_properties(instruction, data, result, instance_location, context)
|
|
916
|
+
return unless data.is_a?(Hash)
|
|
917
|
+
|
|
918
|
+
data.each do |key, value|
|
|
919
|
+
instruction.payload[:patterns].each do |entry|
|
|
920
|
+
next unless key.match?(entry[:pattern])
|
|
921
|
+
|
|
922
|
+
child_context = context.fork
|
|
923
|
+
before = result.errors.length
|
|
924
|
+
valid = validate_instructions(entry[:schema], value, result, join_instance(instance_location, key),
|
|
925
|
+
child_context)
|
|
926
|
+
if valid
|
|
927
|
+
context.mark_property(instance_location, key)
|
|
928
|
+
context.merge(child_context)
|
|
929
|
+
end
|
|
930
|
+
return if result.fail_fast? && result.errors.length > before
|
|
931
|
+
end
|
|
932
|
+
end
|
|
933
|
+
end
|
|
934
|
+
|
|
935
|
+
def execute_additional_properties(instruction, data, result, instance_location, context)
|
|
936
|
+
return unless data.is_a?(Hash)
|
|
937
|
+
|
|
938
|
+
data.each do |key, value|
|
|
939
|
+
next if instruction.payload[:known].include?(key)
|
|
940
|
+
next if instruction.payload[:patterns].any? { |pattern| key.match?(pattern) }
|
|
941
|
+
|
|
942
|
+
if instruction.payload[:schema] == false
|
|
943
|
+
add_error(
|
|
944
|
+
result,
|
|
945
|
+
instruction,
|
|
946
|
+
value,
|
|
947
|
+
:additional_properties,
|
|
948
|
+
{ property: key },
|
|
949
|
+
join_instance(instance_location, key)
|
|
950
|
+
)
|
|
951
|
+
return if result.fail_fast?
|
|
952
|
+
else
|
|
953
|
+
child_context = context.fork
|
|
954
|
+
before = result.errors.length
|
|
955
|
+
valid = validate_instructions(instruction.payload[:schema], value, result,
|
|
956
|
+
join_instance(instance_location, key), child_context)
|
|
957
|
+
if valid
|
|
958
|
+
context.mark_property(instance_location, key)
|
|
959
|
+
context.merge(child_context)
|
|
960
|
+
end
|
|
961
|
+
return if result.fail_fast? && result.errors.length > before
|
|
962
|
+
end
|
|
963
|
+
end
|
|
964
|
+
end
|
|
965
|
+
|
|
966
|
+
def execute_property_names(instruction, data, result, instance_location, context)
|
|
967
|
+
return unless data.is_a?(Hash)
|
|
968
|
+
|
|
969
|
+
data.each_key do |key|
|
|
970
|
+
next if schema_valid?(instruction.payload[:schema], key, join_instance(instance_location, key), context.fork)
|
|
971
|
+
|
|
972
|
+
add_error(result, instruction, key, :property_names, { property: key }, join_instance(instance_location, key))
|
|
973
|
+
return if result.fail_fast?
|
|
974
|
+
end
|
|
975
|
+
end
|
|
976
|
+
|
|
977
|
+
def execute_dependent_required(instruction, data, result, instance_location)
|
|
978
|
+
return unless data.is_a?(Hash)
|
|
979
|
+
|
|
980
|
+
instruction.payload[:requirements].each do |property, dependencies|
|
|
981
|
+
next unless data.key?(property)
|
|
982
|
+
|
|
983
|
+
dependencies.each do |dependency|
|
|
984
|
+
next if data.key?(dependency)
|
|
985
|
+
|
|
986
|
+
add_error(
|
|
987
|
+
result,
|
|
988
|
+
instruction,
|
|
989
|
+
data,
|
|
990
|
+
:dependent_required,
|
|
991
|
+
{ property: property, dependency: dependency },
|
|
992
|
+
instance_location
|
|
993
|
+
)
|
|
994
|
+
return if result.fail_fast?
|
|
995
|
+
end
|
|
996
|
+
end
|
|
997
|
+
end
|
|
998
|
+
|
|
999
|
+
def execute_dependent_schemas(instruction, data, result, instance_location, context)
|
|
1000
|
+
return unless data.is_a?(Hash)
|
|
1001
|
+
|
|
1002
|
+
instruction.payload[:schemas].each do |property, schema|
|
|
1003
|
+
next unless data.key?(property)
|
|
1004
|
+
|
|
1005
|
+
child_context = context.fork
|
|
1006
|
+
before = result.errors.length
|
|
1007
|
+
valid = validate_instructions(schema, data, result, instance_location, child_context)
|
|
1008
|
+
context.merge(child_context) if valid
|
|
1009
|
+
return if result.fail_fast? && result.errors.length > before
|
|
1010
|
+
end
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
def execute_all_of(instruction, data, result, instance_location, context)
|
|
1014
|
+
instruction.payload[:schemas].each do |schema|
|
|
1015
|
+
child_context = context.fork
|
|
1016
|
+
before = result.errors.length
|
|
1017
|
+
valid = validate_instructions(schema, data, result, instance_location, child_context)
|
|
1018
|
+
context.merge(child_context) if valid
|
|
1019
|
+
return if result.fail_fast? && result.errors.length > before
|
|
1020
|
+
end
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
def execute_any_of(instruction, data, result, instance_location, context)
|
|
1024
|
+
valid_contexts = []
|
|
1025
|
+
|
|
1026
|
+
instruction.payload[:schemas].each do |schema|
|
|
1027
|
+
child_context = context.fork
|
|
1028
|
+
valid_contexts << child_context if schema_valid?(schema, data, instance_location, child_context)
|
|
1029
|
+
end
|
|
1030
|
+
|
|
1031
|
+
if valid_contexts.empty?
|
|
1032
|
+
add_error(result, instruction, data, :any_of, {}, instance_location)
|
|
1033
|
+
else
|
|
1034
|
+
valid_contexts.each { |child_context| context.merge(child_context) }
|
|
1035
|
+
end
|
|
1036
|
+
end
|
|
1037
|
+
|
|
1038
|
+
def execute_one_of(instruction, data, result, instance_location, context)
|
|
1039
|
+
matches = []
|
|
1040
|
+
|
|
1041
|
+
instruction.payload[:schemas].each do |schema|
|
|
1042
|
+
child_context = context.fork
|
|
1043
|
+
next unless schema_valid?(schema, data, instance_location, child_context)
|
|
1044
|
+
|
|
1045
|
+
matches << child_context
|
|
1046
|
+
break if result.fail_fast? && matches.length > 1
|
|
1047
|
+
end
|
|
1048
|
+
|
|
1049
|
+
if matches.length == 1
|
|
1050
|
+
context.merge(matches.first)
|
|
1051
|
+
elsif matches.empty?
|
|
1052
|
+
add_error(result, instruction, data, :one_of_none, {}, instance_location)
|
|
1053
|
+
else
|
|
1054
|
+
add_error(result, instruction, data, :one_of_multiple, { count: matches.length }, instance_location)
|
|
1055
|
+
end
|
|
1056
|
+
end
|
|
1057
|
+
|
|
1058
|
+
def execute_not(instruction, data, result, instance_location, context)
|
|
1059
|
+
return unless schema_valid?(instruction.payload[:schema], data, instance_location, context.fork)
|
|
1060
|
+
|
|
1061
|
+
add_error(result, instruction, data, :not, {}, instance_location)
|
|
1062
|
+
end
|
|
1063
|
+
|
|
1064
|
+
def execute_discriminator(instruction, data, result, instance_location, context)
|
|
1065
|
+
property = instruction.payload[:property]
|
|
1066
|
+
unless data.is_a?(Hash) && data.key?(property)
|
|
1067
|
+
add_discriminator_error(instruction, data, result, instance_location, property)
|
|
1068
|
+
return
|
|
1069
|
+
end
|
|
1070
|
+
|
|
1071
|
+
schema = instruction.payload[:mapping][data[property]] || instruction.payload[:mapping][data[property].to_s]
|
|
1072
|
+
unless schema
|
|
1073
|
+
add_discriminator_error(instruction, data, result, instance_location, property)
|
|
1074
|
+
return
|
|
1075
|
+
end
|
|
1076
|
+
|
|
1077
|
+
child_context = context.fork
|
|
1078
|
+
valid = validate_instructions(schema, data, result, instance_location, child_context)
|
|
1079
|
+
context.merge(child_context) if valid
|
|
1080
|
+
end
|
|
1081
|
+
|
|
1082
|
+
def execute_dynamic_ref(instruction, data, result, instance_location, context)
|
|
1083
|
+
target = if instruction.payload[:dynamic] && instruction.payload[:anchor]
|
|
1084
|
+
context.dynamic_target(instruction.payload[:anchor])
|
|
1085
|
+
end
|
|
1086
|
+
target ||= instruction.payload[:instructions]
|
|
1087
|
+
|
|
1088
|
+
validate_instructions(target, data, result, instance_location, context)
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
def add_discriminator_error(instruction, data, result, instance_location, property)
|
|
1092
|
+
key = instruction.payload[:mode] == :any_of ? :any_of : :one_of_none
|
|
1093
|
+
values = key == :any_of ? {} : { property: property }
|
|
1094
|
+
add_error(result, instruction, data, key, values, instance_location)
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
def execute_if_then_else(instruction, data, result, instance_location, context)
|
|
1098
|
+
condition_context = context.fork
|
|
1099
|
+
condition_passed = schema_valid?(instruction.payload[:if_schema], data, instance_location, condition_context)
|
|
1100
|
+
selected_schema = condition_passed ? instruction.payload[:then_schema] : instruction.payload[:else_schema]
|
|
1101
|
+
if selected_schema.nil?
|
|
1102
|
+
context.merge(condition_context) if condition_passed
|
|
1103
|
+
return
|
|
1104
|
+
end
|
|
1105
|
+
|
|
1106
|
+
child_context = context.fork
|
|
1107
|
+
before = result.errors.length
|
|
1108
|
+
valid = validate_instructions(selected_schema, data, result, instance_location, child_context)
|
|
1109
|
+
if valid
|
|
1110
|
+
context.merge(condition_context) if condition_passed
|
|
1111
|
+
context.merge(child_context)
|
|
1112
|
+
end
|
|
1113
|
+
nil unless result.fail_fast? && result.errors.length > before
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1116
|
+
def execute_unevaluated_properties(instruction, data, result, instance_location, context)
|
|
1117
|
+
return unless data.is_a?(Hash)
|
|
1118
|
+
|
|
1119
|
+
data.each do |key, value|
|
|
1120
|
+
next if context.property_evaluated?(instance_location, key)
|
|
1121
|
+
|
|
1122
|
+
if instruction.payload[:schema] == false
|
|
1123
|
+
add_error(
|
|
1124
|
+
result,
|
|
1125
|
+
instruction,
|
|
1126
|
+
value,
|
|
1127
|
+
:unevaluated_properties,
|
|
1128
|
+
{ property: key },
|
|
1129
|
+
join_instance(instance_location, key)
|
|
1130
|
+
)
|
|
1131
|
+
return if result.fail_fast?
|
|
1132
|
+
else
|
|
1133
|
+
child_context = context.fork
|
|
1134
|
+
before = result.errors.length
|
|
1135
|
+
valid = validate_instructions(instruction.payload[:schema], value, result,
|
|
1136
|
+
join_instance(instance_location, key), child_context)
|
|
1137
|
+
if valid
|
|
1138
|
+
context.mark_property(instance_location, key)
|
|
1139
|
+
context.merge(child_context)
|
|
1140
|
+
end
|
|
1141
|
+
return if result.fail_fast? && result.errors.length > before
|
|
1142
|
+
end
|
|
1143
|
+
end
|
|
1144
|
+
end
|
|
1145
|
+
|
|
1146
|
+
def execute_unevaluated_items(instruction, data, result, instance_location, context)
|
|
1147
|
+
return unless data.is_a?(Array)
|
|
1148
|
+
|
|
1149
|
+
data.each_with_index do |item, index|
|
|
1150
|
+
next if context.item_evaluated?(instance_location, index)
|
|
1151
|
+
|
|
1152
|
+
if instruction.payload[:schema] == false
|
|
1153
|
+
add_error(result, instruction, item, :unevaluated_items, { index: index },
|
|
1154
|
+
join_instance(instance_location, index))
|
|
1155
|
+
return if result.fail_fast?
|
|
1156
|
+
else
|
|
1157
|
+
child_context = context.fork
|
|
1158
|
+
before = result.errors.length
|
|
1159
|
+
valid = validate_instructions(instruction.payload[:schema], item, result,
|
|
1160
|
+
join_instance(instance_location, index), child_context)
|
|
1161
|
+
if valid
|
|
1162
|
+
context.mark_item(instance_location, index)
|
|
1163
|
+
context.merge(child_context)
|
|
1164
|
+
end
|
|
1165
|
+
return if result.fail_fast? && result.errors.length > before
|
|
1166
|
+
end
|
|
1167
|
+
end
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1170
|
+
def execute_custom_keyword(instruction, data, result, instance_location)
|
|
1171
|
+
valid = custom_keyword_result(instruction, data)
|
|
1172
|
+
|
|
1173
|
+
return if valid == true
|
|
1174
|
+
|
|
1175
|
+
message = valid.is_a?(String) ? valid : nil
|
|
1176
|
+
add_error(
|
|
1177
|
+
result,
|
|
1178
|
+
instruction,
|
|
1179
|
+
data,
|
|
1180
|
+
:custom_keyword,
|
|
1181
|
+
{ keyword: instruction.payload[:keyword] },
|
|
1182
|
+
instance_location,
|
|
1183
|
+
message
|
|
1184
|
+
)
|
|
1185
|
+
end
|
|
1186
|
+
|
|
1187
|
+
def schema_valid?(instructions, data, instance_location, context)
|
|
1188
|
+
valid_instructions?(instructions, data, instance_location, context)
|
|
1189
|
+
end
|
|
1190
|
+
|
|
1191
|
+
def add_error(result, instruction, data, key, values, instance_location, explicit_message = nil)
|
|
1192
|
+
result.add_error(
|
|
1193
|
+
Error.new(
|
|
1194
|
+
message: explicit_message || Kernel.format(@messages.fetch(key), values),
|
|
1195
|
+
instance_location: instance_location,
|
|
1196
|
+
keyword_location: instruction.keyword_location,
|
|
1197
|
+
keyword: instruction.keyword,
|
|
1198
|
+
schema: instruction.schema,
|
|
1199
|
+
data: data
|
|
1200
|
+
)
|
|
1201
|
+
)
|
|
1202
|
+
end
|
|
1203
|
+
|
|
1204
|
+
def custom_keyword_result(instruction, data)
|
|
1205
|
+
validator = instruction.payload[:validator]
|
|
1206
|
+
return true unless validator.respond_to?(:call)
|
|
1207
|
+
|
|
1208
|
+
keyword_value = instruction.payload[:value]
|
|
1209
|
+
case validator.arity
|
|
1210
|
+
when 1 then validator.call(data)
|
|
1211
|
+
when 2 then validator.call(data, keyword_value)
|
|
1212
|
+
else validator.call(data, keyword_value, instruction)
|
|
1213
|
+
end
|
|
1214
|
+
end
|
|
1215
|
+
|
|
1216
|
+
def multiple_of?(value, factor)
|
|
1217
|
+
if value.is_a?(Integer) && factor.is_a?(Integer)
|
|
1218
|
+
(value % factor).zero?
|
|
1219
|
+
else
|
|
1220
|
+
(decimal(value) % decimal(factor)).zero?
|
|
1221
|
+
end
|
|
1222
|
+
end
|
|
1223
|
+
|
|
1224
|
+
def decimal(value)
|
|
1225
|
+
BigDecimal(value.to_s)
|
|
1226
|
+
end
|
|
1227
|
+
|
|
1228
|
+
def unique_items?(items)
|
|
1229
|
+
return Senko::Native.unique_array?(items) if native_available?
|
|
1230
|
+
return small_unique_items?(items) if items.length <= 16
|
|
1231
|
+
|
|
1232
|
+
seen = {}
|
|
1233
|
+
items.each do |item|
|
|
1234
|
+
key = canonical_json(item)
|
|
1235
|
+
return false if seen.key?(key)
|
|
1236
|
+
|
|
1237
|
+
seen[key] = true
|
|
1238
|
+
end
|
|
1239
|
+
true
|
|
1240
|
+
end
|
|
1241
|
+
|
|
1242
|
+
def small_unique_items?(items)
|
|
1243
|
+
items.each_with_index do |left, left_index|
|
|
1244
|
+
((left_index + 1)...items.length).each do |right_index|
|
|
1245
|
+
return false if json_equal?(left, items[right_index])
|
|
1246
|
+
end
|
|
1247
|
+
end
|
|
1248
|
+
true
|
|
1249
|
+
end
|
|
1250
|
+
|
|
1251
|
+
def canonical_json(value)
|
|
1252
|
+
case value
|
|
1253
|
+
when Hash
|
|
1254
|
+
['object', value.keys.sort.map { |key| [key, canonical_json(value[key])] }]
|
|
1255
|
+
when Array
|
|
1256
|
+
['array', value.map { |item| canonical_json(item) }]
|
|
1257
|
+
when Integer, Float, BigDecimal
|
|
1258
|
+
['number', decimal(value).to_s('F')]
|
|
1259
|
+
else
|
|
1260
|
+
[value.class.name, value]
|
|
1261
|
+
end
|
|
1262
|
+
end
|
|
1263
|
+
|
|
1264
|
+
def json_equal?(left, right)
|
|
1265
|
+
left == right
|
|
1266
|
+
end
|
|
1267
|
+
|
|
1268
|
+
def numeric_instance?(value)
|
|
1269
|
+
value.is_a?(Integer) || value.is_a?(Float) || value.is_a?(BigDecimal)
|
|
1270
|
+
end
|
|
1271
|
+
|
|
1272
|
+
def integer_number?(value)
|
|
1273
|
+
case value
|
|
1274
|
+
when Integer
|
|
1275
|
+
true
|
|
1276
|
+
when Float, BigDecimal
|
|
1277
|
+
value.finite? && value == value.to_i
|
|
1278
|
+
else
|
|
1279
|
+
false
|
|
1280
|
+
end
|
|
1281
|
+
end
|
|
1282
|
+
|
|
1283
|
+
def type_mask_for(value)
|
|
1284
|
+
return Senko::Native.type_mask(value) if native_available?
|
|
1285
|
+
|
|
1286
|
+
case value
|
|
1287
|
+
when NilClass
|
|
1288
|
+
Instructions::TYPE_NULL
|
|
1289
|
+
when TrueClass, FalseClass
|
|
1290
|
+
Instructions::TYPE_BOOLEAN
|
|
1291
|
+
when Integer
|
|
1292
|
+
Instructions::TYPE_INTEGER
|
|
1293
|
+
when Float, BigDecimal
|
|
1294
|
+
integer_number?(value) ? Instructions::TYPE_INTEGER : (Instructions::TYPE_NUMBER & ~Instructions::TYPE_INTEGER)
|
|
1295
|
+
when String
|
|
1296
|
+
Instructions::TYPE_STRING
|
|
1297
|
+
when Array
|
|
1298
|
+
Instructions::TYPE_ARRAY
|
|
1299
|
+
when Hash
|
|
1300
|
+
Instructions::TYPE_OBJECT
|
|
1301
|
+
else
|
|
1302
|
+
0
|
|
1303
|
+
end
|
|
1304
|
+
end
|
|
1305
|
+
|
|
1306
|
+
def actual_type(value)
|
|
1307
|
+
case value
|
|
1308
|
+
when NilClass then 'null'
|
|
1309
|
+
when TrueClass, FalseClass then 'boolean'
|
|
1310
|
+
when Integer then 'integer'
|
|
1311
|
+
when Float, BigDecimal then integer_number?(value) ? 'integer' : 'number'
|
|
1312
|
+
when String then 'string'
|
|
1313
|
+
when Array then 'array'
|
|
1314
|
+
when Hash then 'object'
|
|
1315
|
+
else value.class.name
|
|
1316
|
+
end
|
|
1317
|
+
end
|
|
1318
|
+
|
|
1319
|
+
def string_length(value)
|
|
1320
|
+
native_available? ? Senko::Native.string_length(value) : value.length
|
|
1321
|
+
end
|
|
1322
|
+
|
|
1323
|
+
def native_numeric_lte?(left, right)
|
|
1324
|
+
native_available? ? Senko::Native.numeric_lte?(left, right) : left <= right
|
|
1325
|
+
end
|
|
1326
|
+
|
|
1327
|
+
def native_numeric_lt?(left, right)
|
|
1328
|
+
native_available? ? Senko::Native.numeric_lt?(left, right) : left < right
|
|
1329
|
+
end
|
|
1330
|
+
|
|
1331
|
+
def native_numeric_gte?(left, right)
|
|
1332
|
+
native_available? ? Senko::Native.numeric_gte?(left, right) : left >= right
|
|
1333
|
+
end
|
|
1334
|
+
|
|
1335
|
+
def native_numeric_gt?(left, right)
|
|
1336
|
+
native_available? ? Senko::Native.numeric_gt?(left, right) : left > right
|
|
1337
|
+
end
|
|
1338
|
+
|
|
1339
|
+
def native_available?
|
|
1340
|
+
defined?(Senko::Native) &&
|
|
1341
|
+
Senko::Native.respond_to?(:type_mask) &&
|
|
1342
|
+
Senko::Native.respond_to?(:unique_array?)
|
|
1343
|
+
end
|
|
1344
|
+
|
|
1345
|
+
def symbolized_messages(messages)
|
|
1346
|
+
messages.each_with_object({}) do |(key, value), result|
|
|
1347
|
+
result[key.to_sym] = value
|
|
1348
|
+
end
|
|
1349
|
+
end
|
|
1350
|
+
|
|
1351
|
+
def child_instance_location(context, base, token)
|
|
1352
|
+
context.track_evaluation? ? join_instance(base, token) : base
|
|
1353
|
+
end
|
|
1354
|
+
|
|
1355
|
+
def valid_child_schema?(schema, data, instance_location, context)
|
|
1356
|
+
return valid_instructions?(schema, data, instance_location, context) unless context.track_evaluation?
|
|
1357
|
+
|
|
1358
|
+
child_context = context.fork
|
|
1359
|
+
return false unless valid_instructions?(schema, data, instance_location, child_context)
|
|
1360
|
+
|
|
1361
|
+
context.merge(child_context)
|
|
1362
|
+
true
|
|
1363
|
+
end
|
|
1364
|
+
|
|
1365
|
+
def valid_child_instance_schema?(schema, data, instance_location, context)
|
|
1366
|
+
return valid_instructions?(schema, data, instance_location, context) unless context.track_evaluation?
|
|
1367
|
+
if schema_requires_evaluation_tracking?(schema)
|
|
1368
|
+
return valid_child_schema?(schema, data, instance_location,
|
|
1369
|
+
context)
|
|
1370
|
+
end
|
|
1371
|
+
|
|
1372
|
+
valid_instructions?(schema, data, instance_location, context.fork(track_evaluation: false))
|
|
1373
|
+
end
|
|
1374
|
+
|
|
1375
|
+
def schema_requires_evaluation_tracking?(schema)
|
|
1376
|
+
return false unless schema.is_a?(Array)
|
|
1377
|
+
|
|
1378
|
+
@tracking_cache.fetch(schema.object_id) do
|
|
1379
|
+
@tracking_cache[schema.object_id] = self.class.requires_evaluation_tracking?(schema)
|
|
1380
|
+
end
|
|
1381
|
+
end
|
|
1382
|
+
|
|
1383
|
+
def join_instance(base, token)
|
|
1384
|
+
"#{base}/#{escape_pointer_token(token)}"
|
|
1385
|
+
end
|
|
1386
|
+
|
|
1387
|
+
def escape_pointer_token(token)
|
|
1388
|
+
token.to_s.gsub('~', '~0').gsub('/', '~1')
|
|
1389
|
+
end
|
|
1390
|
+
end
|
|
1391
|
+
end
|