json-schema 2.2.5 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +8 -8
  2. data/README.textile +1 -1
  3. data/lib/json-schema.rb +1 -0
  4. data/lib/json-schema/attribute.rb +43 -0
  5. data/lib/json-schema/attributes/additionalitems.rb +3 -1
  6. data/lib/json-schema/attributes/additionalproperties.rb +1 -0
  7. data/lib/json-schema/attributes/allof.rb +6 -4
  8. data/lib/json-schema/attributes/anyof.rb +7 -5
  9. data/lib/json-schema/attributes/dependencies.rb +3 -1
  10. data/lib/json-schema/attributes/dependencies_v4.rb +3 -1
  11. data/lib/json-schema/attributes/disallow.rb +3 -1
  12. data/lib/json-schema/attributes/divisibleby.rb +3 -1
  13. data/lib/json-schema/attributes/enum.rb +3 -1
  14. data/lib/json-schema/attributes/extends.rb +1 -0
  15. data/lib/json-schema/attributes/format.rb +6 -112
  16. data/lib/json-schema/attributes/formats/custom.rb +22 -0
  17. data/lib/json-schema/attributes/formats/date.rb +25 -0
  18. data/lib/json-schema/attributes/formats/date_time.rb +35 -0
  19. data/lib/json-schema/attributes/formats/ip4.rb +22 -0
  20. data/lib/json-schema/attributes/formats/ip6.rb +30 -0
  21. data/lib/json-schema/attributes/formats/time.rb +22 -0
  22. data/lib/json-schema/attributes/formats/uri.rb +18 -0
  23. data/lib/json-schema/attributes/items.rb +3 -1
  24. data/lib/json-schema/attributes/maxdecimal.rb +3 -1
  25. data/lib/json-schema/attributes/maximum.rb +3 -1
  26. data/lib/json-schema/attributes/maximum_inclusive.rb +2 -0
  27. data/lib/json-schema/attributes/maxitems.rb +2 -0
  28. data/lib/json-schema/attributes/maxlength.rb +3 -1
  29. data/lib/json-schema/attributes/maxproperties.rb +3 -1
  30. data/lib/json-schema/attributes/minimum.rb +3 -1
  31. data/lib/json-schema/attributes/minimum_inclusive.rb +3 -1
  32. data/lib/json-schema/attributes/minitems.rb +4 -2
  33. data/lib/json-schema/attributes/minlength.rb +3 -1
  34. data/lib/json-schema/attributes/minproperties.rb +3 -1
  35. data/lib/json-schema/attributes/multipleof.rb +3 -1
  36. data/lib/json-schema/attributes/not.rb +3 -1
  37. data/lib/json-schema/attributes/oneof.rb +2 -0
  38. data/lib/json-schema/attributes/pattern.rb +3 -1
  39. data/lib/json-schema/attributes/patternproperties.rb +3 -1
  40. data/lib/json-schema/attributes/properties.rb +4 -2
  41. data/lib/json-schema/attributes/properties_optional.rb +3 -1
  42. data/lib/json-schema/attributes/properties_v4.rb +2 -0
  43. data/lib/json-schema/attributes/ref.rb +3 -0
  44. data/lib/json-schema/attributes/required.rb +2 -0
  45. data/lib/json-schema/attributes/type.rb +6 -20
  46. data/lib/json-schema/attributes/type_v4.rb +3 -21
  47. data/lib/json-schema/attributes/uniqueitems.rb +3 -1
  48. data/lib/json-schema/errors/custom_format_error.rb +6 -0
  49. data/lib/json-schema/errors/json_parse_error.rb +6 -0
  50. data/lib/json-schema/errors/schema_error.rb +6 -0
  51. data/lib/json-schema/errors/validation_error.rb +46 -0
  52. data/lib/json-schema/schema.rb +1 -5
  53. data/lib/json-schema/schema/validator.rb +31 -0
  54. data/lib/json-schema/validator.rb +61 -126
  55. data/lib/json-schema/validators/draft1.rb +14 -1
  56. data/lib/json-schema/validators/draft2.rb +14 -1
  57. data/lib/json-schema/validators/draft3.rb +14 -2
  58. data/lib/json-schema/validators/draft4.rb +12 -1
  59. data/test/test_all_of_ref_schema.rb +29 -2
  60. data/test/test_any_of_ref_schema.rb +26 -0
  61. data/test/test_bad_schema_ref.rb +2 -2
  62. data/test/test_common_test_suite.rb +53 -0
  63. data/test/test_custom_format.rb +117 -0
  64. data/test/test_jsonschema_draft1.rb +12 -0
  65. data/test/test_jsonschema_draft2.rb +12 -0
  66. data/test/test_jsonschema_draft3.rb +31 -0
  67. data/test/test_jsonschema_draft4.rb +14 -48
  68. data/test/test_minitems.rb +18 -0
  69. metadata +21 -4
  70. data/test/test_suite.rb +0 -71
@@ -1,3 +1,5 @@
1
+ require 'json-schema/attribute'
2
+
1
3
  module JSON
2
4
  class Schema
3
5
  class UniqueItemsAttribute < Attribute
@@ -13,4 +15,4 @@ module JSON
13
15
  end
14
16
  end
15
17
  end
16
- end
18
+ end
@@ -0,0 +1,6 @@
1
+ module JSON
2
+ class Schema
3
+ class CustomFormatError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module JSON
2
+ class Schema
3
+ class JsonParseError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module JSON
2
+ class Schema
3
+ class SchemaError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,46 @@
1
+ module JSON
2
+ class Schema
3
+ class ValidationError < StandardError
4
+ INDENT = " "
5
+ attr_accessor :fragments, :schema, :failed_attribute, :sub_errors, :message
6
+
7
+ def initialize(message, fragments, failed_attribute, schema)
8
+ @fragments = fragments.clone
9
+ @schema = schema
10
+ @sub_errors = {}
11
+ @failed_attribute = failed_attribute
12
+ @message = message
13
+ super(message_with_schema)
14
+ end
15
+
16
+ def to_string(subschema_level = 0)
17
+ if @sub_errors.empty?
18
+ subschema_level == 0 ? message_with_schema : message
19
+ else
20
+ messages = ["#{message}. The schema specific errors were:\n"]
21
+ @sub_errors.each do |subschema, errors|
22
+ messages.push "- #{subschema}:"
23
+ messages.concat Array(errors).map { |e| "#{INDENT}- #{e.to_string(subschema_level + 1)}" }
24
+ end
25
+ messages.map { |m| (INDENT * subschema_level) + m }.join("\n")
26
+ end
27
+ end
28
+
29
+ def to_hash
30
+ base = {:schema => @schema.uri, :fragment => ::JSON::Schema::Attribute.build_fragment(fragments), :message => message_with_schema, :failed_attribute => @failed_attribute.to_s.split(":").last.split("Attribute").first}
31
+ if !@sub_errors.empty?
32
+ base[:errors] = @sub_errors.inject({}) do |hsh, (subschema, errors)|
33
+ subschema_sym = subschema.downcase.gsub(/\W+/, '_').to_sym
34
+ hsh[subschema_sym] = Array(errors).map{|e| e.to_hash}
35
+ hsh
36
+ end
37
+ end
38
+ base
39
+ end
40
+
41
+ def message_with_schema
42
+ "#{message} in schema #{schema.uri}"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -24,11 +24,7 @@ module JSON
24
24
 
25
25
  # If there is a $schema on this schema, use it to determine which validator to use
26
26
  if @schema['$schema']
27
- u = URI.parse(@schema['$schema'])
28
- @validator = JSON::Validator.validators["#{u.scheme}://#{u.host}#{u.path}"]
29
- if @validator.nil?
30
- raise SchemaError.new("This library does not have support for schemas defined by #{u.scheme}://#{u.host}#{u.path}")
31
- end
27
+ @validator = JSON::Validator.validator_for(@schema['$schema'])
32
28
  elsif parent_validator
33
29
  @validator = parent_validator
34
30
  else
@@ -0,0 +1,31 @@
1
+ module JSON
2
+ class Schema
3
+ class Validator
4
+ attr_accessor :attributes, :formats, :uri, :names, :metaschema
5
+ attr_reader :default_formats
6
+
7
+ def initialize()
8
+ @attributes = {}
9
+ @formats = {}
10
+ @default_formats = {}
11
+ @uri = nil
12
+ @names = []
13
+ @metaschema = ''
14
+ end
15
+
16
+ def extend_schema_definition(schema_uri)
17
+ validator = JSON::Validator.validator_for(schema_uri)
18
+ @attributes.merge!(validator.attributes)
19
+ end
20
+
21
+ def validate(current_schema, data, fragments, processor, options = {})
22
+ current_schema.schema.each do |attr_name,attribute|
23
+ if @attributes.has_key?(attr_name.to_s)
24
+ @attributes[attr_name.to_s].validate(current_schema, data, fragments, processor, self, options)
25
+ end
26
+ end
27
+ data
28
+ end
29
+ end
30
+ end
31
+ end
@@ -7,100 +7,10 @@ require 'date'
7
7
  require 'thread'
8
8
  require 'yaml'
9
9
 
10
- module JSON
11
-
12
- class Schema
13
- class ValidationError < StandardError
14
- attr_accessor :fragments, :schema, :failed_attribute, :sub_errors
15
-
16
- def initialize(message, fragments, failed_attribute, schema)
17
- @fragments = fragments.clone
18
- @schema = schema
19
- @sub_errors = []
20
- @failed_attribute = failed_attribute
21
- message = "#{message} in schema #{schema.uri}"
22
- super(message)
23
- end
24
-
25
- def to_string
26
- if @sub_errors.empty?
27
- message
28
- else
29
- full_message = message + "\n The schema specific errors were:\n"
30
- @sub_errors.each{|e| full_message = full_message + " - " + e.to_string + "\n"}
31
- full_message
32
- end
33
- end
34
-
35
- def to_hash
36
- base = {:schema => @schema.uri, :fragment => ::JSON::Schema::Attribute.build_fragment(fragments), :message => message, :failed_attribute => @failed_attribute.to_s.split(":").last.split("Attribute").first}
37
- if !@sub_errors.empty?
38
- base[:errors] = @sub_errors.map{|e| e.to_hash}
39
- end
40
- base
41
- end
42
- end
43
-
44
- class SchemaError < StandardError
45
- end
46
-
47
- class JsonParseError < StandardError
48
- end
49
-
50
- class Attribute
51
- def self.validate(current_schema, data, fragments, processor, validator, options = {})
52
- end
53
-
54
- def self.build_fragment(fragments)
55
- "#/#{fragments.join('/')}"
56
- end
57
-
58
- def self.validation_error(processor, message, fragments, current_schema, failed_attribute, record_errors)
59
- error = ValidationError.new(message, fragments, failed_attribute, current_schema)
60
- if record_errors
61
- processor.validation_error(error)
62
- else
63
- raise error
64
- end
65
- end
66
-
67
- def self.validation_errors(validator)
68
- validator.validation_errors
69
- end
70
- end
71
-
72
- class Validator
73
- attr_accessor :attributes, :uri
74
-
75
- def initialize()
76
- @attributes = {}
77
- @uri = nil
78
- end
79
-
80
- def extend_schema_definition(schema_uri)
81
- u = URI.parse(schema_uri)
82
- validator = JSON::Validator.validators["#{u.scheme}://#{u.host}#{u.path}"]
83
- if validator.nil?
84
- raise SchemaError.new("Schema not found: #{u.scheme}://#{u.host}#{u.path}")
85
- end
86
- @attributes.merge!(validator.attributes)
87
- end
88
-
89
- def to_s
90
- "#{@uri.scheme}://#{uri.host}#{uri.path}"
91
- end
92
-
93
- def validate(current_schema, data, fragments, processor, options = {})
94
- current_schema.schema.each do |attr_name,attribute|
95
- if @attributes.has_key?(attr_name.to_s)
96
- @attributes[attr_name.to_s].validate(current_schema, data, fragments, processor, self, options)
97
- end
98
- end
99
- data
100
- end
101
- end
102
- end
10
+ require 'json-schema/errors/schema_error'
11
+ require 'json-schema/errors/json_parse_error'
103
12
 
13
+ module JSON
104
14
 
105
15
  class Validator
106
16
 
@@ -123,39 +33,12 @@ module JSON
123
33
  @@serializer = nil
124
34
  @@mutex = Mutex.new
125
35
 
126
- def self.version_string_for(version)
127
- # I'm not a fan of this, but it's quick and dirty to get it working for now
128
- return "draft-04" unless version
129
- case version.to_s
130
- when "draft4", "http://json-schema.org/draft-04/schema#"
131
- "draft-04"
132
- when "draft3", "http://json-schema.org/draft-03/schema#"
133
- "draft-03"
134
- when "draft2"
135
- "draft-02"
136
- when "draft1"
137
- "draft-01"
138
- else
139
- raise JSON::Schema::SchemaError.new("The requested JSON schema version is not supported")
140
- end
141
- end
142
-
143
- def self.metaschema_for(version_string)
144
- File.join(Pathname.new(File.dirname(__FILE__)).parent.parent, "resources", "#{version_string}.json").to_s
145
- end
146
-
147
36
  def initialize(schema_data, data, opts={})
148
37
  @options = @@default_opts.clone.merge(opts)
149
38
  @errors = []
150
39
 
151
- # I'm not a fan of this, but it's quick and dirty to get it working for now
152
- version_string = "draft-04"
153
- if @options[:version]
154
- version_string = @options[:version] = self.class.version_string_for(@options[:version])
155
- u = URI.parse("http://json-schema.org/#{@options[:version]}/schema#")
156
- validator = JSON::Validator.validators["#{u.scheme}://#{u.host}#{u.path}"]
157
- @options[:version] = validator
158
- end
40
+ validator = JSON::Validator.validator_for_name(@options[:version])
41
+ @options[:version] = validator
159
42
 
160
43
  @validation_options = @options[:record_errors] ? {:record_errors => true} : {}
161
44
  @validation_options[:insert_defaults] = true if @options[:insert_defaults]
@@ -169,10 +52,11 @@ module JSON
169
52
  if @options[:validate_schema]
170
53
  begin
171
54
  if @base_schema.schema["$schema"]
172
- version_string = @options[:version] = self.class.version_string_for(@base_schema.schema["$schema"])
55
+ base_validator = JSON::Validator.validator_for_name(@base_schema.schema["$schema"])
173
56
  end
57
+ metaschema = base_validator ? base_validator.metaschema : validator.metaschema
174
58
  # Don't clear the cache during metaschema validation!
175
- meta_validator = JSON::Validator.new(self.class.metaschema_for(version_string), @base_schema.schema, {:clear_cache => false})
59
+ meta_validator = JSON::Validator.new(metaschema, @base_schema.schema, {:clear_cache => false})
176
60
  meta_validator.validate
177
61
  rescue JSON::Schema::ValidationError, JSON::Schema::SchemaError
178
62
  raise $!
@@ -414,7 +298,7 @@ module JSON
414
298
 
415
299
  def fully_validate_schema(schema, opts={})
416
300
  data = schema
417
- schema = metaschema_for(version_string_for(opts[:version]))
301
+ schema = JSON::Validator.validator_for_name(opts[:version]).metaschema
418
302
  fully_validate(schema, data, opts)
419
303
  end
420
304
 
@@ -451,14 +335,56 @@ module JSON
451
335
  @@default_validator
452
336
  end
453
337
 
338
+ def validator_for_uri(schema_uri)
339
+ return default_validator unless schema_uri
340
+ u = URI.parse(schema_uri)
341
+ validator = validators["#{u.scheme}://#{u.host}#{u.path}"]
342
+ if validator.nil?
343
+ raise JSON::Schema::SchemaError.new("Schema not found: #{schema_uri}")
344
+ else
345
+ validator
346
+ end
347
+ end
348
+
349
+ def validator_for_name(schema_name)
350
+ return default_validator unless schema_name
351
+ validator = validators_for_names([schema_name]).first
352
+ if validator.nil?
353
+ raise JSON::Schema::SchemaError.new("The requested JSON schema version is not supported")
354
+ else
355
+ validator
356
+ end
357
+ end
358
+
359
+ alias_method :validator_for, :validator_for_uri
360
+
454
361
  def register_validator(v)
455
- @@validators[v.to_s] = v
362
+ @@validators["#{v.uri.scheme}://#{v.uri.host}#{v.uri.path}"] = v
456
363
  end
457
364
 
458
365
  def register_default_validator(v)
459
366
  @@default_validator = v
460
367
  end
461
368
 
369
+ def register_format_validator(format, validation_proc, versions = ["draft1", "draft2", "draft3", "draft4"])
370
+ custom_format_validator = JSON::Schema::CustomFormat.new(validation_proc)
371
+ validators_for_names(versions).each do |validator|
372
+ validator.formats[format.to_s] = custom_format_validator
373
+ end
374
+ end
375
+
376
+ def deregister_format_validator(format, versions = ["draft1", "draft2", "draft3", "draft4"])
377
+ validators_for_names(versions).each do |validator|
378
+ validator.formats[format.to_s] = validator.default_formats[format.to_s]
379
+ end
380
+ end
381
+
382
+ def restore_default_formats(versions = ["draft1", "draft2", "draft3", "draft4"])
383
+ validators_for_names(versions).each do |validator|
384
+ validator.formats = validator.default_formats.clone
385
+ end
386
+ end
387
+
462
388
  def json_backend
463
389
  if defined?(MultiJson)
464
390
  MultiJson.respond_to?(:adapter) ? MultiJson.adapter : MultiJson.engine
@@ -539,6 +465,15 @@ module JSON
539
465
  }
540
466
  end
541
467
  end
468
+
469
+ private
470
+
471
+ def validators_for_names(names)
472
+ names.map! { |name| name.to_s }
473
+ validators.reduce([]) do |memo, (_, validator)|
474
+ memo.tap { |m| m << validator if (validator.names & names).any? }
475
+ end
476
+ end
542
477
  end
543
478
 
544
479
  private
@@ -1,3 +1,5 @@
1
+ require 'json-schema/schema/validator'
2
+
1
3
  module JSON
2
4
  class Schema
3
5
 
@@ -22,11 +24,22 @@ module JSON
22
24
  "items" => JSON::Schema::ItemsAttribute,
23
25
  "extends" => JSON::Schema::ExtendsAttribute
24
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
25
36
  @uri = URI.parse("http://json-schema.org/draft-01/schema#")
37
+ @names = ["draft1"]
38
+ @metaschema = File.join("resources", "draft-01.json")
26
39
  end
27
40
 
28
41
  JSON::Validator.register_validator(self.new)
29
42
  end
30
43
 
31
44
  end
32
- end
45
+ end
@@ -1,3 +1,5 @@
1
+ require 'json-schema/schema/validator'
2
+
1
3
  module JSON
2
4
  class Schema
3
5
 
@@ -23,11 +25,22 @@ module JSON
23
25
  "items" => JSON::Schema::ItemsAttribute,
24
26
  "extends" => JSON::Schema::ExtendsAttribute
25
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
26
37
  @uri = URI.parse("http://json-schema.org/draft-02/schema#")
38
+ @names = ["draft2"]
39
+ @metaschema = File.join("resources", "draft-02.json")
27
40
  end
28
41
 
29
42
  JSON::Validator.register_validator(self.new)
30
43
  end
31
44
 
32
45
  end
33
- end
46
+ end
@@ -1,3 +1,5 @@
1
+ require 'json-schema/schema/validator'
2
+
1
3
  module JSON
2
4
  class Schema
3
5
 
@@ -27,12 +29,22 @@ module JSON
27
29
  "extends" => JSON::Schema::ExtendsAttribute,
28
30
  "$ref" => JSON::Schema::RefAttribute
29
31
  }
32
+ @default_formats = {
33
+ 'date-time' => DateTimeFormat,
34
+ 'date' => DateFormat,
35
+ 'ip-address' => IP4Format,
36
+ 'ipv6' => IP6Format,
37
+ 'time' => TimeFormat,
38
+ 'uri' => UriFormat
39
+ }
40
+ @formats = @default_formats.clone
30
41
  @uri = URI.parse("http://json-schema.org/draft-03/schema#")
42
+ @names = ["draft3", "http://json-schema.org/draft-03/schema#"]
43
+ @metaschema = File.join("resources", "draft-03.json")
31
44
  end
32
45
 
33
46
  JSON::Validator.register_validator(self.new)
34
- JSON::Validator.register_default_validator(self.new)
35
47
  end
36
48
 
37
49
  end
38
- end
50
+ end