Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions robyn/robyn.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,14 @@ class Headers:
"""
pass

def get_headers(self) -> dict[str, list[str]]:
"""Returns all headers as a dictionary where keys are header names and values are lists of all values for that header."""
...

def to_dict(self) -> dict[str, str]:
"""Returns headers as a flattened dictionary, joining duplicate headers with commas."""
...

@dataclass
class Request:
"""
Expand Down
14 changes: 14 additions & 0 deletions src/types/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,20 @@ impl Headers {
dict.into()
}

pub fn to_dict(&self, py: Python) -> Py<PyDict> {
let dict = PyDict::new(py);
for iter in self.headers.iter() {
let (key, values) = iter.pair();
let joined_value = if values.len() == 1 {
values[0].clone()
} else {
values.join(",")
};
Comment on lines +101 to +105
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 11, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

to_dict() should not comma-merge non-combinable headers (e.g., set-cookie).

At Line 104, duplicate values are always joined with commas. That is not valid for all HTTP headers and can corrupt semantics for headers like set-cookie. Prefer special-casing non-combinable names (or documenting to_dict() as lossy and directing callers to get_headers() for full fidelity).

Suggested adjustment
 pub fn to_dict(&self, py: Python) -> Py<PyDict> {
     let dict = PyDict::new(py);
     for iter in self.headers.iter() {
         let (key, values) = iter.pair();
-        let joined_value = if values.len() == 1 {
+        let joined_value = if values.len() == 1 {
             values[0].clone()
+        } else if key.eq_ignore_ascii_case("set-cookie") {
+            // Non-combinable header; keep a deterministic single value in flattened view.
+            // Callers needing all values should use `get_headers()`.
+            values.last().cloned().unwrap_or_default()
         } else {
             values.join(",")
         };
         dict.set_item(key, joined_value).unwrap();
     }
     dict.into()
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let joined_value = if values.len() == 1 {
values[0].clone()
} else {
values.join(",")
};
pub fn to_dict(&self, py: Python) -> Py<PyDict> {
let dict = PyDict::new(py);
for iter in self.headers.iter() {
let (key, values) = iter.pair();
let joined_value = if values.len() == 1 {
values[0].clone()
} else if key.eq_ignore_ascii_case("set-cookie") {
// Non-combinable header; keep a deterministic single value in flattened view.
// Callers needing all values should use `get_headers()`.
values.last().cloned().unwrap_or_default()
} else {
values.join(",")
};
dict.set_item(key, joined_value).unwrap();
}
dict.into()
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/headers.rs` around lines 101 - 105, to_dict() currently
unconditionally joins multiple header values with commas (joined_value), which
corrupts non-combinable headers like "set-cookie"; change the construction so
that when the header name is in a small constant set (e.g.,
NON_COMBINABLE_HEADERS containing "set-cookie", "set-cookie2", etc.) you do NOT
comma-merge but instead preserve a single value (e.g., values[0].clone()) or
otherwise avoid joining, and leave a note that callers should use get_headers()
for full-fidelity multi-value headers; add the NON_COMBINABLE_HEADERS constant
and use it inside to_dict() where joined_value is computed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Resolve — by design. to_dict() is a convenience flattened view. get_headers() preserves full multi-value fidelity for headers like set-cookie.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@sansyrox have exceeded the limit for the number of chat messages per hour. Please wait 2 minutes and 1 seconds before sending another message.

dict.set_item(key, joined_value).unwrap();
}
dict.into()
}

pub fn contains(&self, key: String) -> bool {
self.headers.contains_key(&key.to_lowercase())
}
Expand Down
35 changes: 35 additions & 0 deletions unit_tests/test_request_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,38 @@ def test_request_object():
print(request.headers.get("Content-Type"))
assert request.headers.get("Content-Type") == "application/json"
assert request.method == "GET"


def test_headers_to_dict():
headers = Headers({"Content-Type": "application/json", "Authorization": "Bearer token"})
headers_dict = headers.to_dict()
assert headers_dict["content-type"] == "application/json"
assert headers_dict["authorization"] == "Bearer token"
custom_header = headers_dict.get("x-custom", "default")
assert custom_header == "default"


def test_headers_to_dict_with_duplicates():
headers = Headers({})
headers.append("X-Custom", "value1")
headers.append("X-Custom", "value2")
headers.append("X-Custom", "value3")
headers_dict = headers.to_dict()
assert headers_dict["x-custom"] == "value1,value2,value3"


def test_headers_get_headers():
headers = Headers({})
headers.set("Content-Type", "application/json")
headers.append("X-Custom", "value1")
headers.append("X-Custom", "value2")
headers_lists = headers.get_headers()
assert headers_lists["content-type"] == ["application/json"]
assert headers_lists["x-custom"] == ["value1", "value2"]


def test_headers_to_dict_empty():
headers = Headers({})
headers_dict = headers.to_dict()
assert headers_dict == {}
assert headers_dict.get("any-header", "default") == "default"
Loading