diff --git a/json_matchers.gemspec b/json_matchers.gemspec index 47662f3..521e6e1 100644 --- a/json_matchers.gemspec +++ b/json_matchers.gemspec @@ -18,7 +18,8 @@ Gem::Specification.new do |spec| spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] - spec.add_dependency("json-schema", "~> 2.7") + spec.add_dependency("json_schema") + spec.add_dependency("activesupport", '>= 3.0.0') spec.add_development_dependency "bundler", "~> 1.7" spec.add_development_dependency "pry" diff --git a/lib/json_matchers/matcher.rb b/lib/json_matchers/matcher.rb index b2792a4..f16350f 100644 --- a/lib/json_matchers/matcher.rb +++ b/lib/json_matchers/matcher.rb @@ -1,5 +1,4 @@ -require "json-schema" -require "json_matchers/validator" +require "json_schema" module JsonMatchers class Matcher @@ -9,16 +8,22 @@ def initialize(schema_path, options = {}) end def matches?(payload) - validator = build_validator(payload) - - self.errors = validator.validate! - - errors.empty? - rescue JSON::Schema::ValidationError => error - self.errors = [error.message] - false - rescue JSON::Schema::JsonParseError - raise InvalidSchemaError + begin + add_schemata_to_document_store + schema_data = JSON.parse(File.read(@schema_path.to_s)) + response_body = JSON.parse(@response.body) + json_schema = JsonSchema.parse!(schema_data) + + json_schema.expand_references!(store: document_store) + json_schema.validate!(response_body) + rescue RuntimeError => ex + @validation_failure_message = ex.message + return false + rescue JsonSchema::SchemaError, JSON::ParserError => ex + raise InvalidSchemaError + end + + true end def validation_failure_message @@ -28,10 +33,13 @@ def validation_failure_message private attr_reader :schema_path, :options - attr_accessor :errors - def default_options - JsonMatchers.configuration.options || {} + def add_schemata_to_document_store + Dir.glob("#{JsonMatchers.schema_root}/**/*.json").each do |path| + schema_data = JSON.parse(File.read(path)) + extra_schema = JsonSchema.parse!(schema_data) + document_store.add_schema(extra_schema) + end end def build_validator(payload) @@ -41,5 +49,9 @@ def build_validator(payload) schema_path: schema_path, ) end + + def document_store + @document_store ||= JsonSchema::DocumentStore.new + end end end diff --git a/spec/json_matchers/match_json_schema_spec.rb b/spec/json_matchers/match_json_schema_spec.rb index ebac209..18407b3 100644 --- a/spec/json_matchers/match_json_schema_spec.rb +++ b/spec/json_matchers/match_json_schema_spec.rb @@ -194,6 +194,67 @@ expect(json_as_array).not_to match_json_schema(schema) end + it "supports $ref" do + create_schema("user", { + "id": "file:/user.json#", + "type": "object", + "required": ["id"], + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "address": { "type": "string" }, + }, + }) + create_schema("users/index", { + "id": "file:/users/index.json#", + "type": "object", + "definitions": { + "users": { + "description": "A collection of users", + "example": [{ "id": "1" }], + "type": "array", + "items": { "$ref": "file:/user.json#" }, + }, + }, + "required": ["users"], + "properties": { "users": { "$ref": "#/definitions/users" } }, + }) + + valid_response = response_for({ + "users": [{ "id": 1, "name": "Me!", "address": "Here!" }], + }) + invalid_response = response_for({ + "users": [{ "id": "invalid", "name": "You!", "address": "There!" }], + }) + + expect(valid_response).to match_response_schema("users/index") + expect(invalid_response).not_to match_response_schema("users/index") + end + + it "supports the 'id' keyword" do + create_schema("top-level-schema", { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "a": { "$ref": "file:/#{JsonMatchers.schema_root}/nested.json#" }, + }, + }) + create_schema("nested-schema", { + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "file:/#{JsonMatchers.schema_root}/nested.json#", + "type": "object", + "required": ["b"], + "properties": { "b": { "type": "string" } }, + }) + response_json = { a: { b: "foo" } } + invalid_response_json = { a: { b: 4 } } + + expect(response_for(response_json)). + to match_response_schema("top-level-schema") + expect(response_for(invalid_response_json)). + not_to match_response_schema("top-level-schema") + end + context "when options are passed directly to the matcher" do it "forwards options to the validator" do schema = create(:schema, :object)