schema_matcher 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2f63cddcf87db79491fd97d87fb673ad47dd9d096a7697be86e0b8ce71f48ff6
4
+ data.tar.gz: '0879f7a6573e9459bbb8b4a3797d3deecdbf5060cc458a57fcd46477b5c4e5d7'
5
+ SHA512:
6
+ metadata.gz: 73ca9559bfc1be14108137dc3c26450671150859870e75163e56f50d33067d4353a7e4ec89ee3cd36f8dcc03b407eaaee37fdfe053792ed3fe38ac3dc0fde174
7
+ data.tar.gz: cfcdba8173782eeefdb0d3dfe31b201530fb7cc64c92e4b19749a9e26e2e865402de866bab69bea312397230f033df0f9dd3aa24bebc27b33953abe75bcaf18f
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Ivan Rudskikh
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # SchemaMatcher
2
+
3
+ Use power of ruby DSL to validate json!
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+ ```ruby
9
+ group :test do
10
+ gem 'schema_matcher'
11
+ end
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install schema_matcher
22
+ ```
23
+
24
+ Add include to `spec/spec_helper.rb` or `spec/rails_helper.rb` if using Rails
25
+ ```ruby
26
+ require "schema_matcher/rspec"
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ 1) Write your schema
32
+ ```ruby
33
+ SchemaMatcher.build_schema do
34
+ define :user do
35
+ attribute :id, type: :number
36
+ attribute :first_name #it uses type string by default
37
+ attribute :last_name
38
+ attribute :avatar, nullable: true
39
+ attribute :disabled, type: :boolean
40
+ attribute :role, optional: true
41
+ attribute :posts, array: true, ref: :post
42
+ end
43
+
44
+ define :post do
45
+ attribute :id, type: :number
46
+ attribute :content
47
+ attribute :author do
48
+ attribute :id, type: :number
49
+ attribute :first_name
50
+ attribute :last_name
51
+ end
52
+ end
53
+ end
54
+ ```
55
+
56
+ 2) Check responses:
57
+ ```ruby
58
+ RSpec.describe 'Users' do
59
+ let(:json_response) { JSON.parse(response.body) }
60
+
61
+ it 'returns user' do
62
+ get '/users/1'
63
+ expect(json_response).to match_json_schema(:user)
64
+ end
65
+
66
+ it 'returns a few users' do
67
+ get '/users'
68
+ expect(json_response).to match_json_schema(:user, array: true)
69
+ end
70
+ end
71
+ ```
72
+ 3) Enjoy!
73
+
74
+ ## Contributing
75
+
76
+ * Fork the project.
77
+ * Add a breaking test for your change.
78
+ * Make the tests pass.
79
+ * Run `rubocop -a`
80
+ * Push your fork.
81
+ * Submit a pull request.
82
+
83
+ ## License
84
+
85
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'SchemaMatcher'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ require 'bundler/gem_tasks'
18
+
19
+ require 'rake/testtask'
20
+
21
+ Rake::TestTask.new(:test) do |t|
22
+ t.libs << 'test'
23
+ t.pattern = 'test/**/*_test.rb'
24
+ t.verbose = false
25
+ end
26
+
27
+ task default: :test
@@ -0,0 +1,39 @@
1
+ require 'json-schema'
2
+ require 'schema_matcher/extended_schema'
3
+
4
+ module SchemaMatcher
5
+ class Assertion
6
+ attr_reader :errors
7
+
8
+ def initialize(schema_name, payload, options = {})
9
+ @schema_name = schema_name.to_sym
10
+ @payload = payload
11
+ @options = options
12
+ end
13
+
14
+ def valid?
15
+ @errors = JSON::Validator.fully_validate(schema, payload, validator_options)
16
+ return true if @errors.empty?
17
+
18
+ false
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :payload, :schema_name, :last_error_message, :options
24
+
25
+ def schema
26
+ SchemaMatcher
27
+ .schema[schema_name]
28
+ .merge('$schema' => SchemaMatcher::ExtendedSchema::SCHEMA_URI)
29
+ .merge!(definitions: SchemaMatcher.schema)
30
+ end
31
+
32
+ def validator_options
33
+ {
34
+ strict: true,
35
+ list: options.key?(:array) ? options[:array] : payload.is_a?(Array)
36
+ }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ module SchemaMatcher
2
+ class Builder
3
+ include BuilderApi
4
+ end
5
+ end
@@ -0,0 +1,22 @@
1
+ module SchemaMatcher
2
+ module BuilderApi
3
+ def self.included(base)
4
+ base.instance_variable_set(:@schema, {})
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def schema
10
+ instance_variable_get(:@schema)
11
+ end
12
+
13
+ def define(model, &blk)
14
+ schema[model] = Entity.new(&blk)
15
+ end
16
+
17
+ def to_schema
18
+ schema.transform_values(&:to_schema)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,53 @@
1
+ module SchemaMatcher
2
+ class Entity
3
+ attr_reader :attributes
4
+
5
+ def initialize(&blk)
6
+ @attributes = {}
7
+ instance_exec(&blk)
8
+ end
9
+
10
+ def attribute(name, options = {}, &blk)
11
+ return attributes[name] = { type: self.class.new(&blk) }.merge(options).compact if block_given?
12
+
13
+ attributes[name] = { type: :string }.merge(options).compact
14
+ end
15
+
16
+ def to_schema
17
+ {
18
+ type: :object,
19
+ properties: attributes.transform_values { |attribute| swaggerize_attribute(attribute) }.compact
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ def swaggerize_attribute(attribute)
26
+ if attribute[:array]
27
+ {
28
+ type: :array,
29
+ item: attr_to_schema(attribute)
30
+ }.merge(extra_schema_properties(attribute))
31
+ else
32
+ attr_to_schema(attribute).merge(extra_schema_properties(attribute))
33
+ end
34
+ end
35
+
36
+ def attr_to_schema(attribute)
37
+ return { '$ref' => "#/definitions/#{attribute[:ref]}" } if attribute[:ref]
38
+
39
+ if attribute[:type].respond_to?(:to_schema)
40
+ attribute[:type].to_schema
41
+ else
42
+ attribute.except(:nullable, :optional, :array)
43
+ end
44
+ end
45
+
46
+ def extra_schema_properties(attribute)
47
+ {
48
+ 'x-nullable' => attribute[:nullable],
49
+ required: attribute[:ref] && !attribute[:array] ? nil : !attribute[:optional]
50
+ }.compact
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,27 @@
1
+ require 'json-schema'
2
+
3
+ module SchemaMatcher
4
+ class ExtendedSchema < JSON::Schema::Draft4
5
+ SCHEMA_URI = 'http://tempuri.org/schema_matcher/extended_schema'.freeze
6
+
7
+ def initialize
8
+ super
9
+ @attributes['type'] = ExtendedTypeAttribute
10
+ @attributes['properties'] = JSON::Schema::PropertiesAttribute
11
+ @uri = URI.parse(SCHEMA_URI)
12
+ @names = ['draft4-custom', SCHEMA_URI]
13
+ end
14
+ end
15
+
16
+ class ExtendedTypeAttribute < JSON::Schema::TypeV4Attribute
17
+ # rubocop:disable Metrics/ParameterLists
18
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
19
+ return if data.nil? && current_schema.schema['x-nullable']
20
+
21
+ super
22
+ end
23
+ # rubocop:enable Metrics/ParameterLists
24
+ end
25
+
26
+ JSON::Validator.register_validator(ExtendedSchema.new)
27
+ end
@@ -0,0 +1,5 @@
1
+ require 'schema_matcher/rspec_matchers'
2
+
3
+ RSpec.configure do |config|
4
+ config.include(SchemaMatcher::RspecMatchers)
5
+ end
@@ -0,0 +1,70 @@
1
+ require 'schema_matcher'
2
+ require 'schema_matcher/assertion'
3
+
4
+ module SchemaMatcher
5
+ module RspecMatchers
6
+ class JsonSchemaMatcher
7
+ attr_reader :schema_name, :payload, :assertion
8
+
9
+ def initialize(schema_name)
10
+ @schema_name = schema_name
11
+ end
12
+
13
+ def matches?(payload, options = {})
14
+ @payload = payload
15
+ @assertion = SchemaMatcher::Assertion.new(schema_name, payload, options)
16
+ @assertion.valid?
17
+ end
18
+
19
+ def error_message
20
+ @assertion.errors[0]
21
+ end
22
+
23
+ def failure_message
24
+ <<~FAIL
25
+ #{error_message}
26
+
27
+ ---
28
+
29
+ expected
30
+
31
+ #{format_json(payload)}
32
+
33
+ to match schema "#{schema_name}":
34
+
35
+ FAIL
36
+ end
37
+
38
+ def failure_message_when_negated
39
+ <<~FAIL
40
+ #{error_message}
41
+
42
+ ---
43
+
44
+ expected
45
+
46
+ #{format_json(payload)}
47
+
48
+ not to match schema "#{schema_name}":
49
+
50
+ FAIL
51
+ end
52
+
53
+ def failure_message_for_should
54
+ failure_message
55
+ end
56
+
57
+ def failure_message_for_should_not
58
+ failure_message_when_negated
59
+ end
60
+
61
+ def format_json(_json)
62
+ JSON.pretty_generate(payload)
63
+ end
64
+ end
65
+
66
+ def match_json_schema(schema)
67
+ JsonSchemaMatcher.new(schema)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,3 @@
1
+ module SchemaMatcher
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,16 @@
1
+ require 'schema_matcher/version'
2
+ require 'schema_matcher/entity'
3
+ require 'schema_matcher/builder_api'
4
+ require 'schema_matcher/builder'
5
+
6
+ module SchemaMatcher
7
+ @schema = nil
8
+
9
+ def self.schema
10
+ @schema ||= Builder.to_schema
11
+ end
12
+
13
+ def self.build_schema(&blk)
14
+ Builder.class_eval(&blk)
15
+ end
16
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :schema_matcher do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: schema_matcher
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ivan Rudskikh
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-07-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json-schema
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: factory_bot
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '4.8'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '4.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ffaker
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ description: Use power of ruby DSL to validate json!
70
+ email:
71
+ - shredder.rull@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - MIT-LICENSE
77
+ - README.md
78
+ - Rakefile
79
+ - lib/schema_matcher.rb
80
+ - lib/schema_matcher/assertion.rb
81
+ - lib/schema_matcher/builder.rb
82
+ - lib/schema_matcher/builder_api.rb
83
+ - lib/schema_matcher/entity.rb
84
+ - lib/schema_matcher/extended_schema.rb
85
+ - lib/schema_matcher/rspec.rb
86
+ - lib/schema_matcher/rspec_matchers.rb
87
+ - lib/schema_matcher/version.rb
88
+ - lib/tasks/schema_matcher_tasks.rake
89
+ homepage: https://github.com/digital-design-nyc/schema_matcher
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.7.9
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Use power of ruby DSL to validate json!
113
+ test_files: []