goose_http/headers/
mod.rs1use std::collections::HashMap;
8use std::fmt;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub struct HeaderName(String);
13
14impl HeaderName {
15 pub fn new(name: impl Into<String>) -> Self {
17 let name = name.into();
18 HeaderName(name.to_lowercase())
19 }
20
21 pub fn as_str(&self) -> &str {
23 &self.0
24 }
25}
26
27impl From<&str> for HeaderName {
28 fn from(s: &str) -> Self {
29 HeaderName::new(s)
30 }
31}
32
33impl From<String> for HeaderName {
34 fn from(s: String) -> Self {
35 HeaderName::new(s)
36 }
37}
38
39impl fmt::Display for HeaderName {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 write!(f, "{}", self.0)
42 }
43}
44
45pub mod header_keys {
47 pub const CONTENT_TYPE: &str = "content-type";
49 pub const CONTENT_ENCODING: &str = "content-encoding";
51 pub const CONTENT_LANGUAGE: &str = "content-language";
53 pub const CONTENT_LOCATION: &str = "content-location";
55
56 pub const CONTENT_LENGTH: &str = "content-length";
58 pub const HOST: &str = "host";
59 pub const TRANSFER_ENCODING: &str = "transfer-encoding";
60 pub const CONNECTION: &str = "connection";
61 pub const DATE: &str = "date";
62 pub const EXPECT: &str = "expect";
63
64 pub const AUTHORIZATION: &str = "authorization";
66 pub const CACHE_CONTROL: &str = "cache-control";
67 pub const ETAG: &str = "etag";
68 pub const EXPIRES: &str = "expires";
69 pub const IF_MATCH: &str = "if-match";
70 pub const IF_MODIFIED_SINCE: &str = "if-modified-since";
71 pub const IF_UNMODIFIED_SINCE: &str = "if-unmodified-since";
72 pub const IF_NONE_MATCH: &str = "if-none-match";
73 pub const IF_RANGE: &str = "if-range";
74 pub const LAST_MODIFIED: &str = "last-modified";
75 pub const AGE: &str = "age";
76 pub const VARY: &str = "vary";
77 pub const PRAGMA: &str = "pragma";
78 pub const RANGE: &str = "range";
79 pub const CONTENT_RANGE: &str = "content-range";
80 pub const ACCEPT_RANGES: &str = "accept-ranges";
81 pub const ALLOW: &str = "allow";
82
83 pub const ACCEPT: &str = "accept";
85 pub const ACCEPT_ENCODING: &str = "accept-encoding";
86 pub const ACCEPT_LANGUAGE: &str = "accept-language";
87
88 pub const USER_AGENT: &str = "user-agent";
90 pub const SERVER: &str = "server";
91 pub const SET_COOKIE: &str = "set-cookie";
92 pub const COOKIE: &str = "cookie";
93 pub const LOCATION: &str = "location";
94 pub const UPGRADE: &str = "upgrade";
95}
96
97#[derive(Debug, Clone)]
99pub struct Headers {
100 map: HashMap<HeaderName, Vec<String>>,
101 order: Vec<HeaderName>,
102}
103
104impl Headers {
105 pub fn new() -> Self {
107 Headers {
108 map: HashMap::new(),
109 order: Vec::new(),
110 }
111 }
112
113 pub fn insert(&mut self, name: impl Into<HeaderName>, value: impl Into<String>) {
115 let name = name.into();
116 let value = value.into();
117
118 if !self.map.contains_key(&name) {
119 self.order.push(name.clone());
120 }
121
122 self.map.insert(name, vec![value]);
123 }
124
125 pub fn append(&mut self, name: impl Into<HeaderName>, value: impl Into<String>) {
127 let name = name.into();
128 let value = value.into();
129
130 if !self.map.contains_key(&name) {
131 self.order.push(name.clone());
132 }
133
134 self.map.entry(name).or_insert_with(Vec::new).push(value);
135 }
136
137 pub fn get(&self, name: impl Into<HeaderName>) -> Option<&str> {
139 let name = name.into();
140 self.map
141 .get(&name)
142 .and_then(|values| values.first().map(|s| s.as_str()))
143 }
144
145 pub fn get_all(&self, name: impl Into<HeaderName>) -> Option<&[String]> {
147 let name = name.into();
148 self.map.get(&name).map(|v| v.as_slice())
149 }
150
151 pub fn contains(&self, name: impl Into<HeaderName>) -> bool {
153 let name = name.into();
154 self.map.contains_key(&name)
155 }
156
157 pub fn remove(&mut self, name: impl Into<HeaderName>) -> Option<Vec<String>> {
159 let name = name.into();
160 if let Some(values) = self.map.remove(&name) {
161 self.order.retain(|n| n != &name);
162 Some(values)
163 } else {
164 None
165 }
166 }
167
168 pub fn len(&self) -> usize {
170 self.map.len()
171 }
172
173 pub fn is_empty(&self) -> bool {
175 self.map.is_empty()
176 }
177
178 pub fn iter(&self) -> impl Iterator<Item = (&HeaderName, &Vec<String>)> {
180 self.order
181 .iter()
182 .filter_map(move |name| self.map.get(name).map(|values| (name, values)))
183 }
184
185 pub fn clear(&mut self) {
187 self.map.clear();
188 self.order.clear();
189 }
190}
191
192impl Default for Headers {
193 fn default() -> Self {
194 Self::new()
195 }
196}
197
198impl fmt::Display for Headers {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 for (name, values) in self.iter() {
201 for value in values {
202 writeln!(f, "{}: {}", name, value)?;
203 }
204 }
205 Ok(())
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn test_case_insensitive_header_name() {
215 let name1 = HeaderName::new("Content-Type");
216 let name2 = HeaderName::new("content-type");
217 let name3 = HeaderName::new("CONTENT-TYPE");
218
219 assert_eq!(name1, name2);
220 assert_eq!(name2, name3);
221 }
222
223 #[test]
224 fn test_insert_and_get() {
225 let mut headers = Headers::new();
226 headers.insert("Content-Type", "application/json");
227
228 assert_eq!(headers.get("content-type"), Some("application/json"));
229 assert_eq!(headers.get("Content-Type"), Some("application/json"));
230 }
231
232 #[test]
233 fn test_append_multiple_values() {
234 let mut headers = Headers::new();
235 headers.append("Accept", "text/html");
236 headers.append("Accept", "application/json");
237
238 let values = headers.get_all("accept").unwrap();
239 assert_eq!(values.len(), 2);
240 assert_eq!(values[0], "text/html");
241 assert_eq!(values[1], "application/json");
242 }
243
244 #[test]
245 fn test_insert_replaces() {
246 let mut headers = Headers::new();
247 headers.insert("Content-Type", "text/html");
248 headers.insert("Content-Type", "application/json");
249
250 let values = headers.get_all("content-type").unwrap();
251 assert_eq!(values.len(), 1);
252 assert_eq!(values[0], "application/json");
253 }
254
255 #[test]
256 fn test_remove() {
257 let mut headers = Headers::new();
258 headers.insert("Content-Type", "application/json");
259
260 let removed = headers.remove("content-type");
261 assert!(removed.is_some());
262 assert_eq!(removed.unwrap(), vec!["application/json"]);
263 assert!(headers.get("content-type").is_none());
264 }
265
266 #[test]
267 fn test_order_preservation() {
268 let mut headers = Headers::new();
269 headers.insert("Content-Type", "application/json");
270 headers.insert("Content-Length", "1234");
271 headers.insert("Host", "example.com");
272
273 let order: Vec<String> = headers
274 .iter()
275 .map(|(name, _)| name.as_str().to_string())
276 .collect();
277
278 assert_eq!(order, vec!["content-type", "content-length", "host"]);
279 }
280}