Repository navigation
Keep each API's content type order when negotiating */* - #3032
Merged
Merged
Conversation
ericproulx
force-pushed
the
fix/content-type-order-cache
branch
from
October 16, 2026 19:26
54c879e to
f79d8ab
Compare
Danger ReportNo issues found. |
ericproulx
force-pushed
the
fix/content-type-order-cache
branch
5 times, most recently
from
October 17, 2026 20:42
c2d60f7 to
233c13b
Compare
dblock
approved these changes
Oct 19, 2026
A browser's Accept header ends in */*, and on an API whose content types it names none of, Rack's best_q_match answers it with the first media type the API declares: the declaration order is the API's preference. ContentTypes::MimeTypesCache, shared process-wide so that registries with the same content types share one table, was keyed on the content-types Hash, and Hash equality ignores order. The first API built with a set of content types fixed the order for every later API that declared the same set in another order, so `content_type :json` then `:md` could answer a browser in Markdown. mime_types_for's shortcut to MIME_TYPES compared with == and had the same blind spot. The cache is now keyed on the registry's pairs, in order, and the shortcut takes DEFAULTS itself only. mime_types_for runs once per middleware build and is memoized there, so the Array costs nothing per request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ericproulx
force-pushed
the
fix/content-type-order-cache
branch
from
October 19, 2026 11:41
233c13b to
c4bcbc8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every browser's
Acceptheader ends in*/*:Accepttext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8On an API that declares none of the types it names explicitly,
Rack::Utils.best_q_matchanswers the*/*with the first media type inmime_types, which is the order the API declared its content types in. That order is the API's preference.Bug
Grape::ContentTypes::MimeTypesCacheis shared process-wide, so that every registry with the same content types shares one table. It was keyed on the content-types Hash, and Hash equality ignores order ({ json: 1, md: 2 } == { md: 2, json: 1 }). The first API built with a set of content types fixed the order for every later API that declared the same set in another order:mime_types_for's shortcut toMIME_TYPEScompared the registry with==, so an API declaring exactly the default content types in another order got the defaults' order.Fix
mime_types_forkeysMimeTypesCacheonfrom_settings.to_a, the registry's pairs in order. Registries that declare the same content types in the same order still share one table.MIME_TYPESshortcut takesDEFAULTSitself (equal?), which is whatcontent_types_forhands over when an API declares nothing.mime_types_forruns once per middleware build and is memoized on the instance, so the Array costs nothing per request.LookupCacheandFormatter::FormatForAcceptCachekeep their Hash keys: their tables answer one key at a time, and the answers do not depend on order. The header versioner'sMediaTypeForAcceptCacheis already keyed on an Array.Tests
spec/grape/api_spec.rb: two APIs declaring:json/:txtin opposite orders each answer a browserAcceptin their own first type.spec/grape/content_types_spec.rb: each registry keeps its order, and the default content types in another order keep that order. The context that passed a copy ofDEFAULTSand expected theMIME_TYPESobject itself now passesDEFAULTS.content_types.rbfails all three new examples. Restoring only==fails one, and restoring only the Hash key fails the other two.bundle exec rakepasses.🤖 Generated with Claude Code