How do i restrict my API to accept & respond only json format on Rails & Grape, i've try format :json
on my Grape controller and (for example) i can access it on example.com/api/v1/ping.json, but i also can access it via example.com/api/v1/ping.xml, example.com/api/v1/ping.foobar, and the list of extensions goes on...
The things i would like to do is, throwing error on example.com/api/v1/ping.not_json_extensions
Im using:
/config/routes.rb
mount API::Base => '/api'
/controllers/api/base.rb
module API
class Base < Grape::API
mount API::V1::Base
end
end
/controllers/api/v1/base.rb
module API
module V1
class Base < Grape::API
format :json
mount API::V1::Ping
end
end
end
/controllers/api/v1/ping.rb
module API
module V1
class Ping < Grape::API
include API::V1::Defaults
desc 'Returns pong.'
get :ping do
{ ping: params[:pong] || 'pong' }
end
end
end
end
Looking at Grape's source code, it seems this was the intended behaviour but a change to prevent memory leaks effectively broke it.
You can implement the correct behaviour "manually" by adding an explicit check to your API class (in /controllers/api/base.rb):
before do
# Make sure the format specified by the request extension is one
# we support
parts = request.path.split('.')
if parts.size > 1
extension = parts.last
if !extension.eql? 'json'
throw :error, {
status: 406,
message: "The requested format '#{extension}' is not supported."
}
end
end
end
This code is copied pretty much verbatim from Grape's source (in lib/grape/middleware/formatter.rb) and is how Grape itself checks the extension used in the request.
In that file, negotiate_content_type
is responsible for checking whether the requested format is one supported by the API, and clearly gives priority to the request extension. However the method that parses the extension from the URI, format_from_extension
, also checks whether the format is supported and returns nil
if it isn't, as though there were no extension at all. As a result negotiate_content_type
will never trigger an error if the request's extension specifies an unsupported format, even though it clearly was meant to do so.
You can "fix" this by changing the code at formatter.rb:109 from
# avoid symbol memory leak on an unknown format
return extension.to_sym if content_type_for(extension)
to simply
return extension.to_sym
The comment suggests the code was written this way for a reason, though, so proceed with caution.