1use std::borrow::Cow;
14use std::collections::HashMap;
15
16use smallvec::SmallVec;
17
18pub type HeaderValues = SmallVec<[String; 1]>;
23
24pub type OptimizedHeaderMap = HashMap<String, HeaderValues>;
29
30#[derive(Debug)]
34pub struct HeadersRef<'a> {
35 inner: &'a HashMap<String, Vec<String>>,
36}
37
38impl<'a> HeadersRef<'a> {
39 #[inline]
41 pub fn new(headers: &'a HashMap<String, Vec<String>>) -> Self {
42 Self { inner: headers }
43 }
44
45 #[inline]
47 pub fn get(&self, name: &str) -> Option<&Vec<String>> {
48 self.inner.get(name)
49 }
50
51 #[inline]
53 pub fn get_first(&self, name: &str) -> Option<&str> {
54 self.inner
55 .get(name)
56 .and_then(|v| v.first())
57 .map(|s| s.as_str())
58 }
59
60 #[inline]
62 pub fn contains(&self, name: &str) -> bool {
63 self.inner.contains_key(name)
64 }
65
66 #[inline]
68 pub fn len(&self) -> usize {
69 self.inner.len()
70 }
71
72 #[inline]
74 pub fn is_empty(&self) -> bool {
75 self.inner.is_empty()
76 }
77
78 #[inline]
80 pub fn iter(&self) -> impl Iterator<Item = (&str, &Vec<String>)> {
81 self.inner.iter().map(|(k, v)| (k.as_str(), v))
82 }
83
84 #[inline]
86 pub fn iter_flat(&self) -> impl Iterator<Item = (&str, &str)> {
87 self.inner
88 .iter()
89 .flat_map(|(k, values)| values.iter().map(move |v| (k.as_str(), v.as_str())))
90 }
91
92 #[inline]
94 pub fn to_owned(&self) -> HashMap<String, Vec<String>> {
95 self.inner.clone()
96 }
97
98 #[inline]
100 pub fn as_inner(&self) -> &HashMap<String, Vec<String>> {
101 self.inner
102 }
103}
104
105#[derive(Debug, Clone)]
109pub struct HeadersCow<'a> {
110 inner: Cow<'a, HashMap<String, Vec<String>>>,
111}
112
113impl<'a> HeadersCow<'a> {
114 #[inline]
116 pub fn borrowed(headers: &'a HashMap<String, Vec<String>>) -> Self {
117 Self {
118 inner: Cow::Borrowed(headers),
119 }
120 }
121
122 #[inline]
124 pub fn owned(headers: HashMap<String, Vec<String>>) -> Self {
125 Self {
126 inner: Cow::Owned(headers),
127 }
128 }
129
130 #[inline]
132 pub fn get(&self, name: &str) -> Option<&Vec<String>> {
133 self.inner.get(name)
134 }
135
136 #[inline]
138 pub fn get_first(&self, name: &str) -> Option<&str> {
139 self.inner
140 .get(name)
141 .and_then(|v| v.first())
142 .map(|s| s.as_str())
143 }
144
145 #[inline]
147 pub fn contains(&self, name: &str) -> bool {
148 self.inner.contains_key(name)
149 }
150
151 pub fn set(&mut self, name: impl Into<String>, value: impl Into<String>) {
153 self.inner.to_mut().insert(name.into(), vec![value.into()]);
154 }
155
156 pub fn add(&mut self, name: impl Into<String>, value: impl Into<String>) {
158 self.inner
159 .to_mut()
160 .entry(name.into())
161 .or_default()
162 .push(value.into());
163 }
164
165 pub fn remove(&mut self, name: &str) -> Option<Vec<String>> {
167 self.inner.to_mut().remove(name)
168 }
169
170 #[inline]
172 pub fn is_owned(&self) -> bool {
173 matches!(self.inner, Cow::Owned(_))
174 }
175
176 #[inline]
178 pub fn into_owned(self) -> HashMap<String, Vec<String>> {
179 self.inner.into_owned()
180 }
181
182 #[inline]
184 pub fn len(&self) -> usize {
185 self.inner.len()
186 }
187
188 #[inline]
190 pub fn is_empty(&self) -> bool {
191 self.inner.is_empty()
192 }
193
194 #[inline]
196 pub fn iter(&self) -> impl Iterator<Item = (&str, &Vec<String>)> {
197 self.inner.iter().map(|(k, v)| (k.as_str(), v))
198 }
199}
200
201impl Default for HeadersCow<'_> {
202 fn default() -> Self {
203 Self::owned(HashMap::new())
204 }
205}
206
207impl<'a> From<&'a HashMap<String, Vec<String>>> for HeadersCow<'a> {
208 fn from(headers: &'a HashMap<String, Vec<String>>) -> Self {
209 Self::borrowed(headers)
210 }
211}
212
213impl From<HashMap<String, Vec<String>>> for HeadersCow<'_> {
214 fn from(headers: HashMap<String, Vec<String>>) -> Self {
215 Self::owned(headers)
216 }
217}
218
219pub struct HeaderIterator<'a> {
221 inner: std::collections::hash_map::Iter<'a, String, Vec<String>>,
222 current_name: Option<&'a str>,
223 current_values: Option<std::slice::Iter<'a, String>>,
224}
225
226impl<'a> HeaderIterator<'a> {
227 pub fn new(headers: &'a HashMap<String, Vec<String>>) -> Self {
229 Self {
230 inner: headers.iter(),
231 current_name: None,
232 current_values: None,
233 }
234 }
235}
236
237impl<'a> Iterator for HeaderIterator<'a> {
238 type Item = (&'a str, &'a str);
239
240 fn next(&mut self) -> Option<Self::Item> {
241 loop {
242 if let (Some(name), Some(values)) = (self.current_name, self.current_values.as_mut()) {
244 if let Some(value) = values.next() {
245 return Some((name, value.as_str()));
246 }
247 }
248
249 let (name, values) = self.inner.next()?;
251 self.current_name = Some(name.as_str());
252 self.current_values = Some(values.iter());
253 }
254 }
255}
256
257pub mod names {
259 pub const HOST: &str = "host";
260 pub const CONTENT_TYPE: &str = "content-type";
261 pub const CONTENT_LENGTH: &str = "content-length";
262 pub const USER_AGENT: &str = "user-agent";
263 pub const ACCEPT: &str = "accept";
264 pub const ACCEPT_ENCODING: &str = "accept-encoding";
265 pub const ACCEPT_LANGUAGE: &str = "accept-language";
266 pub const AUTHORIZATION: &str = "authorization";
267 pub const COOKIE: &str = "cookie";
268 pub const SET_COOKIE: &str = "set-cookie";
269 pub const CACHE_CONTROL: &str = "cache-control";
270 pub const CONNECTION: &str = "connection";
271 pub const DATE: &str = "date";
272 pub const ETAG: &str = "etag";
273 pub const IF_MATCH: &str = "if-match";
274 pub const IF_NONE_MATCH: &str = "if-none-match";
275 pub const IF_MODIFIED_SINCE: &str = "if-modified-since";
276 pub const LAST_MODIFIED: &str = "last-modified";
277 pub const LOCATION: &str = "location";
278 pub const ORIGIN: &str = "origin";
279 pub const REFERER: &str = "referer";
280 pub const SERVER: &str = "server";
281 pub const TRANSFER_ENCODING: &str = "transfer-encoding";
282 pub const VARY: &str = "vary";
283 pub const X_FORWARDED_FOR: &str = "x-forwarded-for";
284 pub const X_FORWARDED_PROTO: &str = "x-forwarded-proto";
285 pub const X_FORWARDED_HOST: &str = "x-forwarded-host";
286 pub const X_REAL_IP: &str = "x-real-ip";
287 pub const X_REQUEST_ID: &str = "x-request-id";
288 pub const X_CORRELATION_ID: &str = "x-correlation-id";
289 pub const X_TRACE_ID: &str = "x-trace-id";
290 pub const X_SPAN_ID: &str = "x-span-id";
291}
292
293pub type CowHeaderName = Cow<'static, str>;
298
299pub type CowHeaderMap = HashMap<CowHeaderName, HeaderValues>;
320
321#[inline]
347pub fn intern_header_name(name: &str) -> CowHeaderName {
348 let lower = name.to_ascii_lowercase();
350
351 match lower.as_str() {
352 "host" => Cow::Borrowed(names::HOST),
353 "content-type" => Cow::Borrowed(names::CONTENT_TYPE),
354 "content-length" => Cow::Borrowed(names::CONTENT_LENGTH),
355 "user-agent" => Cow::Borrowed(names::USER_AGENT),
356 "accept" => Cow::Borrowed(names::ACCEPT),
357 "accept-encoding" => Cow::Borrowed(names::ACCEPT_ENCODING),
358 "accept-language" => Cow::Borrowed(names::ACCEPT_LANGUAGE),
359 "authorization" => Cow::Borrowed(names::AUTHORIZATION),
360 "cookie" => Cow::Borrowed(names::COOKIE),
361 "set-cookie" => Cow::Borrowed(names::SET_COOKIE),
362 "cache-control" => Cow::Borrowed(names::CACHE_CONTROL),
363 "connection" => Cow::Borrowed(names::CONNECTION),
364 "date" => Cow::Borrowed(names::DATE),
365 "etag" => Cow::Borrowed(names::ETAG),
366 "if-match" => Cow::Borrowed(names::IF_MATCH),
367 "if-none-match" => Cow::Borrowed(names::IF_NONE_MATCH),
368 "if-modified-since" => Cow::Borrowed(names::IF_MODIFIED_SINCE),
369 "last-modified" => Cow::Borrowed(names::LAST_MODIFIED),
370 "location" => Cow::Borrowed(names::LOCATION),
371 "origin" => Cow::Borrowed(names::ORIGIN),
372 "referer" => Cow::Borrowed(names::REFERER),
373 "server" => Cow::Borrowed(names::SERVER),
374 "transfer-encoding" => Cow::Borrowed(names::TRANSFER_ENCODING),
375 "vary" => Cow::Borrowed(names::VARY),
376 "x-forwarded-for" => Cow::Borrowed(names::X_FORWARDED_FOR),
377 "x-forwarded-proto" => Cow::Borrowed(names::X_FORWARDED_PROTO),
378 "x-forwarded-host" => Cow::Borrowed(names::X_FORWARDED_HOST),
379 "x-real-ip" => Cow::Borrowed(names::X_REAL_IP),
380 "x-request-id" => Cow::Borrowed(names::X_REQUEST_ID),
381 "x-correlation-id" => Cow::Borrowed(names::X_CORRELATION_ID),
382 "x-trace-id" => Cow::Borrowed(names::X_TRACE_ID),
383 "x-span-id" => Cow::Borrowed(names::X_SPAN_ID),
384 _ => Cow::Owned(lower), }
386}
387
388#[inline]
393pub fn to_cow_optimized(headers: HashMap<String, Vec<String>>) -> CowHeaderMap {
394 headers
395 .into_iter()
396 .map(|(name, values)| (intern_header_name(&name), HeaderValues::from_vec(values)))
397 .collect()
398}
399
400#[inline]
404pub fn from_cow_optimized(headers: CowHeaderMap) -> HashMap<String, Vec<String>> {
405 headers
406 .into_iter()
407 .map(|(name, values)| (name.into_owned(), values.into_vec()))
408 .collect()
409}
410
411#[inline]
413pub fn iter_flat_cow(headers: &CowHeaderMap) -> impl Iterator<Item = (&str, &str)> {
414 headers
415 .iter()
416 .flat_map(|(name, values)| values.iter().map(move |v| (name.as_ref(), v.as_str())))
417}
418
419#[inline]
424pub fn to_optimized(headers: HashMap<String, Vec<String>>) -> OptimizedHeaderMap {
425 headers
426 .into_iter()
427 .map(|(name, values)| (name, HeaderValues::from_vec(values)))
428 .collect()
429}
430
431#[inline]
435pub fn from_optimized(headers: OptimizedHeaderMap) -> HashMap<String, Vec<String>> {
436 headers
437 .into_iter()
438 .map(|(name, values)| (name, values.into_vec()))
439 .collect()
440}
441
442#[inline]
446pub fn iter_flat(headers: &HashMap<String, Vec<String>>) -> impl Iterator<Item = (&str, &str)> {
447 headers
448 .iter()
449 .flat_map(|(name, values)| values.iter().map(move |v| (name.as_str(), v.as_str())))
450}
451
452#[inline]
454pub fn iter_flat_optimized(headers: &OptimizedHeaderMap) -> impl Iterator<Item = (&str, &str)> {
455 headers
456 .iter()
457 .flat_map(|(name, values)| values.iter().map(move |v| (name.as_str(), v.as_str())))
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463
464 fn sample_headers() -> HashMap<String, Vec<String>> {
465 let mut h = HashMap::new();
466 h.insert(
467 "content-type".to_string(),
468 vec!["application/json".to_string()],
469 );
470 h.insert(
471 "accept".to_string(),
472 vec!["text/html".to_string(), "application/json".to_string()],
473 );
474 h.insert("x-custom".to_string(), vec!["value".to_string()]);
475 h
476 }
477
478 #[test]
479 fn test_headers_ref() {
480 let headers = sample_headers();
481 let ref_ = HeadersRef::new(&headers);
482
483 assert_eq!(ref_.get_first("content-type"), Some("application/json"));
484 assert_eq!(ref_.get("accept").map(|v| v.len()), Some(2));
485 assert!(ref_.contains("x-custom"));
486 assert!(!ref_.contains("not-present"));
487 assert_eq!(ref_.len(), 3);
488 }
489
490 #[test]
491 fn test_headers_ref_iter() {
492 let headers = sample_headers();
493 let ref_ = HeadersRef::new(&headers);
494
495 let flat: Vec<_> = ref_.iter_flat().collect();
496 assert!(flat.contains(&("content-type", "application/json")));
497 assert!(flat.contains(&("accept", "text/html")));
498 assert!(flat.contains(&("accept", "application/json")));
499 }
500
501 #[test]
502 fn test_headers_cow_borrowed() {
503 let headers = sample_headers();
504 let cow = HeadersCow::borrowed(&headers);
505
506 assert!(!cow.is_owned());
507 assert_eq!(cow.get_first("content-type"), Some("application/json"));
508 }
509
510 #[test]
511 fn test_headers_cow_mutation() {
512 let headers = sample_headers();
513 let mut cow = HeadersCow::borrowed(&headers);
514
515 assert!(!cow.is_owned());
516
517 cow.set("x-new", "new-value");
519 assert!(cow.is_owned());
520
521 assert_eq!(cow.get_first("x-new"), Some("new-value"));
522 assert!(!headers.contains_key("x-new"));
524 }
525
526 #[test]
527 fn test_headers_cow_add() {
528 let headers = sample_headers();
529 let mut cow = HeadersCow::borrowed(&headers);
530
531 cow.add("accept", "text/plain");
532 assert!(cow.is_owned());
533
534 let accept = cow.get("accept").unwrap();
535 assert_eq!(accept.len(), 3);
536 }
537
538 #[test]
539 fn test_header_iterator() {
540 let headers = sample_headers();
541 let iter = HeaderIterator::new(&headers);
542
543 let pairs: Vec<_> = iter.collect();
544 assert!(pairs.contains(&("content-type", "application/json")));
545 assert!(pairs.contains(&("accept", "text/html")));
546 assert!(pairs.contains(&("accept", "application/json")));
547 assert!(pairs.contains(&("x-custom", "value")));
548 }
549
550 #[test]
551 fn test_header_names() {
552 use names::*;
553
554 assert_eq!(CONTENT_TYPE, "content-type");
556 assert_eq!(AUTHORIZATION, "authorization");
557 assert_eq!(X_FORWARDED_FOR, "x-forwarded-for");
558 }
559
560 #[test]
561 fn test_optimized_header_map() {
562 let mut optimized: OptimizedHeaderMap = HashMap::new();
563
564 optimized.insert(
566 "content-type".to_string(),
567 HeaderValues::from_iter(["application/json".to_string()]),
568 );
569
570 optimized.insert(
572 "accept".to_string(),
573 HeaderValues::from_iter(["text/html".to_string(), "application/json".to_string()]),
574 );
575
576 assert_eq!(optimized.get("content-type").map(|v| v.len()), Some(1));
577 assert_eq!(optimized.get("accept").map(|v| v.len()), Some(2));
578 }
579
580 #[test]
581 fn test_to_from_optimized() {
582 let headers = sample_headers();
583
584 let optimized = to_optimized(headers.clone());
586 assert_eq!(optimized.len(), headers.len());
587
588 let back = from_optimized(optimized);
590 assert_eq!(back, headers);
591 }
592
593 #[test]
594 fn test_iter_flat_helper() {
595 let headers = sample_headers();
596 let pairs: Vec<_> = iter_flat(&headers).collect();
597
598 assert_eq!(pairs.len(), 4);
600 assert!(pairs.contains(&("content-type", "application/json")));
601 assert!(pairs.contains(&("accept", "text/html")));
602 assert!(pairs.contains(&("accept", "application/json")));
603 assert!(pairs.contains(&("x-custom", "value")));
604 }
605
606 #[test]
607 fn test_iter_flat_optimized_helper() {
608 let headers = sample_headers();
609 let optimized = to_optimized(headers);
610 let pairs: Vec<_> = iter_flat_optimized(&optimized).collect();
611
612 assert_eq!(pairs.len(), 4);
613 assert!(pairs.contains(&("content-type", "application/json")));
614 }
615
616 #[test]
617 fn test_smallvec_single_value_inline() {
618 let values: HeaderValues = HeaderValues::from_iter(["single".to_string()]);
620
621 assert!(!values.spilled());
623 assert_eq!(values.len(), 1);
624 assert_eq!(values[0], "single");
625 }
626
627 #[test]
628 fn test_smallvec_multiple_values_spill() {
629 let values: HeaderValues =
631 HeaderValues::from_iter(["first".to_string(), "second".to_string()]);
632
633 assert!(values.spilled());
635 assert_eq!(values.len(), 2);
636 }
637
638 #[test]
639 fn test_intern_header_name_known() {
640 let ct = intern_header_name("content-type");
642 assert!(matches!(ct, Cow::Borrowed(_)));
643 assert_eq!(ct, "content-type");
644
645 let ct_upper = intern_header_name("Content-Type");
647 assert!(matches!(ct_upper, Cow::Borrowed(_)));
648 assert_eq!(ct_upper, "content-type");
649
650 let ct_mixed = intern_header_name("CONTENT-TYPE");
652 assert!(matches!(ct_mixed, Cow::Borrowed(_)));
653 assert_eq!(ct_mixed, "content-type");
654 }
655
656 #[test]
657 fn test_intern_header_name_unknown() {
658 let custom = intern_header_name("x-custom-header");
660 assert!(matches!(custom, Cow::Owned(_)));
661 assert_eq!(custom, "x-custom-header");
662 }
663
664 #[test]
665 fn test_intern_header_name_all_known() {
666 let known_headers = [
668 "host",
669 "content-type",
670 "content-length",
671 "user-agent",
672 "accept",
673 "accept-encoding",
674 "accept-language",
675 "authorization",
676 "cookie",
677 "set-cookie",
678 "cache-control",
679 "connection",
680 "date",
681 "etag",
682 "if-match",
683 "if-none-match",
684 "if-modified-since",
685 "last-modified",
686 "location",
687 "origin",
688 "referer",
689 "server",
690 "transfer-encoding",
691 "vary",
692 "x-forwarded-for",
693 "x-forwarded-proto",
694 "x-forwarded-host",
695 "x-real-ip",
696 "x-request-id",
697 "x-correlation-id",
698 "x-trace-id",
699 "x-span-id",
700 ];
701
702 for header in known_headers {
703 let interned = intern_header_name(header);
704 assert!(
705 matches!(interned, Cow::Borrowed(_)),
706 "Header '{}' should be interned as borrowed",
707 header
708 );
709 assert_eq!(interned, header);
710 }
711 }
712
713 #[test]
714 fn test_cow_header_map() {
715 let mut headers = CowHeaderMap::new();
716
717 headers.insert(
719 intern_header_name("content-type"),
720 HeaderValues::from_iter(["application/json".to_string()]),
721 );
722 headers.insert(
723 intern_header_name("x-custom"),
724 HeaderValues::from_iter(["value".to_string()]),
725 );
726
727 assert!(headers.contains_key("content-type"));
729 assert!(headers.contains_key("x-custom"));
730 }
731
732 #[test]
733 fn test_to_from_cow_optimized() {
734 let headers = sample_headers();
735
736 let cow_optimized = to_cow_optimized(headers.clone());
738 assert_eq!(cow_optimized.len(), headers.len());
739
740 for name in cow_optimized.keys() {
742 if name == "content-type" || name == "accept" {
743 assert!(
744 matches!(name, Cow::Borrowed(_)),
745 "Known header '{}' should be borrowed",
746 name
747 );
748 }
749 }
750
751 let back = from_cow_optimized(cow_optimized);
753 assert_eq!(back, headers);
754 }
755
756 #[test]
757 fn test_iter_flat_cow() {
758 let headers = sample_headers();
759 let cow_optimized = to_cow_optimized(headers);
760 let pairs: Vec<_> = iter_flat_cow(&cow_optimized).collect();
761
762 assert_eq!(pairs.len(), 4);
763 assert!(pairs.contains(&("content-type", "application/json")));
764 assert!(pairs.contains(&("accept", "text/html")));
765 assert!(pairs.contains(&("accept", "application/json")));
766 assert!(pairs.contains(&("x-custom", "value")));
767 }
768}