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.
Files changed (88) hide show
  1. checksums.yaml +6 -14
  2. data/README.textile +58 -7
  3. data/lib/json-schema.rb +3 -1
  4. data/lib/json-schema/attributes/additionalitems.rb +14 -11
  5. data/lib/json-schema/attributes/additionalproperties.rb +33 -43
  6. data/lib/json-schema/attributes/anyof.rb +4 -0
  7. data/lib/json-schema/attributes/dependencies.rb +31 -19
  8. data/lib/json-schema/attributes/disallow.rb +2 -3
  9. data/lib/json-schema/attributes/divisibleby.rb +11 -7
  10. data/lib/json-schema/attributes/enum.rb +14 -16
  11. data/lib/json-schema/attributes/format.rb +4 -7
  12. data/lib/json-schema/attributes/formats/date_time_v4.rb +5 -8
  13. data/lib/json-schema/attributes/formats/ip.rb +41 -0
  14. data/lib/json-schema/attributes/formats/uri.rb +10 -8
  15. data/lib/json-schema/attributes/items.rb +15 -16
  16. data/lib/json-schema/attributes/limit.rb +179 -0
  17. data/lib/json-schema/attributes/maxdecimal.rb +7 -6
  18. data/lib/json-schema/attributes/multipleof.rb +4 -11
  19. data/lib/json-schema/attributes/not.rb +1 -1
  20. data/lib/json-schema/attributes/oneof.rb +15 -6
  21. data/lib/json-schema/attributes/pattern.rb +7 -6
  22. data/lib/json-schema/attributes/patternproperties.rb +9 -12
  23. data/lib/json-schema/attributes/properties.rb +55 -39
  24. data/lib/json-schema/attributes/properties_optional.rb +13 -12
  25. data/lib/json-schema/attributes/ref.rb +4 -4
  26. data/lib/json-schema/attributes/required.rb +16 -13
  27. data/lib/json-schema/attributes/type.rb +13 -18
  28. data/lib/json-schema/attributes/type_v4.rb +11 -18
  29. data/lib/json-schema/attributes/uniqueitems.rb +5 -7
  30. data/lib/json-schema/schema.rb +8 -8
  31. data/lib/json-schema/schema/#validator.rb# +37 -0
  32. data/lib/json-schema/schema/reader.rb +113 -0
  33. data/lib/json-schema/util/uri.rb +16 -0
  34. data/lib/json-schema/validator.rb +123 -128
  35. data/lib/json-schema/validators/draft1.rb +1 -1
  36. data/lib/json-schema/validators/draft2.rb +1 -1
  37. data/lib/json-schema/validators/draft3.rb +1 -1
  38. data/lib/json-schema/validators/draft4.rb +1 -1
  39. data/lib/json-schema/validators/hyper-draft4.rb +1 -1
  40. data/test/schemas/address_microformat.json +18 -0
  41. data/test/schemas/definition_schema.json +15 -0
  42. data/test/schemas/ref john with spaces schema.json +11 -0
  43. data/test/schemas/relative_definition_schema.json +8 -0
  44. data/test/test_all_of_ref_schema.rb +12 -15
  45. data/test/test_any_of_ref_schema.rb +7 -9
  46. data/test/test_bad_schema_ref.rb +18 -12
  47. data/test/test_common_test_suite.rb +45 -29
  48. data/test/test_custom_format.rb +2 -3
  49. data/test/test_definition.rb +15 -0
  50. data/test/test_extended_schema.rb +25 -31
  51. data/test/test_extends_and_additionalProperties.rb +23 -21
  52. data/test/test_files_v3.rb +14 -23
  53. data/test/test_fragment_resolution.rb +6 -7
  54. data/test/test_fragment_validation_with_ref.rb +2 -8
  55. data/test/test_full_validation.rb +2 -3
  56. data/test/test_helper.rb +46 -1
  57. data/test/test_initialize_data.rb +118 -0
  58. data/test/test_jsonschema_draft1.rb +48 -600
  59. data/test/test_jsonschema_draft2.rb +48 -699
  60. data/test/test_jsonschema_draft3.rb +91 -861
  61. data/test/test_jsonschema_draft4.rb +173 -812
  62. data/test/test_list_option.rb +6 -7
  63. data/test/{test_merge_misisng_values.rb → test_merge_missing_values.rb} +2 -3
  64. data/test/test_minitems.rb +2 -4
  65. data/test/test_one_of.rb +9 -19
  66. data/test/test_ruby_schema.rb +5 -14
  67. data/test/test_schema_loader.rb +74 -0
  68. data/test/test_schema_type_attribute.rb +2 -3
  69. data/test/test_schema_validation.rb +4 -5
  70. data/test/test_stringify.rb +2 -3
  71. data/test/test_uri_related.rb +67 -0
  72. data/test/test_validator.rb +53 -0
  73. metadata +129 -51
  74. data/lib/json-schema/attributes/dependencies_v4.rb +0 -27
  75. data/lib/json-schema/attributes/formats/ip4.rb +0 -20
  76. data/lib/json-schema/attributes/formats/ip6.rb +0 -20
  77. data/lib/json-schema/attributes/maximum.rb +0 -17
  78. data/lib/json-schema/attributes/maximum_inclusive.rb +0 -17
  79. data/lib/json-schema/attributes/maxitems.rb +0 -14
  80. data/lib/json-schema/attributes/maxlength.rb +0 -16
  81. data/lib/json-schema/attributes/maxproperties.rb +0 -14
  82. data/lib/json-schema/attributes/minimum.rb +0 -17
  83. data/lib/json-schema/attributes/minimum_inclusive.rb +0 -17
  84. data/lib/json-schema/attributes/minitems.rb +0 -14
  85. data/lib/json-schema/attributes/minlength.rb +0 -16
  86. data/lib/json-schema/attributes/minproperties.rb +0 -14
  87. data/lib/json-schema/attributes/properties_v4.rb +0 -58
  88. 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
- if data.is_a?(Hash)
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
- if data.has_key?(property.to_s)
15
- schema = JSON::Schema.new(property_schema,current_schema.uri,validator)
16
- fragments << property
17
- schema.validate(data[property],fragments,processor,options)
18
- fragments.pop
19
- end
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.merge(path)
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.schemas[schema_key]
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
- if data.is_a?(Hash)
8
- current_schema.schema['required'].each do |property,property_schema|
9
- if !data.has_key?(property.to_s)
10
- prop_defaults = options[:insert_defaults] &&
11
- current_schema.schema['properties'] &&
12
- current_schema.schema['properties'][property] &&
13
- !current_schema.schema['properties'][property]["default"].nil? &&
14
- !current_schema.schema['properties'][property]["readonly"]
15
- if !prop_defaults
16
- message = "The property '#{build_fragment(fragments)}' did not contain a required property of '#{property}'"
17
- validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
18
- end
19
- end
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 (options[:disallow])
53
- if valid
54
- message = "The property '#{build_fragment(fragments)}' matched one or more of the following types:"
55
- types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
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, klass = TYPE_CLASS_MAPPINGS.map { |k,v| [k,v] }.sort_by { |i|
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 { |i|
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.each do |type|
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
- if !valid
21
- if union
22
- message = "The property '#{build_fragment(fragments)}' of type #{data.class} did not match one or more of the following types:"
23
- types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
24
- message.chop!
25
- validation_error(processor, message, fragments, current_schema, self, options[:record_errors])
26
- else
27
- message = "The property '#{build_fragment(fragments)}' of type #{data.class} did not match the following type:"
28
- types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
29
- message.chop!
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
- if data.is_a?(Array)
8
- d = data.clone
9
- dupes = d.uniq!
10
- if dupes
11
- message = "The property '#{build_fragment(fragments)}' contained duplicated array values"
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
@@ -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 = self.class.stringify(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
- uri = uri.merge(@schema['id'])
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
- def base_uri
53
- parts = @uri.to_s.split('/')
54
- parts.pop
55
- parts.join('/') + '/'
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
- if @options[:list] #check if the schema is validating a list
103
- base_schema.schema = schema_to_list(base_schema.schema)
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
- def load_ref_schema(parent_schema,ref)
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
- # Check for absolute path
132
- path = ref.split("#")[0]
135
+ schema = @options[:schema_reader].read(schema_uri)
136
+ self.class.add_schema(schema)
137
+ build_schemas(schema)
138
+ end
133
139
 
134
- # This is a self reference and thus the schema does not need to be re-loaded
135
- if path.nil? || path == ''
136
- return
137
- end
140
+ def absolutize_ref_uri(ref, parent_schema_uri)
141
+ ref_uri = Addressable::URI.parse(ref)
138
142
 
139
- if path && path[0,1] == '/'
140
- uri.path = Pathname.new(path).cleanpath.to_s
141
- else
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
- if Validator.schemas[uri.to_s].nil?
148
- schema = JSON::Schema.new(JSON::Validator.parse(open(uri.to_s).read), uri, @options[:version])
149
- Validator.add_schema(schema)
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 parent_schema.schema["$ref"]
159
- load_ref_schema(parent_schema, parent_schema.schema["$ref"])
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
- # handle validations that always contain schemas
172
- ["allOf", "anyOf", "oneOf", "not"].each do |key|
173
- if parent_schema.schema.has_key?(key)
174
- validations = parent_schema.schema[key]
175
- validations = [validations] unless validations.is_a?(Array)
176
- validations.each {|v| handle_schema(parent_schema, v) }
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 parent_schema.schema[key] && parent_schema.schema[key].is_a?(Array)
183
- parent_schema.schema[key].each_with_index do |type,i|
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
- # "definitions" are schemas in V4
192
- if parent_schema.schema["definitions"]
193
- parent_schema.schema["definitions"].each do |k,v|
194
- handle_schema(parent_schema, v)
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
- # All properties are schemas
199
- if parent_schema.schema["properties"]
200
- parent_schema.schema["properties"].each do |k,v|
201
- handle_schema(parent_schema, v)
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
- if parent_schema.schema["patternProperties"]
205
- parent_schema.schema["patternProperties"].each do |k,v|
206
- handle_schema(parent_schema, v)
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 parent_schema.schema["items"]
212
- items = parent_schema.schema["items"].clone
213
- single = false
214
- if !items.is_a?(Array)
215
- items = [items]
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 parent_schema.schema["enum"] && parent_schema.schema["enum"].is_a?(Array)
225
- parent_schema.schema["enum"] = ArraySet.new(parent_schema.schema["enum"])
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.to_s] = schema if @@schemas[schema.uri.to_s].nil?
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 = schema_to_list(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 Validator.schemas[schema_uri.to_s].nil?
530
- schema = JSON::Validator.parse(open(schema_uri.to_s).read)
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 = schema_to_list(schema)
531
+ schema = schema.to_array_schema
533
532
  end
534
- schema = JSON::Schema.new(schema,schema_uri,@options[:version])
533
+
535
534
  Validator.add_schema(schema)
536
535
  else
537
- schema = Validator.schemas[schema_uri.to_s]
536
+ schema = self.class.schema_for_uri(schema_uri)
538
537
  if @options[:list] && @options[:fragment].nil?
539
- schema = schema_to_list(schema.schema)
540
- schema_uri = URI.parse(fake_uuid(serialize(schema)))
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 = schema_to_list(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[:json]
564
- data = JSON::Validator.parse(data)
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
- rescue
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
- json_uri = normalized_uri(data)
574
- data = JSON::Validator.parse(open(json_uri.to_s).read)
569
+ data = JSON::Validator.parse(data)
575
570
  rescue
576
- # Silently discard the error - the data will not change
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 normalized_uri(data)
584
- uri = URI.parse(data)
585
- if uri.relative?
586
- # Check for absolute path
587
- if data[0,1] == '/'
588
- uri = URI.parse("file://#{data}")
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
OSZAR »