Skip to content

Commit 0f6390e

Browse files
author
Jeff Cousens
committed
Initial commit.
0 parents  commit 0f6390e

31 files changed

+2280
-0
lines changed

.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/.bundle/
2+
/.yardoc
3+
/Gemfile.lock
4+
/_yardoc/
5+
/coverage/
6+
/doc/
7+
/pkg/
8+
/spec/reports/
9+
/tmp/

.rspec

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
--format documentation
2+
--color

.rubocop.yml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Metrics/AbcSize:
2+
Max: 30
3+
4+
Metrics/ClassLength:
5+
Max: 200
6+
7+
Metrics/LineLength:
8+
Max: 120
9+
10+
Metrics/MethodLength:
11+
Max: 25
12+
13+
Metrics/PerceivedComplexity:
14+
Max: 10
15+
16+
Style/Documentation:
17+
Enabled: false
18+
19+
Style/FileName:
20+
Enabled: false

.ruby-version

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2.2.2

.travis.yml

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
language: ruby
2+
cache: bundler
3+
script:
4+
- bundle exec rubocop

Gemfile

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
source 'https://rubygems.org'
2+
3+
# Specify your gem's dependencies in swagger-diff.gemspec
4+
gemspec

LICENSE.txt

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright (c) 2015, Civis Analytics
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
7+
* Redistributions of source code must retain the above copyright notice, this
8+
list of conditions and the following disclaimer.
9+
10+
* Redistributions in binary form must reproduce the above copyright notice,
11+
this list of conditions and the following disclaimer in the documentation
12+
and/or other materials provided with the distribution.
13+
14+
* Neither the name of Civis Analytics nor the names of its
15+
contributors may be used to endorse or promote products derived from
16+
this software without specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Swagger::Diff
2+
3+
[![Build Status](https://travis-ci.org/civisanalytics/swagger-diff.svg?branch=master)](https://travis-ci.org/civisanalytics/swagger-diff)
4+
5+
![Swagger::Diff in action](swagger-diff.gif)
6+
7+
> You can tell me by the way I walk - Genesis
8+
9+
Swagger::Diff is a utility for comparing two different
10+
[Swagger](http://swagger.io/) specifications.
11+
Its intended use is to determine whether a newer API specification is
12+
backwards-compatible with an older API specification.
13+
It provides both an [RSpec](http://rspec.info/) matcher and helper functions
14+
that can be used directly.
15+
Specifications are considered backwards compatible if:
16+
17+
- all path and verb combinations in the old specification are present in the
18+
new one
19+
- no request parameters are required in the new specification that were not
20+
required in the old one
21+
- all request parameters in the old specification are present in the new one
22+
- all request parameters in the old specification have the same type in the
23+
new one
24+
- all response attributes in the old specification are present in the new one
25+
- all response attributes in the old specification have the same type in the new
26+
one
27+
28+
## Installation
29+
30+
Add this line to your application's Gemfile:
31+
32+
```ruby
33+
gem 'swagger-diff'
34+
```
35+
36+
And then execute:
37+
38+
$ bundle
39+
40+
Or install it yourself as:
41+
42+
$ gem install swagger-diff
43+
44+
## Usage
45+
46+
Swagger::Diff uses the [Swagger](https://github.com/swagger-rb/swagger-rb) gem
47+
to parse Swagger specifications.
48+
Specifications can be any
49+
[supported format](https://github.com/swagger-rb/swagger-rb/tree/v0.2.3#parsing):
50+
51+
- the path to a file containing a Swagger specification.
52+
This may be local (*e.g.*, `/path/to/swagger.json`) or remote (*e.g.*,
53+
`http://host.domain/swagger.yml`)
54+
- a Hash containing a parsed Swagger specification (*e.g.*, the output of
55+
`JSON.parse`)
56+
- a string of JSON containing Swagger specification
57+
- a string of YAML containing Swagger specification
58+
59+
### RSpec
60+
61+
```ruby
62+
expect(<new>).to be_compatible_with(<old>)
63+
```
64+
65+
If `new` is incompatible with `old`, the spec will fail and print a list of
66+
backwards-incompatibilities.
67+
68+
### Direct Invocation
69+
70+
If you are not using RSpec, you can directly invoke the comparison function:
71+
72+
```ruby
73+
diff = Swagger::Diff::Diff.new(<old>, <new>)
74+
diff.compatible?
75+
```
76+
77+
It will return `true` if `new` is compatible with `old`, `false` otherwise.
78+
`#incompatibilities` will return a hash containing the incompatible endpoints,
79+
request parameters, and response attributes; *e.g.*,
80+
81+
```ruby
82+
{ endpoints: ['put /a/{}'],
83+
request_params: {
84+
'get /a/' => ['missing request param: limit (type: integer)'],
85+
'post /a/' => ['new required request param: extra'],
86+
'put /b/{}' => ['new required request param: extra']
87+
},
88+
response_attributes: {
89+
'post /a/' => ['missing attribute from 200 response: description (type: string)'],
90+
'get /a/{}' => ['missing attribute from 200 response: description (type: string)'],
91+
'put /b/{}' => ['missing attribute from 200 response: description (type: string)']
92+
}
93+
}
94+
```
95+
96+
### Command-Line
97+
98+
It also includes a command-line version:
99+
100+
```bash
101+
$ swagger-diff <old> <new>
102+
```
103+
104+
`swagger-diff` will print a list of any backwards-incompatibilities `new` has
105+
when compared to `old`.
106+
107+
## Gem Development
108+
109+
After checking out the repo, run `bin/setup` to install dependencies.
110+
Then, run `bin/console` for an interactive prompt that will allow you to experiment.
111+
112+
To install this gem onto your local machine, run `bundle exec rake install`.
113+
To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
114+
115+
## Contributing
116+
117+
1. Fork it ( https://github.com/civisanalytics/swagger-diff/fork )
118+
2. Create your feature branch (`git checkout -b my-new-feature`)
119+
3. Commit your changes (`git commit -am 'Add some feature'`)
120+
4. Push to the branch (`git push origin my-new-feature`)
121+
5. Create a new Pull Request
122+
123+
## License
124+
125+
Swagger::Diff is released under the [BSD 3-Clause License](LICENSE.txt).

Rakefile

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require 'bundler/gem_tasks'

bin/console

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env ruby
2+
3+
require 'bundler/setup'
4+
require 'swagger/diff'
5+
6+
require 'pry'
7+
Pry.start

bin/setup

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
IFS=$'\n\t'
4+
5+
bundle install
6+
7+
# Do any other automated setup that you need to do here

exe/swagger-diff

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env ruby
2+
3+
require 'swagger/diff'
4+
5+
if ARGV.length == 2
6+
diff = Swagger::Diff::Diff.new(ARGV[0], ARGV[1])
7+
8+
unless diff.compatible?
9+
puts diff.incompatibilities_message
10+
exit 1
11+
end
12+
else
13+
puts 'Usage: swagger-diff <old> <new>
14+
15+
Checks <new> for backwards-compatibility with <old>. If <new> is incompatible,
16+
a list of incompatibilities will be printed.'
17+
end

lib/swagger/diff.rb

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require 'forwardable' # Needed to require swagger-core.
2+
require 'swagger'
3+
require 'rspec/expectations'
4+
require 'swagger/diff/diff'
5+
require 'swagger/diff/patch'
6+
require 'swagger/diff/rspec_matchers'
7+
require 'swagger/diff/specification'
8+
require 'swagger/diff/version'

lib/swagger/diff/diff.rb

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
module Swagger
2+
module Diff
3+
class Diff
4+
def initialize(old, new)
5+
@new_specification = Swagger::Diff::Specification.new(new)
6+
@old_specification = Swagger::Diff::Specification.new(old)
7+
end
8+
9+
def compatible?
10+
endpoints_compatible? && requests_compatible? && responses_compatible?
11+
end
12+
13+
def incompatibilities
14+
{ endpoints: missing_endpoints.to_a.sort,
15+
request_params: incompatible_request_params,
16+
response_attributes: incompatible_response_attributes }
17+
end
18+
19+
def incompatibilities_message
20+
msg = ''
21+
if incompatibilities[:endpoints]
22+
msg += incompatibilities_message_endpoints(incompatibilities[:endpoints])
23+
end
24+
if incompatibilities[:request_params]
25+
msg += incompatibilities_message_params(incompatibilities[:request_params])
26+
end
27+
if incompatibilities[:response_attributes]
28+
msg += incompatibilities_message_attributes(incompatibilities[:response_attributes])
29+
end
30+
msg
31+
end
32+
33+
private
34+
35+
def incompatibilities_message_endpoints(endpoints)
36+
if endpoints.empty?
37+
''
38+
else
39+
msg = "- missing endpoints\n"
40+
endpoints.each do |endpoint|
41+
msg += " - #{endpoint}\n"
42+
end
43+
msg
44+
end
45+
end
46+
47+
def incompatibilities_message_inner(typestr, collection)
48+
if collection.nil? || collection.empty?
49+
''
50+
else
51+
msg = "- incompatible #{typestr}\n"
52+
collection.sort.each do |endpoint, attributes|
53+
msg += " - #{endpoint}\n"
54+
attributes.each do |attribute|
55+
msg += " - #{attribute}\n"
56+
end
57+
end
58+
msg
59+
end
60+
end
61+
62+
def incompatibilities_message_params(params)
63+
incompatibilities_message_inner('request params', params)
64+
end
65+
66+
def incompatibilities_message_attributes(attributes)
67+
incompatibilities_message_inner('response attributes', attributes)
68+
end
69+
70+
def missing_endpoints
71+
@old_specification.endpoints - @new_specification.endpoints
72+
end
73+
74+
def incompatible_request_params
75+
ret = {}
76+
incompatible_request_params_enumerator.each do |key, val|
77+
ret[key] ||= []
78+
ret[key] << val
79+
end
80+
ret
81+
end
82+
83+
def incompatible_request_params_enumerator
84+
Enumerator.new do |yielder|
85+
@old_specification.request_params.each do |key, old_params|
86+
new_params = @new_specification.request_params[key]
87+
next if new_params.nil?
88+
(new_params[:required] - old_params[:required]).each do |req|
89+
yielder << [key, "new required request param: #{req}"]
90+
end
91+
(old_params[:all] - new_params[:all]).each do |req|
92+
yielder << [key, "missing request param: #{req}"]
93+
end
94+
end
95+
end.lazy
96+
end
97+
98+
def incompatible_response_attributes
99+
ret = {}
100+
incompatible_response_attributes_enumerator.each do |key, val|
101+
ret[key] ||= []
102+
ret[key] << val
103+
end
104+
ret
105+
end
106+
107+
def incompatible_response_attributes_enumerator
108+
Enumerator.new do |yielder|
109+
@old_specification.response_attributes.each do |key, old_attributes|
110+
new_attributes = @new_specification.response_attributes[key]
111+
next if new_attributes.nil?
112+
old_attributes.keys.each do |code|
113+
if new_attributes.key?(code)
114+
(old_attributes[code] - new_attributes[code]).each do |resp|
115+
yielder << [key, "missing attribute from #{code} response: #{resp}"]
116+
end
117+
else
118+
yielder << [key, "missing #{code} response"]
119+
end
120+
end
121+
end
122+
end.lazy
123+
end
124+
125+
def endpoints_compatible?
126+
missing_endpoints.empty?
127+
end
128+
129+
def requests_compatible?
130+
incompatible_request_params_enumerator.none?
131+
end
132+
133+
def responses_compatible?
134+
incompatible_response_attributes_enumerator.none?
135+
end
136+
end
137+
end
138+
end

lib/swagger/diff/patch.rb

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Workaround for Swagger::V2::SecurityScheme[1] incorrectly specifying
2+
# required fields. maxlinc is right: the Swagger specification[2] is
3+
# misleading. Fixed Fields suggests many parameters are required that are not
4+
# required by Swagger's official validator[3]. Working around this by removing
5+
# them from the array of required properties.
6+
#
7+
# [1] https://github.com/swagger-rb/swagger-rb/blob/v0.2.3/lib/swagger/v2/security_scheme.rb#L9-L11
8+
# [2] http://swagger.io/specification/#securityDefinitionsObject
9+
# [3] https://github.com/swagger-api/validator-badge
10+
11+
OAUTH2_PARAMS = [:flow, :authorizationUrl, :scopes]
12+
13+
Swagger::V2::SecurityScheme.required_properties.reject! do |k, _|
14+
OAUTH2_PARAMS.include?(k)
15+
end

0 commit comments

Comments
 (0)