1use std::collections::HashMap;
4use std::io::{self, Write};
5
6#[derive(Debug, Clone, Default, PartialEq)]
11pub struct Header(HashMap<String, Vec<String>>);
12
13impl Header {
14 pub fn new() -> Self {
15 Self::default()
16 }
17
18 pub fn canonical_key(s: &str) -> String {
20 let mut out = String::with_capacity(s.len());
21 let mut upper = true;
22 for c in s.chars() {
23 if c == '-' {
24 out.push('-');
25 upper = true;
26 } else if upper {
27 out.extend(c.to_uppercase());
28 upper = false;
29 } else {
30 out.extend(c.to_lowercase());
31 }
32 }
33 out
34 }
35
36 pub fn get(&self, key: &str) -> Option<&str> {
38 let k = Self::canonical_key(key);
39 self.0.get(&k).and_then(|v| v.first()).map(String::as_str)
40 }
41
42 pub fn set(&mut self, key: &str, value: impl Into<String>) {
44 let k = Self::canonical_key(key);
45 self.0.insert(k, vec![value.into()]);
46 }
47
48 pub fn add(&mut self, key: &str, value: impl Into<String>) {
50 let k = Self::canonical_key(key);
51 self.0.entry(k).or_default().push(value.into());
52 }
53
54 pub fn del(&mut self, key: &str) {
56 let k = Self::canonical_key(key);
57 self.0.remove(&k);
58 }
59
60 pub fn values(&self, key: &str) -> &[String] {
62 let k = Self::canonical_key(key);
63 self.0.get(&k).map(Vec::as_slice).unwrap_or(&[])
64 }
65
66 pub fn iter(&self) -> impl Iterator<Item = (&str, &[String])> {
68 self.0.iter().map(|(k, v)| (k.as_str(), v.as_slice()))
69 }
70
71 pub fn is_empty(&self) -> bool {
73 self.0.is_empty()
74 }
75
76 pub fn write_to(&self, w: &mut impl Write) -> io::Result<()> {
79 let mut keys: Vec<&String> = self.0.keys().collect();
80 keys.sort();
81 for key in keys {
82 for val in &self.0[key] {
83 write!(w, "{key}: {val}\r\n")?;
84 }
85 }
86 Ok(())
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn canonical_key() {
96 assert_eq!(Header::canonical_key("content-type"), "Content-Type");
97 assert_eq!(Header::canonical_key("x-request-id"), "X-Request-Id");
98 assert_eq!(Header::canonical_key("ACCEPT"), "Accept");
99 }
100
101 #[test]
102 fn set_get_del() {
103 let mut h = Header::new();
104 h.set("content-type", "text/plain");
105 assert_eq!(h.get("Content-Type"), Some("text/plain"));
106 h.del("content-type");
107 assert_eq!(h.get("content-type"), None);
108 }
109
110 #[test]
111 fn add_multi_value() {
112 let mut h = Header::new();
113 h.add("Accept", "text/html");
114 h.add("accept", "application/json");
115 assert_eq!(h.values("Accept"), &["text/html", "application/json"]);
116 }
117
118 #[test]
119 fn write_wire_format() {
120 let mut h = Header::new();
121 h.set("Content-Type", "text/plain");
122 h.set("Content-Length", "5");
123 let mut buf = Vec::new();
124 h.write_to(&mut buf).unwrap();
125 let s = String::from_utf8(buf).unwrap();
126 assert!(s.contains("Content-Type: text/plain\r\n"));
127 assert!(s.contains("Content-Length: 5\r\n"));
128 }
129}