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:

  1. to fill in the parameters needed for a request, rspec-openapi consumes path_parameters, query_parameters and request_parameters from the original request.
  2. when using ActionDispatch::IntegrationTest, Rails will serialise take what you define in params and pass it to a new ActionDispatch::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.
  3. ActionDispatch::Request inherits from Rack::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.
  4. when our gem calls request_parameters on the ActionDispatch::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.
  5. 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