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.get(name).and_then(|v| v.first()).map(|s| s.as_str())
55 }
56
57 #[inline]
59 pub fn contains(&self, name: &str) -> bool {
60 self.inner.contains_key(name)
61 }
62
63 #[inline]
65 pub fn len(&self) -> usize {
66 self.inner.len()
67 }
68
69 #[inline]
71 pub fn is_empty(&self) -> bool {
72 self.inner.is_empty()
73 }
74
75 #[inline]
77 pub fn iter(&self) -> impl Iterator<Item = (&str, &Vec<String>)> {
78 self.inner.iter().map(|(k, v)| (k.as_str(), v))
79 }
80
81 #[inline]
83 pub fn iter_flat(&self) -> impl Iterator<Item = (&str, &str)> {
84 self.inner
85 .iter()
86 .flat_map(|(k, values)| values.iter().map(move |v| (k.as_str(), v.as_str())))
87 }
88
89 #[inline]
91 pub fn to_owned(&self) -> HashMap<String, Vec<String>> {
92 self.inner.clone()
93 }
94
95 #[inline]
97 pub fn as_inner(&self) -> &HashMap<String, Vec<String>> {
98 self.inner
99 }
100}
101
102#[derive(Debug, Clone)]
106pub struct HeadersCow<'a> {
107 inner: Cow<'a, HashMap<String, Vec<String>>>,
108}
109
110impl<'a> HeadersCow<'a> {
111 #[inline]
113 pub fn borrowed(headers: &'a HashMap<String, Vec<String>>) -> Self {
114 Self {
115 inner: Cow::Borrowed(headers),
116 }
117 }
118
119 #[inline]
121 pub fn owned(headers: HashMap<String, Vec<String>>) -> Self {
122 Self {
123 inner: Cow::Owned(headers),
124 }
125 }
126
127 #[inline]
129 pub fn get(&self, name: &str) -> Option<&Vec<String>> {
130 self.inner.get(name)
131 }
132
133 #[inline]
135 pub fn get_first(&self, name: &str) -> Option<&str> {
136 self.inner.get(name).and_then(|v| v.first()).map(|s| s.as_str())
137 }
138
139 #[inline]
141 pub fn contains(&self, name: &str) -> bool {
142 self.inner.contains_key(name)
143 }
144
145 pub fn set(&mut self, name: impl Into<String>, value: impl Into<String>) {
147 self.inner.to_mut().insert(name.into(), vec![value.into()]);
148 }
149
150 pub fn add(&mut self, name: impl Into<String>, value: impl Into<String>) {
152 self.inner
153 .to_mut()
154 .entry(name.into())
155 .or_default()
156 .push(value.into());
157 }
158
159 pub fn remove(&mut self, name: &str) -> Option<Vec<String>> {
161 self.inner.to_mut().remove(name)
162 }
163
164 #[inline]
166 pub fn is_owned(&self) -> bool {
167 matches!(self.inner, Cow::Owned(_))
168 }
169
170 #[inline]
172 pub fn into_owned(self) -> HashMap<String, Vec<String>> {
173 self.inner.into_owned()
174 }
175
176 #[inline]
178 pub fn len(&self) -> usize {
179 self.inner.len()
180 }
181
182 #[inline]
184 pub fn is_empty(&self) -> bool {
185 self.inner.is_empty()
186 }
187
188 #[inline]
190 pub fn iter(&self) -> impl Iterator<Item = (&str, &Vec<String>)> {
191 self.inner.iter().map(|(k, v)| (k.as_str(), v))
192 }
193}
194
195impl Default for HeadersCow<'_> {
196 fn default() -> Self {
197 Self::owned(HashMap::new())
198 }
199}
200
201impl<'a> From<&'a HashMap<String, Vec<String>>> for HeadersCow<'a> {
202 fn from(headers: &'a HashMap<String, Vec<String>>) -> Self {
203 Self::borrowed(headers)
204 }
205}
206
207impl From<HashMap<String, Vec<String>>> for HeadersCow<'_> {
208 fn from(headers: HashMap<String, Vec<String>>) -> Self {
209 Self::owned(headers)
210 }
211}
212
213pub struct HeaderIterator<'a> {
215 inner: std::collections::hash_map::Iter<'a, String, Vec<String>>,
216 current_name: Option<&'a str>,
217 current_values: Option<std::slice::Iter<'a, String>>,
218}
219
220impl<'a> HeaderIterator<'a> {
221 pub fn new(headers: &'a HashMap<String, Vec<String>>) -> Self {
223 Self {
224 inner: headers.iter(),
225 current_name: None,
226 current_values: None,
227 }
228 }
229}
230
231impl<'a> Iterator for HeaderIterator<'a> {
232 type Item = (&'a str, &'a str);
233
234 fn next(&mut self) -> Option<Self::Item> {
235 loop {
236 if let (Some(name), Some(values)) = (self.current_name, self.current_values.as_mut()) {
238 if let Some(value) = values.next() {
239 return Some((name, value.as_str()));
240 }
241 }
242
243 let (name, values) = self.inner.next()?;
245 self.current_name = Some(name.as_str());
246 self.current_values = Some(values.iter());
247 }
248 }
249}
250
251pub mod names {
253 pub const HOST: &str = "host";
254 pub const CONTENT_TYPE: &str = "content-type";
255 pub const CONTENT_LENGTH: &str = "content-length";
256 pub const USER_AGENT: &str = "user-agent";
257 pub const ACCEPT: &str = "accept";
258 pub const ACCEPT_ENCODING: &str = "accept-encoding";
259 pub const ACCEPT_LANGUAGE: &str = "accept-language";
260 pub const AUTHORIZATION: &str = "authorization";
261 pub const COOKIE: &str = "cookie";
262 pub const SET_COOKIE: &str = "set-cookie";
263 pub const CACHE_CONTROL: &str = "cache-control";
264 pub const CONNECTION: &str = "connection";
265 pub const DATE: &str = "date";
266 pub const ETAG: &str = "etag";
267 pub const IF_MATCH: &str = "if-match";
268 pub const IF_NONE_MATCH: &str = "if-none-match";
269 pub const IF_MODIFIED_SINCE: &str = "if-modified-since";
270 pub const LAST_MODIFIED: &str = "last-modified";
271 pub const LOCATION: &str = "location";
272 pub const ORIGIN: &str = "origin";
273 pub const REFERER: &str = "referer";
274 pub const SERVER: &str = "server";
275 pub const TRANSFER_ENCODING: &str = "transfer-encoding";
276 pub const VARY: &str = "vary";
277 pub const X_FORWARDED_FOR: &str = "x-forwarded-for";
278 pub const X_FORWARDED_PROTO: &str = "x-forwarded-proto";
279 pub const X_FORWARDED_HOST: &str = "x-forwarded-host";
280 pub const X_REAL_IP: &str = "x-real-ip";
281 pub const X_REQUEST_ID: &str = "x-request-id";
282 pub const X_CORRELATION_ID: &str = "x-correlation-id";
283 pub const X_TRACE_ID: &str = "x-trace-id";
284 pub const X_SPAN_ID: &str = "x-span-id";
285}
286
287pub type CowHeaderName = Cow<'static, str>;
292
293pub type CowHeaderMap = HashMap<CowHeaderName, HeaderValues>;
314
315#[inline]
341pub fn intern_header_name(name: &str) -> CowHeaderName {
342 let lower = name.to_ascii_lowercase();
344
345 match lower.as_str() {
346 "host" => Cow::Borrowed(names::HOST),
347 "content-type" => Cow::Borrowed(names::CONTENT_TYPE),
348 "content-length" => Cow::Borrowed(names::CONTENT_LENGTH),
349 "user-agent" => Cow::Borrowed(names::USER_AGENT),
350 "accept" => Cow::Borrowed(names::ACCEPT),
351 "accept-encoding" => Cow::Borrowed(names::ACCEPT_ENCODING),
352 "accept-language" => Cow::Borrowed(names::ACCEPT_LANGUAGE),
353 "authorization" => Cow::Borrowed(names::AUTHORIZATION),
354 "cookie" => Cow::Borrowed(names::COOKIE),
355 "set-cookie" => Cow::Borrowed(names::SET_COOKIE),
356 "cache-control" => Cow::Borrowed(names::CACHE_CONTROL),
357 "connection" => Cow::Borrowed(names::CONNECTION),
358 "date" => Cow::Borrowed(names::DATE),
359 "etag" => Cow::Borrowed(names::ETAG),
360 "if-match" => Cow::Borrowed(names::IF_MATCH),
361 "if-none-match" => Cow::Borrowed(names::IF_NONE_MATCH),
362 "if-modified-since" => Cow::Borrowed(names::IF_MODIFIED_SINCE),
363 "last-modified" => Cow::Borrowed(names::LAST_MODIFIED),
364 "location" => Cow::Borrowed(names::LOCATION),
365 "origin" => Cow::Borrowed(names::ORIGIN),
366 "referer" => Cow::Borrowed(names::REFERER),
367 "server" => Cow::Borrowed(names::SERVER),
368 "transfer-encoding" => Cow::Borrowed(names::TRANSFER_ENCODING),
369 "vary" => Cow::Borrowed(names::VARY),
370 "x-forwarded-for" => Cow::Borrowed(names::X_FORWARDED_FOR),
371 "x-forwarded-proto" => Cow::Borrowed(names::X_FORWARDED_PROTO),
372 "x-forwarded-host" => Cow::Borrowed(names::X_FORWARDED_HOST),
373 "x-real-ip" => Cow::Borrowed(names::X_REAL_IP),
374 "x-request-id" => Cow::Borrowed(names::X_REQUEST_ID),
375 "x-correlation-id" => Cow::Borrowed(names::X_CORRELATION_ID),
376 "x-trace-id" => Cow::Borrowed(names::X_TRACE_ID),
377 "x-span-id" => Cow::Borrowed(names::X_SPAN_ID),
378 _ => Cow::Owned(lower), }
380}
381
382#[inline]
387pub fn to_cow_optimized(headers: HashMap<String, Vec<String>>) -> CowHeaderMap {
388 headers
389 .into_iter()
390 .map(|(name, values)| (intern_header_name(&name), HeaderValues::from_vec(values)))
391 .collect()
392}
393
394#[inline]
398pub fn from_cow_optimized(headers: CowHeaderMap) -> HashMap<String, Vec<String>> {
399 headers
400 .into_iter()
401 .map(|(name, values)| (name.into_owned(), values.into_vec()))
402 .collect()
403}
404
405#[inline]
407pub fn iter_flat_cow(headers: &CowHeaderMap) -> impl Iterator<Item = (&str, &str)> {
408 headers
409 .iter()
410 .flat_map(|(name, values)| values.iter().map(move |v| (name.as_ref(), v.as_str())))
411}
412
413#[inline]
418pub fn to_optimized(headers: HashMap<String, Vec<String>>) -> OptimizedHeaderMap {
419 headers
420 .into_iter()
421 .map(|(name, values)| (name, HeaderValues::from_vec(values)))
422 .collect()
423}
424
425#[inline]
429pub fn from_optimized(headers: OptimizedHeaderMap) -> HashMap<String, Vec<String>> {
430 headers
431 .into_iter()
432 .map(|(name, values)| (name, values.into_vec()))
433 .collect()
434}
435
436#[inline]
440pub fn iter_flat(headers: &HashMap<String, Vec<String>>) -> impl Iterator<Item = (&str, &str)> {
441 headers
442 .iter()
443 .flat_map(|(name, values)| values.iter().map(move |v| (name.as_str(), v.as_str())))
444}
445
446#[inline]
448pub fn iter_flat_optimized(headers: &OptimizedHeaderMap) -> impl Iterator<Item = (&str, &str)> {
449 headers
450 .iter()
451 .flat_map(|(name, values)| values.iter().map(move |v| (name.as_str(), v.as_str())))
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457
458 fn sample_headers() -> HashMap<String, Vec<String>> {
459 let mut h = HashMap::new();
460 h.insert("content-type".to_string(), vec!["application/json".to_string()]);
461 h.insert("accept".to_string(), vec!["text/html".to_string(), "application/json".to_string()]);
462 h.insert("x-custom".to_string(), vec!["value".to_string()]);
463 h
464 }
465
466 #[test]
467 fn test_headers_ref() {
468 let headers = sample_headers();
469 let ref_ = HeadersRef::new(&headers);
470
471 assert_eq!(ref_.get_first("content-type"), Some("application/json"));
472 assert_eq!(ref_.get("accept").map(|v| v.len()), Some(2));
473 assert!(ref_.contains("x-custom"));
474 assert!(!ref_.contains("not-present"));
475 assert_eq!(ref_.len(), 3);
476 }
477
478 #[test]
479 fn test_headers_ref_iter() {
480 let headers = sample_headers();
481 let ref_ = HeadersRef::new(&headers);
482
483 let flat: Vec<_> = ref_.iter_flat().collect();
484 assert!(flat.contains(&("content-type", "application/json")));
485 assert!(flat.contains(&("accept", "text/html")));
486 assert!(flat.contains(&("accept", "application/json")));
487 }
488
489 #[test]
490 fn test_headers_cow_borrowed() {
491 let headers = sample_headers();
492 let cow = HeadersCow::borrowed(&headers);
493
494 assert!(!cow.is_owned());
495 assert_eq!(cow.get_first("content-type"), Some("application/json"));
496 }
497
498 #[test]
499 fn test_headers_cow_mutation() {
500 let headers = sample_headers();
501 let mut cow = HeadersCow::borrowed(&headers);
502
503 assert!(!cow.is_owned());
504
505 cow.set("x-new", "new-value");
507 assert!(cow.is_owned());
508
509 assert_eq!(cow.get_first("x-new"), Some("new-value"));
510 assert!(!headers.contains_key("x-new"));
512 }
513
514 #[test]
515 fn test_headers_cow_add() {
516 let headers = sample_headers();
517 let mut cow = HeadersCow::borrowed(&headers);
518
519 cow.add("accept", "text/plain");
520 assert!(cow.is_owned());
521
522 let accept = cow.get("accept").unwrap();
523 assert_eq!(accept.len(), 3);
524 }
525
526 #[test]
527 fn test_header_iterator() {
528 let headers = sample_headers();
529 let iter = HeaderIterator::new(&headers);
530
531 let pairs: Vec<_> = iter.collect();
532 assert!(pairs.contains(&("content-type", "application/json")));
533 assert!(pairs.contains(&("accept", "text/html")));
534 assert!(pairs.contains(&("accept", "application/json")));
535 assert!(pairs.contains(&("x-custom", "value")));
536 }
537
538 #[test]
539 fn test_header_names() {
540 use names::*;
541
542 assert_eq!(CONTENT_TYPE, "content-type");
544 assert_eq!(AUTHORIZATION, "authorization");
545 assert_eq!(X_FORWARDED_FOR, "x-forwarded-for");
546 }
547
548 #[test]
549 fn test_optimized_header_map() {
550 let mut optimized: OptimizedHeaderMap = HashMap::new();
551
552 optimized.insert("content-type".to_string(), HeaderValues::from_iter(["application/json".to_string()]));
554
555 optimized.insert("accept".to_string(), HeaderValues::from_iter([
557 "text/html".to_string(),
558 "application/json".to_string(),
559 ]));
560
561 assert_eq!(optimized.get("content-type").map(|v| v.len()), Some(1));
562 assert_eq!(optimized.get("accept").map(|v| v.len()), Some(2));
563 }
564
565 #[test]
566 fn test_to_from_optimized() {
567 let headers = sample_headers();
568
569 let optimized = to_optimized(headers.clone());
571 assert_eq!(optimized.len(), headers.len());
572
573 let back = from_optimized(optimized);
575 assert_eq!(back, headers);
576 }
577
578 #[test]
579 fn test_iter_flat_helper() {
580 let headers = sample_headers();
581 let pairs: Vec<_> = iter_flat(&headers).collect();
582
583 assert_eq!(pairs.len(), 4);
585 assert!(pairs.contains(&("content-type", "application/json")));
586 assert!(pairs.contains(&("accept", "text/html")));
587 assert!(pairs.contains(&("accept", "application/json")));
588 assert!(pairs.contains(&("x-custom", "value")));
589 }
590
591 #[test]
592 fn test_iter_flat_optimized_helper() {
593 let headers = sample_headers();
594 let optimized = to_optimized(headers);
595 let pairs: Vec<_> = iter_flat_optimized(&optimized).collect();
596
597 assert_eq!(pairs.len(), 4);
598 assert!(pairs.contains(&("content-type", "application/json")));
599 }
600
601 #[test]
602 fn test_smallvec_single_value_inline() {
603 let values: HeaderValues = HeaderValues::from_iter(["single".to_string()]);
605
606 assert!(!values.spilled());
608 assert_eq!(values.len(), 1);
609 assert_eq!(values[0], "single");
610 }
611
612 #[test]
613 fn test_smallvec_multiple_values_spill() {
614 let values: HeaderValues = HeaderValues::from_iter([
616 "first".to_string(),
617 "second".to_string(),
618 ]);
619
620 assert!(values.spilled());
622 assert_eq!(values.len(), 2);
623 }
624
625 #[test]
626 fn test_intern_header_name_known() {
627 let ct = intern_header_name("content-type");
629 assert!(matches!(ct, Cow::Borrowed(_)));
630 assert_eq!(ct, "content-type");
631
632 let ct_upper = intern_header_name("Content-Type");
634 assert!(matches!(ct_upper, Cow::Borrowed(_)));
635 assert_eq!(ct_upper, "content-type");
636
637 let ct_mixed = intern_header_name("CONTENT-TYPE");
639 assert!(matches!(ct_mixed, Cow::Borrowed(_)));
640 assert_eq!(ct_mixed, "content-type");
641 }
642
643 #[test]
644 fn test_intern_header_name_unknown() {
645 let custom = intern_header_name("x-custom-header");
647 assert!(matches!(custom, Cow::Owned(_)));
648 assert_eq!(custom, "x-custom-header");
649 }
650
651 #[test]
652 fn test_intern_header_name_all_known() {
653 let known_headers = [
655 "host", "content-type", "content-length", "user-agent",
656 "accept", "accept-encoding", "accept-language", "authorization",
657 "cookie", "set-cookie", "cache-control", "connection", "date",
658 "etag", "if-match", "if-none-match", "if-modified-since",
659 "last-modified", "location", "origin", "referer", "server",
660 "transfer-encoding", "vary", "x-forwarded-for", "x-forwarded-proto",
661 "x-forwarded-host", "x-real-ip", "x-request-id", "x-correlation-id",
662 "x-trace-id", "x-span-id",
663 ];
664
665 for header in known_headers {
666 let interned = intern_header_name(header);
667 assert!(
668 matches!(interned, Cow::Borrowed(_)),
669 "Header '{}' should be interned as borrowed",
670 header
671 );
672 assert_eq!(interned, header);
673 }
674 }
675
676 #[test]
677 fn test_cow_header_map() {
678 let mut headers = CowHeaderMap::new();
679
680 headers.insert(
682 intern_header_name("content-type"),
683 HeaderValues::from_iter(["application/json".to_string()])
684 );
685 headers.insert(
686 intern_header_name("x-custom"),
687 HeaderValues::from_iter(["value".to_string()])
688 );
689
690 assert!(headers.contains_key("content-type"));
692 assert!(headers.contains_key("x-custom"));
693 }
694
695 #[test]
696 fn test_to_from_cow_optimized() {
697 let headers = sample_headers();
698
699 let cow_optimized = to_cow_optimized(headers.clone());
701 assert_eq!(cow_optimized.len(), headers.len());
702
703 for (name, _) in &cow_optimized {
705 if name == "content-type" || name == "accept" {
706 assert!(
707 matches!(name, Cow::Borrowed(_)),
708 "Known header '{}' should be borrowed",
709 name
710 );
711 }
712 }
713
714 let back = from_cow_optimized(cow_optimized);
716 assert_eq!(back, headers);
717 }
718
719 #[test]
720 fn test_iter_flat_cow() {
721 let headers = sample_headers();
722 let cow_optimized = to_cow_optimized(headers);
723 let pairs: Vec<_> = iter_flat_cow(&cow_optimized).collect();
724
725 assert_eq!(pairs.len(), 4);
726 assert!(pairs.contains(&("content-type", "application/json")));
727 assert!(pairs.contains(&("accept", "text/html")));
728 assert!(pairs.contains(&("accept", "application/json")));
729 assert!(pairs.contains(&("x-custom", "value")));
730 }
731}