Skip to content

Commit 97c2348

Browse files
committed
Set response headers based on Rack version
1 parent 4e2a2ff commit 97c2348

File tree

12 files changed

+81
-35
lines changed

12 files changed

+81
-35
lines changed

.github/workflows/test.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@ jobs:
6363
if: ${{ matrix.gemfile == 'multi_xml' }}
6464
run: bundle exec rspec spec/integration/multi_xml
6565

66+
- name: Run tests (spec/integration/rack/v2)
67+
# rack_2_0.gemfile is equals to Gemfile
68+
if: ${{ matrix.gemfile == 'rack_2_0' }}
69+
run: bundle exec rspec spec/integration/rack/v2
70+
71+
- name: Run tests (spec/integration/rack/v3)
72+
# rack_2_0.gemfile is equals to Gemfile
73+
if: ${{ matrix.gemfile == 'rack_3_0' }}
74+
run: bundle exec rspec spec/integration/rack/v3
75+
6676
- name: Coveralls
6777
uses: coverallsapp/github-action@master
6878
with:

.rubocop_todo.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ RSpec/FilePath:
280280
- 'spec/integration/eager_load/eager_load_spec.rb'
281281
- 'spec/integration/multi_json/json_spec.rb'
282282
- 'spec/integration/multi_xml/xml_spec.rb'
283+
- 'spec/integration/rack/v2/headers_spec.rb'
284+
- 'spec/integration/rack/v3/headers_spec.rb'
283285

284286
# Offense count: 12
285287
# Configuration parameters: Max.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#### Features
44

55
* [#2353](https://github.com/ruby-grape/grape/pull/2353): Added Rails 7.1 support - [@ericproulx](https://github.com/ericproulx).
6+
* [#2355](https://github.com/ruby-grape/grape/pull/2355): Set headers based on Rack version - [@schinery](https://github.com/schinery).
67
* Your contribution here.
78

89
#### Fixes

lib/grape/http/headers.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ module Headers
1010
PATH_INFO = 'PATH_INFO'
1111
REQUEST_METHOD = 'REQUEST_METHOD'
1212
QUERY_STRING = 'QUERY_STRING'
13-
CONTENT_TYPE = 'Content-Type'
13+
14+
if Gem::Version.new(Rack::RELEASE) < Gem::Version.new('3')
15+
CONTENT_TYPE = 'Content-Type'
16+
X_CASCADE = 'X-Cascade'
17+
else
18+
CONTENT_TYPE = 'content-type'
19+
X_CASCADE = 'x-cascade'
20+
end
1421

1522
GET = 'GET'
1623
POST = 'POST'
@@ -24,7 +31,6 @@ module Headers
2431
SUPPORTED_METHODS_WITHOUT_OPTIONS = Grape::Util::LazyObject.new { [GET, POST, PUT, PATCH, DELETE, HEAD].freeze }
2532

2633
HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION'
27-
X_CASCADE = 'X-Cascade'
2834
HTTP_TRANSFER_ENCODING = 'HTTP_TRANSFER_ENCODING'
2935
HTTP_ACCEPT = 'HTTP_ACCEPT'
3036

spec/grape/api_spec.rb

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,7 @@ class DummyFormatClass
689689
'example'
690690
end
691691
put '/example'
692-
expect(last_response.headers['Content-Type']).to eql 'text/plain'
692+
expect(last_response.headers[content_type_header]).to eql 'text/plain'
693693
end
694694

695695
describe 'adds an OPTIONS route that' do
@@ -1196,7 +1196,7 @@ class DummyFormatClass
11961196

11971197
it 'sets content type for txt format' do
11981198
get '/foo'
1199-
expect(last_response.headers['Content-Type']).to eq('text/plain')
1199+
expect(last_response.headers[content_type_header]).to eq('text/plain')
12001200
end
12011201

12021202
it 'does not set Cache-Control' do
@@ -1206,22 +1206,22 @@ class DummyFormatClass
12061206

12071207
it 'sets content type for xml' do
12081208
get '/foo.xml'
1209-
expect(last_response.headers['Content-Type']).to eq('application/xml')
1209+
expect(last_response.headers[content_type_header]).to eq('application/xml')
12101210
end
12111211

12121212
it 'sets content type for json' do
12131213
get '/foo.json'
1214-
expect(last_response.headers['Content-Type']).to eq('application/json')
1214+
expect(last_response.headers[content_type_header]).to eq('application/json')
12151215
end
12161216

12171217
it 'sets content type for serializable hash format' do
12181218
get '/foo.serializable_hash'
1219-
expect(last_response.headers['Content-Type']).to eq('application/json')
1219+
expect(last_response.headers[content_type_header]).to eq('application/json')
12201220
end
12211221

12221222
it 'sets content type for binary format' do
12231223
get '/foo.binary'
1224-
expect(last_response.headers['Content-Type']).to eq('application/octet-stream')
1224+
expect(last_response.headers[content_type_header]).to eq('application/octet-stream')
12251225
end
12261226

12271227
it 'returns raw data when content type binary' do
@@ -1230,7 +1230,7 @@ class DummyFormatClass
12301230
subject.format :binary
12311231
subject.get('/binary_file') { File.binread(image_filename) }
12321232
get '/binary_file'
1233-
expect(last_response.headers['Content-Type']).to eq('application/octet-stream')
1233+
expect(last_response.headers[content_type_header]).to eq('application/octet-stream')
12341234
expect(last_response.body).to eq(file)
12351235
end
12361236

@@ -1243,7 +1243,7 @@ class DummyFormatClass
12431243
subject.get('/file') { file test_file }
12441244
get '/file'
12451245
expect(last_response.headers['Content-Length']).to eq('25')
1246-
expect(last_response.headers['Content-Type']).to eq('text/plain')
1246+
expect(last_response.headers[content_type_header]).to eq('text/plain')
12471247
expect(last_response.body).to eq(file_content)
12481248
end
12491249

@@ -1257,7 +1257,7 @@ class DummyFormatClass
12571257
subject.get('/stream') { stream test_stream }
12581258
get '/stream', {}, 'HTTP_VERSION' => 'HTTP/1.1', 'SERVER_PROTOCOL' => 'HTTP/1.1'
12591259

1260-
expect(last_response.headers['Content-Type']).to eq('text/plain')
1260+
expect(last_response.headers[content_type_header]).to eq('text/plain')
12611261
expect(last_response.headers['Content-Length']).to be_nil
12621262
expect(last_response.headers['Cache-Control']).to eq('no-cache')
12631263
expect(last_response.headers['Transfer-Encoding']).to eq('chunked')
@@ -1268,23 +1268,23 @@ class DummyFormatClass
12681268
it 'sets content type for error' do
12691269
subject.get('/error') { error!('error in plain text', 500) }
12701270
get '/error'
1271-
expect(last_response.headers['Content-Type']).to eql 'text/plain'
1271+
expect(last_response.headers[content_type_header]).to eql 'text/plain'
12721272
end
12731273

12741274
it 'sets content type for json error' do
12751275
subject.format :json
12761276
subject.get('/error') { error!('error in json', 500) }
12771277
get '/error.json'
12781278
expect(last_response.status).to be 500
1279-
expect(last_response.headers['Content-Type']).to eql 'application/json'
1279+
expect(last_response.headers[content_type_header]).to eql 'application/json'
12801280
end
12811281

12821282
it 'sets content type for xml error' do
12831283
subject.format :xml
12841284
subject.get('/error') { error!('error in xml', 500) }
12851285
get '/error'
12861286
expect(last_response.status).to be 500
1287-
expect(last_response.headers['Content-Type']).to eql 'application/xml'
1287+
expect(last_response.headers[content_type_header]).to eql 'application/xml'
12881288
end
12891289

12901290
it 'includes extension in format' do
@@ -1314,12 +1314,12 @@ class DummyFormatClass
13141314

13151315
it 'sets content type' do
13161316
get '/custom.custom'
1317-
expect(last_response.headers['Content-Type']).to eql 'application/custom'
1317+
expect(last_response.headers[content_type_header]).to eql 'application/custom'
13181318
end
13191319

13201320
it 'sets content type for error' do
13211321
get '/error.custom'
1322-
expect(last_response.headers['Content-Type']).to eql 'application/custom'
1322+
expect(last_response.headers[content_type_header]).to eql 'application/custom'
13231323
end
13241324
end
13251325

@@ -1339,7 +1339,7 @@ class DummyFormatClass
13391339
image_filename = 'grape.png'
13401340
post url, file: Rack::Test::UploadedFile.new(image_filename, 'image/png', true)
13411341
expect(last_response.status).to eq(201)
1342-
expect(last_response.headers['Content-Type']).to eq('image/png')
1342+
expect(last_response.headers[content_type_header]).to eq('image/png')
13431343
expect(last_response.headers['Content-Disposition']).to eq("attachment; filename*=UTF-8''grape.png")
13441344
File.open(image_filename, 'rb') do |io|
13451345
expect(last_response.body).to eq io.read
@@ -1351,7 +1351,7 @@ class DummyFormatClass
13511351
filename = __FILE__
13521352
post '/attachment.rb', file: Rack::Test::UploadedFile.new(filename, 'application/x-ruby', true)
13531353
expect(last_response.status).to eq(201)
1354-
expect(last_response.headers['Content-Type']).to eq('application/x-ruby')
1354+
expect(last_response.headers[content_type_header]).to eq('application/x-ruby')
13551355
expect(last_response.headers['Content-Disposition']).to eq("attachment; filename*=UTF-8''api_spec.rb")
13561356
File.open(filename, 'rb') do |io|
13571357
expect(last_response.body).to eq io.read
@@ -3311,7 +3311,7 @@ def static
33113311
it 'is able to cascade' do
33123312
subject.mount lambda { |env|
33133313
headers = {}
3314-
headers['X-Cascade'] == 'pass' if env['PATH_INFO'].exclude?('boo')
3314+
headers[x_cascade_header] == 'pass' if env['PATH_INFO'].exclude?('boo')
33153315
[200, headers, ['Farfegnugen']]
33163316
} => '/'
33173317

@@ -4081,14 +4081,14 @@ def before
40814081
subject.version 'v1', using: :path, cascade: true
40824082
get '/v1/hello'
40834083
expect(last_response.status).to eq(404)
4084-
expect(last_response.headers['X-Cascade']).to eq('pass')
4084+
expect(last_response.headers[x_cascade_header]).to eq('pass')
40854085
end
40864086

40874087
it 'does not cascade' do
40884088
subject.version 'v2', using: :path, cascade: false
40894089
get '/v2/hello'
40904090
expect(last_response.status).to eq(404)
4091-
expect(last_response.headers.keys).not_to include 'X-Cascade'
4091+
expect(last_response.headers.keys).not_to include x_cascade_header
40924092
end
40934093
end
40944094

@@ -4097,14 +4097,14 @@ def before
40974097
subject.cascade true
40984098
get '/hello'
40994099
expect(last_response.status).to eq(404)
4100-
expect(last_response.headers['X-Cascade']).to eq('pass')
4100+
expect(last_response.headers[x_cascade_header]).to eq('pass')
41014101
end
41024102

41034103
it 'does not cascade' do
41044104
subject.cascade false
41054105
get '/hello'
41064106
expect(last_response.status).to eq(404)
4107-
expect(last_response.headers.keys).not_to include 'X-Cascade'
4107+
expect(last_response.headers.keys).not_to include x_cascade_header
41084108
end
41094109
end
41104110
end

spec/grape/endpoint_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ def app
497497
end
498498

499499
it 'responses with given content type in headers' do
500-
expect(last_response.headers['Content-Type']).to eq 'application/json; charset=utf-8'
500+
expect(last_response.headers[content_type_header]).to eq 'application/json; charset=utf-8'
501501
end
502502
end
503503

spec/grape/exceptions/invalid_accept_header_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
shared_examples_for 'a not-cascaded request' do
2121
it 'does not include the X-Cascade=pass header' do
22-
expect(last_response.headers['X-Cascade']).to be_nil
22+
expect(last_response.headers[x_cascade_header]).to be_nil
2323
end
2424

2525
it 'does not accept the request' do
@@ -29,7 +29,7 @@
2929

3030
shared_examples_for 'a rescued request' do
3131
it 'does not include the X-Cascade=pass header' do
32-
expect(last_response.headers['X-Cascade']).to be_nil
32+
expect(last_response.headers[x_cascade_header]).to be_nil
3333
end
3434

3535
it 'does show rescue handler processing' do

spec/grape/middleware/versioner/accept_version_header_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
end.to throw_symbol(
3737
:error,
3838
status: 406,
39-
headers: { 'X-Cascade' => 'pass' },
39+
headers: { x_cascade_header => 'pass' },
4040
message: 'The requested version is not supported.'
4141
)
4242
end
@@ -65,7 +65,7 @@
6565
end.to throw_symbol(
6666
:error,
6767
status: 406,
68-
headers: { 'X-Cascade' => 'pass' },
68+
headers: { x_cascade_header => 'pass' },
6969
message: 'Accept-Version header must be set.'
7070
)
7171
end
@@ -76,7 +76,7 @@
7676
end.to throw_symbol(
7777
:error,
7878
status: 406,
79-
headers: { 'X-Cascade' => 'pass' },
79+
headers: { x_cascade_header => 'pass' },
8080
message: 'Accept-Version header must be set.'
8181
)
8282
end

spec/grape/middleware/versioner/header_spec.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
expect { subject.call('HTTP_ACCEPT' => 'application/vnd.othervendor+json').last }
8989
.to raise_exception do |exception|
9090
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
91-
expect(exception.headers).to eql('X-Cascade' => 'pass')
91+
expect(exception.headers).to eql(x_cascade_header => 'pass')
9292
expect(exception.status).to be 406
9393
expect(exception.message).to include 'API vendor not found'
9494
end
@@ -115,7 +115,7 @@
115115
expect { subject.call('HTTP_ACCEPT' => 'application/vnd.othervendor-v1+json').last }
116116
.to raise_exception do |exception|
117117
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
118-
expect(exception.headers).to eql('X-Cascade' => 'pass')
118+
expect(exception.headers).to eql(x_cascade_header => 'pass')
119119
expect(exception.status).to be 406
120120
expect(exception.message).to include('API vendor not found')
121121
end
@@ -143,7 +143,7 @@
143143
it 'fails with 406 Not Acceptable if version is invalid' do
144144
expect { subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v2+json').last }.to raise_exception do |exception|
145145
expect(exception).to be_a(Grape::Exceptions::InvalidVersionHeader)
146-
expect(exception.headers).to eql('X-Cascade' => 'pass')
146+
expect(exception.headers).to eql(x_cascade_header => 'pass')
147147
expect(exception.status).to be 406
148148
expect(exception.message).to include('API version not found')
149149
end
@@ -176,7 +176,7 @@
176176
it 'fails with 406 Not Acceptable if header is not set' do
177177
expect { subject.call({}).last }.to raise_exception do |exception|
178178
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
179-
expect(exception.headers).to eql('X-Cascade' => 'pass')
179+
expect(exception.headers).to eql(x_cascade_header => 'pass')
180180
expect(exception.status).to be 406
181181
expect(exception.message).to include('Accept header must be set.')
182182
end
@@ -185,7 +185,7 @@
185185
it 'fails with 406 Not Acceptable if header is empty' do
186186
expect { subject.call('HTTP_ACCEPT' => '').last }.to raise_exception do |exception|
187187
expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)
188-
expect(exception.headers).to eql('X-Cascade' => 'pass')
188+
expect(exception.headers).to eql(x_cascade_header => 'pass')
189189
expect(exception.status).to be 406
190190
expect(exception.message).to include('Accept header must be set.')
191191
end
@@ -262,7 +262,7 @@
262262
it 'fails with another version' do
263263
expect { subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v3+json') }.to raise_exception do |exception|
264264
expect(exception).to be_a(Grape::Exceptions::InvalidVersionHeader)
265-
expect(exception.headers).to eql('X-Cascade' => 'pass')
265+
expect(exception.headers).to eql(x_cascade_header => 'pass')
266266
expect(exception.status).to be 406
267267
expect(exception.message).to include('API version not found')
268268
end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# frozen_string_literal: true
2+
3+
describe Grape::Http::Headers do
4+
it { expect(described_class::CONTENT_TYPE).to eq('Content-Type') }
5+
it { expect(described_class::X_CASCADE).to eq('X-Cascade') }
6+
end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# frozen_string_literal: true
2+
3+
describe Grape::Http::Headers do
4+
it { expect(described_class::CONTENT_TYPE).to eq('content-type') }
5+
it { expect(described_class::X_CASCADE).to eq('x-cascade') }
6+
end

spec/support/headers_helpers.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
module Spec
4+
module Support
5+
module Helpers
6+
def content_type_header
7+
Grape::Http::Headers::CONTENT_TYPE
8+
end
9+
10+
def x_cascade_header
11+
Grape::Http::Headers::X_CASCADE
12+
end
13+
end
14+
end
15+
end

0 commit comments

Comments
 (0)