json_schemer 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +10 -0
- data/lib/json_schemer.rb +34 -4
- data/lib/json_schemer/cached_ref_resolver.rb +15 -0
- data/lib/json_schemer/schema/base.rb +222 -106
- data/lib/json_schemer/schema/draft4.rb +8 -8
- data/lib/json_schemer/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8dc3f244ec43815a4207b9be2112cc76afee3cd37b96a3e1eb0fd9b189f8ad72
|
4
|
+
data.tar.gz: 3dcede7d0418d30e98fe75054000657bf03201a1fbf8231810506a2a91de1bbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ea22d403dd91c7c555d7fe949a25840255a1ed55af658aad703b965b79e063fe2ae5a1d82b3b3ac7a6a56cc32b5720ec0639d734235e934ccaef04f87a11146
|
7
|
+
data.tar.gz: 243e04ef109d923d9de753856943f2269548485de36f476e9d527be132c1f737abcd1ee6e74c6b3263bb95d9b03fcfc0a6f8a11c4249d538baab0d469c97652d
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -46,6 +46,16 @@ schemer.valid?({ 'abc' => 10 })
|
|
46
46
|
|
47
47
|
schemer.validate({ 'abc' => 10 }).to_a
|
48
48
|
# => [{"data"=>10, "schema"=>{"type"=>"integer", "minimum"=>11}, "pointer"=>"#/abc", "type"=>"minimum"}]
|
49
|
+
|
50
|
+
# schema files
|
51
|
+
|
52
|
+
schema = Pathname.new('/path/to/schema.json')
|
53
|
+
schemer = JSONSchemer.schema(schema)
|
54
|
+
|
55
|
+
# schema json string
|
56
|
+
|
57
|
+
schema = '{ "type": "integer" }'
|
58
|
+
schemer = JSONSchemer.schema(schema)
|
49
59
|
```
|
50
60
|
|
51
61
|
## Options
|
data/lib/json_schemer.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'json_schemer/version'
|
4
4
|
require 'json_schemer/format'
|
5
|
+
require 'json_schemer/cached_ref_resolver'
|
5
6
|
require 'json_schemer/schema/base'
|
6
7
|
require 'json_schemer/schema/draft4'
|
7
8
|
require 'json_schemer/schema/draft6'
|
@@ -10,6 +11,8 @@ require 'json_schemer/schema/draft7'
|
|
10
11
|
module JSONSchemer
|
11
12
|
class UnsupportedMetaSchema < StandardError; end
|
12
13
|
class UnknownRef < StandardError; end
|
14
|
+
class InvalidFileURI < StandardError; end
|
15
|
+
class InvalidSymbolKey < StandardError; end
|
13
16
|
|
14
17
|
DRAFT_CLASS_BY_META_SCHEMA = {
|
15
18
|
'http://json-schema.org/draft-04/schema#' => Schema::Draft4,
|
@@ -19,9 +22,36 @@ module JSONSchemer
|
|
19
22
|
|
20
23
|
DEFAULT_META_SCHEMA = 'http://json-schema.org/draft-07/schema#'
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
25
|
+
FILE_URI_REF_RESOLVER = proc do |uri|
|
26
|
+
raise InvalidFileURI, 'must use `file` scheme' unless uri.scheme == 'file'
|
27
|
+
raise InvalidFileURI, 'cannot have a host (use `file:///`)' if uri.host
|
28
|
+
JSON.parse(File.read(uri.path))
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
def schema(schema, **options)
|
33
|
+
case schema
|
34
|
+
when String
|
35
|
+
schema = JSON.parse(schema)
|
36
|
+
when Pathname
|
37
|
+
uri = URI.parse("file://#{schema.realpath}")
|
38
|
+
if options.key?(:ref_resolver)
|
39
|
+
schema = FILE_URI_REF_RESOLVER.call(uri)
|
40
|
+
else
|
41
|
+
ref_resolver = CachedRefResolver.new(&FILE_URI_REF_RESOLVER)
|
42
|
+
schema = ref_resolver.call(uri)
|
43
|
+
options[:ref_resolver] = ref_resolver
|
44
|
+
end
|
45
|
+
schema[draft_class(schema)::ID_KEYWORD] ||= uri.to_s
|
46
|
+
end
|
47
|
+
draft_class(schema).new(schema, **options)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def draft_class(schema)
|
53
|
+
meta_schema = schema.is_a?(Hash) && schema.key?('$schema') ? schema['$schema'] : DEFAULT_META_SCHEMA
|
54
|
+
DRAFT_CLASS_BY_META_SCHEMA[meta_schema] || raise(UnsupportedMetaSchema, meta_schema)
|
55
|
+
end
|
26
56
|
end
|
27
57
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONSchemer
|
4
|
+
class CachedRefResolver
|
5
|
+
def initialize(&ref_resolver)
|
6
|
+
@ref_resolver = ref_resolver
|
7
|
+
@cache = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(uri)
|
11
|
+
@cache[uri] = @ref_resolver.call(uri) unless @cache.key?(uri)
|
12
|
+
@cache[uri]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -12,6 +12,18 @@ module JSONSchemer
|
|
12
12
|
class Base
|
13
13
|
include Format
|
14
14
|
|
15
|
+
Instance = Struct.new(:data, :data_pointer, :schema, :schema_pointer, :parent_uri) do
|
16
|
+
def merge(
|
17
|
+
data: self.data,
|
18
|
+
data_pointer: self.data_pointer,
|
19
|
+
schema: self.schema,
|
20
|
+
schema_pointer: self.schema_pointer,
|
21
|
+
parent_uri: self.parent_uri
|
22
|
+
)
|
23
|
+
self.class.new(data, data_pointer, schema, schema_pointer, parent_uri)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
15
27
|
ID_KEYWORD = '$id'
|
16
28
|
DEFAULT_REF_RESOLVER = proc { |uri| raise UnknownRef, uri.to_s }.freeze
|
17
29
|
NET_HTTP_REF_RESOLVER = proc { |uri| JSON.parse(Net::HTTP.get(uri)) }.freeze
|
@@ -24,23 +36,36 @@ module JSONSchemer
|
|
24
36
|
keywords: nil,
|
25
37
|
ref_resolver: DEFAULT_REF_RESOLVER
|
26
38
|
)
|
39
|
+
raise InvalidSymbolKey, 'schemas must use string keys' if schema.is_a?(Hash) && schema.first.first.is_a?(Symbol)
|
27
40
|
@root = schema
|
28
41
|
@format = format
|
29
42
|
@formats = formats
|
30
43
|
@keywords = keywords
|
31
|
-
@ref_resolver = ref_resolver == 'net/http' ? NET_HTTP_REF_RESOLVER : ref_resolver
|
44
|
+
@ref_resolver = ref_resolver == 'net/http' ? CachedRefResolver.new(&NET_HTTP_REF_RESOLVER) : ref_resolver
|
32
45
|
end
|
33
46
|
|
34
|
-
def valid?(data
|
35
|
-
|
47
|
+
def valid?(data)
|
48
|
+
valid_instance?(Instance.new(data, '', root, '', nil))
|
36
49
|
end
|
37
50
|
|
38
|
-
def validate(data
|
39
|
-
|
51
|
+
def validate(data)
|
52
|
+
validate_instance(Instance.new(data, '', root, '', nil))
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def valid_instance?(instance)
|
58
|
+
validate_instance(instance).none?
|
59
|
+
end
|
60
|
+
|
61
|
+
def validate_instance(instance)
|
62
|
+
return enum_for(:validate_instance, instance) unless block_given?
|
63
|
+
|
64
|
+
schema = instance.schema
|
40
65
|
|
41
66
|
return if schema == true
|
42
67
|
if schema == false
|
43
|
-
yield error(
|
68
|
+
yield error(instance, 'schema')
|
44
69
|
return
|
45
70
|
end
|
46
71
|
|
@@ -59,60 +84,60 @@ module JSONSchemer
|
|
59
84
|
ref = schema['$ref']
|
60
85
|
id = schema[id_keyword]
|
61
86
|
|
62
|
-
parent_uri = join_uri(parent_uri, id)
|
87
|
+
instance.parent_uri = join_uri(instance.parent_uri, id)
|
63
88
|
|
64
89
|
if ref
|
65
|
-
validate_ref(
|
90
|
+
validate_ref(instance, ref, &Proc.new)
|
66
91
|
return
|
67
92
|
end
|
68
93
|
|
69
94
|
if format? && custom_format?(format)
|
70
|
-
validate_custom_format(
|
95
|
+
validate_custom_format(instance, formats.fetch(format), &Proc.new)
|
71
96
|
end
|
72
97
|
|
73
98
|
if keywords
|
74
99
|
keywords.each do |keyword, callable|
|
75
100
|
if schema.key?(keyword)
|
76
|
-
result = callable.call(data, schema, pointer)
|
101
|
+
result = callable.call(data, schema, instance.pointer)
|
77
102
|
if result.is_a?(Array)
|
78
103
|
result.each { |error| yield error }
|
79
104
|
elsif !result
|
80
|
-
yield error(
|
105
|
+
yield error(instance, keyword)
|
81
106
|
end
|
82
107
|
end
|
83
108
|
end
|
84
109
|
end
|
85
110
|
|
86
|
-
|
87
|
-
|
111
|
+
data = instance.data
|
112
|
+
|
113
|
+
yield error(instance, 'enum') if enum && !enum.include?(data)
|
114
|
+
yield error(instance, 'const') if schema.key?('const') && schema['const'] != data
|
88
115
|
|
89
|
-
yield error(
|
90
|
-
yield error(
|
91
|
-
yield error(
|
92
|
-
yield error(
|
116
|
+
yield error(instance, 'allOf') if all_of && !all_of.all? { |subschema| valid_instance?(instance.merge(schema: subschema)) }
|
117
|
+
yield error(instance, 'anyOf') if any_of && !any_of.any? { |subschema| valid_instance?(instance.merge(schema: subschema)) }
|
118
|
+
yield error(instance, 'oneOf') if one_of && !one_of.one? { |subschema| valid_instance?(instance.merge(schema: subschema)) }
|
119
|
+
yield error(instance, 'not') if !not_schema.nil? && valid_instance?(instance.merge(schema: not_schema))
|
93
120
|
|
94
|
-
if if_schema &&
|
95
|
-
yield error(
|
121
|
+
if if_schema && valid_instance?(instance.merge(schema: if_schema))
|
122
|
+
yield error(instance, 'then') if !then_schema.nil? && !valid_instance?(instance.merge(schema: then_schema))
|
96
123
|
elsif if_schema
|
97
|
-
yield error(
|
124
|
+
yield error(instance, 'else') if !else_schema.nil? && !valid_instance?(instance.merge(schema: else_schema))
|
98
125
|
end
|
99
126
|
|
100
127
|
case type
|
101
128
|
when nil
|
102
|
-
validate_class(
|
129
|
+
validate_class(instance, &Proc.new)
|
103
130
|
when String
|
104
|
-
validate_type(
|
131
|
+
validate_type(instance, type, &Proc.new)
|
105
132
|
when Array
|
106
|
-
if valid_type = type.find { |subtype|
|
107
|
-
validate_type(
|
133
|
+
if valid_type = type.find { |subtype| valid_instance?(instance.merge(schema: { 'type' => subtype })) }
|
134
|
+
validate_type(instance, valid_type, &Proc.new)
|
108
135
|
else
|
109
|
-
yield error(
|
136
|
+
yield error(instance, 'type')
|
110
137
|
end
|
111
138
|
end
|
112
139
|
end
|
113
140
|
|
114
|
-
protected
|
115
|
-
|
116
141
|
def ids
|
117
142
|
@ids ||= resolve_ids(root)
|
118
143
|
end
|
@@ -147,125 +172,157 @@ module JSONSchemer
|
|
147
172
|
)
|
148
173
|
end
|
149
174
|
|
150
|
-
def error(
|
175
|
+
def error(instance, type)
|
151
176
|
{
|
152
|
-
'data' => data,
|
153
|
-
'
|
154
|
-
'
|
177
|
+
'data' => instance.data,
|
178
|
+
'data_pointer' => instance.data_pointer,
|
179
|
+
'schema' => instance.schema,
|
180
|
+
'schema_pointer' => instance.schema_pointer,
|
181
|
+
'root_schema' => root,
|
155
182
|
'type' => type,
|
156
183
|
}
|
157
184
|
end
|
158
185
|
|
159
|
-
def validate_class(
|
160
|
-
case data
|
186
|
+
def validate_class(instance)
|
187
|
+
case instance.data
|
161
188
|
when Integer
|
162
|
-
validate_integer(
|
189
|
+
validate_integer(instance, &Proc.new)
|
163
190
|
when Numeric
|
164
|
-
validate_number(
|
191
|
+
validate_number(instance, &Proc.new)
|
165
192
|
when String
|
166
|
-
validate_string(
|
193
|
+
validate_string(instance, &Proc.new)
|
167
194
|
when Array
|
168
|
-
validate_array(
|
195
|
+
validate_array(instance, &Proc.new)
|
169
196
|
when Hash
|
170
|
-
validate_object(
|
197
|
+
validate_object(instance, &Proc.new)
|
171
198
|
end
|
172
199
|
end
|
173
200
|
|
174
|
-
def validate_type(
|
201
|
+
def validate_type(instance, type)
|
175
202
|
case type
|
176
203
|
when 'null'
|
177
|
-
yield error(
|
204
|
+
yield error(instance, 'null') unless instance.data.nil?
|
178
205
|
when 'boolean'
|
179
|
-
yield error(
|
206
|
+
yield error(instance, 'boolean') unless BOOLEANS.include?(instance.data)
|
180
207
|
when 'number'
|
181
|
-
validate_number(
|
208
|
+
validate_number(instance, &Proc.new)
|
182
209
|
when 'integer'
|
183
|
-
validate_integer(
|
210
|
+
validate_integer(instance, &Proc.new)
|
184
211
|
when 'string'
|
185
|
-
validate_string(
|
212
|
+
validate_string(instance, &Proc.new)
|
186
213
|
when 'array'
|
187
|
-
validate_array(
|
214
|
+
validate_array(instance, &Proc.new)
|
188
215
|
when 'object'
|
189
|
-
validate_object(
|
216
|
+
validate_object(instance, &Proc.new)
|
190
217
|
end
|
191
218
|
end
|
192
219
|
|
193
|
-
def validate_ref(
|
194
|
-
ref_uri = join_uri(parent_uri, ref)
|
220
|
+
def validate_ref(instance, ref)
|
221
|
+
ref_uri = join_uri(instance.parent_uri, ref)
|
195
222
|
|
196
223
|
if valid_json_pointer?(ref_uri.fragment)
|
197
|
-
ref_pointer = Hana::Pointer.new(URI.unescape(ref_uri.fragment
|
224
|
+
ref_pointer = Hana::Pointer.new(URI.unescape(ref_uri.fragment))
|
198
225
|
if ref.start_with?('#')
|
199
|
-
|
226
|
+
subinstance = instance.merge(
|
227
|
+
schema: ref_pointer.eval(root),
|
228
|
+
schema_pointer: ref_uri.fragment,
|
229
|
+
parent_uri: pointer_uri(root, ref_pointer)
|
230
|
+
)
|
231
|
+
validate_instance(subinstance, &Proc.new)
|
200
232
|
else
|
201
233
|
ref_root = ref_resolver.call(ref_uri)
|
202
234
|
ref_object = child(ref_root)
|
203
|
-
|
235
|
+
subinstance = instance.merge(
|
236
|
+
schema: ref_pointer.eval(ref_root),
|
237
|
+
schema_pointer: ref_uri.fragment,
|
238
|
+
parent_uri: pointer_uri(ref_root, ref_pointer)
|
239
|
+
)
|
240
|
+
ref_object.validate_instance(subinstance, &Proc.new)
|
204
241
|
end
|
205
|
-
elsif ids
|
206
|
-
|
242
|
+
elsif id = ids[ref_uri.to_s]
|
243
|
+
subinstance = instance.merge(
|
244
|
+
schema: id.fetch(:schema),
|
245
|
+
schema_pointer: id.fetch(:pointer),
|
246
|
+
parent_uri: ref_uri
|
247
|
+
)
|
248
|
+
validate_instance(subinstance, &Proc.new)
|
207
249
|
else
|
208
250
|
ref_root = ref_resolver.call(ref_uri)
|
209
251
|
ref_object = child(ref_root)
|
210
|
-
|
252
|
+
id = ref_object.ids[ref_uri.to_s] || { schema: ref_root, pointer: '' }
|
253
|
+
subinstance = instance.merge(
|
254
|
+
schema: id.fetch(:schema),
|
255
|
+
schema_pointer: id.fetch(:pointer),
|
256
|
+
parent_uri: ref_uri
|
257
|
+
)
|
258
|
+
ref_object.validate_instance(subinstance, &Proc.new)
|
211
259
|
end
|
212
260
|
end
|
213
261
|
|
214
|
-
def validate_custom_format(
|
215
|
-
yield error(
|
262
|
+
def validate_custom_format(instance, custom_format)
|
263
|
+
yield error(instance, 'format') if custom_format != false && !custom_format.call(instance.data, instance.schema)
|
216
264
|
end
|
217
265
|
|
218
|
-
def validate_exclusive_maximum(
|
219
|
-
yield error(
|
266
|
+
def validate_exclusive_maximum(instance, exclusive_maximum, maximum)
|
267
|
+
yield error(instance, 'exclusiveMaximum') if instance.data >= exclusive_maximum
|
220
268
|
end
|
221
269
|
|
222
|
-
def validate_exclusive_minimum(
|
223
|
-
yield error(
|
270
|
+
def validate_exclusive_minimum(instance, exclusive_minimum, minimum)
|
271
|
+
yield error(instance, 'exclusiveMinimum') if instance.data <= exclusive_minimum
|
224
272
|
end
|
225
273
|
|
226
|
-
def validate_numeric(
|
274
|
+
def validate_numeric(instance)
|
275
|
+
schema = instance.schema
|
276
|
+
data = instance.data
|
277
|
+
|
227
278
|
multiple_of = schema['multipleOf']
|
228
279
|
maximum = schema['maximum']
|
229
280
|
exclusive_maximum = schema['exclusiveMaximum']
|
230
281
|
minimum = schema['minimum']
|
231
282
|
exclusive_minimum = schema['exclusiveMinimum']
|
232
283
|
|
233
|
-
yield error(
|
234
|
-
yield error(
|
284
|
+
yield error(instance, 'maximum') if maximum && data > maximum
|
285
|
+
yield error(instance, 'minimum') if minimum && data < minimum
|
235
286
|
|
236
|
-
validate_exclusive_maximum(
|
237
|
-
validate_exclusive_minimum(
|
287
|
+
validate_exclusive_maximum(instance, exclusive_maximum, maximum, &Proc.new) if exclusive_maximum
|
288
|
+
validate_exclusive_minimum(instance, exclusive_minimum, minimum, &Proc.new) if exclusive_minimum
|
238
289
|
|
239
290
|
if multiple_of
|
240
291
|
quotient = data / multiple_of.to_f
|
241
|
-
yield error(
|
292
|
+
yield error(instance, 'multipleOf') unless quotient.floor == quotient
|
242
293
|
end
|
243
294
|
end
|
244
295
|
|
245
|
-
def validate_number(
|
246
|
-
unless data.is_a?(Numeric)
|
247
|
-
yield error(
|
296
|
+
def validate_number(instance)
|
297
|
+
unless instance.data.is_a?(Numeric)
|
298
|
+
yield error(instance, 'number')
|
248
299
|
return
|
249
300
|
end
|
250
301
|
|
251
|
-
validate_numeric(
|
302
|
+
validate_numeric(instance, &Proc.new)
|
252
303
|
end
|
253
304
|
|
254
|
-
def validate_integer(
|
305
|
+
def validate_integer(instance)
|
306
|
+
data = instance.data
|
307
|
+
|
255
308
|
if !data.is_a?(Numeric) || (!data.is_a?(Integer) && data.floor != data)
|
256
|
-
yield error(
|
309
|
+
yield error(instance, 'integer')
|
257
310
|
return
|
258
311
|
end
|
259
312
|
|
260
|
-
validate_numeric(
|
313
|
+
validate_numeric(instance, &Proc.new)
|
261
314
|
end
|
262
315
|
|
263
|
-
def validate_string(
|
316
|
+
def validate_string(instance)
|
317
|
+
data = instance.data
|
318
|
+
|
264
319
|
unless data.is_a?(String)
|
265
|
-
yield error(
|
320
|
+
yield error(instance, 'string')
|
266
321
|
return
|
267
322
|
end
|
268
323
|
|
324
|
+
schema = instance.schema
|
325
|
+
|
269
326
|
max_length = schema['maxLength']
|
270
327
|
min_length = schema['minLength']
|
271
328
|
pattern = schema['pattern']
|
@@ -273,10 +330,10 @@ module JSONSchemer
|
|
273
330
|
content_encoding = schema['contentEncoding']
|
274
331
|
content_media_type = schema['contentMediaType']
|
275
332
|
|
276
|
-
yield error(
|
277
|
-
yield error(
|
278
|
-
yield error(
|
279
|
-
yield error(
|
333
|
+
yield error(instance, 'maxLength') if max_length && data.size > max_length
|
334
|
+
yield error(instance, 'minLength') if min_length && data.size < min_length
|
335
|
+
yield error(instance, 'pattern') if pattern && Regexp.new(pattern) !~ data
|
336
|
+
yield error(instance, 'format') if format? && spec_format?(format) && !valid_spec_format?(data, format)
|
280
337
|
|
281
338
|
if content_encoding || content_media_type
|
282
339
|
decoded_data = data
|
@@ -288,13 +345,13 @@ module JSONSchemer
|
|
288
345
|
else # '7bit', '8bit', 'binary', 'quoted-printable'
|
289
346
|
raise NotImplementedError
|
290
347
|
end
|
291
|
-
yield error(
|
348
|
+
yield error(instance, 'contentEncoding') unless decoded_data
|
292
349
|
end
|
293
350
|
|
294
351
|
if content_media_type && decoded_data
|
295
352
|
case content_media_type.downcase
|
296
353
|
when 'application/json'
|
297
|
-
yield error(
|
354
|
+
yield error(instance, 'contentMediaType') unless valid_json?(decoded_data)
|
298
355
|
else
|
299
356
|
raise NotImplementedError
|
300
357
|
end
|
@@ -302,12 +359,16 @@ module JSONSchemer
|
|
302
359
|
end
|
303
360
|
end
|
304
361
|
|
305
|
-
def validate_array(
|
362
|
+
def validate_array(instance, &block)
|
363
|
+
data = instance.data
|
364
|
+
|
306
365
|
unless data.is_a?(Array)
|
307
|
-
yield error(
|
366
|
+
yield error(instance, 'array')
|
308
367
|
return
|
309
368
|
end
|
310
369
|
|
370
|
+
schema = instance.schema
|
371
|
+
|
311
372
|
items = schema['items']
|
312
373
|
additional_items = schema['additionalItems']
|
313
374
|
max_items = schema['maxItems']
|
@@ -315,34 +376,56 @@ module JSONSchemer
|
|
315
376
|
unique_items = schema['uniqueItems']
|
316
377
|
contains = schema['contains']
|
317
378
|
|
318
|
-
yield error(
|
319
|
-
yield error(
|
320
|
-
yield error(
|
321
|
-
yield error(
|
379
|
+
yield error(instance, 'maxItems') if max_items && data.size > max_items
|
380
|
+
yield error(instance, 'minItems') if min_items && data.size < min_items
|
381
|
+
yield error(instance, 'uniqueItems') if unique_items && data.size != data.uniq.size
|
382
|
+
yield error(instance, 'contains') if !contains.nil? && data.all? { |item| !valid_instance?(instance.merge(data: item, schema: contains)) }
|
322
383
|
|
323
384
|
if items.is_a?(Array)
|
324
385
|
data.each_with_index do |item, index|
|
325
386
|
if index < items.size
|
326
|
-
|
387
|
+
subinstance = instance.merge(
|
388
|
+
data: item,
|
389
|
+
data_pointer: "#{instance.data_pointer}/#{index}",
|
390
|
+
schema: items[index],
|
391
|
+
schema_pointer: "#{instance.schema_pointer}/items/#{index}"
|
392
|
+
)
|
393
|
+
validate_instance(subinstance, &block)
|
327
394
|
elsif !additional_items.nil?
|
328
|
-
|
395
|
+
subinstance = instance.merge(
|
396
|
+
data: item,
|
397
|
+
data_pointer: "#{instance.data_pointer}/#{index}",
|
398
|
+
schema: additional_items,
|
399
|
+
schema_pointer: "#{instance.schema_pointer}/additionalItems"
|
400
|
+
)
|
401
|
+
validate_instance(subinstance, &block)
|
329
402
|
else
|
330
403
|
break
|
331
404
|
end
|
332
405
|
end
|
333
406
|
elsif !items.nil?
|
334
407
|
data.each_with_index do |item, index|
|
335
|
-
|
408
|
+
subinstance = instance.merge(
|
409
|
+
data: item,
|
410
|
+
data_pointer: "#{instance.data_pointer}/#{index}",
|
411
|
+
schema: items,
|
412
|
+
schema_pointer: "#{instance.schema_pointer}/items"
|
413
|
+
)
|
414
|
+
validate_instance(subinstance, &block)
|
336
415
|
end
|
337
416
|
end
|
338
417
|
end
|
339
418
|
|
340
|
-
def validate_object(
|
419
|
+
def validate_object(instance, &block)
|
420
|
+
data = instance.data
|
421
|
+
|
341
422
|
unless data.is_a?(Hash)
|
342
|
-
yield error(
|
423
|
+
yield error(instance, 'object')
|
343
424
|
return
|
344
425
|
end
|
345
426
|
|
427
|
+
schema = instance.schema
|
428
|
+
|
346
429
|
max_properties = schema['maxProperties']
|
347
430
|
min_properties = schema['minProperties']
|
348
431
|
required = schema['required']
|
@@ -356,32 +439,52 @@ module JSONSchemer
|
|
356
439
|
dependencies.each do |key, value|
|
357
440
|
next unless data.key?(key)
|
358
441
|
subschema = value.is_a?(Array) ? { 'required' => value } : value
|
359
|
-
|
442
|
+
subinstance = instance.merge(schema: subschema, schema_pointer: "#{instance.schema_pointer}/dependencies/#{key}")
|
443
|
+
validate_instance(subinstance, &block)
|
360
444
|
end
|
361
445
|
end
|
362
446
|
|
363
|
-
yield error(
|
364
|
-
yield error(
|
365
|
-
yield error(
|
447
|
+
yield error(instance, 'maxProperties') if max_properties && data.size > max_properties
|
448
|
+
yield error(instance, 'minProperties') if min_properties && data.size < min_properties
|
449
|
+
yield error(instance, 'required') if required && required.any? { |key| !data.key?(key) }
|
366
450
|
|
367
451
|
regex_pattern_properties = nil
|
368
452
|
data.each do |key, value|
|
369
|
-
|
453
|
+
unless property_names.nil?
|
454
|
+
subinstance = instance.merge(
|
455
|
+
data: key,
|
456
|
+
schema: property_names,
|
457
|
+
schema_pointer: "#{instance.schema_pointer}/propertyNames"
|
458
|
+
)
|
459
|
+
validate_instance(subinstance, &block)
|
460
|
+
end
|
370
461
|
|
371
462
|
matched_key = false
|
372
463
|
|
373
464
|
if properties && properties.key?(key)
|
374
|
-
|
465
|
+
subinstance = instance.merge(
|
466
|
+
data: value,
|
467
|
+
data_pointer: "#{instance.data_pointer}/#{key}",
|
468
|
+
schema: properties[key],
|
469
|
+
schema_pointer: "#{instance.schema_pointer}/properties/#{key}"
|
470
|
+
)
|
471
|
+
validate_instance(subinstance, &block)
|
375
472
|
matched_key = true
|
376
473
|
end
|
377
474
|
|
378
475
|
if pattern_properties
|
379
476
|
regex_pattern_properties ||= pattern_properties.map do |pattern, property_schema|
|
380
|
-
[Regexp.new(pattern), property_schema]
|
477
|
+
[pattern, Regexp.new(pattern), property_schema]
|
381
478
|
end
|
382
|
-
regex_pattern_properties.each do |regex, property_schema|
|
479
|
+
regex_pattern_properties.each do |pattern, regex, property_schema|
|
383
480
|
if regex =~ key
|
384
|
-
|
481
|
+
subinstance = instance.merge(
|
482
|
+
data: value,
|
483
|
+
data_pointer: "#{instance.data_pointer}/#{key}",
|
484
|
+
schema: property_schema,
|
485
|
+
schema_pointer: "#{instance.schema_pointer}/patternProperties/#{pattern}"
|
486
|
+
)
|
487
|
+
validate_instance(subinstance, &block)
|
385
488
|
matched_key = true
|
386
489
|
end
|
387
490
|
end
|
@@ -389,7 +492,15 @@ module JSONSchemer
|
|
389
492
|
|
390
493
|
next if matched_key
|
391
494
|
|
392
|
-
|
495
|
+
unless additional_properties.nil?
|
496
|
+
subinstance = instance.merge(
|
497
|
+
data: value,
|
498
|
+
data_pointer: "#{instance.data_pointer}/#{key}",
|
499
|
+
schema: additional_properties,
|
500
|
+
schema_pointer: "#{instance.schema_pointer}/additionalProperties"
|
501
|
+
)
|
502
|
+
validate_instance(subinstance, &block)
|
503
|
+
end
|
393
504
|
end
|
394
505
|
end
|
395
506
|
|
@@ -425,15 +536,20 @@ module JSONSchemer
|
|
425
536
|
uri_parts ? URI.join(*uri_parts) : nil
|
426
537
|
end
|
427
538
|
|
428
|
-
def resolve_ids(schema, ids = {}, parent_uri = nil)
|
539
|
+
def resolve_ids(schema, ids = {}, parent_uri = nil, pointer = '')
|
429
540
|
if schema.is_a?(Array)
|
430
|
-
schema.
|
541
|
+
schema.each_with_index { |subschema, index| resolve_ids(subschema, ids, parent_uri, "#{pointer}/#{index}") }
|
431
542
|
elsif schema.is_a?(Hash)
|
432
543
|
id = schema[id_keyword]
|
433
544
|
uri = join_uri(parent_uri, id)
|
434
|
-
|
545
|
+
unless uri == parent_uri
|
546
|
+
ids[uri.to_s] = {
|
547
|
+
schema: schema,
|
548
|
+
pointer: pointer
|
549
|
+
}
|
550
|
+
end
|
435
551
|
if definitions = schema['definitions']
|
436
|
-
definitions.
|
552
|
+
definitions.each { |key, subschema| resolve_ids(subschema, ids, uri, "#{pointer}/definitions/#{key}") }
|
437
553
|
end
|
438
554
|
end
|
439
555
|
ids
|
@@ -24,21 +24,21 @@ module JSONSchemer
|
|
24
24
|
SUPPORTED_FORMATS.include?(format)
|
25
25
|
end
|
26
26
|
|
27
|
-
def validate_exclusive_maximum(
|
28
|
-
yield error(
|
27
|
+
def validate_exclusive_maximum(instance, exclusive_maximum, maximum)
|
28
|
+
yield error(instance, 'exclusiveMaximum') if exclusive_maximum && instance.data >= maximum
|
29
29
|
end
|
30
30
|
|
31
|
-
def validate_exclusive_minimum(
|
32
|
-
yield error(
|
31
|
+
def validate_exclusive_minimum(instance, exclusive_minimum, minimum)
|
32
|
+
yield error(instance, 'exclusiveMinimum') if exclusive_minimum && instance.data <= minimum
|
33
33
|
end
|
34
34
|
|
35
|
-
def validate_integer(
|
36
|
-
if !data.is_a?(Integer)
|
37
|
-
yield error(
|
35
|
+
def validate_integer(instance)
|
36
|
+
if !instance.data.is_a?(Integer)
|
37
|
+
yield error(instance, 'integer')
|
38
38
|
return
|
39
39
|
end
|
40
40
|
|
41
|
-
validate_numeric(
|
41
|
+
validate_numeric(instance, &Proc.new)
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
data/lib/json_schemer/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: json_schemer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Harsha
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -113,6 +113,7 @@ files:
|
|
113
113
|
- bin/setup
|
114
114
|
- json_schemer.gemspec
|
115
115
|
- lib/json_schemer.rb
|
116
|
+
- lib/json_schemer/cached_ref_resolver.rb
|
116
117
|
- lib/json_schemer/format.rb
|
117
118
|
- lib/json_schemer/schema/base.rb
|
118
119
|
- lib/json_schemer/schema/draft4.rb
|
@@ -139,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
139
140
|
version: '0'
|
140
141
|
requirements: []
|
141
142
|
rubyforge_project:
|
142
|
-
rubygems_version: 2.7.
|
143
|
+
rubygems_version: 2.7.6
|
143
144
|
signing_key:
|
144
145
|
specification_version: 4
|
145
146
|
summary: JSON Schema validator. Supports drafts 4, 6, and 7.
|