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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +1 -6
- data/CHANGELOG.md +25 -0
- data/Gemfile.lock +1 -1
- data/README.md +137 -14
- data/json_schemer.gemspec +1 -1
- data/lib/json_schemer/draft201909/meta.rb +335 -0
- data/lib/json_schemer/draft201909/vocab/applicator.rb +104 -0
- data/lib/json_schemer/draft201909/vocab/core.rb +45 -0
- data/lib/json_schemer/draft201909/vocab.rb +31 -0
- data/lib/json_schemer/draft202012/meta.rb +361 -0
- data/lib/json_schemer/draft202012/vocab/applicator.rb +382 -0
- data/lib/json_schemer/draft202012/vocab/content.rb +44 -0
- data/lib/json_schemer/draft202012/vocab/core.rb +154 -0
- data/lib/json_schemer/draft202012/vocab/format_annotation.rb +31 -0
- data/lib/json_schemer/draft202012/vocab/format_assertion.rb +29 -0
- data/lib/json_schemer/draft202012/vocab/meta_data.rb +30 -0
- data/lib/json_schemer/draft202012/vocab/unevaluated.rb +94 -0
- data/lib/json_schemer/draft202012/vocab/validation.rb +286 -0
- data/lib/json_schemer/draft202012/vocab.rb +103 -0
- data/lib/json_schemer/draft4/meta.rb +155 -0
- data/lib/json_schemer/draft4/vocab/validation.rb +39 -0
- data/lib/json_schemer/draft4/vocab.rb +18 -0
- data/lib/json_schemer/draft6/meta.rb +161 -0
- data/lib/json_schemer/draft6/vocab.rb +16 -0
- data/lib/json_schemer/draft7/meta.rb +178 -0
- data/lib/json_schemer/draft7/vocab/validation.rb +69 -0
- data/lib/json_schemer/draft7/vocab.rb +30 -0
- data/lib/json_schemer/errors.rb +1 -0
- data/lib/json_schemer/format/duration.rb +23 -0
- data/lib/json_schemer/format/json_pointer.rb +18 -0
- data/lib/json_schemer/format.rb +52 -26
- data/lib/json_schemer/keyword.rb +41 -0
- data/lib/json_schemer/location.rb +25 -0
- data/lib/json_schemer/openapi.rb +40 -0
- data/lib/json_schemer/openapi30/document.rb +1673 -0
- data/lib/json_schemer/openapi30/meta.rb +26 -0
- data/lib/json_schemer/openapi30/vocab/base.rb +18 -0
- data/lib/json_schemer/openapi30/vocab.rb +12 -0
- data/lib/json_schemer/openapi31/document.rb +1559 -0
- data/lib/json_schemer/openapi31/meta.rb +128 -0
- data/lib/json_schemer/openapi31/vocab/base.rb +89 -0
- data/lib/json_schemer/openapi31/vocab.rb +18 -0
- data/lib/json_schemer/output.rb +55 -0
- data/lib/json_schemer/result.rb +168 -0
- data/lib/json_schemer/schema.rb +390 -0
- data/lib/json_schemer/version.rb +1 -1
- data/lib/json_schemer.rb +197 -24
- metadata +42 -10
- data/lib/json_schemer/schema/base.rb +0 -677
- data/lib/json_schemer/schema/draft4.json +0 -149
- data/lib/json_schemer/schema/draft4.rb +0 -44
- data/lib/json_schemer/schema/draft6.json +0 -155
- data/lib/json_schemer/schema/draft6.rb +0 -25
- data/lib/json_schemer/schema/draft7.json +0 -172
- data/lib/json_schemer/schema/draft7.rb +0 -32
@@ -0,0 +1,94 @@
|
|
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 parse
|
12
|
+
subschema(value)
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate(instance, instance_location, keyword_location, context)
|
16
|
+
return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array)
|
17
|
+
|
18
|
+
unevaluated_items = instance.size.times.to_set
|
19
|
+
|
20
|
+
context.adjacent_results.each_value do |adjacent_result|
|
21
|
+
collect_unevaluated_items(adjacent_result, instance_location, unevaluated_items)
|
22
|
+
end
|
23
|
+
|
24
|
+
nested = unevaluated_items.map do |index|
|
25
|
+
parsed.validate_instance(instance.fetch(index), join_location(instance_location, index.to_s), keyword_location, context)
|
26
|
+
end
|
27
|
+
|
28
|
+
result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => nested.any?)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def collect_unevaluated_items(result, instance_location, unevaluated_items)
|
34
|
+
return unless result.valid && result.instance_location == instance_location
|
35
|
+
case result.source
|
36
|
+
when Applicator::PrefixItems
|
37
|
+
unevaluated_items.subtract(0..result.annotation)
|
38
|
+
when Applicator::Items, UnevaluatedItems
|
39
|
+
unevaluated_items.clear if result.annotation
|
40
|
+
when Applicator::Contains
|
41
|
+
unevaluated_items.subtract(result.annotation)
|
42
|
+
end
|
43
|
+
result.nested&.each do |subresult|
|
44
|
+
collect_unevaluated_items(subresult, instance_location, unevaluated_items)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class UnevaluatedProperties < Keyword
|
50
|
+
def error(formatted_instance_location:, **)
|
51
|
+
"object properties at #{formatted_instance_location} do not match `unevaluatedProperties` schema"
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse
|
55
|
+
subschema(value)
|
56
|
+
end
|
57
|
+
|
58
|
+
def validate(instance, instance_location, keyword_location, context)
|
59
|
+
return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash)
|
60
|
+
|
61
|
+
evaluated_keys = Set[]
|
62
|
+
|
63
|
+
context.adjacent_results.each_value do |adjacent_result|
|
64
|
+
collect_evaluated_keys(adjacent_result, instance_location, evaluated_keys)
|
65
|
+
end
|
66
|
+
|
67
|
+
evaluated = instance.reject do |key, _value|
|
68
|
+
evaluated_keys.include?(key)
|
69
|
+
end
|
70
|
+
|
71
|
+
nested = evaluated.map do |key, value|
|
72
|
+
parsed.validate_instance(value, join_location(instance_location, key), keyword_location, context)
|
73
|
+
end
|
74
|
+
|
75
|
+
result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => evaluated.keys)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def collect_evaluated_keys(result, instance_location, evaluated_keys)
|
81
|
+
return unless result.valid && result.instance_location == instance_location
|
82
|
+
case result.source
|
83
|
+
when Applicator::Properties, Applicator::PatternProperties, Applicator::AdditionalProperties, UnevaluatedProperties
|
84
|
+
evaluated_keys.merge(result.annotation)
|
85
|
+
end
|
86
|
+
result.nested&.each do |subresult|
|
87
|
+
collect_evaluated_keys(subresult, instance_location, evaluated_keys)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
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,103 @@
|
|
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
|
+
}
|
21
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10
|
22
|
+
APPLICATOR = {
|
23
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10.2
|
24
|
+
'allOf' => Applicator::AllOf,
|
25
|
+
'anyOf' => Applicator::AnyOf,
|
26
|
+
'oneOf' => Applicator::OneOf,
|
27
|
+
'not' => Applicator::Not,
|
28
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10.2.2
|
29
|
+
'if' => Applicator::If,
|
30
|
+
'then' => Applicator::Then,
|
31
|
+
'else' => Applicator::Else,
|
32
|
+
'dependentSchemas' => Applicator::DependentSchemas,
|
33
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10.3
|
34
|
+
'prefixItems' => Applicator::PrefixItems,
|
35
|
+
'items' => Applicator::Items,
|
36
|
+
'contains' => Applicator::Contains,
|
37
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10.3.2
|
38
|
+
'properties' => Applicator::Properties,
|
39
|
+
'patternProperties' => Applicator::PatternProperties,
|
40
|
+
'additionalProperties' => Applicator::AdditionalProperties,
|
41
|
+
'propertyNames' => Applicator::PropertyNames,
|
42
|
+
'dependencies' => Applicator::Dependencies
|
43
|
+
}
|
44
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-11
|
45
|
+
UNEVALUATED = {
|
46
|
+
'unevaluatedItems' => Unevaluated::UnevaluatedItems,
|
47
|
+
'unevaluatedProperties' => Unevaluated::UnevaluatedProperties
|
48
|
+
}
|
49
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6
|
50
|
+
VALIDATION = {
|
51
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.1
|
52
|
+
'type' => Validation::Type,
|
53
|
+
'enum' => Validation::Enum,
|
54
|
+
'const' => Validation::Const,
|
55
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.2
|
56
|
+
'multipleOf' => Validation::MultipleOf,
|
57
|
+
'maximum' => Validation::Maximum,
|
58
|
+
'exclusiveMaximum' => Validation::ExclusiveMaximum,
|
59
|
+
'minimum' => Validation::Minimum,
|
60
|
+
'exclusiveMinimum' => Validation::ExclusiveMinimum,
|
61
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.3
|
62
|
+
'maxLength' => Validation::MaxLength,
|
63
|
+
'minLength' => Validation::MinLength,
|
64
|
+
'pattern' => Validation::Pattern,
|
65
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.4
|
66
|
+
'maxItems' => Validation::MaxItems,
|
67
|
+
'minItems' => Validation::MinItems,
|
68
|
+
'uniqueItems' => Validation::UniqueItems,
|
69
|
+
'maxContains' => Validation::MaxContains,
|
70
|
+
'minContains' => Validation::MinContains,
|
71
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.5
|
72
|
+
'maxProperties' => Validation::MaxProperties,
|
73
|
+
'minProperties' => Validation::MinProperties,
|
74
|
+
'required' => Validation::Required,
|
75
|
+
'dependentRequired' => Validation::DependentRequired
|
76
|
+
}
|
77
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.2.1
|
78
|
+
FORMAT_ANNOTATION = {
|
79
|
+
'format' => FormatAnnotation::Format
|
80
|
+
}
|
81
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.2.2
|
82
|
+
FORMAT_ASSERTION = {
|
83
|
+
'format' => FormatAssertion::Format
|
84
|
+
}
|
85
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-8
|
86
|
+
CONTENT = {
|
87
|
+
'contentEncoding' => Content::ContentEncoding,
|
88
|
+
'contentMediaType' => Content::ContentMediaType,
|
89
|
+
'contentSchema' => Content::ContentSchema
|
90
|
+
}
|
91
|
+
# https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-9
|
92
|
+
META_DATA = {
|
93
|
+
# 'title' => MetaData::Title,
|
94
|
+
# 'description' => MetaData::Description,
|
95
|
+
# 'default' => MetaData::Default,
|
96
|
+
# 'deprecated' => MetaData::Deprecated,
|
97
|
+
'readOnly' => MetaData::ReadOnly,
|
98
|
+
'writeOnly' => MetaData::WriteOnly,
|
99
|
+
# 'examples' => MetaData::Examples
|
100
|
+
}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module JSONSchemer
|
3
|
+
module Draft4
|
4
|
+
BASE_URI = URI('http://json-schema.org/draft-04/schema#')
|
5
|
+
SCHEMA = {
|
6
|
+
'id' => 'http://json-schema.org/draft-04/schema#',
|
7
|
+
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
8
|
+
'description' => 'Core schema meta-schema',
|
9
|
+
'definitions' => {
|
10
|
+
'schemaArray' => {
|
11
|
+
'type' => 'array',
|
12
|
+
'minItems' => 1,
|
13
|
+
'items' => { '$ref' => '#' }
|
14
|
+
},
|
15
|
+
'positiveInteger' => {
|
16
|
+
'type' => 'integer',
|
17
|
+
'minimum' => 0
|
18
|
+
},
|
19
|
+
'positiveIntegerDefault0' => {
|
20
|
+
'allOf' => [ { '$ref' => '#/definitions/positiveInteger' }, { 'default' => 0 } ]
|
21
|
+
},
|
22
|
+
'simpleTypes' => {
|
23
|
+
'enum' => [ 'array', 'boolean', 'integer', 'null', 'number', 'object', 'string' ]
|
24
|
+
},
|
25
|
+
'stringArray' => {
|
26
|
+
'type' => 'array',
|
27
|
+
'items' => { 'type' => 'string' },
|
28
|
+
'minItems' => 1,
|
29
|
+
'uniqueItems' => true
|
30
|
+
}
|
31
|
+
},
|
32
|
+
'type' => 'object',
|
33
|
+
'properties' => {
|
34
|
+
'id' => {
|
35
|
+
'type' => 'string'
|
36
|
+
},
|
37
|
+
'$schema' => {
|
38
|
+
'type' => 'string'
|
39
|
+
},
|
40
|
+
'title' => {
|
41
|
+
'type' => 'string'
|
42
|
+
},
|
43
|
+
'description' => {
|
44
|
+
'type' => 'string'
|
45
|
+
},
|
46
|
+
'default' => {},
|
47
|
+
'multipleOf' => {
|
48
|
+
'type' => 'number',
|
49
|
+
'minimum' => 0,
|
50
|
+
'exclusiveMinimum' => true
|
51
|
+
},
|
52
|
+
'maximum' => {
|
53
|
+
'type' => 'number'
|
54
|
+
},
|
55
|
+
'exclusiveMaximum' => {
|
56
|
+
'type' => 'boolean',
|
57
|
+
'default' => false
|
58
|
+
},
|
59
|
+
'minimum' => {
|
60
|
+
'type' => 'number'
|
61
|
+
},
|
62
|
+
'exclusiveMinimum' => {
|
63
|
+
'type' => 'boolean',
|
64
|
+
'default' => false
|
65
|
+
},
|
66
|
+
'maxLength' => { '$ref' => '#/definitions/positiveInteger' },
|
67
|
+
'minLength' => { '$ref' => '#/definitions/positiveIntegerDefault0' },
|
68
|
+
'pattern' => {
|
69
|
+
'type' => 'string',
|
70
|
+
'format' => 'regex'
|
71
|
+
},
|
72
|
+
'additionalItems' => {
|
73
|
+
'anyOf' => [
|
74
|
+
{ 'type' => 'boolean' },
|
75
|
+
{ '$ref' => '#' }
|
76
|
+
],
|
77
|
+
'default' => {}
|
78
|
+
},
|
79
|
+
'items' => {
|
80
|
+
'anyOf' => [
|
81
|
+
{ '$ref' => '#' },
|
82
|
+
{ '$ref' => '#/definitions/schemaArray' }
|
83
|
+
],
|
84
|
+
'default' => {}
|
85
|
+
},
|
86
|
+
'maxItems' => { '$ref' => '#/definitions/positiveInteger' },
|
87
|
+
'minItems' => { '$ref' => '#/definitions/positiveIntegerDefault0' },
|
88
|
+
'uniqueItems' => {
|
89
|
+
'type' => 'boolean',
|
90
|
+
'default' => false
|
91
|
+
},
|
92
|
+
'maxProperties' => { '$ref' => '#/definitions/positiveInteger' },
|
93
|
+
'minProperties' => { '$ref' => '#/definitions/positiveIntegerDefault0' },
|
94
|
+
'required' => { '$ref' => '#/definitions/stringArray' },
|
95
|
+
'additionalProperties' => {
|
96
|
+
'anyOf' => [
|
97
|
+
{ 'type' => 'boolean' },
|
98
|
+
{ '$ref' => '#' }
|
99
|
+
],
|
100
|
+
'default' => {}
|
101
|
+
},
|
102
|
+
'definitions' => {
|
103
|
+
'type' => 'object',
|
104
|
+
'additionalProperties' => { '$ref' => '#' },
|
105
|
+
'default' => {}
|
106
|
+
},
|
107
|
+
'properties' => {
|
108
|
+
'type' => 'object',
|
109
|
+
'additionalProperties' => { '$ref' => '#' },
|
110
|
+
'default' => {}
|
111
|
+
},
|
112
|
+
'patternProperties' => {
|
113
|
+
'type' => 'object',
|
114
|
+
'additionalProperties' => { '$ref' => '#' },
|
115
|
+
'default' => {}
|
116
|
+
},
|
117
|
+
'dependencies' => {
|
118
|
+
'type' => 'object',
|
119
|
+
'additionalProperties' => {
|
120
|
+
'anyOf' => [
|
121
|
+
{ '$ref' => '#' },
|
122
|
+
{ '$ref' => '#/definitions/stringArray' }
|
123
|
+
]
|
124
|
+
}
|
125
|
+
},
|
126
|
+
'enum' => {
|
127
|
+
'type' => 'array',
|
128
|
+
'minItems' => 1,
|
129
|
+
'uniqueItems' => true
|
130
|
+
},
|
131
|
+
'type' => {
|
132
|
+
'anyOf' => [
|
133
|
+
{ '$ref' => '#/definitions/simpleTypes' },
|
134
|
+
{
|
135
|
+
'type' => 'array',
|
136
|
+
'items' => { '$ref' => '#/definitions/simpleTypes' },
|
137
|
+
'minItems' => 1,
|
138
|
+
'uniqueItems' => true
|
139
|
+
}
|
140
|
+
]
|
141
|
+
},
|
142
|
+
'format' => { 'type' => 'string' },
|
143
|
+
'allOf' => { '$ref' => '#/definitions/schemaArray' },
|
144
|
+
'anyOf' => { '$ref' => '#/definitions/schemaArray' },
|
145
|
+
'oneOf' => { '$ref' => '#/definitions/schemaArray' },
|
146
|
+
'not' => { '$ref' => '#' }
|
147
|
+
},
|
148
|
+
'dependencies' => {
|
149
|
+
'exclusiveMaximum' => [ 'maximum' ],
|
150
|
+
'exclusiveMinimum' => [ 'minimum' ]
|
151
|
+
},
|
152
|
+
'default' => {}
|
153
|
+
}
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module JSONSchemer
|
3
|
+
module Draft4
|
4
|
+
module Vocab
|
5
|
+
module Validation
|
6
|
+
class Type < Draft202012::Vocab::Validation::Type
|
7
|
+
private
|
8
|
+
def valid_type(type, instance)
|
9
|
+
type == 'integer' ? instance.is_a?(Integer) : super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class ExclusiveMaximum < Keyword
|
14
|
+
def error(formatted_instance_location:, **)
|
15
|
+
"number at #{formatted_instance_location} is greater than or equal to `maximum`"
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate(instance, instance_location, keyword_location, _context)
|
19
|
+
maximum = schema.parsed.fetch('maximum').parsed
|
20
|
+
valid = !instance.is_a?(Numeric) || !value || !maximum || instance < maximum
|
21
|
+
result(instance, instance_location, keyword_location, valid)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class ExclusiveMinimum < Keyword
|
26
|
+
def error(formatted_instance_location:, **)
|
27
|
+
"number at #{formatted_instance_location} is less than or equal to `minimum`"
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate(instance, instance_location, keyword_location, _context)
|
31
|
+
minimum = schema.parsed.fetch('minimum').parsed
|
32
|
+
valid = !instance.is_a?(Numeric) || !value || !minimum || instance > minimum
|
33
|
+
result(instance, instance_location, keyword_location, valid)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|