Skip to content

Parse request bodies based on formats config#500

Open
timriley wants to merge 1 commit intomainfrom
body-parsing
Open

Parse request bodies based on formats config#500
timriley wants to merge 1 commit intomainfrom
body-parsing

Conversation

@timriley
Copy link
Member

@timriley timriley commented Feb 22, 2026

Introduce body parsing to Hanami Action, which runs according to the action's configured format. Now Hanami Action can be used completely standalone and provide all the functionality you would expect when it comes to parsing and handling params.

By default, it includes default body parsers for JSON and multipart form requests.

The overall approach to body parsing is the same as Hanami Router, which this new feature will supersede for full Hanami apps:

  • All keys in parsed bodies are deeply symbolized.
  • Non-hash parsed bodies (e.g. JSON arrays) are wrapped in {_: parsed_body}.
  • Parsed body data is merged into request.params.

Because parsing is now handled at the action level, this means a more "correct" overall behaviour, however, because body parsing will only occur if the request matches on of the action's accepted formats.

Failed body parsing will raise a new Hanami::Action::BodyParsingError, which users can handle in their own action classes if they wish to provide a custom response to failed parsing.

This also includes compatibility with router-handled parsing: if env["router.parsed_body"] is already set, body parsing will be skipped, and the "router.parsed_body" and "router.params" keys are used.

Other details

  • Parsers can be registered when registering formats, as a convenience: formats.register(:custom, "application/custom", parser: ->(body, env) { ... })
  • Parsers can also be registered independently: formats.body_parsers["application/custom"] = parser
  • Even though most parsers would only care about the body being given, we also pass env in case future parsers need to access headers or other Rack APIs that require then env (which we actually do inside the multipart form parser).
  • The body parsing logic is encapsulated in a single module: Hanami::Action::BodyParser. This is mostly about mutating the rack env, but because body parsing is such a distinct concern, keeping this separate allows the code to be understood most easily (i.e. we don't make action.rb so big)
  • Just like we did in Hanami Router as part of our Rack 3 transition, we take care to make the input rewindable, and then rewind it after we've done the work of reading and parsing the body.
  • Since the body parsing logic uses a lot of media type checking (we only attempt to parse if the action accepts the corresponding format), I made a few more of the methods on Mime public, so they could be re-used here.
  • In Action#call, since body parsing occurs early, its error is caught and then "suspended" until the request and response objects are built, and at that point re-raised, which ensures custom exception handlers can have access to the request and response objects they expect.

Overall, this feels like a really solid improvement, but there's one gotcha: multipart form bodies won't be parsed unless you add formats.accept :html. Same deal for JSON bodies and formats.accept :json. This is because we connect body parsing with format acceptance. This is IMO the most "correct" behaviour, but it will mean that we will probably need to encourage Hanami app authors to specify a default format to accept across their actions. For Hanami 2.3, our stopgap measure we just to parse multipart forms and JSON bodies at all times (if such bodies were provided). This required zero boilerplate, but it is possibly "overstepping" a little, adding behaviour where you didn't expect it.

Fixes #484

@timriley timriley force-pushed the body-parsing branch 8 times, most recently from be246e8 to 38f442b Compare March 1, 2026 11:13
@timriley timriley requested a review from a team March 1, 2026 11:15
@timriley timriley force-pushed the body-parsing branch 2 times, most recently from 83bbb60 to f421bc9 Compare March 1, 2026 11:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Assume responsibility for body params parsing

1 participant