swaggable 0.4.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 +7 -0
- data/.gitignore +2 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +20 -0
- data/README.md +123 -0
- data/Rakefile +13 -0
- data/assets/json-schema-draft-04.json +150 -0
- data/assets/swagger-2.0-schema.json +1495 -0
- data/lib/swaggable.rb +14 -0
- data/lib/swaggable/api_definition.rb +52 -0
- data/lib/swaggable/endpoint_definition.rb +54 -0
- data/lib/swaggable/grape_adapter.rb +84 -0
- data/lib/swaggable/parameter_definition.rb +50 -0
- data/lib/swaggable/rack_app.rb +30 -0
- data/lib/swaggable/response_definition.rb +16 -0
- data/lib/swaggable/swagger_2_serializer.rb +92 -0
- data/lib/swaggable/swagger_2_validator.rb +28 -0
- data/lib/swaggable/tag_definition.rb +27 -0
- data/lib/swaggable/version.rb +3 -0
- data/spec/assets/valid-swagger-2.0.json +1 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/swaggable/api_definition_spec.rb +169 -0
- data/spec/swaggable/endpoint_definition_spec.rb +77 -0
- data/spec/swaggable/grape_adapter_spec.rb +198 -0
- data/spec/swaggable/integration_spec.rb +100 -0
- data/spec/swaggable/parameter_definition_spec.rb +64 -0
- data/spec/swaggable/rack_app_spec.rb +49 -0
- data/spec/swaggable/response_definition_spec.rb +17 -0
- data/spec/swaggable/swagger_2_serializer_spec.rb +208 -0
- data/spec/swaggable/swagger_2_validator_spec.rb +26 -0
- data/spec/swaggable/tag_definition_spec.rb +48 -0
- data/spec/swaggable_spec.rb +7 -0
- data/swaggable.gemspec +29 -0
- metadata +161 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'rack/test'
|
3
|
+
require 'grape'
|
4
|
+
|
5
|
+
RSpec.describe 'Integration' do
|
6
|
+
context 'RackApp' do
|
7
|
+
include Rack::Test::Methods
|
8
|
+
|
9
|
+
def app
|
10
|
+
subject
|
11
|
+
end
|
12
|
+
|
13
|
+
subject { rack_app }
|
14
|
+
let(:rack_app) { Swaggable::RackApp.new(api_definition: api_def) }
|
15
|
+
let(:api_def) { Swaggable::ApiDefinition.from_grape_api(grape_api) }
|
16
|
+
|
17
|
+
let(:grape_api) do
|
18
|
+
Class.new(Grape::API).tap do |g|
|
19
|
+
g.params do
|
20
|
+
requires :user_uuid
|
21
|
+
end
|
22
|
+
|
23
|
+
g.get '/' do
|
24
|
+
status 200
|
25
|
+
body({some: 'body'})
|
26
|
+
end
|
27
|
+
|
28
|
+
end.tap do |grape|
|
29
|
+
stub_const('MyGrapeApi', grape)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'has status 200 OK' do
|
34
|
+
get '/'
|
35
|
+
expect(last_response.status).to eq 200
|
36
|
+
expect(last_response.headers['Content-Type']).to eq 'application/json'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'validates' do
|
40
|
+
expect(subject.validate!).to be true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'dsl' do
|
45
|
+
it 'supports a full description of the API' do
|
46
|
+
api = Swaggable::ApiDefinition.new do
|
47
|
+
version '1.0'
|
48
|
+
title 'My API'
|
49
|
+
description 'A test API'
|
50
|
+
base_path '/api/1.0'
|
51
|
+
|
52
|
+
endpoints.add_new do
|
53
|
+
path '/users/{id}'
|
54
|
+
verb :get
|
55
|
+
description 'Shows an user'
|
56
|
+
summary 'Returns the JSON representation of such user'
|
57
|
+
|
58
|
+
tags.add_new do
|
59
|
+
name 'Users'
|
60
|
+
description 'Users resource'
|
61
|
+
end
|
62
|
+
|
63
|
+
parameters.add_new do
|
64
|
+
name 'include_comments_count'
|
65
|
+
description 'It will return the comments_count attribute when set to true'
|
66
|
+
location :query # [:path, :query, :header, :body, :form, nil]
|
67
|
+
required false
|
68
|
+
type :boolean # [:string, :number, :integer, :boolean, :array, :file, nil]
|
69
|
+
end
|
70
|
+
|
71
|
+
responses.add_new do
|
72
|
+
status 200
|
73
|
+
description 'Success'
|
74
|
+
end
|
75
|
+
|
76
|
+
responses.add_new do
|
77
|
+
status 404
|
78
|
+
description 'User not found'
|
79
|
+
end
|
80
|
+
|
81
|
+
consumes << :json
|
82
|
+
produces << :json
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
expect(api.version).to eq '1.0'
|
87
|
+
expect(api.endpoints.first).to be api.endpoints['GET /users/{id}']
|
88
|
+
expect(api.endpoints.first.path).to eq '/users/{id}'
|
89
|
+
expect(api.endpoints.first.tags.first).to be api.endpoints.first.tags['Users']
|
90
|
+
expect(api.endpoints.first.tags.first.name).to eq 'Users'
|
91
|
+
expect(api.endpoints.first.parameters.first).to be api.endpoints.first.parameters['include_comments_count']
|
92
|
+
expect(api.endpoints.first.parameters.first.name).to eq 'include_comments_count'
|
93
|
+
expect(api.endpoints.first.responses.first).to be api.endpoints.first.responses[200]
|
94
|
+
expect(api.endpoints.first.responses.first.description).to eq 'Success'
|
95
|
+
expect(api.endpoints.first.consumes).to eq [:json]
|
96
|
+
expect(api.endpoints.first.produces).to eq [:json]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'Swaggable::ParameterDefinition' do
|
4
|
+
let(:subject_class) { Swaggable::ParameterDefinition }
|
5
|
+
let(:subject_instance) { Swaggable::ParameterDefinition.new }
|
6
|
+
subject { subject_instance }
|
7
|
+
|
8
|
+
it 'has a name' do
|
9
|
+
subject.name = 'a name'
|
10
|
+
expect(subject.name).to eq 'a name'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has a description' do
|
14
|
+
subject.description = 'a new desc'
|
15
|
+
expect(subject.description).to eq 'a new desc'
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'has a required option' do
|
19
|
+
subject.required = true
|
20
|
+
expect(subject.required?).to be true
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'yields itself on initialize' do
|
24
|
+
yielded = false
|
25
|
+
|
26
|
+
subject_class.new do |s|
|
27
|
+
expect(s).to be_a subject_class
|
28
|
+
yielded = true
|
29
|
+
end
|
30
|
+
|
31
|
+
expect(yielded).to be true
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#location' do
|
35
|
+
it 'can be set to :body, :header, :path, :query, :form' do
|
36
|
+
[:body, :header, :path, :query, :form, nil].each do |location|
|
37
|
+
subject.location = location
|
38
|
+
expect(subject.location).to eq location
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'cannot be something other than that' do
|
43
|
+
expect { subject.location = :xyz }.to raise_exception
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#type' do
|
48
|
+
it 'can be set to :string, :number, :integer, :boolean, :array, :file' do
|
49
|
+
[:string, :number, :integer, :boolean, :array, :file, nil].each do |type|
|
50
|
+
subject.type = type
|
51
|
+
expect(subject.type).to eq type
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'cannot be something other than that' do
|
56
|
+
expect { subject.type = :xyz }.to raise_exception
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'accepts attributes on initialize' do
|
61
|
+
parameter = subject_class.new location: :path
|
62
|
+
expect(parameter.location).to eq :path
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'rack/test'
|
3
|
+
|
4
|
+
RSpec.describe 'Swaggable::RackApp' do
|
5
|
+
include Rack::Test::Methods
|
6
|
+
|
7
|
+
let(:subject_class) { Swaggable::RackApp }
|
8
|
+
let(:subject_instance) { Swaggable::RackApp.new serializer: serializer, api_definition: api_definition }
|
9
|
+
subject { subject_instance }
|
10
|
+
let(:api_definition) { instance_double(Swaggable::ApiDefinition) }
|
11
|
+
let(:serializer) { instance_double(Swaggable::Swagger2Serializer, serialize: {}) }
|
12
|
+
|
13
|
+
def app
|
14
|
+
subject_instance
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'has status 200 OK' do
|
18
|
+
get '/'
|
19
|
+
expect(last_response.status).to eq 200
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'has content_type application/json' do
|
23
|
+
get '/'
|
24
|
+
expect(last_response.headers['Content-Type']).to eq 'application/json'
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'has body the serialized swagger' do
|
28
|
+
allow(serializer).to receive(:serialize).with(api_definition).and_return({some: :json})
|
29
|
+
|
30
|
+
get '/'
|
31
|
+
expect(last_response.body).to eq '{"some":"json"}'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'uses Swagger2Serializer by default' do
|
35
|
+
subject = subject_class.new
|
36
|
+
expect(subject.serializer).to be_a Swaggable::Swagger2Serializer
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'validate!' do
|
40
|
+
it 'validates against the serializer' do
|
41
|
+
expect(serializer).
|
42
|
+
to receive(:validate!).
|
43
|
+
with api_definition
|
44
|
+
|
45
|
+
subject.validate!
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'Swaggable::ResponseDefinition' do
|
4
|
+
let(:subject_class) { Swaggable::ResponseDefinition }
|
5
|
+
let(:subject_instance) { Swaggable::ResponseDefinition.new }
|
6
|
+
subject { subject_instance }
|
7
|
+
|
8
|
+
it 'has a status' do
|
9
|
+
subject.status = 418
|
10
|
+
expect(subject.status).to eq 418
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has a description' do
|
14
|
+
subject.description = "I'm a teapot"
|
15
|
+
expect(subject.description).to eq "I'm a teapot"
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'Swaggable::Swagger2Serializer' do
|
4
|
+
let(:subject_class) { Swaggable::Swagger2Serializer }
|
5
|
+
let(:subject_instance) { Swaggable::Swagger2Serializer.new }
|
6
|
+
subject { subject_instance }
|
7
|
+
|
8
|
+
let(:api) { Swaggable::ApiDefinition.new }
|
9
|
+
|
10
|
+
describe '#serialize' do
|
11
|
+
def output
|
12
|
+
subject.serialize(api)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'has swagger version 2.0' do
|
16
|
+
expect(output[:swagger]).to eq '2.0'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'has basePath' do
|
20
|
+
api.base_path = '/a/path'
|
21
|
+
expect(output[:basePath]).to eq '/a/path'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'has title' do
|
25
|
+
api.title = 'A Title'
|
26
|
+
expect(output[:info][:title]).to eq 'A Title'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'has title empty string if nil' do
|
30
|
+
api.title = nil
|
31
|
+
expect(output[:info][:title]).to eq ''
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'has description' do
|
35
|
+
api.description = 'A Desc'
|
36
|
+
expect(output[:info][:description]).to eq 'A Desc'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'has no description if nil' do
|
40
|
+
api.description = nil
|
41
|
+
expect(output[:info].has_key?(:description)).to be false
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'has version' do
|
45
|
+
api.version = 'v1.0'
|
46
|
+
expect(output[:info][:version]).to eq 'v1.0'
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'has version "0.0.0" if nil' do
|
50
|
+
api.version = nil
|
51
|
+
expect(output[:info][:version]).to eq "0.0.0"
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#tags' do
|
55
|
+
def serialized_tag
|
56
|
+
output[:tags].first
|
57
|
+
end
|
58
|
+
|
59
|
+
let(:api) { Swaggable::ApiDefinition.new {|a| a.endpoints << endpoint } }
|
60
|
+
let(:endpoint) { Swaggable::EndpointDefinition.new {|e| e.tags << tag } }
|
61
|
+
let(:tag) { Swaggable::TagDefinition.new name: 'A Tag', description: 'A tag description' }
|
62
|
+
|
63
|
+
it 'has a name' do
|
64
|
+
expect(serialized_tag[:name]).to eq tag.name
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'has a description' do
|
68
|
+
expect(serialized_tag[:description]).to eq tag.description
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'has no description if nil' do
|
72
|
+
tag.description = nil
|
73
|
+
expect(serialized_tag.has_key?(:description)).to be false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#endpoints' do
|
78
|
+
let(:api) { Swaggable::ApiDefinition.new {|a| a.endpoints << endpoint } }
|
79
|
+
let(:endpoint) { Swaggable::EndpointDefinition.new path: path, verb: verb }
|
80
|
+
let(:path) { '/a/path' }
|
81
|
+
let(:verb) { 'POST' }
|
82
|
+
|
83
|
+
it 'uses the path as key' do
|
84
|
+
expect(output[:paths][path]).not_to be_nil
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'uses the verb as key' do
|
88
|
+
expect(output[:paths][path][verb]).not_to be_nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def serialized_endpoint
|
92
|
+
output[:paths][path][verb]
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'has description' do
|
96
|
+
endpoint.description = 'A desc.'
|
97
|
+
expect(serialized_endpoint[:description]).to eq 'A desc.'
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'has no description if nil' do
|
101
|
+
endpoint.description = nil
|
102
|
+
expect(serialized_endpoint.has_key?(:description)).to be false
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'has summary' do
|
106
|
+
endpoint.summary = 'A summary.'
|
107
|
+
expect(serialized_endpoint[:summary]).to eq 'A summary.'
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'has no summary if summary is nil' do
|
111
|
+
endpoint.summary = nil
|
112
|
+
expect(serialized_endpoint.has_key?(:summary)).to be false
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'has tags' do
|
116
|
+
endpoint.tags << Swaggable::TagDefinition.new(name: 'Tag 1')
|
117
|
+
endpoint.tags << Swaggable::TagDefinition.new(name: 'Tag 2')
|
118
|
+
expect(serialized_endpoint[:tags]).to eq ['Tag 1', 'Tag 2']
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'has consumes' do
|
122
|
+
endpoint.consumes << 'application/whatever'
|
123
|
+
expect(serialized_endpoint[:consumes]).to eq ['application/whatever']
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'has produces' do
|
127
|
+
endpoint.produces << 'application/whatever'
|
128
|
+
expect(serialized_endpoint[:produces]).to eq ['application/whatever']
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'works with two endpoints with the same path' do
|
132
|
+
api.endpoints << Swaggable::EndpointDefinition.new(path: path, verb: 'get')
|
133
|
+
|
134
|
+
expect(output[:paths][path][verb]).not_to be_nil
|
135
|
+
expect(output[:paths][path]['get']).not_to be_nil
|
136
|
+
end
|
137
|
+
|
138
|
+
describe 'responses' do
|
139
|
+
it 'defaults to 200 Success' do
|
140
|
+
expect(serialized_endpoint[:responses]).to eq({200 => {description: "Success"}})
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'is taken from the responses' do
|
144
|
+
endpoint.responses.add_new do
|
145
|
+
status 418
|
146
|
+
description 'Teapot'
|
147
|
+
end
|
148
|
+
|
149
|
+
expect(serialized_endpoint[:responses]).to eq({418 => {description: "Teapot"}})
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe 'parameters' do
|
154
|
+
before(:each) { endpoint.parameters << parameter }
|
155
|
+
let(:parameter) { Swaggable::ParameterDefinition.new location: :query }
|
156
|
+
|
157
|
+
def serialized_parameter
|
158
|
+
serialized_endpoint[:parameters].first
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'has "in"' do
|
162
|
+
expect(serialized_parameter[:in]).to eq 'query'
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'has name' do
|
166
|
+
parameter.name = 'my_param'
|
167
|
+
expect(serialized_parameter[:name]).to eq 'my_param'
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'has description' do
|
171
|
+
parameter.description = 'A param'
|
172
|
+
expect(serialized_parameter[:description]).to eq 'A param'
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'has no description if nil' do
|
176
|
+
parameter.description = nil
|
177
|
+
expect(serialized_parameter.has_key?(:description)).to be false
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'has required' do
|
181
|
+
parameter.required = nil
|
182
|
+
expect(serialized_parameter[:required]).to eq false
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'has type if present' do
|
186
|
+
parameter.type = :string
|
187
|
+
expect(serialized_parameter[:type]).to eq :string
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'has type string if nil' do
|
191
|
+
parameter.type = nil
|
192
|
+
expect(serialized_parameter[:type]).to eq 'string'
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe '#validate!' do
|
199
|
+
it 'validates against Swagger2Validator' do
|
200
|
+
expect(Swaggable::Swagger2Validator).
|
201
|
+
to receive(:validate!).
|
202
|
+
with(subject.serialize api).
|
203
|
+
and_return(true)
|
204
|
+
|
205
|
+
subject.validate! api
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'json'
|
3
|
+
require 'json-schema'
|
4
|
+
|
5
|
+
RSpec.describe 'Swaggable::Swagger2Validator' do
|
6
|
+
describe '.validate!' do
|
7
|
+
subject { Swaggable::Swagger2Validator }
|
8
|
+
|
9
|
+
let(:valid_swagger) { JSON.parse File.read('spec/assets/valid-swagger-2.0.json') }
|
10
|
+
let(:invalid_swagger) { valid_swagger.merge("info" => nil) }
|
11
|
+
|
12
|
+
it 'returns true for a valid schema' do
|
13
|
+
expect(subject.validate! valid_swagger).to be true
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'raises exception true for an invalid schema' do
|
17
|
+
expect{ subject.validate! invalid_swagger }.
|
18
|
+
to raise_error(JSON::Schema::ValidationError)#(Swaggable::Swagger2Validator::ValidationError)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'has a meaningful exception message' do
|
22
|
+
exception = subject.validate!(invalid_swagger) rescue $!
|
23
|
+
expect(exception.message).to match(/info/)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|