swaggable 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|