Configuring ActionDispatch::Request.parameter_parsers
We started using rspec-openapi in one of our projects to generate an API specification based on our RSpec request specs.
To keep it short, after a request spec runs, the gem inspects both request and response, extracts the relevant information like example response or parameters and writes it into an OpenAPI-compliant YAML file.
In our API, we provide a route that consumes an XML body. While getting the parameters worked for a JSON request, it didn’t work for an XML request, the request parameters were empty.
After some digging in Rails source code, here is what I found out:
- to fill in the parameters needed for a request, rspec-openapi consumes
path_parameters
,query_parameters
andrequest_parameters
from the original request. - when using
ActionDispatch::IntegrationTest
, Rails will serialise take what you define inparams
and pass it to a newActionDispatch::Request
object. Here you have the option to specify a custom encoder. Note that this encoder is only used for tests and doesn’t influence your application. ActionDispatch::Request
inherits fromRack::Request
. They share an instance variable named@env
. This is essentially one big key-value store that contains all headers, metadata, internal information about e.g. the logger and many more.- when our gem calls
request_parameters
on theActionDispatch::Request
, it will fetch the original request body and mime type from@env
and then looks for a parser that can decode the given body based on the mime type. If no parser can be found, this method returns an empty hash. - Per default, Rails only ships with a parser for JSON. This explains why
request_parameters
where always empty.
This is the code I used to register a new parser for XML according to the example given in the Rails source code itself.
RSpec.configure do |config|
config.before(:all, type: :request) do
original_parsers = ActionDispatch::Request.parameter_parsers
xml_parser = -> (raw_post) { Hash.from_xml(raw_post) || {} }
new_parsers = original_parsers.merge(xml: xml_parser)
ActionDispatch::Request.parameter_parsers = new_parsers
end
...
end