Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use json_schema instead of json-schema #31

Merged
merged 1 commit into from
Apr 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
master
======

* *Breaking Change* - remove support for configuring validation options.
* *BREAKING CHANGE* Implement the validation with the `json_schema` gem instead
of `json-schema`. [#31]
* *BREAKING CHANGE* - remove support for configuring validation options.

[#31]: https://github.com/thoughtbot/json_matchers/pull/31

0.9.0
=====
Expand Down
106 changes: 68 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Or install it yourself as:

Inspired by [Validating JSON Schemas with an RSpec Matcher][original-blog-post].

[original-blog-post]: (http://robots.thoughtbot.com/validating-json-schemas-with-an-rspec-matcher)
[original-blog-post]: (https://robots.thoughtbot.com/validating-json-schemas-with-an-rspec-matcher)

First, configure it in your test suite's helper file:

Expand Down Expand Up @@ -55,26 +55,27 @@ Minitest::Test.send(:include, JsonMatchers::Minitest::Assertions)

### Declare

Declare your [JSON Schema](http://json-schema.org/example1.html) in the schema
Declare your [JSON Schema](https://json-schema.org/example1.html) in the schema
directory.

`spec/support/api/schemas/posts.json` or `test/support/api/schemas/posts.json`:
`spec/support/api/schemas/location.json` or
`test/support/api/schemas/location.json`:

Define your [JSON Schema](https://json-schema.org/example1.html) in the schema
directory.

```json
{
"id": "https://json-schema.org/geo",
"$schema": "https://json-schema.org/draft-06/schema#",
"description": "A geographical coordinate",
"type": "object",
"required": ["posts"],
"properties": {
"posts": {
"type": "array",
"items":{
"required": ["id", "title", "body"],
"properties": {
"id": { "type": "integer" },
"title": { "type": "string" },
"body": { "type": "string" }
}
}
"latitude": {
"type": "number"
},
"longitude": {
"type": "number"
}
}
}
Expand All @@ -84,18 +85,20 @@ directory.

#### RSpec

```ruby

Validate a JSON response, a Hash, or a String against a JSON Schema with
`match_json_schema`:

`spec/requests/posts_spec.rb`
`spec/requests/locations_spec.rb`

```ruby
describe "GET /posts" do
it "returns Posts" do
get posts_path, format: :json
describe "GET /locations" do
it "returns Locations" do
get locations_path, format: :json

expect(response.status).to eq 200
expect(response).to match_json_schema("posts")
expect(response).to match_json_schema("locations")
end
end
```
Expand All @@ -105,60 +108,87 @@ end
Validate a JSON response, a Hash, or a String against a JSON Schema with
`assert_matches_json_schema`:

`test/integration/posts_test.rb`
`test/integration/locations_test.rb`

```ruby
def test_GET_posts_returns_Posts
get posts_path, format: :json
def test_GET_posts_returns_Locations
get locations_path, format: :json

assert_equal response.status, 200
assert_matches_json_schema response, "posts"
assert_matches_json_schema response, "locations"
end
```

### Embedding other Schemas

To DRY up your schema definitions, use JSON schema's `$ref`.
To re-use other schema definitions, include `$ref` keys that refer to their
definitions.

First, declare the singular version of your schema.

`spec/support/api/schemas/post.json`:
`spec/support/api/schemas/user.json`:

```json
{
"id": "file:/user.json#",
"type": "object",
"required": ["id", "title", "body"],
"required": ["id"],
"properties": {
"id": { "type": "integer" },
"title": { "type": "string" },
"body": { "type": "string" }
}
"name": { "type": "string" },
"address": { "type": "string" },
},
}
```

Then, when you declare your collection schema, reference your singular schemas.

`spec/support/api/schemas/posts.json`:
`spec/support/api/schemas/users/index.json`:

```json
{
"id": "file:/users/index.json#",
"type": "object",
"required": ["posts"],
"properties": {
"posts": {
"definitions": {
"users": {
"description": "A collection of users",
"example": [{ "id": "1" }],
"type": "array",
"items": { "$ref": "post.json" }
"items": {
"$ref": "file:/user.json#"
},
},
},
"required": ["users"],
"properties": {
"users": {
"$ref": "#/definitions/users"
}
}
},
}
```

NOTE: `$ref` resolves paths relative to the schema in question.

In this case `"post.json"` will be resolved relative to
`"spec/support/api/schemas"`.
In this case `"user.json"` and `"users/index.json"` are resolved relative to
`"spec/support/api/schemas"` or `"test/support/api/schemas"`.

To learn more about `$ref`, check out [Understanding JSON Schema Structuring](http://spacetelescope.github.io/understanding-json-schema/structuring.html)
To learn more about `$ref`, check out
[Understanding JSON Schema Structuring][$ref].

[$ref]: https://spacetelescope.github.io/understanding-json-schema/structuring.html

## Configuration

By default, the schema directory is `spec/support/api/schemas`.

This can be configured via `JsonMatchers.schema_root`.

```ruby
# spec/support/json_matchers.rb

JsonMatchers.schema_root = "docs/api/schemas"
```

## Upgrading from `0.9.x`

Expand Down
2 changes: 1 addition & 1 deletion json_matchers.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ 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_development_dependency "bundler", "~> 1.7"
spec.add_development_dependency "pry"
Expand Down
3 changes: 2 additions & 1 deletion lib/json_matchers.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require "pathname"
require "json_matchers/version"
require "json_matchers/matcher"
require "json_matchers/errors"
Expand All @@ -8,6 +9,6 @@ class << self
end

def self.path_to_schema(schema_name)
Pathname(schema_root).join("#{schema_name}.json")
Pathname.new(schema_root).join("#{schema_name}.json")
end
end
4 changes: 2 additions & 2 deletions lib/json_matchers/assertion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def initialize(schema_name)
def valid?(json)
@payload = Payload.new(json)

matcher.matches?(payload.to_s)
matcher.matches?(payload)
end

def valid_failure_message
Expand Down Expand Up @@ -58,7 +58,7 @@ def last_error_message
end

def schema_body
File.read(schema_path)
schema_path.read
end

def format_json(json)
Expand Down
33 changes: 18 additions & 15 deletions lib/json_matchers/matcher.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
require "json-schema"
require "json_schema"
require "json_matchers/parser"
require "json_matchers/validator"

module JsonMatchers
class Matcher
def initialize(schema_path)
@schema_path = schema_path
@document_store = build_and_populate_document_store
end

def matches?(payload)
validator = build_validator(payload)

self.errors = validator.validate!
self.errors = validator.validate(payload)

errors.empty?
rescue JSON::Schema::ValidationError => error
self.errors = [error.message]
false
rescue JSON::Schema::JsonParseError
raise InvalidSchemaError
end

def validation_failure_message
Expand All @@ -26,14 +21,22 @@ def validation_failure_message

private

attr_reader :schema_path
attr_accessor :errors
attr_reader :document_store, :schema_path

def validator
Validator.new(schema_path: schema_path, document_store: document_store)
end

def build_and_populate_document_store
document_store = JsonSchema::DocumentStore.new

Dir.glob("#{JsonMatchers.schema_root}/**/*.json").
map { |path| Pathname.new(path) }.
map { |schema_path| Parser.new(schema_path).parse }.
each { |schema| document_store.add_schema(schema) }

def build_validator(payload)
Validator.new(
payload: payload,
schema_path: schema_path,
)
document_store
end
end
end
21 changes: 21 additions & 0 deletions lib/json_matchers/parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module JsonMatchers
class Parser
def initialize(schema_path)
@schema_path = schema_path
end

def parse
JsonSchema.parse!(schema_data)
rescue JSON::ParserError, JsonSchema::SchemaError => error
raise InvalidSchemaError.new(error)
end

private

attr_reader :schema_path

def schema_data
JSON.parse(schema_path.read)
end
end
end
4 changes: 4 additions & 0 deletions lib/json_matchers/payload.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ def initialize(payload)
@payload = extract_json_string(payload)
end

def as_json
JSON.parse(payload)
end

def to_s
payload
end
Expand Down
30 changes: 23 additions & 7 deletions lib/json_matchers/validator.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
require "json-schema"
require "json_matchers/parser"

module JsonMatchers
class Validator
def initialize(payload:, schema_path:)
@payload = payload
@schema_path = schema_path.to_s
def initialize(document_store:, schema_path:)
@document_store = document_store
@schema_path = schema_path
end

def validate!
JSON::Validator.fully_validate(schema_path, payload, record_errors: true)
def validate(payload)
json_schema.validate!(payload.as_json, fail_fast: true)

[]
rescue JsonSchema::Error => error
[error.message]
end

private

attr_reader :payload, :schema_path
attr_reader :document_store, :schema_path

def json_schema
@json_schema ||= build_json_schema_with_expanded_references
end

def build_json_schema_with_expanded_references
json_schema = Parser.new(schema_path).parse

json_schema.expand_references!(store: document_store)

json_schema
end
end
end
Loading