1use smallvec::SmallVec;
27use std::collections::HashMap;
28use std::fmt;
29
30pub const INLINE_HEADERS: usize = 12;
34
35#[derive(Clone, PartialEq, Eq)]
37pub struct Header {
38 pub name: String,
40 pub value: String,
42}
43
44impl Header {
45 #[inline]
47 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
48 Self {
49 name: name.into(),
50 value: value.into(),
51 }
52 }
53
54 #[inline]
56 pub fn name_eq(&self, name: &str) -> bool {
57 self.name.eq_ignore_ascii_case(name)
58 }
59}
60
61impl fmt::Debug for Header {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 write!(f, "{}: {}", self.name, self.value)
64 }
65}
66
67#[derive(Clone, Default)]
85pub struct HeaderMap {
86 inner: SmallVec<[Header; INLINE_HEADERS]>,
87}
88
89impl HeaderMap {
90 #[inline]
92 pub const fn new() -> Self {
93 Self {
94 inner: SmallVec::new_const(),
95 }
96 }
97
98 #[inline]
102 pub fn with_capacity(capacity: usize) -> Self {
103 Self {
104 inner: SmallVec::with_capacity(capacity),
105 }
106 }
107
108 #[inline]
110 pub fn is_inline(&self) -> bool {
111 !self.inner.spilled()
112 }
113
114 #[inline]
116 pub fn len(&self) -> usize {
117 self.inner.len()
118 }
119
120 #[inline]
122 pub fn is_empty(&self) -> bool {
123 self.inner.is_empty()
124 }
125
126 #[inline]
128 pub fn get(&self, name: &str) -> Option<&String> {
129 self.inner
130 .iter()
131 .find(|h| h.name_eq(name))
132 .map(|h| &h.value)
133 }
134
135 #[inline]
140 pub fn get_ignore_case(&self, name: &str) -> Option<&String> {
141 self.get(name)
142 }
143
144 #[inline]
146 pub fn contains(&self, name: &str) -> bool {
147 self.inner.iter().any(|h| h.name_eq(name))
148 }
149
150 #[inline]
154 pub fn insert(&mut self, name: impl Into<String>, value: impl Into<String>) -> Option<String> {
155 let name = name.into();
156 let value = value.into();
157
158 for h in &mut self.inner {
160 if h.name_eq(&name) {
161 let old = std::mem::replace(&mut h.value, value);
162 return Some(old);
163 }
164 }
165
166 self.inner.push(Header { name, value });
168 None
169 }
170
171 #[inline]
176 pub fn append(&mut self, name: impl Into<String>, value: impl Into<String>) {
177 self.inner.push(Header {
178 name: name.into(),
179 value: value.into(),
180 });
181 }
182
183 #[inline]
187 pub fn remove(&mut self, name: &str) -> Option<String> {
188 if let Some(pos) = self.inner.iter().position(|h| h.name_eq(name)) {
189 Some(self.inner.remove(pos).value)
190 } else {
191 None
192 }
193 }
194
195 #[inline]
199 pub fn remove_all(&mut self, name: &str) -> usize {
200 let before = self.inner.len();
201 self.inner.retain(|h| !h.name_eq(name));
202 before - self.inner.len()
203 }
204
205 #[inline]
207 pub fn iter(&self) -> impl Iterator<Item = (&String, &String)> {
208 self.inner.iter().map(|h| (&h.name, &h.value))
209 }
210
211 #[inline]
213 pub fn names(&self) -> impl Iterator<Item = &String> {
214 self.inner.iter().map(|h| &h.name)
215 }
216
217 #[inline]
219 pub fn values(&self) -> impl Iterator<Item = &String> {
220 self.inner.iter().map(|h| &h.value)
221 }
222
223 #[inline]
225 pub fn get_all(&self, name: &str) -> Vec<&String> {
226 self.inner
227 .iter()
228 .filter(|h| h.name_eq(name))
229 .map(|h| &h.value)
230 .collect()
231 }
232
233 #[inline]
235 pub fn clear(&mut self) {
236 self.inner.clear();
237 }
238
239 #[inline]
241 pub fn extend<I, K, V>(&mut self, iter: I)
242 where
243 I: IntoIterator<Item = (K, V)>,
244 K: Into<String>,
245 V: Into<String>,
246 {
247 for (k, v) in iter {
248 self.insert(k, v);
249 }
250 }
251
252 #[inline]
254 pub fn to_hash_map(&self) -> HashMap<String, String> {
255 self.inner
256 .iter()
257 .map(|h| (h.name.clone(), h.value.clone()))
258 .collect()
259 }
260
261 #[inline]
263 pub fn from_hash_map(map: HashMap<String, String>) -> Self {
264 let mut headers = Self::with_capacity(map.len());
265 for (k, v) in map {
266 headers.inner.push(Header { name: k, value: v });
267 }
268 headers
269 }
270
271 #[inline]
277 pub fn content_type(&self) -> Option<&String> {
278 self.get("Content-Type")
279 }
280
281 #[inline]
283 pub fn content_length(&self) -> Option<usize> {
284 self.get("Content-Length")?.parse().ok()
285 }
286
287 #[inline]
289 pub fn accept(&self) -> Option<&String> {
290 self.get("Accept")
291 }
292
293 #[inline]
295 pub fn authorization(&self) -> Option<&String> {
296 self.get("Authorization")
297 }
298
299 #[inline]
301 pub fn user_agent(&self) -> Option<&String> {
302 self.get("User-Agent")
303 }
304
305 #[inline]
307 pub fn host(&self) -> Option<&String> {
308 self.get("Host")
309 }
310
311 #[inline]
313 pub fn cookie(&self) -> Option<&String> {
314 self.get("Cookie")
315 }
316
317 #[inline]
319 pub fn is_keep_alive(&self) -> bool {
320 self.get("Connection")
321 .map(|v| v.eq_ignore_ascii_case("keep-alive"))
322 .unwrap_or(true) }
324
325 #[inline]
327 pub fn is_chunked(&self) -> bool {
328 self.get("Transfer-Encoding")
329 .map(|v| v.contains("chunked"))
330 .unwrap_or(false)
331 }
332
333 #[inline]
335 pub fn set_content_type(&mut self, value: impl Into<String>) {
336 self.insert("Content-Type", value);
337 }
338
339 #[inline]
341 pub fn set_content_length(&mut self, len: usize) {
342 self.insert("Content-Length", len.to_string());
343 }
344}
345
346impl fmt::Debug for HeaderMap {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 f.debug_map()
349 .entries(self.inner.iter().map(|h| (&h.name, &h.value)))
350 .finish()
351 }
352}
353
354impl<K, V> FromIterator<(K, V)> for HeaderMap
355where
356 K: Into<String>,
357 V: Into<String>,
358{
359 fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
360 let iter = iter.into_iter();
361 let (min, max) = iter.size_hint();
362 let mut map = HeaderMap::with_capacity(max.unwrap_or(min));
363 for (k, v) in iter {
364 map.insert(k, v);
365 }
366 map
367 }
368}
369
370impl<'a> IntoIterator for &'a HeaderMap {
371 type Item = (&'a String, &'a String);
372 type IntoIter =
373 std::iter::Map<std::slice::Iter<'a, Header>, fn(&'a Header) -> (&'a String, &'a String)>;
374
375 fn into_iter(self) -> Self::IntoIter {
376 self.inner.iter().map(|h| (&h.name, &h.value))
377 }
378}
379
380impl IntoIterator for HeaderMap {
381 type Item = (String, String);
382 type IntoIter = std::iter::Map<
383 smallvec::IntoIter<[Header; INLINE_HEADERS]>,
384 fn(Header) -> (String, String),
385 >;
386
387 fn into_iter(self) -> Self::IntoIter {
388 self.inner.into_iter().map(|h| (h.name, h.value))
389 }
390}
391
392impl std::ops::Index<&str> for HeaderMap {
394 type Output = String;
395
396 fn index(&self, name: &str) -> &Self::Output {
397 self.get(name).expect("header not found")
398 }
399}
400
401impl From<HashMap<String, String>> for HeaderMap {
406 fn from(map: HashMap<String, String>) -> Self {
407 Self::from_hash_map(map)
408 }
409}
410
411impl From<HeaderMap> for HashMap<String, String> {
412 fn from(map: HeaderMap) -> Self {
413 map.to_hash_map()
414 }
415}
416
417#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
426 fn test_new_is_inline() {
427 let headers = HeaderMap::new();
428 assert!(headers.is_inline());
429 assert!(headers.is_empty());
430 }
431
432 #[test]
433 fn test_insert_and_get() {
434 let mut headers = HeaderMap::new();
435 headers.insert("Content-Type", "application/json");
436 headers.insert("Accept", "text/html");
437
438 assert_eq!(headers.len(), 2);
439 assert_eq!(
440 headers.get("Content-Type"),
441 Some(&"application/json".to_string())
442 );
443 assert_eq!(
444 headers.get("content-type"),
445 Some(&"application/json".to_string())
446 ); }
448
449 #[test]
450 fn test_insert_replaces() {
451 let mut headers = HeaderMap::new();
452 headers.insert("Content-Type", "text/plain");
453 let old = headers.insert("Content-Type", "application/json");
454
455 assert_eq!(old, Some("text/plain".to_string()));
456 assert_eq!(headers.len(), 1);
457 assert_eq!(
458 headers.get("Content-Type"),
459 Some(&"application/json".to_string())
460 );
461 }
462
463 #[test]
464 fn test_append_duplicates() {
465 let mut headers = HeaderMap::new();
466 headers.append("Set-Cookie", "session=abc");
467 headers.append("Set-Cookie", "user=123");
468
469 assert_eq!(headers.len(), 2);
470 let cookies = headers.get_all("Set-Cookie");
471 assert_eq!(cookies.len(), 2);
472 }
473
474 #[test]
475 fn test_remove() {
476 let mut headers = HeaderMap::new();
477 headers.insert("Content-Type", "application/json");
478 headers.insert("Accept", "text/html");
479
480 let removed = headers.remove("Content-Type");
481 assert_eq!(removed, Some("application/json".to_string()));
482 assert_eq!(headers.len(), 1);
483 assert!(!headers.contains("Content-Type"));
484 }
485
486 #[test]
487 fn test_inline_capacity() {
488 let mut headers = HeaderMap::new();
489
490 for i in 0..INLINE_HEADERS {
492 headers.insert(format!("Header-{}", i), format!("Value-{}", i));
493 }
494
495 assert!(headers.is_inline()); headers.insert("Extra-Header", "Extra-Value");
499
500 assert!(!headers.is_inline());
502 }
503
504 #[test]
505 fn test_iter() {
506 let mut headers = HeaderMap::new();
507 headers.insert("A", "1");
508 headers.insert("B", "2");
509
510 let pairs: Vec<_> = headers.iter().collect();
511 assert_eq!(pairs.len(), 2);
512 }
513
514 #[test]
515 fn test_common_accessors() {
516 let mut headers = HeaderMap::new();
517 headers.insert("Content-Type", "application/json");
518 headers.insert("Content-Length", "100");
519 headers.insert("Connection", "keep-alive");
520 headers.insert("Transfer-Encoding", "chunked");
521
522 assert_eq!(
523 headers.content_type(),
524 Some(&"application/json".to_string())
525 );
526 assert_eq!(headers.content_length(), Some(100));
527 assert!(headers.is_keep_alive());
528 assert!(headers.is_chunked());
529 }
530
531 #[test]
532 fn test_from_hash_map() {
533 let mut map = HashMap::new();
534 map.insert("Content-Type".to_string(), "application/json".to_string());
535 map.insert("Accept".to_string(), "text/html".to_string());
536
537 let headers = HeaderMap::from_hash_map(map);
538 assert_eq!(headers.len(), 2);
539 assert!(headers.contains("Content-Type"));
540 }
541
542 #[test]
543 fn test_to_hash_map() {
544 let mut headers = HeaderMap::new();
545 headers.insert("Content-Type", "application/json");
546
547 let map = headers.to_hash_map();
548 assert_eq!(
549 map.get("Content-Type"),
550 Some(&"application/json".to_string())
551 );
552 }
553
554 #[test]
555 fn test_from_iterator() {
556 let headers: HeaderMap = [
557 ("Content-Type", "application/json"),
558 ("Accept", "text/html"),
559 ]
560 .into_iter()
561 .collect();
562
563 assert_eq!(headers.len(), 2);
564 }
565
566 #[test]
567 fn test_indexing() {
568 let mut headers = HeaderMap::new();
569 headers.insert("Content-Type", "application/json");
570
571 assert_eq!(&headers["Content-Type"], "application/json");
572 }
573}