Skip to content

bug fix: set content-type header based on response type in mapResponse and mapEarlyResponse#1766

Open
KnextKoder wants to merge 3 commits intoelysiajs:mainfrom
KnextKoder:main
Open

bug fix: set content-type header based on response type in mapResponse and mapEarlyResponse#1766
KnextKoder wants to merge 3 commits intoelysiajs:mainfrom
KnextKoder:main

Conversation

@KnextKoder
Copy link

@KnextKoder KnextKoder commented Feb 27, 2026

Bug #1379

When a route handler sets two or more cookies, the content type header of the response silently becomes text/plain instead of application/json, the bug is caused by the handleSet func in src/adapter/utils.ts.

image

Here, when the result is an array, parseSetCookies() replaces set.headers with a Headers instance so that multiple set-cookie entries can be added correctly, but Headers doesn't support index based property assignment, so no Content-Type is ever written to the response. My fix is to preset the content-type on set.headers before the handleSet func is called, when it is guaranteed to still be a plain object. Both mapResponse and mapEarlyResponse functions in both the bun and web standard adapters are patched.

All existing tests continue to pass.

image

Plus some throwaway test I used to confirm these behaviors also passed

Scenario Asserts
Original bug report (2 cookies + response schema) Content-Type: application/json
Direct .value = assignment syntax Content-Type: application/json
Three or more cookies Content-Type: application/json
Array response with multiple cookies Content-Type: application/json
mapEarlyResponse path Content-Type: application/json
Single cookie (regression guard) Still works correctly
String response with multiple cookies Not incorrectly set to application/json

Summary by CodeRabbit

Bug Fixes

  • Fixed automatic Content-Type header detection and assignment for API responses: JSON objects and arrays are now correctly labeled as application/json, while text and string responses are marked as text/plain
  • Enhanced header handling compatibility and robustness to ensure proper Content-Type assignment across different response types and runtime configurations

Copilot AI review requested due to automatic review settings February 27, 2026 15:35
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 27, 2026

Walkthrough

Updated Content-Type header assignment logic in Bun and web-standard adapters to conditionally set application/json for Object/Array responses and text/plain for String responses. Added robust handling to distinguish between Headers instances and plain objects when updating headers, ensuring correct Content-Type labeling across different response types.

Changes

Cohort / File(s) Summary
Adapter Response Handler Improvements
src/adapter/bun/handler.ts, src/adapter/web-standard/handler.ts
Enhanced Content-Type header assignment in mapResponse and mapEarlyResponse flows. Added conditional logic to detect response types (Object/Array → application/json; String → text/plain) and safely handle both Headers instances and plain object representations. Removed redundant Content-Type assignments previously scattered across switch branches.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 Headers hop and dance with care,
JSON labeled, plain and fair,
Objects, arrays, strings so bright,
Content-Type now set just right! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: fixing content-type header assignment based on response type in the mapResponse and mapEarlyResponse functions across multiple adapters.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/adapter/web-standard/handler.ts (1)

182-186: Consider using lowercase content-type for consistency.

The header key casing differs between the pre-handleSet code (lowercase 'content-type' at line 63) and this fallback path (capitalized 'Content-Type'). While HTTP headers are case-insensitive, using consistent casing improves code clarity. This is a minor nit.

♻️ Suggested change for consistency
 if (set.headers instanceof Headers) {
-    if (!set.headers.has('Content-Type'))
-        set.headers.set('Content-Type', 'application/json')
-} else if (!set.headers['Content-Type'])
-    set.headers['Content-Type'] = 'application/json'
+    if (!set.headers.has('content-type'))
+        set.headers.set('content-type', 'application/json')
+} else if (!set.headers['content-type'])
+    set.headers['content-type'] = 'application/json'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adapter/web-standard/handler.ts` around lines 182 - 186, The header key
casing is inconsistent: in the fallback branch you set 'Content-Type' while
elsewhere you use lowercase 'content-type'; update the fallback to use lowercase
'content-type' for consistency by checking and setting set.headers (both when
it's a Headers instance and when it's a plain object) using 'content-type'
instead of 'Content-Type' so the code paths match the pre-handleSet usage of
lowercase.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/adapter/web-standard/handler.ts`:
- Around line 182-186: The header key casing is inconsistent: in the fallback
branch you set 'Content-Type' while elsewhere you use lowercase 'content-type';
update the fallback to use lowercase 'content-type' for consistency by checking
and setting set.headers (both when it's a Headers instance and when it's a plain
object) using 'content-type' instead of 'Content-Type' so the code paths match
the pre-handleSet usage of lowercase.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbaf6b7 and 3549daf.

📒 Files selected for processing (2)
  • src/adapter/bun/handler.ts
  • src/adapter/web-standard/handler.ts

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a bug where setting multiple cookies could prevent Content-Type from being written correctly (notably for JSON responses) by ensuring Content-Type is determined/set before cookie handling can convert set.headers into a Headers instance.

Changes:

  • Pre-sets content-type in mapResponse/mapEarlyResponse (web-standard + bun) before calling handleSet(set) so cookie parsing can’t block Content-Type writes.
  • Updates JSON “string looks like JSON” fallback to set Content-Type correctly when set.headers is a Headers instance.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/adapter/web-standard/handler.ts Pre-sets content-type before handleSet; adds Headers-aware Content-Type setting in JSON-detection branch.
src/adapter/bun/handler.ts Pre-sets JSON content-type before handleSet; adds Headers-aware Content-Type setting in JSON-detection branch.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +57 to +65
switch (response?.constructor?.name) {
case 'String':
set.headers['content-type'] = 'text/plain'
break
case 'Array':
case 'Object':
set.headers['content-type'] = 'application/json'
break
}
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting set.headers['content-type'] here assumes set.headers is a plain object. However set.headers can already be a Headers instance in this codepath (e.g. if handleSet was called earlier, or after the ElysiaCustomStatusResponse branch recurses with a set whose headers were converted by handleSet). In that case, index assignment won’t write the header and the Content-Type fix won’t apply (and the post-handleSet assignment was removed). Update this block to support both Headers and plain objects (e.g. use set.headers.set('content-type', ...) when set.headers instanceof Headers).

Copilot uses AI. Check for mistakes.
Comment on lines +218 to +226
switch (response?.constructor?.name) {
case 'String':
set.headers['content-type'] = 'text/plain'
break
case 'Array':
case 'Object':
set.headers['content-type'] = 'application/json'
break
}
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as mapResponse: this pre-handleSet set.headers['content-type'] write will fail if set.headers is already a Headers instance (possible when mapEarlyResponse is called with a set that has already passed through handleSet elsewhere). Since the post-handleSet content-type assignment was removed, this can leave JSON responses without Content-Type. Handle both Headers and plain header objects when setting this value.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +32
)
set.headers['content-type'] = 'application/json'
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new pre-handleSet assignment uses index-based writes (set.headers['content-type'] = ...). set.headers can be a Headers instance in some call paths (for example when handleSet already ran before calling mapResponse, or after recursive mapResponse calls using the same set), in which case this write is ignored and JSON responses won’t get Content-Type. Update to set the header via Headers.set(...) when set.headers instanceof Headers (or use a helper that supports both shapes).

Suggested change
)
set.headers['content-type'] = 'application/json'
) {
if (set.headers instanceof Headers)
set.headers.set('content-type', 'application/json')
else (set.headers as any)['content-type'] = 'application/json'
}

Copilot uses AI. Check for mistakes.
Comment on lines +188 to +189
)
set.headers['content-type'] = 'application/json'
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as mapResponse: this pre-handleSet set.headers['content-type'] write assumes set.headers is a plain object. If set.headers is already a Headers instance, the assignment won’t apply and JSON responses can miss the Content-Type header. Handle the Headers case explicitly (e.g. set.headers.set('content-type', 'application/json')).

Suggested change
)
set.headers['content-type'] = 'application/json'
) {
if (set.headers instanceof Headers) {
set.headers.set('content-type', 'application/json')
} else {
;(set.headers as any)['content-type'] = 'application/json'
}
}

Copilot uses AI. Check for mistakes.
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.

2 participants