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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +4 -0
  4. data/.rubocop_todo.yml +29 -32
  5. data/.travis.yml +2 -0
  6. data/CHANGELOG.md +15 -0
  7. data/Gemfile +1 -1
  8. data/LICENSE.txt +1 -1
  9. data/README.md +253 -223
  10. data/UPGRADING.md +4 -0
  11. data/grape-swagger.gemspec +2 -2
  12. data/lib/grape-swagger.rb +10 -4
  13. data/lib/grape-swagger/doc_methods.rb +10 -5
  14. data/lib/grape-swagger/doc_methods/build_model_definition.rb +38 -0
  15. data/lib/grape-swagger/doc_methods/data_type.rb +6 -5
  16. data/lib/grape-swagger/doc_methods/move_params.rb +9 -1
  17. data/lib/grape-swagger/doc_methods/optional_object.rb +1 -1
  18. data/lib/grape-swagger/doc_methods/parse_params.rb +20 -19
  19. data/lib/grape-swagger/doc_methods/tag_name_description.rb +9 -17
  20. data/lib/grape-swagger/endpoint.rb +26 -29
  21. data/lib/grape-swagger/model_parsers.rb +7 -0
  22. data/lib/grape-swagger/version.rb +1 -1
  23. data/spec/issues/427_entity_as_string_spec.rb +39 -0
  24. data/spec/issues/430_entity_definitions_spec.rb +48 -4
  25. data/spec/support/model_parsers/entity_parser.rb +10 -1
  26. data/spec/support/model_parsers/mock_parser.rb +4 -1
  27. data/spec/support/model_parsers/representable_parser.rb +13 -2
  28. data/spec/swagger_v2/api_swagger_v2_detail_spec.rb +0 -2
  29. data/spec/swagger_v2/api_swagger_v2_param_type_body_spec.rb +56 -0
  30. data/spec/swagger_v2/api_swagger_v2_spec.rb +87 -61
  31. data/spec/swagger_v2/endpoint_versioned_path_spec.rb +24 -3
  32. data/spec/swagger_v2/guarded_endpoint_spec.rb +8 -3
  33. data/spec/swagger_v2/namespace_tags_prefix_spec.rb +2 -8
  34. data/spec/swagger_v2/namespace_tags_spec.rb +2 -8
  35. data/spec/swagger_v2/params_array_spec.rb +36 -0
  36. data/spec/swagger_v2/simple_mounted_api_spec.rb +2 -14
  37. 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 :one_thing
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 :another_thing
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::Entity
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 'AnotherGroupingModuleClass1' }
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( /thing /other_thing /dummy )
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( /thing /other_thing /dummy )
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' => 'Representable::JSON' },
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( /thing /other_thing /dummy )
411
+ %w(/thing /other_thing /dummy)
401
412
  end
@@ -1,5 +1,3 @@
1
- # encoding: UTF-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
3
  def details
@@ -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
- before do
122
- get '/v3/swagger_doc'
123
- end
121
+ describe 'whole documentation' do
122
+ subject do
123
+ get '/v3/swagger_doc'
124
+ JSON.parse(last_response.body)
125
+ end
124
126
 
125
- let(:json) { JSON.parse(last_response.body) }
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
- describe 'swagger object' do
128
- describe 'required keys' do
129
- it { expect(json.keys).to include 'swagger' }
130
- it { expect(json['swagger']).to eql '2.0' }
131
- it { expect(json.keys).to include 'info' }
132
- it { expect(json['info']).to be_a Hash }
133
- it { expect(json.keys).to include 'paths' }
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
- describe 'info object required keys' do
138
- let(:info) { json['info'] }
145
+ describe 'license object' do
146
+ let(:license) { subject['info']['license'] }
139
147
 
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 }
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
- describe 'license object' do
146
- let(:license) { json['info']['license'] }
154
+ describe 'contact object' do
155
+ let(:contact) { subject['info']['contact'] }
147
156
 
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
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
- describe 'contact object' do
155
- let(:contact) { json['info']['contact'] }
165
+ describe 'global tags' do
166
+ let(:tags) { subject['tags'] }
156
167
 
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 }
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
- describe 'path object' do
167
- let(:paths) { json['paths'] }
173
+ describe 'path object' do
174
+ let(:paths) { subject['paths'] }
168
175
 
169
- it 'hides documentation paths per default' do
170
- expect(paths.keys).not_to include '/swagger_doc', '/swagger_doc/{name}'
171
- end
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
- specify do
174
- paths.each_pair do |path, value|
175
- expect(path).to start_with('/')
176
- expect(value).to be_a Hash
177
- expect(value).not_to be_empty
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
- value.each do |method, declaration|
180
- expect(http_verbs).to include method
181
- expect(declaration).to have_key('responses')
186
+ value.each do |method, declaration|
187
+ expect(http_verbs).to include method
188
+ expect(declaration).to have_key('responses')
182
189
 
183
- declaration['responses'].each do |status_code, response|
184
- expect(status_code).to match(/\d{3}/)
185
- expect(response).to have_key('description')
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
- describe 'definitions object' do
193
- let(:definitions) { json['definitions'] }
194
- specify do
195
- definitions.each do |model, properties|
196
- expect(model).to match(/\w+/)
197
- expect(properties).to have_key('properties')
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 'swagger file' do
204
- it do
205
- expect(json).to eql swagger_json
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(:api) do
5
- item = Class.new(Grape::API) do
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 item
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