json_schemer 0.1.5 → 0.1.6

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 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.