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
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZWEyNDAyZWRhMzlhYTFjZjdhNDA0NTFkOGE5NmY3OWVmN2JlYjFmOA==
4
+ YzRhZDY5NzVjYTlkZTM4MDU4YjI1Mjg5YWI3MmJkMDdhNjFiZGE3OA==
5
5
  data.tar.gz: !binary |-
6
- NDg2MjAzYWUxZjYzM2JlNTVkNTY0YTcyNjUwYjk4MDI3OWQyZjVmOA==
6
+ YWJhZDY3ZWRlZDBlNWFiNDc0ZTM5MzcyZjkyNWJkNWViOGNjN2NiMA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- YmNjZDBkNjg0MTVmMmU0NDdkZmI0NjcyODgwNjZmMDIzMTBiMmJkYzUwZDYx
10
- NGI3NTY5ZDFmNTI3NmIxMTQyOTVjODNjNWNjOWMyY2E1YmUyMzY5YjJhMmIz
11
- MWUwNmJmOGE5YzMxOTNhMjU2ZjIwNjYzNGE1ODgzYzc4NWRkYWI=
9
+ OTYzYzFkYzlkMjM5NjY0ZjBmNTVjM2IwZmM0ODQxZWQwMDk5OGE5MTliMmQw
10
+ MmMwYjAwNWE3NzkzMDYzNzUzN2Q0M2RhMjNmNTFlZGYzZDE0MmMxMTRjM2I2
11
+ NGM2ZWIxMmE0N2NjMjBjMjRmZTk3NDYyY2Q2YTcyMjNhYzEzMTY=
12
12
  data.tar.gz: !binary |-
13
- YzQ0NTQ1NmNiOWUwNDZjNzIzNTY0OGIzMzM3MzRkYzFhNzQxMTVkYjdmYjQ1
14
- OGRmYjUzNjhjZjlmMjc3ZGUyODlhYzU0ZjcwMGQ2YjU1NDU0NmYxMGRiNGZj
15
- M2E5ZWIwMmMwMDJkNzhlNDQ2YzI0NTJjMDg1YWVkOTBjMjgwNDQ=
13
+ YWY3ZjZkMTcxMWRhNDA0MWQxODk3MWQwNzc0NjgyZTc2ZjE5YzE4MjYwYjFj
14
+ NzI5N2I3NDRhYWM0Y2U4MjhmNDMxZTc1YWYwZmRhNWMxODk5ZjU1YzliZmFm
15
+ YmY4MTA0ZWRlMzYyMWQ1NDA3MDk5MzUwYmJkN2ExNmQ0NDU5ZjA=
data/README.textile CHANGED
@@ -24,7 +24,7 @@ From the git repo:
24
24
 
25
25
  <pre>
26
26
  $ gem build json-schema.gemspec
27
- $ gem install json-schema-2.2.5.gem
27
+ $ gem install json-schema-2.3.0.gem
28
28
  </pre>
29
29
 
30
30
 
data/lib/json-schema.rb CHANGED
@@ -20,5 +20,6 @@ require 'json-schema/util/array_set'
20
20
  require 'json-schema/schema'
21
21
  require 'json-schema/validator'
22
22
  Dir[File.join(File.dirname(__FILE__), "json-schema/attributes/*.rb")].each {|file| require file }
23
+ Dir[File.join(File.dirname(__FILE__), "json-schema/attributes/formats/*.rb")].each {|file| require file }
23
24
  Dir[File.join(File.dirname(__FILE__), "json-schema/validators/*.rb")].sort!.each {|file| require file }
24
25
  require 'json-schema/uri/file'
@@ -0,0 +1,43 @@
1
+ require 'json-schema/errors/validation_error'
2
+
3
+ module JSON
4
+ class Schema
5
+ class Attribute
6
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
7
+ end
8
+
9
+ def self.build_fragment(fragments)
10
+ "#/#{fragments.join('/')}"
11
+ end
12
+
13
+ def self.validation_error(processor, message, fragments, current_schema, failed_attribute, record_errors)
14
+ error = ValidationError.new(message, fragments, failed_attribute, current_schema)
15
+ if record_errors
16
+ processor.validation_error(error)
17
+ else
18
+ raise error
19
+ end
20
+ end
21
+
22
+ def self.validation_errors(validator)
23
+ validator.validation_errors
24
+ end
25
+
26
+ TYPE_CLASS_MAPPINGS = {
27
+ "string" => String,
28
+ "number" => Numeric,
29
+ "integer" => Integer,
30
+ "boolean" => [TrueClass, FalseClass],
31
+ "object" => Hash,
32
+ "array" => Array,
33
+ "null" => NilClass,
34
+ "any" => Object
35
+ }
36
+
37
+ def self.data_valid_for_type?(data, type)
38
+ valid_classes = TYPE_CLASS_MAPPINGS.fetch(type) { return true }
39
+ Array(valid_classes).any? { |c| data.is_a?(c) }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,3 +1,5 @@
1
+ require 'json-schema/attribute'
2
+
1
3
  module JSON
2
4
  class Schema
3
5
  class AdditionalItemsAttribute < Attribute
@@ -20,4 +22,4 @@ module JSON
20
22
  end
21
23
  end
22
24
  end
23
- end
25
+ end
@@ -1,3 +1,4 @@
1
+ require 'json-schema/attribute'
1
2
  require 'json-schema/attributes/extends'
2
3
 
3
4
  module JSON
@@ -1,12 +1,14 @@
1
+ require 'json-schema/attribute'
2
+
1
3
  module JSON
2
4
  class Schema
3
5
  class AllOfAttribute < Attribute
4
6
  def self.validate(current_schema, data, fragments, processor, validator, options = {})
5
- # Create an array to hold errors that are generated during validation
6
- errors = []
7
+ # Create an hash to hold errors that are generated during validation
8
+ errors = Hash.new { |hsh, k| hsh[k] = [] }
7
9
  valid = true
8
10
 
9
- current_schema.schema['allOf'].each do |element|
11
+ current_schema.schema['allOf'].each_with_index do |element, schema_index|
10
12
  schema = JSON::Schema.new(element,current_schema.uri,validator)
11
13
 
12
14
  # We're going to add a little cruft here to try and maintain any validation errors that occur in the allOf
@@ -22,7 +24,7 @@ module JSON
22
24
  diff = validation_errors(processor).count - pre_validation_error_count
23
25
  while diff > 0
24
26
  diff = diff - 1
25
- errors.push(validation_errors(processor).pop)
27
+ errors["allOf ##{schema_index}"].push(validation_errors(processor).pop)
26
28
  end
27
29
  end
28
30
 
@@ -1,12 +1,14 @@
1
+ require 'json-schema/attribute'
2
+
1
3
  module JSON
2
4
  class Schema
3
5
  class AnyOfAttribute < Attribute
4
6
  def self.validate(current_schema, data, fragments, processor, validator, options = {})
5
- # Create an array to hold errors that are generated during validation
6
- errors = []
7
+ # Create a hash to hold errors that are generated during validation
8
+ errors = Hash.new { |hsh, k| hsh[k] = [] }
7
9
  valid = false
8
10
 
9
- current_schema.schema['anyOf'].each do |element|
11
+ current_schema.schema['anyOf'].each_with_index do |element, schema_index|
10
12
  schema = JSON::Schema.new(element,current_schema.uri,validator)
11
13
 
12
14
  # We're going to add a little cruft here to try and maintain any validation errors that occur in the anyOf
@@ -24,7 +26,7 @@ module JSON
24
26
  valid = false if diff > 0
25
27
  while diff > 0
26
28
  diff = diff - 1
27
- errors.push(validation_errors(processor).pop)
29
+ errors["anyOf ##{schema_index}"].push(validation_errors(processor).pop)
28
30
  end
29
31
 
30
32
  break if valid
@@ -38,4 +40,4 @@ module JSON
38
40
  end
39
41
  end
40
42
  end
41
- end
43
+ end
@@ -1,3 +1,5 @@
1
+ require 'json-schema/attribute'
2
+
1
3
  module JSON
2
4
  class Schema
3
5
  class DependenciesAttribute < Attribute
@@ -27,4 +29,4 @@ module JSON
27
29
  end
28
30
  end
29
31
  end
30
- end
32
+ end
@@ -1,3 +1,5 @@
1
+ require 'json-schema/attribute'
2
+
1
3
  module JSON
2
4
  class Schema
3
5
  class DependenciesV4Attribute < Attribute
@@ -17,4 +19,4 @@ module JSON
17
19
  end
18
20
  end
19
21
  end
20
- end
22
+ end
@@ -1,3 +1,5 @@
1
+ require 'json-schema/attribute'
2
+
1
3
  module JSON
2
4
  class Schema
3
5
  class DisallowAttribute < Attribute
@@ -8,4 +10,4 @@ module JSON
8
10
  end
9
11
  end
10
12
  end
11
- end
13
+ end
@@ -1,3 +1,5 @@
1
+ require 'json-schema/attribute'
2
+
1
3
  module JSON
2
4
  class Schema
3
5
  class DivisibleByAttribute < Attribute
@@ -13,4 +15,4 @@ module JSON
13
15
  end
14
16
  end
15
17
  end
16
- end
18
+ end
@@ -1,3 +1,5 @@
1
+ require 'json-schema/attribute'
2
+
1
3
  module JSON
2
4
  class Schema
3
5
  class EnumAttribute < Attribute
@@ -21,4 +23,4 @@ module JSON
21
23
  end
22
24
  end
23
25
  end
24
- end
26
+ end
@@ -1,3 +1,4 @@
1
+ require 'json-schema/attribute'
1
2
  require 'json-schema/attributes/ref'
2
3
 
3
4
  module JSON
@@ -1,121 +1,15 @@
1
+ require 'json-schema/attribute'
1
2
  require 'uri'
2
3
 
3
4
  module JSON
4
5
  class Schema
5
6
  class FormatAttribute < Attribute
6
- def self.validate(current_schema, data, fragments, processor, validator, options = {})
7
- case current_schema.schema['format']
8
-
9
- # Timestamp in restricted ISO-8601 YYYY-MM-DDThh:mm:ssZ with optional decimal fraction of the second
10
- when 'date-time'
11
- if data.is_a?(String)
12
- error_message = "The property '#{build_fragment(fragments)}' must be a date/time in the ISO-8601 format of YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss.ssZ"
13
- r = Regexp.new('^\d\d\d\d-\d\d-\d\dT(\d\d):(\d\d):(\d\d)([\.,]\d+)?(Z|[+-](\d\d)(:?\d\d)?)?$')
14
- if (m = r.match(data))
15
- parts = data.split("T")
16
- begin
17
- Date.parse(parts[0])
18
- rescue Exception
19
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
20
- return
21
- end
22
- begin
23
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[1].to_i > 23
24
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[2].to_i > 59
25
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[3].to_i > 59
26
- rescue Exception
27
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
28
- return
29
- end
30
- else
31
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
32
- return
33
- end
34
- end
35
- # Date in the format of YYYY-MM-DD
36
- when 'date'
37
- if data.is_a?(String)
38
- error_message = "The property '#{build_fragment(fragments)}' must be a date in the format of YYYY-MM-DD"
39
- r = Regexp.new('^\d\d\d\d-\d\d-\d\d$')
40
- if (m = r.match(data))
41
- begin
42
- Date.parse(data)
43
- rescue Exception
44
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
45
- return
46
- end
47
- else
48
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
49
- return
50
- end
51
- end
52
-
53
- # Time in the format of HH:MM:SS
54
- when 'time'
55
- if data.is_a?(String)
56
- error_message = "The property '#{build_fragment(fragments)}' must be a time in the format of hh:mm:ss"
57
- r = Regexp.new('^(\d\d):(\d\d):(\d\d)$')
58
- if (m = r.match(data))
59
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[1].to_i > 23
60
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[2].to_i > 59
61
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[3].to_i > 59
62
- else
63
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
64
- return
65
- end
66
- end
67
-
68
- # IPv4 in dotted-quad format
69
- when 'ip-address', 'ipv4'
70
- if data.is_a?(String)
71
- error_message = "The property '#{build_fragment(fragments)}' must be a valid IPv4 address"
72
- r = Regexp.new('^(\d+){1,3}\.(\d+){1,3}\.(\d+){1,3}\.(\d+){1,3}$')
73
- if (m = r.match(data))
74
- 1.upto(4) do |x|
75
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[x].to_i > 255
76
- end
77
- else
78
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
79
- return
80
- end
81
- end
82
7
 
83
- # IPv6 in standard format (including abbreviations)
84
- when 'ipv6'
85
- if data.is_a?(String)
86
- error_message = "The property '#{build_fragment(fragments)}' must be a valid IPv6 address"
87
- r = Regexp.new('^[a-f0-9:]+$')
88
- if (m = r.match(data))
89
- # All characters are valid, now validate structure
90
- parts = data.split(":")
91
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if parts.length > 8
92
- condensed_zeros = false
93
- parts.each do |part|
94
- if part.length == 0
95
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if condensed_zeros
96
- condensed_zeros = true
97
- end
98
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if part.length > 4
99
- end
100
- else
101
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
102
- return
103
- end
104
- end
105
-
106
- when 'uri'
107
- if data.is_a?(String)
108
- error_message = "The property '#{build_fragment(fragments)}' must be a valid URI"
109
- begin
110
- URI.parse(URI.escape(data))
111
- rescue URI::InvalidURIError
112
- validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
113
- end
114
- end
115
-
116
- when 'hostname'
117
-
118
- when 'email'
8
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
9
+ if self.data_valid_for_type?(data, current_schema.schema['type'])
10
+ format = current_schema.schema['format'].to_s
11
+ validator = validator.formats[format]
12
+ validator.validate(current_schema, data, fragments, processor, validator, options) unless validator.nil?
119
13
  end
120
14
  end
121
15
  end
@@ -0,0 +1,22 @@
1
+ require 'json-schema/attribute'
2
+ require 'json-schema/errors/custom_format_error'
3
+
4
+ require 'uri'
5
+ module JSON
6
+ class Schema
7
+ class CustomFormat < FormatAttribute
8
+ def initialize(validation_proc)
9
+ @validation_proc = validation_proc
10
+ end
11
+
12
+ def validate(current_schema, data, fragments, processor, validator, options = {})
13
+ begin
14
+ @validation_proc.call data
15
+ rescue JSON::Schema::CustomFormatError => e
16
+ message = "The property '#{self.class.build_fragment(fragments)}' #{e.message}"
17
+ self.class.validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ require 'json-schema/attribute'
2
+ require 'uri'
3
+ module JSON
4
+ class Schema
5
+ class DateFormat < FormatAttribute
6
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
7
+ if data.is_a?(String)
8
+ error_message = "The property '#{build_fragment(fragments)}' must be a date in the format of YYYY-MM-DD"
9
+ r = Regexp.new('^\d\d\d\d-\d\d-\d\d$')
10
+ if (r.match(data))
11
+ begin
12
+ Date.parse(data)
13
+ rescue Exception
14
+ validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
15
+ return
16
+ end
17
+ else
18
+ validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
19
+ return
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ require 'json-schema/attribute'
2
+ require 'uri'
3
+ module JSON
4
+ class Schema
5
+ class DateTimeFormat < FormatAttribute
6
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
7
+ # Timestamp in restricted ISO-8601 YYYY-MM-DDThh:mm:ssZ with optional decimal fraction of the second
8
+ if data.is_a?(String)
9
+ error_message = "The property '#{build_fragment(fragments)}' must be a date/time in the ISO-8601 format of YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss.ssZ"
10
+ r = Regexp.new('^\d\d\d\d-\d\d-\d\dT(\d\d):(\d\d):(\d\d)([\.,]\d+)?(Z|[+-](\d\d)(:?\d\d)?)?$')
11
+ if (m = r.match(data))
12
+ parts = data.split("T")
13
+ begin
14
+ Date.parse(parts[0])
15
+ rescue Exception
16
+ validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
17
+ return
18
+ end
19
+ begin
20
+ validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[1].to_i > 23
21
+ validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[2].to_i > 59
22
+ validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[3].to_i > 59
23
+ rescue Exception
24
+ validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
25
+ return
26
+ end
27
+ else
28
+ validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors])
29
+ return
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end