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|
|
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
|
-
%{
|
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 <<
|
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 <<
|
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 <<
|
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 <<
|
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 <<
|
182
|
+
errors << ValidationError.new(schema, path, message)
|
183
183
|
false
|
184
184
|
end
|
185
185
|
end
|
186
186
|
|
187
|
-
def validate_items(schema, data,
|
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 <<
|
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 <<
|
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.
|
209
|
-
valid = strict_and valid,
|
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,
|
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 <<
|
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,
|
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 <<
|
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,
|
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 <<
|
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,
|
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 <<
|
257
|
+
errors << ValidationError.new(schema, path, message)
|
257
258
|
false
|
258
259
|
end
|
259
260
|
end
|
260
261
|
|
261
|
-
def validate_min(schema, data,
|
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 <<
|
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,
|
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 <<
|
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,
|
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 <<
|
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,
|
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 <<
|
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 <<
|
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
|
-
|
334
|
-
|
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,
|
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 <<
|
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,
|
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,
|
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 <<
|
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 <<
|
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,
|
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 <<
|
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
|