json_schemer 1.0.3 → 2.0.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -6
  3. data/CHANGELOG.md +25 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +137 -14
  6. data/json_schemer.gemspec +1 -1
  7. data/lib/json_schemer/draft201909/meta.rb +335 -0
  8. data/lib/json_schemer/draft201909/vocab/applicator.rb +104 -0
  9. data/lib/json_schemer/draft201909/vocab/core.rb +45 -0
  10. data/lib/json_schemer/draft201909/vocab.rb +31 -0
  11. data/lib/json_schemer/draft202012/meta.rb +361 -0
  12. data/lib/json_schemer/draft202012/vocab/applicator.rb +382 -0
  13. data/lib/json_schemer/draft202012/vocab/content.rb +44 -0
  14. data/lib/json_schemer/draft202012/vocab/core.rb +154 -0
  15. data/lib/json_schemer/draft202012/vocab/format_annotation.rb +31 -0
  16. data/lib/json_schemer/draft202012/vocab/format_assertion.rb +29 -0
  17. data/lib/json_schemer/draft202012/vocab/meta_data.rb +30 -0
  18. data/lib/json_schemer/draft202012/vocab/unevaluated.rb +94 -0
  19. data/lib/json_schemer/draft202012/vocab/validation.rb +286 -0
  20. data/lib/json_schemer/draft202012/vocab.rb +103 -0
  21. data/lib/json_schemer/draft4/meta.rb +155 -0
  22. data/lib/json_schemer/draft4/vocab/validation.rb +39 -0
  23. data/lib/json_schemer/draft4/vocab.rb +18 -0
  24. data/lib/json_schemer/draft6/meta.rb +161 -0
  25. data/lib/json_schemer/draft6/vocab.rb +16 -0
  26. data/lib/json_schemer/draft7/meta.rb +178 -0
  27. data/lib/json_schemer/draft7/vocab/validation.rb +69 -0
  28. data/lib/json_schemer/draft7/vocab.rb +30 -0
  29. data/lib/json_schemer/errors.rb +1 -0
  30. data/lib/json_schemer/format/duration.rb +23 -0
  31. data/lib/json_schemer/format/json_pointer.rb +18 -0
  32. data/lib/json_schemer/format.rb +52 -26
  33. data/lib/json_schemer/keyword.rb +41 -0
  34. data/lib/json_schemer/location.rb +25 -0
  35. data/lib/json_schemer/openapi.rb +40 -0
  36. data/lib/json_schemer/openapi30/document.rb +1673 -0
  37. data/lib/json_schemer/openapi30/meta.rb +26 -0
  38. data/lib/json_schemer/openapi30/vocab/base.rb +18 -0
  39. data/lib/json_schemer/openapi30/vocab.rb +12 -0
  40. data/lib/json_schemer/openapi31/document.rb +1559 -0
  41. data/lib/json_schemer/openapi31/meta.rb +128 -0
  42. data/lib/json_schemer/openapi31/vocab/base.rb +89 -0
  43. data/lib/json_schemer/openapi31/vocab.rb +18 -0
  44. data/lib/json_schemer/output.rb +55 -0
  45. data/lib/json_schemer/result.rb +168 -0
  46. data/lib/json_schemer/schema.rb +390 -0
  47. data/lib/json_schemer/version.rb +1 -1
  48. data/lib/json_schemer.rb +197 -24
  49. metadata +42 -10
  50. data/lib/json_schemer/schema/base.rb +0 -677
  51. data/lib/json_schemer/schema/draft4.json +0 -149
  52. data/lib/json_schemer/schema/draft4.rb +0 -44
  53. data/lib/json_schemer/schema/draft6.json +0 -155
  54. data/lib/json_schemer/schema/draft6.rb +0 -25
  55. data/lib/json_schemer/schema/draft7.json +0 -172
  56. 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