json-schema 0.1.13 → 0.1.14

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