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
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