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
@@ -0,0 +1,382 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft202012
4
+ module Vocab
5
+ module Applicator
6
+ class AllOf < Keyword
7
+ def error(formatted_instance_location:, **)
8
+ "value at #{formatted_instance_location} does not match all `allOf` schemas"
9
+ end
10
+
11
+ def parse
12
+ value.map.with_index do |subschema, index|
13
+ subschema(subschema, index.to_s)
14
+ end
15
+ end
16
+
17
+ def validate(instance, instance_location, keyword_location, context)
18
+ nested = parsed.map.with_index do |subschema, index|
19
+ subschema.validate_instance(instance, instance_location, join_location(keyword_location, index.to_s), context)
20
+ end
21
+ result(instance, instance_location, keyword_location, nested.all?(&:valid), nested)
22
+ end
23
+ end
24
+
25
+ class AnyOf < Keyword
26
+ def error(formatted_instance_location:, **)
27
+ "value at #{formatted_instance_location} does not match any `anyOf` schemas"
28
+ end
29
+
30
+ def parse
31
+ value.map.with_index do |subschema, index|
32
+ subschema(subschema, index.to_s)
33
+ end
34
+ end
35
+
36
+ def validate(instance, instance_location, keyword_location, context)
37
+ nested = parsed.map.with_index do |subschema, index|
38
+ subschema.validate_instance(instance, instance_location, join_location(keyword_location, index.to_s), context)
39
+ end
40
+ result(instance, instance_location, keyword_location, nested.any?(&:valid), nested)
41
+ end
42
+ end
43
+
44
+ class OneOf < Keyword
45
+ def error(formatted_instance_location:, **)
46
+ "value at #{formatted_instance_location} does not match exactly one `oneOf` schema"
47
+ end
48
+
49
+ def parse
50
+ value.map.with_index do |subschema, index|
51
+ subschema(subschema, index.to_s)
52
+ end
53
+ end
54
+
55
+ def validate(instance, instance_location, keyword_location, context)
56
+ nested = parsed.map.with_index do |subschema, index|
57
+ subschema.validate_instance(instance, instance_location, join_location(keyword_location, index.to_s), context)
58
+ end
59
+ valid_count = nested.count(&:valid)
60
+ result(instance, instance_location, keyword_location, valid_count == 1, nested, :ignore_nested => valid_count > 1)
61
+ end
62
+ end
63
+
64
+ class Not < Keyword
65
+ def error(formatted_instance_location:, **)
66
+ "value at #{formatted_instance_location} matches `not` schema"
67
+ end
68
+
69
+ def parse
70
+ subschema(value)
71
+ end
72
+
73
+ def validate(instance, instance_location, keyword_location, context)
74
+ subschema_result = parsed.validate_instance(instance, instance_location, keyword_location, context)
75
+ result(instance, instance_location, keyword_location, !subschema_result.valid, subschema_result.nested)
76
+ end
77
+ end
78
+
79
+ class If < Keyword
80
+ def parse
81
+ subschema(value)
82
+ end
83
+
84
+ def validate(instance, instance_location, keyword_location, context)
85
+ subschema_result = parsed.validate_instance(instance, instance_location, keyword_location, context)
86
+ result(instance, instance_location, keyword_location, true, subschema_result.nested, :annotation => subschema_result.valid)
87
+ end
88
+ end
89
+
90
+ class Then < Keyword
91
+ def error(formatted_instance_location:, **)
92
+ "value at #{formatted_instance_location} does not match conditional `then` schema"
93
+ end
94
+
95
+ def parse
96
+ subschema(value)
97
+ end
98
+
99
+ def validate(instance, instance_location, keyword_location, context)
100
+ return unless context.adjacent_results.key?(If) && context.adjacent_results.fetch(If).annotation
101
+ subschema_result = parsed.validate_instance(instance, instance_location, keyword_location, context)
102
+ result(instance, instance_location, keyword_location, subschema_result.valid, subschema_result.nested)
103
+ end
104
+ end
105
+
106
+ class Else < Keyword
107
+ def error(formatted_instance_location:, **)
108
+ "value at #{formatted_instance_location} does not match conditional `else` schema"
109
+ end
110
+
111
+ def parse
112
+ subschema(value)
113
+ end
114
+
115
+ def validate(instance, instance_location, keyword_location, context)
116
+ return unless context.adjacent_results.key?(If) && !context.adjacent_results.fetch(If).annotation
117
+ subschema_result = parsed.validate_instance(instance, instance_location, keyword_location, context)
118
+ result(instance, instance_location, keyword_location, subschema_result.valid, subschema_result.nested)
119
+ end
120
+ end
121
+
122
+ class DependentSchemas < Keyword
123
+ def error(formatted_instance_location:, **)
124
+ "value at #{formatted_instance_location} does not match applicable `dependentSchemas` schemas"
125
+ end
126
+
127
+ def parse
128
+ value.each_with_object({}) do |(key, subschema), out|
129
+ out[key] = subschema(subschema, key)
130
+ end
131
+ end
132
+
133
+ def validate(instance, instance_location, keyword_location, context)
134
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash)
135
+
136
+ nested = parsed.select do |key, _subschema|
137
+ instance.key?(key)
138
+ end.map do |key, subschema|
139
+ subschema.validate_instance(instance, instance_location, join_location(keyword_location, key), context)
140
+ end
141
+
142
+ result(instance, instance_location, keyword_location, nested.all?(&:valid), nested)
143
+ end
144
+ end
145
+
146
+ class PrefixItems < Keyword
147
+ def error(formatted_instance_location:, **)
148
+ "array items at #{formatted_instance_location} do not match corresponding `prefixItems` schemas"
149
+ end
150
+
151
+ def parse
152
+ value.map.with_index do |subschema, index|
153
+ subschema(subschema, index.to_s)
154
+ end
155
+ end
156
+
157
+ def validate(instance, instance_location, keyword_location, context)
158
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array)
159
+
160
+ nested = instance.take(parsed.size).map.with_index do |item, index|
161
+ parsed.fetch(index).validate_instance(item, join_location(instance_location, index.to_s), join_location(keyword_location, index.to_s), context)
162
+ end
163
+
164
+ result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => (nested.size - 1))
165
+ end
166
+ end
167
+
168
+ class Items < Keyword
169
+ def error(formatted_instance_location:, **)
170
+ "array items at #{formatted_instance_location} do not match `items` schema"
171
+ end
172
+
173
+ def parse
174
+ subschema(value)
175
+ end
176
+
177
+ def validate(instance, instance_location, keyword_location, context)
178
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array)
179
+
180
+ evaluated_index = context.adjacent_results[PrefixItems]&.annotation
181
+ offset = evaluated_index ? (evaluated_index + 1) : 0
182
+
183
+ nested = instance.slice(offset..-1).map.with_index do |item, index|
184
+ parsed.validate_instance(item, join_location(instance_location, (offset + index).to_s), keyword_location, context)
185
+ end
186
+
187
+ result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => nested.any?)
188
+ end
189
+ end
190
+
191
+ class Contains < Keyword
192
+ def error(formatted_instance_location:, **)
193
+ "array at #{formatted_instance_location} does not contain enough items that match `contains` schema"
194
+ end
195
+
196
+ def parse
197
+ subschema(value)
198
+ end
199
+
200
+ def validate(instance, instance_location, keyword_location, context)
201
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array)
202
+
203
+ nested = instance.map.with_index do |item, index|
204
+ parsed.validate_instance(item, join_location(instance_location, index.to_s), keyword_location, context)
205
+ end
206
+
207
+ annotation = []
208
+ nested.each_with_index do |nested_result, index|
209
+ annotation << index if nested_result.valid
210
+ end
211
+
212
+ min_contains = schema.parsed['minContains']&.parsed || 1
213
+
214
+ result(instance, instance_location, keyword_location, annotation.size >= min_contains, nested, :annotation => annotation, :ignore_nested => true)
215
+ end
216
+ end
217
+
218
+ class Properties < Keyword
219
+ def error(formatted_instance_location:, **)
220
+ "object properties at #{formatted_instance_location} do not match corresponding `properties` schemas"
221
+ end
222
+
223
+ def parse
224
+ value.each_with_object({}) do |(property, subschema), out|
225
+ out[property] = subschema(subschema, property)
226
+ end
227
+ end
228
+
229
+ def validate(instance, instance_location, keyword_location, context)
230
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash)
231
+
232
+ if root.before_property_validation.any?
233
+ original_instance = context.original_instance(instance_location)
234
+ root.before_property_validation.each do |hook|
235
+ parsed.each do |property, subschema|
236
+ hook.call(original_instance, property, subschema.value, schema.value)
237
+ end
238
+ end
239
+ instance.replace(deep_stringify_keys(original_instance))
240
+ end
241
+
242
+ evaluated_keys = []
243
+ nested = []
244
+
245
+ parsed.each do |property, subschema|
246
+ if instance.key?(property)
247
+ evaluated_keys << property
248
+ nested << subschema.validate_instance(instance.fetch(property), join_location(instance_location, property), join_location(keyword_location, property), context)
249
+ end
250
+ end
251
+
252
+ if root.after_property_validation.any?
253
+ original_instance = context.original_instance(instance_location)
254
+ root.after_property_validation.each do |hook|
255
+ parsed.each do |property, subschema|
256
+ hook.call(original_instance, property, subschema.value, schema.value)
257
+ end
258
+ end
259
+ instance.replace(deep_stringify_keys(original_instance))
260
+ end
261
+
262
+ result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => evaluated_keys)
263
+ end
264
+ end
265
+
266
+ class PatternProperties < Keyword
267
+ def error(formatted_instance_location:, **)
268
+ "object properties at #{formatted_instance_location} do not match corresponding `patternProperties` schemas"
269
+ end
270
+
271
+ def parse
272
+ value.each_with_object({}) do |(pattern, subschema), out|
273
+ out[pattern] = subschema(subschema, pattern)
274
+ end
275
+ end
276
+
277
+ def validate(instance, instance_location, keyword_location, context)
278
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash)
279
+
280
+ evaluated = Set[]
281
+ nested = []
282
+
283
+ parsed.each do |pattern, subschema|
284
+ regexp = root.resolve_regexp(pattern)
285
+ instance.each do |key, value|
286
+ if regexp.match?(key)
287
+ evaluated << key
288
+ nested << subschema.validate_instance(value, join_location(instance_location, key), join_location(keyword_location, pattern), context)
289
+ end
290
+ end
291
+ end
292
+
293
+ result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => evaluated.to_a)
294
+ end
295
+ end
296
+
297
+ class AdditionalProperties < Keyword
298
+ def error(formatted_instance_location:, **)
299
+ "object properties at #{formatted_instance_location} do not match `additionalProperties` schema"
300
+ end
301
+
302
+ def false_schema_error(formatted_instance_location:, **)
303
+ "object property at #{formatted_instance_location} is not defined and schema does not allow additional properties"
304
+ end
305
+
306
+ def parse
307
+ subschema(value)
308
+ end
309
+
310
+ def validate(instance, instance_location, keyword_location, context)
311
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash)
312
+
313
+ evaluated_keys = context.adjacent_results[Properties]&.annotation || []
314
+ evaluated_keys += context.adjacent_results[PatternProperties]&.annotation || []
315
+ evaluated_keys = evaluated_keys.to_set
316
+
317
+ evaluated = instance.reject do |key, _value|
318
+ evaluated_keys.include?(key)
319
+ end
320
+
321
+ nested = evaluated.map do |key, value|
322
+ parsed.validate_instance(value, join_location(instance_location, key), keyword_location, context)
323
+ end
324
+
325
+ result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => evaluated.keys)
326
+ end
327
+ end
328
+
329
+ class PropertyNames < Keyword
330
+ def error(formatted_instance_location:, **)
331
+ "object property names at #{formatted_instance_location} do not match `propertyNames` schema"
332
+ end
333
+
334
+ def parse
335
+ subschema(value)
336
+ end
337
+
338
+ def validate(instance, instance_location, keyword_location, context)
339
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash)
340
+
341
+ nested = instance.map do |key, _value|
342
+ parsed.validate_instance(key, instance_location, keyword_location, context)
343
+ end
344
+
345
+ result(instance, instance_location, keyword_location, nested.all?(&:valid), nested)
346
+ end
347
+ end
348
+
349
+ class Dependencies < Keyword
350
+ def error(formatted_instance_location:, **)
351
+ "object at #{formatted_instance_location} either does not match applicable `dependencies` schemas or is missing required `dependencies` properties"
352
+ end
353
+
354
+ def parse
355
+ value.each_with_object({}) do |(key, value), out|
356
+ out[key] = value.is_a?(Array) ? value : subschema(value, key)
357
+ end
358
+ end
359
+
360
+ def validate(instance, instance_location, keyword_location, context)
361
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash)
362
+
363
+ existing_keys = instance.keys
364
+
365
+ nested = parsed.select do |key, _value|
366
+ instance.key?(key)
367
+ end.map do |key, value|
368
+ if value.is_a?(Array)
369
+ missing_keys = value - existing_keys
370
+ result(instance, instance_location, join_location(keyword_location, key), missing_keys.none?, :details => { 'missing_keys' => missing_keys })
371
+ else
372
+ value.validate_instance(instance, instance_location, join_location(keyword_location, key), context)
373
+ end
374
+ end
375
+
376
+ result(instance, instance_location, keyword_location, nested.all?(&:valid), nested)
377
+ end
378
+ end
379
+ end
380
+ end
381
+ end
382
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft202012
4
+ module Vocab
5
+ module Content
6
+ class ContentEncoding < Keyword
7
+ def validate(instance, instance_location, keyword_location, _context)
8
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(String)
9
+
10
+ _valid, annotation = Format.decode_content_encoding(instance, value)
11
+
12
+ result(instance, instance_location, keyword_location, true, :annotation => annotation)
13
+ end
14
+ end
15
+
16
+ class ContentMediaType < Keyword
17
+ def validate(instance, instance_location, keyword_location, context)
18
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(String)
19
+
20
+ decoded_instance = context.adjacent_results[ContentEncoding]&.annotation || instance
21
+ _valid, annotation = Format.parse_content_media_type(decoded_instance, value)
22
+
23
+ result(instance, instance_location, keyword_location, true, :annotation => annotation)
24
+ end
25
+ end
26
+
27
+ class ContentSchema < Keyword
28
+ def parse
29
+ subschema(value)
30
+ end
31
+
32
+ def validate(instance, instance_location, keyword_location, context)
33
+ return result(instance, instance_location, keyword_location, true) unless context.adjacent_results.key?(ContentMediaType)
34
+
35
+ parsed_instance = context.adjacent_results.fetch(ContentMediaType).annotation
36
+ annotation = parsed.validate_instance(parsed_instance, instance_location, keyword_location, context)
37
+
38
+ result(instance, instance_location, keyword_location, true, :annotation => annotation.to_output_unit)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft202012
4
+ module Vocab
5
+ module Core
6
+ class Schema < Keyword
7
+ def parse
8
+ schema.meta_schema = if value == schema.base_uri.to_s
9
+ schema
10
+ else
11
+ META_SCHEMAS_BY_BASE_URI_STR[value] || root.resolve_ref(URI(value))
12
+ end
13
+ value
14
+ end
15
+ end
16
+
17
+ class Vocabulary < Keyword
18
+ def parse
19
+ value.each_with_object({}) do |(vocabulary, required), out|
20
+ if VOCABULARIES.key?(vocabulary)
21
+ out[vocabulary] = VOCABULARIES.fetch(vocabulary)
22
+ elsif required
23
+ raise UnknownVocabulary, vocabulary
24
+ end
25
+ end.tap do |vocabularies|
26
+ schema.keywords = vocabularies.sort_by do |vocabulary, _keywords|
27
+ VOCABULARY_ORDER.fetch(vocabulary, Float::INFINITY)
28
+ end.each_with_object({}) do |(_vocabulary, keywords), out|
29
+ out.merge!(keywords)
30
+ end
31
+ schema.keyword_order = schema.keywords.transform_values.with_index { |_keyword_class, index| index }
32
+ end
33
+ end
34
+ end
35
+
36
+ class Id < Keyword
37
+ def parse
38
+ URI.join(schema.base_uri, value).tap do |uri|
39
+ schema.base_uri = uri
40
+ root.resources[:lexical][uri] = schema
41
+ end
42
+ end
43
+ end
44
+
45
+ class Anchor < Keyword
46
+ def parse
47
+ URI.join(schema.base_uri, "##{value}").tap do |uri|
48
+ root.resources[:lexical][uri] = schema
49
+ end
50
+ end
51
+ end
52
+
53
+ class Ref < Keyword
54
+ def self.exclusive?
55
+ false
56
+ end
57
+
58
+ def ref_uri
59
+ @ref_uri ||= URI.join(schema.base_uri, value)
60
+ end
61
+
62
+ def ref_schema
63
+ @ref_schema ||= root.resolve_ref(ref_uri)
64
+ end
65
+
66
+ def validate(instance, instance_location, keyword_location, context)
67
+ ref_schema.validate_instance(instance, instance_location, keyword_location, context)
68
+ end
69
+ end
70
+
71
+ class DynamicAnchor < Keyword
72
+ def parse
73
+ URI.join(schema.base_uri, "##{value}").tap do |uri|
74
+ root.resources[:lexical][uri] = schema
75
+ root.resources[:dynamic][uri] = schema
76
+ end
77
+ end
78
+ end
79
+
80
+ class DynamicRef < Keyword
81
+ def ref_uri
82
+ @ref_uri ||= URI.join(schema.base_uri, value)
83
+ end
84
+
85
+ def ref_schema
86
+ @ref_schema ||= root.resolve_ref(ref_uri)
87
+ end
88
+
89
+ def dynamic_anchor
90
+ return @dynamic_anchor if defined?(@dynamic_anchor)
91
+ fragment = ref_schema.parsed['$dynamicAnchor']&.parsed&.fragment
92
+ @dynamic_anchor = (fragment == ref_uri.fragment ? fragment : nil)
93
+ end
94
+
95
+ def validate(instance, instance_location, keyword_location, context)
96
+ schema = ref_schema
97
+
98
+ if dynamic_anchor
99
+ context.dynamic_scope.each do |ancestor|
100
+ dynamic_uri = URI.join(ancestor.base_uri, "##{dynamic_anchor}")
101
+ if ancestor.root.resources.fetch(:dynamic).key?(dynamic_uri)
102
+ schema = ancestor.root.resources.fetch(:dynamic).fetch(dynamic_uri)
103
+ break
104
+ end
105
+ end
106
+ end
107
+
108
+ schema.validate_instance(instance, instance_location, keyword_location, context)
109
+ end
110
+ end
111
+
112
+ class Defs < Keyword
113
+ def parse
114
+ value.each_with_object({}) do |(key, subschema), out|
115
+ out[key] = subschema(subschema, key)
116
+ end
117
+ end
118
+ end
119
+
120
+ class Comment < Keyword; end
121
+
122
+ class UnknownKeyword < Keyword
123
+ def parse
124
+ if value.is_a?(Hash)
125
+ {}
126
+ elsif value.is_a?(Array)
127
+ []
128
+ else
129
+ value
130
+ end
131
+ end
132
+
133
+ def fetch_unknown!(token)
134
+ if value.is_a?(Hash)
135
+ parsed[token] ||= JSONSchemer::Schema::UNKNOWN_KEYWORD_CLASS.new(value.fetch(token), self, token, schema)
136
+ elsif value.is_a?(Array)
137
+ parsed[token.to_i] ||= JSONSchemer::Schema::UNKNOWN_KEYWORD_CLASS.new(value.fetch(token.to_i), self, token, schema)
138
+ else
139
+ raise KeyError.new(:receiver => parsed, :key => token)
140
+ end
141
+ end
142
+
143
+ def unknown_schema!
144
+ @unknown_schema ||= subschema(value)
145
+ end
146
+
147
+ def validate(instance, instance_location, keyword_location, _context)
148
+ result(instance, instance_location, keyword_location, true, :annotation => value)
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft202012
4
+ module Vocab
5
+ module FormatAnnotation
6
+ class Format < Keyword
7
+ extend JSONSchemer::Format
8
+
9
+ DEFAULT_FORMAT = proc do |instance, value|
10
+ !instance.is_a?(String) || valid_spec_format?(instance, value)
11
+ rescue UnknownFormat
12
+ true
13
+ end
14
+
15
+ def error(formatted_instance_location:, **)
16
+ "value at #{formatted_instance_location} does not match format: #{value}"
17
+ end
18
+
19
+ def parse
20
+ root.format && root.formats.fetch(value) { root.meta_schema.formats.fetch(value, DEFAULT_FORMAT) }
21
+ end
22
+
23
+ def validate(instance, instance_location, keyword_location, _context)
24
+ valid = parsed == false || parsed.call(instance, value)
25
+ result(instance, instance_location, keyword_location, valid, :annotation => value)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft202012
4
+ module Vocab
5
+ module FormatAssertion
6
+ class Format < Keyword
7
+ extend JSONSchemer::Format
8
+
9
+ DEFAULT_FORMAT = proc do |instance, value|
10
+ !instance.is_a?(String) || valid_spec_format?(instance, value)
11
+ end
12
+
13
+ def error(formatted_instance_location:, **)
14
+ "value at #{formatted_instance_location} does not match format: #{value}"
15
+ end
16
+
17
+ def parse
18
+ root.format && root.formats.fetch(value) { root.meta_schema.formats.fetch(value, DEFAULT_FORMAT) }
19
+ end
20
+
21
+ def validate(instance, instance_location, keyword_location, _context)
22
+ valid = parsed == false || parsed.call(instance, value)
23
+ result(instance, instance_location, keyword_location, valid, :annotation => value)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft202012
4
+ module Vocab
5
+ module MetaData
6
+ class ReadOnly < Keyword
7
+ def error(formatted_instance_location:, **)
8
+ "value at #{formatted_instance_location} is `readOnly`"
9
+ end
10
+
11
+ def validate(instance, instance_location, keyword_location, context)
12
+ valid = parsed != true || !context.access_mode || context.access_mode == 'read'
13
+ result(instance, instance_location, keyword_location, valid, :annotation => value)
14
+ end
15
+ end
16
+
17
+ class WriteOnly < Keyword
18
+ def error(formatted_instance_location:, **)
19
+ "value at #{formatted_instance_location} is `writeOnly`"
20
+ end
21
+
22
+ def validate(instance, instance_location, keyword_location, context)
23
+ valid = parsed != true || !context.access_mode || context.access_mode == 'write'
24
+ result(instance, instance_location, keyword_location, valid, :annotation => value)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end