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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ddb07f2bbccb28d21477a4514b460b385363ff5a3c7d1b981c538408228ae796
4
- data.tar.gz: 374a24a40fd3c6714a24be1ef248a79143cb36121d60996017ac4cd2858b5dbb
3
+ metadata.gz: 8dc3f244ec43815a4207b9be2112cc76afee3cd37b96a3e1eb0fd9b189f8ad72
4
+ data.tar.gz: 3dcede7d0418d30e98fe75054000657bf03201a1fbf8231810506a2a91de1bbb
5
5
  SHA512:
6
- metadata.gz: 0e34958d512ef4093d360a9596cd15ec51a90ea30d4b346453379c2d749496b83be9d237e34c332d225302a6fecf5fc156785d3aae4fe2be95f47fa00ee1e1e4
7
- data.tar.gz: fa555c0ed341eae0bcd861c4be770eca06e8aa136dfcb4b4d4bad83661b9042dcfdc5dcc76ed5c59b40946cd2930323710153740ef315f32587105f252caa88b
6
+ metadata.gz: 1ea22d403dd91c7c555d7fe949a25840255a1ed55af658aad703b965b79e063fe2ae5a1d82b3b3ac7a6a56cc32b5720ec0639d734235e934ccaef04f87a11146
7
+ data.tar.gz: 243e04ef109d923d9de753856943f2269548485de36f476e9d527be132c1f737abcd1ee6e74c6b3263bb95d9b03fcfc0a6f8a11c4249d538baab0d469c97652d
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- json_schemer (0.1.5)
4
+ json_schemer (0.1.6)
5
5
  ecma-re-validator (~> 0.1.2)
6
6
  hana (~> 1.3.3)
7
7
  uri_template (~> 0.7.0)
@@ -27,4 +27,4 @@ DEPENDENCIES
27
27
  rake (~> 10.0)
28
28
 
29
29
  BUNDLED WITH
30
- 1.16.2
30
+ 1.16.4
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
@@ -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
- def self.schema(schema, **options)
23
- meta_schema = schema.is_a?(Hash) && schema.key?('$schema') ? schema['$schema'] : DEFAULT_META_SCHEMA
24
- draft_class = DRAFT_CLASS_BY_META_SCHEMA[meta_schema] || raise(UnsupportedMetaSchema, meta_schema)
25
- draft_class.new(schema, **options)
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, schema = root, pointer = '', parent_uri = nil)
35
- validate(data, schema, pointer, parent_uri).none?
47
+ def valid?(data)
48
+ valid_instance?(Instance.new(data, '', root, '', nil))
36
49
  end
37
50
 
38
- def validate(data, schema = root, pointer = '', parent_uri = nil)
39
- return enum_for(:validate, data, schema, pointer, parent_uri) unless block_given?
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(data, schema, pointer, 'schema')
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(data, schema, pointer, parent_uri, ref, &Proc.new)
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(data, schema, pointer, formats.fetch(format), &Proc.new)
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(data, schema, pointer, keyword)
105
+ yield error(instance, keyword)
81
106
  end
82
107
  end
83
108
  end
84
109
  end
85
110
 
86
- yield error(data, schema, pointer, 'enum') if enum && !enum.include?(data)
87
- yield error(data, schema, pointer, 'const') if schema.key?('const') && schema['const'] != data
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(data, schema, pointer, 'allOf') if all_of && !all_of.all? { |subschema| valid?(data, subschema, pointer, parent_uri) }
90
- yield error(data, schema, pointer, 'anyOf') if any_of && !any_of.any? { |subschema| valid?(data, subschema, pointer, parent_uri) }
91
- yield error(data, schema, pointer, 'oneOf') if one_of && !one_of.one? { |subschema| valid?(data, subschema, pointer, parent_uri) }
92
- yield error(data, schema, pointer, 'not') if !not_schema.nil? && valid?(data, not_schema, pointer, parent_uri)
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 && valid?(data, if_schema, pointer, parent_uri)
95
- yield error(data, schema, pointer, 'then') if !then_schema.nil? && !valid?(data, then_schema, pointer, parent_uri)
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(data, schema, pointer, 'else') if !else_schema.nil? && !valid?(data, else_schema, pointer, parent_uri)
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(data, schema, pointer, parent_uri, &Proc.new)
129
+ validate_class(instance, &Proc.new)
103
130
  when String
104
- validate_type(data, schema, pointer, parent_uri, type, &Proc.new)
131
+ validate_type(instance, type, &Proc.new)
105
132
  when Array
106
- if valid_type = type.find { |subtype| valid?(data, { 'type' => subtype }, pointer, parent_uri) }
107
- validate_type(data, schema, pointer, parent_uri, valid_type, &Proc.new)
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(data, schema, pointer, 'type')
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(data, schema, pointer, type)
175
+ def error(instance, type)
151
176
  {
152
- 'data' => data,
153
- 'schema' => schema,
154
- 'pointer' => pointer,
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(data, schema, pointer, parent_uri)
160
- case data
186
+ def validate_class(instance)
187
+ case instance.data
161
188
  when Integer
162
- validate_integer(data, schema, pointer, &Proc.new)
189
+ validate_integer(instance, &Proc.new)
163
190
  when Numeric
164
- validate_number(data, schema, pointer, &Proc.new)
191
+ validate_number(instance, &Proc.new)
165
192
  when String
166
- validate_string(data, schema, pointer, &Proc.new)
193
+ validate_string(instance, &Proc.new)
167
194
  when Array
168
- validate_array(data, schema, pointer, parent_uri, &Proc.new)
195
+ validate_array(instance, &Proc.new)
169
196
  when Hash
170
- validate_object(data, schema, pointer, parent_uri, &Proc.new)
197
+ validate_object(instance, &Proc.new)
171
198
  end
172
199
  end
173
200
 
174
- def validate_type(data, schema, pointer, parent_uri, type)
201
+ def validate_type(instance, type)
175
202
  case type
176
203
  when 'null'
177
- yield error(data, schema, pointer, 'null') unless data.nil?
204
+ yield error(instance, 'null') unless instance.data.nil?
178
205
  when 'boolean'
179
- yield error(data, schema, pointer, 'boolean') unless BOOLEANS.include?(data)
206
+ yield error(instance, 'boolean') unless BOOLEANS.include?(instance.data)
180
207
  when 'number'
181
- validate_number(data, schema, pointer, &Proc.new)
208
+ validate_number(instance, &Proc.new)
182
209
  when 'integer'
183
- validate_integer(data, schema, pointer, &Proc.new)
210
+ validate_integer(instance, &Proc.new)
184
211
  when 'string'
185
- validate_string(data, schema, pointer, &Proc.new)
212
+ validate_string(instance, &Proc.new)
186
213
  when 'array'
187
- validate_array(data, schema, pointer, parent_uri, &Proc.new)
214
+ validate_array(instance, &Proc.new)
188
215
  when 'object'
189
- validate_object(data, schema, pointer, parent_uri, &Proc.new)
216
+ validate_object(instance, &Proc.new)
190
217
  end
191
218
  end
192
219
 
193
- def validate_ref(data, schema, pointer, parent_uri, 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
- validate(data, ref_pointer.eval(root), pointer, pointer_uri(root, ref_pointer), &Proc.new)
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
- ref_object.validate(data, ref_pointer.eval(ref_root), pointer, pointer_uri(ref_root, ref_pointer), &Proc.new)
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.key?(ref_uri.to_s)
206
- validate(data, ids.fetch(ref_uri.to_s), pointer, ref_uri, &Proc.new)
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
- ref_object.validate(data, ref_object.ids.fetch(ref_uri.to_s, ref_root), pointer, ref_uri, &Proc.new)
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(data, schema, pointer, custom_format)
215
- yield error(data, schema, pointer, 'format') if custom_format != false && !custom_format.call(data, schema)
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(data, schema, pointer, exclusive_maximum, maximum)
219
- yield error(data, schema, pointer, 'exclusiveMaximum') if data >= exclusive_maximum
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(data, schema, pointer, exclusive_minimum, minimum)
223
- yield error(data, schema, pointer, 'exclusiveMinimum') if data <= exclusive_minimum
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(data, schema, pointer)
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(data, schema, pointer, 'maximum') if maximum && data > maximum
234
- yield error(data, schema, pointer, 'minimum') if minimum && data < minimum
284
+ yield error(instance, 'maximum') if maximum && data > maximum
285
+ yield error(instance, 'minimum') if minimum && data < minimum
235
286
 
236
- validate_exclusive_maximum(data, schema, pointer, exclusive_maximum, maximum, &Proc.new) if exclusive_maximum
237
- validate_exclusive_minimum(data, schema, pointer, exclusive_minimum, minimum, &Proc.new) if 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(data, schema, pointer, 'multipleOf') unless quotient.floor == quotient
292
+ yield error(instance, 'multipleOf') unless quotient.floor == quotient
242
293
  end
243
294
  end
244
295
 
245
- def validate_number(data, schema, pointer)
246
- unless data.is_a?(Numeric)
247
- yield error(data, schema, pointer, 'number')
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(data, schema, pointer, &Proc.new)
302
+ validate_numeric(instance, &Proc.new)
252
303
  end
253
304
 
254
- def validate_integer(data, schema, pointer)
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(data, schema, pointer, 'integer')
309
+ yield error(instance, 'integer')
257
310
  return
258
311
  end
259
312
 
260
- validate_numeric(data, schema, pointer, &Proc.new)
313
+ validate_numeric(instance, &Proc.new)
261
314
  end
262
315
 
263
- def validate_string(data, schema, pointer)
316
+ def validate_string(instance)
317
+ data = instance.data
318
+
264
319
  unless data.is_a?(String)
265
- yield error(data, schema, pointer, 'string')
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(data, schema, pointer, 'maxLength') if max_length && data.size > max_length
277
- yield error(data, schema, pointer, 'minLength') if min_length && data.size < min_length
278
- yield error(data, schema, pointer, 'pattern') if pattern && Regexp.new(pattern) !~ data
279
- yield error(data, schema, pointer, 'format') if format? && spec_format?(format) && !valid_spec_format?(data, format)
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(data, schema, pointer, 'contentEncoding') unless decoded_data
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(data, schema, pointer, 'contentMediaType') unless valid_json?(decoded_data)
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(data, schema, pointer, parent_uri, &block)
362
+ def validate_array(instance, &block)
363
+ data = instance.data
364
+
306
365
  unless data.is_a?(Array)
307
- yield error(data, schema, pointer, 'array')
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(data, schema, pointer, 'maxItems') if max_items && data.size > max_items
319
- yield error(data, schema, pointer, 'minItems') if min_items && data.size < min_items
320
- yield error(data, schema, pointer, 'uniqueItems') if unique_items && data.size != data.uniq.size
321
- yield error(data, schema, pointer, 'contains') if !contains.nil? && data.all? { |item| !valid?(item, contains, pointer, parent_uri) }
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
- validate(item, items[index], "#{pointer}/#{index}", parent_uri, &block)
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
- validate(item, additional_items, "#{pointer}/#{index}", parent_uri, &block)
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
- validate(item, items, "#{pointer}/#{index}", parent_uri, &block)
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(data, schema, pointer, parent_uri, &block)
419
+ def validate_object(instance, &block)
420
+ data = instance.data
421
+
341
422
  unless data.is_a?(Hash)
342
- yield error(data, schema, pointer, 'object')
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
- validate(data, subschema, pointer, parent_uri, &block)
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(data, schema, pointer, 'maxProperties') if max_properties && data.size > max_properties
364
- yield error(data, schema, pointer, 'minProperties') if min_properties && data.size < min_properties
365
- yield error(data, schema, pointer, 'required') if required && required.any? { |key| !data.key?(key) }
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
- validate(key, property_names, pointer, parent_uri, &block) unless property_names.nil?
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
- validate(value, properties[key], "#{pointer}/#{key}", parent_uri, &block)
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
- validate(value, property_schema, "#{pointer}/#{key}", parent_uri, &block)
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
- validate(value, additional_properties, "#{pointer}/#{key}", parent_uri, &block) unless additional_properties.nil?
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.each { |subschema| resolve_ids(subschema, ids, parent_uri) }
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
- ids[uri.to_s] = schema unless uri == parent_uri
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.each_value { |subschema| resolve_ids(subschema, ids, uri) }
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(data, schema, pointer, exclusive_maximum, maximum)
28
- yield error(data, schema, pointer, 'exclusiveMaximum') if exclusive_maximum && data >= maximum
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(data, schema, pointer, exclusive_minimum, minimum)
32
- yield error(data, schema, pointer, 'exclusiveMinimum') if exclusive_minimum && data <= minimum
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(data, schema, pointer)
36
- if !data.is_a?(Integer)
37
- yield error(data, schema, pointer, 'integer')
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(data, schema, pointer, &Proc.new)
41
+ validate_numeric(instance, &Proc.new)
42
42
  end
43
43
  end
44
44
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSONSchemer
4
- VERSION = '0.1.5'
4
+ VERSION = '0.1.6'
5
5
  end
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.5
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-05-23 00:00:00.000000000 Z
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.3
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.