json_schemer 0.2.18 → 2.2.1

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