json_schema 0.0.10 → 0.0.11

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.
@@ -80,7 +80,13 @@ module Commands
80
80
 
81
81
  # Builds a JSON Reference + message like "/path/to/file#/path/to/data".
82
82
  def map_schema_errors(file, errors)
83
- errors.map { |e| "#{file}#{e.schema.pointer}: #{e.message}" }
83
+ errors.map { |e|
84
+ if e.is_a?(JsonSchema::ValidationError)
85
+ "#{file}#{e.pointer}: failed #{e.schema.pointer}: #{e.message}"
86
+ else
87
+ "#{file}#{e.schema.pointer}: #{e.message}"
88
+ end
89
+ }
84
90
  end
85
91
 
86
92
  def parse(file)
@@ -10,7 +10,7 @@ module JsonSchema
10
10
  # for now.
11
11
  errors.map { |e|
12
12
  if e.schema
13
- %{At "#{e.schema.uri}": #{e.message}}
13
+ %{#{e.schema.pointer}: #{e.message}}
14
14
  else
15
15
  e.message
16
16
  end
@@ -22,4 +22,17 @@ module JsonSchema
22
22
  @message = message
23
23
  end
24
24
  end
25
+
26
+ class ValidationError < SchemaError
27
+ attr_accessor :path
28
+
29
+ def initialize(schema, path, message)
30
+ super(schema, message)
31
+ @path = path
32
+ end
33
+
34
+ def pointer
35
+ path.join("/")
36
+ end
37
+ end
25
38
  end
@@ -20,7 +20,7 @@ module JsonSchema
20
20
 
21
21
  def validate(data)
22
22
  @errors = []
23
- validate_data(@schema, data, @errors)
23
+ validate_data(@schema, data, @errors, ['#'])
24
24
  @errors.size == 0
25
25
  end
26
26
 
@@ -37,55 +37,55 @@ module JsonSchema
37
37
  valid_old && valid_new
38
38
  end
39
39
 
40
- def validate_data(schema, data, errors)
40
+ def validate_data(schema, data, errors, path)
41
41
  valid = true
42
42
 
43
43
  # validation: any
44
- valid = strict_and valid, validate_all_of(schema, data, errors)
45
- valid = strict_and valid, validate_any_of(schema, data, errors)
46
- valid = strict_and valid, validate_enum(schema, data, errors)
47
- valid = strict_and valid, validate_one_of(schema, data, errors)
48
- valid = strict_and valid, validate_not(schema, data, errors)
49
- valid = strict_and valid, validate_type(schema, data, errors)
44
+ valid = strict_and valid, validate_all_of(schema, data, errors, path)
45
+ valid = strict_and valid, validate_any_of(schema, data, errors, path)
46
+ valid = strict_and valid, validate_enum(schema, data, errors, path)
47
+ valid = strict_and valid, validate_one_of(schema, data, errors, path)
48
+ valid = strict_and valid, validate_not(schema, data, errors, path)
49
+ valid = strict_and valid, validate_type(schema, data, errors, path)
50
50
 
51
51
  # validation: array
52
52
  if data.is_a?(Array)
53
- valid = strict_and valid, validate_items(schema, data, errors)
54
- valid = strict_and valid, validate_max_items(schema, data, errors)
55
- valid = strict_and valid, validate_min_items(schema, data, errors)
56
- valid = strict_and valid, validate_unique_items(schema, data, errors)
53
+ valid = strict_and valid, validate_items(schema, data, errors, path)
54
+ valid = strict_and valid, validate_max_items(schema, data, errors, path)
55
+ valid = strict_and valid, validate_min_items(schema, data, errors, path)
56
+ valid = strict_and valid, validate_unique_items(schema, data, errors, path)
57
57
  end
58
58
 
59
59
  # validation: integer/number
60
60
  if data.is_a?(Float) || data.is_a?(Integer)
61
- valid = strict_and valid, validate_max(schema, data, errors)
62
- valid = strict_and valid, validate_min(schema, data, errors)
63
- valid = strict_and valid, validate_multiple_of(schema, data, errors)
61
+ valid = strict_and valid, validate_max(schema, data, errors, path)
62
+ valid = strict_and valid, validate_min(schema, data, errors, path)
63
+ valid = strict_and valid, validate_multiple_of(schema, data, errors, path)
64
64
  end
65
65
 
66
66
  # validation: object
67
67
  if data.is_a?(Hash)
68
- valid = strict_and valid, validate_additional_properties(schema, data, errors)
69
- valid = strict_and valid, validate_dependencies(schema, data, errors)
70
- valid = strict_and valid, validate_max_properties(schema, data, errors)
71
- valid = strict_and valid, validate_min_properties(schema, data, errors)
72
- valid = strict_and valid, validate_pattern_properties(schema, data, errors)
73
- valid = strict_and valid, validate_properties(schema, data, errors)
74
- valid = strict_and valid, validate_required(schema, data, errors, schema.required)
68
+ valid = strict_and valid, validate_additional_properties(schema, data, errors, path)
69
+ valid = strict_and valid, validate_dependencies(schema, data, errors, path)
70
+ valid = strict_and valid, validate_max_properties(schema, data, errors, path)
71
+ valid = strict_and valid, validate_min_properties(schema, data, errors, path)
72
+ valid = strict_and valid, validate_pattern_properties(schema, data, errors, path)
73
+ valid = strict_and valid, validate_properties(schema, data, errors, path)
74
+ valid = strict_and valid, validate_required(schema, data, errors, path, schema.required)
75
75
  end
76
76
 
77
77
  # validation: string
78
78
  if data.is_a?(String)
79
- valid = strict_and valid, validate_format(schema, data, errors)
80
- valid = strict_and valid, validate_max_length(schema, data, errors)
81
- valid = strict_and valid, validate_min_length(schema, data, errors)
82
- valid = strict_and valid, validate_pattern(schema, data, errors)
79
+ valid = strict_and valid, validate_format(schema, data, errors, path)
80
+ valid = strict_and valid, validate_max_length(schema, data, errors, path)
81
+ valid = strict_and valid, validate_min_length(schema, data, errors, path)
82
+ valid = strict_and valid, validate_pattern(schema, data, errors, path)
83
83
  end
84
84
 
85
85
  valid
86
86
  end
87
87
 
88
- def validate_additional_properties(schema, data, errors)
88
+ def validate_additional_properties(schema, data, errors, path)
89
89
  return true if schema.additional_properties == true
90
90
 
91
91
  extra = data.keys - schema.properties.keys
@@ -94,7 +94,7 @@ module JsonSchema
94
94
  # validated according to subschema
95
95
  if schema.additional_properties.is_a?(Schema)
96
96
  extra.each do |key|
97
- validate_data(schema.additional_properties, data[key], errors)
97
+ validate_data(schema.additional_properties, data[key], errors, path + [key])
98
98
  end
99
99
  # boolean indicates whether additional properties are allowed
100
100
  else
@@ -102,49 +102,49 @@ module JsonSchema
102
102
  true
103
103
  else
104
104
  message = %{Extra keys in object: #{extra.sort.join(", ")}.}
105
- errors << SchemaError.new(schema, message)
105
+ errors << ValidationError.new(schema, path, message)
106
106
  false
107
107
  end
108
108
  end
109
109
  end
110
110
 
111
- def validate_all_of(schema, data, errors)
111
+ def validate_all_of(schema, data, errors, path)
112
112
  return true if schema.all_of.empty?
113
113
  valid = schema.all_of.all? do |subschema|
114
- validate_data(subschema, data, errors)
114
+ validate_data(subschema, data, errors, path)
115
115
  end
116
116
  message = %{Data did not match all subschemas of "allOf" condition.}
117
- errors << SchemaError.new(schema, message) if !valid
117
+ errors << ValidationError.new(schema, path, message) if !valid
118
118
  valid
119
119
  end
120
120
 
121
- def validate_any_of(schema, data, errors)
121
+ def validate_any_of(schema, data, errors, path)
122
122
  return true if schema.any_of.empty?
123
123
  valid = schema.any_of.any? do |subschema|
124
- validate_data(subschema, data, [])
124
+ validate_data(subschema, data, [], path)
125
125
  end
126
126
  if !valid
127
127
  message = %{Data did not match any subschema of "anyOf" condition.}
128
- errors << SchemaError.new(schema, message)
128
+ errors << ValidationError.new(schema, path, message)
129
129
  end
130
130
  valid
131
131
  end
132
132
 
133
- def validate_dependencies(schema, data, errors)
133
+ def validate_dependencies(schema, data, errors, path)
134
134
  return true if schema.dependencies.empty?
135
135
  schema.dependencies.each do |key, obj|
136
136
  # if the key is not present, the dependency is fulfilled by definition
137
137
  next unless data[key]
138
138
  if obj.is_a?(Schema)
139
- validate_data(obj, data, errors)
139
+ validate_data(obj, data, errors, path)
140
140
  else
141
141
  # if not a schema, value is an array of required fields
142
- validate_required(schema, data, errors, obj)
142
+ validate_required(schema, data, errors, path, obj)
143
143
  end
144
144
  end
145
145
  end
146
146
 
147
- def validate_format(schema, data, errors)
147
+ def validate_format(schema, data, errors, path)
148
148
  return true unless schema.format
149
149
  valid = case schema.format
150
150
  when "date-time"
@@ -168,51 +168,52 @@ module JsonSchema
168
168
  true
169
169
  else
170
170
  message = %{Expected data to match "#{schema.format}" format, value was: #{data}.}
171
- errors << SchemaError.new(schema, message)
171
+ errors << ValidationError.new(schema, path, message)
172
172
  false
173
173
  end
174
174
  end
175
175
 
176
- def validate_enum(schema, data, errors)
176
+ def validate_enum(schema, data, errors, path)
177
177
  return true unless schema.enum
178
178
  if schema.enum.include?(data)
179
179
  true
180
180
  else
181
181
  message = %{Expected data to be a member of enum #{schema.enum}, value was: #{data}.}
182
- errors << SchemaError.new(schema, message)
182
+ errors << ValidationError.new(schema, path, message)
183
183
  false
184
184
  end
185
185
  end
186
186
 
187
- def validate_items(schema, data, error)
187
+ def validate_items(schema, data, errors, path)
188
188
  return true unless schema.items
189
189
  if schema.items.is_a?(Array)
190
190
  if data.size < schema.items.count
191
191
  message = %{Expected array to have at least #{schema.items.count} item(s), had #{data.size} item(s).}
192
- errors << SchemaError.new(schema, message)
192
+ errors << ValidationError.new(schema, path, message)
193
193
  false
194
194
  elsif data.size > schema.items.count && !schema.additional_items?
195
195
  message = %{Expected array to have no more than #{schema.items.count} item(s), had #{data.size} item(s).}
196
- errors << SchemaError.new(schema, message)
196
+ errors << ValidationError.new(schema, path, message)
197
197
  false
198
198
  else
199
199
  valid = true
200
200
  schema.items.each_with_index do |subschema, i|
201
201
  valid = strict_and valid,
202
- validate_data(subschema, data[i], errors)
202
+ validate_data(subschema, data[i], errors, path + [i])
203
203
  end
204
204
  valid
205
205
  end
206
206
  else
207
207
  valid = true
208
- data.each do |value|
209
- valid = strict_and valid, validate_data(schema.items, value, errors)
208
+ data.each_with_index do |value, i|
209
+ valid = strict_and valid,
210
+ validate_data(schema.items, value, errors, path + [i])
210
211
  end
211
212
  valid
212
213
  end
213
214
  end
214
215
 
215
- def validate_max(schema, data, error)
216
+ def validate_max(schema, data, errors, path)
216
217
  return true unless schema.max
217
218
  if schema.max_exclusive? && data < schema.max
218
219
  true
@@ -220,45 +221,45 @@ module JsonSchema
220
221
  true
221
222
  else
222
223
  message = %{Expected data to be smaller than maximum #{schema.max} (exclusive: #{schema.max_exclusive?}), value was: #{data}.}
223
- errors << SchemaError.new(schema, message)
224
+ errors << ValidationError.new(schema, path, message)
224
225
  false
225
226
  end
226
227
  end
227
228
 
228
- def validate_max_items(schema, data, error)
229
+ def validate_max_items(schema, data, errors, path)
229
230
  return true unless schema.max_items
230
231
  if data.size <= schema.max_items
231
232
  true
232
233
  else
233
234
  message = %{Expected array to have no more than #{schema.max_items} item(s), had #{data.size} item(s).}
234
- errors << SchemaError.new(schema, message)
235
+ errors << ValidationError.new(schema, path, message)
235
236
  false
236
237
  end
237
238
  end
238
239
 
239
- def validate_max_length(schema, data, error)
240
+ def validate_max_length(schema, data, errors, path)
240
241
  return true unless schema.max_length
241
242
  if data.length <= schema.max_length
242
243
  true
243
244
  else
244
245
  message = %{Expected string to have a maximum length of #{schema.max_length}, was #{data.length} character(s) long.}
245
- errors << SchemaError.new(schema, message)
246
+ errors << ValidationError.new(schema, path, message)
246
247
  false
247
248
  end
248
249
  end
249
250
 
250
- def validate_max_properties(schema, data, error)
251
+ def validate_max_properties(schema, data, errors, path)
251
252
  return true unless schema.max_properties
252
253
  if data.keys.size <= schema.max_properties
253
254
  true
254
255
  else
255
256
  message = %{Expected object to have a maximum of #{schema.max_properties} property/ies; it had #{data.keys.size}.}
256
- errors << SchemaError.new(schema, message)
257
+ errors << ValidationError.new(schema, path, message)
257
258
  false
258
259
  end
259
260
  end
260
261
 
261
- def validate_min(schema, data, error)
262
+ def validate_min(schema, data, errors, path)
262
263
  return true unless schema.min
263
264
  if schema.min_exclusive? && data > schema.min
264
265
  true
@@ -266,140 +267,146 @@ module JsonSchema
266
267
  true
267
268
  else
268
269
  message = %{Expected data to be larger than minimum #{schema.min} (exclusive: #{schema.min_exclusive?}), value was: #{data}.}
269
- errors << SchemaError.new(schema, message)
270
+ errors << ValidationError.new(schema, path, message)
270
271
  false
271
272
  end
272
273
  end
273
274
 
274
- def validate_min_items(schema, data, error)
275
+ def validate_min_items(schema, data, errors, path)
275
276
  return true unless schema.min_items
276
277
  if data.size >= schema.min_items
277
278
  true
278
279
  else
279
280
  message = %{Expected array to have at least #{schema.min_items} item(s), had #{data.size} item(s).}
280
- errors << SchemaError.new(schema, message)
281
+ errors << ValidationError.new(schema, path, message)
281
282
  false
282
283
  end
283
284
  end
284
285
 
285
- def validate_min_length(schema, data, error)
286
+ def validate_min_length(schema, data, errors, path)
286
287
  return true unless schema.min_length
287
288
  if data.length >= schema.min_length
288
289
  true
289
290
  else
290
291
  message = %{Expected string to have a minimum length of #{schema.min_length}, was #{data.length} character(s) long.}
291
- errors << SchemaError.new(schema, message)
292
+ errors << ValidationError.new(schema, path, message)
292
293
  false
293
294
  end
294
295
  end
295
296
 
296
- def validate_min_properties(schema, data, error)
297
+ def validate_min_properties(schema, data, errors, path)
297
298
  return true unless schema.min_properties
298
299
  if data.keys.size >= schema.min_properties
299
300
  true
300
301
  else
301
302
  message = %{Expected object to have a minimum of #{schema.min_properties} property/ies; it had #{data.keys.size}.}
302
- errors << SchemaError.new(schema, message)
303
+ errors << ValidationError.new(schema, path, message)
303
304
  false
304
305
  end
305
306
  end
306
307
 
307
- def validate_multiple_of(schema, data, errors)
308
+ def validate_multiple_of(schema, data, errors, path)
308
309
  return true unless schema.multiple_of
309
310
  if data % schema.multiple_of == 0
310
311
  true
311
312
  else
312
313
  message = %{Expected data to be a multiple of #{schema.multiple_of}, value was: #{data}.}
313
- errors << SchemaError.new(schema, message)
314
+ errors << ValidationError.new(schema, path, message)
314
315
  false
315
316
  end
316
317
  end
317
318
 
318
- def validate_one_of(schema, data, errors)
319
+ def validate_one_of(schema, data, errors, path)
319
320
  return true if schema.one_of.empty?
320
321
  num_valid = schema.one_of.count do |subschema|
321
- validate_data(subschema, data, [])
322
+ validate_data(subschema, data, [], path)
323
+ end
324
+ if num_valid != 1
325
+ message = %{Data did not match exactly one subschema of "oneOf" condition.}
326
+ errors << ValidationError.new(schema, path, message)
322
327
  end
323
- message = %{Data did not match exactly one subschema of "oneOf" condition.}
324
- errors << SchemaError.new(schema, message) if num_valid != 1
325
328
  num_valid == 1
326
329
  end
327
330
 
328
- def validate_not(schema, data, errors)
331
+ def validate_not(schema, data, errors, path)
329
332
  return true unless schema.not
330
333
  # don't bother accumulating these errors, they'll all be worded
331
334
  # incorrectly for the inverse condition
332
- valid = !validate_data(schema.not, data, [])
333
- message = %{Data matched subschema of "not" condition.}
334
- errors << SchemaError.new(schema, message) if !valid
335
+ valid = !validate_data(schema.not, data, [], path)
336
+ if !valid
337
+ message = %{Data matched subschema of "not" condition.}
338
+ errors << ValidationError.new(schema, path, message)
339
+ end
335
340
  valid
336
341
  end
337
342
 
338
- def validate_pattern(schema, data, error)
343
+ def validate_pattern(schema, data, errors, path)
339
344
  return true unless schema.pattern
340
345
  if data =~ schema.pattern
341
346
  true
342
347
  else
343
348
  message = %{Expected string to match pattern "#{schema.pattern.inspect}", value was: #{data}.}
344
- errors << SchemaError.new(schema, message)
349
+ errors << ValidationError.new(schema, path, message)
345
350
  false
346
351
  end
347
352
  end
348
353
 
349
- def validate_pattern_properties(schema, data, errors)
354
+ def validate_pattern_properties(schema, data, errors, path)
350
355
  return true if schema.pattern_properties.empty?
351
356
  valid = true
352
357
  schema.pattern_properties.each do |pattern, subschema|
353
358
  data.each do |key, value|
354
359
  if key =~ pattern
355
- valid = strict_and valid, validate_data(subschema, value, errors)
360
+ valid = strict_and valid,
361
+ validate_data(subschema, value, errors, path + [key])
356
362
  end
357
363
  end
358
364
  end
359
365
  valid
360
366
  end
361
367
 
362
- def validate_properties(schema, data, errors)
368
+ def validate_properties(schema, data, errors, path)
363
369
  return true if schema.properties.empty?
364
370
  valid = true
365
371
  schema.properties.each do |key, subschema|
366
372
  if value = data[key]
367
- valid = strict_and valid, validate_data(subschema, value, errors)
373
+ valid = strict_and valid,
374
+ validate_data(subschema, value, errors, path + [key])
368
375
  end
369
376
  end
370
377
  valid
371
378
  end
372
379
 
373
- def validate_required(schema, data, errors, required)
380
+ def validate_required(schema, data, errors, path, required)
374
381
  return true if !required || required.empty?
375
382
  if (missing = required - data.keys).empty?
376
383
  true
377
384
  else
378
385
  message = %{Missing required keys "#{missing.sort.join(", ")}" in object; keys are "#{data.keys.sort.join(", ")}".}
379
- errors << SchemaError.new(schema, message)
386
+ errors << ValidationError.new(schema, path, message)
380
387
  false
381
388
  end
382
389
  end
383
390
 
384
- def validate_type(schema, data, errors)
391
+ def validate_type(schema, data, errors, path)
385
392
  return true if schema.type.empty?
386
393
  valid_types = schema.type.map { |t| TYPE_MAP[t] }.flatten.compact
387
394
  if valid_types.any? { |t| data.is_a?(t) }
388
395
  true
389
396
  else
390
397
  message = %{Expected data to be of type "#{schema.type.join("/")}"; value was: #{data.inspect}.}
391
- errors << SchemaError.new(schema, message)
398
+ errors << ValidationError.new(schema, path, message)
392
399
  false
393
400
  end
394
401
  end
395
402
 
396
- def validate_unique_items(schema, data, error)
403
+ def validate_unique_items(schema, data, errors, path)
397
404
  return true unless schema.unique_items?
398
405
  if data.size == data.uniq.size
399
406
  true
400
407
  else
401
408
  message = %{Expected array items to be unique, but duplicate items were found.}
402
- errors << SchemaError.new(schema, message)
409
+ errors << ValidationError.new(schema, path, message)
403
410
  false
404
411
  end
405
412
  end
@@ -640,6 +640,15 @@ describe JsonSchema::Validator do
640
640
  %{Expected string to match pattern "/^[a-z][a-z0-9-]{3,30}$/", value was: ab.}
641
641
  end
642
642
 
643
+ it "builds appropriate JSON Pointers to bad data" do
644
+ pointer("#/definitions/app/definitions/visibility").merge!(
645
+ "enum" => ["private", "public"]
646
+ )
647
+ data_sample["visibility"] = "personal"
648
+ refute validate
649
+ assert_equal "#/visibility", @validator.errors[0].pointer
650
+ end
651
+
643
652
  def data_sample
644
653
  @data_sample ||= DataScaffold.data_sample
645
654
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.11
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: