Skip to content

Middleware tidy-up: dedupe versioner media types + Formatter cleanups#2749

Open
ericproulx wants to merge 1 commit into
masterfrom
refactor/middleware-cleanups
Open

Middleware tidy-up: dedupe versioner media types + Formatter cleanups#2749
ericproulx wants to merge 1 commit into
masterfrom
refactor/middleware-cleanups

Conversation

@ericproulx
Copy link
Copy Markdown
Contributor

Summary

Two unrelated middleware cleanups bundled together.

Grape::Middleware::Versioner::Base#build_available_media_types

The bare versioned media type "application/vnd.<vendor>-<version>" was emitted inside the content_types.each_key loop, even though it doesn't depend on extension. With the 5 default content types and a single declared version, the same string ended up in @available_media_types 5 times — and N times generally, where N is the number of registered content types.

Hoisted the bare versioned emission out of the extension loop:

versions&.reverse_each do |version|
  versioned_base = "#{base_media_type}-#{version}"
  content_types.each_key { |extension| media_types << "#{versioned_base}+#{extension}" }
  media_types << versioned_base
end

available_media_types.first (used when Accept is blank) is unchanged: still application/vnd.<vendor>-<latest-version>+<first-content-type>. The change only affects ordering between equal-Q-value entries in Rack::Utils.best_q_match; the versioner specs verify specific header matches, not relative ordering, and all pass.

Grape::Middleware::Formatter

  • #read_body_input? — chained && rewritten as guard-style early returns; each rejection reason now reads on its own line.
  • #read_rack_input — when merging the parsed body into env[RACK_REQUEST_FORM_HASH], merge in place via merge! rather than allocating a fresh hash via merge + reassign. Also tolerates env[RACK_REQUEST_FORM_HASH] being explicitly set to nil (previously nil.merge(...) would have raised).
  • #ensure_content_type — assign the content-type header in place rather than allocating a merged headers hash.
  • #after — drop the no-op splat in status, headers, bodies = *@app_response. The RHS is already a Rack response triple (Array); the * doesn't change destructuring of an Array.

Test plan

  • bundle exec rspec spec/grape/middleware/ — 356 examples, 0 failures
  • bundle exec rspec spec/grape/util/media_type_spec.rb — passes
  • Parity confirmed for available_media_types.first (the only ordering-sensitive consumer)

🤖 Generated with Claude Code

* `Versioner::Base#build_available_media_types`: emit the bare
  `application/vnd.<vendor>-<version>` once per version instead of once
  per (version × content_type), eliminating N-1 duplicate entries per
  version in `@available_media_types`.
* `Formatter#read_body_input?`: chained `&&` rewritten as guard returns.
* `Formatter#read_rack_input`: merge parsed body into the existing
  `RACK_REQUEST_FORM_HASH` in place via `merge!` instead of allocating
  a new hash; also tolerates a nil-valued key (previously crashed).
* `Formatter#ensure_content_type`: assign content-type in place rather
  than allocating a merged headers hash.
* `Formatter#after`: drop the no-op splat on
  `status, headers, bodies = *@app_response`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ericproulx ericproulx force-pushed the refactor/middleware-cleanups branch from 0dfc1db to 28c4528 Compare May 27, 2026 16:06
@ericproulx ericproulx requested a review from dblock May 27, 2026 16:06
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 27, 2026

Danger Report

No issues found.

View run

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.

1 participant