seomoz-json-schema 1.0.1

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