json-schema 2.4.1 → 2.5.0
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.
- checksums.yaml +6 -14
- data/README.textile +58 -7
- data/lib/json-schema.rb +3 -1
- data/lib/json-schema/attributes/additionalitems.rb +14 -11
- data/lib/json-schema/attributes/additionalproperties.rb +33 -43
- data/lib/json-schema/attributes/anyof.rb +4 -0
- data/lib/json-schema/attributes/dependencies.rb +31 -19
- data/lib/json-schema/attributes/disallow.rb +2 -3
- data/lib/json-schema/attributes/divisibleby.rb +11 -7
- data/lib/json-schema/attributes/enum.rb +14 -16
- data/lib/json-schema/attributes/format.rb +4 -7
- data/lib/json-schema/attributes/formats/date_time_v4.rb +5 -8
- data/lib/json-schema/attributes/formats/ip.rb +41 -0
- data/lib/json-schema/attributes/formats/uri.rb +10 -8
- data/lib/json-schema/attributes/items.rb +15 -16
- data/lib/json-schema/attributes/limit.rb +179 -0
- data/lib/json-schema/attributes/maxdecimal.rb +7 -6
- data/lib/json-schema/attributes/multipleof.rb +4 -11
- data/lib/json-schema/attributes/not.rb +1 -1
- data/lib/json-schema/attributes/oneof.rb +15 -6
- data/lib/json-schema/attributes/pattern.rb +7 -6
- data/lib/json-schema/attributes/patternproperties.rb +9 -12
- data/lib/json-schema/attributes/properties.rb +55 -39
- data/lib/json-schema/attributes/properties_optional.rb +13 -12
- data/lib/json-schema/attributes/ref.rb +4 -4
- data/lib/json-schema/attributes/required.rb +16 -13
- data/lib/json-schema/attributes/type.rb +13 -18
- data/lib/json-schema/attributes/type_v4.rb +11 -18
- data/lib/json-schema/attributes/uniqueitems.rb +5 -7
- data/lib/json-schema/schema.rb +8 -8
- data/lib/json-schema/schema/#validator.rb# +37 -0
- data/lib/json-schema/schema/reader.rb +113 -0
- data/lib/json-schema/util/uri.rb +16 -0
- data/lib/json-schema/validator.rb +123 -128
- data/lib/json-schema/validators/draft1.rb +1 -1
- data/lib/json-schema/validators/draft2.rb +1 -1
- data/lib/json-schema/validators/draft3.rb +1 -1
- data/lib/json-schema/validators/draft4.rb +1 -1
- data/lib/json-schema/validators/hyper-draft4.rb +1 -1
- data/test/schemas/address_microformat.json +18 -0
- data/test/schemas/definition_schema.json +15 -0
- data/test/schemas/ref john with spaces schema.json +11 -0
- data/test/schemas/relative_definition_schema.json +8 -0
- data/test/test_all_of_ref_schema.rb +12 -15
- data/test/test_any_of_ref_schema.rb +7 -9
- data/test/test_bad_schema_ref.rb +18 -12
- data/test/test_common_test_suite.rb +45 -29
- data/test/test_custom_format.rb +2 -3
- data/test/test_definition.rb +15 -0
- data/test/test_extended_schema.rb +25 -31
- data/test/test_extends_and_additionalProperties.rb +23 -21
- data/test/test_files_v3.rb +14 -23
- data/test/test_fragment_resolution.rb +6 -7
- data/test/test_fragment_validation_with_ref.rb +2 -8
- data/test/test_full_validation.rb +2 -3
- data/test/test_helper.rb +46 -1
- data/test/test_initialize_data.rb +118 -0
- data/test/test_jsonschema_draft1.rb +48 -600
- data/test/test_jsonschema_draft2.rb +48 -699
- data/test/test_jsonschema_draft3.rb +91 -861
- data/test/test_jsonschema_draft4.rb +173 -812
- data/test/test_list_option.rb +6 -7
- data/test/{test_merge_misisng_values.rb → test_merge_missing_values.rb} +2 -3
- data/test/test_minitems.rb +2 -4
- data/test/test_one_of.rb +9 -19
- data/test/test_ruby_schema.rb +5 -14
- data/test/test_schema_loader.rb +74 -0
- data/test/test_schema_type_attribute.rb +2 -3
- data/test/test_schema_validation.rb +4 -5
- data/test/test_stringify.rb +2 -3
- data/test/test_uri_related.rb +67 -0
- data/test/test_validator.rb +53 -0
- metadata +129 -51
- data/lib/json-schema/attributes/dependencies_v4.rb +0 -27
- data/lib/json-schema/attributes/formats/ip4.rb +0 -20
- data/lib/json-schema/attributes/formats/ip6.rb +0 -20
- data/lib/json-schema/attributes/maximum.rb +0 -17
- data/lib/json-schema/attributes/maximum_inclusive.rb +0 -17
- data/lib/json-schema/attributes/maxitems.rb +0 -14
- data/lib/json-schema/attributes/maxlength.rb +0 -16
- data/lib/json-schema/attributes/maxproperties.rb +0 -14
- data/lib/json-schema/attributes/minimum.rb +0 -17
- data/lib/json-schema/attributes/minimum_inclusive.rb +0 -17
- data/lib/json-schema/attributes/minitems.rb +0 -14
- data/lib/json-schema/attributes/minlength.rb +0 -16
- data/lib/json-schema/attributes/minproperties.rb +0 -14
- data/lib/json-schema/attributes/properties_v4.rb +0 -58
- data/lib/json-schema/uri/file.rb +0 -36
@@ -4,19 +4,20 @@ module JSON
|
|
4
4
|
class Schema
|
5
5
|
class PropertiesOptionalAttribute < Attribute
|
6
6
|
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
7
|
-
|
8
|
-
current_schema.schema['properties'].each do |property,property_schema|
|
9
|
-
if (property_schema['optional'].nil? || property_schema['optional'] == false) && !data.has_key?(property.to_s)
|
10
|
-
message = "The property '#{build_fragment(fragments)}' did not contain a required property of '#{property}'"
|
11
|
-
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
12
|
-
end
|
7
|
+
return unless data.is_a?(Hash)
|
13
8
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
9
|
+
schema = current_schema.schema
|
10
|
+
schema['properties'].each do |property, property_schema|
|
11
|
+
property = property.to_s
|
12
|
+
|
13
|
+
if !property_schema['optional'] && !data.key?(property)
|
14
|
+
message = "The property '#{build_fragment(fragments)}' did not contain a required property of '#{property}'"
|
15
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
16
|
+
end
|
17
|
+
|
18
|
+
if data.has_key?(property)
|
19
|
+
expected_schema = JSON::Schema.new(property_schema, current_schema.uri, validator)
|
20
|
+
expected_schema.validate(data[property], fragments + [property], processor, options)
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
@@ -21,7 +21,7 @@ module JSON
|
|
21
21
|
def self.get_referenced_uri_and_schema(s, current_schema, validator)
|
22
22
|
uri,schema = nil,nil
|
23
23
|
|
24
|
-
temp_uri = URI.parse(s['$ref'])
|
24
|
+
temp_uri = Addressable::URI.parse(s['$ref'])
|
25
25
|
if temp_uri.relative?
|
26
26
|
temp_uri = current_schema.uri.clone
|
27
27
|
# Check for absolute path
|
@@ -31,7 +31,7 @@ module JSON
|
|
31
31
|
elsif path[0,1] == "/"
|
32
32
|
temp_uri.path = Pathname.new(path).cleanpath.to_s
|
33
33
|
else
|
34
|
-
temp_uri = current_schema.uri.
|
34
|
+
temp_uri = current_schema.uri.join(path)
|
35
35
|
end
|
36
36
|
temp_uri.fragment = s['$ref'].split("#")[1]
|
37
37
|
end
|
@@ -40,7 +40,7 @@ module JSON
|
|
40
40
|
# Grab the parent schema from the schema list
|
41
41
|
schema_key = temp_uri.to_s.split("#")[0] + "#"
|
42
42
|
|
43
|
-
ref_schema = JSON::Validator.
|
43
|
+
ref_schema = JSON::Validator.schema_for_uri(schema_key)
|
44
44
|
|
45
45
|
if ref_schema
|
46
46
|
# Perform fragment resolution to retrieve the appropriate level for the schema
|
@@ -49,7 +49,7 @@ module JSON
|
|
49
49
|
fragment_path = ''
|
50
50
|
fragments.each do |fragment|
|
51
51
|
if fragment && fragment != ''
|
52
|
-
fragment = URI.unescape(fragment.gsub('~0', '~').gsub('~1', '/'))
|
52
|
+
fragment = Addressable::URI.unescape(fragment.gsub('~0', '~').gsub('~1', '/'))
|
53
53
|
if target_schema.is_a?(Array)
|
54
54
|
target_schema = target_schema[fragment.to_i]
|
55
55
|
else
|
@@ -4,19 +4,22 @@ module JSON
|
|
4
4
|
class Schema
|
5
5
|
class RequiredAttribute < Attribute
|
6
6
|
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
7
|
+
return unless data.is_a?(Hash)
|
8
|
+
|
9
|
+
schema = current_schema.schema
|
10
|
+
defined_properties = schema['properties']
|
11
|
+
|
12
|
+
schema['required'].each do |property, property_schema|
|
13
|
+
next if data.has_key?(property.to_s)
|
14
|
+
prop_defaults = options[:insert_defaults] &&
|
15
|
+
defined_properties &&
|
16
|
+
defined_properties[property] &&
|
17
|
+
!defined_properties[property]["default"].nil? &&
|
18
|
+
!defined_properties[property]["readonly"]
|
19
|
+
|
20
|
+
if !prop_defaults
|
21
|
+
message = "The property '#{build_fragment(fragments)}' did not contain a required property of '#{property}'"
|
22
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
20
23
|
end
|
21
24
|
end
|
22
25
|
end
|
@@ -32,7 +32,7 @@ module JSON
|
|
32
32
|
pre_validation_error_count = validation_errors(processor).count
|
33
33
|
|
34
34
|
begin
|
35
|
-
schema.validate(data,fragments,processor,options)
|
35
|
+
schema.validate(data,fragments,processor,options.merge(:disallow => false))
|
36
36
|
valid = true
|
37
37
|
rescue ValidationError
|
38
38
|
# We don't care that these schemas don't validate - we only care that one validated
|
@@ -49,36 +49,31 @@ module JSON
|
|
49
49
|
break if valid
|
50
50
|
end
|
51
51
|
|
52
|
-
if
|
53
|
-
if valid
|
54
|
-
|
55
|
-
|
56
|
-
message.chop!
|
57
|
-
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
58
|
-
end
|
52
|
+
if options[:disallow]
|
53
|
+
return if !valid
|
54
|
+
message = "The property '#{build_fragment(fragments)}' matched one or more of the following types: #{list_types(types)}"
|
55
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
59
56
|
elsif !valid
|
60
57
|
if union
|
61
|
-
message = "The property '#{build_fragment(fragments)}' of type #{type_of_data(data)} did not match one or more of the following types:"
|
62
|
-
types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
|
63
|
-
message.chop!
|
58
|
+
message = "The property '#{build_fragment(fragments)}' of type #{type_of_data(data)} did not match one or more of the following types: #{list_types(types)}"
|
64
59
|
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
65
60
|
validation_errors(processor).last.sub_errors = union_errors
|
66
61
|
else
|
67
|
-
message = "The property '#{build_fragment(fragments)}' of type #{type_of_data(data)} did not match the following type:"
|
68
|
-
types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
|
69
|
-
message.chop!
|
62
|
+
message = "The property '#{build_fragment(fragments)}' of type #{type_of_data(data)} did not match the following type: #{list_types(types)}"
|
70
63
|
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
71
64
|
end
|
72
65
|
end
|
73
66
|
end
|
74
67
|
|
68
|
+
def self.list_types(types)
|
69
|
+
types.map { |type| type.is_a?(String) ? type : '(schema)' }.join(', ')
|
70
|
+
end
|
71
|
+
|
75
72
|
# Lookup Schema type of given class instance
|
76
73
|
def self.type_of_data(data)
|
77
|
-
type,
|
78
|
-
k,v = i
|
74
|
+
type, _ = TYPE_CLASS_MAPPINGS.map { |k,v| [k,v] }.sort_by { |(_, v)|
|
79
75
|
-Array(v).map { |klass| klass.ancestors.size }.max
|
80
|
-
}.find { |
|
81
|
-
k,v = i
|
76
|
+
}.find { |(_, v)|
|
82
77
|
Array(v).any? { |klass| data.kind_of?(klass) }
|
83
78
|
}
|
84
79
|
type
|
@@ -10,26 +10,19 @@ module JSON
|
|
10
10
|
types = [types]
|
11
11
|
union = false
|
12
12
|
end
|
13
|
-
valid = false
|
14
13
|
|
15
|
-
types.
|
16
|
-
valid = data_valid_for_type?(data, type)
|
17
|
-
break if valid
|
18
|
-
end
|
14
|
+
return if types.any? { |type| data_valid_for_type?(data, type) }
|
19
15
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
31
|
-
end
|
32
|
-
end
|
16
|
+
types = types.map { |type| type.is_a?(String) ? type : '(schema)' }.join(', ')
|
17
|
+
message = format(
|
18
|
+
"The property '%s' of type %s did not match %s: %s",
|
19
|
+
build_fragment(fragments),
|
20
|
+
data.class,
|
21
|
+
union ? 'one or more of the following types' : 'the following type',
|
22
|
+
types
|
23
|
+
)
|
24
|
+
|
25
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
33
26
|
end
|
34
27
|
end
|
35
28
|
end
|
@@ -4,13 +4,11 @@ module JSON
|
|
4
4
|
class Schema
|
5
5
|
class UniqueItemsAttribute < Attribute
|
6
6
|
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
13
|
-
end
|
7
|
+
return unless data.is_a?(Array)
|
8
|
+
|
9
|
+
if data.clone.uniq!
|
10
|
+
message = "The property '#{build_fragment(fragments)}' contained duplicated array values"
|
11
|
+
validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
|
14
12
|
end
|
15
13
|
end
|
16
14
|
end
|
data/lib/json-schema/schema.rb
CHANGED
@@ -6,15 +6,14 @@ module JSON
|
|
6
6
|
attr_accessor :schema, :uri, :validator
|
7
7
|
|
8
8
|
def initialize(schema,uri,parent_validator=nil)
|
9
|
-
@schema =
|
9
|
+
@schema = schema
|
10
10
|
@uri = uri
|
11
11
|
|
12
12
|
# If there is an ID on this schema, use it to generate the URI
|
13
13
|
if @schema['id'] && @schema['id'].kind_of?(String)
|
14
|
-
temp_uri = URI.parse(@schema['id'])
|
14
|
+
temp_uri = Addressable::URI.parse(@schema['id'])
|
15
15
|
if temp_uri.relative?
|
16
|
-
|
17
|
-
temp_uri = uri
|
16
|
+
temp_uri = uri.join(temp_uri)
|
18
17
|
end
|
19
18
|
@uri = temp_uri
|
20
19
|
end
|
@@ -49,10 +48,11 @@ module JSON
|
|
49
48
|
end
|
50
49
|
end
|
51
50
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
51
|
+
# @return [JSON::Schema] a new schema matching an array whose items all match this schema.
|
52
|
+
def to_array_schema
|
53
|
+
array_schema = { 'type' => 'array', 'items' => schema }
|
54
|
+
array_schema['$schema'] = schema['$schema'] unless schema['$schema'].nil?
|
55
|
+
JSON::Schema.new(array_schema, uri, validator)
|
56
56
|
end
|
57
57
|
|
58
58
|
def to_s
|
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
module JSON
|
3
|
+
class Schema
|
4
|
+
class Validator
|
5
|
+
attr_accessor :attributes, :formats, :uri, :names
|
6
|
+
attr_reader :default_formats
|
7
|
+
|
8
|
+
def initialize()
|
9
|
+
@attributes = {}
|
10
|
+
@formats = {}
|
11
|
+
@default_formats = {}
|
12
|
+
@uri = nil
|
13
|
+
@names = []
|
14
|
+
@metaschema_name = ''
|
15
|
+
end
|
16
|
+
|
17
|
+
def extend_schema_definition(schema_uri)
|
18
|
+
validator = JSON::Validator.validator_for(schema_uri)
|
19
|
+
@attributes.merge!(validator.attributes)
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate(current_schema, data, fragments, processor, options = {})
|
23
|
+
current_schema.schema.each do |attr_name,attribute|
|
24
|
+
if @attributes.has_key?(attr_name.to_s)
|
25
|
+
@attributes[attr_name.to_s].validate(current_schema, data, fragments, processor, self, options)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
data
|
29
|
+
end
|
30
|
+
|
31
|
+
def metaschema
|
32
|
+
resources = File.expand_path('../../../../resources', __FILE__)
|
33
|
+
File.join(resources, @metaschema_name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'addressable/uri'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module JSON
|
6
|
+
class Schema
|
7
|
+
# Raised by {JSON::Schema::Reader} when one of its settings indicate
|
8
|
+
# a schema should not be readed.
|
9
|
+
class ReadRefused < StandardError
|
10
|
+
# @return [String] the requested schema location which was refused
|
11
|
+
attr_reader :location
|
12
|
+
|
13
|
+
# @return [Symbol] either +:uri+ or +:file+
|
14
|
+
attr_reader :type
|
15
|
+
|
16
|
+
def initialize(location, type)
|
17
|
+
@location = location
|
18
|
+
@type = type
|
19
|
+
super("Read of #{type == :uri ? 'URI' : type} at #{location} refused!")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# When an unregistered schema is encountered, the {JSON::Schema::Reader} is
|
24
|
+
# used to fetch its contents and register it with the {JSON::Validator}.
|
25
|
+
#
|
26
|
+
# This default reader will read schemas from the filesystem or from a URI.
|
27
|
+
class Reader
|
28
|
+
# The behavior of the schema reader can be controlled by providing
|
29
|
+
# callbacks to determine whether to permit reading referenced schemas.
|
30
|
+
# The options +accept_uri+ and +accept_file+ should be procs which
|
31
|
+
# accept a +URI+ or +Pathname+ object, and return a boolean value
|
32
|
+
# indicating whether to read the referenced schema.
|
33
|
+
#
|
34
|
+
# URIs using the +file+ scheme will be normalized into +Pathname+ objects
|
35
|
+
# and passed to the +accept_file+ callback.
|
36
|
+
#
|
37
|
+
# @param options [Hash]
|
38
|
+
# @option options [Boolean, #call] accept_uri (true)
|
39
|
+
# @option options [Boolean, #call] accept_file (true)
|
40
|
+
#
|
41
|
+
# @example Reject all unregistered schemas
|
42
|
+
# JSON::Validator.schema_reader = JSON::Schema::Reader.new(
|
43
|
+
# :accept_uri => false,
|
44
|
+
# :accept_file => false
|
45
|
+
# )
|
46
|
+
#
|
47
|
+
# @example Only permit URIs from certain hosts
|
48
|
+
# JSON::Validator.schema_reader = JSON::Schema::Reader.new(
|
49
|
+
# :accept_file => false,
|
50
|
+
# :accept_uri => proc { |uri| ['mycompany.com', 'json-schema.org'].include?(uri.host) }
|
51
|
+
# )
|
52
|
+
def initialize(options = {})
|
53
|
+
@accept_uri = options.fetch(:accept_uri, true)
|
54
|
+
@accept_file = options.fetch(:accept_file, true)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param location [#to_s] The location from which to read the schema
|
58
|
+
# @return [JSON::Schema]
|
59
|
+
# @raise [JSON::Schema::ReadRefused] if +accept_uri+ or +accept_file+
|
60
|
+
# indicated the schema should not be readed
|
61
|
+
# @raise [JSON::ParserError] if the schema was not a valid JSON object
|
62
|
+
def read(location)
|
63
|
+
uri = Addressable::URI.parse(location.to_s)
|
64
|
+
body = if uri.scheme.nil? || uri.scheme == 'file'
|
65
|
+
uri = Addressable::URI.convert_path(uri.path)
|
66
|
+
read_file(Pathname.new(uri.path).expand_path)
|
67
|
+
else
|
68
|
+
read_uri(uri)
|
69
|
+
end
|
70
|
+
|
71
|
+
JSON::Schema.new(JSON::Validator.parse(body), uri)
|
72
|
+
end
|
73
|
+
|
74
|
+
# @param uri [Addressable::URI]
|
75
|
+
# @return [Boolean]
|
76
|
+
def accept_uri?(uri)
|
77
|
+
if @accept_uri.respond_to?(:call)
|
78
|
+
@accept_uri.call(uri)
|
79
|
+
else
|
80
|
+
@accept_uri
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# @param pathname [Pathname]
|
85
|
+
# @return [Boolean]
|
86
|
+
def accept_file?(pathname)
|
87
|
+
if @accept_file.respond_to?(:call)
|
88
|
+
@accept_file.call(pathname)
|
89
|
+
else
|
90
|
+
@accept_file
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def read_uri(uri)
|
97
|
+
if accept_uri?(uri)
|
98
|
+
open(uri.to_s).read
|
99
|
+
else
|
100
|
+
raise JSON::Schema::ReadRefused.new(uri.to_s, :uri)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def read_file(pathname)
|
105
|
+
if accept_file?(pathname)
|
106
|
+
File.read(Addressable::URI.unescape(pathname.to_s))
|
107
|
+
else
|
108
|
+
raise JSON::Schema::ReadRefused.new(pathname.to_s, :file)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module JSON
|
2
|
+
module Util
|
3
|
+
module URI
|
4
|
+
def self.normalized_uri(uri)
|
5
|
+
uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
|
6
|
+
# Check for absolute path
|
7
|
+
if uri.relative?
|
8
|
+
data = uri.to_s
|
9
|
+
data = "#{Dir.pwd}/#{data}" if data[0,1] != '/'
|
10
|
+
uri = Addressable::URI.convert_path(data)
|
11
|
+
end
|
12
|
+
uri
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'uri'
|
1
|
+
require 'addressable/uri'
|
2
2
|
require 'open-uri'
|
3
3
|
require 'pathname'
|
4
4
|
require 'bigdecimal'
|
@@ -7,6 +7,7 @@ require 'date'
|
|
7
7
|
require 'thread'
|
8
8
|
require 'yaml'
|
9
9
|
|
10
|
+
require 'json-schema/schema/reader'
|
10
11
|
require 'json-schema/errors/schema_error'
|
11
12
|
require 'json-schema/errors/json_parse_error'
|
12
13
|
|
@@ -24,7 +25,8 @@ module JSON
|
|
24
25
|
:errors_as_objects => false,
|
25
26
|
:insert_defaults => false,
|
26
27
|
:clear_cache => true,
|
27
|
-
:strict => false
|
28
|
+
:strict => false,
|
29
|
+
:parse_data => true
|
28
30
|
}
|
29
31
|
@@validators = {}
|
30
32
|
@@default_validator = nil
|
@@ -39,6 +41,7 @@ module JSON
|
|
39
41
|
|
40
42
|
validator = JSON::Validator.validator_for_name(@options[:version])
|
41
43
|
@options[:version] = validator
|
44
|
+
@options[:schema_reader] ||= JSON::Validator.schema_reader
|
42
45
|
|
43
46
|
@validation_options = @options[:record_errors] ? {:record_errors => true} : {}
|
44
47
|
@validation_options[:insert_defaults] = true if @options[:insert_defaults]
|
@@ -99,10 +102,12 @@ module JSON
|
|
99
102
|
raise JSON::Schema::SchemaError.new("Invalid schema encountered when resolving :fragment option")
|
100
103
|
end
|
101
104
|
end
|
102
|
-
|
103
|
-
|
105
|
+
|
106
|
+
if @options[:list]
|
107
|
+
base_schema.to_array_schema
|
108
|
+
else
|
109
|
+
base_schema
|
104
110
|
end
|
105
|
-
base_schema
|
106
111
|
end
|
107
112
|
|
108
113
|
# Run a simple true/false validation of data against a schema
|
@@ -122,65 +127,50 @@ module JSON
|
|
122
127
|
end
|
123
128
|
end
|
124
129
|
|
130
|
+
def load_ref_schema(parent_schema, ref)
|
131
|
+
schema_uri = absolutize_ref_uri(ref, parent_schema.uri)
|
125
132
|
|
126
|
-
|
127
|
-
uri = URI.parse(ref)
|
128
|
-
if uri.relative?
|
129
|
-
uri = parent_schema.uri.clone
|
133
|
+
return true if self.class.schema_loaded?(schema_uri)
|
130
134
|
|
131
|
-
|
132
|
-
|
135
|
+
schema = @options[:schema_reader].read(schema_uri)
|
136
|
+
self.class.add_schema(schema)
|
137
|
+
build_schemas(schema)
|
138
|
+
end
|
133
139
|
|
134
|
-
|
135
|
-
|
136
|
-
return
|
137
|
-
end
|
140
|
+
def absolutize_ref_uri(ref, parent_schema_uri)
|
141
|
+
ref_uri = Addressable::URI.parse(ref)
|
138
142
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
uri = parent_schema.uri.merge(path)
|
143
|
-
end
|
144
|
-
uri.fragment = ''
|
145
|
-
end
|
143
|
+
return ref_uri if ref_uri.absolute?
|
144
|
+
# This is a self reference and thus the schema does not need to be re-loaded
|
145
|
+
return parent_schema_uri if ref_uri.path.empty?
|
146
146
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
build_schemas(schema)
|
151
|
-
end
|
147
|
+
uri = parent_schema_uri.clone
|
148
|
+
uri.fragment = ''
|
149
|
+
Util::URI.normalized_uri(uri.join(ref_uri.path))
|
152
150
|
end
|
153
151
|
|
154
|
-
|
155
152
|
# Build all schemas with IDs, mapping out the namespace
|
156
153
|
def build_schemas(parent_schema)
|
154
|
+
schema = parent_schema.schema
|
155
|
+
|
157
156
|
# Build ref schemas if they exist
|
158
|
-
if
|
159
|
-
load_ref_schema(parent_schema,
|
160
|
-
end
|
161
|
-
if parent_schema.schema["extends"]
|
162
|
-
if parent_schema.schema["extends"].is_a?(String)
|
163
|
-
load_ref_schema(parent_schema, parent_schema.schema["extends"])
|
164
|
-
elsif parent_schema.schema["extends"].is_a?(Array)
|
165
|
-
parent_schema.schema["extends"].each do |type|
|
166
|
-
handle_schema(parent_schema, type)
|
167
|
-
end
|
168
|
-
end
|
157
|
+
if schema["$ref"]
|
158
|
+
load_ref_schema(parent_schema, schema["$ref"])
|
169
159
|
end
|
170
160
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
161
|
+
case schema["extends"]
|
162
|
+
when String
|
163
|
+
load_ref_schema(parent_schema, schema["extends"])
|
164
|
+
when Array
|
165
|
+
schema['extends'].each do |type|
|
166
|
+
handle_schema(parent_schema, type)
|
177
167
|
end
|
178
168
|
end
|
179
169
|
|
180
170
|
# Check for schemas in union types
|
181
171
|
["type", "disallow"].each do |key|
|
182
|
-
if
|
183
|
-
|
172
|
+
if schema[key].is_a?(Array)
|
173
|
+
schema[key].each do |type|
|
184
174
|
if type.is_a?(Hash)
|
185
175
|
handle_schema(parent_schema, type)
|
186
176
|
end
|
@@ -188,48 +178,42 @@ module JSON
|
|
188
178
|
end
|
189
179
|
end
|
190
180
|
|
191
|
-
#
|
192
|
-
|
193
|
-
|
194
|
-
|
181
|
+
# Schema properties whose values are objects, the values of which
|
182
|
+
# are themselves schemas.
|
183
|
+
%w[definitions properties patternProperties].each do |key|
|
184
|
+
next unless value = schema[key]
|
185
|
+
value.each do |k, inner_schema|
|
186
|
+
handle_schema(parent_schema, inner_schema)
|
195
187
|
end
|
196
188
|
end
|
197
189
|
|
198
|
-
#
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
end
|
190
|
+
# Schema properties whose values are themselves schemas.
|
191
|
+
%w[additionalProperties additionalItems dependencies extends].each do |key|
|
192
|
+
next unless schema[key].is_a?(Hash)
|
193
|
+
handle_schema(parent_schema, schema[key])
|
203
194
|
end
|
204
|
-
|
205
|
-
|
206
|
-
|
195
|
+
|
196
|
+
# Schema properties whose values may be an array of schemas.
|
197
|
+
%w[allOf anyOf oneOf not].each do |key|
|
198
|
+
next unless value = schema[key]
|
199
|
+
Array(value).each do |inner_schema|
|
200
|
+
handle_schema(parent_schema, inner_schema)
|
207
201
|
end
|
208
202
|
end
|
209
203
|
|
210
204
|
# Items are always schemas
|
211
|
-
if
|
212
|
-
items =
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
single = true
|
217
|
-
end
|
218
|
-
items.each_with_index do |item,i|
|
205
|
+
if schema["items"]
|
206
|
+
items = schema["items"].clone
|
207
|
+
items = [items] unless items.is_a?(Array)
|
208
|
+
|
209
|
+
items.each do |item|
|
219
210
|
handle_schema(parent_schema, item)
|
220
211
|
end
|
221
212
|
end
|
222
213
|
|
223
214
|
# Convert enum to a ArraySet
|
224
|
-
if
|
225
|
-
|
226
|
-
end
|
227
|
-
|
228
|
-
# Each of these might be schemas
|
229
|
-
["additionalProperties", "additionalItems", "dependencies", "extends"].each do |key|
|
230
|
-
if parent_schema.schema[key].is_a?(Hash)
|
231
|
-
handle_schema(parent_schema, parent_schema.schema[key])
|
232
|
-
end
|
215
|
+
if schema["enum"].is_a?(Array)
|
216
|
+
schema["enum"] = ArraySet.new(schema["enum"])
|
233
217
|
end
|
234
218
|
|
235
219
|
end
|
@@ -238,7 +222,7 @@ module JSON
|
|
238
222
|
def handle_schema(parent_schema, obj)
|
239
223
|
if obj.is_a?(Hash)
|
240
224
|
schema_uri = parent_schema.uri.clone
|
241
|
-
schema = JSON::Schema.new(obj,schema_uri,parent_schema.validator)
|
225
|
+
schema = JSON::Schema.new(obj, schema_uri, parent_schema.validator)
|
242
226
|
if obj['id']
|
243
227
|
Validator.add_schema(schema)
|
244
228
|
end
|
@@ -309,6 +293,14 @@ module JSON
|
|
309
293
|
fully_validate(schema, data, opts.merge(:uri => true))
|
310
294
|
end
|
311
295
|
|
296
|
+
def schema_reader
|
297
|
+
@@schema_reader ||= JSON::Schema::Reader.new
|
298
|
+
end
|
299
|
+
|
300
|
+
def schema_reader=(reader)
|
301
|
+
@@schema_reader = reader
|
302
|
+
end
|
303
|
+
|
312
304
|
def clear_cache
|
313
305
|
@@schemas = {} if @@cache_schemas == false
|
314
306
|
end
|
@@ -318,7 +310,22 @@ module JSON
|
|
318
310
|
end
|
319
311
|
|
320
312
|
def add_schema(schema)
|
321
|
-
@@schemas[schema.uri
|
313
|
+
@@schemas[schema_key_for(schema.uri)] ||= schema
|
314
|
+
end
|
315
|
+
|
316
|
+
def schema_for_uri(uri)
|
317
|
+
# We only store normalized uris terminated with fragment #, so we can try whether
|
318
|
+
# normalization can be skipped
|
319
|
+
@@schemas[uri] || @@schemas[schema_key_for(uri)]
|
320
|
+
end
|
321
|
+
|
322
|
+
def schema_loaded?(schema_uri)
|
323
|
+
!schema_for_uri(schema_uri).nil?
|
324
|
+
end
|
325
|
+
|
326
|
+
def schema_key_for(uri)
|
327
|
+
key = Util::URI.normalized_uri(uri).to_s
|
328
|
+
key.end_with?('#') ? key : "#{key}#"
|
322
329
|
end
|
323
330
|
|
324
331
|
def cache_schemas=(val)
|
@@ -336,7 +343,7 @@ module JSON
|
|
336
343
|
|
337
344
|
def validator_for_uri(schema_uri)
|
338
345
|
return default_validator unless schema_uri
|
339
|
-
u = URI.parse(schema_uri)
|
346
|
+
u = Addressable::URI.parse(schema_uri)
|
340
347
|
validator = validators["#{u.scheme}://#{u.host}#{u.path}"]
|
341
348
|
if validator.nil?
|
342
349
|
raise JSON::Schema::SchemaError.new("Schema not found: #{schema_uri}")
|
@@ -412,7 +419,7 @@ module JSON
|
|
412
419
|
else
|
413
420
|
case @@json_backend.to_s
|
414
421
|
when 'json'
|
415
|
-
JSON.parse(s)
|
422
|
+
JSON.parse(s, :quirks_mode => true)
|
416
423
|
when 'yajl'
|
417
424
|
json = StringIO.new(s)
|
418
425
|
parser = Yajl::Parser.new
|
@@ -503,53 +510,45 @@ module JSON
|
|
503
510
|
@@fake_uuid_generator.call(schema)
|
504
511
|
end
|
505
512
|
|
506
|
-
def schema_to_list(schema)
|
507
|
-
new_schema = {"type" => "array", "items" => schema}
|
508
|
-
if !schema["$schema"].nil?
|
509
|
-
new_schema["$schema"] = schema["$schema"]
|
510
|
-
end
|
511
|
-
|
512
|
-
new_schema
|
513
|
-
end
|
514
|
-
|
515
513
|
def initialize_schema(schema)
|
516
514
|
if schema.is_a?(String)
|
517
515
|
begin
|
518
516
|
# Build a fake URI for this
|
519
|
-
schema_uri = URI.parse(fake_uuid(schema))
|
520
|
-
schema = JSON::Validator.parse(schema)
|
517
|
+
schema_uri = Addressable::URI.parse(fake_uuid(schema))
|
518
|
+
schema = JSON::Schema.new(JSON::Validator.parse(schema), schema_uri, @options[:version])
|
521
519
|
if @options[:list] && @options[:fragment].nil?
|
522
|
-
schema =
|
520
|
+
schema = schema.to_array_schema
|
523
521
|
end
|
524
|
-
schema = JSON::Schema.new(schema,schema_uri,@options[:version])
|
525
522
|
Validator.add_schema(schema)
|
526
523
|
rescue
|
527
524
|
# Build a uri for it
|
528
|
-
schema_uri = normalized_uri(schema)
|
529
|
-
if
|
530
|
-
schema =
|
525
|
+
schema_uri = Util::URI.normalized_uri(schema)
|
526
|
+
if !self.class.schema_loaded?(schema_uri)
|
527
|
+
schema = @options[:schema_reader].read(schema_uri)
|
528
|
+
schema = JSON::Schema.stringify(schema)
|
529
|
+
|
531
530
|
if @options[:list] && @options[:fragment].nil?
|
532
|
-
schema =
|
531
|
+
schema = schema.to_array_schema
|
533
532
|
end
|
534
|
-
|
533
|
+
|
535
534
|
Validator.add_schema(schema)
|
536
535
|
else
|
537
|
-
schema =
|
536
|
+
schema = self.class.schema_for_uri(schema_uri)
|
538
537
|
if @options[:list] && @options[:fragment].nil?
|
539
|
-
schema =
|
540
|
-
|
541
|
-
schema = JSON::Schema.new(schema, schema_uri, @options[:version])
|
538
|
+
schema = schema.to_array_schema
|
539
|
+
schema.uri = Addressable::URI.parse(fake_uuid(serialize(schema.schema)))
|
542
540
|
Validator.add_schema(schema)
|
543
541
|
end
|
544
542
|
schema
|
545
543
|
end
|
546
544
|
end
|
547
545
|
elsif schema.is_a?(Hash)
|
546
|
+
schema_uri = Addressable::URI.parse(fake_uuid(serialize(schema)))
|
547
|
+
schema = JSON::Schema.stringify(schema)
|
548
|
+
schema = JSON::Schema.new(schema, schema_uri, @options[:version])
|
548
549
|
if @options[:list] && @options[:fragment].nil?
|
549
|
-
schema =
|
550
|
+
schema = schema.to_array_schema
|
550
551
|
end
|
551
|
-
schema_uri = URI.parse(fake_uuid(serialize(schema)))
|
552
|
-
schema = JSON::Schema.new(schema,schema_uri,@options[:version])
|
553
552
|
Validator.add_schema(schema)
|
554
553
|
else
|
555
554
|
raise "Invalid schema - must be either a string or a hash"
|
@@ -558,40 +557,36 @@ module JSON
|
|
558
557
|
schema
|
559
558
|
end
|
560
559
|
|
561
|
-
|
562
560
|
def initialize_data(data)
|
563
|
-
if @options[:
|
564
|
-
|
565
|
-
elsif @options[:uri]
|
566
|
-
json_uri = normalized_uri(data)
|
567
|
-
data = JSON::Validator.parse(open(json_uri.to_s).read)
|
568
|
-
elsif data.is_a?(String)
|
569
|
-
begin
|
561
|
+
if @options[:parse_data]
|
562
|
+
if @options[:json]
|
570
563
|
data = JSON::Validator.parse(data)
|
571
|
-
|
564
|
+
elsif @options[:uri]
|
565
|
+
json_uri = Util::URI.normalized_uri(data)
|
566
|
+
data = JSON::Validator.parse(custom_open(json_uri))
|
567
|
+
elsif data.is_a?(String)
|
572
568
|
begin
|
573
|
-
|
574
|
-
data = JSON::Validator.parse(open(json_uri.to_s).read)
|
569
|
+
data = JSON::Validator.parse(data)
|
575
570
|
rescue
|
576
|
-
|
571
|
+
begin
|
572
|
+
json_uri = Util::URI.normalized_uri(data)
|
573
|
+
data = JSON::Validator.parse(custom_open(json_uri))
|
574
|
+
rescue
|
575
|
+
# Silently discard the error - the data will not change
|
576
|
+
end
|
577
577
|
end
|
578
578
|
end
|
579
579
|
end
|
580
580
|
JSON::Schema.stringify(data)
|
581
581
|
end
|
582
582
|
|
583
|
-
def
|
584
|
-
uri = URI.
|
585
|
-
if uri.
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
else
|
590
|
-
uri = URI.parse("file://#{Dir.pwd}/#{data}")
|
591
|
-
end
|
583
|
+
def custom_open(uri)
|
584
|
+
uri = Util::URI.normalized_uri(uri) if uri.is_a?(String)
|
585
|
+
if uri.absolute? && uri.scheme != 'file'
|
586
|
+
open(uri.to_s).read
|
587
|
+
else
|
588
|
+
File.read(Addressable::URI.unescape(uri.path))
|
592
589
|
end
|
593
|
-
uri
|
594
590
|
end
|
595
|
-
|
596
591
|
end
|
597
592
|
end
|