json-schema 1.2.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -1,6 +1,10 @@
1
1
  h1. Ruby JSON Schema Validator
2
2
 
3
- This library is intended to provide Ruby with an interface for validating JSON objects against a JSON schema conforming to "JSON Schema Draft 3":http://tools.ietf.org/html/draft-zyp-json-schema-03. Legacy support for "JSON Schema Draft 2":http://tools.ietf.org/html/draft-zyp-json-schema-02 and "JSON Schema Draft 1":http://tools.ietf.org/html/draft-zyp-json-schema-01 is also included.
3
+ This library is intended to provide Ruby with an interface for validating JSON objects against a JSON schema conforming to "JSON Schema Draft 4":http://tools.ietf.org/html/draft-zyp-json-schema-04. Legacy support for "JSON Schema Draft 3":http://tools.ietf.org/html/draft-zyp-json-schema-03, "JSON Schema Draft 2":http://tools.ietf.org/html/draft-zyp-json-schema-02, and "JSON Schema Draft 1":http://tools.ietf.org/html/draft-zyp-json-schema-01 is also included.
4
+
5
+ h2. Version 2.0.0 Upgrade Notes
6
+
7
+ Please be aware that the upgrade to version 2.0.0 will use Draft-04 *by default*, so schemas that do not declare a validator using the `$schema` keyword will use Draft-04 now instead of Draft-03. This is the reason for the major version upgrade.
4
8
 
5
9
  h2. Dependencies
6
10
 
@@ -18,7 +22,7 @@ From the git repo:
18
22
 
19
23
  <pre>
20
24
  $ gem build json-schema.gemspec
21
- $ gem install json-schema-1.2.1.gem
25
+ $ gem install json-schema-2.0.0.gem
22
26
  </pre>
23
27
 
24
28
 
@@ -28,7 +32,7 @@ Three base validation methods exist: <code>validate</code>, <code>validate!</cod
28
32
 
29
33
  All methods take two arguments, which can be either a JSON string, a file containing JSON, or a Ruby object representing JSON data. The first argument to these methods is always the schema, the second is always the data to validate. An optional third options argument is also accepted; available options are used in the examples below.
30
34
 
31
- By default, the validator uses the "JSON Schema Draft 3":http://tools.ietf.org/html/draft-zyp-json-schema-03 specification for validation; however, the user is free to specify additional specifications or extend existing ones. Legacy support for Draft 1 and Draft 2 is included by either passing an optional <code>:version</code> parameter to the <code>validate</code> method (set either as <code>:draft1</code> or <code>draft2</code>), or by declaring the <code>$schema</code> attribute in the schema and referencing the appropriate specification URI. Note that the <code>$schema</code> attribute takes precedence over the <code>:version</code> option during parsing and validation.
35
+ By default, the validator uses the "JSON Schema Draft 4":http://tools.ietf.org/html/draft-zyp-json-schema-04 specification for validation; however, the user is free to specify additional specifications or extend existing ones. Legacy support for Draft 1, Draft 2, and Draft 3 is included by either passing an optional <code>:version</code> parameter to the <code>validate</code> method (set either as <code>:draft1</code> or <code>draft2</code>), or by declaring the <code>$schema</code> attribute in the schema and referencing the appropriate specification URI. Note that the <code>$schema</code> attribute takes precedence over the <code>:version</code> option during parsing and validation.
32
36
 
33
37
  h3. Validate Ruby objects against a Ruby schema
34
38
 
@@ -38,8 +42,9 @@ require 'json-schema'
38
42
 
39
43
  schema = {
40
44
  "type" => "object",
45
+ "required" => ["a"],
41
46
  "properties" => {
42
- "a" => {"type" => "integer", "required" => true}
47
+ "a" => {"type" => "integer"}
43
48
  }
44
49
  }
45
50
 
@@ -69,27 +74,6 @@ data = ['user','user','user']
69
74
  JSON::Validator.validate('user.json', data, :list => true)
70
75
  </pre>
71
76
 
72
- h3. Strictly validate an object's properties
73
-
74
- With the <code>:strict</code>code> option, validation fails when an object contains properties that are not defined in the schema's property list or doesn't match the <code>additionalProperties</code> property. Furthermore, all properties are treated as <code>required</code> by default.
75
-
76
- <pre>
77
- require 'rubygems'
78
- require 'json-schema'
79
-
80
- schema = {
81
- "type" => "object",
82
- "properties" => {
83
- "a" => {"type" => "integer"},
84
- "b" => {"type" => "integer"}
85
- }
86
- }
87
-
88
- JSON::Validator.validate(schema, {"a" => 1, "b" => 2}, :strict => true) # ==> true
89
- JSON::Validator.validate(schema, {"a" => 1, "b" => 2, "c" => 3}, :strict => true) # ==> false
90
- JSON::Validator.validate(schema, {"a" => 1}, :strict => true) # ==> false
91
- </pre>
92
-
93
77
  h3. Catch a validation error and print it out
94
78
 
95
79
  <pre>
@@ -98,8 +82,9 @@ require 'json-schema'
98
82
 
99
83
  schema = {
100
84
  "type" => "object",
85
+ "required" => ["a"],
101
86
  "properties" => {
102
- "a" => {"type" => "integer", "required" => true}
87
+ "a" => {"type" => "integer"}
103
88
  }
104
89
  }
105
90
 
@@ -123,9 +108,10 @@ require 'json-schema'
123
108
 
124
109
  schema = {
125
110
  "type" => "object",
111
+ "required" => ["a","b"],
126
112
  "properties" => {
127
- "a" => {"type" => "integer", "required" => true},
128
- "b" => {"type" => "string", "required" => "true"}
113
+ "a" => {"type" => "integer"},
114
+ "b" => {"type" => "string"}
129
115
  }
130
116
  }
131
117
 
@@ -146,9 +132,10 @@ require 'json-schema'
146
132
 
147
133
  schema = {
148
134
  "type" => "object",
135
+ "required" => ["a","b"],
149
136
  "properties" => {
150
- "a" => {"type" => "integer", "required" => true},
151
- "b" => {"type" => "string", "required" => "true"}
137
+ "a" => {"type" => "integer"},
138
+ "b" => {"type" => "string"}
152
139
  }
153
140
  }
154
141
 
@@ -170,8 +157,9 @@ require 'json-schema'
170
157
 
171
158
  schema = {
172
159
  "type" => "object",
160
+ "required" => ["a"],
173
161
  "properties" => {
174
- "a" => {"type" => "integer", "required" => "true"} # This will fail schema validation!
162
+ "a" => {"type" => "integer"} # This will fail schema validation!
175
163
  }
176
164
  }
177
165
 
@@ -193,8 +181,9 @@ require 'json-schema'
193
181
 
194
182
  schema = {
195
183
  "type" => "object",
184
+ "required" => ["a"],
196
185
  "properties" => {
197
- "a" => {"type" => "integer", "default" => 42, "required" => "true"},
186
+ "a" => {"type" => "integer", "default" => 42},
198
187
  "b" => {"type" => "integer"}
199
188
  }
200
189
  }
@@ -0,0 +1,37 @@
1
+ module JSON
2
+ class Schema
3
+ class AllOfAttribute < Attribute
4
+ 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
+ valid = true
8
+
9
+ current_schema.schema['allOf'].each do |element|
10
+ schema = JSON::Schema.new(element,current_schema.uri,validator)
11
+
12
+ # We're going to add a little cruft here to try and maintain any validation errors that occur in the allOf
13
+ # We'll handle this by keeping an error count before and after validation, extracting those errors and pushing them onto an error array
14
+ pre_validation_error_count = validation_errors(processor).count
15
+
16
+ begin
17
+ schema.validate(data,fragments,processor,options)
18
+ rescue ValidationError
19
+ valid = false
20
+ end
21
+
22
+ diff = validation_errors(processor).count - pre_validation_error_count
23
+ while diff > 0
24
+ diff = diff - 1
25
+ errors.push(validation_errors(processor).pop)
26
+ end
27
+ end
28
+
29
+ if !valid
30
+ message = "The property '#{build_fragment(fragments)}' of type #{data.class} did not match all of the required schemas"
31
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
32
+ validation_errors(processor).last.sub_errors = errors
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ module JSON
2
+ class Schema
3
+ class AnyOfAttribute < Attribute
4
+ 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
+ valid = false
8
+
9
+ current_schema.schema['anyOf'].each do |element|
10
+ schema = JSON::Schema.new(element,current_schema.uri,validator)
11
+
12
+ # We're going to add a little cruft here to try and maintain any validation errors that occur in the anyOf
13
+ # We'll handle this by keeping an error count before and after validation, extracting those errors and pushing them onto a union error
14
+ pre_validation_error_count = validation_errors(processor).count
15
+
16
+ begin
17
+ schema.validate(data,fragments,processor,options)
18
+ valid = true
19
+ rescue ValidationError
20
+ # We don't care that these schemas don't validate - we only care that one validated
21
+ end
22
+
23
+ diff = validation_errors(processor).count - pre_validation_error_count
24
+ valid = false if diff > 0
25
+ while diff > 0
26
+ diff = diff - 1
27
+ errors.push(validation_errors(processor).pop)
28
+ end
29
+
30
+ break if valid
31
+ end
32
+
33
+ if !valid
34
+ message = "The property '#{build_fragment(fragments)}' of type #{data.class} did not match one or more of the required schemas"
35
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
36
+ validation_errors(processor).last.sub_errors = errors
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ module JSON
2
+ class Schema
3
+ class DependenciesV4Attribute < Attribute
4
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
5
+ if data.is_a?(Hash)
6
+ current_schema.schema['dependencies'].each do |property,dependency_value|
7
+ if data.has_key?(property) && dependency_value.is_a?(Array)
8
+ dependency_value.each do |value|
9
+ if !data.has_key?(value)
10
+ message = "The property '#{build_fragment(fragments)}' has a property '#{property}' that depends on a missing property '#{value}'"
11
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ module JSON
2
+ class Schema
3
+ class MaxPropertiesAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
5
+ if data.is_a?(Hash) && (data.size > current_schema.schema['maxProperties'])
6
+ message = "The property '#{build_fragment(fragments)}' did not contain a minimum number of properties #{current_schema.schema['maxProperties']}"
7
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module JSON
2
+ class Schema
3
+ class MinPropertiesAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
5
+ if data.is_a?(Hash) && (data.size < current_schema.schema['minProperties'])
6
+ message = "The property '#{build_fragment(fragments)}' did not contain a minimum number of properties #{current_schema.schema['minProperties']}"
7
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ module JSON
2
+ class Schema
3
+ class MultipleOfAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
5
+ if data.is_a?(Numeric)
6
+ if current_schema.schema['multipleOf'] == 0 ||
7
+ current_schema.schema['multipleOf'] == 0.0 ||
8
+ (BigDecimal.new(data.to_s) % BigDecimal.new(current_schema.schema['multipleOf'].to_s)).to_f != 0
9
+ message = "The property '#{build_fragment(fragments)}' was not divisible by #{current_schema.schema['multipleOf']}"
10
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ module JSON
2
+ class Schema
3
+ class NotAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
5
+
6
+ schema = JSON::Schema.new(current_schema.schema['not'],current_schema.uri,validator)
7
+ failed = true
8
+ begin
9
+ schema.validate(data,fragments,processor,options)
10
+ message = "The property '#{build_fragment(fragments)}' of type #{data.class} matched the disallowed schema"
11
+ failed = false
12
+ rescue
13
+ # Yay, we failed validation
14
+ end
15
+
16
+ unless failed
17
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,35 @@
1
+ module JSON
2
+ class Schema
3
+ class OneOfAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
5
+ matched = false
6
+ valid = false
7
+
8
+ current_schema.schema['oneOf'].each do |element|
9
+ schema = JSON::Schema.new(element,current_schema.uri,validator)
10
+
11
+ begin
12
+ schema.validate(data,fragments,processor,options)
13
+ if matched
14
+ valid = false
15
+ else
16
+ matched = true
17
+ valid = true
18
+ end
19
+ rescue ValidationError
20
+ end
21
+
22
+ end
23
+
24
+ if !valid
25
+ if matched
26
+ message = "The property '#{build_fragment(fragments)}' of type #{data.class} matched more than one of the required schemas"
27
+ else
28
+ message = "The property '#{build_fragment(fragments)}' of type #{data.class} did not match any of the required schemas"
29
+ end
30
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -9,7 +9,7 @@ module JSON
9
9
  data[property] = (default.is_a?(Hash) ? default.clone : default)
10
10
  end
11
11
 
12
- if (property_schema['required'] || options[:strict] == true) and !data.has_key?(property)
12
+ if property_schema['required'] and !data.has_key?(property)
13
13
  message = "The property '#{build_fragment(fragments)}' did not contain a required property of '#{property}'"
14
14
  validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
15
15
  end
@@ -21,31 +21,6 @@ module JSON
21
21
  fragments.pop
22
22
  end
23
23
  end
24
-
25
- # When strict is true, ensure no undefined properties exist in the data
26
- if (options[:strict] == true && !current_schema.schema.has_key?('additionalProperties'))
27
- diff = data.select do |k,v|
28
- if current_schema.schema.has_key?('patternProperties')
29
- match = false
30
- current_schema.schema['patternProperties'].each do |property,property_schema|
31
- r = Regexp.new(property)
32
- if r.match(k)
33
- match = true
34
- break
35
- end
36
- end
37
-
38
- !current_schema.schema['properties'].has_key?(k.to_s) && !current_schema.schema['properties'].has_key?(k.to_sym) && !match
39
- else
40
- !current_schema.schema['properties'].has_key?(k.to_s) && !current_schema.schema['properties'].has_key?(k.to_sym)
41
- end
42
- end
43
-
44
- if diff.size > 0
45
- message = "The property '#{build_fragment(fragments)}' contained undefined properties: '#{diff.keys.join(", ")}'"
46
- validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
47
- end
48
- end
49
24
  end
50
25
  end
51
26
  end
@@ -0,0 +1,23 @@
1
+ module JSON
2
+ class Schema
3
+ class PropertiesV4Attribute < Attribute
4
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
5
+ if data.is_a?(Hash)
6
+ current_schema.schema['properties'].each do |property,property_schema|
7
+ if !data.has_key?(property) and property_schema['default'] and !property_schema['readonly'] and options[:insert_defaults]
8
+ default = property_schema['default']
9
+ data[property] = (default.is_a?(Hash) ? default.clone : default)
10
+ end
11
+
12
+ if data.has_key?(property)
13
+ schema = JSON::Schema.new(property_schema,current_schema.uri,validator)
14
+ fragments << property
15
+ schema.validate(data[property],fragments,processor,options)
16
+ fragments.pop
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module JSON
2
+ class Schema
3
+ class RequiredAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
5
+ if data.is_a?(Hash)
6
+ current_schema.schema['required'].each do |property,property_schema|
7
+ if !data.has_key?(property)
8
+ prop_defaults = options[:insert_defaults] &&
9
+ current_schema.schema['properties'] &&
10
+ current_schema.schema['properties'][property] &&
11
+ !current_schema.schema['properties'][property]["default"].nil? &&
12
+ !current_schema.schema['properties'][property]["readonly"]
13
+ if !prop_defaults
14
+ message = "The property '#{build_fragment(fragments)}' did not contain a required property of '#{property}'"
15
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -3,7 +3,6 @@ module JSON
3
3
  class TypeAttribute < Attribute
4
4
  def self.validate(current_schema, data, fragments, processor, validator, options = {})
5
5
  union = true
6
-
7
6
  if options[:disallow]
8
7
  types = current_schema.schema['disallow']
9
8
  else
@@ -15,28 +14,28 @@ module JSON
15
14
  union = false
16
15
  end
17
16
  valid = false
18
-
17
+
19
18
  # Create an array to hold errors that are generated during union validation
20
19
  union_errors = []
21
-
20
+
22
21
  types.each do |type|
23
22
  if type.is_a?(String)
24
23
  valid = data_valid_for_type?(data, type)
25
24
  elsif type.is_a?(Hash) && union
26
25
  # Validate as a schema
27
26
  schema = JSON::Schema.new(type,current_schema.uri,validator)
28
-
27
+
29
28
  # We're going to add a little cruft here to try and maintain any validation errors that occur in this union type
30
29
  # We'll handle this by keeping an error count before and after validation, extracting those errors and pushing them onto a union error
31
30
  pre_validation_error_count = validation_errors(processor).count
32
-
31
+
33
32
  begin
34
33
  schema.validate(data,fragments,processor,options)
35
34
  valid = true
36
35
  rescue ValidationError
37
36
  # We don't care that these schemas don't validate - we only care that one validated
38
37
  end
39
-
38
+
40
39
  diff = validation_errors(processor).count - pre_validation_error_count
41
40
  valid = false if diff > 0
42
41
  while diff > 0
@@ -66,7 +65,7 @@ module JSON
66
65
  message = "The property '#{build_fragment(fragments)}' of type #{data.class} did not match the following type:"
67
66
  types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
68
67
  message.chop!
69
- validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
68
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
70
69
  end
71
70
  end
72
71
  end