json_schemer 1.0.3 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -7
  3. data/CHANGELOG.md +51 -0
  4. data/Gemfile.lock +10 -3
  5. data/README.md +328 -14
  6. data/json_schemer.gemspec +3 -1
  7. data/lib/json_schemer/content.rb +18 -0
  8. data/lib/json_schemer/draft201909/meta.rb +320 -0
  9. data/lib/json_schemer/draft201909/vocab/applicator.rb +104 -0
  10. data/lib/json_schemer/draft201909/vocab/core.rb +45 -0
  11. data/lib/json_schemer/draft201909/vocab.rb +31 -0
  12. data/lib/json_schemer/draft202012/meta.rb +364 -0
  13. data/lib/json_schemer/draft202012/vocab/applicator.rb +382 -0
  14. data/lib/json_schemer/draft202012/vocab/content.rb +52 -0
  15. data/lib/json_schemer/draft202012/vocab/core.rb +160 -0
  16. data/lib/json_schemer/draft202012/vocab/format_annotation.rb +23 -0
  17. data/lib/json_schemer/draft202012/vocab/format_assertion.rb +23 -0
  18. data/lib/json_schemer/draft202012/vocab/meta_data.rb +30 -0
  19. data/lib/json_schemer/draft202012/vocab/unevaluated.rb +94 -0
  20. data/lib/json_schemer/draft202012/vocab/validation.rb +286 -0
  21. data/lib/json_schemer/draft202012/vocab.rb +105 -0
  22. data/lib/json_schemer/draft4/meta.rb +161 -0
  23. data/lib/json_schemer/draft4/vocab/validation.rb +39 -0
  24. data/lib/json_schemer/draft4/vocab.rb +18 -0
  25. data/lib/json_schemer/draft6/meta.rb +172 -0
  26. data/lib/json_schemer/draft6/vocab.rb +16 -0
  27. data/lib/json_schemer/draft7/meta.rb +183 -0
  28. data/lib/json_schemer/draft7/vocab/validation.rb +69 -0
  29. data/lib/json_schemer/draft7/vocab.rb +30 -0
  30. data/lib/json_schemer/errors.rb +1 -0
  31. data/lib/json_schemer/format/duration.rb +23 -0
  32. data/lib/json_schemer/format/json_pointer.rb +18 -0
  33. data/lib/json_schemer/format.rb +128 -106
  34. data/lib/json_schemer/keyword.rb +53 -0
  35. data/lib/json_schemer/location.rb +25 -0
  36. data/lib/json_schemer/openapi.rb +40 -0
  37. data/lib/json_schemer/openapi30/document.rb +1672 -0
  38. data/lib/json_schemer/openapi30/meta.rb +32 -0
  39. data/lib/json_schemer/openapi30/vocab/base.rb +18 -0
  40. data/lib/json_schemer/openapi30/vocab.rb +12 -0
  41. data/lib/json_schemer/openapi31/document.rb +1557 -0
  42. data/lib/json_schemer/openapi31/meta.rb +136 -0
  43. data/lib/json_schemer/openapi31/vocab/base.rb +127 -0
  44. data/lib/json_schemer/openapi31/vocab.rb +18 -0
  45. data/lib/json_schemer/output.rb +56 -0
  46. data/lib/json_schemer/result.rb +229 -0
  47. data/lib/json_schemer/schema.rb +423 -0
  48. data/lib/json_schemer/version.rb +1 -1
  49. data/lib/json_schemer.rb +198 -24
  50. metadata +71 -10
  51. data/lib/json_schemer/schema/base.rb +0 -677
  52. data/lib/json_schemer/schema/draft4.json +0 -149
  53. data/lib/json_schemer/schema/draft4.rb +0 -44
  54. data/lib/json_schemer/schema/draft6.json +0 -155
  55. data/lib/json_schemer/schema/draft6.rb +0 -25
  56. data/lib/json_schemer/schema/draft7.json +0 -172
  57. data/lib/json_schemer/schema/draft7.rb +0 -32
@@ -1,677 +0,0 @@
1
- # frozen_string_literal: true
2
- module JSONSchemer
3
- module Schema
4
- class Base
5
- include Format
6
-
7
- Instance = Struct.new(:data, :data_pointer, :schema, :schema_pointer, :base_uri, :before_property_validation, :after_property_validation) do
8
- def merge(
9
- data: self.data,
10
- data_pointer: self.data_pointer,
11
- schema: self.schema,
12
- schema_pointer: self.schema_pointer,
13
- base_uri: self.base_uri,
14
- before_property_validation: self.before_property_validation,
15
- after_property_validation: self.after_property_validation
16
- )
17
- self.class.new(data, data_pointer, schema, schema_pointer, base_uri, before_property_validation, after_property_validation)
18
- end
19
- end
20
-
21
- ID_KEYWORD = '$id'
22
- DEFAULT_REF_RESOLVER = proc { |uri| raise UnknownRef, uri.to_s }
23
- NET_HTTP_REF_RESOLVER = proc { |uri| JSON.parse(Net::HTTP.get(uri)) }
24
- RUBY_REGEXP_RESOLVER = proc { |pattern| Regexp.new(pattern) }
25
- ECMA_REGEXP_RESOLVER = proc { |pattern| Regexp.new(EcmaRegexp.ruby_equivalent(pattern)) }
26
- BOOLEANS = Set[true, false].freeze
27
-
28
- INSERT_DEFAULT_PROPERTY = proc do |data, property, property_schema, _parent|
29
- if !data.key?(property) && property_schema.is_a?(Hash) && property_schema.key?('default')
30
- data[property] = property_schema.fetch('default').clone
31
- end
32
- end
33
-
34
- JSON_POINTER_TOKEN_ESCAPE_CHARS = { '~' => '~0', '/' => '~1' }
35
- JSON_POINTER_TOKEN_ESCAPE_REGEX = Regexp.union(JSON_POINTER_TOKEN_ESCAPE_CHARS.keys)
36
-
37
- class << self
38
- def draft_name
39
- name.split('::').last.downcase
40
- end
41
-
42
- def meta_schema
43
- @meta_schema ||= JSON.parse(Pathname.new(__dir__).join("#{draft_name}.json").read).freeze
44
- end
45
-
46
- def meta_schemer
47
- @meta_schemer ||= JSONSchemer.schema(meta_schema)
48
- end
49
- end
50
-
51
- def initialize(
52
- schema,
53
- base_uri: nil,
54
- format: true,
55
- insert_property_defaults: false,
56
- before_property_validation: nil,
57
- after_property_validation: nil,
58
- formats: nil,
59
- keywords: nil,
60
- ref_resolver: DEFAULT_REF_RESOLVER,
61
- regexp_resolver: 'ruby'
62
- )
63
- raise InvalidSymbolKey, 'schemas must use string keys' if schema.is_a?(Hash) && !schema.empty? && !schema.first.first.is_a?(String)
64
- @root = schema
65
- @base_uri = base_uri
66
- @format = format
67
- @before_property_validation = [*before_property_validation]
68
- @before_property_validation.unshift(INSERT_DEFAULT_PROPERTY) if insert_property_defaults
69
- @after_property_validation = [*after_property_validation]
70
- @formats = formats
71
- @keywords = keywords
72
- @ref_resolver = ref_resolver == 'net/http' ? CachedResolver.new(&NET_HTTP_REF_RESOLVER) : ref_resolver
73
- @regexp_resolver = case regexp_resolver
74
- when 'ecma'
75
- CachedResolver.new(&ECMA_REGEXP_RESOLVER)
76
- when 'ruby'
77
- CachedResolver.new(&RUBY_REGEXP_RESOLVER)
78
- else
79
- regexp_resolver
80
- end
81
- end
82
-
83
- def valid?(data)
84
- valid_instance?(Instance.new(data, '', root, '', @base_uri, @before_property_validation, @after_property_validation))
85
- end
86
-
87
- def validate(data)
88
- validate_instance(Instance.new(data, '', root, '', @base_uri, @before_property_validation, @after_property_validation))
89
- end
90
-
91
- def valid_schema?
92
- self.class.meta_schemer.valid?(root)
93
- end
94
-
95
- def validate_schema
96
- self.class.meta_schemer.validate(root)
97
- end
98
-
99
- protected
100
-
101
- attr_reader :root
102
-
103
- def valid_instance?(instance)
104
- validate_instance(instance).none?
105
- end
106
-
107
- def validate_instance(instance, &block)
108
- return enum_for(:validate_instance, instance) unless block_given?
109
-
110
- schema = instance.schema
111
-
112
- if schema == false
113
- yield error(instance, 'schema')
114
- return
115
- end
116
-
117
- return if schema == true || schema.empty?
118
-
119
- type = schema['type']
120
- enum = schema['enum']
121
- all_of = schema['allOf']
122
- any_of = schema['anyOf']
123
- one_of = schema['oneOf']
124
- not_schema = schema['not']
125
- if_schema = schema['if']
126
- then_schema = schema['then']
127
- else_schema = schema['else']
128
- format = schema['format']
129
- ref = schema['$ref']
130
- id = schema[id_keyword]
131
-
132
- if ref
133
- validate_ref(instance, ref, &block)
134
- return
135
- end
136
-
137
- instance.base_uri = join_uri(instance.base_uri, id)
138
-
139
- if format? && custom_format?(format)
140
- validate_custom_format(instance, formats.fetch(format), &block)
141
- end
142
-
143
- data = instance.data
144
-
145
- if keywords
146
- keywords.each do |keyword, callable|
147
- if schema.key?(keyword)
148
- result = callable.call(data, schema, instance.data_pointer)
149
- if result.is_a?(Array)
150
- result.each(&block)
151
- elsif !result
152
- yield error(instance, keyword)
153
- end
154
- end
155
- end
156
- end
157
-
158
- yield error(instance, 'enum') if enum && !enum.include?(data)
159
- yield error(instance, 'const') if schema.key?('const') && schema['const'] != data
160
-
161
- if all_of
162
- all_of.each_with_index do |subschema, index|
163
- subinstance = instance.merge(
164
- schema: subschema,
165
- schema_pointer: "#{instance.schema_pointer}/allOf/#{index}",
166
- before_property_validation: false,
167
- after_property_validation: false
168
- )
169
- validate_instance(subinstance, &block)
170
- end
171
- end
172
-
173
- if any_of
174
- subschemas = any_of.lazy.with_index.map do |subschema, index|
175
- subinstance = instance.merge(
176
- schema: subschema,
177
- schema_pointer: "#{instance.schema_pointer}/anyOf/#{index}",
178
- before_property_validation: false,
179
- after_property_validation: false
180
- )
181
- validate_instance(subinstance)
182
- end
183
- subschemas.each { |subschema| subschema.each(&block) } unless subschemas.any?(&:none?)
184
- end
185
-
186
- if one_of
187
- subschemas = one_of.map.with_index do |subschema, index|
188
- subinstance = instance.merge(
189
- schema: subschema,
190
- schema_pointer: "#{instance.schema_pointer}/oneOf/#{index}",
191
- before_property_validation: false,
192
- after_property_validation: false
193
- )
194
- validate_instance(subinstance)
195
- end
196
- valid_subschema_count = subschemas.count(&:none?)
197
- if valid_subschema_count > 1
198
- yield error(instance, 'oneOf')
199
- elsif valid_subschema_count == 0
200
- subschemas.each { |subschema| subschema.each(&block) }
201
- end
202
- end
203
-
204
- unless not_schema.nil?
205
- subinstance = instance.merge(
206
- schema: not_schema,
207
- schema_pointer: "#{instance.schema_pointer}/not",
208
- before_property_validation: false,
209
- after_property_validation: false
210
- )
211
- yield error(subinstance, 'not') if valid_instance?(subinstance)
212
- end
213
-
214
- if if_schema && valid_instance?(instance.merge(schema: if_schema, before_property_validation: false, after_property_validation: false))
215
- validate_instance(instance.merge(schema: then_schema, schema_pointer: "#{instance.schema_pointer}/then"), &block) unless then_schema.nil?
216
- elsif schema.key?('if')
217
- validate_instance(instance.merge(schema: else_schema, schema_pointer: "#{instance.schema_pointer}/else"), &block) unless else_schema.nil?
218
- end
219
-
220
- case type
221
- when nil
222
- validate_class(instance, &block)
223
- when String
224
- validate_type(instance, type, &block)
225
- when Array
226
- if valid_type = type.find { |subtype| valid_instance?(instance.merge(schema: { 'type' => subtype })) }
227
- validate_type(instance, valid_type, &block)
228
- else
229
- yield error(instance, 'type')
230
- end
231
- end
232
- end
233
-
234
- def ids
235
- @ids ||= resolve_ids(root)
236
- end
237
-
238
- private
239
-
240
- attr_reader :formats, :keywords, :ref_resolver, :regexp_resolver
241
-
242
- def id_keyword
243
- ID_KEYWORD
244
- end
245
-
246
- def format?
247
- !!@format
248
- end
249
-
250
- def custom_format?(format)
251
- !!(formats && formats.key?(format))
252
- end
253
-
254
- def spec_format?(format)
255
- !custom_format?(format) && supported_format?(format)
256
- end
257
-
258
- def child(schema, base_uri:)
259
- JSONSchemer.schema(
260
- schema,
261
- default_schema_class: self.class,
262
- base_uri: base_uri,
263
- format: format?,
264
- formats: formats,
265
- keywords: keywords,
266
- ref_resolver: ref_resolver,
267
- regexp_resolver: regexp_resolver
268
- )
269
- end
270
-
271
- def error(instance, type, details = nil)
272
- error = {
273
- 'data' => instance.data,
274
- 'data_pointer' => instance.data_pointer,
275
- 'schema' => instance.schema,
276
- 'schema_pointer' => instance.schema_pointer,
277
- 'root_schema' => root,
278
- 'type' => type,
279
- }
280
- error['details'] = details if details
281
- error
282
- end
283
-
284
- def validate_class(instance, &block)
285
- case instance.data
286
- when Integer
287
- validate_integer(instance, &block)
288
- when Numeric
289
- validate_number(instance, &block)
290
- when String
291
- validate_string(instance, &block)
292
- when Array
293
- validate_array(instance, &block)
294
- when Hash
295
- validate_object(instance, &block)
296
- end
297
- end
298
-
299
- def validate_type(instance, type, &block)
300
- case type
301
- when 'null'
302
- yield error(instance, 'null') unless instance.data.nil?
303
- when 'boolean'
304
- yield error(instance, 'boolean') unless BOOLEANS.include?(instance.data)
305
- when 'number'
306
- validate_number(instance, &block)
307
- when 'integer'
308
- validate_integer(instance, &block)
309
- when 'string'
310
- validate_string(instance, &block)
311
- when 'array'
312
- validate_array(instance, &block)
313
- when 'object'
314
- validate_object(instance, &block)
315
- end
316
- end
317
-
318
- def validate_ref(instance, ref, &block)
319
- ref_uri = join_uri(instance.base_uri, ref)
320
-
321
- ref_uri_pointer = ''
322
- if valid_json_pointer?(ref_uri.fragment)
323
- ref_uri_pointer = ref_uri.fragment
324
- ref_uri.fragment = nil
325
- end
326
-
327
- ref_object = if ids.key?(ref_uri) || ref_uri.to_s.empty? || ref_uri.to_s == @base_uri.to_s
328
- self
329
- else
330
- child(resolve_ref(ref_uri), base_uri: ref_uri)
331
- end
332
-
333
- ref_schema, ref_schema_pointer, ref_parent_base_uri = ref_object.ids[ref_uri] || [ref_object.root, '', ref_uri]
334
-
335
- ref_uri_pointer_parts = Hana::Pointer.parse(URI.decode_www_form_component(ref_uri_pointer))
336
- schema, base_uri = ref_uri_pointer_parts.reduce([ref_schema, ref_parent_base_uri]) do |(obj, uri), token|
337
- if obj.is_a?(Array)
338
- [obj.fetch(token.to_i), uri]
339
- else
340
- [obj.fetch(token), join_uri(uri, obj[id_keyword])]
341
- end
342
- end
343
-
344
- subinstance = instance.merge(
345
- schema: schema,
346
- schema_pointer: "#{ref_schema_pointer}#{ref_uri_pointer}",
347
- base_uri: base_uri
348
- )
349
-
350
- ref_object.validate_instance(subinstance, &block)
351
- end
352
-
353
- def validate_custom_format(instance, custom_format)
354
- yield error(instance, 'format') if custom_format != false && !custom_format.call(instance.data, instance.schema)
355
- end
356
-
357
- def validate_exclusive_maximum(instance, exclusive_maximum, maximum)
358
- yield error(instance, 'exclusiveMaximum') if instance.data >= exclusive_maximum
359
- end
360
-
361
- def validate_exclusive_minimum(instance, exclusive_minimum, minimum)
362
- yield error(instance, 'exclusiveMinimum') if instance.data <= exclusive_minimum
363
- end
364
-
365
- def validate_numeric(instance, &block)
366
- schema = instance.schema
367
- data = instance.data
368
-
369
- multiple_of = schema['multipleOf']
370
- maximum = schema['maximum']
371
- exclusive_maximum = schema['exclusiveMaximum']
372
- minimum = schema['minimum']
373
- exclusive_minimum = schema['exclusiveMinimum']
374
-
375
- yield error(instance, 'maximum') if maximum && data > maximum
376
- yield error(instance, 'minimum') if minimum && data < minimum
377
-
378
- validate_exclusive_maximum(instance, exclusive_maximum, maximum, &block) if exclusive_maximum
379
- validate_exclusive_minimum(instance, exclusive_minimum, minimum, &block) if exclusive_minimum
380
-
381
- if multiple_of
382
- yield error(instance, 'multipleOf') unless BigDecimal(data.to_s).modulo(multiple_of).zero?
383
- end
384
- end
385
-
386
- def validate_number(instance, &block)
387
- unless instance.data.is_a?(Numeric)
388
- yield error(instance, 'number')
389
- return
390
- end
391
-
392
- validate_numeric(instance, &block)
393
- end
394
-
395
- def validate_integer(instance, &block)
396
- data = instance.data
397
-
398
- if !data.is_a?(Numeric) || (!data.is_a?(Integer) && data.floor != data)
399
- yield error(instance, 'integer')
400
- return
401
- end
402
-
403
- validate_numeric(instance, &block)
404
- end
405
-
406
- def validate_string(instance, &block)
407
- data = instance.data
408
-
409
- unless data.is_a?(String)
410
- yield error(instance, 'string')
411
- return
412
- end
413
-
414
- schema = instance.schema
415
-
416
- max_length = schema['maxLength']
417
- min_length = schema['minLength']
418
- pattern = schema['pattern']
419
- format = schema['format']
420
- content_encoding = schema['contentEncoding']
421
- content_media_type = schema['contentMediaType']
422
-
423
- yield error(instance, 'maxLength') if max_length && data.size > max_length
424
- yield error(instance, 'minLength') if min_length && data.size < min_length
425
- yield error(instance, 'pattern') if pattern && !resolve_regexp(pattern).match?(data)
426
- yield error(instance, 'format') if format? && spec_format?(format) && !valid_spec_format?(data, format)
427
-
428
- if content_encoding || content_media_type
429
- decoded_data = data
430
-
431
- if content_encoding
432
- decoded_data = case content_encoding.downcase
433
- when 'base64'
434
- safe_strict_decode64(data)
435
- else # '7bit', '8bit', 'binary', 'quoted-printable'
436
- raise NotImplementedError
437
- end
438
- yield error(instance, 'contentEncoding') unless decoded_data
439
- end
440
-
441
- if content_media_type && decoded_data
442
- case content_media_type.downcase
443
- when 'application/json'
444
- yield error(instance, 'contentMediaType') unless valid_json?(decoded_data)
445
- else
446
- raise NotImplementedError
447
- end
448
- end
449
- end
450
- end
451
-
452
- def validate_array(instance, &block)
453
- data = instance.data
454
-
455
- unless data.is_a?(Array)
456
- yield error(instance, 'array')
457
- return
458
- end
459
-
460
- schema = instance.schema
461
-
462
- items = schema['items']
463
- additional_items = schema['additionalItems']
464
- max_items = schema['maxItems']
465
- min_items = schema['minItems']
466
- unique_items = schema['uniqueItems']
467
- contains = schema['contains']
468
-
469
- yield error(instance, 'maxItems') if max_items && data.size > max_items
470
- yield error(instance, 'minItems') if min_items && data.size < min_items
471
- yield error(instance, 'uniqueItems') if unique_items && data.size != data.uniq.size
472
- yield error(instance, 'contains') if !contains.nil? && data.all? { |item| !valid_instance?(instance.merge(data: item, schema: contains)) }
473
-
474
- if items.is_a?(Array)
475
- data.each_with_index do |item, index|
476
- if index < items.size
477
- subinstance = instance.merge(
478
- data: item,
479
- data_pointer: "#{instance.data_pointer}/#{index}",
480
- schema: items[index],
481
- schema_pointer: "#{instance.schema_pointer}/items/#{index}"
482
- )
483
- validate_instance(subinstance, &block)
484
- elsif !additional_items.nil?
485
- subinstance = instance.merge(
486
- data: item,
487
- data_pointer: "#{instance.data_pointer}/#{index}",
488
- schema: additional_items,
489
- schema_pointer: "#{instance.schema_pointer}/additionalItems"
490
- )
491
- validate_instance(subinstance, &block)
492
- else
493
- break
494
- end
495
- end
496
- elsif !items.nil?
497
- data.each_with_index do |item, index|
498
- subinstance = instance.merge(
499
- data: item,
500
- data_pointer: "#{instance.data_pointer}/#{index}",
501
- schema: items,
502
- schema_pointer: "#{instance.schema_pointer}/items"
503
- )
504
- validate_instance(subinstance, &block)
505
- end
506
- end
507
- end
508
-
509
- def validate_object(instance, &block)
510
- data = instance.data
511
-
512
- unless data.is_a?(Hash)
513
- yield error(instance, 'object')
514
- return
515
- end
516
-
517
- schema = instance.schema
518
-
519
- max_properties = schema['maxProperties']
520
- min_properties = schema['minProperties']
521
- required = schema['required']
522
- properties = schema['properties']
523
- pattern_properties = schema['patternProperties']
524
- additional_properties = schema['additionalProperties']
525
- dependencies = schema['dependencies']
526
- property_names = schema['propertyNames']
527
-
528
- if instance.before_property_validation && properties
529
- properties.each do |property, property_schema|
530
- instance.before_property_validation.each do |hook|
531
- hook.call(data, property, property_schema, schema)
532
- end
533
- end
534
- end
535
-
536
- if dependencies
537
- dependencies.each do |key, value|
538
- next unless data.key?(key)
539
- subschema = value.is_a?(Array) ? { 'required' => value } : value
540
- escaped_key = escape_json_pointer_token(key)
541
- subinstance = instance.merge(schema: subschema, schema_pointer: "#{instance.schema_pointer}/dependencies/#{escaped_key}")
542
- validate_instance(subinstance, &block)
543
- end
544
- end
545
-
546
- yield error(instance, 'maxProperties') if max_properties && data.size > max_properties
547
- yield error(instance, 'minProperties') if min_properties && data.size < min_properties
548
- if required
549
- missing_keys = required - data.keys
550
- yield error(instance, 'required', 'missing_keys' => missing_keys) if missing_keys.any?
551
- end
552
-
553
- regex_pattern_properties = nil
554
- data.each do |key, value|
555
- escaped_key = escape_json_pointer_token(key)
556
-
557
- unless property_names.nil?
558
- subinstance = instance.merge(
559
- data: key,
560
- schema: property_names,
561
- schema_pointer: "#{instance.schema_pointer}/propertyNames"
562
- )
563
- validate_instance(subinstance, &block)
564
- end
565
-
566
- matched_key = false
567
-
568
- if properties && properties.key?(key)
569
- subinstance = instance.merge(
570
- data: value,
571
- data_pointer: "#{instance.data_pointer}/#{escaped_key}",
572
- schema: properties[key],
573
- schema_pointer: "#{instance.schema_pointer}/properties/#{escaped_key}"
574
- )
575
- validate_instance(subinstance, &block)
576
- matched_key = true
577
- end
578
-
579
- if pattern_properties
580
- regex_pattern_properties ||= pattern_properties.map do |pattern, property_schema|
581
- [pattern, resolve_regexp(pattern), property_schema]
582
- end
583
- regex_pattern_properties.each do |pattern, regex, property_schema|
584
- escaped_pattern = escape_json_pointer_token(pattern)
585
- if regex.match?(key)
586
- subinstance = instance.merge(
587
- data: value,
588
- data_pointer: "#{instance.data_pointer}/#{escaped_key}",
589
- schema: property_schema,
590
- schema_pointer: "#{instance.schema_pointer}/patternProperties/#{escaped_pattern}"
591
- )
592
- validate_instance(subinstance, &block)
593
- matched_key = true
594
- end
595
- end
596
- end
597
-
598
- next if matched_key
599
-
600
- unless additional_properties.nil?
601
- subinstance = instance.merge(
602
- data: value,
603
- data_pointer: "#{instance.data_pointer}/#{escaped_key}",
604
- schema: additional_properties,
605
- schema_pointer: "#{instance.schema_pointer}/additionalProperties"
606
- )
607
- validate_instance(subinstance, &block)
608
- end
609
- end
610
-
611
- if instance.after_property_validation && properties
612
- properties.each do |property, property_schema|
613
- instance.after_property_validation.each do |hook|
614
- hook.call(data, property, property_schema, schema)
615
- end
616
- end
617
- end
618
- end
619
-
620
- def safe_strict_decode64(data)
621
- Base64.strict_decode64(data)
622
- rescue ArgumentError
623
- nil
624
- end
625
-
626
- def escape_json_pointer_token(token)
627
- token.gsub(JSON_POINTER_TOKEN_ESCAPE_REGEX, JSON_POINTER_TOKEN_ESCAPE_CHARS)
628
- end
629
-
630
- def join_uri(a, b)
631
- b = URI.parse(b) if b
632
- uri = if a && b && a.relative? && b.relative?
633
- b
634
- elsif a && b
635
- URI.join(a, b)
636
- elsif b
637
- b
638
- else
639
- a
640
- end
641
- uri.fragment = nil if uri.is_a?(URI) && uri.fragment == ''
642
- uri
643
- end
644
-
645
- def resolve_ids(schema, ids = {}, base_uri = @base_uri, pointer = '')
646
- if schema.is_a?(Array)
647
- schema.each_with_index { |subschema, index| resolve_ids(subschema, ids, base_uri, "#{pointer}/#{index}") }
648
- elsif schema.is_a?(Hash)
649
- if schema.key?(id_keyword)
650
- parent_base_uri = base_uri
651
- base_uri = join_uri(base_uri, schema[id_keyword])
652
- ids[base_uri] ||= [schema, pointer, parent_base_uri]
653
- end
654
- schema.each do |key, value|
655
- case key
656
- when 'items', 'allOf', 'anyOf', 'oneOf', 'additionalItems', 'contains', 'additionalProperties', 'propertyNames', 'if', 'then', 'else', 'not'
657
- resolve_ids(value, ids, base_uri, "#{pointer}/#{key}")
658
- when 'properties', 'patternProperties', 'definitions', 'dependencies'
659
- value.each do |subkey, subvalue|
660
- resolve_ids(subvalue, ids, base_uri, "#{pointer}/#{key}/#{subkey}")
661
- end
662
- end
663
- end
664
- end
665
- ids
666
- end
667
-
668
- def resolve_ref(uri)
669
- ref_resolver.call(uri) || raise(InvalidRefResolution, uri.to_s)
670
- end
671
-
672
- def resolve_regexp(pattern)
673
- regexp_resolver.call(pattern) || raise(InvalidRegexpResolution, pattern)
674
- end
675
- end
676
- end
677
- end