json_schema 0.0.10 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: