json-schema 0.1.13 → 0.1.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ module JSON
2
+ class Schema
3
+ class MaximumAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ if data.is_a?(Numeric)
6
+ if (current_schema.schema['exclusiveMaximum'] ? data >= current_schema.schema['maximum'] : data > current_schema.schema['maximum'])
7
+ message = "The property '#{build_fragment(fragments)}' did not have a maximum value of #{current_schema.schema['maximum']}, "
8
+ message += current_schema.schema['exclusiveMaximum'] ? 'exclusively' : 'inclusively'
9
+ raise ValidationError.new(message, fragments, current_schema)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ module JSON
2
+ class Schema
3
+ class MaxItemsAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ if data.is_a?(Array) && (data.compact.size > current_schema.schema['maxItems'])
6
+ message = "The property '#{build_fragment(fragments)}' did not contain a minimum number of items #{current_schema.schema['minItems']}"
7
+ raise ValidationError.new(message, fragments, current_schema)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module JSON
2
+ class Schema
3
+ class MaxLengthAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ if data.is_a?(String)
6
+ if data.length > current_schema.schema['maxLength']
7
+ message = "The property '#{build_fragment(fragments)}' was not of a maximum string length of #{current_schema.schema['maxLength']}"
8
+ raise ValidationError.new(message, fragments, current_schema)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module JSON
2
+ class Schema
3
+ class MinimumAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ if data.is_a?(Numeric)
6
+ if (current_schema.schema['exclusiveMinimum'] ? data <= current_schema.schema['minimum'] : data < current_schema.schema['minimum'])
7
+ message = "The property '#{build_fragment(fragments)}' did not have a minimum value of #{current_schema.schema['minimum']}, "
8
+ message += current_schema.schema['exclusiveMinimum'] ? 'exclusively' : 'inclusively'
9
+ raise ValidationError.new(message, fragments, current_schema)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ module JSON
2
+ class Schema
3
+ class MinItemsAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ if data.is_a?(Array) && (data.compact.size < current_schema.schema['minItems'])
6
+ message = "The property '#{build_fragment(fragments)}' did not contain a minimum number of items #{current_schema.schema['minItems']}"
7
+ raise ValidationError.new(message, fragments, current_schema)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module JSON
2
+ class Schema
3
+ class MinLengthAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ if data.is_a?(String)
6
+ if data.length < current_schema.schema['minLength']
7
+ message = "The property '#{build_fragment(fragments)}' was not of a minimum string length of #{current_schema.schema['minLength']}"
8
+ raise ValidationError.new(message, fragments, current_schema)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module JSON
2
+ class Schema
3
+ class PatternAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ if data.is_a?(String)
6
+ r = Regexp.new(current_schema.schema['pattern'])
7
+ if (r.match(data)).nil?
8
+ message = "The property '#{build_fragment(fragments)}' did not match the regex '#{current_schema.schema['pattern']}'"
9
+ raise ValidationError.new(message, fragments, current_schema)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ module JSON
2
+ class Schema
3
+ class PatternPropertiesAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ if data.is_a?(Hash)
6
+ current_schema.schema['patternProperties'].each do |property,property_schema|
7
+ r = Regexp.new(property)
8
+
9
+ # Check each key in the data hash to see if it matches the regex
10
+ data.each do |key,value|
11
+ if r.match(key)
12
+ schema = JSON::Schema.new(property_schema,current_schema.uri,validator)
13
+ fragments << key
14
+ schema.validate(data[key],fragments)
15
+ fragments.pop
16
+ end
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 PropertiesAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ if data.is_a?(Hash)
6
+ current_schema.schema['properties'].each do |property,property_schema|
7
+ if (property_schema['required'] && !data.has_key?(property))
8
+ message = "The property '#{build_fragment(fragments)}' did not contain a required property of '#{property}'"
9
+ raise ValidationError.new(message, fragments, current_schema)
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)
16
+ fragments.pop
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,53 @@
1
+ module JSON
2
+ class Schema
3
+ class RefAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ temp_uri = URI.parse(current_schema.schema['$ref'])
6
+ if temp_uri.relative?
7
+ temp_uri = current_schema.uri.clone
8
+ # Check for absolute path
9
+ path = current_schema.schema['$ref'].split("#")[0]
10
+ if path.nil? || path == ''
11
+ temp_uri.path = current_schema.uri.path
12
+ elsif path[0,1] == "/"
13
+ temp_uri.path = Pathname.new(path).cleanpath.to_s
14
+ else
15
+ temp_uri.path = (Pathname.new(current_schema.uri.path).parent + path).cleanpath.to_s
16
+ end
17
+ temp_uri.fragment = current_schema.schema['$ref'].split("#")[1]
18
+ end
19
+ temp_uri.fragment = "" if temp_uri.fragment.nil?
20
+
21
+ # Grab the parent schema from the schema list
22
+ schema_key = temp_uri.to_s.split("#")[0]
23
+ ref_schema = JSON::Validator.schemas[schema_key]
24
+
25
+ if ref_schema
26
+ # Perform fragment resolution to retrieve the appropriate level for the schema
27
+ target_schema = ref_schema.schema
28
+ fragments = temp_uri.fragment.split("/")
29
+ fragment_path = ''
30
+ fragments.each do |fragment|
31
+ if fragment && fragment != ''
32
+ if target_schema.is_a?(Array)
33
+ target_schema = target_schema[fragment.to_i]
34
+ else
35
+ target_schema = target_schema[fragment]
36
+ end
37
+ fragment_path = fragment_path + "/#{fragment}"
38
+ if target_schema.nil?
39
+ raise SchemaError.new("The fragment '#{fragment_path}' does not exist on schema #{ref_schema.uri.to_s}")
40
+ end
41
+ end
42
+ end
43
+
44
+ # We have the schema finally, build it and validate!
45
+ schema = JSON::Schema.new(target_schema,temp_uri,validator)
46
+ schema.validate(data, fragments)
47
+ else
48
+ raise ValidationError.new("The referenced schema '#{temp_uri.to_s}' cannot be found", fragments, current_schema)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,71 @@
1
+ module JSON
2
+ class Schema
3
+ class TypeAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ union = true
6
+
7
+ if options[:disallow]
8
+ types = current_schema.schema['disallow']
9
+ else
10
+ types = current_schema.schema['type']
11
+ end
12
+
13
+ if !types.is_a?(Array)
14
+ types = [types]
15
+ union = false
16
+ end
17
+ valid = false
18
+
19
+ types.each do |type|
20
+ if type.is_a?(String)
21
+ case type
22
+ when "string"
23
+ valid = data.is_a?(String)
24
+ when "number"
25
+ valid = data.is_a?(Numeric)
26
+ when "integer"
27
+ valid = data.is_a?(Integer)
28
+ when "boolean"
29
+ valid = (data.is_a?(TrueClass) || data.is_a?(FalseClass))
30
+ when "object"
31
+ valid = data.is_a?(Hash)
32
+ when "array"
33
+ valid = data.is_a?(Array)
34
+ when "null"
35
+ valid = data.is_a?(NilClass)
36
+ when "any"
37
+ valid = true
38
+ else
39
+ valid = true
40
+ end
41
+ elsif type.is_a?(Hash) && union
42
+ # Validate as a schema
43
+ schema = JSON::Schema.new(type,current_schema.uri,validator)
44
+ begin
45
+ schema.validate(data,fragments)
46
+ valid = true
47
+ rescue ValidationError
48
+ # We don't care that these schemas don't validate - we only care that one validated
49
+ end
50
+ end
51
+
52
+ break if valid
53
+ end
54
+
55
+ if (options[:disallow])
56
+ if valid
57
+ message = "The property '#{build_fragment(fragments)}' matched one or more of the following types:"
58
+ types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
59
+ message.chop!
60
+ raise ValidationError.new(message, fragments, current_schema)
61
+ end
62
+ elsif !valid
63
+ message = "The property '#{build_fragment(fragments)}' did not match one or more of the following types:"
64
+ types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
65
+ message.chop!
66
+ raise ValidationError.new(message, fragments, current_schema)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,16 @@
1
+ module JSON
2
+ class Schema
3
+ class UniqueItemsAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ if data.is_a?(Array)
6
+ d = data.clone
7
+ dupes = d.uniq!
8
+ if dupes
9
+ message = "The property '#{build_fragment(fragments)}' contained duplicated array values"
10
+ raise ValidationError.new(message, fragments, current_schema)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -5,9 +5,9 @@ require 'pathname'
5
5
  module JSON
6
6
  class Schema
7
7
 
8
- attr_accessor :schema, :uri
8
+ attr_accessor :schema, :uri, :validator
9
9
 
10
- def initialize(schema,uri)
10
+ def initialize(schema,uri,parent_validator=nil)
11
11
  @schema = schema
12
12
  @uri = uri
13
13
 
@@ -21,6 +21,23 @@ module JSON
21
21
  @uri = temp_uri
22
22
  end
23
23
  @uri.fragment = nil
24
+
25
+ # If there is a $schema on this schema, use it to determine which validator to use
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
32
+ elsif parent_validator
33
+ @validator = parent_validator
34
+ else
35
+ @validator = JSON::Validator.default_validator
36
+ end
37
+ end
38
+
39
+ def validate(data, fragments)
40
+ @validator.validate(self, data, fragments)
24
41
  end
25
42
 
26
43
  def base_uri
@@ -7,19 +7,62 @@ require 'date'
7
7
 
8
8
  module JSON
9
9
 
10
- class ValidationError < Exception
11
- attr_reader :fragments, :schema
10
+ class Schema
11
+ class ValidationError < Exception
12
+ attr_reader :fragments, :schema
13
+
14
+ def initialize(message, fragments, schema)
15
+ @fragments = fragments
16
+ @schema = schema
17
+ message = "#{message} in schema #{schema.uri}"
18
+ super(message)
19
+ end
20
+ end
21
+
22
+ class SchemaError < Exception
23
+ end
24
+
25
+ class Attribute
26
+ def self.validate(current_schema, data, fragments, validator, options = {})
27
+ end
28
+
29
+ def self.build_fragment(fragments)
30
+ "#/#{fragments.join('/')}"
31
+ end
32
+ end
12
33
 
13
- def initialize(message, fragments, schema)
14
- @fragments = fragments
15
- @schema = schema
16
- message = "#{message} in schema #{schema.uri}"
17
- super(message)
34
+ class Validator
35
+ attr_accessor :attributes, :uri
36
+
37
+ def initialize()
38
+ @attributes = {}
39
+ @uri = nil
40
+ end
41
+
42
+ def extend_schema_definition(schema_uri)
43
+ u = URI.parse(schema_uri)
44
+ validator = JSON::Validator.validators["#{u.scheme}://#{u.host}#{u.path}"]
45
+ if validator.nil?
46
+ raise SchemaError.new("Schema not found: #{u.scheme}://#{u.host}#{u.path}")
47
+ end
48
+ @attributes.merge!(validator.attributes)
49
+ end
50
+
51
+ def to_s
52
+ "#{@uri.scheme}://#{uri.host}#{uri.path}"
53
+ end
54
+
55
+ def validate(current_schema, data, fragments)
56
+ current_schema.schema.each do |attr_name,attribute|
57
+ if @attributes.has_key?(attr_name)
58
+ @attributes[attr_name].validate(current_schema, data, fragments, self)
59
+ end
60
+ end
61
+ data
62
+ end
18
63
  end
19
64
  end
20
65
 
21
- class SchemaError < Exception
22
- end
23
66
 
24
67
  class Validator
25
68
 
@@ -28,31 +71,8 @@ module JSON
28
71
  @@default_opts = {
29
72
  :list => false
30
73
  }
31
-
32
- ValidationMethods = [
33
- "type",
34
- "disallow",
35
- "minimum",
36
- "maximum",
37
- "minItems",
38
- "maxItems",
39
- "uniqueItems",
40
- "pattern",
41
- "minLength",
42
- "maxLength",
43
- "divisibleBy",
44
- "enum",
45
- "properties",
46
- "patternProperties",
47
- "additionalProperties",
48
- "items",
49
- "additionalItems",
50
- "dependencies",
51
- "extends",
52
- "format",
53
- "$ref"
54
- ]
55
-
74
+ @@validators = {}
75
+ @@default_validator = nil
56
76
 
57
77
  def initialize(schema_data, data, opts={})
58
78
  @options = @@default_opts.clone.merge(opts)
@@ -65,549 +85,12 @@ module JSON
65
85
  # Run a simple true/false validation of data against a schema
66
86
  def validate()
67
87
  begin
68
- validate_schema(@base_schema, @data, [])
69
- Validator.clear_cache
70
- return true
71
- rescue ValidationError
72
- Validator.clear_cache
73
- return false
74
- end
75
- end
76
-
77
-
78
- # Validate data against a schema, returning nil if the data is valid. If the data is invalid,
79
- # a ValidationError will be raised with links to the specific location that the first error
80
- # occurred during validation
81
- def validate2()
82
- begin
83
- validate_schema(@base_schema, @data, [])
88
+ @base_schema.validate(@data,[])
84
89
  Validator.clear_cache
85
- rescue ValidationError
90
+ rescue JSON::Schema::ValidationError
86
91
  Validator.clear_cache
87
92
  raise $!
88
93
  end
89
- nil
90
- end
91
-
92
-
93
- # Validate the current schema
94
- def validate_schema(current_schema, data, fragments)
95
-
96
- ValidationMethods.each do |method|
97
- if !current_schema.schema[method].nil?
98
- self.send(("validate_" + method.sub('$','')).to_sym, current_schema, data, fragments)
99
- end
100
- end
101
-
102
- data
103
- end
104
-
105
-
106
- # Validate the type
107
- def validate_type(current_schema, data, fragments, disallow=false)
108
- union = true
109
-
110
- if disallow
111
- types = current_schema.schema['disallow']
112
- else
113
- types = current_schema.schema['type']
114
- end
115
-
116
- if !types.is_a?(Array)
117
- types = [types]
118
- union = false
119
- end
120
- valid = false
121
-
122
- types.each do |type|
123
- if type.is_a?(String)
124
- case type
125
- when "string"
126
- valid = data.is_a?(String)
127
- when "number"
128
- valid = data.is_a?(Numeric)
129
- when "integer"
130
- valid = data.is_a?(Integer)
131
- when "boolean"
132
- valid = (data.is_a?(TrueClass) || data.is_a?(FalseClass))
133
- when "object"
134
- valid = data.is_a?(Hash)
135
- when "array"
136
- valid = data.is_a?(Array)
137
- when "null"
138
- valid = data.is_a?(NilClass)
139
- when "any"
140
- valid = true
141
- else
142
- valid = true
143
- end
144
- elsif type.is_a?(Hash) && union
145
- # Validate as a schema
146
- schema = JSON::Schema.new(type,current_schema.uri)
147
- begin
148
- validate_schema(schema,data,fragments)
149
- valid = true
150
- rescue ValidationError
151
- # We don't care that these schemas don't validate - we only care that one validated
152
- end
153
- end
154
-
155
- break if valid
156
- end
157
-
158
- if (disallow)
159
- if valid
160
- message = "The property '#{build_fragment(fragments)}' matched one or more of the following types:"
161
- types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
162
- message.chop!
163
- raise ValidationError.new(message, fragments, current_schema)
164
- end
165
- elsif !valid
166
- message = "The property '#{build_fragment(fragments)}' did not match one or more of the following types:"
167
- types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
168
- message.chop!
169
- raise ValidationError.new(message, fragments, current_schema)
170
- end
171
- end
172
-
173
-
174
- # Validate the disallowed types
175
- def validate_disallow(current_schema, data, fragments)
176
- validate_type(current_schema, data, fragments, true)
177
- end
178
-
179
-
180
- # Validate the format of an item
181
- def validate_format(current_schema, data, fragments)
182
- case current_schema.schema['format']
183
-
184
- # Timestamp in restricted ISO-8601 YYYY-MM-DDThh:mm:ssZ
185
- when 'date-time'
186
- error_message = "The property '#{build_fragment(fragments)}' must be a string and be a date/time in the ISO-8601 format of YYYY-MM-DDThh:mm:ssZ"
187
- raise ValidationError.new(error_message, fragments, current_schema) if !data.is_a?(String)
188
- r = Regexp.new('^\d\d\d\d-\d\d-\d\dT(\d\d):(\d\d):(\d\d)Z$')
189
- if (m = r.match(data))
190
- parts = data.split("T")
191
- begin
192
- Date.parse(parts[0])
193
- rescue Exception
194
- raise ValidationError.new(error_message, fragments, current_schema)
195
- end
196
- begin
197
- raise ValidationError.new(error_message, fragments, current_schema) if m[1].to_i > 23
198
- raise ValidationError.new(error_message, fragments, current_schema) if m[2].to_i > 59
199
- raise ValidationError.new(error_message, fragments, current_schema) if m[3].to_i > 59
200
- rescue Exception
201
- raise ValidationError.new(error_message, fragments, current_schema)
202
- end
203
- else
204
- raise ValidationError.new(error_message, fragments, current_schema)
205
- end
206
-
207
- # Date in the format of YYYY-MM-DD
208
- when 'date'
209
- error_message = "The property '#{build_fragment(fragments)}' must be a string and be a date in the format of YYYY-MM-DD"
210
- raise ValidationError.new(error_message, fragments, current_schema) if !data.is_a?(String)
211
- r = Regexp.new('^\d\d\d\d-\d\d-\d\d$')
212
- if (m = r.match(data))
213
- begin
214
- Date.parse(data)
215
- rescue Exception
216
- raise ValidationError.new(error_message, fragments, current_schema)
217
- end
218
- else
219
- raise ValidationError.new(error_message, fragments, current_schema)
220
- end
221
-
222
- # Time in the format of HH:MM:SS
223
- when 'time'
224
- error_message = "The property '#{build_fragment(fragments)}' must be a string and be a time in the format of hh:mm:ss"
225
- raise ValidationError.new(error_message, fragments, current_schema) if !data.is_a?(String)
226
- r = Regexp.new('^(\d\d):(\d\d):(\d\d)$')
227
- if (m = r.match(data))
228
- raise ValidationError.new(error_message, fragments, current_schema) if m[1].to_i > 23
229
- raise ValidationError.new(error_message, fragments, current_schema) if m[2].to_i > 59
230
- raise ValidationError.new(error_message, fragments, current_schema) if m[3].to_i > 59
231
- else
232
- raise ValidationError.new(error_message, fragments, current_schema)
233
- end
234
-
235
- # IPv4 in dotted-quad format
236
- when 'ip-address', 'ipv4'
237
- error_message = "The property '#{build_fragment(fragments)}' must be a string and be a valid IPv4 address"
238
- raise ValidationError.new(error_message, fragments, current_schema) if !data.is_a?(String)
239
- r = Regexp.new('^(\d+){1,3}\.(\d+){1,3}\.(\d+){1,3}\.(\d+){1,3}$')
240
- if (m = r.match(data))
241
- 1.upto(4) do |x|
242
- raise ValidationError.new(error_message, fragments, current_schema) if m[x].to_i > 255
243
- end
244
- else
245
- raise ValidationError.new(error_message, fragments, current_schema)
246
- end
247
-
248
- # IPv6 in standard format (including abbreviations)
249
- when 'ipv6'
250
- error_message = "The property '#{build_fragment(fragments)}' must be a string and be a valid IPv6 address"
251
- raise ValidationError.new(error_message, fragments, current_schema) if !data.is_a?(String)
252
- r = Regexp.new('^[a-f0-9:]+$')
253
- if (m = r.match(data))
254
- # All characters are valid, now validate structure
255
- parts = data.split(":")
256
- raise ValidationError.new(error_message, fragments, current_schema) if parts.length > 8
257
- condensed_zeros = false
258
- parts.each do |part|
259
- if part.length == 0
260
- raise ValidationError.new(error_message, fragments, current_schema) if condensed_zeros
261
- condensed_zeros = true
262
- end
263
- raise ValidationError.new(error_message, fragments, current_schema) if part.length > 4
264
- end
265
- else
266
- raise ValidationError.new(error_message, fragments, current_schema)
267
- end
268
-
269
- # Milliseconds since the epoch. Must be an integer or a float
270
- when 'utc-millisec'
271
- error_message = "The property '#{build_fragment(fragments)}' must be an integer or a float"
272
- raise ValidationError.new(error_message, fragments, current_schema) if (!data.is_a?(Numeric))
273
-
274
- # Must be a string
275
- when 'regex','color','style','phone','uri','email','host-name'
276
- error_message = "The property '#{build_fragment(fragments)}' must be a string"
277
- raise ValidationError.new(error_message, fragments, current_schema) if (!data.is_a?(String))
278
- end
279
- end
280
-
281
-
282
- # Validate the minimum value of a number
283
- def validate_minimum(current_schema, data, fragments)
284
- if data.is_a?(Numeric)
285
- if (current_schema.schema['exclusiveMinimum'] ? data <= current_schema.schema['minimum'] : data < current_schema.schema['minimum'])
286
- message = "The property '#{build_fragment(fragments)}' did not have a minimum value of #{current_schema.schema['minimum']}, "
287
- message += current_schema.schema['exclusiveMinimum'] ? 'exclusively' : 'inclusively'
288
- raise ValidationError.new(message, fragments, current_schema)
289
- end
290
- end
291
- end
292
-
293
-
294
- # Validate the maximum value of a number
295
- def validate_maximum(current_schema, data, fragments)
296
- if data.is_a?(Numeric)
297
- if (current_schema.schema['exclusiveMaximum'] ? data >= current_schema.schema['maximum'] : data > current_schema.schema['maximum'])
298
- message = "The property '#{build_fragment(fragments)}' did not have a maximum value of #{current_schema.schema['maximum']}, "
299
- message += current_schema.schema['exclusiveMaximum'] ? 'exclusively' : 'inclusively'
300
- raise ValidationError.new(message, fragments, current_schema)
301
- end
302
- end
303
- end
304
-
305
-
306
- # Validate the minimum number of items in an array
307
- def validate_minItems(current_schema, data, fragments)
308
- if data.is_a?(Array) && (data.compact.size < current_schema.schema['minItems'])
309
- message = "The property '#{build_fragment(fragments)}' did not contain a minimum number of items #{current_schema.schema['minItems']}"
310
- raise ValidationError.new(message, fragments, current_schema)
311
- end
312
- end
313
-
314
-
315
- # Validate the maximum number of items in an array
316
- def validate_maxItems(current_schema, data, fragments)
317
- if data.is_a?(Array) && (data.compact.size > current_schema.schema['maxItems'])
318
- message = "The property '#{build_fragment(fragments)}' did not contain a minimum number of items #{current_schema.schema['minItems']}"
319
- raise ValidationError.new(message, fragments, current_schema)
320
- end
321
- end
322
-
323
-
324
- # Validate the uniqueness of elements in an array
325
- def validate_uniqueItems(current_schema, data, fragments)
326
- if data.is_a?(Array)
327
- d = data.clone
328
- dupes = d.uniq!
329
- if dupes
330
- message = "The property '#{build_fragment(fragments)}' contained duplicated array values"
331
- raise ValidationError.new(message, fragments, current_schema)
332
- end
333
- end
334
- end
335
-
336
-
337
- # Validate a string matches a regex pattern
338
- def validate_pattern(current_schema, data, fragments)
339
- if data.is_a?(String)
340
- r = Regexp.new(current_schema.schema['pattern'])
341
- if (r.match(data)).nil?
342
- message = "The property '#{build_fragment(fragments)}' did not match the regex '#{current_schema.schema['pattern']}'"
343
- raise ValidationError.new(message, fragments, current_schema)
344
- end
345
- end
346
- end
347
-
348
-
349
- # Validate a string is at least of a certain length
350
- def validate_minLength(current_schema, data, fragments)
351
- if data.is_a?(String)
352
- if data.length < current_schema.schema['minLength']
353
- message = "The property '#{build_fragment(fragments)}' was not of a minimum string length of #{current_schema.schema['minLength']}"
354
- raise ValidationError.new(message, fragments, current_schema)
355
- end
356
- end
357
- end
358
-
359
-
360
- # Validate a string is at maximum of a certain length
361
- def validate_maxLength(current_schema, data, fragments)
362
- if data.is_a?(String)
363
- if data.length > current_schema.schema['maxLength']
364
- message = "The property '#{build_fragment(fragments)}' was not of a maximum string length of #{current_schema.schema['maxLength']}"
365
- raise ValidationError.new(message, fragments, current_schema)
366
- end
367
- end
368
- end
369
-
370
-
371
- # Validate a numeric is divisible by another numeric
372
- def validate_divisibleBy(current_schema, data, fragments)
373
- if data.is_a?(Numeric)
374
- if current_schema.schema['divisibleBy'] == 0 ||
375
- current_schema.schema['divisibleBy'] == 0.0 ||
376
- (BigDecimal.new(data.to_s) % BigDecimal.new(current_schema.schema['divisibleBy'].to_s)).to_f != 0
377
- message = "The property '#{build_fragment(fragments)}' was not divisible by #{current_schema.schema['divisibleBy']}"
378
- raise ValidationError.new(message, fragments, current_schema)
379
- end
380
- end
381
- end
382
-
383
-
384
- # Validate an item matches at least one of an array of values
385
- def validate_enum(current_schema, data, fragments)
386
- if !current_schema.schema['enum'].include?(data)
387
- message = "The property '#{build_fragment(fragments)}' did not match one of the following values:"
388
- current_schema.schema['enum'].each {|val|
389
- if val.is_a?(NilClass)
390
- message += " null,"
391
- elsif val.is_a?(Array)
392
- message += " (array),"
393
- elsif val.is_a?(Hash)
394
- message += " (object),"
395
- else
396
- message += " #{val.to_s},"
397
- end
398
- }
399
- message.chop!
400
- raise ValidationError.new(message, fragments, current_schema)
401
- end
402
- end
403
-
404
-
405
- # Validate a set of properties of an object
406
- def validate_properties(current_schema, data, fragments)
407
- if data.is_a?(Hash)
408
- current_schema.schema['properties'].each do |property,property_schema|
409
- if (property_schema['required'] && !data.has_key?(property))
410
- message = "The property '#{build_fragment(fragments)}' did not contain a required property of '#{property}'"
411
- raise ValidationError.new(message, fragments, current_schema)
412
- end
413
-
414
- if data.has_key?(property)
415
- schema = JSON::Schema.new(property_schema,current_schema.uri)
416
- fragments << property
417
- validate_schema(schema, data[property], fragments)
418
- fragments.pop
419
- end
420
- end
421
- end
422
- end
423
-
424
-
425
- # Validate properties of an object against a schema when the property name matches a specific regex
426
- def validate_patternProperties(current_schema, data, fragments)
427
- if data.is_a?(Hash)
428
- current_schema.schema['patternProperties'].each do |property,property_schema|
429
- r = Regexp.new(property)
430
-
431
- # Check each key in the data hash to see if it matches the regex
432
- data.each do |key,value|
433
- if r.match(key)
434
- schema = JSON::Schema.new(property_schema,current_schema.uri)
435
- fragments << key
436
- validate_schema(schema, data[key], fragments)
437
- fragments.pop
438
- end
439
- end
440
- end
441
- end
442
- end
443
-
444
-
445
- # Validate properties of an object that are not defined in the schema at least validate against a set of rules
446
- def validate_additionalProperties(current_schema, data, fragments)
447
- if data.is_a?(Hash)
448
- extra_properties = data.keys
449
-
450
- if current_schema.schema['properties']
451
- extra_properties = extra_properties - current_schema.schema['properties'].keys
452
- end
453
-
454
- if current_schema.schema['patternProperties']
455
- current_schema.schema['patternProperties'].each_key do |key|
456
- r = Regexp.new(key)
457
- extras_clone = extra_properties.clone
458
- extras_clone.each do |prop|
459
- if r.match(prop)
460
- extra_properties = extra_properties - [prop]
461
- end
462
- end
463
- end
464
- end
465
-
466
- if current_schema.schema['additionalProperties'] == false && !extra_properties.empty?
467
- message = "The property '#{build_fragment(fragments)}' contains additional properties outside of the schema when none are allowed"
468
- raise ValidationError.new(message, fragments, current_schema)
469
- elsif current_schema.schema['additionalProperties'].is_a?(Hash)
470
- extra_properties.each do |key|
471
- schema = JSON::Schema.new(current_schema.schema['additionalProperties'],current_schema.uri)
472
- fragments << key
473
- validate_schema(schema, data[key], fragments)
474
- fragments.pop
475
- end
476
- end
477
- end
478
- end
479
-
480
-
481
- # Validate items in an array match a schema or a set of schemas
482
- def validate_items(current_schema, data, fragments)
483
- if data.is_a?(Array)
484
- if current_schema.schema['items'].is_a?(Hash)
485
- data.each_with_index do |item,i|
486
- schema = JSON::Schema.new(current_schema.schema['items'],current_schema.uri)
487
- fragments << i.to_s
488
- validate_schema(schema,item,fragments)
489
- fragments.pop
490
- end
491
- elsif current_schema.schema['items'].is_a?(Array)
492
- current_schema.schema['items'].each_with_index do |item_schema,i|
493
- schema = JSON::Schema.new(item_schema,current_schema.uri)
494
- fragments << i.to_s
495
- validate_schema(schema,data[i],fragments)
496
- fragments.pop
497
- end
498
- end
499
- end
500
- end
501
-
502
-
503
- # Validate items in an array that are not part of the schema at least match a set of rules
504
- def validate_additionalItems(current_schema, data, fragments)
505
- if data.is_a?(Array) && current_schema.schema['items'].is_a?(Array)
506
- if current_schema.schema['additionalItems'] == false && current_schema.schema['items'].length != data.length
507
- message = "The property '#{build_fragment(fragments)}' contains additional array elements outside of the schema when none are allowed"
508
- raise ValidationError.new(message, fragments, current_schema)
509
- elsif current_schema.schema['additionalItems'].is_a?(Hash)
510
- schema = JSON::Schema.new(current_schema.schema['additionalItems'],current_schema.uri)
511
- data.each_with_index do |item,i|
512
- if i >= current_schema.schema['items'].length
513
- fragments << i.to_s
514
- validate_schema(schema, item, fragments)
515
- fragments.pop
516
- end
517
- end
518
- end
519
- end
520
- end
521
-
522
-
523
- # Validate the dependencies of a property
524
- def validate_dependencies(current_schema, data, fragments)
525
- if data.is_a?(Hash)
526
- current_schema.schema['dependencies'].each do |property,dependency_value|
527
- if data.has_key?(property)
528
- if dependency_value.is_a?(String) && !data.has_key?(dependency_value)
529
- message = "The property '#{build_fragment(fragments)}' has a property '#{property}' that depends on a missing property '#{dependency_value}'"
530
- raise ValidationError.new(message, fragments, current_schema)
531
- elsif dependency_value.is_a?(Array)
532
- dependency_value.each do |value|
533
- if !data.has_key?(value)
534
- message = "The property '#{build_fragment(fragments)}' has a property '#{property}' that depends on a missing property '#{value}'"
535
- raise ValidationError.new(message, fragments, current_schema)
536
- end
537
- end
538
- else
539
- schema = JSON::Schema.new(dependency_value,current_schema.uri)
540
- validate_schema(schema, data, fragments)
541
- end
542
- end
543
- end
544
- end
545
- end
546
-
547
-
548
- # Validate extensions of other schemas
549
- def validate_extends(current_schema, data, fragments)
550
- schemas = current_schema.schema['extends']
551
- schemas = [schemas] if !schemas.is_a?(Array)
552
- schemas.each do |s|
553
- schema = JSON::Schema.new(s,current_schema.uri)
554
- validate_schema(schema, data, fragments)
555
- end
556
- end
557
-
558
-
559
- # Validate schema references
560
- def validate_ref(current_schema, data, fragments)
561
- temp_uri = URI.parse(current_schema.schema['$ref'])
562
- if temp_uri.relative?
563
- temp_uri = current_schema.uri.clone
564
- # Check for absolute path
565
- path = current_schema.schema['$ref'].split("#")[0]
566
- if path.nil? || path == ''
567
- temp_uri.path = current_schema.uri.path
568
- elsif path[0,1] == "/"
569
- temp_uri.path = Pathname.new(path).cleanpath.to_s
570
- else
571
- temp_uri.path = (Pathname.new(current_schema.uri.path).parent + path).cleanpath.to_s
572
- end
573
- temp_uri.fragment = current_schema.schema['$ref'].split("#")[1]
574
- end
575
- temp_uri.fragment = "" if temp_uri.fragment.nil?
576
-
577
- # Grab the parent schema from the schema list
578
- schema_key = temp_uri.to_s.split("#")[0]
579
- ref_schema = Validator.schemas[schema_key]
580
-
581
- if ref_schema
582
- # Perform fragment resolution to retrieve the appropriate level for the schema
583
- target_schema = ref_schema.schema
584
- fragments = temp_uri.fragment.split("/")
585
- fragment_path = ''
586
- fragments.each do |fragment|
587
- if fragment && fragment != ''
588
- if target_schema.is_a?(Array)
589
- target_schema = target_schema[fragment.to_i]
590
- else
591
- target_schema = target_schema[fragment]
592
- end
593
- fragment_path = fragment_path + "/#{fragment}"
594
- if target_schema.nil?
595
- raise SchemaError.new("The fragment '#{fragment_path}' does not exist on schema #{ref_schema.uri.to_s}")
596
- end
597
- end
598
- end
599
-
600
- # We have the schema finally, build it and validate!
601
- schema = JSON::Schema.new(target_schema,temp_uri)
602
- validate_schema(schema, data, fragments)
603
- else
604
- raise ValidationError.new("The referenced schema '#{temp_uri.to_s}' cannot be found", fragments, current_schema)
605
- end
606
- end
607
-
608
-
609
- def build_fragment(fragments)
610
- "#/#{fragments.join('/')}"
611
94
  end
612
95
 
613
96
 
@@ -707,14 +190,20 @@ module JSON
707
190
 
708
191
  class << self
709
192
  def validate(schema, data,opts={})
710
- validator = JSON::Validator.new(schema, data, opts)
711
- validator.validate
193
+ begin
194
+ validator = JSON::Validator.new(schema, data, opts)
195
+ validator.validate
196
+ return true
197
+ rescue JSON::Schema::ValidationError, JSON::Schema::SchemaError
198
+ return false
199
+ end
712
200
  end
713
201
 
714
- def validate2(schema, data,opts={})
202
+ def validate!(schema, data,opts={})
715
203
  validator = JSON::Validator.new(schema, data, opts)
716
- validator.validate2
204
+ validator.validate
717
205
  end
206
+ alias_method 'validate2', 'validate!'
718
207
 
719
208
  def clear_cache
720
209
  @@schemas = {} if @@cache_schemas == false
@@ -731,7 +220,22 @@ module JSON
731
220
  def cache_schemas=(val)
732
221
  @@cache_schemas = val == true ? true : false
733
222
  end
734
-
223
+
224
+ def validators
225
+ @@validators
226
+ end
227
+
228
+ def default_validator
229
+ @@default_validator
230
+ end
231
+
232
+ def register_validator(v)
233
+ @@validators[v.to_s] = v
234
+ end
235
+
236
+ def register_default_validator(v)
237
+ @@default_validator = v
238
+ end
735
239
  end
736
240
 
737
241