diff --git a/json-schema.gemspec b/json-schema.gemspec index 97f22389..c63e1d84 100644 --- a/json-schema.gemspec +++ b/json-schema.gemspec @@ -19,7 +19,6 @@ Gem::Specification.new do |s| s.license = "MIT" s.required_rubygems_version = ">= 1.8" - s.add_runtime_dependency "addressable" s.add_development_dependency "webmock" s.add_runtime_dependency "addressable", '~> 2.3' end diff --git a/lib/json-schema/attributes/ref.rb b/lib/json-schema/attributes/ref.rb index c72276e6..c64dc0bc 100644 --- a/lib/json-schema/attributes/ref.rb +++ b/lib/json-schema/attributes/ref.rb @@ -5,7 +5,7 @@ module JSON class Schema class RefAttribute < Attribute def self.validate(current_schema, data, fragments, processor, validator, options = {}) - uri,schema = get_referenced_uri_and_schema(current_schema.schema, current_schema, validator) + uri, schema = get_referenced_uri_and_schema(current_schema.schema, current_schema, validator) if schema schema.validate(data, fragments, processor, options) @@ -18,14 +18,14 @@ def self.validate(current_schema, data, fragments, processor, validator, options end end - def self.get_referenced_uri_and_schema(s, current_schema, validator) - uri,schema = nil,nil + def self.get_referenced_uri_and_schema(contents, current_schema, validator) + uri, schema = nil,nil - temp_uri = Addressable::URI.parse(s['$ref']) + temp_uri = Addressable::URI.parse(contents['$ref']) if temp_uri.relative? temp_uri = current_schema.uri.clone # Check for absolute path - path = s['$ref'].split("#")[0] + path = contents['$ref'].split("#")[0] if path.nil? || path == '' temp_uri.path = current_schema.uri.path elsif path[0,1] == "/" @@ -33,7 +33,7 @@ def self.get_referenced_uri_and_schema(s, current_schema, validator) else temp_uri = current_schema.uri.join(path) end - temp_uri.fragment = s['$ref'].split("#")[1] + temp_uri.fragment = contents['$ref'].split("#")[1] end temp_uri.fragment = "" if temp_uri.fragment.nil? diff --git a/lib/json-schema/schema.rb b/lib/json-schema/schema.rb index be3a43b4..2337b933 100644 --- a/lib/json-schema/schema.rb +++ b/lib/json-schema/schema.rb @@ -55,6 +55,13 @@ def base_uri parts.join('/') + '/' end + # @return [JSON::Schema] a new schema matching an array whose items all match this schema. + def to_array_schema + array_schema = { 'type' => 'array', 'items' => schema } + array_schema['$schema'] = schema['$schema'] unless schema['$schema'].nil? + JSON::Schema.new(array_schema, uri, validator) + end + def to_s @schema.to_json end diff --git a/lib/json-schema/schema/loader.rb b/lib/json-schema/schema/loader.rb index 4c6c4275..485961ac 100644 --- a/lib/json-schema/schema/loader.rb +++ b/lib/json-schema/schema/loader.rb @@ -103,7 +103,7 @@ def read_uri(uri) def read_file(pathname) if accept_file?(pathname) - pathname.read + File.read(Addressable::URI.unescape(pathname.to_s)) else raise JSON::Schema::LoadRefused.new(pathname.to_s, :file) end diff --git a/lib/json-schema/validator.rb b/lib/json-schema/validator.rb index ff17c27e..b5c386b3 100644 --- a/lib/json-schema/validator.rb +++ b/lib/json-schema/validator.rb @@ -7,6 +7,7 @@ require 'thread' require 'yaml' +require 'json-schema/schema/loader' require 'json-schema/errors/schema_error' require 'json-schema/errors/json_parse_error' @@ -39,6 +40,7 @@ def initialize(schema_data, data, opts={}) validator = JSON::Validator.validator_for_name(@options[:version]) @options[:version] = validator + @options[:schema_loader] ||= JSON::Validator.schema_loader @validation_options = @options[:record_errors] ? {:record_errors => true} : {} @validation_options[:insert_defaults] = true if @options[:insert_defaults] @@ -99,10 +101,12 @@ def schema_from_fragment(base_schema, fragment) raise JSON::Schema::SchemaError.new("Invalid schema encountered when resolving :fragment option") end end - if @options[:list] #check if the schema is validating a list - base_schema.schema = schema_to_list(base_schema.schema) + + if @options[:list] + base_schema.to_array_schema + else + base_schema end - base_schema end # Run a simple true/false validation of data against a schema @@ -127,8 +131,7 @@ def load_ref_schema(parent_schema, ref) return true if self.class.schema_loaded?(schema_uri) - schema_data = self.class.parse(custom_open(schema_uri)) - schema = JSON::Schema.new(schema_data, schema_uri, @options[:version]) + schema = @options[:schema_loader].load(schema_uri) self.class.add_schema(schema) build_schemas(schema) end @@ -218,7 +221,7 @@ def build_schemas(parent_schema) def handle_schema(parent_schema, obj) if obj.is_a?(Hash) schema_uri = parent_schema.uri.clone - schema = JSON::Schema.new(obj,schema_uri,parent_schema.validator) + schema = JSON::Schema.new(obj, schema_uri, parent_schema.validator) if obj['id'] Validator.add_schema(schema) end @@ -289,6 +292,14 @@ def fully_validate_uri(schema, data, opts={}) fully_validate(schema, data, opts.merge(:uri => true)) end + def schema_loader + @@schema_loader ||= JSON::Schema::Loader.new + end + + def schema_loader=(loader) + @@schema_loader = loader + end + def clear_cache @@schemas = {} if @@cache_schemas == false end @@ -491,55 +502,45 @@ def fake_uuid schema @@fake_uuid_generator.call(schema) end - def schema_to_list(schema) - new_schema = {"type" => "array", "items" => schema} - if !schema["$schema"].nil? - new_schema["$schema"] = schema["$schema"] - end - - new_schema - end - def initialize_schema(schema) if schema.is_a?(String) begin # Build a fake URI for this schema_uri = Addressable::URI.parse(fake_uuid(schema)) - schema = JSON::Validator.parse(schema) + schema = JSON::Schema.new(JSON::Validator.parse(schema), schema_uri, @options[:version]) if @options[:list] && @options[:fragment].nil? - schema = schema_to_list(schema) + schema = schema.to_array_schema end - schema = JSON::Schema.new(schema,schema_uri,@options[:version]) Validator.add_schema(schema) rescue # Build a uri for it schema_uri = normalized_uri(schema) if !self.class.schema_loaded?(schema_uri) - schema = JSON::Validator.parse(custom_open(schema_uri)) + schema = @options[:schema_loader].load(schema_uri) schema = JSON::Schema.stringify(schema) + if @options[:list] && @options[:fragment].nil? - schema = schema_to_list(schema) + schema = schema.to_array_schema end - schema = JSON::Schema.new(schema,schema_uri,@options[:version]) + Validator.add_schema(schema) else schema = self.class.schema_for_uri(schema_uri) if @options[:list] && @options[:fragment].nil? - schema = schema_to_list(schema.schema) - schema_uri = Addressable::URI.parse(fake_uuid(serialize(schema))) - schema = JSON::Schema.new(schema, schema_uri, @options[:version]) + schema = schema.to_array_schema + schema.uri = Addressable::URI.parse(fake_uuid(serialize(schema.schema))) Validator.add_schema(schema) end schema end end elsif schema.is_a?(Hash) - if @options[:list] && @options[:fragment].nil? - schema = schema_to_list(schema) - end schema_uri = Addressable::URI.parse(fake_uuid(serialize(schema))) schema = JSON::Schema.stringify(schema) - schema = JSON::Schema.new(schema,schema_uri,@options[:version]) + schema = JSON::Schema.new(schema, schema_uri, @options[:version]) + if @options[:list] && @options[:fragment].nil? + schema = schema.to_array_schema + end Validator.add_schema(schema) else raise "Invalid schema - must be either a string or a hash" @@ -548,7 +549,6 @@ def initialize_schema(schema) schema end - def initialize_data(data) if @options[:json] data = JSON::Validator.parse(data) diff --git a/test/test_validator.rb b/test/test_validator.rb new file mode 100644 index 00000000..a460e716 --- /dev/null +++ b/test/test_validator.rb @@ -0,0 +1,50 @@ +require File.expand_path('../test_helper', __FILE__) + +class TestValidator < Minitest::Test + + class MockLoader + def load(location) + schema = { + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'type' => 'string', + 'minLength' => 2 + } + + JSON::Schema.new(schema, Addressable::URI.parse(location.to_s)) + end + end + + def teardown + # Hacky. Not sure of a better tactic for resetting just yet. + JSON::Validator.send(:class_variable_set, :@@schema_loader, nil) + end + + def test_default_schema_loader + loader = JSON::Validator.schema_loader + assert loader.accept_uri?(Addressable::URI.parse('http://example.com')) + assert loader.accept_file?(Pathname.new('/etc/passwd')) + end + + def test_set_default_schema_loader + JSON::Validator.schema_loader = MockLoader.new + + schema = { '$ref' => 'http://any.url/at/all' } + assert_valid schema, 'abc' + refute_valid schema, 'a' + end + + def test_validate_with_loader + loader = MockLoader.new + schema = { '$ref' => 'http://any.url/at/all' } + assert_valid schema, 'abc', :schema_loader => loader + refute_valid schema, 'a', :schema_loader => loader + end + + def test_validate_list_with_loader + loader = MockLoader.new + schema = { '$ref' => 'http://what.ever/schema' } + assert_valid schema, ['abc', 'def'], :schema_loader => loader, :list => true + refute_valid schema, ['abc', 'a'], :schema_loader => loader, :list => true + end + +end