diff --git a/.gitignore b/.gitignore index 554401e9..e3206f09 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ pkg /Gemfile.lock .bundle .idea +/coverage diff --git a/.travis.yml b/.travis.yml index bc8eb19b..e12623d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,15 @@ language: "ruby" rvm: + - "1.9" - "2.0" - "2.1" - "2.2" - - "2.3.3" - - "2.4.0" - - "jruby-9.1.7.0" + - "2.3" + - "2.4" + - "2.5" + - "jruby-1.7" + - "jruby-9.1" sudo: false @@ -19,9 +22,9 @@ matrix: gemfile: "gemfiles/Gemfile.ruby_19.x" - rvm: "jruby-1.7" gemfile: "gemfiles/Gemfile.ruby_19.x" - - rvm: "2.3.3" + - rvm: "2.5" gemfile: "gemfiles/Gemfile.multi_json.x" - - rvm: "2.3.3" + - rvm: "2.5" gemfile: "gemfiles/Gemfile.yajl-ruby.x" - - rvm: "2.3.3" + - rvm: "2.5" gemfile: "gemfiles/Gemfile.uuidtools.x" diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ff324fb..e5097bf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## [2.8.1] - 2019-10-14 + +### Changed +- All limit classes are now stored in their own files in 'json-schema/attributes/limits' +- All attribute classes are now stored in their own files in 'json-schema/attributes' + +### Fixed +- Corrected the draft6 schema id to `http://json-schema.org/draft/schema#` +- Rescue URI error when initializing a data string that contains a colon +- Fragments with an odd number of components no longer raise an `undefined method `validate'` + error + ## [2.8.0] - 2017-02-07 ### Added diff --git a/Gemfile b/Gemfile index 07af36e7..5bf28e53 100644 --- a/Gemfile +++ b/Gemfile @@ -3,3 +3,4 @@ source "https://rubygems.org" gemspec gem "json", ">= 1.7", :platforms => :mri_19 +gem 'simplecov', :require => false diff --git a/README.md b/README.md index 61e47b9b..bd29da49 100644 --- a/README.md +++ b/README.md @@ -375,6 +375,33 @@ schema = { errors = JSON::Validator.fully_validate(schema, {"a" => "23"}) ``` +Validating a JSON Schema +------------------------ + +To validate that a JSON Schema conforms to the JSON Schema standard, +you need to validate your schema against the metaschema for the appropriate +JSON Schema Draft. All of the normal validation methods can be used +for this. First retrieve the appropriate metaschema from the internal +cache (using `JSON::Validator.validator_for_name()` or +`JSON::Validator.validator_for_uri()`) and then simply validate your +schema against it. + + +```ruby +require "json-schema" + +schema = { + "type" => "object", + "properties" => { + "a" => {"type" => "integer"} + } +} + +metaschema = JSON::Validator.validator_for_name("draft4").metaschema +# => true +JSON::Validator.validate(metaschema, schema) +``` + Controlling Remote Schema Reading --------------------------------- diff --git a/Rakefile b/Rakefile index 8eefedd0..b4a13a26 100644 --- a/Rakefile +++ b/Rakefile @@ -9,17 +9,60 @@ task :update_common_tests do unless File.read(".git/config").include?('submodule "test/test-suite"') sh "git submodule init" end - sh "git submodule update --remote --quiet" + + puts "Updating json-schema common test suite..." + + begin + sh "git submodule update --remote --quiet" + rescue StandardError + STDERR.puts "Failed to update common test suite." + end +end + +desc "Update meta-schemas to the latest version" +task :update_meta_schemas do + puts "Updating meta-schemas..." + + id_mappings = { + 'http://json-schema.org/draft/schema#' => 'https://raw.githubusercontent.com/json-schema-org/json-schema-spec/master/schema.json' + } + + require 'open-uri' + require 'thwait' + + download_threads = Dir['resources/*.json'].map do |path| + schema_id = File.read(path)[/"\$?id"\s*:\s*"(.*?)"/, 1] + schema_uri = id_mappings[schema_id] || schema_id + + Thread.new(schema_uri) do |uri| + Thread.current[:uri] = uri + + begin + metaschema = URI(uri).read + + File.write(path, metaschema) + rescue StandardError + false + end + end + end + + ThreadsWait.all_waits(*download_threads) do |t| + if t.value + puts t[:uri] + else + STDERR.puts "Failed to update meta-schema #{t[:uri]}" + end + end end Rake::TestTask.new do |t| t.libs << "." - # disabled warnings because addressable 2.4 has lots of them - t.warning = false + t.warning = true t.verbose = true t.test_files = FileList.new('test/*_test.rb') end -task :test => :update_common_tests +task update: [:update_common_tests, :update_meta_schemas] task :default => :test diff --git a/VERSION.yml b/VERSION.yml index f9e96e5e..dcd73b51 100644 --- a/VERSION.yml +++ b/VERSION.yml @@ -1,3 +1,3 @@ major: 2 minor: 8 -patch: 0 +patch: 1 diff --git a/gemfiles/Gemfile.ruby_19.x b/gemfiles/Gemfile.ruby_19.x index 10ad7316..6fb8eaf7 100644 --- a/gemfiles/Gemfile.ruby_19.x +++ b/gemfiles/Gemfile.ruby_19.x @@ -4,3 +4,4 @@ gemspec :path => "../" gem "json", "~> 1.0" gem "addressable", "< 2.5" +gem "webmock", "< 3" diff --git a/lib/json-schema.rb b/lib/json-schema.rb index 630001d7..c0814e7a 100644 --- a/lib/json-schema.rb +++ b/lib/json-schema.rb @@ -14,6 +14,5 @@ require 'json-schema/schema/reader' require 'json-schema/validator' -Dir[File.join(File.dirname(__FILE__), "json-schema/attributes/*.rb")].each {|file| require file } -Dir[File.join(File.dirname(__FILE__), "json-schema/attributes/formats/*.rb")].each {|file| require file } +Dir[File.join(File.dirname(__FILE__), "json-schema/attributes/**/*.rb")].each {|file| require file } Dir[File.join(File.dirname(__FILE__), "json-schema/validators/*.rb")].sort!.each {|file| require file } diff --git a/lib/json-schema/attributes/dependencies.rb b/lib/json-schema/attributes/dependencies.rb index e006921e..b8ebc646 100644 --- a/lib/json-schema/attributes/dependencies.rb +++ b/lib/json-schema/attributes/dependencies.rb @@ -34,11 +34,5 @@ def self.accept_value?(value) value.is_a?(String) || value.is_a?(Array) || value.is_a?(Hash) end end - - class DependenciesV4Attribute < DependenciesAttribute - def self.accept_value?(value) - value.is_a?(Array) || value.is_a?(Hash) - end - end end end diff --git a/lib/json-schema/attributes/dependencies_v4.rb b/lib/json-schema/attributes/dependencies_v4.rb new file mode 100644 index 00000000..4fb5578f --- /dev/null +++ b/lib/json-schema/attributes/dependencies_v4.rb @@ -0,0 +1,11 @@ +require 'json-schema/attributes/dependencies' + +module JSON + class Schema + class DependenciesV4Attribute < DependenciesAttribute + def self.accept_value?(value) + value.is_a?(Array) || value.is_a?(Hash) + end + end + end +end diff --git a/lib/json-schema/attributes/divisibleby.rb b/lib/json-schema/attributes/divisibleby.rb index d4655b54..9ca63de6 100644 --- a/lib/json-schema/attributes/divisibleby.rb +++ b/lib/json-schema/attributes/divisibleby.rb @@ -12,7 +12,7 @@ def self.validate(current_schema, data, fragments, processor, validator, options factor = current_schema.schema[keyword] - if factor == 0 || factor == 0.0 || (BigDecimal.new(data.to_s) % BigDecimal.new(factor.to_s)).to_f != 0 + if factor == 0 || factor == 0.0 || (BigDecimal(data.to_s) % BigDecimal(factor.to_s)).to_f != 0 message = "The property '#{build_fragment(fragments)}' was not divisible by #{factor}" validation_error(processor, message, fragments, current_schema, self, options[:record_errors]) end diff --git a/lib/json-schema/attributes/formats/custom.rb b/lib/json-schema/attributes/formats/custom.rb index d1638419..6559097d 100644 --- a/lib/json-schema/attributes/formats/custom.rb +++ b/lib/json-schema/attributes/formats/custom.rb @@ -1,4 +1,4 @@ -require 'json-schema/attribute' +require 'json-schema/attributes/format' require 'json-schema/errors/custom_format_error' module JSON diff --git a/lib/json-schema/attributes/formats/date.rb b/lib/json-schema/attributes/formats/date.rb index 451f6ff1..5540029e 100644 --- a/lib/json-schema/attributes/formats/date.rb +++ b/lib/json-schema/attributes/formats/date.rb @@ -1,4 +1,4 @@ -require 'json-schema/attribute' +require 'json-schema/attributes/format' module JSON class Schema diff --git a/lib/json-schema/attributes/formats/date_time.rb b/lib/json-schema/attributes/formats/date_time.rb index 01987485..27ec22e5 100644 --- a/lib/json-schema/attributes/formats/date_time.rb +++ b/lib/json-schema/attributes/formats/date_time.rb @@ -1,4 +1,4 @@ -require 'json-schema/attribute' +require 'json-schema/attributes/format' module JSON class Schema diff --git a/lib/json-schema/attributes/formats/date_time_v4.rb b/lib/json-schema/attributes/formats/date_time_v4.rb index 0a1903fc..e09c5130 100644 --- a/lib/json-schema/attributes/formats/date_time_v4.rb +++ b/lib/json-schema/attributes/formats/date_time_v4.rb @@ -1,4 +1,4 @@ -require 'json-schema/attribute' +require 'json-schema/attributes/format' module JSON class Schema diff --git a/lib/json-schema/attributes/formats/time.rb b/lib/json-schema/attributes/formats/time.rb index 158df193..8e46123f 100644 --- a/lib/json-schema/attributes/formats/time.rb +++ b/lib/json-schema/attributes/formats/time.rb @@ -1,4 +1,4 @@ -require 'json-schema/attribute' +require 'json-schema/attributes/format' module JSON class Schema diff --git a/lib/json-schema/attributes/formats/uri.rb b/lib/json-schema/attributes/formats/uri.rb index 17e989ea..6d41df90 100644 --- a/lib/json-schema/attributes/formats/uri.rb +++ b/lib/json-schema/attributes/formats/uri.rb @@ -1,4 +1,4 @@ -require 'json-schema/attribute' +require 'json-schema/attributes/format' require 'json-schema/errors/uri_error' module JSON diff --git a/lib/json-schema/attributes/limit.rb b/lib/json-schema/attributes/limit.rb index 90de84f2..0e2a0779 100644 --- a/lib/json-schema/attributes/limit.rb +++ b/lib/json-schema/attributes/limit.rb @@ -48,132 +48,5 @@ def self.limit_name raise NotImplementedError end end - - class MinLengthAttribute < LimitAttribute - def self.acceptable_type - String - end - - def self.limit_name - 'minLength' - end - - def self.error_message(schema) - "was not of a minimum string length of #{limit(schema)}" - end - - def self.value(data) - data.length - end - end - - class MaxLengthAttribute < MinLengthAttribute - def self.limit_name - 'maxLength' - end - - def self.error_message(schema) - "was not of a maximum string length of #{limit(schema)}" - end - end - - class MinItemsAttribute < LimitAttribute - def self.acceptable_type - Array - end - - def self.value(data) - data.length - end - - def self.limit_name - 'minItems' - end - - def self.error_message(schema) - "did not contain a minimum number of items #{limit(schema)}" - end - end - - class MaxItemsAttribute < MinItemsAttribute - def self.limit_name - 'maxItems' - end - - def self.error_message(schema) - "had more items than the allowed #{limit(schema)}" - end - end - - class MinPropertiesAttribute < LimitAttribute - def self.acceptable_type - Hash - end - - def self.value(data) - data.size - end - - def self.limit_name - 'minProperties' - end - - def self.error_message(schema) - "did not contain a minimum number of properties #{limit(schema)}" - end - end - - class MaxPropertiesAttribute < MinPropertiesAttribute - def self.limit_name - 'maxProperties' - end - - def self.error_message(schema) - "had more properties than the allowed #{limit(schema)}" - end - end - - class NumericLimitAttribute < LimitAttribute - def self.acceptable_type - Numeric - end - - def self.error_message(schema) - exclusivity = exclusive?(schema) ? 'exclusively' : 'inclusively' - format("did not have a %s value of %s, %s", limit_name, limit(schema), exclusivity) - end - end - - class MaximumAttribute < NumericLimitAttribute - def self.limit_name - 'maximum' - end - - def self.exclusive?(schema) - schema['exclusiveMaximum'] - end - end - - class MaximumInclusiveAttribute < MaximumAttribute - def self.exclusive?(schema) - schema['maximumCanEqual'] == false - end - end - - class MinimumAttribute < NumericLimitAttribute - def self.limit_name - 'minimum' - end - - def self.exclusive?(schema) - schema['exclusiveMinimum'] - end - end - - class MinimumInclusiveAttribute < MinimumAttribute - def self.exclusive?(schema) - schema['minimumCanEqual'] == false - end - end end end diff --git a/lib/json-schema/attributes/limits/items.rb b/lib/json-schema/attributes/limits/items.rb new file mode 100644 index 00000000..94fbdd19 --- /dev/null +++ b/lib/json-schema/attributes/limits/items.rb @@ -0,0 +1,15 @@ +require 'json-schema/attributes/limit' + +module JSON + class Schema + class ItemsLimitAttribute < LimitAttribute + def self.acceptable_type + Array + end + + def self.value(data) + data.length + end + end + end +end diff --git a/lib/json-schema/attributes/limits/length.rb b/lib/json-schema/attributes/limits/length.rb new file mode 100644 index 00000000..18532204 --- /dev/null +++ b/lib/json-schema/attributes/limits/length.rb @@ -0,0 +1,15 @@ +require 'json-schema/attributes/limit' + +module JSON + class Schema + class LengthLimitAttribute < LimitAttribute + def self.acceptable_type + String + end + + def self.value(data) + data.length + end + end + end +end diff --git a/lib/json-schema/attributes/limits/max_items.rb b/lib/json-schema/attributes/limits/max_items.rb new file mode 100644 index 00000000..fc1a057a --- /dev/null +++ b/lib/json-schema/attributes/limits/max_items.rb @@ -0,0 +1,15 @@ +require 'json-schema/attributes/limits/items' + +module JSON + class Schema + class MaxItemsAttribute < ItemsLimitAttribute + def self.limit_name + 'maxItems' + end + + def self.error_message(schema) + "had more items than the allowed #{limit(schema)}" + end + end + end +end diff --git a/lib/json-schema/attributes/limits/max_length.rb b/lib/json-schema/attributes/limits/max_length.rb new file mode 100644 index 00000000..f455c8cc --- /dev/null +++ b/lib/json-schema/attributes/limits/max_length.rb @@ -0,0 +1,15 @@ +require 'json-schema/attributes/limits/length' + +module JSON + class Schema + class MaxLengthAttribute < LengthLimitAttribute + def self.limit_name + 'maxLength' + end + + def self.error_message(schema) + "was not of a maximum string length of #{limit(schema)}" + end + end + end +end diff --git a/lib/json-schema/attributes/limits/max_properties.rb b/lib/json-schema/attributes/limits/max_properties.rb new file mode 100644 index 00000000..e46ae4a3 --- /dev/null +++ b/lib/json-schema/attributes/limits/max_properties.rb @@ -0,0 +1,15 @@ +require 'json-schema/attributes/limits/properties' + +module JSON + class Schema + class MaxPropertiesAttribute < PropertiesLimitAttribute + def self.limit_name + 'maxProperties' + end + + def self.error_message(schema) + "had more properties than the allowed #{limit(schema)}" + end + end + end +end diff --git a/lib/json-schema/attributes/limits/maximum.rb b/lib/json-schema/attributes/limits/maximum.rb new file mode 100644 index 00000000..acced564 --- /dev/null +++ b/lib/json-schema/attributes/limits/maximum.rb @@ -0,0 +1,15 @@ +require 'json-schema/attributes/limits/numeric' + +module JSON + class Schema + class MaximumAttribute < NumericLimitAttribute + def self.limit_name + 'maximum' + end + + def self.exclusive?(schema) + schema['exclusiveMaximum'] + end + end + end +end diff --git a/lib/json-schema/attributes/limits/maximum_inclusive.rb b/lib/json-schema/attributes/limits/maximum_inclusive.rb new file mode 100644 index 00000000..2ecb5993 --- /dev/null +++ b/lib/json-schema/attributes/limits/maximum_inclusive.rb @@ -0,0 +1,11 @@ +require 'json-schema/attributes/limits/maximum' + +module JSON + class Schema + class MaximumInclusiveAttribute < MaximumAttribute + def self.exclusive?(schema) + schema['maximumCanEqual'] == false + end + end + end +end diff --git a/lib/json-schema/attributes/limits/min_items.rb b/lib/json-schema/attributes/limits/min_items.rb new file mode 100644 index 00000000..e0a3e8f0 --- /dev/null +++ b/lib/json-schema/attributes/limits/min_items.rb @@ -0,0 +1,15 @@ +require 'json-schema/attributes/limits/items' + +module JSON + class Schema + class MinItemsAttribute < ItemsLimitAttribute + def self.limit_name + 'minItems' + end + + def self.error_message(schema) + "did not contain a minimum number of items #{limit(schema)}" + end + end + end +end diff --git a/lib/json-schema/attributes/limits/min_length.rb b/lib/json-schema/attributes/limits/min_length.rb new file mode 100644 index 00000000..fd42f67f --- /dev/null +++ b/lib/json-schema/attributes/limits/min_length.rb @@ -0,0 +1,15 @@ +require 'json-schema/attributes/limits/length' + +module JSON + class Schema + class MinLengthAttribute < LengthLimitAttribute + def self.limit_name + 'minLength' + end + + def self.error_message(schema) + "was not of a minimum string length of #{limit(schema)}" + end + end + end +end diff --git a/lib/json-schema/attributes/limits/min_properties.rb b/lib/json-schema/attributes/limits/min_properties.rb new file mode 100644 index 00000000..b6c75dc1 --- /dev/null +++ b/lib/json-schema/attributes/limits/min_properties.rb @@ -0,0 +1,15 @@ +require 'json-schema/attributes/limits/properties' + +module JSON + class Schema + class MinPropertiesAttribute < PropertiesLimitAttribute + def self.limit_name + 'minProperties' + end + + def self.error_message(schema) + "did not contain a minimum number of properties #{limit(schema)}" + end + end + end +end diff --git a/lib/json-schema/attributes/limits/minimum.rb b/lib/json-schema/attributes/limits/minimum.rb new file mode 100644 index 00000000..f3e977d3 --- /dev/null +++ b/lib/json-schema/attributes/limits/minimum.rb @@ -0,0 +1,15 @@ +require 'json-schema/attributes/limits/numeric' + +module JSON + class Schema + class MinimumAttribute < NumericLimitAttribute + def self.limit_name + 'minimum' + end + + def self.exclusive?(schema) + schema['exclusiveMinimum'] + end + end + end +end diff --git a/lib/json-schema/attributes/limits/minimum_inclusive.rb b/lib/json-schema/attributes/limits/minimum_inclusive.rb new file mode 100644 index 00000000..6d6e66f0 --- /dev/null +++ b/lib/json-schema/attributes/limits/minimum_inclusive.rb @@ -0,0 +1,11 @@ +require 'json-schema/attributes/limits/minimum' + +module JSON + class Schema + class MinimumInclusiveAttribute < MinimumAttribute + def self.exclusive?(schema) + schema['minimumCanEqual'] == false + end + end + end +end diff --git a/lib/json-schema/attributes/limits/numeric.rb b/lib/json-schema/attributes/limits/numeric.rb new file mode 100644 index 00000000..df3db6f2 --- /dev/null +++ b/lib/json-schema/attributes/limits/numeric.rb @@ -0,0 +1,16 @@ +require 'json-schema/attributes/limit' + +module JSON + class Schema + class NumericLimitAttribute < LimitAttribute + def self.acceptable_type + Numeric + end + + def self.error_message(schema) + exclusivity = exclusive?(schema) ? 'exclusively' : 'inclusively' + format("did not have a %s value of %s, %s", limit_name, limit(schema), exclusivity) + end + end + end +end diff --git a/lib/json-schema/attributes/limits/properties.rb b/lib/json-schema/attributes/limits/properties.rb new file mode 100644 index 00000000..6f5b2e8a --- /dev/null +++ b/lib/json-schema/attributes/limits/properties.rb @@ -0,0 +1,15 @@ +require 'json-schema/attributes/limit' + +module JSON + class Schema + class PropertiesLimitAttribute < LimitAttribute + def self.acceptable_type + Hash + end + + def self.value(data) + data.size + end + end + end +end diff --git a/lib/json-schema/attributes/properties.rb b/lib/json-schema/attributes/properties.rb index 13c0f252..cbb81125 100644 --- a/lib/json-schema/attributes/properties.rb +++ b/lib/json-schema/attributes/properties.rb @@ -62,13 +62,5 @@ def self.validate(current_schema, data, fragments, processor, validator, options end end end - - class PropertiesV4Attribute < PropertiesAttribute - # draft4 relies on its own RequiredAttribute validation at a higher level, rather than - # as an attribute of individual properties. - def self.required?(schema, options) - options[:strict] == true - end - end end end diff --git a/lib/json-schema/attributes/properties_v4.rb b/lib/json-schema/attributes/properties_v4.rb new file mode 100644 index 00000000..878d7609 --- /dev/null +++ b/lib/json-schema/attributes/properties_v4.rb @@ -0,0 +1,13 @@ +require 'json-schema/attributes/properties' + +module JSON + class Schema + class PropertiesV4Attribute < PropertiesAttribute + # draft4 relies on its own RequiredAttribute validation at a higher level, rather than + # as an attribute of individual properties. + def self.required?(schema, options) + options[:strict] == true + end + end + end +end diff --git a/lib/json-schema/validator.rb b/lib/json-schema/validator.rb index 82d23dc3..c439b613 100644 --- a/lib/json-schema/validator.rb +++ b/lib/json-schema/validator.rb @@ -103,6 +103,8 @@ def schema_from_fragment(base_schema, fragment) if @options[:list] base_schema.to_array_schema + elsif base_schema.is_a?(Hash) + JSON::Schema.new(base_schema, schema_uri, @options[:version]) else base_schema end @@ -578,7 +580,7 @@ def initialize_data(data) begin json_uri = Util::URI.normalized_uri(data) data = self.class.parse(custom_open(json_uri)) - rescue JSON::Schema::JsonLoadError + rescue JSON::Schema::JsonLoadError, JSON::Schema::UriError # Silently discard the error - use the data as-is end end diff --git a/lib/json-schema/validators/draft6.rb b/lib/json-schema/validators/draft6.rb index 7387f3c1..cd748dfe 100644 --- a/lib/json-schema/validators/draft6.rb +++ b/lib/json-schema/validators/draft6.rb @@ -43,8 +43,8 @@ def initialize 'uri' => UriFormat } @formats = @default_formats.clone - @uri = JSON::Util::URI.parse("http://json-schema.org/draft-06/schema#") - @names = ["draft6", "http://json-schema.org/draft-06/schema#"] + @uri = JSON::Util::URI.parse("http://json-schema.org/draft/schema#") + @names = ["draft6", "http://json-schema.org/draft/schema#"] @metaschema_name = "draft-06.json" end diff --git a/resources/draft-06.json b/resources/draft-06.json index de1316a1..bf657507 100644 --- a/resources/draft-06.json +++ b/resources/draft-06.json @@ -1,7 +1,7 @@ { - "id": "http://json-schema.org/draft-06/schema#", - "$schema": "http://json-schema.org/draft-06/schema#", - "description": "Core schema meta-schema", + "$schema": "http://json-schema.org/draft/schema#", + "$id": "http://json-schema.org/draft/schema#", + "title": "Core schema meta-schema", "definitions": { "schemaArray": { "type": "array", @@ -13,28 +13,43 @@ "minimum": 0 }, "positiveIntegerDefault0": { - "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] + "allOf": [ + { "$ref": "#/definitions/positiveInteger" }, + { "default": 0 } + ] }, "simpleTypes": { - "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] }, "stringArray": { "type": "array", "items": { "type": "string" }, - "minItems": 1, - "uniqueItems": true + "uniqueItems": true, + "defaultItems": [] } }, - "type": "object", + "type": ["object", "boolean"], "properties": { - "id": { + "$id": { "type": "string", - "format": "uri" + "format": "uri-reference" }, "$schema": { "type": "string", "format": "uri" }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, "title": { "type": "string" }, @@ -44,22 +59,19 @@ "default": {}, "multipleOf": { "type": "number", - "minimum": 0, - "exclusiveMinimum": true + "exclusiveMinimum": 0 }, "maximum": { "type": "number" }, "exclusiveMaximum": { - "type": "boolean", - "default": false + "type": "number" }, "minimum": { "type": "number" }, "exclusiveMinimum": { - "type": "boolean", - "default": false + "type": "number" }, "maxLength": { "$ref": "#/definitions/positiveInteger" }, "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, @@ -67,13 +79,7 @@ "type": "string", "format": "regex" }, - "additionalItems": { - "anyOf": [ - { "type": "boolean" }, - { "$ref": "#" } - ], - "default": {} - }, + "additionalItems": { "$ref": "#" }, "items": { "anyOf": [ { "$ref": "#" }, @@ -87,16 +93,11 @@ "type": "boolean", "default": false }, + "contains": { "$ref": "#" }, "maxProperties": { "$ref": "#/definitions/positiveInteger" }, "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, "required": { "$ref": "#/definitions/stringArray" }, - "additionalProperties": { - "anyOf": [ - { "type": "boolean" }, - { "$ref": "#" } - ], - "default": {} - }, + "additionalProperties": { "$ref": "#" }, "definitions": { "type": "object", "additionalProperties": { "$ref": "#" }, @@ -121,6 +122,8 @@ ] } }, + "propertyNames": { "$ref": "#" }, + "const": {}, "enum": { "type": "array", "minItems": 1, @@ -137,14 +140,11 @@ } ] }, + "format": { "type": "string" }, "allOf": { "$ref": "#/definitions/schemaArray" }, "anyOf": { "$ref": "#/definitions/schemaArray" }, "oneOf": { "$ref": "#/definitions/schemaArray" }, "not": { "$ref": "#" } }, - "dependencies": { - "exclusiveMaximum": [ "maximum" ], - "exclusiveMinimum": [ "minimum" ] - }, "default": {} } diff --git a/test/common_test_suite_test.rb b/test/common_test_suite_test.rb index fcf3f708..c5287974 100644 --- a/test/common_test_suite_test.rb +++ b/test/common_test_suite_test.rb @@ -4,48 +4,7 @@ class CommonTestSuiteTest < Minitest::Test TEST_DIR = File.expand_path('../test-suite/tests', __FILE__) - # These are test files which we know fail spectacularly, either because we - # don't support that functionality or because they require external - # dependencies. To allow finer-grained control over which tests to run, - # you can replace `:all` with an array containing the names of individual - # tests to skip. - IGNORED_TESTS = Hash.new { |h,k| h[k] = [] }.merge({ - "draft3/optional/jsregex.json" => :all, - "draft3/optional/format.json" => [ - "validation of regular expressions", - "validation of e-mail addresses", - "validation of URIs", - "validation of host names", - "validation of CSS colors" - ], - "draft3/ref.json" => [ - "ref overrides any sibling keywords/remote ref valid, maxItems ignored" - ], - "draft4/optional/format.json" => [ - "validation of URIs", - "validation of e-mail addresses", - "validation of host names" - ], - "draft4/optional/ecmascript-regex.json" => [ - "ECMA 262 regex non-compliance/ECMA 262 has no support for \\Z anchor from .NET" - ], - "draft4/ref.json" => [ - "ref overrides any sibling keywords/remote ref valid, maxItems ignored", - "ref overrides any sibling keywords/ref valid, maxItems ignored" - ], - "draft6/optional/format.json" => [ - "validation of URIs", - "validation of e-mail addresses", - "validation of host names" - ], - "draft6/optional/ecmascript-regex.json" => [ - "ECMA 262 regex non-compliance/ECMA 262 has no support for \\Z anchor from .NET" - ], - "draft6/ref.json" => [ - "ref overrides any sibling keywords/remote ref valid, maxItems ignored", - "ref overrides any sibling keywords/ref valid, maxItems ignored" - ] - }) + IGNORED_TESTS = YAML.load_file(File.expand_path('../support/test_suite_ignored_tests.yml', __FILE__)) def setup Dir["#{TEST_DIR}/../remotes/**/*.json"].each do |path| @@ -55,6 +14,14 @@ def setup end end + def self.skip?(current_test, file_path) + skipped_in_file = file_path.chomp('.json').split('/').inject(IGNORED_TESTS) do |ignored, path_component| + ignored.nil? ? nil : ignored[path_component] + end + + !skipped_in_file.nil? && (skipped_in_file == :all || skipped_in_file.include?(current_test)) + end + Dir["#{TEST_DIR}/*"].each do |suite| version = File.basename(suite).to_sym Dir["#{suite}/**/*.json"].each do |tfile| @@ -66,13 +33,14 @@ def setup base_description = test["description"] test["tests"].each do |t| - next if IGNORED_TESTS[rel_file] == :all - next if IGNORED_TESTS[rel_file].any? { |ignored| - base_description == ignored || "#{base_description}/#{t['description']}" == ignored - } + full_description = "#{base_description}/#{t['description']}" + + next if rel_file.include?('/optional/') && skip?(full_description, rel_file) - err_id = "#{rel_file}: #{base_description}/#{t['description']}" + err_id = "#{rel_file}: #{full_description}" define_method("test_#{err_id}") do + skip if self.class.skip?(full_description, rel_file) + errors = JSON::Validator.fully_validate(schema, t["data"], :parse_data => false, diff --git a/test/custom_format_test.rb b/test/custom_format_test.rb index 08e94fc0..fc5747e5 100644 --- a/test/custom_format_test.rb +++ b/test/custom_format_test.rb @@ -6,7 +6,7 @@ def setup @all_versions = ['draft1', 'draft2', 'draft3', 'draft4', 'draft6', nil] @format_proc = lambda { |value| raise JSON::Schema::CustomFormatError.new("must be 42") unless value == "42" } @schema_6 = { - "$schema" => "http://json-schema.org/draft-06/schema#", + "$schema" => "http://json-schema.org/draft/schema#", "properties" => { "a" => { "type" => "string", diff --git a/test/fragment_resolution_test.rb b/test/fragment_resolution_test.rb index 36e9aee2..5fe90113 100644 --- a/test/fragment_resolution_test.rb +++ b/test/fragment_resolution_test.rb @@ -27,4 +27,57 @@ def test_fragment_resolution JSON::Validator.validate!(schema,data,:fragment => "#/properties/b") end end + + def test_odd_level_fragment_resolution + schema = { + "foo" => { + "type" => "object", + "required" => ["a"], + "properties" => { + "a" => {"type" => "integer"} + } + } + } + + assert_valid schema, {"a" => 1}, :fragment => "#/foo" + refute_valid schema, {}, :fragment => "#/foo" + end + + def test_even_level_fragment_resolution + schema = { + "foo" => { + "bar" => { + "type" => "object", + "required" => ["a"], + "properties" => { + "a" => {"type" => "integer"} + } + } + } + } + + assert_valid schema, {"a" => 1}, :fragment => "#/foo/bar" + refute_valid schema, {}, :fragment => "#/foo/bar" + end + + def test_array_fragment_resolution + schema = { + "type" => "object", + "required" => ["a"], + "properties" => { + "a" => { + "anyOf" => [ + {"type" => "integer"}, + {"type" => "string"} + ] + } + } + } + + refute_valid schema, "foo", :fragment => "#/properties/a/anyOf/0" + assert_valid schema, "foo", :fragment => "#/properties/a/anyOf/1" + + assert_valid schema, 5, :fragment => "#/properties/a/anyOf/0" + refute_valid schema, 5, :fragment => "#/properties/a/anyOf/1" + end end diff --git a/test/fragment_validation_with_ref_test.rb b/test/fragment_validation_with_ref_test.rb index dbcbd882..9ed3801a 100644 --- a/test/fragment_validation_with_ref_test.rb +++ b/test/fragment_validation_with_ref_test.rb @@ -27,8 +27,41 @@ def whole_schema } end + def whole_schema_with_array + { + "$schema" => "http://json-schema.org/draft-04/schema#", + "type" => "object", + "definitions" => { + "omg" => { + "links" => [ + { + "type" => "object", + "schema" => { + "properties" => { + "content" => { + "type" => "string" + }, + "author" => { + "type" => "string" + } + }, + "required" => ["content", "author"] + } + } + ] + } + } + } + end + def test_validation_of_fragment data = [{"content" => "ohai", "author" => "Bob"}] assert_valid whole_schema, data, :fragment => "#/definitions/posts" end + + def test_validation_of_fragment_with_array + data = {"content" => "ohai", "author" => "Bob"} + assert_valid(whole_schema_with_array, data, + :fragment => "#/definitions/omg/links/0/schema") + end end diff --git a/test/initialize_data_test.rb b/test/initialize_data_test.rb index c760f848..cf08cbe1 100644 --- a/test/initialize_data_test.rb +++ b/test/initialize_data_test.rb @@ -56,6 +56,21 @@ def test_parse_json_string assert_raises(JSON::Schema::JsonLoadError) { JSON::Validator.validate(schema, data, :uri => true) } end + def test_parse_plain_text_string + schema = {'type' => 'string'} + data = 'kapow' + + assert(JSON::Validator.validate(schema, data)) + + assert(JSON::Validator.validate(schema, data, :parse_data => false)) + + assert_raises(JSON::Schema::JsonParseError) do + JSON::Validator.validate(schema, data, :json => true) + end + + assert_raises(JSON::Schema::JsonLoadError) { JSON::Validator.validate(schema, data, :uri => true) } + end + def test_parse_valid_uri_string schema = {'type' => 'string'} data = 'http://foo.bar/' @@ -96,6 +111,21 @@ def test_parse_invalid_uri_string assert_raises(JSON::Schema::JsonLoadError) { JSON::Validator.validate(schema, data, :uri => true) } end + def test_parse_invalid_scheme_string + schema = {'type' => 'string'} + data = 'pick one: [1, 2, 3]' + + assert(JSON::Validator.validate(schema, data)) + + assert(JSON::Validator.validate(schema, data, :parse_data => false)) + + assert_raises(JSON::Schema::JsonParseError) do + JSON::Validator.validate(schema, data, :json => true) + end + + assert_raises(JSON::Schema::UriError) { JSON::Validator.validate(schema, data, :uri => true) } + end + def test_parse_integer schema = {'type' => 'integer'} data = 42 diff --git a/test/support/test_helper.rb b/test/support/test_helper.rb index d32d758f..8c831daa 100644 --- a/test/support/test_helper.rb +++ b/test/support/test_helper.rb @@ -1,3 +1,10 @@ +if ENV['COVERAGE'] + require 'simplecov' + SimpleCov.start do + add_filter '/test/' + end +end + require 'minitest/autorun' require 'webmock/minitest' diff --git a/test/support/test_suite_ignored_tests.yml b/test/support/test_suite_ignored_tests.yml new file mode 100644 index 00000000..02efe74c --- /dev/null +++ b/test/support/test_suite_ignored_tests.yml @@ -0,0 +1,109 @@ +# These are test files which we know fail spectacularly, either because we +# don't support that functionality or because they require external +# dependencies. To allow finer-grained control over which tests to run, +# you can replace `:all` with an array containing the names of individual +# tests to skip. +draft3: + ref: + - ref overrides any sibling keywords/remote ref valid, maxItems ignored + optional: + jsregex: :all + format: + - validation of regular expressions/a regular expression with unclosed parens is invalid + - validation of e-mail addresses/an invalid e-mail address + - validation of URIs/an invalid URI + - validation of URIs/an invalid protocol-relative URI Reference + - validation of URIs/an invalid URI though valid URI reference + - validation of host names/a host name with a component too long + - validation of host names/a host name containing illegal characters + - validation of host names/a host name starting with an illegal character + - validation of CSS colors/an invalid CSS color code + - validation of CSS colors/an invalid CSS color name + - validation of CSS colors/a CSS color name containing invalid characters +draft4: + ref: + - ref overrides any sibling keywords/remote ref valid, maxItems ignored + - ref overrides any sibling keywords/ref valid, maxItems ignored + optional: + format: + - validation of URIs/an invalid URI + - validation of URIs/an invalid protocol-relative URI Reference + - validation of URIs/an invalid URI though valid URI reference + - validation of e-mail addresses/an invalid e-mail address + - validation of host names/a host name with a component too long + - validation of host names/a host name containing illegal characters + - validation of host names/a host name starting with an illegal character + ecmascript-regex: + - ECMA 262 regex non-compliance/ECMA 262 has no support for \Z anchor from .NET + bignum: + - float comparison with high precision on negative numbers/comparison works for very negative numbers + - float comparison with high precision/comparison works for high numbers +draft6: + allOf: + - allOf with boolean schemas, some false/any value is invalid + - allOf with boolean schemas, all false/any value is invalid + - allOf with boolean schemas, all true/any value is valid + anyOf: + - anyOf with boolean schemas, all false/any value is invalid + - anyOf with boolean schemas, all true/any value is valid + - anyOf with boolean schemas, some true/any value is valid + boolean_schema: :all + const: :all + contains: :all + dependencies: + - dependencies with boolean subschemas/empty object is valid + - dependencies with boolean subschemas/object with both properties is invalid + - dependencies with boolean subschemas/object with property having schema false is invalid + - dependencies with boolean subschemas/object with property having schema true is valid + - dependencies with empty array/empty object + - dependencies with empty array/object with one property + exclusiveMaximum: :all + exclusiveMinimum: :all + items: + - items with boolean schema (false)/any non-empty array is invalid + - items with boolean schema (false)/empty array is valid + - items with boolean schema (true)/any array is valid + - items with boolean schema (true)/empty array is valid + - items with boolean schemas/array with one item is valid + - items with boolean schemas/array with two items is invalid + - items with boolean schemas/empty array is valid + not: :all + oneOf: + - oneOf with boolean schemas, all false/any value is invalid + - oneOf with boolean schemas, all true/any value is invalid + - oneOf with boolean schemas, one true/any value is valid + - oneOf with boolean schemas, more than one true/any value is invalid + patternProperties: + - patternProperties with boolean schemas/object with property matching schema false is invalid + - patternProperties with boolean schemas/object with both properties is invalid + - patternProperties with boolean schemas/object with property matching schema true is valid + - patternProperties with boolean schemas/empty object is valid + properties: + - properties with boolean schema/only 'true' property present is valid + - properties with boolean schema/only 'false' property present is invalid + - properties with boolean schema/no property present is valid + - properties with boolean schema/both properties present is invalid + propertyNames: :all + ref: + - ref overrides any sibling keywords/remote ref valid, maxItems ignored + - ref overrides any sibling keywords/ref valid, maxItems ignored + - $ref to boolean schema true/any value is valid + - $ref to boolean schema false/any value is invalid + required: + - required with empty array/property not required + optional: + bignum: + - float comparison with high precision/comparison works for high numbers + - float comparison with high precision on negative numbers/comparison works for very negative numbers + format: + - validation of URIs/an invalid URI + - validation of URIs/an invalid protocol-relative URI Reference + - validation of URIs/an invalid URI though valid URI reference + - validation of e-mail addresses/an invalid e-mail address + - validation of host names/a host name with a component too long + - validation of host names/a host name containing illegal characters + - validation of host names/a host name starting with an illegal character + ecmascript-regex: + - ECMA 262 regex non-compliance/ECMA 262 has no support for \Z anchor from .NET + zeroTerminatedFloats: + - some languages do not distinguish between different types of numeric value/a float is not an integer even without fractional part diff --git a/test/test-suite b/test/test-suite index 07992129..da8b14ee 160000 --- a/test/test-suite +++ b/test/test-suite @@ -1 +1 @@ -Subproject commit 0799212985a16768b83a2f5b0ad525ab2203e328 +Subproject commit da8b14eef365886cac3caba5d6d995db3e02544e