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
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft202012
4
+ module Vocab
5
+ module Unevaluated
6
+ class UnevaluatedItems < Keyword
7
+ def error(formatted_instance_location:, **)
8
+ "array items at #{formatted_instance_location} do not match `unevaluatedItems` schema"
9
+ end
10
+
11
+ def false_schema_error(formatted_instance_location:, **)
12
+ "array item at #{formatted_instance_location} is a disallowed unevaluated item"
13
+ end
14
+
15
+ def parse
16
+ subschema(value)
17
+ end
18
+
19
+ def validate(instance, instance_location, keyword_location, context)
20
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array)
21
+
22
+ unevaluated_items = instance.size.times.to_set
23
+
24
+ context.adjacent_results.each_value do |adjacent_result|
25
+ collect_unevaluated_items(adjacent_result, unevaluated_items)
26
+ end
27
+
28
+ nested = unevaluated_items.map do |index|
29
+ parsed.validate_instance(instance.fetch(index), join_location(instance_location, index.to_s), keyword_location, context)
30
+ end
31
+
32
+ result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => nested.any?)
33
+ end
34
+
35
+ private
36
+
37
+ def collect_unevaluated_items(result, unevaluated_items)
38
+ case result.source
39
+ when Applicator::PrefixItems
40
+ unevaluated_items.subtract(0..result.annotation)
41
+ when Applicator::Items, UnevaluatedItems
42
+ unevaluated_items.clear if result.annotation
43
+ when Applicator::Contains
44
+ unevaluated_items.subtract(result.annotation)
45
+ end
46
+ result.nested&.each do |subresult|
47
+ if subresult.valid && subresult.instance_location == result.instance_location
48
+ collect_unevaluated_items(subresult, unevaluated_items)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ class UnevaluatedProperties < Keyword
55
+ def error(formatted_instance_location:, **)
56
+ "object properties at #{formatted_instance_location} do not match `unevaluatedProperties` schema"
57
+ end
58
+
59
+ def false_schema_error(formatted_instance_location:, **)
60
+ "object property at #{formatted_instance_location} is a disallowed unevaluated property"
61
+ end
62
+
63
+ def parse
64
+ subschema(value)
65
+ end
66
+
67
+ def validate(instance, instance_location, keyword_location, context)
68
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash)
69
+
70
+ evaluated_keys = Set[]
71
+
72
+ context.adjacent_results.each_value do |adjacent_result|
73
+ collect_evaluated_keys(adjacent_result, evaluated_keys)
74
+ end
75
+
76
+ evaluated = instance.reject do |key, _value|
77
+ evaluated_keys.include?(key)
78
+ end
79
+
80
+ nested = evaluated.map do |key, value|
81
+ parsed.validate_instance(value, join_location(instance_location, key), keyword_location, context)
82
+ end
83
+
84
+ result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => evaluated.keys)
85
+ end
86
+
87
+ private
88
+
89
+ def collect_evaluated_keys(result, evaluated_keys)
90
+ case result.source
91
+ when Applicator::Properties, Applicator::PatternProperties, Applicator::AdditionalProperties, UnevaluatedProperties
92
+ evaluated_keys.merge(result.annotation)
93
+ end
94
+ result.nested&.each do |subresult|
95
+ if subresult.valid && subresult.instance_location == result.instance_location
96
+ collect_evaluated_keys(subresult, evaluated_keys)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,286 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft202012
4
+ module Vocab
5
+ module Validation
6
+ class Type < Keyword
7
+ def error(formatted_instance_location:, **)
8
+ case value
9
+ when 'null'
10
+ "value at #{formatted_instance_location} is not null"
11
+ when 'boolean'
12
+ "value at #{formatted_instance_location} is not a boolean"
13
+ when 'number'
14
+ "value at #{formatted_instance_location} is not a number"
15
+ when 'integer'
16
+ "value at #{formatted_instance_location} is not an integer"
17
+ when 'string'
18
+ "value at #{formatted_instance_location} is not a string"
19
+ when 'array'
20
+ "value at #{formatted_instance_location} is not an array"
21
+ when 'object'
22
+ "value at #{formatted_instance_location} is not an object"
23
+ else
24
+ "value at #{formatted_instance_location} is not one of the types: #{value}"
25
+ end
26
+ end
27
+
28
+ def validate(instance, instance_location, keyword_location, _context)
29
+ case parsed
30
+ when String
31
+ result(instance, instance_location, keyword_location, valid_type(parsed, instance), :type => parsed)
32
+ when Array
33
+ result(instance, instance_location, keyword_location, parsed.any? { |type| valid_type(type, instance) })
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def valid_type(type, instance)
40
+ case type
41
+ when 'null'
42
+ instance.nil?
43
+ when 'boolean'
44
+ instance == true || instance == false
45
+ when 'number'
46
+ instance.is_a?(Numeric)
47
+ when 'integer'
48
+ instance.is_a?(Numeric) && (instance.is_a?(Integer) || instance.floor == instance)
49
+ when 'string'
50
+ instance.is_a?(String)
51
+ when 'array'
52
+ instance.is_a?(Array)
53
+ when 'object'
54
+ instance.is_a?(Hash)
55
+ else
56
+ true
57
+ end
58
+ end
59
+ end
60
+
61
+ class Enum < Keyword
62
+ def error(formatted_instance_location:, **)
63
+ "value at #{formatted_instance_location} is not one of: #{value}"
64
+ end
65
+
66
+ def validate(instance, instance_location, keyword_location, _context)
67
+ result(instance, instance_location, keyword_location, !value || value.include?(instance))
68
+ end
69
+ end
70
+
71
+ class Const < Keyword
72
+ def error(formatted_instance_location:, **)
73
+ "value at #{formatted_instance_location} is not: #{value}"
74
+ end
75
+
76
+ def validate(instance, instance_location, keyword_location, _context)
77
+ result(instance, instance_location, keyword_location, value == instance)
78
+ end
79
+ end
80
+
81
+ class MultipleOf < Keyword
82
+ def error(formatted_instance_location:, **)
83
+ "number at #{formatted_instance_location} is not a multiple of: #{value}"
84
+ end
85
+
86
+ def validate(instance, instance_location, keyword_location, _context)
87
+ result(instance, instance_location, keyword_location, !instance.is_a?(Numeric) || BigDecimal(instance.to_s).modulo(value).zero?)
88
+ end
89
+ end
90
+
91
+ class Maximum < Keyword
92
+ def error(formatted_instance_location:, **)
93
+ "number at #{formatted_instance_location} is greater than: #{value}"
94
+ end
95
+
96
+ def validate(instance, instance_location, keyword_location, _context)
97
+ result(instance, instance_location, keyword_location, !instance.is_a?(Numeric) || instance <= value)
98
+ end
99
+ end
100
+
101
+ class ExclusiveMaximum < Keyword
102
+ def error(formatted_instance_location:, **)
103
+ "number at #{formatted_instance_location} is greater than or equal to: #{value}"
104
+ end
105
+
106
+ def validate(instance, instance_location, keyword_location, _context)
107
+ result(instance, instance_location, keyword_location, !instance.is_a?(Numeric) || instance < value)
108
+ end
109
+ end
110
+
111
+ class Minimum < Keyword
112
+ def error(formatted_instance_location:, **)
113
+ "number at #{formatted_instance_location} is less than: #{value}"
114
+ end
115
+
116
+ def validate(instance, instance_location, keyword_location, _context)
117
+ result(instance, instance_location, keyword_location, !instance.is_a?(Numeric) || instance >= value)
118
+ end
119
+ end
120
+
121
+ class ExclusiveMinimum < Keyword
122
+ def error(formatted_instance_location:, **)
123
+ "number at #{formatted_instance_location} is less than or equal to: #{value}"
124
+ end
125
+
126
+ def validate(instance, instance_location, keyword_location, _context)
127
+ result(instance, instance_location, keyword_location, !instance.is_a?(Numeric) || instance > value)
128
+ end
129
+ end
130
+
131
+ class MaxLength < Keyword
132
+ def error(formatted_instance_location:, **)
133
+ "string length at #{formatted_instance_location} is greater than: #{value}"
134
+ end
135
+
136
+ def validate(instance, instance_location, keyword_location, _context)
137
+ result(instance, instance_location, keyword_location, !instance.is_a?(String) || instance.size <= value)
138
+ end
139
+ end
140
+
141
+ class MinLength < Keyword
142
+ def error(formatted_instance_location:, **)
143
+ "string length at #{formatted_instance_location} is less than: #{value}"
144
+ end
145
+
146
+ def validate(instance, instance_location, keyword_location, _context)
147
+ result(instance, instance_location, keyword_location, !instance.is_a?(String) || instance.size >= value)
148
+ end
149
+ end
150
+
151
+ class Pattern < Keyword
152
+ def error(formatted_instance_location:, **)
153
+ "string at #{formatted_instance_location} does not match pattern: #{value}"
154
+ end
155
+
156
+ def parse
157
+ root.resolve_regexp(value)
158
+ end
159
+
160
+ def validate(instance, instance_location, keyword_location, _context)
161
+ result(instance, instance_location, keyword_location, !instance.is_a?(String) || parsed.match?(instance))
162
+ end
163
+ end
164
+
165
+ class MaxItems < Keyword
166
+ def error(formatted_instance_location:, **)
167
+ "array size at #{formatted_instance_location} is greater than: #{value}"
168
+ end
169
+
170
+ def validate(instance, instance_location, keyword_location, _context)
171
+ result(instance, instance_location, keyword_location, !instance.is_a?(Array) || instance.size <= value)
172
+ end
173
+ end
174
+
175
+ class MinItems < Keyword
176
+ def error(formatted_instance_location:, **)
177
+ "array size at #{formatted_instance_location} is less than: #{value}"
178
+ end
179
+
180
+ def validate(instance, instance_location, keyword_location, _context)
181
+ result(instance, instance_location, keyword_location, !instance.is_a?(Array) || instance.size >= value)
182
+ end
183
+ end
184
+
185
+ class UniqueItems < Keyword
186
+ def error(formatted_instance_location:, **)
187
+ "array items at #{formatted_instance_location} are not unique"
188
+ end
189
+
190
+ def validate(instance, instance_location, keyword_location, _context)
191
+ result(instance, instance_location, keyword_location, !instance.is_a?(Array) || value == false || instance.size == instance.uniq.size)
192
+ end
193
+ end
194
+
195
+ class MaxContains < Keyword
196
+ def error(formatted_instance_location:, **)
197
+ "number of array items at #{formatted_instance_location} matching `contains` schema is greater than: #{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) && context.adjacent_results.key?(Applicator::Contains)
202
+ evaluated_items = context.adjacent_results.fetch(Applicator::Contains).annotation
203
+ result(instance, instance_location, keyword_location, evaluated_items.size <= value)
204
+ end
205
+ end
206
+
207
+ class MinContains < Keyword
208
+ def error(formatted_instance_location:, **)
209
+ "number of array items at #{formatted_instance_location} matching `contains` schema is less than: #{value}"
210
+ end
211
+
212
+ def validate(instance, instance_location, keyword_location, context)
213
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array) && context.adjacent_results.key?(Applicator::Contains)
214
+ evaluated_items = context.adjacent_results.fetch(Applicator::Contains).annotation
215
+ result(instance, instance_location, keyword_location, evaluated_items.size >= value)
216
+ end
217
+ end
218
+
219
+ class MaxProperties < Keyword
220
+ def error(formatted_instance_location:, **)
221
+ "object size at #{formatted_instance_location} is greater than: #{value}"
222
+ end
223
+
224
+ def validate(instance, instance_location, keyword_location, _context)
225
+ result(instance, instance_location, keyword_location, !instance.is_a?(Hash) || instance.size <= value)
226
+ end
227
+ end
228
+
229
+ class MinProperties < Keyword
230
+ def error(formatted_instance_location:, **)
231
+ "object size at #{formatted_instance_location} is less than: #{value}"
232
+ end
233
+
234
+ def validate(instance, instance_location, keyword_location, _context)
235
+ result(instance, instance_location, keyword_location, !instance.is_a?(Hash) || instance.size >= value)
236
+ end
237
+ end
238
+
239
+ class Required < Keyword
240
+ def error(formatted_instance_location:, details:, **)
241
+ "object at #{formatted_instance_location} is missing required properties: #{details.fetch('missing_keys').join(', ')}"
242
+ end
243
+
244
+ def validate(instance, instance_location, keyword_location, context)
245
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash)
246
+
247
+ required_keys = value
248
+
249
+ if context.access_mode && schema.parsed.key?('properties')
250
+ inapplicable_access_mode_keys = []
251
+ schema.parsed.fetch('properties').parsed.each do |property, subschema|
252
+ read_only, write_only = subschema.parsed.values_at('readOnly', 'writeOnly')
253
+ inapplicable_access_mode_keys << property if context.access_mode == 'write' && read_only&.parsed == true
254
+ inapplicable_access_mode_keys << property if context.access_mode == 'read' && write_only&.parsed == true
255
+ end
256
+ required_keys -= inapplicable_access_mode_keys
257
+ end
258
+
259
+ missing_keys = required_keys - instance.keys
260
+ result(instance, instance_location, keyword_location, missing_keys.none?, :details => { 'missing_keys' => missing_keys })
261
+ end
262
+ end
263
+
264
+ class DependentRequired < Keyword
265
+ def error(formatted_instance_location:, **)
266
+ "object at #{formatted_instance_location} is missing required `dependentRequired` properties"
267
+ end
268
+
269
+ def validate(instance, instance_location, keyword_location, _context)
270
+ return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash)
271
+
272
+ existing_keys = instance.keys
273
+
274
+ nested = value.select do |key, _required_keys|
275
+ instance.key?(key)
276
+ end.map do |key, required_keys|
277
+ result(instance, join_location(instance_location, key), join_location(keyword_location, key), (required_keys - existing_keys).none?)
278
+ end
279
+
280
+ result(instance, instance_location, keyword_location, nested.all?(&:valid), nested)
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft202012
4
+ module Vocab
5
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-8
6
+ CORE = {
7
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-8.1
8
+ '$schema' => Core::Schema,
9
+ '$vocabulary' => Core::Vocabulary,
10
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-8.2
11
+ '$id' => Core::Id,
12
+ '$anchor' => Core::Anchor,
13
+ '$ref' => Core::Ref,
14
+ '$dynamicAnchor' => Core::DynamicAnchor,
15
+ '$dynamicRef' => Core::DynamicRef,
16
+ '$defs' => Core::Defs,
17
+ 'definitions' => Core::Defs,
18
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-8.3
19
+ '$comment' => Core::Comment,
20
+ # https://github.com/orgs/json-schema-org/discussions/329
21
+ 'x-error' => Core::XError
22
+ }
23
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10
24
+ APPLICATOR = {
25
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10.2
26
+ 'allOf' => Applicator::AllOf,
27
+ 'anyOf' => Applicator::AnyOf,
28
+ 'oneOf' => Applicator::OneOf,
29
+ 'not' => Applicator::Not,
30
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10.2.2
31
+ 'if' => Applicator::If,
32
+ 'then' => Applicator::Then,
33
+ 'else' => Applicator::Else,
34
+ 'dependentSchemas' => Applicator::DependentSchemas,
35
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10.3
36
+ 'prefixItems' => Applicator::PrefixItems,
37
+ 'items' => Applicator::Items,
38
+ 'contains' => Applicator::Contains,
39
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10.3.2
40
+ 'properties' => Applicator::Properties,
41
+ 'patternProperties' => Applicator::PatternProperties,
42
+ 'additionalProperties' => Applicator::AdditionalProperties,
43
+ 'propertyNames' => Applicator::PropertyNames,
44
+ 'dependencies' => Applicator::Dependencies
45
+ }
46
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-11
47
+ UNEVALUATED = {
48
+ 'unevaluatedItems' => Unevaluated::UnevaluatedItems,
49
+ 'unevaluatedProperties' => Unevaluated::UnevaluatedProperties
50
+ }
51
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6
52
+ VALIDATION = {
53
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.1
54
+ 'type' => Validation::Type,
55
+ 'enum' => Validation::Enum,
56
+ 'const' => Validation::Const,
57
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.2
58
+ 'multipleOf' => Validation::MultipleOf,
59
+ 'maximum' => Validation::Maximum,
60
+ 'exclusiveMaximum' => Validation::ExclusiveMaximum,
61
+ 'minimum' => Validation::Minimum,
62
+ 'exclusiveMinimum' => Validation::ExclusiveMinimum,
63
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.3
64
+ 'maxLength' => Validation::MaxLength,
65
+ 'minLength' => Validation::MinLength,
66
+ 'pattern' => Validation::Pattern,
67
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.4
68
+ 'maxItems' => Validation::MaxItems,
69
+ 'minItems' => Validation::MinItems,
70
+ 'uniqueItems' => Validation::UniqueItems,
71
+ 'maxContains' => Validation::MaxContains,
72
+ 'minContains' => Validation::MinContains,
73
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.5
74
+ 'maxProperties' => Validation::MaxProperties,
75
+ 'minProperties' => Validation::MinProperties,
76
+ 'required' => Validation::Required,
77
+ 'dependentRequired' => Validation::DependentRequired
78
+ }
79
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.2.1
80
+ FORMAT_ANNOTATION = {
81
+ 'format' => FormatAnnotation::Format
82
+ }
83
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.2.2
84
+ FORMAT_ASSERTION = {
85
+ 'format' => FormatAssertion::Format
86
+ }
87
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-8
88
+ CONTENT = {
89
+ 'contentEncoding' => Content::ContentEncoding,
90
+ 'contentMediaType' => Content::ContentMediaType,
91
+ 'contentSchema' => Content::ContentSchema
92
+ }
93
+ # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-9
94
+ META_DATA = {
95
+ # 'title' => MetaData::Title,
96
+ # 'description' => MetaData::Description,
97
+ # 'default' => MetaData::Default,
98
+ # 'deprecated' => MetaData::Deprecated,
99
+ 'readOnly' => MetaData::ReadOnly,
100
+ 'writeOnly' => MetaData::WriteOnly,
101
+ # 'examples' => MetaData::Examples
102
+ }
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ module Draft4
4
+ BASE_URI = URI('http://json-schema.org/draft-04/schema#')
5
+ FORMATS = Draft6::FORMATS.dup
6
+ FORMATS.delete('uri-reference')
7
+ FORMATS.delete('uri-template')
8
+ FORMATS.delete('json-pointer')
9
+ CONTENT_ENCODINGS = Draft6::CONTENT_ENCODINGS
10
+ CONTENT_MEDIA_TYPES = Draft6::CONTENT_MEDIA_TYPES
11
+ SCHEMA = {
12
+ 'id' => 'http://json-schema.org/draft-04/schema#',
13
+ '$schema' => 'http://json-schema.org/draft-04/schema#',
14
+ 'description' => 'Core schema meta-schema',
15
+ 'definitions' => {
16
+ 'schemaArray' => {
17
+ 'type' => 'array',
18
+ 'minItems' => 1,
19
+ 'items' => { '$ref' => '#' }
20
+ },
21
+ 'positiveInteger' => {
22
+ 'type' => 'integer',
23
+ 'minimum' => 0
24
+ },
25
+ 'positiveIntegerDefault0' => {
26
+ 'allOf' => [ { '$ref' => '#/definitions/positiveInteger' }, { 'default' => 0 } ]
27
+ },
28
+ 'simpleTypes' => {
29
+ 'enum' => [ 'array', 'boolean', 'integer', 'null', 'number', 'object', 'string' ]
30
+ },
31
+ 'stringArray' => {
32
+ 'type' => 'array',
33
+ 'items' => { 'type' => 'string' },
34
+ 'minItems' => 1,
35
+ 'uniqueItems' => true
36
+ }
37
+ },
38
+ 'type' => 'object',
39
+ 'properties' => {
40
+ 'id' => {
41
+ 'type' => 'string'
42
+ },
43
+ '$schema' => {
44
+ 'type' => 'string'
45
+ },
46
+ 'title' => {
47
+ 'type' => 'string'
48
+ },
49
+ 'description' => {
50
+ 'type' => 'string'
51
+ },
52
+ 'default' => {},
53
+ 'multipleOf' => {
54
+ 'type' => 'number',
55
+ 'minimum' => 0,
56
+ 'exclusiveMinimum' => true
57
+ },
58
+ 'maximum' => {
59
+ 'type' => 'number'
60
+ },
61
+ 'exclusiveMaximum' => {
62
+ 'type' => 'boolean',
63
+ 'default' => false
64
+ },
65
+ 'minimum' => {
66
+ 'type' => 'number'
67
+ },
68
+ 'exclusiveMinimum' => {
69
+ 'type' => 'boolean',
70
+ 'default' => false
71
+ },
72
+ 'maxLength' => { '$ref' => '#/definitions/positiveInteger' },
73
+ 'minLength' => { '$ref' => '#/definitions/positiveIntegerDefault0' },
74
+ 'pattern' => {
75
+ 'type' => 'string',
76
+ 'format' => 'regex'
77
+ },
78
+ 'additionalItems' => {
79
+ 'anyOf' => [
80
+ { 'type' => 'boolean' },
81
+ { '$ref' => '#' }
82
+ ],
83
+ 'default' => {}
84
+ },
85
+ 'items' => {
86
+ 'anyOf' => [
87
+ { '$ref' => '#' },
88
+ { '$ref' => '#/definitions/schemaArray' }
89
+ ],
90
+ 'default' => {}
91
+ },
92
+ 'maxItems' => { '$ref' => '#/definitions/positiveInteger' },
93
+ 'minItems' => { '$ref' => '#/definitions/positiveIntegerDefault0' },
94
+ 'uniqueItems' => {
95
+ 'type' => 'boolean',
96
+ 'default' => false
97
+ },
98
+ 'maxProperties' => { '$ref' => '#/definitions/positiveInteger' },
99
+ 'minProperties' => { '$ref' => '#/definitions/positiveIntegerDefault0' },
100
+ 'required' => { '$ref' => '#/definitions/stringArray' },
101
+ 'additionalProperties' => {
102
+ 'anyOf' => [
103
+ { 'type' => 'boolean' },
104
+ { '$ref' => '#' }
105
+ ],
106
+ 'default' => {}
107
+ },
108
+ 'definitions' => {
109
+ 'type' => 'object',
110
+ 'additionalProperties' => { '$ref' => '#' },
111
+ 'default' => {}
112
+ },
113
+ 'properties' => {
114
+ 'type' => 'object',
115
+ 'additionalProperties' => { '$ref' => '#' },
116
+ 'default' => {}
117
+ },
118
+ 'patternProperties' => {
119
+ 'type' => 'object',
120
+ 'additionalProperties' => { '$ref' => '#' },
121
+ 'default' => {}
122
+ },
123
+ 'dependencies' => {
124
+ 'type' => 'object',
125
+ 'additionalProperties' => {
126
+ 'anyOf' => [
127
+ { '$ref' => '#' },
128
+ { '$ref' => '#/definitions/stringArray' }
129
+ ]
130
+ }
131
+ },
132
+ 'enum' => {
133
+ 'type' => 'array',
134
+ 'minItems' => 1,
135
+ 'uniqueItems' => true
136
+ },
137
+ 'type' => {
138
+ 'anyOf' => [
139
+ { '$ref' => '#/definitions/simpleTypes' },
140
+ {
141
+ 'type' => 'array',
142
+ 'items' => { '$ref' => '#/definitions/simpleTypes' },
143
+ 'minItems' => 1,
144
+ 'uniqueItems' => true
145
+ }
146
+ ]
147
+ },
148
+ 'format' => { 'type' => 'string' },
149
+ 'allOf' => { '$ref' => '#/definitions/schemaArray' },
150
+ 'anyOf' => { '$ref' => '#/definitions/schemaArray' },
151
+ 'oneOf' => { '$ref' => '#/definitions/schemaArray' },
152
+ 'not' => { '$ref' => '#' }
153
+ },
154
+ 'dependencies' => {
155
+ 'exclusiveMaximum' => [ 'maximum' ],
156
+ 'exclusiveMinimum' => [ 'minimum' ]
157
+ },
158
+ 'default' => {}
159
+ }
160
+ end
161
+ end