fix(beam): read request headers from Cowboy instead of returning empty - #80
Merged
Conversation
`HttpRequest.GetTypedHeaders()` returned an empty `RequestHeaders` and `HttpRequest.Headers` an empty `HeaderDictionary`, so on the BEAM target `mustAccept` and content negotiation could never resolve, and no handler could read a request header at all -- an endpoint gating on `Authorization` had to reach past Giraffe to `cowboy_req:header/3` itself. The binding was never missing: `Fable.Beam.Cowboy.CowboyReq.headers` has been there all along. Both accessors now read it through a shared `HeaderPairs`. Cowboy is already the normalising layer -- lowercase names, duplicates folded into one comma-joined value -- so this is a map read rather than a reimplementation of header semantics, and the two-element rows it yields match the shape the Python/ASGI backend builds from `scope["headers"]`, keeping `mustAccept` identical across targets. The BEAM `TestContext` was the other half of the gap, and the reason the suite could not have caught this: it accepted a `?headers` argument and silently dropped it, while the JS and Python factories both wired theirs in. It now seeds them into the fake Req map, lowercased on the way in to match what Cowboy hands a real handler. The three Accept-header cases are un-skipped on BEAM, and two tests cover reading an arbitrary header and case-insensitive lookup -- the case Accept-only coverage would not have pinned. BEAM 99 passed / 4 skipped (the remaining skips are the unrelated `routef` typed-capture divergences), JS 102/1, Python 103/0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
The gap
On the BEAM target both request-header accessors were placeholders:
So
mustAcceptand content negotiation could never resolve, and no handler could read a request header at all. An endpoint gating onAuthorizationhad to reach past Giraffe tocowboy_req:header/3itself. Tracked inFOLLOWUPS.md; the JS and Python backends both read their real header source.The binding was never missing —
Fable.Beam.Cowboy.CowboyReq.headershas been there all along. Giraffe just wasn't calling it.The fix
src/beam/HttpContext.fs— both accessors readcowboy_req:headers/1through a shared privateHeaderPairs. Cowboy is already the normalising layer (lowercase names, duplicates folded into one comma-joined value), so this is a map read rather than a reimplementation of header semantics. The two-element rows it yields match the shape the Python/ASGI backend builds fromscope["headers"], somustAcceptbehaves identically across targets. Explicitforloops rather thanSeq.map, per theRef-vs-list caveat the file already documents.test/beam/TestContext.fs— the other half of the gap, and the reason the suite couldn't have caught this: it accepted a?headersargument and silently dropped it, while the JS and Python factories both wired theirs in. It now seeds them into the fake Req map underheaders, lowercased on the way in to match what Cowboy hands a real handler.test/shared/HandlerTests.fs— un-skips the three Accept-header cases on BEAM, and adds two tests covering reading an arbitrary header (Authorization,X-Request-Id) and case-insensitive lookup. Those two are the case Accept-only coverage wouldn't have pinned.Test results
The 4 remaining BEAM skips are the unrelated
routeftyped-capture divergences (%i/%O/%u), still tracked inFOLLOWUPS.md. Fantomas clean.🤖 Generated with Claude Code