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.
- data/README.textile +216 -0
- data/lib/json-schema.rb +13 -0
- data/lib/json-schema/attributes/additionalitems.rb +23 -0
- data/lib/json-schema/attributes/additionalproperties.rb +39 -0
- data/lib/json-schema/attributes/dependencies.rb +30 -0
- data/lib/json-schema/attributes/disallow.rb +11 -0
- data/lib/json-schema/attributes/divisibleby.rb +16 -0
- data/lib/json-schema/attributes/enum.rb +24 -0
- data/lib/json-schema/attributes/extends.rb +14 -0
- data/lib/json-schema/attributes/format.rb +113 -0
- data/lib/json-schema/attributes/items.rb +25 -0
- data/lib/json-schema/attributes/maxdecimal.rb +15 -0
- data/lib/json-schema/attributes/maximum.rb +15 -0
- data/lib/json-schema/attributes/maximum_inclusive.rb +15 -0
- data/lib/json-schema/attributes/maxitems.rb +12 -0
- data/lib/json-schema/attributes/maxlength.rb +14 -0
- data/lib/json-schema/attributes/minimum.rb +15 -0
- data/lib/json-schema/attributes/minimum_inclusive.rb +15 -0
- data/lib/json-schema/attributes/minitems.rb +12 -0
- data/lib/json-schema/attributes/minlength.rb +14 -0
- data/lib/json-schema/attributes/pattern.rb +15 -0
- data/lib/json-schema/attributes/patternproperties.rb +23 -0
- data/lib/json-schema/attributes/properties.rb +23 -0
- data/lib/json-schema/attributes/properties_optional.rb +23 -0
- data/lib/json-schema/attributes/ref.rb +55 -0
- data/lib/json-schema/attributes/type.rb +71 -0
- data/lib/json-schema/attributes/uniqueitems.rb +16 -0
- data/lib/json-schema/schema.rb +50 -0
- data/lib/json-schema/uri/file.rb +32 -0
- data/lib/json-schema/uri/uuid.rb +285 -0
- data/lib/json-schema/validator.rb +425 -0
- data/lib/json-schema/validators/draft1.rb +32 -0
- data/lib/json-schema/validators/draft2.rb +33 -0
- data/lib/json-schema/validators/draft3.rb +38 -0
- data/resources/draft-01.json +155 -0
- data/resources/draft-02.json +166 -0
- data/resources/draft-03.json +174 -0
- data/test/data/bad_data_1.json +3 -0
- data/test/data/good_data_1.json +3 -0
- data/test/schemas/good_schema_1.json +10 -0
- data/test/schemas/good_schema_2.json +10 -0
- data/test/test_extended_schema.rb +68 -0
- data/test/test_files.rb +35 -0
- data/test/test_full_validation.rb +38 -0
- data/test/test_jsonschema_draft1.rb +703 -0
- data/test/test_jsonschema_draft2.rb +775 -0
- data/test/test_jsonschema_draft3.rb +972 -0
- data/test/test_schema_validation.rb +43 -0
- 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
|
+
|