diff --git a/.travis.yml b/.travis.yml index 0b38174c..41eb1e6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ rvm: - "2.0" - "2.1" - "2.2" + - "2.3.1" - "jruby" - "rbx" @@ -15,5 +16,5 @@ install: matrix: include: - - rvm: "2.2" + - rvm: "2.3.1" gemfile: "gemfiles/Gemfile.uuidtools.x" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..3610dc7a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,32 @@ +# Change Log +All notable changes to this project will be documented in this file. +Please keep to the changelog format described on [keepachangelog.com](http://keepachangelog.com). +This project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + +## [2.6.2] - 2016-05-13 + +### Fixed +- Made it possible to include colons in a $ref + +### Changed +- Reformatted examples in the readme + +## [2.6.1] - 2016-02-26 + +### Fixed +- Made sure schemas of an unrecognized type raise a SchemaParseError (not Name error) + +### Changed +- Readme was converted from textile to markdown + +## [2.6.0] - 2016-01-08 + +### Added +- Added a changelog + +### Changed +- Improved performance by caching the parsing and normalization of URIs +- Made validation failures raise a `JSON::Schema::SchemaParseError` and data + loading failures a `JSON::Schema::JsonLoadError` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ca5c4d57..1b2df766 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,6 @@ The Ruby JSON Schema library is meant to be a community effort, and as such, the All individuals that have a pull request merged will receive collaborator access to the repository. Due to the restrictions on RubyGems authentication, permissions to release a gem must be requested along with the email desired to be associated with the release credentials. -Accepting changes to the JSON Schema library shall be made through the use of pull requests on GitHub. A pull request must receive at least two (2) "+1" comments from current contributors to the JSON Schema library before being accepted and merged. If a breaking issue and fix exists, please feel free to contact the project maintainer at hoxworth@gmail.com or @hoxworth for faster resolution. +Accepting changes to the JSON Schema library shall be made through the use of pull requests on GitHub. A pull request must receive at least two (2) "+1" comments from current contributors, and include a relevant changelog entry, before being accepted and merged. If a breaking issue and fix exists, please feel free to contact the project maintainer at hoxworth@gmail.com or @hoxworth for faster resolution. Releases follow semantic versioning and may be made at a maintainer's discretion. diff --git a/Gemfile b/Gemfile index dc60a594..06c12768 100644 --- a/Gemfile +++ b/Gemfile @@ -3,8 +3,3 @@ source "https://rubygems.org" gemspec gem "json", ">= 1.7", :platforms => [:mri_19] - -group :development do - gem "rake" - gem "minitest", '~> 5.0' -end diff --git a/README.md b/README.md new file mode 100644 index 00000000..fd66fee9 --- /dev/null +++ b/README.md @@ -0,0 +1,409 @@ +[![Travis](https://travis-ci.org/ruby-json-schema/json-schema.svg?branch=master)](https://travis-ci.org/ruby-json-schema/json-schema) +[![Code Climate](https://codeclimate.com/github/ruby-json-schema/json-schema/badges/gpa.svg)](https://codeclimate.com/github/ruby-json-schema/json-schema) + +Ruby JSON Schema Validator +========================== + +This library is intended to provide Ruby with an interface for validating JSON +objects against a JSON schema conforming to [JSON Schema Draft +4](http://tools.ietf.org/html/draft-zyp-json-schema-04). Legacy support for +[JSON Schema Draft 3](http://tools.ietf.org/html/draft-zyp-json-schema-03), +[JSON Schema Draft 2](http://tools.ietf.org/html/draft-zyp-json-schema-02), and +[JSON Schema Draft 1](http://tools.ietf.org/html/draft-zyp-json-schema-01) is +also included. + +Additional Resources +-------------------- + +- [Google Groups](https://groups.google.com/forum/#!forum/ruby-json-schema) +- #ruby-json-schema on chat.freenode.net + +Version 2.0.0 Upgrade Notes +--------------------------- + +Please be aware that the upgrade to version 2.0.0 will use Draft-04 **by +default**, so schemas that do not declare a validator using the `$schema` +keyword will use Draft-04 now instead of Draft-03. This is the reason for the +major version upgrade. + +Installation +------------ + +From rubygems.org: + +```sh +gem install json-schema +``` + +From the git repo: + +```sh +$ gem build json-schema.gemspec +$ gem install json-schema-2.5.2.gem +``` + +Validation +----- + +Three base validation methods exist: + +1. `validate`: returns a boolean on whether a validation attempt passes +2. `validate!`: throws a `JSON::Schema::ValidationError` with an appropriate message/trace on where the validation failed +3. `fully_validate`: builds an array of validation errors return when validation is complete + +All methods take two arguments, which can be either a JSON string, a file +containing JSON, or a Ruby object representing JSON data. The first argument to +these methods is always the schema, the second is always the data to validate. +An optional third options argument is also accepted; available options are used +in the examples below. + +By default, the validator uses the [JSON Schema Draft +4](http://tools.ietf.org/html/draft-zyp-json-schema-04) specification for +validation; however, the user is free to specify additional specifications or +extend existing ones. Legacy support for Draft 1, Draft 2, and Draft 3 is +included by either passing an optional `:version` parameter to the `validate` +method (set either as `:draft1` or `draft2`), or by declaring the `$schema` +attribute in the schema and referencing the appropriate specification URI. Note +that the `$schema` attribute takes precedence over the `:version` option during +parsing and validation. + +For further information on json schema itself refer to Understanding +JSON Schema. + +Basic Usage +-------------- + +```ruby +require "json-schema" + +schema = { + "type" => "object", + "required" => ["a"], + "properties" => { + "a" => {"type" => "integer"} + } +} + +# +# validate ruby objects against a ruby schema +# + +# => true +JSON::Validator.validate(schema, { "a" => 5 }) +# => false +JSON::Validator.validate(schema, {}) + +# +# validate a json string against a json schema file +# + +require "json" +File.write("schema.json", JSON.dump(schema)) + +# => true +JSON::Validator.validate('schema.json', '{ "a": 5 }') + +# +# raise an error when validation fails +# + +# => "The property '#/a' of type String did not match the following type: integer" +begin + JSON::Validator.validate!(schema, { "a" => "taco" }) +rescue JSON::Schema::ValidationError => e + e.message +end + +# +# return an array of error messages when validation fails +# + +# => ["The property '#/a' of type String did not match the following type: integer in schema 18a1ffbb-4681-5b00-bd15-2c76aee4b28f"] +JSON::Validator.fully_validate(schema, { "a" => "taco" }) +``` + +Advanced Options +----------------- + +```ruby +require "json-schema" + +schema = { + "type"=>"object", + "required" => ["a"], + "properties" => { + "a" => { + "type" => "integer", + "default" => 42 + }, + "b" => { + "type" => "object", + "properties" => { + "x" => { + "type" => "integer" + } + } + } + } +} + +# +# with the `:list` option, a list can be validated against a schema that represents the individual objects +# + +# => true +JSON::Validator.validate(schema, [{"a" => 1}, {"a" => 2}, {"a" => 3}], :list => true) +# => false +JSON::Validator.validate(schema, [{"a" => 1}, {"a" => 2}, {"a" => 3}]) + +# +# with the `:errors_as_objects` option, `#fully_validate` returns errors as hashes instead of strings +# + +# => [{:schema=>#, :fragment=>"#/a", :message=>"The property '#/a' of type String did not match the following type: integer in schema 18a1ffbb-4681-5b00-bd15-2c76aee4b28f", :failed_attribute=>"TypeV4"}] +JSON::Validator.fully_validate(schema, { "a" => "taco" }, :errors_as_objects => true) + +# +# with the `:strict` option, all properties are condisidered to have `"required": true` and all objects `"additionalProperties": false` +# + +# => true +JSON::Validator.validate(schema, { "a" => 1, "b" => { "x" => 2 } }, :strict => true) +# => false +JSON::Validator.validate(schema, { "a" => 1, "b" => { "x" => 2 }, "c" => 3 }, :strict => true) +# => false +JSON::Validator.validate(schema, { "a" => 1 }, :strict => true) + +# +# with the `:fragment` option, only a fragment of the schema is used for validation +# + +# => true +JSON::Validator.validate(schema, { "x" => 1 }, :fragment => "#/properties/b") +# => false +JSON::Validator.validate(schema, { "x" => 1 }) + +# +# with the `:validate_schema` option, the schema is validated (against the json schema spec) before the json is validated (against the specified schema) +# + +# => true +JSON::Validator.validate(schema, { "a" => 1 }, :validate_schema => true) +# => false +JSON::Validator.validate({ "required" => true }, { "a" => 1 }, :validate_schema => true) + +# +# with the `:insert_defaults` option, any undefined values in the json that have a default in the schema are replaced with the default before validation +# + +# => true +JSON::Validator.validate(schema, {}, :insert_defaults => true) +# => false +JSON::Validator.validate(schema, {}) + +# +# with the `:version` option, schemas conforming to older drafts of the json schema spec can be used +# + +v2_schema = { + "type" => "object", + "properties" => { + "a" => { + "type" => "integer" + } + } +} + +# => false +JSON::Validator.validate(v2_schema, {}, :version => :draft2) +# => true +JSON::Validator.validate(v2_schema, {}) + +# +# with the `:parse_data` option set to false, the json must be a parsed ruby object (not a json text, a uri or a file path) +# + +# => true +JSON::Validator.validate(schema, { "a" => 1 }, :parse_data => false) +# => false +JSON::Validator.validate(schema, '{ "a": 1 }', :parse_data => false) + +# +# with the `:json` option, the json must be an unparsed json text (not a hash, a uri or a file path) +# + +# => true +JSON::Validator.validate(schema, '{ "a": 1 }', :json => true) +# => "no implicit conversion of Hash into String" +begin + JSON::Validator.validate(schema, { "a" => 1 }, :json => true) +rescue TypeError => e + e.message +end + +# +# with the `:uri` option, the json must be a uri or file path (not a hash or a json text) +# + +File.write("data.json", '{ "a": 1 }') + +# => true +JSON::Validator.validate(schema, "data.json", :uri => true) +# => "Can't convert Hash into String." +begin + JSON::Validator.validate(schema, { "a" => 1 }, :uri => true) +rescue TypeError => e + e.message +end +``` + +Extending Schemas +----------------- + +For this example, we are going to extend the [JSON Schema Draft +3](http://tools.ietf.org/html/draft-zyp-json-schema-03) specification by adding +a 'bitwise-and' property for validation. + +```ruby +require "json-schema" + +class BitwiseAndAttribute < JSON::Schema::Attribute + def self.validate(current_schema, data, fragments, processor, validator, options = {}) + if data.is_a?(Integer) && data & current_schema.schema['bitwise-and'].to_i == 0 + message = "The property '#{build_fragment(fragments)}' did not evaluate to true when bitwise-AND'd with #{current_schema.schema['bitwise-or']}" + raise JSON::Schema::ValidationError.new(message, fragments, current_schema) + end + end +end + +class ExtendedSchema < JSON::Schema::Validator + def initialize + super + extend_schema_definition("http://json-schema.org/draft-03/schema#") + @attributes["bitwise-and"] = BitwiseAndAttribute + @uri = URI.parse("http://test.com/test.json") + end + + JSON::Validator.register_validator(self.new) +end + +schema = { + "$schema" => "http://test.com/test.json", + "properties" => { + "a" => { + "bitwise-and" => 1 + }, + "b" => { + "type" => "string" + } + } +} + +data = { + "a" => 0 +} + +data = {"a" => 1, "b" => "taco"} +JSON::Validator.validate(schema,data) # => true +data = {"a" => 1, "b" => 5} +JSON::Validator.validate(schema,data) # => false +data = {"a" => 0, "b" => "taco"} +JSON::Validator.validate(schema,data) # => false +``` + +Custom format validation +------------------------ + +The JSON schema standard allows custom formats in schema definitions which +should be ignored by validators that do not support them. JSON::Schema allows +registering procs as custom format validators which receive the value to be +checked as parameter and must raise a `JSON::Schema::CustomFormatError` to +indicate a format violation. The error message will be prepended by the property +name, e.g. [The property '#a']() + +```ruby +require "json-schema" + +format_proc = -> value { + raise JSON::Schema::CustomFormatError.new("must be 42") unless value == "42" +} + +# register the proc for format 'the-answer' for draft4 schema +JSON::Validator.register_format_validator("the-answer", format_proc, ["draft4"]) + +# omitting the version parameter uses ["draft1", "draft2", "draft3", "draft4"] as default +JSON::Validator.register_format_validator("the-answer", format_proc) + +# deregistering the custom validator +# (also ["draft1", "draft2", "draft3", "draft4"] as default version) +JSON::Validator.deregister_format_validator('the-answer', ["draft4"]) + +# shortcut to restore the default formats for validators (same default as before) +JSON::Validator.restore_default_formats(["draft4"]) + +# with the validator registered as above, the following results in +# ["The property '#a' must be 42"] as returned errors +schema = { + "$schema" => "http://json-schema.org/draft-04/schema#", + "properties" => { + "a" => { + "type" => "string", + "format" => "the-answer", + } + } +} +errors = JSON::Validator.fully_validate(schema, {"a" => "23"}) +``` + +Controlling Remote Schema Reading +--------------------------------- + +In some cases, you may wish to prevent the JSON Schema library from making HTTP +calls or reading local files in order to resolve `$ref` schemas. If you fully +control all schemas which should be used by validation, this could be +accomplished by registering all referenced schemas with the validator in +advance: + +```ruby +schema = JSON::Schema.new(some_schema_definition, Addressable::URI.parse('http://example.com/my-schema')) +JSON::Validator.add_schema(schema) +``` + +If more extensive control is necessary, the `JSON::Schema::Reader` instance used +can be configured in a few ways: + +```ruby +# Change the default schema reader used +JSON::Validator.schema_reader = JSON::Schema::Reader.new(:accept_uri => true, :accept_file => false) + +# For this validation call, use a reader which only accepts URIs from my-website.com +schema_reader = JSON::Schema::Reader.new( + :accept_uri => proc { |uri| uri.host == 'my-website.com' } +) +JSON::Validator.validate(some_schema, some_object, :schema_reader => schema_reader) +``` + +The `JSON::Schema::Reader` interface requires only an object which responds to +`read(string)` and returns a `JSON::Schema` instance. See the [API +documentation](http://www.rubydoc.info/github/ruby-json-schema/json-schema/master/JSON/Schema/Reader) +for more information. + +Notes +----- + +The 'format' attribute is only validated for the following values: + +- date-time +- date +- time +- ip-address (IPv4 address in draft1, draft2 and draft3) +- ipv4 (IPv4 address in draft4) +- ipv6 +- uri + +All other 'format' attribute values are simply checked to ensure the instance +value is of the correct datatype (e.g., an instance value is validated to be an +integer or a float in the case of 'utc-millisec'). + +Additionally, JSON::Validator does not handle any json hyperschema attributes. diff --git a/README.textile b/README.textile deleted file mode 100644 index 7af50741..00000000 --- a/README.textile +++ /dev/null @@ -1,440 +0,0 @@ -!https://travis-ci.org/ruby-json-schema/json-schema.svg?branch=master!:https://travis-ci.org/ruby-json-schema/json-schema -!https://codeclimate.com/github/ruby-json-schema/json-schema/badges/gpa.svg!:https://codeclimate.com/github/ruby-json-schema/json-schema - -h1. Ruby JSON Schema Validator - -This library is intended to provide Ruby with an interface for validating JSON objects against a JSON schema conforming to "JSON Schema Draft 4":http://tools.ietf.org/html/draft-zyp-json-schema-04. Legacy support for "JSON Schema Draft 3":http://tools.ietf.org/html/draft-zyp-json-schema-03, "JSON Schema Draft 2":http://tools.ietf.org/html/draft-zyp-json-schema-02, and "JSON Schema Draft 1":http://tools.ietf.org/html/draft-zyp-json-schema-01 is also included. - -h2. Additional Resources - -* "Google Groups":https://groups.google.com/forum/#!forum/ruby-json-schema -* #ruby-json-schema on chat.freenode.net - -h2. Version 2.0.0 Upgrade Notes - -Please be aware that the upgrade to version 2.0.0 will use Draft-04 *by default*, so schemas that do not declare a validator using the $schema keyword will use Draft-04 now instead of Draft-03. This is the reason for the major version upgrade. - -h2. Installation - -From rubygems.org: - -
-gem install json-schema
-
- -From the git repo: - -
-$ gem build json-schema.gemspec
-$ gem install json-schema-2.5.0.gem
-
- - -h2. Usage - -Three base validation methods exist: validate, validate!, and fully_validate. The first returns a boolean on whether a validation attempt passes and the second will throw a JSON::Schema::ValidationError with an appropriate message/trace on where the validation failed. The third validation method does not immediately fail upon a validation error and instead builds an array of validation errors return when validation is complete. - -All methods take two arguments, which can be either a JSON string, a file containing JSON, or a Ruby object representing JSON data. The first argument to these methods is always the schema, the second is always the data to validate. An optional third options argument is also accepted; available options are used in the examples below. - -By default, the validator uses the "JSON Schema Draft 4":http://tools.ietf.org/html/draft-zyp-json-schema-04 specification for validation; however, the user is free to specify additional specifications or extend existing ones. Legacy support for Draft 1, Draft 2, and Draft 3 is included by either passing an optional :version parameter to the validate method (set either as :draft1 or draft2), or by declaring the $schema attribute in the schema and referencing the appropriate specification URI. Note that the $schema attribute takes precedence over the :version option during parsing and validation. - -h3. Validate Ruby objects against a Ruby schema - -For further information on json schema itself refer to Understanding JSON Schema. - -
-require 'rubygems'
-require 'json-schema'
-
-schema = {
-  "type" => "object",
-  "required" => ["a"],
-  "properties" => {
-    "a" => {"type" => "integer"}
-  }
-}
-
-data = {
-  "a" => 5
-}
-
-JSON::Validator.validate(schema, data)
-
- -h3. Validate a JSON string against a JSON schema file - -
-require 'rubygems'
-require 'json-schema'
-
-JSON::Validator.validate('schema.json', '{"a" : 5}')
-
- -h3. Validate a list of objects against a schema that represents the individual objects - -
-require 'rubygems'
-require 'json-schema'
-
-data = ['user','user','user']
-JSON::Validator.validate('user.json', data, :list => true)
-
- -h3. Strictly validate an object's properties - -With the :strict option, validation fails when an object contains properties that are not defined in the schema's property list or doesn't match the additionalProperties property. Furthermore, all properties are treated as required by default. - -
-require 'rubygems'
-require 'json-schema'
-
-schema = {
-  "type" => "object",
-  "properties" => {
-    "a" => {"type" => "integer"},
-    "b" => {"type" => "integer"}
-  }
-}
-
-JSON::Validator.validate(schema, {"a" => 1, "b" => 2}, :strict => true)            # ==> true
-JSON::Validator.validate(schema, {"a" => 1, "b" => 2, "c" => 3}, :strict => true)  # ==> false
-JSON::Validator.validate(schema, {"a" => 1}, :strict => true)                      # ==> false
-
- -h3. Catch a validation error and print it out - -
-require 'rubygems'
-require 'json-schema'
-
-schema = {
-  "type" => "object",
-  "required" => ["a"],
-  "properties" => {
-    "a" => {"type" => "integer"}
-  }
-}
-
-data = {
-  "a" => "taco"
-}
-
-begin
-  JSON::Validator.validate!(schema, data)
-rescue JSON::Schema::ValidationError
-  puts $!.message
-end
-
- - -h3. Fully validate against a schema and catch all errors - -
-require 'rubygems'
-require 'json-schema'
-
-schema = {
-  "type" => "object",
-  "required" => ["a","b"],
-  "properties" => {
-    "a" => {"type" => "integer"},
-    "b" => {"type" => "string"}
-  }
-}
-
-data = {
-  "a" => "taco"
-}
-
-errors = JSON::Validator.fully_validate(schema, data)
-
-# ["The property '#/a' of type String did not match the following type: integer in schema 03179a21-197e-5414-9611-e9f63e8324cd#", "The property '#/' did not contain a required property of 'b' in schema 03179a21-197e-5414-9611-e9f63e8324cd#"]
-
- -h3. Fully validate against a schema and catch all errors as objects - -
-require 'rubygems'
-require 'json-schema'
-
-schema = {
-  "type" => "object",
-  "required" => ["a","b"],
-  "properties" => {
-    "a" => {"type" => "integer"},
-    "b" => {"type" => "string"}
-  }
-}
-
-data = {
-  "a" => "taco"
-}
-
-errors = JSON::Validator.fully_validate(schema, data, :errors_as_objects => true)
-
-# [{:message=>"The property '#/a' of type String did not match the following type: integer in schema 03179a21-197e-5414-9611-e9f63e8324cd#", :schema=>#, :failed_attribute=>"Type", :fragment=>"#/a"}, {:message=>"The property '#/' did not contain a required property of 'b' in schema 03179a21-197e-5414-9611-e9f63e8324cd#", :schema=>#, :failed_attribute=>"Properties", :fragment=>"#/"}]
-
-
- -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 - -
-require 'rubygems'
-require 'json-schema'
-
-schema = {
-  "type" => "object",
-  "required" => ["a"],
-  "properties" => {
-    "a" => {"type" => "integer"}  # This will fail schema validation!
-  }
-}
-
-data = {
-  "a" => 5
-}
-
-JSON::Validator.validate(schema, data, :validate_schema => true)
-
- -h3. Validate a JSON object against a JSON schema object, while inserting default values from the schema - -With the :insert_defaults option set to true any missing property that has a -default value specified in the schema will be inserted into the validated data. The inserted default value is validated hence catching a schema that specifies an invalid default value. - -
-require 'rubygems'
-require 'json-schema'
-
-schema = {
-  "type" => "object",
-  "required" => ["a"],
-  "properties" => {
-    "a" => {"type" => "integer", "default" => 42},
-    "b" => {"type" => "integer"}
-  }
-}
-
-# Would not normally validate because "a" is missing and required by schema,
-# but "default" option allows insertion of valid default.
-data = {
-  "b" => 5
-}
-
-JSON::Validator.validate(schema, data)
-# false
-
-JSON::Validator.validate(schema, data, :insert_defaults => true)
-# true
-# data = {
-#   "a" => 42,
-#   "b" => 5
-# }
-
-
-h3. Validate an object against a JSON Schema Draft 2 schema - -
-require 'rubygems'
-require 'json-schema'
-
-schema = {
-  "type" => "object",
-  "properties" => {
-    "a" => {"type" => "integer", "optional" => true}
-  }
-}
-
-data = {
-  "a" => 5
-}
-
-JSON::Validator.validate(schema, data, :version => :draft2)
-
- -h3. Explicitly specifying the type of the data - -By default, json-schema accepts a variety of different types for the data parameter, and it will try to work out what to do with it dynamically. You can pass it a string uri (in which case it will download the json from that location before validating), a string of JSON text, or simply a ruby object (such as an array or hash representing parsed json). However, sometimes the nature of the data is ambiguous (for example, is "http://github.com" just a string, or is it a uri?). In other situations, you have already parsed your JSON, and you don't need to re-parse it. - -If you want to be explict about what kind of data is being parsed, JSON schema supports a number of options: - -
-require 'rubygems'
-require 'json-schema'
-
-schema = {
-  "type" => "string"
-}
-
-# examines the data, determines it's a uri, then tries to load data from it
-JSON::Validator.validate(schema, 'https://api.github.com') # returns false
-
-# data is already parsed json - just accept it as-is
-JSON::Validator.validate(schema, 'https://api.github.com', :parse_data => false) # returns true
-
-# data is parsed to a json string
-JSON::Validator.validate(schema, '"https://api.github.com"', :json => true) # returns true
-
-# loads data from the uri
-JSON::Validator.validate(schema, 'https://api.github.com', :uri => true) # returns false
-
- -h3. Extend an existing schema and validate against it - -For this example, we are going to extend the "JSON Schema Draft 3":http://tools.ietf.org/html/draft-zyp-json-schema-03 specification by adding a 'bitwise-and' property for validation. - -
-require 'rubygems'
-require 'json-schema'
-
-class BitwiseAndAttribute < JSON::Schema::Attribute
-  def self.validate(current_schema, data, fragments, processor, validator, options = {})
-    if data.is_a?(Integer) && data & current_schema.schema['bitwise-and'].to_i == 0
-      message = "The property '#{build_fragment(fragments)}' did not evaluate  to true when bitwise-AND'd with  #{current_schema.schema['bitwise-or']}"
-      raise JSON::Schema::ValidationError.new(message, fragments, current_schema)
-    end
-  end
-end
-
-class ExtendedSchema < JSON::Schema::Validator
-  def initialize
-    super
-    extend_schema_definition("http://json-schema.org/draft-03/schema#")
-    @attributes["bitwise-and"] = BitwiseAndAttribute
-    @uri = URI.parse("http://test.com/test.json")
-  end
-
-  JSON::Validator.register_validator(self.new)
-end
-
-schema = {
-  "$schema" => "http://test.com/test.json",
-  "properties" => {
-    "a" => {
-      "bitwise-and" => 1
-    },
-    "b" => {
-      "type" => "string"
-    }
-  }
-}
-
-data = {
-  "a" => 0
-}
-
-data = {"a" => 1, "b" => "taco"}
-JSON::Validator.validate(schema,data) # => true
-data = {"a" => 1, "b" => 5}
-JSON::Validator.validate(schema,data) # => false
-data = {"a" => 0, "b" => "taco"}
-JSON::Validator.validate(schema,data) # => false
-
- -h3. Custom format validation - -The JSON schema standard allows custom formats in schema definitions which should be ignored by validators that do not support them. JSON::Schema allows registering procs as custom format validators which receive the value to be checked as parameter and must raise a JSON::Schema::CustomFormatError to indicate a format violation. The error message will be prepended by the property name, e.g. "The property '#a'": - -
-require 'rubygems'
-require 'json-schema'
-
-format_proc = -> value {
-  raise JSON::Schema::CustomFormatError.new("must be 42") unless value == "42"
-}
-
-# register the proc for format 'the-answer' for draft4 schema
-JSON::Validator.register_format_validator("the-answer", format_proc, ["draft4"])
-
-# omitting the version parameter uses ["draft1", "draft2", "draft3", "draft4"] as default
-JSON::Validator.register_format_validator("the-answer", format_proc)
-
-# deregistering the custom validator
-# (also ["draft1", "draft2", "draft3", "draft4"] as default version)
-JSON::Validator.deregister_format_validator('the-answer', ["draft4"])
-
-# shortcut to restore the default formats for validators (same default as before)
-JSON::Validator.restore_default_formats(["draft4"])
-
-# with the validator registered as above, the following results in
-# ["The property '#a' must be 42"] as returned errors
-schema = {
-  "$schema" => "http://json-schema.org/draft-04/schema#",
-  "properties" => {
-    "a" => {
-      "type" => "string",
-      "format" => "the-answer",
-    }
-  }
-}
-errors = JSON::Validator.fully_validate(schema, {"a" => "23"})
-
-
- -h2. Controlling Remote Schema Reading - -In some cases, you may wish to prevent the JSON Schema library from making HTTP calls or reading local files in order to resolve $ref schemas. If you fully control all schemas which should be used by validation, this could be accomplished by registering all referenced schemas with the validator in advance: - -
-schema = JSON::Schema.new(some_schema_definition, Addressable::URI.parse('http://example.com/my-schema'))
-JSON::Validator.add_schema(schema)
-
- -If more extensive control is necessary, the JSON::Schema::Reader instance used can be configured in a few ways: - -
-# Change the default schema reader used
-JSON::Validator.schema_reader = JSON::Schema::Reader.new(:accept_uri => true, :accept_file => false)
-
-# For this validation call, use a reader which only accepts URIs from my-website.com
-schema_reader = JSON::Schema::Reader.new(
-  :accept_uri => proc { |uri| uri.host == 'my-website.com' }
-)
-JSON::Validator.validate(some_schema, some_object, :schema_reader => schema_reader)
-
- -The JSON::Schema::Reader interface requires only an object which responds to read(string) and returns a JSON::Schema instance. See the "API documentation":http://www.rubydoc.info/github/ruby-json-schema/json-schema/master/JSON/Schema/Reader for more information. - -h2. Notes - -The 'format' attribute is only validated for the following values: - -* date-time -* date -* time -* ip-address (IPv4 address in draft1, draft2 and draft3) -* ipv4 (IPv4 address in draft4) -* ipv6 -* uri - -All other 'format' attribute values are simply checked to ensure the instance value is of the correct datatype (e.g., an instance value is validated to be an integer or a float in the case of 'utc-millisec'). - -Additionally, JSON::Validator does not handle any json hyperschema attributes. diff --git a/Rakefile b/Rakefile index a6a66259..1033ed9f 100644 --- a/Rakefile +++ b/Rakefile @@ -14,7 +14,9 @@ end Rake::TestTask.new do |t| t.libs << "." - t.test_files = FileList['test/test*.rb'] + t.warning = true + t.verbose = true + t.test_files = FileList.new('test/*_test.rb') end task :test => :update_common_tests diff --git a/VERSION.yml b/VERSION.yml index 10e76f93..6f1b0691 100644 --- a/VERSION.yml +++ b/VERSION.yml @@ -1,3 +1,3 @@ major: 2 -minor: 5 -patch: 1 +minor: 6 +patch: 2 diff --git a/json-schema.gemspec b/json-schema.gemspec index a825c62f..e392b357 100644 --- a/json-schema.gemspec +++ b/json-schema.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |s| s.summary = "Ruby JSON Schema Validator" s.files = Dir[ "lib/**/*", "resources/*.json" ] s.require_path = "lib" - s.extra_rdoc_files = ["README.textile","LICENSE.md"] + s.extra_rdoc_files = ["README.md","LICENSE.md"] s.required_ruby_version = ">= 1.9.0" s.license = "MIT" s.required_rubygems_version = ">= 1.8" @@ -23,5 +23,5 @@ Gem::Specification.new do |s| s.add_development_dependency "webmock" s.add_development_dependency "bundler" - s.add_runtime_dependency "addressable", '~> 2.3.7' + s.add_runtime_dependency "addressable", '~> 2.3.8' end diff --git a/lib/json-schema/attributes/formats/date.rb b/lib/json-schema/attributes/formats/date.rb index 24b21700..451f6ff1 100644 --- a/lib/json-schema/attributes/formats/date.rb +++ b/lib/json-schema/attributes/formats/date.rb @@ -11,7 +11,8 @@ def self.validate(current_schema, data, fragments, processor, validator, options if REGEXP.match(data) begin Date.parse(data) - rescue Exception + rescue ArgumentError => e + raise e unless e.message == 'invalid date' validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) end else diff --git a/lib/json-schema/attributes/formats/date_time.rb b/lib/json-schema/attributes/formats/date_time.rb index b1baa15c..01987485 100644 --- a/lib/json-schema/attributes/formats/date_time.rb +++ b/lib/json-schema/attributes/formats/date_time.rb @@ -14,18 +14,16 @@ def self.validate(current_schema, data, fragments, processor, validator, options begin Date.parse(parts[0]) - rescue Exception + rescue ArgumentError => e + raise e unless e.message == 'invalid date' validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) return end - begin - validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[1].to_i > 23 - validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[2].to_i > 59 - validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[3].to_i > 59 - rescue Exception - validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) - end + validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m.length < 4 + validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[1].to_i > 23 + validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[2].to_i > 59 + validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) and return if m[3].to_i > 59 else validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) end diff --git a/lib/json-schema/attributes/formats/uri.rb b/lib/json-schema/attributes/formats/uri.rb index ff74ad7d..17e989ea 100644 --- a/lib/json-schema/attributes/formats/uri.rb +++ b/lib/json-schema/attributes/formats/uri.rb @@ -1,5 +1,6 @@ require 'json-schema/attribute' -require 'addressable/uri' +require 'json-schema/errors/uri_error' + module JSON class Schema class UriFormat < FormatAttribute @@ -7,8 +8,8 @@ def self.validate(current_schema, data, fragments, processor, validator, options return unless data.is_a?(String) error_message = "The property '#{build_fragment(fragments)}' must be a valid URI" begin - Addressable::URI.parse(data) - rescue Addressable::URI::InvalidURIError + JSON::Util::URI.parse(data) + rescue JSON::Schema::UriError validation_error(processor, error_message, fragments, current_schema, self, options[:record_errors]) end end diff --git a/lib/json-schema/attributes/not.rb b/lib/json-schema/attributes/not.rb index 52729ef3..64fbd6e3 100644 --- a/lib/json-schema/attributes/not.rb +++ b/lib/json-schema/attributes/not.rb @@ -17,7 +17,7 @@ def self.validate(current_schema, data, fragments, processor, validator, options message = "The property '#{build_fragment(fragments)}' of type #{data.class} matched the disallowed schema" failed = false end - rescue + rescue ValidationError # Yay, we failed validation. end diff --git a/lib/json-schema/attributes/ref.rb b/lib/json-schema/attributes/ref.rb index a537cffe..2db8b2f5 100644 --- a/lib/json-schema/attributes/ref.rb +++ b/lib/json-schema/attributes/ref.rb @@ -1,5 +1,6 @@ require 'json-schema/attribute' require 'json-schema/errors/schema_error' +require 'json-schema/util/uri' module JSON class Schema @@ -21,21 +22,23 @@ def self.validate(current_schema, data, fragments, processor, validator, options def self.get_referenced_uri_and_schema(s, current_schema, validator) uri,schema = nil,nil - temp_uri = Addressable::URI.parse(s['$ref']) - if temp_uri.relative? - temp_uri = current_schema.uri.clone - # Check for absolute path - path = s['$ref'].split("#")[0] - if path.nil? || path == '' - temp_uri.path = current_schema.uri.path - elsif path[0,1] == "/" - temp_uri.path = Pathname.new(path).cleanpath.to_s - else - temp_uri = current_schema.uri.join(path) + temp_uri = JSON::Util::URI.parse(s['$ref']) + temp_uri.defer_validation do + if temp_uri.relative? + temp_uri.merge!(current_schema.uri) + # Check for absolute path + path, fragment = s['$ref'].split("#") + if path.nil? || path == '' + temp_uri.path = current_schema.uri.path + elsif path[0,1] == "/" + temp_uri.path = Pathname.new(path).cleanpath.to_s + else + temp_uri.join!(path) + end + temp_uri.fragment = fragment end - temp_uri.fragment = s['$ref'].split("#")[1] + temp_uri.fragment = "" if temp_uri.fragment.nil? || temp_uri.fragment.empty? end - temp_uri.fragment = "" if temp_uri.fragment.nil? # Grab the parent schema from the schema list schema_key = temp_uri.to_s.split("#")[0] + "#" @@ -45,11 +48,11 @@ def self.get_referenced_uri_and_schema(s, current_schema, validator) if ref_schema # Perform fragment resolution to retrieve the appropriate level for the schema target_schema = ref_schema.schema - fragments = temp_uri.fragment.split("/") + fragments = JSON::Util::URI.parse(JSON::Util::URI.unescape_uri(temp_uri)).fragment.split("/") fragment_path = '' fragments.each do |fragment| if fragment && fragment != '' - fragment = Addressable::URI.unescape(fragment.gsub('~0', '~').gsub('~1', '/')) + fragment = fragment.gsub('~0', '~').gsub('~1', '/') if target_schema.is_a?(Array) target_schema = target_schema[fragment.to_i] else diff --git a/lib/json-schema/errors/json_load_error.rb b/lib/json-schema/errors/json_load_error.rb new file mode 100644 index 00000000..f5a74b78 --- /dev/null +++ b/lib/json-schema/errors/json_load_error.rb @@ -0,0 +1,6 @@ +module JSON + class Schema + class JsonLoadError < StandardError + end + end +end diff --git a/lib/json-schema/errors/schema_parse_error.rb b/lib/json-schema/errors/schema_parse_error.rb new file mode 100644 index 00000000..afdf84f1 --- /dev/null +++ b/lib/json-schema/errors/schema_parse_error.rb @@ -0,0 +1,8 @@ +require 'json/common' + +module JSON + class Schema + class SchemaParseError < JSON::ParserError + end + end +end diff --git a/lib/json-schema/errors/uri_error.rb b/lib/json-schema/errors/uri_error.rb new file mode 100644 index 00000000..cf4d306b --- /dev/null +++ b/lib/json-schema/errors/uri_error.rb @@ -0,0 +1,6 @@ +module JSON + class Schema + class UriError < StandardError + end + end +end diff --git a/lib/json-schema/schema.rb b/lib/json-schema/schema.rb index cba7bac0..1df0623b 100644 --- a/lib/json-schema/schema.rb +++ b/lib/json-schema/schema.rb @@ -11,13 +11,13 @@ def initialize(schema,uri,parent_validator=nil) # If there is an ID on this schema, use it to generate the URI if @schema['id'] && @schema['id'].kind_of?(String) - temp_uri = Addressable::URI.parse(@schema['id']) + temp_uri = JSON::Util::URI.parse(@schema['id']) if temp_uri.relative? temp_uri = uri.join(temp_uri) end @uri = temp_uri end - @uri.fragment = '' + @uri = JSON::Util::URI.strip_fragment(@uri) # If there is a $schema on this schema, use it to determine which validator to use if @schema['$schema'] @@ -60,4 +60,3 @@ def to_s end end end - diff --git a/lib/json-schema/schema/reader.rb b/lib/json-schema/schema/reader.rb index 9b13c66b..1493d1f0 100644 --- a/lib/json-schema/schema/reader.rb +++ b/lib/json-schema/schema/reader.rb @@ -1,5 +1,4 @@ require 'open-uri' -require 'addressable/uri' require 'pathname' module JSON @@ -57,12 +56,12 @@ def initialize(options = {}) # @param location [#to_s] The location from which to read the schema # @return [JSON::Schema] # @raise [JSON::Schema::ReadRefused] if +accept_uri+ or +accept_file+ - # indicated the schema should not be readed - # @raise [JSON::ParserError] if the schema was not a valid JSON object + # indicated the schema could not be read + # @raise [JSON::Schema::ParseError] if the schema was not a valid JSON object def read(location) - uri = Addressable::URI.parse(location.to_s) + uri = JSON::Util::URI.parse(location.to_s) body = if uri.scheme.nil? || uri.scheme == 'file' - uri = Addressable::URI.convert_path(uri.path) + uri = JSON::Util::URI.file_uri(uri) read_file(Pathname.new(uri.path).expand_path) else read_uri(uri) @@ -103,7 +102,7 @@ def read_uri(uri) def read_file(pathname) if accept_file?(pathname) - File.read(Addressable::URI.unescape(pathname.to_s)) + File.read(JSON::Util::URI.unescaped_path(pathname.to_s)) else raise JSON::Schema::ReadRefused.new(pathname.to_s, :file) end diff --git a/lib/json-schema/util/uri.rb b/lib/json-schema/util/uri.rb index eca9e6ed..6061625e 100644 --- a/lib/json-schema/util/uri.rb +++ b/lib/json-schema/util/uri.rb @@ -1,15 +1,67 @@ +require 'addressable/uri' + module JSON module Util module URI - def self.normalized_uri(uri) - uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI) - # Check for absolute path - if uri.relative? - data = uri.to_s - data = "#{Dir.pwd}/#{data}" if data[0,1] != '/' - uri = Addressable::URI.convert_path(data) + SUPPORTED_PROTOCOLS = %w(http https ftp tftp sftp ssh svn+ssh telnet nntp gopher wais ldap prospero) + + def self.normalized_uri(uri, base_path = Dir.pwd) + @normalize_cache ||= {} + normalized_uri = @normalize_cache[uri] + + if !normalized_uri + normalized_uri = parse(uri) + # Check for absolute path + if normalized_uri.relative? + data = normalized_uri + data = File.join(base_path, data) if data.path[0,1] != "/" + normalized_uri = file_uri(data) + end + @normalize_cache[uri] = normalized_uri.freeze end - uri + + normalized_uri + end + + def self.parse(uri) + if uri.is_a?(Addressable::URI) + return uri.dup + else + @parse_cache ||= {} + parsed_uri = @parse_cache[uri] + if parsed_uri + parsed_uri.dup + else + @parse_cache[uri] = Addressable::URI.parse(uri) + end + end + rescue Addressable::URI::InvalidURIError => e + raise JSON::Schema::UriError.new(e.message) + end + + def self.strip_fragment(uri) + parsed_uri = parse(uri) + if parsed_uri.fragment.nil? || parsed_uri.fragment.empty? + parsed_uri + else + parsed_uri.merge(:fragment => "") + end + end + + def self.file_uri(uri) + parsed_uri = parse(uri) + + Addressable::URI.convert_path(parsed_uri.path) + end + + def self.unescape_uri(uri) + Addressable::URI.unescape(uri) + end + + def self.unescaped_path(uri) + parsed_uri = parse(uri) + + Addressable::URI.unescape(parsed_uri.path) end end end diff --git a/lib/json-schema/validator.rb b/lib/json-schema/validator.rb index b30ddc15..9fc23447 100644 --- a/lib/json-schema/validator.rb +++ b/lib/json-schema/validator.rb @@ -1,4 +1,3 @@ -require 'addressable/uri' require 'open-uri' require 'pathname' require 'bigdecimal' @@ -9,7 +8,10 @@ require 'json-schema/schema/reader' require 'json-schema/errors/schema_error' +require 'json-schema/errors/schema_parse_error' +require 'json-schema/errors/json_load_error' require 'json-schema/errors/json_parse_error' +require 'json-schema/util/uri' module JSON @@ -53,17 +55,13 @@ def initialize(schema_data, data, opts={}) # validate the schema, if requested if @options[:validate_schema] - begin - if @base_schema.schema["$schema"] - base_validator = JSON::Validator.validator_for_name(@base_schema.schema["$schema"]) - end - metaschema = base_validator ? base_validator.metaschema : validator.metaschema - # Don't clear the cache during metaschema validation! - meta_validator = JSON::Validator.new(metaschema, @base_schema.schema, {:clear_cache => false}) - meta_validator.validate - rescue JSON::Schema::ValidationError, JSON::Schema::SchemaError - raise $! + if @base_schema.schema["$schema"] + base_validator = JSON::Validator.validator_for_name(@base_schema.schema["$schema"]) end + metaschema = base_validator ? base_validator.metaschema : validator.metaschema + # Don't clear the cache during metaschema validation! + meta_validator = JSON::Validator.new(metaschema, @base_schema.schema, {:clear_cache => false}) + meta_validator.validate end # If the :fragment option is set, try and validate against the fragment @@ -136,15 +134,13 @@ def load_ref_schema(parent_schema, ref) end def absolutize_ref_uri(ref, parent_schema_uri) - ref_uri = Addressable::URI.parse(ref) - ref_uri.fragment = '' + ref_uri = JSON::Util::URI.strip_fragment(ref) return ref_uri if ref_uri.absolute? # This is a self reference and thus the schema does not need to be re-loaded return parent_schema_uri if ref_uri.path.empty? - uri = parent_schema_uri.clone - uri.fragment = '' + uri = JSON::Util::URI.strip_fragment(parent_schema_uri.dup) Util::URI.normalized_uri(uri.join(ref_uri.path)) end @@ -220,7 +216,7 @@ def build_schemas(parent_schema) # Either load a reference schema or create a new schema def handle_schema(parent_schema, obj) if obj.is_a?(Hash) - schema_uri = parent_schema.uri.clone + schema_uri = parent_schema.uri.dup schema = JSON::Schema.new(obj, schema_uri, parent_schema.validator) if obj['id'] Validator.add_schema(schema) @@ -342,7 +338,7 @@ def default_validator def validator_for_uri(schema_uri) return default_validator unless schema_uri - u = Addressable::URI.parse(schema_uri) + u = JSON::Util::URI.parse(schema_uri) validator = validators["#{u.scheme}://#{u.host}#{u.path}"] if validator.nil? raise JSON::Schema::SchemaError.new("Schema not found: #{schema_uri}") @@ -371,20 +367,20 @@ def register_default_validator(v) @@default_validator = v end - def register_format_validator(format, validation_proc, versions = ["draft1", "draft2", "draft3", "draft4"]) + def register_format_validator(format, validation_proc, versions = ["draft1", "draft2", "draft3", "draft4", nil]) custom_format_validator = JSON::Schema::CustomFormat.new(validation_proc) validators_for_names(versions).each do |validator| validator.formats[format.to_s] = custom_format_validator end end - def deregister_format_validator(format, versions = ["draft1", "draft2", "draft3", "draft4"]) + def deregister_format_validator(format, versions = ["draft1", "draft2", "draft3", "draft4", nil]) validators_for_names(versions).each do |validator| validator.formats[format.to_s] = validator.default_formats[format.to_s] end end - def restore_default_formats(versions = ["draft1", "draft2", "draft3", "draft4"]) + def restore_default_formats(versions = ["draft1", "draft2", "draft3", "draft4", nil]) validators_for_names(versions).each do |validator| validator.formats = validator.default_formats.clone end @@ -416,9 +412,16 @@ def merge_missing_values(source, destination) private def validators_for_names(names) - names.map! { |name| name.to_s } - validators.reduce([]) do |memo, (_, validator)| - memo.tap { |m| m << validator if (validator.names & names).any? } + names = names.map { |name| name.to_s } + [].tap do |memo| + validators.each do |_, validator| + if (validator.names & names).any? + memo << validator + end + end + if names.include?('') + memo << default_validator + end end end end @@ -445,13 +448,13 @@ 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_uri = JSON::Util::URI.parse(fake_uuid(schema)) schema = JSON::Schema.new(JSON::Validator.parse(schema), schema_uri, @options[:version]) if @options[:list] && @options[:fragment].nil? schema = schema.to_array_schema end Validator.add_schema(schema) - rescue + rescue JSON::ParserError # Build a uri for it schema_uri = Util::URI.normalized_uri(schema) if !self.class.schema_loaded?(schema_uri) @@ -467,14 +470,14 @@ def initialize_schema(schema) schema = self.class.schema_for_uri(schema_uri) if @options[:list] && @options[:fragment].nil? schema = schema.to_array_schema - schema.uri = Addressable::URI.parse(fake_uuid(serialize(schema.schema))) + schema.uri = JSON::Util::URI.parse(fake_uuid(serialize(schema.schema))) Validator.add_schema(schema) end schema end end elsif schema.is_a?(Hash) - schema_uri = Addressable::URI.parse(fake_uuid(serialize(schema))) + schema_uri = JSON::Util::URI.parse(fake_uuid(serialize(schema))) schema = JSON::Schema.stringify(schema) schema = JSON::Schema.new(schema, schema_uri, @options[:version]) if @options[:list] && @options[:fragment].nil? @@ -482,7 +485,7 @@ def initialize_schema(schema) end Validator.add_schema(schema) else - raise "Invalid schema - must be either a string or a hash" + raise JSON::Schema::SchemaParseError, "Invalid schema - must be either a string or a hash" end schema @@ -503,7 +506,7 @@ def initialize_data(data) json_uri = Util::URI.normalized_uri(data) data = JSON::Validator.parse(custom_open(json_uri)) rescue - # Silently discard the error - the data will not change + # Silently discard the error - use the data as-is end end end @@ -513,10 +516,18 @@ def initialize_data(data) def custom_open(uri) uri = Util::URI.normalized_uri(uri) if uri.is_a?(String) - if uri.absolute? && uri.scheme != 'file' - open(uri.to_s).read + if uri.absolute? && Util::URI::SUPPORTED_PROTOCOLS.include?(uri.scheme) + begin + open(uri.to_s).read + rescue OpenURI::HTTPError, Timeout::Error => e + raise JSON::Schema::JsonLoadError, e.message + end else - File.read(Addressable::URI.unescape(uri.path)) + begin + File.read(JSON::Util::URI.unescaped_path(uri)) + rescue SystemCallError => e + raise JSON::Schema::JsonLoadError, e.message + end end end end diff --git a/lib/json-schema/validators/draft1.rb b/lib/json-schema/validators/draft1.rb index 25910a77..aca51efa 100644 --- a/lib/json-schema/validators/draft1.rb +++ b/lib/json-schema/validators/draft1.rb @@ -2,7 +2,7 @@ module JSON class Schema - + class Draft1 < Validator def initialize super @@ -33,13 +33,13 @@ def initialize 'uri' => UriFormat } @formats = @default_formats.clone - @uri = Addressable::URI.parse("http://json-schema.org/draft-01/schema#") + @uri = JSON::Util::URI.parse("http://json-schema.org/draft-01/schema#") @names = ["draft1"] @metaschema_name = "draft-01.json" end - + JSON::Validator.register_validator(self.new) end - + end end diff --git a/lib/json-schema/validators/draft2.rb b/lib/json-schema/validators/draft2.rb index 4f12ae7a..82825d57 100644 --- a/lib/json-schema/validators/draft2.rb +++ b/lib/json-schema/validators/draft2.rb @@ -2,7 +2,7 @@ module JSON class Schema - + class Draft2 < Validator def initialize super @@ -34,13 +34,13 @@ def initialize 'uri' => UriFormat } @formats = @default_formats.clone - @uri = Addressable::URI.parse("http://json-schema.org/draft-02/schema#") + @uri = JSON::Util::URI.parse("http://json-schema.org/draft-02/schema#") @names = ["draft2"] @metaschema_name = "draft-02.json" end - + JSON::Validator.register_validator(self.new) end - + end end diff --git a/lib/json-schema/validators/draft3.rb b/lib/json-schema/validators/draft3.rb index 572cbabc..6d0784ea 100644 --- a/lib/json-schema/validators/draft3.rb +++ b/lib/json-schema/validators/draft3.rb @@ -2,7 +2,7 @@ module JSON class Schema - + class Draft3 < Validator def initialize super @@ -38,13 +38,13 @@ def initialize 'uri' => UriFormat } @formats = @default_formats.clone - @uri = Addressable::URI.parse("http://json-schema.org/draft-03/schema#") + @uri = JSON::Util::URI.parse("http://json-schema.org/draft-03/schema#") @names = ["draft3", "http://json-schema.org/draft-03/schema#"] @metaschema_name = "draft-03.json" end - + JSON::Validator.register_validator(self.new) end - + end end diff --git a/lib/json-schema/validators/draft4.rb b/lib/json-schema/validators/draft4.rb index dac27f2c..904cdf37 100644 --- a/lib/json-schema/validators/draft4.rb +++ b/lib/json-schema/validators/draft4.rb @@ -43,7 +43,7 @@ def initialize 'uri' => UriFormat } @formats = @default_formats.clone - @uri = Addressable::URI.parse("http://json-schema.org/draft-04/schema#") + @uri = JSON::Util::URI.parse("http://json-schema.org/draft-04/schema#") @names = ["draft4", "http://json-schema.org/draft-04/schema#"] @metaschema_name = "draft-04.json" end diff --git a/lib/json-schema/validators/hyper-draft1.rb b/lib/json-schema/validators/hyper-draft1.rb index 2eabe9a4..6b26daa8 100644 --- a/lib/json-schema/validators/hyper-draft1.rb +++ b/lib/json-schema/validators/hyper-draft1.rb @@ -4,7 +4,7 @@ class Schema class HyperDraft1 < Draft1 def initialize super - @uri = Addressable::URI.parse("http://json-schema.org/draft-01/hyper-schema#") + @uri = JSON::Util::URI.parse("http://json-schema.org/draft-01/hyper-schema#") end JSON::Validator.register_validator(self.new) diff --git a/lib/json-schema/validators/hyper-draft2.rb b/lib/json-schema/validators/hyper-draft2.rb index be02bc7d..86b5ed81 100644 --- a/lib/json-schema/validators/hyper-draft2.rb +++ b/lib/json-schema/validators/hyper-draft2.rb @@ -4,7 +4,7 @@ class Schema class HyperDraft2 < Draft2 def initialize super - @uri = Addressable::URI.parse("http://json-schema.org/draft-02/hyper-schema#") + @uri = JSON::Util::URI.parse("http://json-schema.org/draft-02/hyper-schema#") end JSON::Validator.register_validator(self.new) diff --git a/lib/json-schema/validators/hyper-draft4.rb b/lib/json-schema/validators/hyper-draft4.rb index d39b183f..fdc10f3a 100644 --- a/lib/json-schema/validators/hyper-draft4.rb +++ b/lib/json-schema/validators/hyper-draft4.rb @@ -4,7 +4,7 @@ class Schema class HyperDraft4 < Draft4 def initialize super - @uri = Addressable::URI.parse("http://json-schema.org/draft-04/hyper-schema#") + @uri = JSON::Util::URI.parse("http://json-schema.org/draft-04/hyper-schema#") end JSON::Validator.register_validator(self.new) diff --git a/test/test_all_of_ref_schema.rb b/test/all_of_ref_schema_test.rb similarity index 94% rename from test/test_all_of_ref_schema.rb rename to test/all_of_ref_schema_test.rb index 8ab6bf0d..c178333b 100644 --- a/test/test_all_of_ref_schema.rb +++ b/test/all_of_ref_schema_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) class AllOfRefSchemaTest < Minitest::Test def schema diff --git a/test/test_any_of_ref_schema.rb b/test/any_of_ref_schema_test.rb similarity index 96% rename from test/test_any_of_ref_schema.rb rename to test/any_of_ref_schema_test.rb index 475222de..5e5ae734 100644 --- a/test/test_any_of_ref_schema.rb +++ b/test/any_of_ref_schema_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) class AnyOfRefSchemaTest < Minitest::Test def schema diff --git a/test/test_bad_schema_ref.rb b/test/bad_schema_ref_test.rb similarity index 93% rename from test/test_bad_schema_ref.rb rename to test/bad_schema_ref_test.rb index ef4d9761..0ab67f63 100644 --- a/test/test_bad_schema_ref.rb +++ b/test/bad_schema_ref_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) require 'socket' diff --git a/test/test_common_test_suite.rb b/test/common_test_suite_test.rb similarity index 97% rename from test/test_common_test_suite.rb rename to test/common_test_suite_test.rb index 3c9d3aff..1597b91d 100644 --- a/test/test_common_test_suite.rb +++ b/test/common_test_suite_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) require 'json' class CommonTestSuiteTest < Minitest::Test diff --git a/test/test_custom_format.rb b/test/custom_format_test.rb similarity index 87% rename from test/test_custom_format.rb rename to test/custom_format_test.rb index e7ab3cc6..084d4914 100644 --- a/test/test_custom_format.rb +++ b/test/custom_format_test.rb @@ -1,9 +1,9 @@ # encoding: utf-8 -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) -class JSONSchemaCustomFormatTest < Minitest::Test +class CustomFormatTest < Minitest::Test def setup - @all_versions = ['draft1', 'draft2', 'draft3', 'draft4'] + @all_versions = ['draft1', 'draft2', 'draft3', 'draft4', nil] @format_proc = lambda { |value| raise JSON::Schema::CustomFormatError.new("must be 42") unless value == "42" } @schema_4 = { "$schema" => "http://json-schema.org/draft-04/schema#", @@ -20,36 +20,39 @@ def setup @schema_2["$schema"] = "http://json-schema.org/draft-02/schema#" @schema_1 = @schema_4.clone @schema_1["$schema"] = "http://json-schema.org/draft-01/schema#" + @default = @schema_4.clone + @default.delete("$schema") @schemas = { "draft1" => @schema_1, "draft2" => @schema_2, "draft3" => @schema_3, - "draft4" => @schema_4 + "draft4" => @schema_4, + nil => @default, } JSON::Validator.restore_default_formats end def test_single_registration @all_versions.each do |version| - assert(JSON::Validator.validator_for_name(version).formats['custom'].nil?, "Format 'custom' for #{version} should be nil") + assert(JSON::Validator.validator_for_name(version).formats['custom'].nil?, "Format 'custom' for #{version || 'default'} should be nil") JSON::Validator.register_format_validator("custom", @format_proc, [version]) - assert(JSON::Validator.validator_for_name(version).formats['custom'].is_a?(JSON::Schema::CustomFormat), "Format 'custom' should be registered for #{version}") + assert(JSON::Validator.validator_for_name(version).formats['custom'].is_a?(JSON::Schema::CustomFormat), "Format 'custom' should be registered for #{version || 'default'}") (@all_versions - [version]).each do |other_version| - assert(JSON::Validator.validator_for_name(other_version).formats['custom'].nil?, "Format 'custom' should still be nil for #{other_version}") + assert(JSON::Validator.validator_for_name(other_version).formats['custom'].nil?, "Format 'custom' should still be nil for #{other_version || 'default'}") end JSON::Validator.deregister_format_validator("custom", [version]) - assert(JSON::Validator.validator_for_name(version).formats['custom'].nil?, "Format 'custom' should be deregistered for #{version}") + assert(JSON::Validator.validator_for_name(version).formats['custom'].nil?, "Format 'custom' should be deregistered for #{version || 'default'}") end end def test_register_for_all_by_default JSON::Validator.register_format_validator("custom", @format_proc) @all_versions.each do |version| - assert(JSON::Validator.validator_for_name(version).formats['custom'].is_a?(JSON::Schema::CustomFormat), "Format 'custom' should be registered for #{version}") + assert(JSON::Validator.validator_for_name(version).formats['custom'].is_a?(JSON::Schema::CustomFormat), "Format 'custom' should be registered for #{version || 'default'}") end JSON::Validator.restore_default_formats @all_versions.each do |version| - assert(JSON::Validator.validator_for_name(version).formats['custom'].nil?, "Format 'custom' should still be nil for #{version}") + assert(JSON::Validator.validator_for_name(version).formats['custom'].nil?, "Format 'custom' should still be nil for #{version || 'default'}") end end @@ -57,7 +60,7 @@ def test_multi_registration unregistered_version = @all_versions.delete("draft1") JSON::Validator.register_format_validator("custom", @format_proc, @all_versions) @all_versions.each do |version| - assert(JSON::Validator.validator_for_name(version).formats['custom'].is_a?(JSON::Schema::CustomFormat), "Format 'custom' should be registered for #{version}") + assert(JSON::Validator.validator_for_name(version).formats['custom'].is_a?(JSON::Schema::CustomFormat), "Format 'custom' should be registered for #{version || 'default'}") end assert(JSON::Validator.validator_for_name(unregistered_version).formats['custom'].nil?, "Format 'custom' should still be nil for #{unregistered_version}") end @@ -68,7 +71,7 @@ def test_format_validation "a" => "23" } schema = @schemas[version] - prefix = "Validation for '#{version}'" + prefix = "Validation for '#{version || 'default'}'" assert(JSON::Validator.validate(schema, data), "#{prefix} succeeds with no 'custom' format validator registered") @@ -95,7 +98,7 @@ def test_override_default_format } schema = @schemas[version] schema["properties"]["a"]["format"] = "ipv6" - prefix = "Validation for '#{version}'" + prefix = "Validation for '#{version || 'default'}'" assert(JSON::Validator.validate(schema, data), "#{prefix} succeeds for default format with correct data") @@ -112,5 +115,3 @@ def test_override_default_format end end end - - diff --git a/test/test_jsonschema_draft1.rb b/test/draft1_test.rb similarity index 96% rename from test/test_jsonschema_draft1.rb rename to test/draft1_test.rb index 06c8298e..b1b3f9d1 100644 --- a/test/test_jsonschema_draft1.rb +++ b/test/draft1_test.rb @@ -1,6 +1,6 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) -class JSONSchemaDraft1Test < Minitest::Test +class Draft1Test < Minitest::Test def schema_version :draft1 end @@ -138,4 +138,3 @@ def test_format_datetime end end - diff --git a/test/test_jsonschema_draft2.rb b/test/draft2_test.rb similarity index 96% rename from test/test_jsonschema_draft2.rb rename to test/draft2_test.rb index 6b4c25c2..5a2398a3 100644 --- a/test/test_jsonschema_draft2.rb +++ b/test/draft2_test.rb @@ -1,6 +1,6 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) -class JSONSchemaDraft2Test < Minitest::Test +class Draft2Test < Minitest::Test def schema_version :draft2 end @@ -110,4 +110,3 @@ def test_format_datetime end end - diff --git a/test/test_jsonschema_draft3.rb b/test/draft3_test.rb similarity index 99% rename from test/test_jsonschema_draft3.rb rename to test/draft3_test.rb index 1c9cae7b..69063110 100644 --- a/test/test_jsonschema_draft3.rb +++ b/test/draft3_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) -class JSONSchemaDraft3Test < Minitest::Test +class Draft3Test < Minitest::Test def schema_version :draft3 end @@ -447,4 +447,3 @@ def test_default end - diff --git a/test/test_jsonschema_draft4.rb b/test/draft4_test.rb similarity index 99% rename from test/test_jsonschema_draft4.rb rename to test/draft4_test.rb index 8ddb7e10..e9474cd4 100644 --- a/test/test_jsonschema_draft4.rb +++ b/test/draft4_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) -class JSONSchemaDraft4Test < Minitest::Test +class Draft4Test < Minitest::Test def schema_version :draft4 end @@ -671,5 +671,3 @@ def test_definitions refute_valid schema, data end end - - diff --git a/test/extended_schema_test.rb b/test/extended_schema_test.rb new file mode 100644 index 00000000..5d9ed88b --- /dev/null +++ b/test/extended_schema_test.rb @@ -0,0 +1,62 @@ +require File.expand_path('../support/test_helper', __FILE__) + +class ExtendedSchemaTest < Minitest::Test + class BitwiseAndAttribute < JSON::Schema::Attribute + def self.validate(current_schema, data, fragments, processor, validator, options = {}) + return unless data.is_a?(Integer) + + if data & current_schema.schema['bitwise-and'].to_i == 0 + message = "The property '#{build_fragment(fragments)}' did not evaluate to true when bitwise-AND'd with #{current_schema.schema['bitwise-and']}" + validation_error(processor, message, fragments, current_schema, self, options[:record_errors]) + end + end + end + + class ExtendedSchema < JSON::Schema::Validator + def initialize + super + extend_schema_definition("http://json-schema.org/draft-03/schema#") + @attributes["bitwise-and"] = BitwiseAndAttribute + @uri = Addressable::URI.parse("http://test.com/test.json") + end + + JSON::Validator.register_validator(self.new) + end + + def test_extended_schema_validation + schema = { + "$schema" => "http://test.com/test.json", + "properties" => { + "a" => { + "bitwise-and" => 1 + }, + "b" => { + "type" => "string" + } + } + } + + assert_valid schema, {"a" => 1, "b" => "taco"} + refute_valid schema, {"a" => 0, "b" => "taco"} + refute_valid schema, {"a" => 1, "b" => 5} + end + + def test_unextended_schema + # Verify that using the original schema disregards the `bitwise-and` property + schema = { + "$schema" => "http://json-schema.org/draft-03/schema#", + "properties" => { + "a" => { + "bitwise-and" => 1 + }, + "b" => { + "type" => "string" + } + } + } + + assert_valid schema, {"a" => 0, "b" => "taco"} + assert_valid schema, {"a" => 1, "b" => "taco"} + refute_valid schema, {"a" => 1, "b" => 5} + end +end diff --git a/test/test_extends_and_additionalProperties.rb b/test/extends_nested_test.rb similarity index 96% rename from test/test_extends_and_additionalProperties.rb rename to test/extends_nested_test.rb index 9f6b40db..2e409e04 100644 --- a/test/test_extends_and_additionalProperties.rb +++ b/test/extends_nested_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) class ExtendsNestedTest < Minitest::Test diff --git a/test/test_files_v3.rb b/test/files_test.rb similarity index 64% rename from test/test_files_v3.rb rename to test/files_test.rb index 8d664135..505d0322 100644 --- a/test/test_files_v3.rb +++ b/test/files_test.rb @@ -1,24 +1,36 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) -class JSONSchemaTest < Minitest::Test +class FilesTest < Minitest::Test def test_schema_from_file assert_valid schema_fixture_path('good_schema_1.json'), { "a" => 5 } refute_valid schema_fixture_path('good_schema_1.json'), { "a" => "bad" } end - def test_data_from_file + def test_data_from_file_v3 schema = {"$schema" => "http://json-schema.org/draft-03/schema#","type" => "object", "properties" => {"a" => {"type" => "integer"}}} assert_valid schema, data_fixture_path('good_data_1.json'), :uri => true refute_valid schema, data_fixture_path('bad_data_1.json'), :uri => true end - def test_data_from_json + def test_data_from_json_v3 schema = {"$schema" => "http://json-schema.org/draft-03/schema#","type" => "object", "properties" => {"a" => {"type" => "integer"}}} assert_valid schema, %Q({"a": 5}), :json => true refute_valid schema, %Q({"a": "poop"}), :json => true end + def test_data_from_file_v4 + schema = {"$schema" => "http://json-schema.org/draft-04/schema#","type" => "object", "properties" => {"a" => {"type" => "integer"}}} + assert_valid schema, data_fixture_path('good_data_1.json'), :uri => true + refute_valid schema, data_fixture_path('bad_data_1.json'), :uri => true + end + + def test_data_from_json_v4 + schema = {"$schema" => "http://json-schema.org/draft-04/schema#","type" => "object", "properties" => {"a" => {"type" => "integer"}}} + assert_valid schema, %Q({"a": 5}), :json => true + refute_valid schema, %Q({"a": "poop"}), :json => true + end + def test_both_from_file assert_valid schema_fixture_path('good_schema_1.json'), data_fixture_path('good_data_1.json'), :uri => true refute_valid schema_fixture_path('good_schema_1.json'), data_fixture_path('bad_data_1.json'), :uri => true diff --git a/test/test_fragment_resolution.rb b/test/fragment_resolution_test.rb similarity index 86% rename from test/test_fragment_resolution.rb rename to test/fragment_resolution_test.rb index 95985c40..36e9aee2 100644 --- a/test/test_fragment_resolution.rb +++ b/test/fragment_resolution_test.rb @@ -1,6 +1,6 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) -class FragmentResolution < Minitest::Test +class FragmentResolutionTest < Minitest::Test def test_fragment_resolution schema = { "$schema" => "http://json-schema.org/draft-04/schema#", diff --git a/test/test_fragment_validation_with_ref.rb b/test/fragment_validation_with_ref_test.rb similarity index 86% rename from test/test_fragment_validation_with_ref.rb rename to test/fragment_validation_with_ref_test.rb index 459c541c..dbcbd882 100644 --- a/test/test_fragment_validation_with_ref.rb +++ b/test/fragment_validation_with_ref_test.rb @@ -1,6 +1,6 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) -class FragmentValidationWithRef < Minitest::Test +class FragmentValidationWithRefTest < Minitest::Test def whole_schema { "$schema" => "http://json-schema.org/draft-04/schema#", diff --git a/test/test_full_validation.rb b/test/full_validation_test.rb similarity index 96% rename from test/test_full_validation.rb rename to test/full_validation_test.rb index 4fe470a5..0ad8ff6b 100644 --- a/test/test_full_validation.rb +++ b/test/full_validation_test.rb @@ -1,7 +1,7 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) + +class FullValidationTest < Minitest::Test -class JSONFullValidation < Minitest::Test - def test_full_validation data = {"b" => {"a" => 5}} schema = { @@ -13,7 +13,7 @@ def test_full_validation } } } - + errors = JSON::Validator.fully_validate(schema,data) assert(errors.empty?) @@ -34,7 +34,7 @@ def test_full_validation errors = JSON::Validator.fully_validate(schema,data) assert(errors.length == 2) end - + def test_full_validation_with_union_types data = {"b" => 5} schema = { @@ -46,10 +46,10 @@ def test_full_validation_with_union_types } } } - + errors = JSON::Validator.fully_validate(schema,data) assert(errors.empty?) - + schema = { "$schema" => "http://json-schema.org/draft-03/schema#", "type" => "object", @@ -59,15 +59,15 @@ def test_full_validation_with_union_types } } } - + errors = JSON::Validator.fully_validate(schema,data) assert(errors.empty?) - + data = {"b" => "a string"} - + errors = JSON::Validator.fully_validate(schema,data) assert(errors.length == 1) - + schema = { "$schema" => "http://json-schema.org/draft-03/schema#", "type" => "object", @@ -90,24 +90,24 @@ def test_full_validation_with_union_types } } } - + data = {"b" => {"c" => "taco"}} - + errors = JSON::Validator.fully_validate(schema,data) assert(errors.empty?) - + data = {"b" => {"d" => 6}} - + errors = JSON::Validator.fully_validate(schema,data) assert(errors.empty?) - + data = {"b" => {"c" => 6, "d" => "OH GOD"}} - + errors = JSON::Validator.fully_validate(schema,data) assert(errors.length == 1) end - - + + def test_full_validation_with_object_errors data = {"b" => {"a" => 5}} schema = { @@ -119,7 +119,7 @@ def test_full_validation_with_object_errors } } } - + errors = JSON::Validator.fully_validate(schema,data,:errors_as_objects => true) assert(errors.empty?) @@ -144,7 +144,7 @@ def test_full_validation_with_object_errors assert(errors[1][:failed_attribute] == "Type") assert(errors[1][:fragment] == "#/c") end - + def test_full_validation_with_nested_required_properties schema = { "$schema" => "http://json-schema.org/draft-03/schema#", @@ -164,7 +164,7 @@ def test_full_validation_with_nested_required_properties } } data = {"x" => {"a"=>5, "d"=>5, "e"=>"what?"}} - + errors = JSON::Validator.fully_validate(schema,data,:errors_as_objects => true) assert_equal 2, errors.length assert_equal '#/x', errors[0][:fragment] @@ -172,7 +172,7 @@ def test_full_validation_with_nested_required_properties assert_equal '#/x/e', errors[1][:fragment] assert_equal 'Type', errors[1][:failed_attribute] end - + def test_full_validation_with_nested_required_propertiesin_array schema = { "$schema" => "http://json-schema.org/draft-03/schema#", @@ -197,7 +197,7 @@ def test_full_validation_with_nested_required_propertiesin_array missing_b= {"a"=>5} e_is_wrong_type= {"a"=>5,"b"=>5,"e"=>"what?"} data = {"x" => [missing_b, e_is_wrong_type]} - + errors = JSON::Validator.fully_validate(schema,data,:errors_as_objects => true) assert_equal 2, errors.length assert_equal '#/x/0', errors[0][:fragment] diff --git a/test/test_initialize_data.rb b/test/initialize_data_test.rb similarity index 79% rename from test/test_initialize_data.rb rename to test/initialize_data_test.rb index ca93efdd..5529cc04 100644 --- a/test/test_initialize_data.rb +++ b/test/initialize_data_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) class InitializeDataTest < Minitest::Test @@ -14,7 +14,7 @@ def test_parse_character_string JSON::Validator.validate(schema, data, :json => true) end - assert_raises(Errno::ENOENT) { JSON::Validator.validate(schema, data, :uri => true) } + assert_raises(JSON::Schema::JsonLoadError) { JSON::Validator.validate(schema, data, :uri => true) } end def test_parse_integer_string @@ -27,7 +27,7 @@ def test_parse_integer_string assert(JSON::Validator.validate(schema, data, :json => true)) - assert_raises(Errno::ENOENT) { JSON::Validator.validate(schema, data, :uri => true) } + assert_raises(JSON::Schema::JsonLoadError) { JSON::Validator.validate(schema, data, :uri => true) } end def test_parse_hash_string @@ -40,7 +40,7 @@ def test_parse_hash_string assert(JSON::Validator.validate(schema, data, :json => true)) - assert_raises(Errno::ENOENT) { JSON::Validator.validate(schema, data, :uri => true) } + assert_raises(JSON::Schema::JsonLoadError) { JSON::Validator.validate(schema, data, :uri => true) } end def test_parse_json_string @@ -53,7 +53,7 @@ def test_parse_json_string assert(JSON::Validator.validate(schema, data, :json => true)) - assert_raises(Errno::ENOENT) { JSON::Validator.validate(schema, data, :uri => true) } + assert_raises(JSON::Schema::JsonLoadError) { JSON::Validator.validate(schema, data, :uri => true) } end def test_parse_valid_uri_string @@ -83,11 +83,17 @@ def test_parse_invalid_uri_string assert(JSON::Validator.validate(schema, data, :parse_data => false)) + stub_request(:get, "foo.bar").to_return(:status => [500, "Internal Server Error"]) + + assert(JSON::Validator.validate(schema, data)) + + assert(JSON::Validator.validate(schema, data, :parse_data => false)) + assert_raises(JSON::ParserError) do JSON::Validator.validate(schema, data, :json => true) end - assert_raises(Timeout::Error) { JSON::Validator.validate(schema, data, :uri => true) } + assert_raises(JSON::Schema::JsonLoadError) { JSON::Validator.validate(schema, data, :uri => true) } end def test_parse_integer diff --git a/test/test_list_option.rb b/test/list_option_test.rb similarity index 89% rename from test/test_list_option.rb rename to test/list_option_test.rb index 2360cf53..afe3377b 100644 --- a/test/test_list_option.rb +++ b/test/list_option_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) class ListOptionTest < Minitest::Test def test_list_option_reusing_schemas diff --git a/test/test_load_ref_schema.rb b/test/load_ref_schema_test.rb similarity index 69% rename from test/test_load_ref_schema.rb rename to test/load_ref_schema_test.rb index f37942dc..2a3d1927 100644 --- a/test/test_load_ref_schema.rb +++ b/test/load_ref_schema_test.rb @@ -1,6 +1,6 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) -class LoadRefSchemaTests < Minitest::Test +class LoadRefSchemaTest < Minitest::Test def load_other_schema JSON::Validator.add_schema(JSON::Schema.new( { @@ -18,23 +18,21 @@ def load_other_schema def test_cached_schema schema_url = "http://example.com/schema#" - schema = { - "$ref" => schema_url - } + schema = { "$ref" => schema_url } data = {} load_other_schema - validator = JSON::Validator.new(schema, data) + _validator = JSON::Validator.new(schema, data) + assert JSON::Validator.schema_loaded?(schema_url) end def test_cached_schema_with_fragment schema_url = "http://example.com/schema#" - schema = { - "$ref" => "#{schema_url}/properties/title" - } + schema = { "$ref" => "#{schema_url}/properties/title" } data = {} load_other_schema - validator = JSON::Validator.new(schema, data) + _validator = JSON::Validator.new(schema, data) + assert JSON::Validator.schema_loaded?(schema_url) end end diff --git a/test/test_merge_missing_values.rb b/test/merge_missing_values_test.rb similarity index 96% rename from test/test_merge_missing_values.rb rename to test/merge_missing_values_test.rb index a375a89e..c5c8a216 100644 --- a/test/test_merge_missing_values.rb +++ b/test/merge_missing_values_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) class MergeMissingValuesTest < Minitest::Test def test_merge_missing_values_for_string diff --git a/test/test_minitems.rb b/test/min_items_test.rb similarity index 85% rename from test/test_minitems.rb rename to test/min_items_test.rb index d17d2242..0824ea43 100644 --- a/test/test_minitems.rb +++ b/test/min_items_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) class MinItemsTest < Minitest::Test def test_minitems_nils diff --git a/test/test_one_of.rb b/test/one_of_test.rb similarity index 97% rename from test/test_one_of.rb rename to test/one_of_test.rb index f26e924f..e4464941 100644 --- a/test/test_one_of.rb +++ b/test/one_of_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) class OneOfTest < Minitest::Test def test_one_of_links_schema diff --git a/test/test_definition.rb b/test/relative_definition_test.rb similarity index 61% rename from test/test_definition.rb rename to test/relative_definition_test.rb index 42541204..6d13e8c2 100644 --- a/test/test_definition.rb +++ b/test/relative_definition_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) class RelativeDefinitionTest < Minitest::Test @@ -6,6 +6,10 @@ def test_definition_schema assert_valid schema_fixture_path('definition_schema.json'), {"a" => 5} end + def test_definition_schema_with_special_characters + assert_valid schema_fixture_path('definition_schema_with_special_characters.json'), {"a" => 5} + end + def test_relative_definition schema = schema_fixture_path('relative_definition_schema.json') assert_valid schema, {"a" => 5} diff --git a/test/test_ruby_schema.rb b/test/ruby_schema_test.rb similarity index 83% rename from test/test_ruby_schema.rb rename to test/ruby_schema_test.rb index 7693b336..fba786cb 100644 --- a/test/test_ruby_schema.rb +++ b/test/ruby_schema_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) class RubySchemaTest < Minitest::Test def test_string_keys @@ -56,4 +56,10 @@ def test_symbol_keys_in_hash_within_array assert_valid schema, data, :validate_schema => true end + + def test_schema_of_unrecognized_type + assert_raises JSON::Schema::SchemaParseError do + JSON::Validator.validate(Object.new, {}) + end + end end diff --git a/test/test_schema_loader.rb b/test/schema_reader_test.rb similarity index 95% rename from test/test_schema_loader.rb rename to test/schema_reader_test.rb index 38209516..005f7b1e 100644 --- a/test/test_schema_loader.rb +++ b/test/schema_reader_test.rb @@ -1,6 +1,6 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) -class TestSchemaReader < Minitest::Test +class SchemaReaderTest < Minitest::Test ADDRESS_SCHEMA_URI = 'http://json-schema.org/address' ADDRESS_SCHEMA_PATH = File.expand_path('../schemas/address_microformat.json', __FILE__) diff --git a/test/test_schema_validation.rb b/test/schema_validation_test.rb similarity index 97% rename from test/test_schema_validation.rb rename to test/schema_validation_test.rb index 940715e4..160ec71c 100644 --- a/test/test_schema_validation.rb +++ b/test/schema_validation_test.rb @@ -1,7 +1,7 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) require 'tmpdir' -class JSONSchemaValidation < Minitest::Test +class SchemaValidationTest < Minitest::Test def valid_schema_v3 { "$schema" => "http://json-schema.org/draft-03/schema#", diff --git a/test/schemas/definition_schema_with_special_characters.json b/test/schemas/definition_schema_with_special_characters.json new file mode 100644 index 00000000..d6ac2338 --- /dev/null +++ b/test/schemas/definition_schema_with_special_characters.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "a": { + "$ref": "#/definitions/foo:bar" + } + }, + "definitions": { + "foo:bar": { + "type": "integer" + } + } +} diff --git a/test/schemas/relative_definition_schema.json b/test/schemas/relative_definition_schema.json index 240b35c8..441d66d6 100644 --- a/test/schemas/relative_definition_schema.json +++ b/test/schemas/relative_definition_schema.json @@ -5,4 +5,4 @@ "$ref": "definition_schema.json#/definitions/foo" } } -} \ No newline at end of file +} diff --git a/test/test_stringify.rb b/test/stringify_test.rb similarity index 95% rename from test/test_stringify.rb rename to test/stringify_test.rb index 99994373..09bbd579 100644 --- a/test/test_stringify.rb +++ b/test/stringify_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) class StringifyTest < Minitest::Test def test_stringify_on_hash diff --git a/test/test_helper.rb b/test/support/test_helper.rb similarity index 73% rename from test/test_helper.rb rename to test/support/test_helper.rb index 14510863..6bf315fb 100644 --- a/test/test_helper.rb +++ b/test/support/test_helper.rb @@ -1,20 +1,20 @@ require 'minitest/autorun' require 'webmock/minitest' -$:.unshift(File.expand_path('../../lib', __FILE__)) +$LOAD_PATH.unshift(File.expand_path('../../../lib', __FILE__)) require 'json-schema' -Dir[File.join(File.expand_path('../support', __FILE__), '*.rb')].each do |support_file| - require support_file +Dir[File.join(File.expand_path('../', __FILE__), '*.rb')].each do |support_file| + require support_file unless support_file == __FILE__ end class Minitest::Test def schema_fixture_path(filename) - File.join(File.dirname(__FILE__), 'schemas', filename) + File.join(File.dirname(__FILE__), '../schemas', filename) end def data_fixture_path(filename) - File.join(File.dirname(__FILE__), 'data', filename) + File.join(File.dirname(__FILE__), '../data', filename) end def assert_valid(schema, data, options = {}) diff --git a/test/test-suite b/test/test-suite index 8f867168..9355965c 160000 --- a/test/test-suite +++ b/test/test-suite @@ -1 +1 @@ -Subproject commit 8f867168d17497a775141704960906db83634a04 +Subproject commit 9355965c517b91ec586aaaf301cc619d0e3001d2 diff --git a/test/test_extended_schema.rb b/test/test_extended_schema.rb deleted file mode 100644 index b7023860..00000000 --- a/test/test_extended_schema.rb +++ /dev/null @@ -1,62 +0,0 @@ -require File.expand_path('../test_helper', __FILE__) - -class BitwiseAndAttribute < JSON::Schema::Attribute - def self.validate(current_schema, data, fragments, processor, validator, options = {}) - return unless data.is_a?(Integer) - - if data & current_schema.schema['bitwise-and'].to_i == 0 - message = "The property '#{build_fragment(fragments)}' did not evaluate to true when bitwise-AND'd with #{current_schema.schema['bitwise-and']}" - validation_error(processor, message, fragments, current_schema, self, options[:record_errors]) - end - end -end - -class ExtendedSchema < JSON::Schema::Validator - def initialize - super - extend_schema_definition("http://json-schema.org/draft-03/schema#") - @attributes["bitwise-and"] = BitwiseAndAttribute - @uri = Addressable::URI.parse("http://test.com/test.json") - end - - JSON::Validator.register_validator(self.new) -end - -class TestExtendedSchema < Minitest::Test - def test_extended_schema_validation - schema = { - "$schema" => "http://test.com/test.json", - "properties" => { - "a" => { - "bitwise-and" => 1 - }, - "b" => { - "type" => "string" - } - } - } - - assert_valid schema, {"a" => 1, "b" => "taco"} - refute_valid schema, {"a" => 0, "b" => "taco"} - refute_valid schema, {"a" => 1, "b" => 5} - end - - def test_unextended_schema - # Verify that using the original schema disregards the `bitwise-and` property - schema = { - "$schema" => "http://json-schema.org/draft-03/schema#", - "properties" => { - "a" => { - "bitwise-and" => 1 - }, - "b" => { - "type" => "string" - } - } - } - - assert_valid schema, {"a" => 0, "b" => "taco"} - assert_valid schema, {"a" => 1, "b" => "taco"} - refute_valid schema, {"a" => 1, "b" => 5} - end -end diff --git a/test/test_schema_type_attribute.rb b/test/type_attribute_test.rb similarity index 83% rename from test/test_schema_type_attribute.rb rename to test/type_attribute_test.rb index 5b446e94..81da466f 100644 --- a/test/test_schema_type_attribute.rb +++ b/test/type_attribute_test.rb @@ -1,6 +1,6 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) -class TestSchemaTypeAttribute < Minitest::Test +class TypeAttributeTest < Minitest::Test def test_type_of_data assert_equal(type_of_data(String.new), 'string') assert_equal(type_of_data(Numeric.new), 'number') diff --git a/test/test_uri_related.rb b/test/uri_parsing_test.rb similarity index 94% rename from test/test_uri_related.rb rename to test/uri_parsing_test.rb index 70069ba5..cecd8f97 100644 --- a/test/test_uri_related.rb +++ b/test/uri_parsing_test.rb @@ -1,7 +1,7 @@ # coding: utf-8 -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) -class UriRelatedTest < Minitest::Test +class UriParsingTest < Minitest::Test def test_asian_characters schema = { "$schema"=> "http://json-schema.org/draft-04/schema#", diff --git a/test/test_validator.rb b/test/validator_schema_reader_test.rb similarity index 92% rename from test/test_validator.rb rename to test/validator_schema_reader_test.rb index eb65de61..d1e4f67b 100644 --- a/test/test_validator.rb +++ b/test/validator_schema_reader_test.rb @@ -1,6 +1,6 @@ -require File.expand_path('../test_helper', __FILE__) +require File.expand_path('../support/test_helper', __FILE__) -class TestValidator < Minitest::Test +class ValidatorSchemaReaderTest < Minitest::Test class MockReader def read(location)