Skip to content

Commit 6209297

Browse files
author
Josh Murphy
committed
Use 415 status code when content type is not supported
1 parent d845a3a commit 6209297

15 files changed

+101
-17
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
### 1.0.4 (Next)
1+
### 1.1.0 (Next)
22

33
#### Features
44

@@ -10,6 +10,7 @@
1010
* [#1762](https://github.com/ruby-grape/grape/pull/1763): Fix unsafe HTML rendering on errors - [@ctennis](https://github.com/ctennis).
1111
* [#1759](https://github.com/ruby-grape/grape/pull/1759): Update appraisal for rails_edge - [@zvkemp](https://github.com/zvkemp).
1212
* [#1758](https://github.com/ruby-grape/grape/pull/1758): Fix expanding load_path in gemspec - [@2maz](https://github.com/2maz).
13+
* [#1765](https://github.com/ruby-grape/grape/pull/1765): Use 415 when request body is of an unsupported media type - [@jdmurphy](https://github.com/jdmurphy).
1314
* Your contribution here.
1415

1516
### 1.0.3 (4/23/2018)

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ content negotiation, versioning and much more.
145145

146146
## Stable Release
147147

148-
You're reading the documentation for the next release of Grape, which should be **1.0.4**.
148+
You're reading the documentation for the next release of Grape, which should be **1.1.0**.
149149
Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
150150
The current stable release is [1.0.3](https://github.com/ruby-grape/grape/blob/v1.0.3/README.md).
151151

@@ -2551,6 +2551,9 @@ Built-in formatters are the following.
25512551
* `:serializable_hash`: use object's `serializable_hash` when available, otherwise fallback to `:json`
25522552
* `:binary`: data will be returned "as is"
25532553

2554+
If a body is present in a request to an API, with a Content-Type header value that is of an unsupported type a
2555+
"415 Unsupported Media Type" error code will be returned by Grape.
2556+
25542557
Response statuses that indicate no content as defined by [Rack](https://github.com/rack)
25552558
[here](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567)
25562559
will bypass serialization and the body entity - though there should be none -

UPGRADING.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Upgrading Grape
22
===============
33

4+
### Upgrading to >= 1.1.0
5+
6+
#### Changes in HTTP Response Code for Unsupported Content Type
7+
8+
For PUT, POST, PATCH, and DELETE requests where a non-empty body and a "Content-Type" header is supplied that is not supported by the Grape API, Grape will no longer return a 406 "Not Acceptable" HTTP status code and will instead return a 415 "Unsupported Media Type" so that the usage of HTTP status code falls more in line with the specification of [RFC 2616](https://www.ietf.org/rfc/rfc2616.txt).
9+
410
### Upgrading to >= 1.0.0
511

612
#### Changes in XML and JSON Parsers

gemfiles/multi_json.gemfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ end
2222
group :test do
2323
gem 'cookiejar'
2424
gem 'coveralls', '~> 0.8.17', require: false
25-
gem 'danger-toc', '~> 0.1.0'
25+
gem 'danger-toc', '~> 0.1.2'
2626
gem 'grape-entity', '~> 0.6'
2727
gem 'maruku'
2828
gem 'mime-types'

gemfiles/multi_xml.gemfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ end
2222
group :test do
2323
gem 'cookiejar'
2424
gem 'coveralls', '~> 0.8.17', require: false
25-
gem 'danger-toc', '~> 0.1.0'
25+
gem 'danger-toc', '~> 0.1.2'
2626
gem 'grape-entity', '~> 0.6'
2727
gem 'maruku'
2828
gem 'mime-types'

gemfiles/rack_1.5.2.gemfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ end
2222
group :test do
2323
gem 'cookiejar'
2424
gem 'coveralls', '~> 0.8.17', require: false
25-
gem 'danger-toc', '~> 0.1.0'
25+
gem 'danger-toc', '~> 0.1.2'
2626
gem 'grape-entity', '~> 0.6'
2727
gem 'maruku'
2828
gem 'mime-types'

gemfiles/rack_edge.gemfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ end
2222
group :test do
2323
gem 'cookiejar'
2424
gem 'coveralls', '~> 0.8.17', require: false
25-
gem 'danger-toc', '~> 0.1.0'
25+
gem 'danger-toc', '~> 0.1.2'
2626
gem 'grape-entity', '~> 0.6'
2727
gem 'maruku'
2828
gem 'mime-types'

gemfiles/rails_3.gemfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ end
2323
group :test do
2424
gem 'cookiejar'
2525
gem 'coveralls', '~> 0.8.17', require: false
26-
gem 'danger-toc', '~> 0.1.0'
26+
gem 'danger-toc', '~> 0.1.2'
2727
gem 'grape-entity', '~> 0.6'
2828
gem 'maruku'
2929
gem 'mime-types'

gemfiles/rails_4.gemfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ end
2222
group :test do
2323
gem 'cookiejar'
2424
gem 'coveralls', '~> 0.8.17', require: false
25-
gem 'danger-toc', '~> 0.1.0'
25+
gem 'danger-toc', '~> 0.1.2'
2626
gem 'grape-entity', '~> 0.6'
2727
gem 'maruku'
2828
gem 'mime-types'

gemfiles/rails_5.gemfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ end
2222
group :test do
2323
gem 'cookiejar'
2424
gem 'coveralls', '~> 0.8.17', require: false
25-
gem 'danger-toc', '~> 0.1.0'
25+
gem 'danger-toc', '~> 0.1.2'
2626
gem 'grape-entity', '~> 0.6'
2727
gem 'maruku'
2828
gem 'mime-types'

gemfiles/rails_edge.gemfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ end
2222
group :test do
2323
gem 'cookiejar'
2424
gem 'coveralls', '~> 0.8.17', require: false
25-
gem 'danger-toc', '~> 0.1.0'
25+
gem 'danger-toc', '~> 0.1.2'
2626
gem 'grape-entity', '~> 0.6'
2727
gem 'maruku'
2828
gem 'mime-types'

lib/grape/middleware/formatter.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def read_rack_input(body)
9595
fmt = request.media_type ? mime_types[request.media_type] : options[:default_format]
9696

9797
unless content_type_for(fmt)
98-
throw :error, status: 406, message: "The requested content-type '#{request.media_type}' is not supported."
98+
throw :error, status: 415, message: "The provided content-type '#{request.media_type}' is not supported."
9999
end
100100
parser = Grape::Parser.parser_for fmt, options
101101
if parser

lib/grape/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
module Grape
22
# The current version of Grape.
3-
VERSION = '1.0.4'.freeze
3+
VERSION = '1.1.0'.freeze
44
end

spec/grape/endpoint_spec.rb

+5-5
Original file line numberDiff line numberDiff line change
@@ -941,15 +941,15 @@ def app
941941
end
942942
end
943943

944-
it 'responds with a 406 for an unsupported content-type' do
944+
it 'responds with a 415 for an unsupported content-type' do
945945
subject.format :json
946946
# subject.content_type :json, "application/json"
947947
subject.put '/request_body' do
948948
params[:user]
949949
end
950950
put '/request_body', '<user>Bobby T.</user>', 'CONTENT_TYPE' => 'application/xml'
951-
expect(last_response.status).to eq(406)
952-
expect(last_response.body).to eq('{"error":"The requested content-type \'application/xml\' is not supported."}')
951+
expect(last_response.status).to eq(415)
952+
expect(last_response.body).to eq('{"error":"The provided content-type \'application/xml\' is not supported."}')
953953
end
954954

955955
it 'does not accept text/plain in JSON format if application/json is specified as content type' do
@@ -960,8 +960,8 @@ def app
960960
end
961961
put '/request_body', ::Grape::Json.dump(user: 'Bob'), 'CONTENT_TYPE' => 'text/plain'
962962

963-
expect(last_response.status).to eq(406)
964-
expect(last_response.body).to eq('{"error":"The requested content-type \'text/plain\' is not supported."}')
963+
expect(last_response.status).to eq(415)
964+
expect(last_response.body).to eq('{"error":"The provided content-type \'text/plain\' is not supported."}')
965965
end
966966

967967
context 'content type with params' do

spec/grape/middleware/formatter_spec.rb

+74
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,80 @@ def to_xml
224224

225225
context 'input' do
226226
%w[POST PATCH PUT DELETE].each do |method|
227+
context 'when body is not nil or empty' do
228+
context 'when Content-Type is supported' do
229+
let(:io) { StringIO.new('{"is_boolean":true,"string":"thing"}') }
230+
let(:content_type) { 'application/json' }
231+
232+
it "parses the body from #{method} and copies values into rack.request.form_hash" do
233+
subject.call(
234+
'PATH_INFO' => '/info',
235+
'REQUEST_METHOD' => method,
236+
'CONTENT_TYPE' => content_type,
237+
'rack.input' => io,
238+
'CONTENT_LENGTH' => io.length
239+
)
240+
expect(subject.env['rack.request.form_hash']['is_boolean']).to be true
241+
expect(subject.env['rack.request.form_hash']['string']).to eq('thing')
242+
end
243+
end
244+
245+
context 'when Content-Type is not supported' do
246+
let(:io) { StringIO.new('{"is_boolean":true,"string":"thing"}') }
247+
let(:content_type) { 'application/atom+xml' }
248+
249+
it 'returns a 415 HTTP error status' do
250+
error = catch(:error) {
251+
subject.call(
252+
'PATH_INFO' => '/info',
253+
'REQUEST_METHOD' => method,
254+
'CONTENT_TYPE' => content_type,
255+
'rack.input' => io,
256+
'CONTENT_LENGTH' => io.length
257+
)
258+
}
259+
expect(error[:status]).to eq(415)
260+
expect(error[:message]).to eq("The provided content-type 'application/atom+xml' is not supported.")
261+
end
262+
end
263+
end
264+
265+
context 'when body is nil' do
266+
let(:io) { double }
267+
before do
268+
allow(io).to receive_message_chain(:rewind, :read).and_return(nil)
269+
end
270+
271+
it 'does not read and parse the body' do
272+
expect(subject).not_to receive(:read_rack_input)
273+
subject.call(
274+
'PATH_INFO' => '/info',
275+
'REQUEST_METHOD' => method,
276+
'CONTENT_TYPE' => 'application/json',
277+
'rack.input' => io,
278+
'CONTENT_LENGTH' => 0
279+
)
280+
end
281+
end
282+
283+
context 'when body is empty' do
284+
let(:io) { double }
285+
before do
286+
allow(io).to receive_message_chain(:rewind, :read).and_return('')
287+
end
288+
289+
it 'does not read and parse the body' do
290+
expect(subject).not_to receive(:read_rack_input)
291+
subject.call(
292+
'PATH_INFO' => '/info',
293+
'REQUEST_METHOD' => method,
294+
'CONTENT_TYPE' => 'application/json',
295+
'rack.input' => io,
296+
'CONTENT_LENGTH' => 0
297+
)
298+
end
299+
end
300+
227301
['application/json', 'application/json; charset=utf-8'].each do |content_type|
228302
context content_type do
229303
it "parses the body from #{method} and copies values into rack.request.form_hash" do

0 commit comments

Comments
 (0)