grape-swagger 0.24.0 → 0.25.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 +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
|