json-schema 2.2.5 → 2.3.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.
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