Skip to content

Commit 1ebeb33

Browse files
blakenumbata
authored andcommitted
Fix file response body
1 parent 7199caf commit 1ebeb33

File tree

2 files changed

+338
-13
lines changed

2 files changed

+338
-13
lines changed

lib/grape-swagger/openapi_3/endpoint.rb

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,7 @@ def response_body_object(_, _, consumes, parameters)
216216
result << response_body_parameter_object(form_params, 'application/x-www-form-urlencoded')
217217
end
218218

219-
unless file_params.empty?
220-
result << response_body_parameter_object(file_params, 'application/octet-stream')
221-
end
219+
result << response_body_parameter_object(file_params, 'application/octet-stream') unless file_params.empty?
222220

223221
{ content: result.to_h }
224222
end
@@ -246,9 +244,17 @@ def response_object(route, content_types)
246244
value[:message] ||= ''
247245
memo[value[:code]] = { description: value[:message] }
248246

249-
memo[value[:code]][:headers] = value[:headers] if value[:headers]
247+
if value[:headers]
248+
value[:headers].each do |_, header|
249+
header[:schema] = { type: header.delete(:type) }
250+
end
251+
memo[value[:code]][:headers] = value[:headers]
252+
end
250253

251-
next build_file_response(memo[value[:code]]) if file_response?(value[:model])
254+
if file_response?(value[:model])
255+
memo[value[:code]][:content] = [content_object(value, value[:model], {}, 'application/octet-stream')].to_h
256+
next
257+
end
252258

253259
response_model = @item
254260
response_model = expose_params_from_model(value[:model]) if value[:model]
@@ -263,19 +269,29 @@ def response_object(route, content_types)
263269
model = !response_model.start_with?('Swagger_doc') && (@definitions[response_model] || value[:model])
264270

265271
ref = build_reference(route, value, response_model)
266-
memo[value[:code]][:content] = content_types.map do |c|
267-
if model
268-
[c, { schema: ref }]
269-
else
270-
[c, {}]
271-
end
272-
end.to_h
272+
273+
memo[value[:code]][:content] = content_types.map { |c| content_object(value, model, ref, c) }.to_h
273274

274275
next unless model
275276

276277
@definitions[response_model][:description] = description_object(route)
278+
end
279+
end
277280

278-
memo[value[:code]][:examples] = value[:examples] if value[:examples]
281+
def content_object(value, model, ref, content_type)
282+
if model
283+
hash = { schema: ref }
284+
if value[:examples]
285+
if value[:examples].keys.length == 1
286+
hash[:example] = value[:examples].values.first
287+
else
288+
hash[:examples] = value[:examples].map { |k, v| [k, { value: v }] }.to_h
289+
end
290+
end
291+
292+
[content_type, hash]
293+
else
294+
[content_type, {}]
279295
end
280296
end
281297

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe 'response with headers' do
6+
include_context "#{MODEL_PARSER} swagger example"
7+
8+
before :all do
9+
module TheApi
10+
class ResponseApiHeaders < Grape::API
11+
format :json
12+
13+
desc 'This returns headers' do
14+
success model: Entities::UseResponse, headers: { 'Location' => { description: 'Location of resource', type: 'string' } }
15+
failure [[404, 'NotFound', Entities::ApiError, { 'application/json' => { code: 404, message: 'Not found' } }, { 'Date' => { description: 'Date of failure', type: 'string' } }]]
16+
end
17+
get '/response_headers' do
18+
{ 'declared_params' => declared(params) }
19+
end
20+
21+
desc 'A 204 can have headers too' do
22+
success Hash[status: 204, message: 'No content', headers: { 'Location' => { description: 'Location of resource', type: 'string' } }]
23+
failure [[400, 'Bad Request', Entities::ApiError, { 'application/json' => { code: 400, message: 'Bad request' } }, { 'Date' => { description: 'Date of failure', type: 'string' } }]]
24+
end
25+
delete '/no_content_response_headers' do
26+
{ 'declared_params' => declared(params) }
27+
end
28+
29+
desc 'A file can have headers too' do
30+
success Hash[status: 200, model: 'File', headers: { 'Cache-Control' => { description: 'Directive for caching', type: 'string' } }]
31+
failure [[404, 'NotFound', Entities::ApiError, { 'application/json' => { code: 404, message: 'Not found' } }, { 'Date' => { description: 'Date of failure', type: 'string' } }]]
32+
end
33+
get '/file_response_headers' do
34+
{ 'declared_params' => declared(params) }
35+
end
36+
37+
desc 'This syntax also returns headers' do
38+
success model: Entities::UseResponse, headers: { 'Location' => { description: 'Location of resource', type: 'string' } }
39+
failure [
40+
{
41+
code: 404,
42+
message: 'NotFound',
43+
model: Entities::ApiError,
44+
headers: { 'Date' => { description: 'Date of failure', type: 'string' } }
45+
},
46+
{
47+
code: 400,
48+
message: 'BadRequest',
49+
model: Entities::ApiError,
50+
headers: { 'Date' => { description: 'Date of failure', type: 'string' } }
51+
}
52+
]
53+
end
54+
get '/response_failure_headers' do
55+
{ 'declared_params' => declared(params) }
56+
end
57+
58+
desc 'This does not return headers' do
59+
success model: Entities::UseResponse
60+
failure [[404, 'NotFound', Entities::ApiError]]
61+
end
62+
get '/response_no_headers' do
63+
{ 'declared_params' => declared(params) }
64+
end
65+
add_swagger_documentation openapi_version: '3.0'
66+
end
67+
end
68+
end
69+
70+
def app
71+
TheApi::ResponseApiHeaders
72+
end
73+
74+
describe 'response headers' do
75+
let(:header_200) do
76+
{ 'Location' => { 'description' => 'Location of resource', 'type' => 'string' } }
77+
end
78+
let(:header_404) do
79+
{ 'Date' => { 'description' => 'Date of failure', 'type' => 'string' } }
80+
end
81+
let(:examples_404) do
82+
{ 'application/json' => { 'code' => 404, 'message' => 'Not found' } }
83+
end
84+
85+
subject do
86+
get '/swagger_doc/response_headers'
87+
JSON.parse(last_response.body)
88+
end
89+
90+
specify do
91+
expect(subject['paths']['/response_headers']['get']).to eql(
92+
'description' => 'This returns headers',
93+
'operationId' => 'getResponseHeaders',
94+
'tags' => ['response_headers'],
95+
'responses' => {
96+
'200' => {
97+
'content' => {
98+
'application/json' => {
99+
'schema' => { '$ref' => '#/components/schemas/UseResponse' }
100+
}
101+
},
102+
'description' => 'This returns headers',
103+
'headers' => {
104+
'Location' => {
105+
'description' => 'Location of resource',
106+
'schema' => { 'type' => 'string' }
107+
}
108+
}
109+
},
110+
'404' => {
111+
'content' => {
112+
'application/json' => {
113+
'example' => {
114+
'code' => 404,
115+
'message' => 'Not found'
116+
},
117+
'schema' => { '$ref' => '#/components/schemas/ApiError' }
118+
}
119+
},
120+
'description' => 'NotFound',
121+
'headers' => {
122+
'Date' => {
123+
'description' => 'Date of failure',
124+
'schema' => { 'type' => 'string' }
125+
}
126+
}
127+
}
128+
}
129+
)
130+
end
131+
end
132+
133+
describe 'no content response headers' do
134+
let(:header_204) do
135+
{ 'Location' => { 'description' => 'Location of resource', 'type' => 'string' } }
136+
end
137+
let(:header_400) do
138+
{ 'Date' => { 'description' => 'Date of failure', 'type' => 'string' } }
139+
end
140+
let(:examples_400) do
141+
{ 'application/json' => { 'code' => 400, 'message' => 'Bad request' } }
142+
end
143+
144+
subject do
145+
get '/swagger_doc/no_content_response_headers', {}
146+
JSON.parse(last_response.body)
147+
end
148+
149+
specify do
150+
expect(subject['paths']['/no_content_response_headers']['delete']).to eql(
151+
'description' => 'A 204 can have headers too',
152+
'responses' => {
153+
'204' => {
154+
'description' => 'No content',
155+
'headers' => {
156+
'Location' => {
157+
'description' => 'Location of resource', 'schema' => { 'type' => 'string' }
158+
}
159+
}
160+
},
161+
'400' => {
162+
'content' => {
163+
'application/json' => {
164+
'example' => { 'code' => 400, 'message' => 'Bad request' },
165+
'schema' => { '$ref' => '#/components/schemas/ApiError' }
166+
}
167+
},
168+
'description' => 'Bad Request',
169+
'headers' => { 'Date' => { 'description' => 'Date of failure', 'schema' => { 'type' => 'string' } } }
170+
}
171+
},
172+
'tags' => ['no_content_response_headers'],
173+
'operationId' => 'deleteNoContentResponseHeaders'
174+
)
175+
end
176+
end
177+
178+
describe 'file response headers' do
179+
let(:header_200) do
180+
{ 'Cache-Control' => { 'description' => 'Directive for caching', 'type' => 'string' } }
181+
end
182+
let(:header_404) do
183+
{ 'Date' => { 'description' => 'Date of failure', 'type' => 'string' } }
184+
end
185+
let(:examples_404) do
186+
{ 'application/json' => { 'code' => 404, 'message' => 'Not found' } }
187+
end
188+
189+
subject do
190+
get '/swagger_doc/file_response_headers'
191+
puts last_response.body
192+
JSON.parse(last_response.body)
193+
end
194+
195+
specify do
196+
expect(subject['paths']['/file_response_headers']['get']).to eql(
197+
'description' => 'A file can have headers too',
198+
'responses' => {
199+
'200' => {
200+
'content' => {
201+
'application/octet-stream' => { 'schema' => {} }
202+
},
203+
'description' => 'A file can have headers too',
204+
'headers' => {
205+
'Cache-Control' => { 'description' => 'Directive for caching', 'schema' => { 'type' => 'string' } }
206+
}
207+
},
208+
'404' => {
209+
'content' => {
210+
'application/json' => {
211+
'example' => { 'code' => 404, 'message' => 'Not found' },
212+
'schema' => { '$ref' => '#/components/schemas/ApiError' }
213+
}
214+
},
215+
'description' => 'NotFound',
216+
'headers' => {
217+
'Date' => { 'description' => 'Date of failure', 'schema' => { 'type' => 'string' } }
218+
}
219+
}
220+
},
221+
'tags' => ['file_response_headers'],
222+
'operationId' => 'getFileResponseHeaders'
223+
)
224+
end
225+
end
226+
227+
describe 'response failure headers' do
228+
let(:header_200) do
229+
{ 'Location' => { 'description' => 'Location of resource', 'schema' => { 'type' => 'string' } } }
230+
end
231+
let(:header_404) do
232+
{ 'Date' => { 'description' => 'Date of failure', 'schema' => { 'type' => 'string' } } }
233+
end
234+
let(:header_400) do
235+
{ 'Date' => { 'description' => 'Date of failure', 'schema' => { 'type' => 'string' } } }
236+
end
237+
238+
subject do
239+
get '/swagger_doc/response_failure_headers'
240+
JSON.parse(last_response.body)
241+
end
242+
243+
specify do
244+
expect(subject['paths']['/response_failure_headers']['get']).to eql(
245+
'description' => 'This syntax also returns headers',
246+
'operationId' => 'getResponseFailureHeaders',
247+
'responses' => {
248+
'200' => {
249+
'content' => {
250+
'application/json' => {
251+
'schema' => { '$ref' => '#/components/schemas/UseResponse' }
252+
}
253+
},
254+
'description' => 'This syntax also returns headers',
255+
'headers' => header_200
256+
},
257+
'400' => {
258+
'content' => {
259+
'application/json' => { 'schema' => { '$ref' => '#/components/schemas/ApiError' } }
260+
},
261+
'description' => 'BadRequest',
262+
'headers' => header_400
263+
},
264+
'404' => {
265+
'content' => {
266+
'application/json' => { 'schema' => { '$ref' => '#/components/schemas/ApiError' } }
267+
},
268+
'description' => 'NotFound',
269+
'headers' => header_404
270+
}
271+
},
272+
'tags' => ['response_failure_headers']
273+
)
274+
end
275+
end
276+
277+
describe 'response no headers' do
278+
subject do
279+
get '/swagger_doc/response_no_headers'
280+
JSON.parse(last_response.body)
281+
end
282+
283+
specify do
284+
expect(subject['paths']['/response_no_headers']['get']).to eql(
285+
'description' => 'This does not return headers',
286+
'responses' => {
287+
'200' => {
288+
'content' => {
289+
'application/json' => {
290+
'schema' => { '$ref' => '#/components/schemas/UseResponse' }
291+
}
292+
},
293+
'description' => 'This does not return headers'
294+
},
295+
'404' => {
296+
'content' => {
297+
'application/json' => {
298+
'schema' => { '$ref' => '#/components/schemas/ApiError' }
299+
}
300+
},
301+
'description' => 'NotFound'
302+
}
303+
},
304+
'tags' => ['response_no_headers'],
305+
'operationId' => 'getResponseNoHeaders'
306+
)
307+
end
308+
end
309+
end

0 commit comments

Comments
 (0)