diff --git a/README.textile b/README.textile index 2755e8c8..8ae964cd 100644 --- a/README.textile +++ b/README.textile @@ -22,7 +22,7 @@ From the git repo:
$ gem build json-schema.gemspec -$ gem install json-schema-2.0.4.gem +$ gem install json-schema-2.1.0.gem@@ -149,6 +149,34 @@ errors = JSON::Validator.fully_validate(schema, data, :errors_as_objects => true +h3. Validate against a fragment of a supplied schema + +
+ require 'rubygems' + require 'json-schema' + + schema = { + "type" => "object", + "required" => ["a","b"], + "properties" => { + "a" => {"type" => "integer"}, + "b" => {"type" => "string"}, + "c" => { + "type" => "object", + "properties" => { + "z" => {"type" => "integer"} + } + } + } + } + + data = { + "z" => 1 + } + + JSON::Validator.validate(schema, data, :fragment => "#/properties/c") ++ h3. Validate a JSON object against a JSON schema object, while also validating the schema itself
diff --git a/VERSION.yml b/VERSION.yml index deefdc7a..ab713c02 100644 --- a/VERSION.yml +++ b/VERSION.yml @@ -1,3 +1,3 @@ major: 2 -minor: 0 -patch: 4 +minor: 1 +patch: 0 diff --git a/lib/json-schema/attributes/additionalproperties.rb b/lib/json-schema/attributes/additionalproperties.rb index c4419ce9..93e767a7 100644 --- a/lib/json-schema/attributes/additionalproperties.rb +++ b/lib/json-schema/attributes/additionalproperties.rb @@ -4,12 +4,16 @@ module JSON class Schema class AdditionalPropertiesAttribute < Attribute def self.validate(current_schema, data, fragments, processor, validator, options = {}) - if data.is_a?(Hash) + if data.is_a?(Hash) && ( + current_schema.schema['type'].nil? || ( + current_schema.schema['type'].is_a?(String) && + current_schema.schema['type'].downcase == 'object' + ) + ) extra_properties = data.keys - extra_properties = remove_valid_properties(extra_properties, current_schema, validator) - addprop= current_schema.schema['additionalProperties'] + addprop = current_schema.schema['additionalProperties'] if addprop.is_a?(Hash) matching_properties= extra_properties # & addprop.keys matching_properties.each do |key| @@ -57,6 +61,7 @@ def self.remove_valid_properties(extra_properties, current_schema, validator) extra_properties end + end end end diff --git a/lib/json-schema/validator.rb b/lib/json-schema/validator.rb index 453ca5fc..c2cd6225 100644 --- a/lib/json-schema/validator.rb +++ b/lib/json-schema/validator.rb @@ -170,8 +170,39 @@ def initialize(schema_data, data, opts={}) @@mutex.synchronize { @base_schema = initialize_schema(schema_data) } @data = initialize_data(data) @@mutex.synchronize { build_schemas(@base_schema) } + + # If the :fragment option is set, try and validate against the fragment + if opts[:fragment] + @base_schema = schema_from_fragment(@base_schema, opts[:fragment]) + end end + def schema_from_fragment(base_schema, fragment) + fragments = fragment.split("/") + + # ensure the first element was a hash, per the fragment spec + if fragments.shift != "#" + raise JSON::Schema::SchemaError.new("Invalid fragment syntax in :fragment option") + end + + fragments.each do |f| + if 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 = base_schema[f] + 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 = base_schema[f.to_i] + else + raise JSON::Schema::SchemaError.new("Invalid schema encountered when resolving :fragment option") + end + end + + base_schema + end # Run a simple true/false validation of data against a schema def validate() diff --git a/test/schemas/test_fragment_resolution.rb b/test/schemas/test_fragment_resolution.rb new file mode 100644 index 00000000..2ef8ebb2 --- /dev/null +++ b/test/schemas/test_fragment_resolution.rb @@ -0,0 +1,30 @@ +require 'test/unit' +require File.dirname(__FILE__) + '/../lib/json-schema' + +class FragmentResolution < Test::Unit::TestCase + def test_fragment_resolution + schema = { + "$schema" => "http://json-schema.org/draft-04/schema#", + "properties" => { + "a" => { + "type" => "object", + "properties" => { + "b" => {"type" => "integer" } + } + } + } + } + + data = {"b" => 5} + assert(!JSON::Validator.validate(schema,data)) + assert(JSON::Validator.validate(schema,data,:fragment => "#/properties/a")) + + assert_raise JSON::Schema::SchemaError do + JSON::Validator.validate!(schema,data,:fragment => "/properties/a") + end + + assert_raise JSON::Schema::SchemaError do + JSON::Validator.validate!(schema,data,:fragment => "#/properties/b") + end + end +end diff --git a/test/test_jsonschema_draft3.rb b/test/test_jsonschema_draft3.rb index 6bbba94a..a8fbdc51 100644 --- a/test/test_jsonschema_draft3.rb +++ b/test/test_jsonschema_draft3.rb @@ -204,9 +204,27 @@ def test_types data["a"] = {"b" => "taco"} assert(!JSON::Validator.validate(schema,data)) - end + # Test an array of unioned-type objects that prevent additionalProperties + schema["properties"]["a"] = { + 'type' => 'array', + 'items' => { + 'type' => [ + { 'type' => 'object', 'properties' => { "b" => { "type" => "integer" } } }, + { 'type' => 'object', 'properties' => { "c" => { "type" => "string" } } } + ], + 'additionalProperties' => false + } + } + + data["a"] = [{"b" => 5}, {"c" => "foo"}] + errors = JSON::Validator.fully_validate(schema, data) + assert(errors.empty?, errors.join("\n")) + # This should actually pass, because this matches the first schema in the union + data["a"] << {"c" => false} + assert(JSON::Validator.validate(schema,data)) + end def test_required # Set up the default datatype