From b1774412f3e1a06a77614ac79b19b5454703b6c1 Mon Sep 17 00:00:00 2001 From: Doug Patti Date: Fri, 15 Apr 2016 14:44:56 -0400 Subject: [PATCH 1/2] fix fragment when used with extended schema This refactors some of `Validator` to fix an issue with validating a fragment of an extended schema against a json object. The problem is that while navigating through the schema, we are using the configured `:version` option as the validator for each partial schema. This is an issue because if the root of the schema declares a `$schema`, that information is lost in the traversal. The refactor does two things. Firstly, it does not write back into `@options[:version]` and it stops using it outside of the constructor entirely. The only place that should know about this is the initialized schema, and from that point on, the canonical validator should be the schema object's validator since it makes a decision about which to use in its constructor. Now we can reduce complexity by using that validator in the `:validate_schema` option branch. The other place that used `:version` is the `schema_from_fragment` method, which is where the bug exists. What we're doing here, instead of dealing with schemas *and* hashes during the iterator, is pulling the schema object out, digging through it using the fragments, and then wrapping the result in a schema using the `base_schema`'s validator. Tests have been included that demonstrate the problem. --- lib/json-schema/validator.rb | 55 +++++++++++++++--------------------- test/extended_schema_test.rb | 18 ++++++++++++ 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/lib/json-schema/validator.rb b/lib/json-schema/validator.rb index c439b613..46a5a48c 100644 --- a/lib/json-schema/validator.rb +++ b/lib/json-schema/validator.rb @@ -41,8 +41,7 @@ def initialize(schema_data, data, opts={}) @options = @@default_opts.clone.merge(opts) @errors = [] - validator = self.class.validator_for_name(@options[:version]) - @options[:version] = validator + configured_validator = self.class.validator_for_name(@options[:version]) @options[:schema_reader] ||= self.class.schema_reader @validation_options = @options[:record_errors] ? {:record_errors => true} : {} @@ -50,19 +49,16 @@ def initialize(schema_data, data, opts={}) @validation_options[:strict] = true if @options[:strict] == true @validation_options[:clear_cache] = true if !@@cache_schemas || @options[:clear_cache] - @@mutex.synchronize { @base_schema = initialize_schema(schema_data) } + @@mutex.synchronize { @base_schema = initialize_schema(schema_data, configured_validator) } @original_data = data @data = initialize_data(data) @@mutex.synchronize { build_schemas(@base_schema) } # validate the schema, if requested if @options[:validate_schema] - if @base_schema.schema["$schema"] - base_validator = self.class.validator_for_name(@base_schema.schema["$schema"]) - end - metaschema = base_validator ? base_validator.metaschema : validator.metaschema # Don't clear the cache during metaschema validation! - self.class.validate!(metaschema, @base_schema.schema, {:clear_cache => false}) + meta_validator = self.class.new(@base_schema.validator.metaschema, @base_schema.schema, {:clear_cache => false}) + meta_validator.validate end # If the :fragment option is set, try and validate against the fragment @@ -80,33 +76,28 @@ def schema_from_fragment(base_schema, fragment) raise JSON::Schema::SchemaError.new("Invalid fragment syntax in :fragment option") end + schema_fragment = base_schema.schema fragments.each do |f| - if base_schema.is_a?(JSON::Schema) #test if fragment is a JSON:Schema instance - if !base_schema.schema.has_key?(f) - raise JSON::Schema::SchemaError.new("Invalid fragment resolution for :fragment option") - end - base_schema = base_schema.schema[f] - elsif base_schema.is_a?(Hash) - if !base_schema.has_key?(f) - raise JSON::Schema::SchemaError.new("Invalid fragment resolution for :fragment option") - end - base_schema = JSON::Schema.new(base_schema[f],schema_uri,@options[:version]) - elsif base_schema.is_a?(Array) - if base_schema[f.to_i].nil? - raise JSON::Schema::SchemaError.new("Invalid fragment resolution for :fragment option") - end - base_schema = JSON::Schema.new(base_schema[f.to_i],schema_uri,@options[:version]) - else - raise JSON::Schema::SchemaError.new("Invalid schema encountered when resolving :fragment option") + case schema_fragment + when Hash + schema_fragment = schema_fragment[f] + when Array + schema_fragment = schema_fragment[f.to_i] end end + unless schema_fragment.is_a?(Hash) + raise JSON::Schema::SchemaError.new("Invalid fragment resolution for :fragment option") + end + + schema = JSON::Schema.new(schema_fragment, schema_uri, base_schema.validator) + if @options[:list] - base_schema.to_array_schema - elsif base_schema.is_a?(Hash) - JSON::Schema.new(base_schema, schema_uri, @options[:version]) + schema.to_array_schema + elsif schema.is_a?(Hash) + JSON::Schema.new(schema, schema_uri, @options[:version]) else - base_schema + schema end end @@ -519,12 +510,12 @@ def fake_uuid schema @@fake_uuid_generator.call(schema) end - def initialize_schema(schema) + def initialize_schema(schema, default_validator) if schema.is_a?(String) begin # Build a fake URI for this schema_uri = JSON::Util::URI.parse(fake_uuid(schema)) - schema = JSON::Schema.new(self.class.parse(schema), schema_uri, @options[:version]) + schema = JSON::Schema.new(JSON::Validator.parse(schema), schema_uri, default_validator) if @options[:list] && @options[:fragment].nil? schema = schema.to_array_schema end @@ -554,7 +545,7 @@ def initialize_schema(schema) elsif schema.is_a?(Hash) schema_uri = JSON::Util::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, default_validator) if @options[:list] && @options[:fragment].nil? schema = schema.to_array_schema end diff --git a/test/extended_schema_test.rb b/test/extended_schema_test.rb index beb73ced..ce22f609 100644 --- a/test/extended_schema_test.rb +++ b/test/extended_schema_test.rb @@ -42,6 +42,24 @@ def test_extended_schema_validation refute_valid schema, {"a" => 1, "b" => 5} end + def test_extended_schema_validation_with_fragment + schema = { + "$schema" => "http://test.com/test.json", + "definitions" => { + "odd-a" => { + "properties" => { + "a" => { + "bitwise-and" => 1 + } + } + } + } + } + + assert_valid schema, {"a" => 1}, :fragment => "#/definitions/odd-a" + refute_valid schema, {"a" => 0}, :fragment => "#/definitions/odd-a" + end + def test_unextended_schema # Verify that using the original schema disregards the `bitwise-and` property schema = { From a03ae58664a2d2b5cd4dba878eba800e6f578ac8 Mon Sep 17 00:00:00 2001 From: Zach Dennis Date: Mon, 1 Apr 2019 10:45:03 -0400 Subject: [PATCH 2/2] Updating common test-suite --- test/test-suite | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-suite b/test/test-suite index da8b14ee..15ba997f 160000 --- a/test/test-suite +++ b/test/test-suite @@ -1 +1 @@ -Subproject commit da8b14eef365886cac3caba5d6d995db3e02544e +Subproject commit 15ba997f9b937150a0ab88244d1d0fbf58526c48