1use crate::error::PickError;
2use serde_json::Value;
3
4pub fn parse(input: &str) -> Result<Value, PickError> {
5 let mut map = serde_json::Map::new();
6
7 for line in input.lines() {
8 let line = line.trim();
9
10 if line.is_empty() || line.starts_with("HTTP/") {
12 continue;
13 }
14
15 if let Some(colon_pos) = line.find(':') {
16 let key = line[..colon_pos].trim().to_lowercase();
17 let value = line[colon_pos + 1..].trim();
18 match map.entry(key) {
19 serde_json::map::Entry::Occupied(mut e) => {
20 if let Value::String(existing) = e.get() {
21 let combined = format!("{}, {}", existing, value);
22 e.insert(Value::String(combined));
23 }
24 }
25 serde_json::map::Entry::Vacant(e) => {
26 e.insert(Value::String(value.to_string()));
27 }
28 }
29 }
30 }
31
32 if map.is_empty() {
33 return Err(PickError::ParseError(
34 "headers".into(),
35 "no headers found".into(),
36 ));
37 }
38
39 Ok(Value::Object(map))
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use serde_json::json;
46
47 #[test]
48 fn parse_standard_headers() {
49 let input = "Content-Type: application/json\nContent-Length: 1234";
50 let v = parse(input).unwrap();
51 assert_eq!(v["content-type"], json!("application/json"));
52 assert_eq!(v["content-length"], json!("1234"));
53 }
54
55 #[test]
56 fn parse_with_status_line() {
57 let input = "HTTP/1.1 200 OK\nContent-Type: text/html\nServer: nginx";
58 let v = parse(input).unwrap();
59 assert_eq!(v["content-type"], json!("text/html"));
60 assert_eq!(v["server"], json!("nginx"));
61 assert!(v.get("http/1.1 200 ok").is_none());
63 }
64
65 #[test]
66 fn parse_case_insensitive_keys() {
67 let input = "Content-Type: text/html\nX-REQUEST-ID: abc123";
68 let v = parse(input).unwrap();
69 assert_eq!(v["content-type"], json!("text/html"));
70 assert_eq!(v["x-request-id"], json!("abc123"));
71 }
72
73 #[test]
74 fn parse_value_with_colon() {
75 let input = "Location: https://example.com:8080/path";
76 let v = parse(input).unwrap();
77 assert_eq!(v["location"], json!("https://example.com:8080/path"));
78 }
79
80 #[test]
81 fn parse_empty_value() {
82 let input = "X-Empty:\nContent-Type: text/html";
83 let v = parse(input).unwrap();
84 assert_eq!(v["x-empty"], json!(""));
85 }
86
87 #[test]
88 fn parse_with_empty_lines() {
89 let input = "Content-Type: text/html\n\nX-After: value";
90 let v = parse(input).unwrap();
91 assert_eq!(v["content-type"], json!("text/html"));
92 assert_eq!(v["x-after"], json!("value"));
93 }
94
95 #[test]
96 fn parse_empty_input() {
97 assert!(parse("").is_err());
98 }
99
100 #[test]
101 fn parse_only_status_line() {
102 assert!(parse("HTTP/1.1 200 OK").is_err());
103 }
104
105 #[test]
106 fn parse_rate_limit_headers() {
107 let input =
108 "X-RateLimit-Limit: 100\nX-RateLimit-Remaining: 42\nX-RateLimit-Reset: 1609459200";
109 let v = parse(input).unwrap();
110 assert_eq!(v["x-ratelimit-remaining"], json!("42"));
111 }
112}