seomoz-json-schema 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/README.textile +216 -0
  2. data/lib/json-schema.rb +13 -0
  3. data/lib/json-schema/attributes/additionalitems.rb +23 -0
  4. data/lib/json-schema/attributes/additionalproperties.rb +39 -0
  5. data/lib/json-schema/attributes/dependencies.rb +30 -0
  6. data/lib/json-schema/attributes/disallow.rb +11 -0
  7. data/lib/json-schema/attributes/divisibleby.rb +16 -0
  8. data/lib/json-schema/attributes/enum.rb +24 -0
  9. data/lib/json-schema/attributes/extends.rb +14 -0
  10. data/lib/json-schema/attributes/format.rb +113 -0
  11. data/lib/json-schema/attributes/items.rb +25 -0
  12. data/lib/json-schema/attributes/maxdecimal.rb +15 -0
  13. data/lib/json-schema/attributes/maximum.rb +15 -0
  14. data/lib/json-schema/attributes/maximum_inclusive.rb +15 -0
  15. data/lib/json-schema/attributes/maxitems.rb +12 -0
  16. data/lib/json-schema/attributes/maxlength.rb +14 -0
  17. data/lib/json-schema/attributes/minimum.rb +15 -0
  18. data/lib/json-schema/attributes/minimum_inclusive.rb +15 -0
  19. data/lib/json-schema/attributes/minitems.rb +12 -0
  20. data/lib/json-schema/attributes/minlength.rb +14 -0
  21. data/lib/json-schema/attributes/pattern.rb +15 -0
  22. data/lib/json-schema/attributes/patternproperties.rb +23 -0
  23. data/lib/json-schema/attributes/properties.rb +23 -0
  24. data/lib/json-schema/attributes/properties_optional.rb +23 -0
  25. data/lib/json-schema/attributes/ref.rb +55 -0
  26. data/lib/json-schema/attributes/type.rb +71 -0
  27. data/lib/json-schema/attributes/uniqueitems.rb +16 -0
  28. data/lib/json-schema/schema.rb +50 -0
  29. data/lib/json-schema/uri/file.rb +32 -0
  30. data/lib/json-schema/uri/uuid.rb +285 -0
  31. data/lib/json-schema/validator.rb +425 -0
  32. data/lib/json-schema/validators/draft1.rb +32 -0
  33. data/lib/json-schema/validators/draft2.rb +33 -0
  34. data/lib/json-schema/validators/draft3.rb +38 -0
  35. data/resources/draft-01.json +155 -0
  36. data/resources/draft-02.json +166 -0
  37. data/resources/draft-03.json +174 -0
  38. data/test/data/bad_data_1.json +3 -0
  39. data/test/data/good_data_1.json +3 -0
  40. data/test/schemas/good_schema_1.json +10 -0
  41. data/test/schemas/good_schema_2.json +10 -0
  42. data/test/test_extended_schema.rb +68 -0
  43. data/test/test_files.rb +35 -0
  44. data/test/test_full_validation.rb +38 -0
  45. data/test/test_jsonschema_draft1.rb +703 -0
  46. data/test/test_jsonschema_draft2.rb +775 -0
  47. data/test/test_jsonschema_draft3.rb +972 -0
  48. data/test/test_schema_validation.rb +43 -0
  49. metadata +137 -0
@@ -0,0 +1,25 @@
1
+ module JSON
2
+ class Schema
3
+ class ItemsAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ if data.is_a?(Array)
6
+ if current_schema.schema['items'].is_a?(Hash)
7
+ data.each_with_index do |item,i|
8
+ schema = JSON::Schema.new(current_schema.schema['items'],current_schema.uri,validator)
9
+ fragments << i.to_s
10
+ schema.validate(item,fragments, options)
11
+ fragments.pop
12
+ end
13
+ elsif current_schema.schema['items'].is_a?(Array)
14
+ current_schema.schema['items'].each_with_index do |item_schema,i|
15
+ schema = JSON::Schema.new(item_schema,current_schema.uri,validator)
16
+ fragments << i.to_s
17
+ schema.validate(data[i],fragments, options)
18
+ fragments.pop
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ module JSON
2
+ class Schema
3
+ class MaxDecimalAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ if data.is_a?(Numeric)
6
+ s = data.to_s.split(".")[1]
7
+ if s && s.length > current_schema.schema['maxDecimal']
8
+ message = "The property '#{build_fragment(fragments)}' had more decimal places than the allowed #{current_schema.schema['maxDecimal']}"
9
+ validation_error(message, fragments, current_schema, options[:record_errors])
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -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
+ validation_error(message, fragments, current_schema, options[:record_errors])
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module JSON
2
+ class Schema
3
+ class MaximumInclusiveAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ if data.is_a?(Numeric)
6
+ if (current_schema.schema['maximumCanEqual'] == false ? 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
+ validation_error(message, fragments, current_schema, options[:record_errors])
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
+ validation_error(message, fragments, current_schema, options[:record_errors])
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
+ validation_error(message, fragments, current_schema, options[:record_errors])
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
+ validation_error(message, fragments, current_schema, options[:record_errors])
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module JSON
2
+ class Schema
3
+ class MinimumInclusiveAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, validator, options = {})
5
+ if data.is_a?(Numeric)
6
+ if (current_schema.schema['minimumCanEqual'] == false ? 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
+ validation_error(message, fragments, current_schema, options[:record_errors])
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
+ validation_error(message, fragments, current_schema, options[:record_errors])
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
+ validation_error(message, fragments, current_schema, options[:record_errors])
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
+ validation_error(message, fragments, current_schema, options[:record_errors])
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,options)
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
+ validation_error(message, fragments, current_schema, options[:record_errors])
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,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 PropertiesOptionalAttribute < 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['optional'].nil? || property_schema['optional'] == false) && !data.has_key?(property))
8
+ message = "The property '#{build_fragment(fragments)}' did not contain a required property of '#{property}'"
9
+ validation_error(message, fragments, current_schema, options[:record_errors])
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,options)
16
+ fragments.pop
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,55 @@
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 = current_schema.uri.merge(path)
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
+
24
+ ref_schema = JSON::Validator.schemas[schema_key]
25
+
26
+ if ref_schema
27
+ # Perform fragment resolution to retrieve the appropriate level for the schema
28
+ target_schema = ref_schema.schema
29
+ fragments = temp_uri.fragment.split("/")
30
+ fragment_path = ''
31
+ fragments.each do |fragment|
32
+ if fragment && fragment != ''
33
+ if target_schema.is_a?(Array)
34
+ target_schema = target_schema[fragment.to_i]
35
+ else
36
+ target_schema = target_schema[fragment]
37
+ end
38
+ fragment_path = fragment_path + "/#{fragment}"
39
+ if target_schema.nil?
40
+ raise SchemaError.new("The fragment '#{fragment_path}' does not exist on schema #{ref_schema.uri.to_s}")
41
+ end
42
+ end
43
+ end
44
+
45
+ # We have the schema finally, build it and validate!
46
+ schema = JSON::Schema.new(target_schema,temp_uri,validator)
47
+ schema.validate(data, fragments, options)
48
+ else
49
+ message = "The referenced schema '#{temp_uri.to_s}' cannot be found"
50
+ validation_error(message, fragments, current_schema, options[:record_errors])
51
+ end
52
+ end
53
+ end
54
+ end
55
+ 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,options)
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
+ validation_error(message, fragments, current_schema, options[:record_errors])
61
+ end
62
+ elsif !valid
63
+ message = "The property '#{build_fragment(fragments)}' of type #{data.class} 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
+ validation_error(message, fragments, current_schema, options[:record_errors])
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
+ validation_error(message, fragments, current_schema, options[:record_errors])
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,50 @@
1
+ require 'pathname'
2
+
3
+ module JSON
4
+ class Schema
5
+
6
+ attr_accessor :schema, :uri, :validator
7
+
8
+ def initialize(schema,uri,parent_validator=nil)
9
+ @schema = schema
10
+ @uri = uri
11
+
12
+ # If there is an ID on this schema, use it to generate the URI
13
+ if @schema['id']
14
+ temp_uri = URI.parse(@schema['id'])
15
+ if temp_uri.relative?
16
+ uri = uri.merge(@schema['id'])
17
+ temp_uri = uri
18
+ end
19
+ @uri = temp_uri
20
+ end
21
+ @uri.fragment = ''
22
+
23
+ # If there is a $schema on this schema, use it to determine which validator to use
24
+ if @schema['$schema']
25
+ u = URI.parse(@schema['$schema'])
26
+ @validator = JSON::Validator.validators["#{u.scheme}://#{u.host}#{u.path}"]
27
+ if @validator.nil?
28
+ raise SchemaError.new("This library does not have support for schemas defined by #{u.scheme}://#{u.host}#{u.path}")
29
+ end
30
+ elsif parent_validator
31
+ @validator = parent_validator
32
+ else
33
+ @validator = JSON::Validator.default_validator
34
+ end
35
+ end
36
+
37
+ def validate(data, fragments, options = {})
38
+ @validator.validate(self, data, fragments, options)
39
+ end
40
+
41
+ def base_uri
42
+ parts = @uri.to_s.split('/')
43
+ parts.pop
44
+ parts.join('/') + '/'
45
+ end
46
+
47
+
48
+ end
49
+ end
50
+