json-schema 0.1.1 → 0.1.3
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.
- data/README.textile +11 -2
- data/lib/json-schema/validator.rb +394 -193
- metadata +4 -4
data/README.textile
CHANGED
|
@@ -36,6 +36,16 @@ data = {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
JSON::Validator.validate(schema, data)
|
|
39
|
+
|
|
40
|
+
data = {
|
|
41
|
+
"a" => "taco"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
begin
|
|
45
|
+
JSON::Validator.validate2(schema, data)
|
|
46
|
+
rescue ValidationError
|
|
47
|
+
puts $!.message
|
|
48
|
+
end
|
|
39
49
|
</pre>
|
|
40
50
|
|
|
41
51
|
|
|
@@ -90,6 +100,5 @@ In addition, the following hyper schema attributes are not implemented at this t
|
|
|
90
100
|
|
|
91
101
|
h2. To Do
|
|
92
102
|
|
|
93
|
-
* Massive re-factoring - the whole validation process needs to be broken down into something a bit more manageable
|
|
94
103
|
* (Much) More testing
|
|
95
|
-
*
|
|
104
|
+
* Adding a persistence option to schemas that enables the validator to only load schemas once across multiple validations
|
|
@@ -4,8 +4,43 @@ require 'pathname'
|
|
|
4
4
|
require 'bigdecimal'
|
|
5
5
|
|
|
6
6
|
module JSON
|
|
7
|
+
|
|
8
|
+
class ValidationError < Exception
|
|
9
|
+
attr_reader :fragments, :schema
|
|
10
|
+
|
|
11
|
+
def initialize(message, fragments, schema)
|
|
12
|
+
@fragments = fragments
|
|
13
|
+
@schema = schema
|
|
14
|
+
super(message)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
7
18
|
class Validator
|
|
8
19
|
|
|
20
|
+
ValidationMethods = [
|
|
21
|
+
"type",
|
|
22
|
+
"disallow",
|
|
23
|
+
"minimum",
|
|
24
|
+
"maximum",
|
|
25
|
+
"minItems",
|
|
26
|
+
"maxItems",
|
|
27
|
+
"uniqueItems",
|
|
28
|
+
"pattern",
|
|
29
|
+
"minLength",
|
|
30
|
+
"maxLength",
|
|
31
|
+
"divisibleBy",
|
|
32
|
+
"enum",
|
|
33
|
+
"properties",
|
|
34
|
+
"patternProperties",
|
|
35
|
+
"additionalProperties",
|
|
36
|
+
"items",
|
|
37
|
+
"additionalItems",
|
|
38
|
+
"dependencies",
|
|
39
|
+
"extends",
|
|
40
|
+
"$ref"
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
9
44
|
def initialize(schema_data, data)
|
|
10
45
|
@schemas = {}
|
|
11
46
|
@base_schema = initialize_schema(schema_data)
|
|
@@ -16,114 +51,279 @@ module JSON
|
|
|
16
51
|
end
|
|
17
52
|
|
|
18
53
|
|
|
54
|
+
# Run a simple true/false validation of data against a schema
|
|
19
55
|
def validate()
|
|
20
|
-
|
|
56
|
+
begin
|
|
57
|
+
validate_schema(@base_schema, @data, [])
|
|
58
|
+
return true
|
|
59
|
+
rescue ValidationError
|
|
60
|
+
return false
|
|
61
|
+
end
|
|
21
62
|
end
|
|
22
63
|
|
|
23
64
|
|
|
24
|
-
|
|
25
|
-
|
|
65
|
+
# Validate data against a schema, returning nil if the data is valid. If the data is invalid,
|
|
66
|
+
# a ValidationError will be raised with links to the specific location that the first error
|
|
67
|
+
# occurred during validation
|
|
68
|
+
def validate2()
|
|
69
|
+
validate_schema(@base_schema, @data, [])
|
|
70
|
+
nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Validate the current schema
|
|
75
|
+
def validate_schema(current_schema, data, fragments)
|
|
26
76
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return valid if !valid
|
|
32
|
-
if current_schema.schema['disallow']
|
|
33
|
-
valid = !validate_type(current_schema.schema['disallow'],data,current_schema)
|
|
77
|
+
ValidationMethods.each do |method|
|
|
78
|
+
if current_schema.schema[method]
|
|
79
|
+
self.send(("validate_" + method.sub('$','')).to_sym, current_schema, data, fragments)
|
|
80
|
+
end
|
|
34
81
|
end
|
|
35
|
-
return valid if !valid
|
|
36
82
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
83
|
+
data
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# Validate the type
|
|
88
|
+
def validate_type(current_schema, data, fragments, disallow=false)
|
|
89
|
+
union = true
|
|
42
90
|
|
|
43
|
-
if
|
|
44
|
-
|
|
91
|
+
if disallow
|
|
92
|
+
types = current_schema.schema['disallow']
|
|
93
|
+
else
|
|
94
|
+
types = current_schema.schema['type']
|
|
45
95
|
end
|
|
46
|
-
return valid if !valid
|
|
47
96
|
|
|
48
|
-
if
|
|
49
|
-
|
|
97
|
+
if !types.is_a?(Array)
|
|
98
|
+
types = [types]
|
|
99
|
+
union = false
|
|
50
100
|
end
|
|
51
|
-
|
|
101
|
+
valid = false
|
|
52
102
|
|
|
53
|
-
|
|
54
|
-
|
|
103
|
+
types.each do |type|
|
|
104
|
+
if type.is_a?(String)
|
|
105
|
+
case type
|
|
106
|
+
when "string"
|
|
107
|
+
valid = data.is_a?(String)
|
|
108
|
+
when "number"
|
|
109
|
+
valid = data.is_a?(Numeric)
|
|
110
|
+
when "integer"
|
|
111
|
+
valid = data.is_a?(Integer)
|
|
112
|
+
when "boolean"
|
|
113
|
+
valid = (data.is_a?(TrueClass) || data.is_a?(FalseClass))
|
|
114
|
+
when "object"
|
|
115
|
+
valid = data.is_a?(Hash)
|
|
116
|
+
when "array"
|
|
117
|
+
valid = data.is_a?(Array)
|
|
118
|
+
when "null"
|
|
119
|
+
valid = data.is_a?(NilClass)
|
|
120
|
+
when "any"
|
|
121
|
+
valid = true
|
|
122
|
+
else
|
|
123
|
+
valid = true
|
|
124
|
+
end
|
|
125
|
+
elsif type.is_a?(Hash) && union
|
|
126
|
+
# Validate as a schema
|
|
127
|
+
schema = JSON::Schema.new(type,current_schema.uri)
|
|
128
|
+
begin
|
|
129
|
+
validate_schema(schema,data,fragments)
|
|
130
|
+
valid = true
|
|
131
|
+
rescue ValidationError
|
|
132
|
+
# We don't care that these schemas don't validate - we only care that one validated
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
break if valid
|
|
55
137
|
end
|
|
56
|
-
return valid if !valid
|
|
57
138
|
|
|
58
|
-
if
|
|
139
|
+
if (disallow)
|
|
140
|
+
if valid
|
|
141
|
+
message = "The property '#{build_fragment(fragments)}' matched one or more of the following types:"
|
|
142
|
+
types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
|
|
143
|
+
message.chop!
|
|
144
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
145
|
+
end
|
|
146
|
+
elsif !valid
|
|
147
|
+
message = "The property '#{build_fragment(fragments)}' did not match one or more of the following types:"
|
|
148
|
+
types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
|
|
149
|
+
message.chop!
|
|
150
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# Validate the disallowed types
|
|
156
|
+
def validate_disallow(current_schema, data, fragments)
|
|
157
|
+
validate_type(current_schema, data, fragments, true)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# Validate the minimum value of a number
|
|
162
|
+
def validate_minimum(current_schema, data, fragments)
|
|
163
|
+
if data.is_a?(Numeric)
|
|
164
|
+
if (current_schema.schema['exclusiveMinimum'] ? data <= current_schema.schema['minimum'] : data < current_schema.schema['minimum'])
|
|
165
|
+
message = "The property '#{build_fragment(fragments)}' did not have a minimum value of #{current_schema.schema['minimum']}, "
|
|
166
|
+
message += current_schema.schema['exclusiveMinimum'] ? 'exclusively' : 'inclusively'
|
|
167
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# Validate the maximum value of a number
|
|
174
|
+
def validate_maximum(current_schema, data, fragments)
|
|
175
|
+
if data.is_a?(Numeric)
|
|
176
|
+
if (current_schema.schema['exclusiveMaximum'] ? data >= current_schema.schema['maximum'] : data > current_schema.schema['maximum'])
|
|
177
|
+
message = "The property '#{build_fragment(fragments)}' did not have a maximum value of #{current_schema.schema['maximum']}, "
|
|
178
|
+
message += current_schema.schema['exclusiveMaximum'] ? 'exclusively' : 'inclusively'
|
|
179
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# Validate the minimum number of items in an array
|
|
186
|
+
def validate_minItems(current_schema, data, fragments)
|
|
187
|
+
if data.is_a?(Array) && (data.nitems < current_schema.schema['minItems'])
|
|
188
|
+
message = "The property '#{build_fragment(fragments)}' did not contain a minimum number of items #{current_schema.schema['minItems']}"
|
|
189
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# Validate the maximum number of items in an array
|
|
195
|
+
def validate_maxItems(current_schema, data, fragments)
|
|
196
|
+
if data.is_a?(Array) && (data.nitems > current_schema.schema['maxItems'])
|
|
197
|
+
message = "The property '#{build_fragment(fragments)}' did not contain a minimum number of items #{current_schema.schema['minItems']}"
|
|
198
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# Validate the uniqueness of elements in an array
|
|
204
|
+
def validate_uniqueItems(current_schema, data, fragments)
|
|
205
|
+
if data.is_a?(Array)
|
|
59
206
|
d = data.clone
|
|
60
207
|
dupes = d.uniq!
|
|
61
|
-
|
|
208
|
+
if dupes
|
|
209
|
+
message = "The property '#{build_fragment(fragments)}' contained duplicated array values"
|
|
210
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
211
|
+
end
|
|
62
212
|
end
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# Validate a string matches a regex pattern
|
|
217
|
+
def validate_pattern(current_schema, data, fragments)
|
|
218
|
+
if data.is_a?(String)
|
|
66
219
|
r = Regexp.new(current_schema.schema['pattern'])
|
|
67
|
-
|
|
220
|
+
if (r.match(data)).nil?
|
|
221
|
+
message = "The property '#{build_fragment(fragments)}' did not match the regex '#{current_schema.schema['pattern']}'"
|
|
222
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
223
|
+
end
|
|
68
224
|
end
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# Validate a string is at least of a certain length
|
|
229
|
+
def validate_minLength(current_schema, data, fragments)
|
|
230
|
+
if data.is_a?(String)
|
|
231
|
+
if data.length < current_schema.schema['minLength']
|
|
232
|
+
message = "The property '#{build_fragment(fragments)}' was not of a minimum string length of #{current_schema.schema['minLength']}"
|
|
233
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
234
|
+
end
|
|
73
235
|
end
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# Validate a string is at maximum of a certain length
|
|
240
|
+
def validate_maxLength(current_schema, data, fragments)
|
|
241
|
+
if data.is_a?(String)
|
|
242
|
+
if data.length > current_schema.schema['maxLength']
|
|
243
|
+
message = "The property '#{build_fragment(fragments)}' was not of a maximum string length of #{current_schema.schema['maxLength']}"
|
|
244
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
245
|
+
end
|
|
78
246
|
end
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# Validate a numeric is divisible by another numeric
|
|
251
|
+
def validate_divisibleBy(current_schema, data, fragments)
|
|
252
|
+
if data.is_a?(Numeric)
|
|
253
|
+
if current_schema.schema['divisibleBy'] == 0 ||
|
|
254
|
+
current_schema.schema['divisibleBy'] == 0.0 ||
|
|
255
|
+
(BigDecimal.new(data.to_s) % BigDecimal.new(current_schema.schema['divisibleBy'].to_s)).to_f != 0
|
|
256
|
+
message = "The property '#{build_fragment(fragments)}' was not divisible by #{current_schema.schema['divisibleBy']}"
|
|
257
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
86
258
|
end
|
|
87
259
|
end
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# Validate an item matches at least one of an array of values
|
|
264
|
+
def validate_enum(current_schema, data, fragments)
|
|
265
|
+
if !current_schema.schema['enum'].include?(data)
|
|
266
|
+
message = "The property '#{build_fragment(fragments)}' did not match one of the following values:"
|
|
267
|
+
current_schema.schema['enum'].each {|val|
|
|
268
|
+
if val.is_a?(NilClass)
|
|
269
|
+
message += " null,"
|
|
270
|
+
elsif val.is_a?(Array)
|
|
271
|
+
message += " (array),"
|
|
272
|
+
elsif val.is_a?(Hash)
|
|
273
|
+
message += " (object),"
|
|
274
|
+
else
|
|
275
|
+
message += " #{val.to_s},"
|
|
276
|
+
end
|
|
277
|
+
}
|
|
278
|
+
message.chop!
|
|
279
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
92
280
|
end
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
# Validate a set of properties of an object
|
|
285
|
+
def validate_properties(current_schema, data, fragments)
|
|
286
|
+
if data.is_a?(Hash)
|
|
97
287
|
current_schema.schema['properties'].each do |property,property_schema|
|
|
98
|
-
|
|
288
|
+
if (property_schema['required'] && !data.has_key?(property))
|
|
289
|
+
message = "The property '#{build_fragment(fragments)}' did not contain a required property of '#{property}'"
|
|
290
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
291
|
+
end
|
|
99
292
|
|
|
100
293
|
if data.has_key?(property)
|
|
101
294
|
schema = JSON::Schema.new(property_schema,current_schema.uri)
|
|
102
|
-
|
|
295
|
+
fragments << property
|
|
296
|
+
validate_schema(schema, data[property], fragments)
|
|
297
|
+
fragments.pop
|
|
103
298
|
end
|
|
104
|
-
return valid if !valid
|
|
105
299
|
end
|
|
106
300
|
end
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# Validate properties of an object against a schema when the property name matches a specific regex
|
|
305
|
+
def validate_patternProperties(current_schema, data, fragments)
|
|
306
|
+
if data.is_a?(Hash)
|
|
307
|
+
current_schema.schema['patternProperties'].each do |property,property_schema|
|
|
112
308
|
r = Regexp.new(property)
|
|
113
309
|
|
|
114
310
|
# Check each key in the data hash to see if it matches the regex
|
|
115
311
|
data.each do |key,value|
|
|
116
312
|
if r.match(key)
|
|
117
313
|
schema = JSON::Schema.new(property_schema,current_schema.uri)
|
|
118
|
-
|
|
314
|
+
fragments << key
|
|
315
|
+
validate_schema(schema, data[key], fragments)
|
|
316
|
+
fragments.pop
|
|
119
317
|
end
|
|
120
|
-
return valid if !valid
|
|
121
318
|
end
|
|
122
319
|
end
|
|
123
320
|
end
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# Validate properties of an object that are not defined in the schema at least validate against a set of rules
|
|
325
|
+
def validate_additionalProperties(current_schema, data, fragments)
|
|
326
|
+
if data.is_a?(Hash)
|
|
127
327
|
extra_properties = data.keys
|
|
128
328
|
|
|
129
329
|
if current_schema.schema['properties']
|
|
@@ -142,162 +342,145 @@ module JSON
|
|
|
142
342
|
end
|
|
143
343
|
end
|
|
144
344
|
|
|
145
|
-
if current_schema.schema['additionalProperties'] == false
|
|
146
|
-
|
|
345
|
+
if current_schema.schema['additionalProperties'] == false && !extra_properties.empty?
|
|
346
|
+
message = "The property '#{build_fragment(fragments)}' contains additional properties outside of the schema when none are allowed"
|
|
347
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
147
348
|
elsif current_schema.schema['additionalProperties'].is_a?(Hash)
|
|
148
349
|
data.each do |key,value|
|
|
149
350
|
schema = JSON::Schema.new(current_schema.schema['additionalProperties'],current_schema.uri)
|
|
150
|
-
|
|
151
|
-
|
|
351
|
+
fragments << key
|
|
352
|
+
validate_schema(schema, value, fragments)
|
|
353
|
+
fragments.pop
|
|
152
354
|
end
|
|
153
355
|
end
|
|
154
356
|
end
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
# Validate items in an array match a schema or a set of schemas
|
|
361
|
+
def validate_items(current_schema, data, fragments)
|
|
362
|
+
if data.is_a?(Array)
|
|
363
|
+
if current_schema.schema['items'].is_a?(Hash)
|
|
364
|
+
data.each_with_index do |item,i|
|
|
365
|
+
schema = JSON::Schema.new(current_schema.schema['items'],current_schema.uri)
|
|
366
|
+
fragments << i.to_s
|
|
367
|
+
validate_schema(schema,item,fragments)
|
|
368
|
+
fragments.pop
|
|
369
|
+
end
|
|
370
|
+
elsif current_schema.schema['items'].is_a?(Array)
|
|
371
|
+
current_schema.schema['items'].each_with_index do |item_schema,i|
|
|
372
|
+
schema = JSON::Schema.new(item_schema,current_schema.uri)
|
|
373
|
+
fragments << i.to_s
|
|
374
|
+
validate_schema(schema,data[i],fragments)
|
|
375
|
+
fragments.pop
|
|
376
|
+
end
|
|
377
|
+
end
|
|
171
378
|
end
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# Validate items in an array that are not part of the schema at least match a set of rules
|
|
383
|
+
def validate_additionalItems(current_schema, data, fragments)
|
|
384
|
+
if data.is_a?(Array) && current_schema.schema['items'].is_a?(Array)
|
|
385
|
+
if current_schema.schema['additionalItems'] == false && current_schema.schema['items'].length != data.length
|
|
386
|
+
message = "The property '#{build_fragment(fragments)}' contains additional array elements outside of the schema when none are allowed"
|
|
387
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
177
388
|
elsif current_schema.schema['additionaItems'].is_a?(Hash)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
389
|
+
schema = JSON::Schema.new(current_schema.schema['additionalItems'],current_schema.uri)
|
|
390
|
+
data.each_with_index do |item,i|
|
|
391
|
+
if i >= current_schema.schema['items'].length
|
|
392
|
+
fragments << i.to_s
|
|
393
|
+
validate_schema(schema, item, fragments)
|
|
394
|
+
fragments.pop
|
|
395
|
+
end
|
|
182
396
|
end
|
|
183
397
|
end
|
|
184
398
|
end
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
# Validate the dependencies of a property
|
|
403
|
+
def validate_dependencies(current_schema, data, fragments)
|
|
404
|
+
if data.is_a?(Hash)
|
|
188
405
|
current_schema.schema['dependencies'].each do |property,dependency_value|
|
|
189
406
|
if data.has_key?(property)
|
|
190
|
-
if dependency_value.is_a?(String)
|
|
191
|
-
|
|
407
|
+
if dependency_value.is_a?(String) && !data.has_key?(dependency_value)
|
|
408
|
+
message = "The property '#{build_fragment(fragments)}' has a property '#{property}' that depends on a missing property '#{dependency_value}'"
|
|
409
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
192
410
|
elsif dependency_value.is_a?(Array)
|
|
193
411
|
dependency_value.each do |value|
|
|
194
|
-
|
|
195
|
-
|
|
412
|
+
if !data.has_key?(value)
|
|
413
|
+
message = "The property '#{build_fragment(fragments)}' has a property '#{property}' that depends on a missing property '#{value}'"
|
|
414
|
+
raise ValidationError.new(message, fragments, current_schema)
|
|
415
|
+
end
|
|
196
416
|
end
|
|
197
417
|
else
|
|
198
418
|
schema = JSON::Schema.new(dependency_value,current_schema.uri)
|
|
199
|
-
|
|
200
|
-
end
|
|
201
|
-
return valid if !valid
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
return valid if !valid
|
|
206
|
-
|
|
207
|
-
# We're referencing another schema
|
|
208
|
-
if current_schema.schema['$ref']
|
|
209
|
-
temp_uri = URI.parse(current_schema.schema['$ref'])
|
|
210
|
-
if temp_uri.relative?
|
|
211
|
-
temp_uri = current_schema.uri.clone
|
|
212
|
-
# Check for absolute path
|
|
213
|
-
path = current_schema.schema['$ref'].split("#")[0]
|
|
214
|
-
if path[0,1] == "/"
|
|
215
|
-
temp_uri.path = Pathname.new(path).cleanpath
|
|
216
|
-
else
|
|
217
|
-
temp_uri.path = (Pathname.new(current_schema.uri.path).parent + path).cleanpath
|
|
218
|
-
end
|
|
219
|
-
temp_uri.fragment = current_schema.schema['$ref'].split("#")[1]
|
|
220
|
-
end
|
|
221
|
-
temp_uri.fragment = "" if temp_uri.fragment.nil?
|
|
222
|
-
|
|
223
|
-
# Grab the parent schema from the schema list
|
|
224
|
-
schema_key = temp_uri.to_s.split("#")[0]
|
|
225
|
-
ref_schema = @schemas[schema_key]
|
|
226
|
-
|
|
227
|
-
if ref_schema
|
|
228
|
-
# Perform fragment resolution to retrieve the appropriate level for the schema
|
|
229
|
-
target_schema = ref_schema.schema
|
|
230
|
-
fragments = temp_uri.fragment.split("/")
|
|
231
|
-
fragments.each do |fragment|
|
|
232
|
-
if fragment && fragment != ''
|
|
233
|
-
if target_schema.is_a?(Array)
|
|
234
|
-
target_schema = target_schema[fragment.to_i]
|
|
235
|
-
else
|
|
236
|
-
target_schema = target_schema[fragment]
|
|
237
|
-
end
|
|
419
|
+
validate_schema(schema, data, fragments)
|
|
238
420
|
end
|
|
239
421
|
end
|
|
240
|
-
|
|
241
|
-
# We have the schema finally, build it and validate!
|
|
242
|
-
schema = JSON::Schema.new(target_schema,temp_uri)
|
|
243
|
-
valid = validate_schema(schema, data)
|
|
244
|
-
end
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
if current_schema.schema['extends']
|
|
248
|
-
schemas = current_schema.schema['extends']
|
|
249
|
-
schemas = [schemas] if !schema.is_a?(Array)
|
|
250
|
-
schemas.each do |s|
|
|
251
|
-
schema = JSON::Schema.new(s,current_schema.uri)
|
|
252
|
-
valid = validate_schema(schema, item)
|
|
253
|
-
return valid if !valid
|
|
254
422
|
end
|
|
255
423
|
end
|
|
256
|
-
|
|
257
|
-
valid
|
|
258
424
|
end
|
|
259
425
|
|
|
260
426
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
427
|
+
# Validate extensions of other schemas
|
|
428
|
+
def validate_extends(current_schema, data, fragments)
|
|
429
|
+
schemas = current_schema.schema['extends']
|
|
430
|
+
schemas = [schemas] if !schemas.is_a?(Array)
|
|
431
|
+
schemas.each do |s|
|
|
432
|
+
schema = JSON::Schema.new(s,current_schema.uri)
|
|
433
|
+
validate_schema(schema, data, fragments)
|
|
266
434
|
end
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
# Validate schema references
|
|
439
|
+
def validate_ref(current_schema, data, fragments)
|
|
440
|
+
temp_uri = URI.parse(current_schema.schema['$ref'])
|
|
441
|
+
if temp_uri.relative?
|
|
442
|
+
temp_uri = current_schema.uri.clone
|
|
443
|
+
# Check for absolute path
|
|
444
|
+
path = current_schema.schema['$ref'].split("#")[0]
|
|
445
|
+
if path[0,1] == "/"
|
|
446
|
+
temp_uri.path = Pathname.new(path).cleanpath
|
|
447
|
+
else
|
|
448
|
+
temp_uri.path = (Pathname.new(current_schema.uri.path).parent + path).cleanpath
|
|
449
|
+
end
|
|
450
|
+
temp_uri.fragment = current_schema.schema['$ref'].split("#")[1]
|
|
451
|
+
end
|
|
452
|
+
temp_uri.fragment = "" if temp_uri.fragment.nil?
|
|
453
|
+
|
|
454
|
+
# Grab the parent schema from the schema list
|
|
455
|
+
schema_key = temp_uri.to_s.split("#")[0]
|
|
456
|
+
ref_schema = @schemas[schema_key]
|
|
457
|
+
|
|
458
|
+
if ref_schema
|
|
459
|
+
# Perform fragment resolution to retrieve the appropriate level for the schema
|
|
460
|
+
target_schema = ref_schema.schema
|
|
461
|
+
fragments = temp_uri.fragment.split("/")
|
|
462
|
+
fragments.each do |fragment|
|
|
463
|
+
if fragment && fragment != ''
|
|
464
|
+
if target_schema.is_a?(Array)
|
|
465
|
+
target_schema = target_schema[fragment.to_i]
|
|
466
|
+
else
|
|
467
|
+
target_schema = target_schema[fragment]
|
|
468
|
+
end
|
|
288
469
|
end
|
|
289
|
-
elsif type.is_a?(Hash) && union
|
|
290
|
-
# Validate as a schema
|
|
291
|
-
schema = JSON::Schema.new(type,current_schema.uri)
|
|
292
|
-
valid = validate_schema(schema,data)
|
|
293
470
|
end
|
|
294
|
-
|
|
295
|
-
|
|
471
|
+
|
|
472
|
+
# We have the schema finally, build it and validate!
|
|
473
|
+
schema = JSON::Schema.new(target_schema,temp_uri)
|
|
474
|
+
validate_schema(schema, data, fragments)
|
|
296
475
|
end
|
|
297
|
-
|
|
298
|
-
valid
|
|
299
476
|
end
|
|
300
477
|
|
|
478
|
+
|
|
479
|
+
def build_fragment(fragments)
|
|
480
|
+
"#/#{fragments.join('/')}"
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
|
|
301
484
|
def load_ref_schema(parent_schema,ref)
|
|
302
485
|
uri = URI.parse(ref)
|
|
303
486
|
if uri.relative?
|
|
@@ -315,7 +498,6 @@ module JSON
|
|
|
315
498
|
if @schemas[uri.to_s].nil?
|
|
316
499
|
begin
|
|
317
500
|
schema = JSON::Schema.new(JSON.parse(open(uri.to_s).read), uri)
|
|
318
|
-
@schemas[uri.to_s] = schema
|
|
319
501
|
build_schemas(schema)
|
|
320
502
|
rescue
|
|
321
503
|
# Failures will occur when this URI cannot be referenced yet. Don't worry about it,
|
|
@@ -436,6 +618,19 @@ module JSON
|
|
|
436
618
|
build_schemas(schema)
|
|
437
619
|
end
|
|
438
620
|
end
|
|
621
|
+
|
|
622
|
+
if parent_schema.schema["extends"].is_a?(Hash)
|
|
623
|
+
if parent_schema.schema["extends"]['$ref']
|
|
624
|
+
load_ref_schema(parent_schema, parent_schema.schema["extends"]['$ref'])
|
|
625
|
+
else
|
|
626
|
+
schema_uri = parent_schema.uri.clone
|
|
627
|
+
schema = JSON::Schema.new(parent_schema.schema["extends"],schema_uri)
|
|
628
|
+
if parent_schema.schema["extends"]['id']
|
|
629
|
+
@schemas[schema.uri.to_s] = schema
|
|
630
|
+
end
|
|
631
|
+
build_schemas(schema)
|
|
632
|
+
end
|
|
633
|
+
end
|
|
439
634
|
end
|
|
440
635
|
|
|
441
636
|
|
|
@@ -444,7 +639,13 @@ module JSON
|
|
|
444
639
|
validator = JSON::Validator.new(schema, data)
|
|
445
640
|
validator.validate
|
|
446
641
|
end
|
|
642
|
+
|
|
643
|
+
def validate2(schema, data)
|
|
644
|
+
validator = JSON::Validator.new(schema, data)
|
|
645
|
+
validator.validate2
|
|
646
|
+
end
|
|
447
647
|
end
|
|
648
|
+
|
|
448
649
|
|
|
449
650
|
|
|
450
651
|
private
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: json-schema
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 29
|
|
5
5
|
prerelease: false
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
8
|
- 1
|
|
9
|
-
-
|
|
10
|
-
version: 0.1.
|
|
9
|
+
- 3
|
|
10
|
+
version: 0.1.3
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Kenny Hoxworth
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2010-11-
|
|
18
|
+
date: 2010-11-30 00:00:00 -05:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies:
|
|
21
21
|
- !ruby/object:Gem::Dependency
|