json_schemer 1.0.3 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -7
  3. data/CHANGELOG.md +42 -0
  4. data/Gemfile.lock +10 -3
  5. data/README.md +328 -14
  6. data/json_schemer.gemspec +3 -1
  7. data/lib/json_schemer/content.rb +18 -0
  8. data/lib/json_schemer/draft201909/meta.rb +320 -0
  9. data/lib/json_schemer/draft201909/vocab/applicator.rb +104 -0
  10. data/lib/json_schemer/draft201909/vocab/core.rb +45 -0
  11. data/lib/json_schemer/draft201909/vocab.rb +31 -0
  12. data/lib/json_schemer/draft202012/meta.rb +364 -0
  13. data/lib/json_schemer/draft202012/vocab/applicator.rb +382 -0
  14. data/lib/json_schemer/draft202012/vocab/content.rb +52 -0
  15. data/lib/json_schemer/draft202012/vocab/core.rb +160 -0
  16. data/lib/json_schemer/draft202012/vocab/format_annotation.rb +23 -0
  17. data/lib/json_schemer/draft202012/vocab/format_assertion.rb +23 -0
  18. data/lib/json_schemer/draft202012/vocab/meta_data.rb +30 -0
  19. data/lib/json_schemer/draft202012/vocab/unevaluated.rb +94 -0
  20. data/lib/json_schemer/draft202012/vocab/validation.rb +286 -0
  21. data/lib/json_schemer/draft202012/vocab.rb +105 -0
  22. data/lib/json_schemer/draft4/meta.rb +161 -0
  23. data/lib/json_schemer/draft4/vocab/validation.rb +39 -0
  24. data/lib/json_schemer/draft4/vocab.rb +18 -0
  25. data/lib/json_schemer/draft6/meta.rb +172 -0
  26. data/lib/json_schemer/draft6/vocab.rb +16 -0
  27. data/lib/json_schemer/draft7/meta.rb +183 -0
  28. data/lib/json_schemer/draft7/vocab/validation.rb +69 -0
  29. data/lib/json_schemer/draft7/vocab.rb +30 -0
  30. data/lib/json_schemer/errors.rb +1 -0
  31. data/lib/json_schemer/format/duration.rb +23 -0
  32. data/lib/json_schemer/format/json_pointer.rb +18 -0
  33. data/lib/json_schemer/format.rb +128 -106
  34. data/lib/json_schemer/keyword.rb +45 -0
  35. data/lib/json_schemer/location.rb +25 -0
  36. data/lib/json_schemer/openapi.rb +40 -0
  37. data/lib/json_schemer/openapi30/document.rb +1673 -0
  38. data/lib/json_schemer/openapi30/meta.rb +32 -0
  39. data/lib/json_schemer/openapi30/vocab/base.rb +18 -0
  40. data/lib/json_schemer/openapi30/vocab.rb +12 -0
  41. data/lib/json_schemer/openapi31/document.rb +1559 -0
  42. data/lib/json_schemer/openapi31/meta.rb +136 -0
  43. data/lib/json_schemer/openapi31/vocab/base.rb +127 -0
  44. data/lib/json_schemer/openapi31/vocab.rb +18 -0
  45. data/lib/json_schemer/output.rb +56 -0
  46. data/lib/json_schemer/result.rb +229 -0
  47. data/lib/json_schemer/schema.rb +424 -0
  48. data/lib/json_schemer/version.rb +1 -1
  49. data/lib/json_schemer.rb +198 -24
  50. metadata +71 -10
  51. data/lib/json_schemer/schema/base.rb +0 -677
  52. data/lib/json_schemer/schema/draft4.json +0 -149
  53. data/lib/json_schemer/schema/draft4.rb +0 -44
  54. data/lib/json_schemer/schema/draft6.json +0 -155
  55. data/lib/json_schemer/schema/draft6.rb +0 -25
  56. data/lib/json_schemer/schema/draft7.json +0 -172
  57. 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,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
@@ -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