json-schema-ouidou 2.9.0
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 +7 -0
- data/LICENSE.md +19 -0
- data/README.md +496 -0
- data/lib/json-schema/attribute.rb +57 -0
- data/lib/json-schema/attributes/additionalitems.rb +28 -0
- data/lib/json-schema/attributes/additionalproperties.rb +58 -0
- data/lib/json-schema/attributes/allof.rb +39 -0
- data/lib/json-schema/attributes/anyof.rb +47 -0
- data/lib/json-schema/attributes/dependencies.rb +38 -0
- data/lib/json-schema/attributes/dependencies_v4.rb +11 -0
- data/lib/json-schema/attributes/disallow.rb +12 -0
- data/lib/json-schema/attributes/divisibleby.rb +22 -0
- data/lib/json-schema/attributes/enum.rb +24 -0
- data/lib/json-schema/attributes/extends.rb +48 -0
- data/lib/json-schema/attributes/format.rb +14 -0
- data/lib/json-schema/attributes/formats/custom.rb +21 -0
- data/lib/json-schema/attributes/formats/date.rb +25 -0
- data/lib/json-schema/attributes/formats/date_time.rb +34 -0
- data/lib/json-schema/attributes/formats/date_time_v4.rb +15 -0
- data/lib/json-schema/attributes/formats/ip.rb +41 -0
- data/lib/json-schema/attributes/formats/time.rb +22 -0
- data/lib/json-schema/attributes/formats/uri.rb +18 -0
- data/lib/json-schema/attributes/items.rb +27 -0
- data/lib/json-schema/attributes/limit.rb +52 -0
- data/lib/json-schema/attributes/limits/items.rb +15 -0
- data/lib/json-schema/attributes/limits/length.rb +15 -0
- data/lib/json-schema/attributes/limits/max_items.rb +15 -0
- data/lib/json-schema/attributes/limits/max_length.rb +15 -0
- data/lib/json-schema/attributes/limits/max_properties.rb +15 -0
- data/lib/json-schema/attributes/limits/maximum.rb +15 -0
- data/lib/json-schema/attributes/limits/maximum_inclusive.rb +11 -0
- data/lib/json-schema/attributes/limits/min_items.rb +15 -0
- data/lib/json-schema/attributes/limits/min_length.rb +15 -0
- data/lib/json-schema/attributes/limits/min_properties.rb +15 -0
- data/lib/json-schema/attributes/limits/minimum.rb +15 -0
- data/lib/json-schema/attributes/limits/minimum_inclusive.rb +11 -0
- data/lib/json-schema/attributes/limits/numeric.rb +16 -0
- data/lib/json-schema/attributes/limits/properties.rb +15 -0
- data/lib/json-schema/attributes/maxdecimal.rb +18 -0
- data/lib/json-schema/attributes/multipleof.rb +11 -0
- data/lib/json-schema/attributes/not.rb +30 -0
- data/lib/json-schema/attributes/oneof.rb +56 -0
- data/lib/json-schema/attributes/pattern.rb +18 -0
- data/lib/json-schema/attributes/patternproperties.rb +22 -0
- data/lib/json-schema/attributes/properties.rb +66 -0
- data/lib/json-schema/attributes/properties_optional.rb +26 -0
- data/lib/json-schema/attributes/properties_v4.rb +13 -0
- data/lib/json-schema/attributes/ref.rb +61 -0
- data/lib/json-schema/attributes/required.rb +28 -0
- data/lib/json-schema/attributes/type.rb +73 -0
- data/lib/json-schema/attributes/type_v4.rb +29 -0
- data/lib/json-schema/attributes/uniqueitems.rb +20 -0
- data/lib/json-schema/errors/custom_format_error.rb +6 -0
- data/lib/json-schema/errors/json_load_error.rb +6 -0
- data/lib/json-schema/errors/json_parse_error.rb +6 -0
- data/lib/json-schema/errors/schema_error.rb +6 -0
- data/lib/json-schema/errors/schema_parse_error.rb +8 -0
- data/lib/json-schema/errors/uri_error.rb +6 -0
- data/lib/json-schema/errors/validation_error.rb +46 -0
- data/lib/json-schema/schema/reader.rb +140 -0
- data/lib/json-schema/schema/validator.rb +40 -0
- data/lib/json-schema/schema.rb +62 -0
- data/lib/json-schema/util/array_set.rb +20 -0
- data/lib/json-schema/util/uri.rb +110 -0
- data/lib/json-schema/util/uuid.rb +284 -0
- data/lib/json-schema/validator.rb +609 -0
- data/lib/json-schema/validators/draft1.rb +45 -0
- data/lib/json-schema/validators/draft2.rb +46 -0
- data/lib/json-schema/validators/draft3.rb +50 -0
- data/lib/json-schema/validators/draft4.rb +56 -0
- data/lib/json-schema/validators/draft6.rb +56 -0
- data/lib/json-schema/validators/hyper-draft1.rb +13 -0
- data/lib/json-schema/validators/hyper-draft2.rb +13 -0
- data/lib/json-schema/validators/hyper-draft3.rb +13 -0
- data/lib/json-schema/validators/hyper-draft4.rb +13 -0
- data/lib/json-schema/validators/hyper-draft6.rb +13 -0
- data/lib/json-schema.rb +18 -0
- data/resources/draft-01.json +155 -0
- data/resources/draft-02.json +166 -0
- data/resources/draft-03.json +174 -0
- data/resources/draft-04.json +150 -0
- data/resources/draft-06.json +150 -0
- metadata +197 -0
@@ -0,0 +1,609 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'pathname'
|
3
|
+
require 'bigdecimal'
|
4
|
+
require 'digest/sha1'
|
5
|
+
require 'date'
|
6
|
+
require 'thread'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
require 'json-schema/schema/reader'
|
10
|
+
require 'json-schema/errors/schema_error'
|
11
|
+
require 'json-schema/errors/schema_parse_error'
|
12
|
+
require 'json-schema/errors/json_load_error'
|
13
|
+
require 'json-schema/errors/json_parse_error'
|
14
|
+
require 'json-schema/util/uri'
|
15
|
+
|
16
|
+
module JSON
|
17
|
+
|
18
|
+
class Validator
|
19
|
+
|
20
|
+
@@schemas = {}
|
21
|
+
@@cache_schemas = true
|
22
|
+
@@default_opts = {
|
23
|
+
:list => false,
|
24
|
+
:version => nil,
|
25
|
+
:validate_schema => false,
|
26
|
+
:record_errors => false,
|
27
|
+
:errors_as_objects => false,
|
28
|
+
:insert_defaults => false,
|
29
|
+
:clear_cache => false,
|
30
|
+
:strict => false,
|
31
|
+
:parse_data => true
|
32
|
+
}
|
33
|
+
@@validators = {}
|
34
|
+
@@default_validator = nil
|
35
|
+
@@available_json_backends = []
|
36
|
+
@@json_backend = nil
|
37
|
+
@@serializer = nil
|
38
|
+
@@mutex = Mutex.new
|
39
|
+
|
40
|
+
def initialize(schema_data, data, opts={})
|
41
|
+
@options = @@default_opts.clone.merge(opts)
|
42
|
+
@errors = []
|
43
|
+
|
44
|
+
validator = self.class.validator_for_name(@options[:version])
|
45
|
+
@options[:version] = validator
|
46
|
+
@options[:schema_reader] ||= self.class.schema_reader
|
47
|
+
|
48
|
+
@validation_options = @options[:record_errors] ? {:record_errors => true} : {}
|
49
|
+
@validation_options[:insert_defaults] = true if @options[:insert_defaults]
|
50
|
+
@validation_options[:strict] = true if @options[:strict] == true
|
51
|
+
@validation_options[:clear_cache] = true if !@@cache_schemas || @options[:clear_cache]
|
52
|
+
|
53
|
+
@@mutex.synchronize { @base_schema = initialize_schema(schema_data) }
|
54
|
+
@original_data = data
|
55
|
+
@data = initialize_data(data)
|
56
|
+
@@mutex.synchronize { build_schemas(@base_schema) }
|
57
|
+
|
58
|
+
# validate the schema, if requested
|
59
|
+
if @options[:validate_schema]
|
60
|
+
if @base_schema.schema["$schema"]
|
61
|
+
base_validator = self.class.validator_for_name(@base_schema.schema["$schema"])
|
62
|
+
end
|
63
|
+
metaschema = base_validator ? base_validator.metaschema : validator.metaschema
|
64
|
+
# Don't clear the cache during metaschema validation!
|
65
|
+
self.class.validate!(metaschema, @base_schema.schema, {:clear_cache => false})
|
66
|
+
end
|
67
|
+
|
68
|
+
# If the :fragment option is set, try and validate against the fragment
|
69
|
+
if opts[:fragment]
|
70
|
+
@base_schema = schema_from_fragment(@base_schema, opts[:fragment])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def schema_from_fragment(base_schema, fragment)
|
75
|
+
schema_uri = base_schema.uri
|
76
|
+
fragments = fragment.split("/")
|
77
|
+
|
78
|
+
# ensure the first element was a hash, per the fragment spec
|
79
|
+
if fragments.shift != "#"
|
80
|
+
raise JSON::Schema::SchemaError.new("Invalid fragment syntax in :fragment option")
|
81
|
+
end
|
82
|
+
|
83
|
+
fragments.each do |f|
|
84
|
+
if base_schema.is_a?(JSON::Schema) #test if fragment is a JSON:Schema instance
|
85
|
+
if !base_schema.schema.has_key?(f)
|
86
|
+
raise JSON::Schema::SchemaError.new("Invalid fragment resolution for :fragment option")
|
87
|
+
end
|
88
|
+
base_schema = base_schema.schema[f]
|
89
|
+
elsif base_schema.is_a?(Hash)
|
90
|
+
if !base_schema.has_key?(f)
|
91
|
+
raise JSON::Schema::SchemaError.new("Invalid fragment resolution for :fragment option")
|
92
|
+
end
|
93
|
+
base_schema = JSON::Schema.new(base_schema[f],schema_uri,@options[:version])
|
94
|
+
elsif base_schema.is_a?(Array)
|
95
|
+
if base_schema[f.to_i].nil?
|
96
|
+
raise JSON::Schema::SchemaError.new("Invalid fragment resolution for :fragment option")
|
97
|
+
end
|
98
|
+
base_schema = JSON::Schema.new(base_schema[f.to_i],schema_uri,@options[:version])
|
99
|
+
else
|
100
|
+
raise JSON::Schema::SchemaError.new("Invalid schema encountered when resolving :fragment option")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
if @options[:list]
|
105
|
+
base_schema.to_array_schema
|
106
|
+
elsif base_schema.is_a?(Hash)
|
107
|
+
JSON::Schema.new(base_schema, schema_uri, @options[:version])
|
108
|
+
else
|
109
|
+
base_schema
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Run a simple true/false validation of data against a schema
|
114
|
+
def validate
|
115
|
+
@base_schema.validate(@data,[],self,@validation_options)
|
116
|
+
|
117
|
+
if @options[:record_errors]
|
118
|
+
if @options[:errors_as_objects]
|
119
|
+
@errors.map{|e| e.to_hash}
|
120
|
+
else
|
121
|
+
@errors.map{|e| e.to_string}
|
122
|
+
end
|
123
|
+
else
|
124
|
+
true
|
125
|
+
end
|
126
|
+
ensure
|
127
|
+
if @validation_options[:clear_cache] == true
|
128
|
+
self.class.clear_cache
|
129
|
+
end
|
130
|
+
if @validation_options[:insert_defaults]
|
131
|
+
self.class.merge_missing_values(@data, @original_data)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def load_ref_schema(parent_schema, ref)
|
136
|
+
schema_uri = JSON::Util::URI.absolutize_ref(ref, parent_schema.uri)
|
137
|
+
return true if self.class.schema_loaded?(schema_uri)
|
138
|
+
|
139
|
+
validator = self.class.validator_for_uri(schema_uri, false)
|
140
|
+
schema_uri = JSON::Util::URI.file_uri(validator.metaschema) if validator
|
141
|
+
|
142
|
+
schema = @options[:schema_reader].read(schema_uri)
|
143
|
+
self.class.add_schema(schema)
|
144
|
+
build_schemas(schema)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Build all schemas with IDs, mapping out the namespace
|
148
|
+
def build_schemas(parent_schema)
|
149
|
+
schema = parent_schema.schema
|
150
|
+
|
151
|
+
# Build ref schemas if they exist
|
152
|
+
if schema["$ref"]
|
153
|
+
load_ref_schema(parent_schema, schema["$ref"])
|
154
|
+
end
|
155
|
+
|
156
|
+
case schema["extends"]
|
157
|
+
when String
|
158
|
+
load_ref_schema(parent_schema, schema["extends"])
|
159
|
+
when Array
|
160
|
+
schema['extends'].each do |type|
|
161
|
+
handle_schema(parent_schema, type)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Check for schemas in union types
|
166
|
+
["type", "disallow"].each do |key|
|
167
|
+
if schema[key].is_a?(Array)
|
168
|
+
schema[key].each do |type|
|
169
|
+
if type.is_a?(Hash)
|
170
|
+
handle_schema(parent_schema, type)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Schema properties whose values are objects, the values of which
|
177
|
+
# are themselves schemas.
|
178
|
+
%w[definitions properties patternProperties].each do |key|
|
179
|
+
next unless value = schema[key]
|
180
|
+
value.each do |k, inner_schema|
|
181
|
+
handle_schema(parent_schema, inner_schema)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Schema properties whose values are themselves schemas.
|
186
|
+
%w[additionalProperties additionalItems dependencies extends].each do |key|
|
187
|
+
next unless schema[key].is_a?(Hash)
|
188
|
+
handle_schema(parent_schema, schema[key])
|
189
|
+
end
|
190
|
+
|
191
|
+
# Schema properties whose values may be an array of schemas.
|
192
|
+
%w[allOf anyOf oneOf not].each do |key|
|
193
|
+
next unless value = schema[key]
|
194
|
+
Array(value).each do |inner_schema|
|
195
|
+
handle_schema(parent_schema, inner_schema)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Items are always schemas
|
200
|
+
if schema["items"]
|
201
|
+
items = schema["items"].clone
|
202
|
+
items = [items] unless items.is_a?(Array)
|
203
|
+
|
204
|
+
items.each do |item|
|
205
|
+
handle_schema(parent_schema, item)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Convert enum to a ArraySet
|
210
|
+
if schema["enum"].is_a?(Array)
|
211
|
+
schema["enum"] = ArraySet.new(schema["enum"])
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
# Either load a reference schema or create a new schema
|
217
|
+
def handle_schema(parent_schema, obj)
|
218
|
+
if obj.is_a?(Hash)
|
219
|
+
schema_uri = parent_schema.uri.dup
|
220
|
+
schema = JSON::Schema.new(obj, schema_uri, parent_schema.validator)
|
221
|
+
if obj['id']
|
222
|
+
self.class.add_schema(schema)
|
223
|
+
end
|
224
|
+
build_schemas(schema)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def validation_error(error)
|
229
|
+
@errors.push(error)
|
230
|
+
end
|
231
|
+
|
232
|
+
def validation_errors
|
233
|
+
@errors
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
class << self
|
238
|
+
def validate(schema, data,opts={})
|
239
|
+
begin
|
240
|
+
validate!(schema, data, opts)
|
241
|
+
rescue JSON::Schema::ValidationError, JSON::Schema::SchemaError
|
242
|
+
return false
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def validate_json(schema, data, opts={})
|
247
|
+
validate(schema, data, opts.merge(:json => true))
|
248
|
+
end
|
249
|
+
|
250
|
+
def validate_uri(schema, data, opts={})
|
251
|
+
validate(schema, data, opts.merge(:uri => true))
|
252
|
+
end
|
253
|
+
|
254
|
+
def validate!(schema, data,opts={})
|
255
|
+
validator = new(schema, data, opts)
|
256
|
+
validator.validate
|
257
|
+
end
|
258
|
+
|
259
|
+
def validate2(schema, data, opts={})
|
260
|
+
warn "[DEPRECATION NOTICE] JSON::Validator#validate2 has been replaced by JSON::Validator#validate! and will be removed in version >= 3. Please use the #validate! method instead."
|
261
|
+
validate!(schema, data, opts)
|
262
|
+
end
|
263
|
+
|
264
|
+
def validate_json!(schema, data, opts={})
|
265
|
+
validate!(schema, data, opts.merge(:json => true))
|
266
|
+
end
|
267
|
+
|
268
|
+
def validate_uri!(schema, data, opts={})
|
269
|
+
validate!(schema, data, opts.merge(:uri => true))
|
270
|
+
end
|
271
|
+
|
272
|
+
def fully_validate(schema, data, opts={})
|
273
|
+
validate!(schema, data, opts.merge(:record_errors => true))
|
274
|
+
end
|
275
|
+
|
276
|
+
def fully_validate_schema(schema, opts={})
|
277
|
+
data = schema
|
278
|
+
schema = validator_for_name(opts[:version]).metaschema
|
279
|
+
fully_validate(schema, data, opts)
|
280
|
+
end
|
281
|
+
|
282
|
+
def fully_validate_json(schema, data, opts={})
|
283
|
+
fully_validate(schema, data, opts.merge(:json => true))
|
284
|
+
end
|
285
|
+
|
286
|
+
def fully_validate_uri(schema, data, opts={})
|
287
|
+
fully_validate(schema, data, opts.merge(:uri => true))
|
288
|
+
end
|
289
|
+
|
290
|
+
def schema_reader
|
291
|
+
@@schema_reader ||= JSON::Schema::Reader.new
|
292
|
+
end
|
293
|
+
|
294
|
+
def schema_reader=(reader)
|
295
|
+
@@schema_reader = reader
|
296
|
+
end
|
297
|
+
|
298
|
+
def clear_cache
|
299
|
+
@@schemas = {}
|
300
|
+
JSON::Util::URI.clear_cache
|
301
|
+
end
|
302
|
+
|
303
|
+
def schemas
|
304
|
+
@@schemas
|
305
|
+
end
|
306
|
+
|
307
|
+
def add_schema(schema)
|
308
|
+
@@schemas[schema_key_for(schema.uri)] ||= schema
|
309
|
+
end
|
310
|
+
|
311
|
+
def schema_for_uri(uri)
|
312
|
+
# We only store normalized uris terminated with fragment #, so we can try whether
|
313
|
+
# normalization can be skipped
|
314
|
+
@@schemas[uri] || @@schemas[schema_key_for(uri)]
|
315
|
+
end
|
316
|
+
|
317
|
+
def schema_loaded?(schema_uri)
|
318
|
+
!schema_for_uri(schema_uri).nil?
|
319
|
+
end
|
320
|
+
|
321
|
+
def schema_key_for(uri)
|
322
|
+
key = Util::URI.normalized_uri(uri).to_s
|
323
|
+
key.end_with?('#') ? key : "#{key}#"
|
324
|
+
end
|
325
|
+
|
326
|
+
def cache_schemas=(val)
|
327
|
+
warn "[DEPRECATION NOTICE] Schema caching is now a validation option. Schemas will still be cached if this is set to true, but this method will be removed in version >= 3. Please use the :clear_cache validation option instead."
|
328
|
+
@@cache_schemas = val == true ? true : false
|
329
|
+
end
|
330
|
+
|
331
|
+
def validators
|
332
|
+
@@validators
|
333
|
+
end
|
334
|
+
|
335
|
+
def default_validator
|
336
|
+
@@default_validator
|
337
|
+
end
|
338
|
+
|
339
|
+
def validator_for_uri(schema_uri, raise_not_found=true)
|
340
|
+
return default_validator unless schema_uri
|
341
|
+
u = JSON::Util::URI.parse(schema_uri)
|
342
|
+
validator = validators["#{u.scheme}://#{u.host}#{u.path}"]
|
343
|
+
if validator.nil? && raise_not_found
|
344
|
+
raise JSON::Schema::SchemaError.new("Schema not found: #{schema_uri}")
|
345
|
+
else
|
346
|
+
validator
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def validator_for_name(schema_name, raise_not_found=true)
|
351
|
+
return default_validator unless schema_name
|
352
|
+
schema_name = schema_name.to_s
|
353
|
+
validator = validators.values.detect do |v|
|
354
|
+
Array(v.names).include?(schema_name)
|
355
|
+
end
|
356
|
+
if validator.nil? && raise_not_found
|
357
|
+
raise JSON::Schema::SchemaError.new("The requested JSON schema version is not supported")
|
358
|
+
else
|
359
|
+
validator
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def validator_for(schema_uri)
|
364
|
+
warn "[DEPRECATION NOTICE] JSON::Validator#validator_for has been replaced by JSON::Validator#validator_for_uri and will be removed in version >= 3. Please use the #validator_for_uri method instead."
|
365
|
+
validator_for_uri(schema_uri)
|
366
|
+
end
|
367
|
+
|
368
|
+
def register_validator(v)
|
369
|
+
@@validators["#{v.uri.scheme}://#{v.uri.host}#{v.uri.path}"] = v
|
370
|
+
end
|
371
|
+
|
372
|
+
def register_default_validator(v)
|
373
|
+
@@default_validator = v
|
374
|
+
end
|
375
|
+
|
376
|
+
def register_format_validator(format, validation_proc, versions = (@@validators.flat_map{ |k, v| v.names.first } + [nil]))
|
377
|
+
custom_format_validator = JSON::Schema::CustomFormat.new(validation_proc)
|
378
|
+
versions.each do |version|
|
379
|
+
validator = validator_for_name(version)
|
380
|
+
validator.formats[format.to_s] = custom_format_validator
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
def deregister_format_validator(format, versions = (@@validators.flat_map{ |k, v| v.names.first } + [nil]))
|
385
|
+
versions.each do |version|
|
386
|
+
validator = validator_for_name(version)
|
387
|
+
validator.formats[format.to_s] = validator.default_formats[format.to_s]
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
def restore_default_formats(versions = (@@validators.flat_map{ |k, v| v.names.first } + [nil]))
|
392
|
+
versions.each do |version|
|
393
|
+
validator = validator_for_name(version)
|
394
|
+
validator.formats = validator.default_formats.clone
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
def json_backend
|
399
|
+
if defined?(MultiJson)
|
400
|
+
MultiJson.respond_to?(:adapter) ? MultiJson.adapter : MultiJson.engine
|
401
|
+
else
|
402
|
+
@@json_backend
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
def json_backend=(backend)
|
407
|
+
if defined?(MultiJson)
|
408
|
+
backend = backend == 'json' ? 'json_gem' : backend
|
409
|
+
MultiJson.respond_to?(:use) ? MultiJson.use(backend) : MultiJson.engine = backend
|
410
|
+
else
|
411
|
+
backend = backend.to_s
|
412
|
+
if @@available_json_backends.include?(backend)
|
413
|
+
@@json_backend = backend
|
414
|
+
else
|
415
|
+
raise JSON::Schema::JsonParseError.new("The JSON backend '#{backend}' could not be found.")
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
def parse(s)
|
421
|
+
if defined?(MultiJson)
|
422
|
+
begin
|
423
|
+
MultiJson.respond_to?(:adapter) ? MultiJson.load(s) : MultiJson.decode(s)
|
424
|
+
rescue MultiJson::ParseError => e
|
425
|
+
raise JSON::Schema::JsonParseError.new(e.message)
|
426
|
+
end
|
427
|
+
else
|
428
|
+
case @@json_backend.to_s
|
429
|
+
when 'json'
|
430
|
+
begin
|
431
|
+
JSON.parse(s, :quirks_mode => true)
|
432
|
+
rescue JSON::ParserError => e
|
433
|
+
raise JSON::Schema::JsonParseError.new(e.message)
|
434
|
+
end
|
435
|
+
when 'yajl'
|
436
|
+
begin
|
437
|
+
json = StringIO.new(s)
|
438
|
+
parser = Yajl::Parser.new
|
439
|
+
parser.parse(json) or raise JSON::Schema::JsonParseError.new("The JSON could not be parsed by yajl")
|
440
|
+
rescue Yajl::ParseError => e
|
441
|
+
raise JSON::Schema::JsonParseError.new(e.message)
|
442
|
+
end
|
443
|
+
else
|
444
|
+
raise JSON::Schema::JsonParseError.new("No supported JSON parsers found. The following parsers are suported:\n * yajl-ruby\n * json")
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
def merge_missing_values(source, destination)
|
450
|
+
case destination
|
451
|
+
when Hash
|
452
|
+
source.each do |key, source_value|
|
453
|
+
destination_value = destination[key] || destination[key.to_sym]
|
454
|
+
if destination_value.nil?
|
455
|
+
destination[key] = source_value
|
456
|
+
else
|
457
|
+
merge_missing_values(source_value, destination_value)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
when Array
|
461
|
+
source.each_with_index do |source_value, i|
|
462
|
+
destination_value = destination[i]
|
463
|
+
merge_missing_values(source_value, destination_value)
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
if !defined?(MultiJson)
|
469
|
+
if Gem::Specification::find_all_by_name('json').any?
|
470
|
+
require 'json'
|
471
|
+
@@available_json_backends << 'json'
|
472
|
+
@@json_backend = 'json'
|
473
|
+
else
|
474
|
+
# Try force-loading json for rubies > 1.9.2
|
475
|
+
begin
|
476
|
+
require 'json'
|
477
|
+
@@available_json_backends << 'json'
|
478
|
+
@@json_backend = 'json'
|
479
|
+
rescue LoadError
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
|
484
|
+
if Gem::Specification::find_all_by_name('yajl-ruby').any?
|
485
|
+
require 'yajl'
|
486
|
+
@@available_json_backends << 'yajl'
|
487
|
+
@@json_backend = 'yajl'
|
488
|
+
end
|
489
|
+
|
490
|
+
if @@json_backend == 'yajl'
|
491
|
+
@@serializer = lambda{|o| Yajl::Encoder.encode(o) }
|
492
|
+
elsif @@json_backend == 'json'
|
493
|
+
@@serializer = lambda{|o| JSON.dump(o) }
|
494
|
+
else
|
495
|
+
@@serializer = lambda{|o| YAML.dump(o) }
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
private
|
501
|
+
|
502
|
+
if Gem::Specification::find_all_by_name('uuidtools').any?
|
503
|
+
require 'uuidtools'
|
504
|
+
@@fake_uuid_generator = lambda{|s| UUIDTools::UUID.sha1_create(UUIDTools::UUID_URL_NAMESPACE, s).to_s }
|
505
|
+
else
|
506
|
+
require 'json-schema/util/uuid'
|
507
|
+
@@fake_uuid_generator = lambda{|s| JSON::Util::UUID.create_v5(s,JSON::Util::UUID::Nil).to_s }
|
508
|
+
end
|
509
|
+
|
510
|
+
def serialize schema
|
511
|
+
if defined?(MultiJson)
|
512
|
+
MultiJson.respond_to?(:dump) ? MultiJson.dump(schema) : MultiJson.encode(schema)
|
513
|
+
else
|
514
|
+
@@serializer.call(schema)
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
def fake_uuid schema
|
519
|
+
@@fake_uuid_generator.call(schema)
|
520
|
+
end
|
521
|
+
|
522
|
+
def initialize_schema(schema)
|
523
|
+
if schema.is_a?(String)
|
524
|
+
begin
|
525
|
+
# Build a fake URI for this
|
526
|
+
schema_uri = JSON::Util::URI.parse(fake_uuid(schema))
|
527
|
+
schema = JSON::Schema.new(self.class.parse(schema), schema_uri, @options[:version])
|
528
|
+
if @options[:list] && @options[:fragment].nil?
|
529
|
+
schema = schema.to_array_schema
|
530
|
+
end
|
531
|
+
self.class.add_schema(schema)
|
532
|
+
rescue JSON::Schema::JsonParseError
|
533
|
+
# Build a uri for it
|
534
|
+
schema_uri = Util::URI.normalized_uri(schema)
|
535
|
+
if !self.class.schema_loaded?(schema_uri)
|
536
|
+
schema = @options[:schema_reader].read(schema_uri)
|
537
|
+
schema = JSON::Schema.stringify(schema)
|
538
|
+
|
539
|
+
if @options[:list] && @options[:fragment].nil?
|
540
|
+
schema = schema.to_array_schema
|
541
|
+
end
|
542
|
+
|
543
|
+
self.class.add_schema(schema)
|
544
|
+
else
|
545
|
+
schema = self.class.schema_for_uri(schema_uri)
|
546
|
+
if @options[:list] && @options[:fragment].nil?
|
547
|
+
schema = schema.to_array_schema
|
548
|
+
schema.uri = JSON::Util::URI.parse(fake_uuid(serialize(schema.schema)))
|
549
|
+
self.class.add_schema(schema)
|
550
|
+
end
|
551
|
+
schema
|
552
|
+
end
|
553
|
+
end
|
554
|
+
elsif schema.is_a?(Hash)
|
555
|
+
schema_uri = JSON::Util::URI.parse(fake_uuid(serialize(schema)))
|
556
|
+
schema = JSON::Schema.stringify(schema)
|
557
|
+
schema = JSON::Schema.new(schema, schema_uri, @options[:version])
|
558
|
+
if @options[:list] && @options[:fragment].nil?
|
559
|
+
schema = schema.to_array_schema
|
560
|
+
end
|
561
|
+
self.class.add_schema(schema)
|
562
|
+
else
|
563
|
+
raise JSON::Schema::SchemaParseError, "Invalid schema - must be either a string or a hash"
|
564
|
+
end
|
565
|
+
|
566
|
+
schema
|
567
|
+
end
|
568
|
+
|
569
|
+
def initialize_data(data)
|
570
|
+
if @options[:parse_data]
|
571
|
+
if @options[:json]
|
572
|
+
data = self.class.parse(data)
|
573
|
+
elsif @options[:uri]
|
574
|
+
json_uri = Util::URI.normalized_uri(data)
|
575
|
+
data = self.class.parse(custom_open(json_uri))
|
576
|
+
elsif data.is_a?(String)
|
577
|
+
begin
|
578
|
+
data = self.class.parse(data)
|
579
|
+
rescue JSON::Schema::JsonParseError
|
580
|
+
begin
|
581
|
+
json_uri = Util::URI.normalized_uri(data)
|
582
|
+
data = self.class.parse(custom_open(json_uri))
|
583
|
+
rescue JSON::Schema::JsonLoadError, JSON::Schema::UriError
|
584
|
+
# Silently discard the error - use the data as-is
|
585
|
+
end
|
586
|
+
end
|
587
|
+
end
|
588
|
+
end
|
589
|
+
JSON::Schema.stringify(data)
|
590
|
+
end
|
591
|
+
|
592
|
+
def custom_open(uri)
|
593
|
+
uri = Util::URI.normalized_uri(uri) if uri.is_a?(String)
|
594
|
+
if uri.absolute? && Util::URI::SUPPORTED_PROTOCOLS.include?(uri.scheme)
|
595
|
+
begin
|
596
|
+
open(uri.to_s).read
|
597
|
+
rescue OpenURI::HTTPError, Timeout::Error => e
|
598
|
+
raise JSON::Schema::JsonLoadError, e.message
|
599
|
+
end
|
600
|
+
else
|
601
|
+
begin
|
602
|
+
File.read(JSON::Util::URI.unescaped_path(uri))
|
603
|
+
rescue SystemCallError => e
|
604
|
+
raise JSON::Schema::JsonLoadError, e.message
|
605
|
+
end
|
606
|
+
end
|
607
|
+
end
|
608
|
+
end
|
609
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'json-schema/schema/validator'
|
2
|
+
|
3
|
+
module JSON
|
4
|
+
class Schema
|
5
|
+
|
6
|
+
class Draft1 < Validator
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
@attributes = {
|
10
|
+
"type" => JSON::Schema::TypeAttribute,
|
11
|
+
"disallow" => JSON::Schema::DisallowAttribute,
|
12
|
+
"format" => JSON::Schema::FormatAttribute,
|
13
|
+
"maximum" => JSON::Schema::MaximumInclusiveAttribute,
|
14
|
+
"minimum" => JSON::Schema::MinimumInclusiveAttribute,
|
15
|
+
"minItems" => JSON::Schema::MinItemsAttribute,
|
16
|
+
"maxItems" => JSON::Schema::MaxItemsAttribute,
|
17
|
+
"minLength" => JSON::Schema::MinLengthAttribute,
|
18
|
+
"maxLength" => JSON::Schema::MaxLengthAttribute,
|
19
|
+
"maxDecimal" => JSON::Schema::MaxDecimalAttribute,
|
20
|
+
"enum" => JSON::Schema::EnumAttribute,
|
21
|
+
"properties" => JSON::Schema::PropertiesOptionalAttribute,
|
22
|
+
"pattern" => JSON::Schema::PatternAttribute,
|
23
|
+
"additionalProperties" => JSON::Schema::AdditionalPropertiesAttribute,
|
24
|
+
"items" => JSON::Schema::ItemsAttribute,
|
25
|
+
"extends" => JSON::Schema::ExtendsAttribute
|
26
|
+
}
|
27
|
+
@default_formats = {
|
28
|
+
'date-time' => DateTimeFormat,
|
29
|
+
'date' => DateFormat,
|
30
|
+
'time' => TimeFormat,
|
31
|
+
'ip-address' => IP4Format,
|
32
|
+
'ipv6' => IP6Format,
|
33
|
+
'uri' => UriFormat
|
34
|
+
}
|
35
|
+
@formats = @default_formats.clone
|
36
|
+
@uri = JSON::Util::URI.parse("http://json-schema.org/draft-01/schema#")
|
37
|
+
@names = ["draft1"]
|
38
|
+
@metaschema_name = "draft-01.json"
|
39
|
+
end
|
40
|
+
|
41
|
+
JSON::Validator.register_validator(self.new)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'json-schema/schema/validator'
|
2
|
+
|
3
|
+
module JSON
|
4
|
+
class Schema
|
5
|
+
|
6
|
+
class Draft2 < Validator
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
@attributes = {
|
10
|
+
"type" => JSON::Schema::TypeAttribute,
|
11
|
+
"disallow" => JSON::Schema::DisallowAttribute,
|
12
|
+
"format" => JSON::Schema::FormatAttribute,
|
13
|
+
"maximum" => JSON::Schema::MaximumInclusiveAttribute,
|
14
|
+
"minimum" => JSON::Schema::MinimumInclusiveAttribute,
|
15
|
+
"minItems" => JSON::Schema::MinItemsAttribute,
|
16
|
+
"maxItems" => JSON::Schema::MaxItemsAttribute,
|
17
|
+
"uniqueItems" => JSON::Schema::UniqueItemsAttribute,
|
18
|
+
"minLength" => JSON::Schema::MinLengthAttribute,
|
19
|
+
"maxLength" => JSON::Schema::MaxLengthAttribute,
|
20
|
+
"divisibleBy" => JSON::Schema::DivisibleByAttribute,
|
21
|
+
"enum" => JSON::Schema::EnumAttribute,
|
22
|
+
"properties" => JSON::Schema::PropertiesOptionalAttribute,
|
23
|
+
"pattern" => JSON::Schema::PatternAttribute,
|
24
|
+
"additionalProperties" => JSON::Schema::AdditionalPropertiesAttribute,
|
25
|
+
"items" => JSON::Schema::ItemsAttribute,
|
26
|
+
"extends" => JSON::Schema::ExtendsAttribute
|
27
|
+
}
|
28
|
+
@default_formats = {
|
29
|
+
'date-time' => DateTimeFormat,
|
30
|
+
'date' => DateFormat,
|
31
|
+
'time' => TimeFormat,
|
32
|
+
'ip-address' => IP4Format,
|
33
|
+
'ipv6' => IP6Format,
|
34
|
+
'uri' => UriFormat
|
35
|
+
}
|
36
|
+
@formats = @default_formats.clone
|
37
|
+
@uri = JSON::Util::URI.parse("http://json-schema.org/draft-02/schema#")
|
38
|
+
@names = ["draft2"]
|
39
|
+
@metaschema_name = "draft-02.json"
|
40
|
+
end
|
41
|
+
|
42
|
+
JSON::Validator.register_validator(self.new)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|