grape-swagger 0.24.0 → 0.25.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +4 -0
- data/.rubocop_todo.yml +29 -32
- data/.travis.yml +2 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +253 -223
- data/UPGRADING.md +4 -0
- data/grape-swagger.gemspec +2 -2
- data/lib/grape-swagger.rb +10 -4
- data/lib/grape-swagger/doc_methods.rb +10 -5
- data/lib/grape-swagger/doc_methods/build_model_definition.rb +38 -0
- data/lib/grape-swagger/doc_methods/data_type.rb +6 -5
- data/lib/grape-swagger/doc_methods/move_params.rb +9 -1
- data/lib/grape-swagger/doc_methods/optional_object.rb +1 -1
- data/lib/grape-swagger/doc_methods/parse_params.rb +20 -19
- data/lib/grape-swagger/doc_methods/tag_name_description.rb +9 -17
- data/lib/grape-swagger/endpoint.rb +26 -29
- data/lib/grape-swagger/model_parsers.rb +7 -0
- data/lib/grape-swagger/version.rb +1 -1
- data/spec/issues/427_entity_as_string_spec.rb +39 -0
- data/spec/issues/430_entity_definitions_spec.rb +48 -4
- data/spec/support/model_parsers/entity_parser.rb +10 -1
- data/spec/support/model_parsers/mock_parser.rb +4 -1
- data/spec/support/model_parsers/representable_parser.rb +13 -2
- data/spec/swagger_v2/api_swagger_v2_detail_spec.rb +0 -2
- data/spec/swagger_v2/api_swagger_v2_param_type_body_spec.rb +56 -0
- data/spec/swagger_v2/api_swagger_v2_spec.rb +87 -61
- data/spec/swagger_v2/endpoint_versioned_path_spec.rb +24 -3
- data/spec/swagger_v2/guarded_endpoint_spec.rb +8 -3
- data/spec/swagger_v2/namespace_tags_prefix_spec.rb +2 -8
- data/spec/swagger_v2/namespace_tags_spec.rb +2 -8
- data/spec/swagger_v2/params_array_spec.rb +36 -0
- data/spec/swagger_v2/simple_mounted_api_spec.rb +2 -14
- metadata +12 -9
@@ -10,19 +10,53 @@ describe 'definition names' do
|
|
10
10
|
module AnotherGroupingModule
|
11
11
|
class Class1
|
12
12
|
class Entity < Grape::Entity
|
13
|
-
expose :
|
13
|
+
expose :first_thing
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
17
|
class Class2
|
18
|
+
class Entities < Grape::Entity
|
19
|
+
expose :second_thing
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Class3
|
18
24
|
class Entity < Grape::Entity
|
19
|
-
expose :
|
25
|
+
expose :third_thing
|
20
26
|
|
21
27
|
def self.entity_name
|
22
28
|
'FooKlass'
|
23
29
|
end
|
24
30
|
end
|
25
31
|
end
|
32
|
+
|
33
|
+
class Class4
|
34
|
+
class FourthEntity < Grape::Entity
|
35
|
+
expose :fourth_thing
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Class5
|
40
|
+
class FithEntity < Class4::FourthEntity
|
41
|
+
expose :fith_thing
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Class6
|
46
|
+
class SixthEntity < Grape::Entity
|
47
|
+
expose :sixth_thing
|
48
|
+
|
49
|
+
def self.entity_name
|
50
|
+
'BarKlass'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Class7
|
56
|
+
class SeventhEntity < Class6::SixthEntity
|
57
|
+
expose :seventh_thing
|
58
|
+
end
|
59
|
+
end
|
26
60
|
end
|
27
61
|
end
|
28
62
|
end
|
@@ -30,7 +64,12 @@ describe 'definition names' do
|
|
30
64
|
class NameApi < Grape::API
|
31
65
|
add_swagger_documentation models: [
|
32
66
|
DummyEntities::WithVeryLongName::AnotherGroupingModule::Class1::Entity,
|
33
|
-
DummyEntities::WithVeryLongName::AnotherGroupingModule::Class2::
|
67
|
+
DummyEntities::WithVeryLongName::AnotherGroupingModule::Class2::Entities,
|
68
|
+
DummyEntities::WithVeryLongName::AnotherGroupingModule::Class3::Entity,
|
69
|
+
DummyEntities::WithVeryLongName::AnotherGroupingModule::Class4::FourthEntity,
|
70
|
+
DummyEntities::WithVeryLongName::AnotherGroupingModule::Class5::FithEntity,
|
71
|
+
DummyEntities::WithVeryLongName::AnotherGroupingModule::Class6::SixthEntity,
|
72
|
+
DummyEntities::WithVeryLongName::AnotherGroupingModule::Class7::SeventhEntity
|
34
73
|
]
|
35
74
|
end
|
36
75
|
end
|
@@ -43,6 +82,11 @@ describe 'definition names' do
|
|
43
82
|
JSON.parse(last_response.body)['definitions']
|
44
83
|
end
|
45
84
|
|
46
|
-
specify { expect(subject).to include '
|
85
|
+
specify { expect(subject).to include 'Class1' }
|
86
|
+
specify { expect(subject).to include 'Class2' }
|
47
87
|
specify { expect(subject).to include 'FooKlass' }
|
88
|
+
specify { expect(subject).to include 'FourthEntity' }
|
89
|
+
specify { expect(subject).to include 'FithEntity' }
|
90
|
+
specify { expect(subject).to include 'BarKlass' }
|
91
|
+
specify { expect(subject).to include 'SeventhEntity' }
|
48
92
|
end
|
@@ -57,6 +57,13 @@ RSpec.shared_context 'entity swagger example' do
|
|
57
57
|
expose :message, documentation: { type: String, desc: 'error message' }
|
58
58
|
end
|
59
59
|
|
60
|
+
module NestedModule
|
61
|
+
class ApiResponse < Grape::Entity
|
62
|
+
expose :status, documentation: { type: String }
|
63
|
+
expose :error, documentation: { type: ::Entities::ApiError }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
60
67
|
class SecondApiError < Grape::Entity
|
61
68
|
expose :code, documentation: { type: Integer }
|
62
69
|
expose :severity, documentation: { type: String }
|
@@ -297,11 +304,13 @@ RSpec.shared_context 'entity swagger example' do
|
|
297
304
|
'definitions' => {
|
298
305
|
'QueryInput' => {
|
299
306
|
'type' => 'object',
|
307
|
+
'required' => ['elements'],
|
300
308
|
'properties' => { 'elements' => { 'type' => 'array', 'items' => { '$ref' => '#/definitions/QueryInputElement' }, 'description' => 'Set of configuration' } },
|
301
309
|
'description' => 'nested route inside namespace'
|
302
310
|
},
|
303
311
|
'QueryInputElement' => {
|
304
312
|
'type' => 'object',
|
313
|
+
'required' => %w(key value),
|
305
314
|
'properties' => { 'key' => { 'type' => 'string', 'description' => 'Name of parameter' }, 'value' => { 'type' => 'string', 'description' => 'Value of parameter' } }
|
306
315
|
},
|
307
316
|
'ApiError' => {
|
@@ -327,5 +336,5 @@ RSpec.shared_context 'entity swagger example' do
|
|
327
336
|
end
|
328
337
|
|
329
338
|
def mounted_paths
|
330
|
-
%w(
|
339
|
+
%w(/thing /other_thing /dummy)
|
331
340
|
end
|
@@ -53,6 +53,9 @@ RSpec.shared_context 'mock swagger example' do
|
|
53
53
|
class SecondApiError < OpenStruct; end
|
54
54
|
class RecursiveModel < OpenStruct; end
|
55
55
|
class DocumentedHashAndArrayModel < OpenStruct; end
|
56
|
+
module NestedModule
|
57
|
+
class ApiResponse < OpenStruct; end
|
58
|
+
end
|
56
59
|
end
|
57
60
|
end
|
58
61
|
|
@@ -329,5 +332,5 @@ RSpec.shared_context 'mock swagger example' do
|
|
329
332
|
end
|
330
333
|
|
331
334
|
def mounted_paths
|
332
|
-
%w(
|
335
|
+
%w(/thing /other_thing /dummy)
|
333
336
|
end
|
@@ -91,6 +91,15 @@ RSpec.shared_context 'representable swagger example' do
|
|
91
91
|
property :message, documentation: { type: String, desc: 'error message' }
|
92
92
|
end
|
93
93
|
|
94
|
+
module NestedModule
|
95
|
+
class ApiResponse < Representable::Decorator
|
96
|
+
include Representable::JSON
|
97
|
+
|
98
|
+
property :status, documentation: { type: String }
|
99
|
+
property :error, documentation: { type: ::Entities::ApiError }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
94
103
|
class SecondApiError < Representable::Decorator
|
95
104
|
include Representable::JSON
|
96
105
|
|
@@ -236,7 +245,7 @@ RSpec.shared_context 'representable swagger example' do
|
|
236
245
|
'prop_file' => { 'description' => 'prop_file description', 'type' => 'file' },
|
237
246
|
'prop_float' => { 'description' => 'prop_float description', 'type' => 'number', 'format' => 'float' },
|
238
247
|
'prop_integer' => { 'description' => 'prop_integer description', 'type' => 'integer', 'format' => 'int32' },
|
239
|
-
'prop_json' => { 'description' => 'prop_json description', 'type' => '
|
248
|
+
'prop_json' => { 'description' => 'prop_json description', 'type' => 'JSON' },
|
240
249
|
'prop_long' => { 'description' => 'prop_long description', 'type' => 'integer', 'format' => 'int64' },
|
241
250
|
'prop_password' => { 'description' => 'prop_password description', 'type' => 'string', 'format' => 'password' },
|
242
251
|
'prop_string' => { 'description' => 'prop_string description', 'type' => 'string' },
|
@@ -367,11 +376,13 @@ RSpec.shared_context 'representable swagger example' do
|
|
367
376
|
'definitions' => {
|
368
377
|
'QueryInput' => {
|
369
378
|
'type' => 'object',
|
379
|
+
'required' => ['elements'],
|
370
380
|
'properties' => { 'elements' => { 'type' => 'array', 'items' => { '$ref' => '#/definitions/QueryInputElement' }, 'description' => 'Set of configuration' } },
|
371
381
|
'description' => 'nested route inside namespace'
|
372
382
|
},
|
373
383
|
'QueryInputElement' => {
|
374
384
|
'type' => 'object',
|
385
|
+
'required' => %w(key value),
|
375
386
|
'properties' => { 'key' => { 'type' => 'string', 'description' => 'Name of parameter' }, 'value' => { 'type' => 'string', 'description' => 'Value of parameter' } }
|
376
387
|
},
|
377
388
|
'ApiError' => {
|
@@ -397,5 +408,5 @@ RSpec.shared_context 'representable swagger example' do
|
|
397
408
|
end
|
398
409
|
|
399
410
|
def mounted_paths
|
400
|
-
%w(
|
411
|
+
%w(/thing /other_thing /dummy)
|
401
412
|
end
|
@@ -54,6 +54,17 @@ describe 'setting of param type, such as `query`, `path`, `formData`, `body`, `h
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
+
namespace :with_entity_param do
|
58
|
+
desc 'put in body with entity parameter'
|
59
|
+
params do
|
60
|
+
optional :data, type: ::Entities::NestedModule::ApiResponse, documentation: { desc: 'request data' }
|
61
|
+
end
|
62
|
+
|
63
|
+
post do
|
64
|
+
{ 'declared_params' => declared(params) }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
57
68
|
add_swagger_documentation
|
58
69
|
end
|
59
70
|
end
|
@@ -156,4 +167,49 @@ describe 'setting of param type, such as `query`, `path`, `formData`, `body`, `h
|
|
156
167
|
)
|
157
168
|
end
|
158
169
|
end
|
170
|
+
|
171
|
+
describe 'complex entity given' do
|
172
|
+
let(:request_parameters_definition) do
|
173
|
+
[
|
174
|
+
{
|
175
|
+
'name' => 'WithEntityParam',
|
176
|
+
'in' => 'body',
|
177
|
+
'required' => true,
|
178
|
+
'schema' => {
|
179
|
+
'$ref' => '#/definitions/postWithEntityParam'
|
180
|
+
}
|
181
|
+
}
|
182
|
+
]
|
183
|
+
end
|
184
|
+
|
185
|
+
let(:request_body_parameters_definition) do
|
186
|
+
{
|
187
|
+
'type' => 'object',
|
188
|
+
'properties' => {
|
189
|
+
'data' => {
|
190
|
+
'$ref' => '#/definitions/ApiResponse',
|
191
|
+
'description' => 'request data'
|
192
|
+
}
|
193
|
+
},
|
194
|
+
'description' => 'put in body with entity parameter'
|
195
|
+
}
|
196
|
+
end
|
197
|
+
|
198
|
+
subject do
|
199
|
+
get '/swagger_doc/with_entity_param'
|
200
|
+
JSON.parse(last_response.body)
|
201
|
+
end
|
202
|
+
|
203
|
+
specify do
|
204
|
+
expect(subject['paths']['/with_entity_param']['post']['parameters']).to eql(request_parameters_definition)
|
205
|
+
end
|
206
|
+
|
207
|
+
specify do
|
208
|
+
expect(subject['definitions']['ApiResponse']).not_to be_nil
|
209
|
+
end
|
210
|
+
|
211
|
+
specify do
|
212
|
+
expect(subject['definitions']['postWithEntityParam']).to eql(request_body_parameters_definition)
|
213
|
+
end
|
214
|
+
end
|
159
215
|
end
|
@@ -118,91 +118,117 @@ describe 'swagger spec v2.0' do
|
|
118
118
|
end
|
119
119
|
end
|
120
120
|
|
121
|
-
|
122
|
-
|
123
|
-
|
121
|
+
describe 'whole documentation' do
|
122
|
+
subject do
|
123
|
+
get '/v3/swagger_doc'
|
124
|
+
JSON.parse(last_response.body)
|
125
|
+
end
|
124
126
|
|
125
|
-
|
127
|
+
describe 'swagger object' do
|
128
|
+
describe 'required keys' do
|
129
|
+
it { expect(subject.keys).to include 'swagger' }
|
130
|
+
it { expect(subject['swagger']).to eql '2.0' }
|
131
|
+
it { expect(subject.keys).to include 'info' }
|
132
|
+
it { expect(subject['info']).to be_a Hash }
|
133
|
+
it { expect(subject.keys).to include 'paths' }
|
134
|
+
it { expect(subject['paths']).to be_a Hash }
|
135
|
+
end
|
126
136
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
it { expect(json['paths']).to be_a Hash }
|
135
|
-
end
|
137
|
+
describe 'info object required keys' do
|
138
|
+
let(:info) { subject['info'] }
|
139
|
+
|
140
|
+
it { expect(info.keys).to include 'title' }
|
141
|
+
it { expect(info['title']).to be_a String }
|
142
|
+
it { expect(info.keys).to include 'version' }
|
143
|
+
it { expect(info['version']).to be_a String }
|
136
144
|
|
137
|
-
|
138
|
-
|
145
|
+
describe 'license object' do
|
146
|
+
let(:license) { subject['info']['license'] }
|
139
147
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
148
|
+
it { expect(license.keys).to include 'name' }
|
149
|
+
it { expect(license['name']).to be_a String }
|
150
|
+
it { expect(license.keys).to include 'url' }
|
151
|
+
it { expect(license['url']).to be_a String }
|
152
|
+
end
|
144
153
|
|
145
|
-
|
146
|
-
|
154
|
+
describe 'contact object' do
|
155
|
+
let(:contact) { subject['info']['contact'] }
|
147
156
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
157
|
+
it { expect(contact.keys).to include 'name' }
|
158
|
+
it { expect(contact['name']).to be_a String }
|
159
|
+
it { expect(contact.keys).to include 'email' }
|
160
|
+
it { expect(contact['email']).to be_a String }
|
161
|
+
it { expect(contact.keys).to include 'url' }
|
162
|
+
it { expect(contact['url']).to be_a String }
|
163
|
+
end
|
153
164
|
|
154
|
-
|
155
|
-
|
165
|
+
describe 'global tags' do
|
166
|
+
let(:tags) { subject['tags'] }
|
156
167
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
it { expect(contact['email']).to be_a String }
|
161
|
-
it { expect(contact.keys).to include 'url' }
|
162
|
-
it { expect(contact['url']).to be_a String }
|
168
|
+
it { expect(tags).to be_a Array }
|
169
|
+
it { expect(tags).not_to be_empty }
|
170
|
+
end
|
163
171
|
end
|
164
|
-
end
|
165
172
|
|
166
|
-
|
167
|
-
|
173
|
+
describe 'path object' do
|
174
|
+
let(:paths) { subject['paths'] }
|
168
175
|
|
169
|
-
|
170
|
-
|
171
|
-
|
176
|
+
it 'hides documentation paths per default' do
|
177
|
+
expect(paths.keys).not_to include '/swagger_doc', '/swagger_doc/{name}'
|
178
|
+
end
|
172
179
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
180
|
+
specify do
|
181
|
+
paths.each_pair do |path, value|
|
182
|
+
expect(path).to start_with('/')
|
183
|
+
expect(value).to be_a Hash
|
184
|
+
expect(value).not_to be_empty
|
178
185
|
|
179
|
-
|
180
|
-
|
181
|
-
|
186
|
+
value.each do |method, declaration|
|
187
|
+
expect(http_verbs).to include method
|
188
|
+
expect(declaration).to have_key('responses')
|
182
189
|
|
183
|
-
|
184
|
-
|
185
|
-
|
190
|
+
declaration['responses'].each do |status_code, response|
|
191
|
+
expect(status_code).to match(/\d{3}/)
|
192
|
+
expect(response).to have_key('description')
|
193
|
+
end
|
186
194
|
end
|
187
195
|
end
|
188
196
|
end
|
189
197
|
end
|
190
|
-
end
|
191
198
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
199
|
+
describe 'definitions object' do
|
200
|
+
let(:definitions) { subject['definitions'] }
|
201
|
+
|
202
|
+
specify do
|
203
|
+
definitions.each do |model, properties|
|
204
|
+
expect(model).to match(/\w+/)
|
205
|
+
expect(properties).to have_key('properties')
|
206
|
+
end
|
198
207
|
end
|
199
208
|
end
|
200
209
|
end
|
210
|
+
|
211
|
+
describe 'swagger file' do
|
212
|
+
it do
|
213
|
+
expect(subject).to eql swagger_json
|
214
|
+
end
|
215
|
+
end
|
201
216
|
end
|
202
217
|
|
203
|
-
describe '
|
204
|
-
|
205
|
-
|
218
|
+
describe 'specific resource documentation' do
|
219
|
+
subject do
|
220
|
+
get '/v3/swagger_doc/other_thing'
|
221
|
+
JSON.parse(last_response.body)
|
222
|
+
end
|
223
|
+
|
224
|
+
let(:tags) { subject['tags'] }
|
225
|
+
specify do
|
226
|
+
expect(tags).to eql [
|
227
|
+
{
|
228
|
+
'name' => 'other_thing',
|
229
|
+
'description' => 'Operations about other_things'
|
230
|
+
}
|
231
|
+
]
|
206
232
|
end
|
207
233
|
end
|
208
234
|
end
|
@@ -1,17 +1,21 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'Grape::Endpoint#path_and_definitions' do
|
4
|
-
let(:
|
5
|
-
|
4
|
+
let(:item) do
|
5
|
+
Class.new(Grape::API) do
|
6
6
|
version 'v1', using: :path
|
7
7
|
|
8
8
|
resource :item do
|
9
9
|
get '/'
|
10
10
|
end
|
11
11
|
end
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:api) do
|
15
|
+
item_api = item
|
12
16
|
|
13
17
|
Class.new(Grape::API) do
|
14
|
-
mount
|
18
|
+
mount item_api
|
15
19
|
add_swagger_documentation add_version: true
|
16
20
|
end
|
17
21
|
end
|
@@ -28,4 +32,21 @@ describe 'Grape::Endpoint#path_and_definitions' do
|
|
28
32
|
it 'tags the endpoint with the resource name' do
|
29
33
|
expect(subject.first['/v1/item'][:get][:tags]).to eq ['item']
|
30
34
|
end
|
35
|
+
|
36
|
+
context 'when custom tags are specified' do
|
37
|
+
let(:item) do
|
38
|
+
Class.new(Grape::API) do
|
39
|
+
version 'v1', using: :path
|
40
|
+
|
41
|
+
resource :item do
|
42
|
+
desc 'Item description', tags: ['special-item']
|
43
|
+
get '/'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'tags the endpoint with the custom tags' do
|
49
|
+
expect(subject.first['/v1/item'][:get][:tags]).to eq ['special-item']
|
50
|
+
end
|
51
|
+
end
|
31
52
|
end
|