1#![cfg(feature = "alloc")]
2
3use core::ops::{Deref, DerefMut};
4
5use alloc::string::String;
6use alloc::vec::Vec;
7use bytes::Bytes;
8use serde::{Deserialize, Serialize};
9
10#[derive(Clone, Default, Debug, PartialEq, Eq)]
15pub struct HeaderList {
16 entries: Vec<(Bytes, Bytes)>,
17}
18
19impl HeaderList {
20 #[must_use]
21 pub fn new() -> Self {
22 Self {
23 entries: Vec::new(),
24 }
25 }
26
27 #[must_use]
28 pub fn with_capacity(capacity: usize) -> Self {
29 Self {
30 entries: Vec::with_capacity(capacity),
31 }
32 }
33
34 #[must_use]
35 pub fn from_pairs<I, K, V>(pairs: I) -> Self
36 where
37 I: IntoIterator<Item = (K, V)>,
38 K: IntoHeaderBytes,
39 V: IntoHeaderBytes,
40 {
41 let mut list = Self::new();
42 for (key, value) in pairs {
43 list.insert(key.into_header_bytes(), value.into_header_bytes());
44 }
45 list
46 }
47
48 #[must_use]
49 pub fn len(&self) -> usize {
50 self.entries.len()
51 }
52
53 #[must_use]
54 pub fn is_empty(&self) -> bool {
55 self.entries.is_empty()
56 }
57
58 #[must_use]
59 pub fn get(&self, name: &str) -> Option<&Bytes> {
60 let needle = name.as_bytes();
61 self.entries
62 .iter()
63 .find(|(key, _)| key.as_ref().eq_ignore_ascii_case(needle))
64 .map(|(_, value)| value)
65 }
66
67 #[must_use]
69 pub fn get_str(&self, name: &str) -> Option<&str> {
70 self.get(name)
71 .and_then(|value| core::str::from_utf8(value).ok())
72 }
73
74 pub fn insert<N: IntoHeaderBytes, V: IntoHeaderBytes>(
77 &mut self,
78 name: N,
79 value: V,
80 ) -> Option<Bytes> {
81 let name_bytes = name.into_header_bytes();
82 let value_bytes = value.into_header_bytes();
83 let needle = name_bytes.as_ref();
84 if let Some(slot) = self
85 .entries
86 .iter_mut()
87 .find(|(key, _)| key.as_ref().eq_ignore_ascii_case(needle))
88 {
89 return Some(core::mem::replace(&mut slot.1, value_bytes));
90 }
91 self.entries.push((name_bytes, value_bytes));
92 None
93 }
94
95 #[must_use]
97 pub fn contains_key(&self, name: &str) -> bool {
98 let needle = name.as_bytes();
99 self.entries
100 .iter()
101 .any(|(key, _)| key.as_ref().eq_ignore_ascii_case(needle))
102 }
103
104 pub fn insert_if_absent<N: IntoHeaderBytes, V: IntoHeaderBytes>(&mut self, name: N, value: V) {
105 let name_bytes = name.into_header_bytes();
106 if !self.contains_key_bytes(name_bytes.as_ref()) {
107 self.entries.push((name_bytes, value.into_header_bytes()));
108 }
109 }
110
111 fn contains_key_bytes(&self, needle: &[u8]) -> bool {
112 self.entries
113 .iter()
114 .any(|(key, _)| key.as_ref().eq_ignore_ascii_case(needle))
115 }
116
117 pub fn iter(&self) -> core::slice::Iter<'_, (Bytes, Bytes)> {
118 self.entries.iter()
119 }
120
121 pub fn keys(&self) -> impl Iterator<Item = &Bytes> {
122 self.entries.iter().map(|(name, _)| name)
123 }
124
125 pub fn retain<F: FnMut(&Bytes, &Bytes) -> bool>(&mut self, mut predicate: F) {
126 self.entries.retain(|(name, value)| predicate(name, value));
127 }
128
129 pub fn remove(&mut self, name: &str) -> Option<Bytes> {
130 let needle = name.as_bytes();
131 let position = self
132 .entries
133 .iter()
134 .position(|(key, _)| key.as_ref().eq_ignore_ascii_case(needle))?;
135 Some(self.entries.swap_remove(position).1)
136 }
137
138 #[must_use]
139 pub fn into_inner(self) -> Vec<(Bytes, Bytes)> {
140 self.entries
141 }
142
143 #[must_use]
144 pub fn as_slice(&self) -> &[(Bytes, Bytes)] {
145 &self.entries
146 }
147}
148
149pub trait IntoHeaderBytes {
152 fn into_header_bytes(self) -> Bytes;
153}
154
155impl IntoHeaderBytes for Bytes {
156 fn into_header_bytes(self) -> Bytes {
157 self
158 }
159}
160
161impl IntoHeaderBytes for &Bytes {
162 fn into_header_bytes(self) -> Bytes {
163 Bytes::clone(self)
164 }
165}
166
167impl IntoHeaderBytes for String {
168 fn into_header_bytes(self) -> Bytes {
169 Bytes::from(self)
170 }
171}
172
173impl IntoHeaderBytes for Vec<u8> {
174 fn into_header_bytes(self) -> Bytes {
175 Bytes::from(self)
176 }
177}
178
179impl IntoHeaderBytes for &str {
180 fn into_header_bytes(self) -> Bytes {
181 Bytes::copy_from_slice(self.as_bytes())
182 }
183}
184
185impl IntoHeaderBytes for &[u8] {
186 fn into_header_bytes(self) -> Bytes {
187 Bytes::copy_from_slice(self)
188 }
189}
190
191impl Deref for HeaderList {
192 type Target = [(Bytes, Bytes)];
193 fn deref(&self) -> &Self::Target {
194 &self.entries
195 }
196}
197
198impl DerefMut for HeaderList {
199 fn deref_mut(&mut self) -> &mut Self::Target {
200 &mut self.entries
201 }
202}
203
204impl<'a> IntoIterator for &'a HeaderList {
205 type Item = &'a (Bytes, Bytes);
206 type IntoIter = core::slice::Iter<'a, (Bytes, Bytes)>;
207 fn into_iter(self) -> Self::IntoIter {
208 self.entries.iter()
209 }
210}
211
212impl IntoIterator for HeaderList {
213 type Item = (Bytes, Bytes);
214 type IntoIter = alloc::vec::IntoIter<(Bytes, Bytes)>;
215 fn into_iter(self) -> Self::IntoIter {
216 self.entries.into_iter()
217 }
218}
219
220impl FromIterator<(Bytes, Bytes)> for HeaderList {
221 fn from_iter<I: IntoIterator<Item = (Bytes, Bytes)>>(iter: I) -> Self {
222 Self {
223 entries: iter.into_iter().collect(),
224 }
225 }
226}
227
228impl From<Vec<(Bytes, Bytes)>> for HeaderList {
229 fn from(entries: Vec<(Bytes, Bytes)>) -> Self {
230 Self { entries }
231 }
232}
233
234impl Serialize for HeaderList {
239 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
240 use serde::ser::SerializeSeq;
241 let mut sequence = serializer.serialize_seq(Some(self.entries.len()))?;
242 for (name, value) in &self.entries {
243 let name_str = String::from_utf8_lossy(name);
244 let value_str = String::from_utf8_lossy(value);
245 sequence.serialize_element(&(name_str.as_ref(), value_str.as_ref()))?;
246 }
247 sequence.end()
248 }
249}
250
251impl<'de> Deserialize<'de> for HeaderList {
252 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
253 let pairs: Vec<(String, String)> = Vec::deserialize(deserializer)?;
254 Ok(Self {
255 entries: pairs
256 .into_iter()
257 .map(|(name, value)| (Bytes::from(name), Bytes::from(value)))
258 .collect(),
259 })
260 }
261}
262
263#[cfg(test)]
264#[allow(clippy::unwrap_used, clippy::expect_used)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn insert_replaces_case_insensitive_match_and_returns_prior() {
270 let mut list = HeaderList::new();
271 let prior = list.insert(
272 Bytes::from_static(b"Content-Type"),
273 Bytes::from_static(b"application/json"),
274 );
275 assert!(prior.is_none());
276 let prior = list.insert(
277 Bytes::from_static(b"content-type"),
278 Bytes::from_static(b"text/plain"),
279 );
280 assert_eq!(prior.as_deref(), Some(b"application/json".as_slice()));
281 assert_eq!(
282 list.len(),
283 1,
284 "case-insensitive replace should not duplicate"
285 );
286 assert_eq!(list.get_str("CONTENT-TYPE"), Some("text/plain"));
287 }
288
289 #[test]
290 fn insert_if_absent_skips_existing_case_insensitive_match() {
291 let mut list = HeaderList::new();
292 list.insert(Bytes::from_static(b"X-Trace"), Bytes::from_static(b"first"));
293 list.insert_if_absent("x-trace", "second");
294 assert_eq!(list.get_str("x-trace"), Some("first"));
295 }
296
297 #[test]
298 fn retain_drops_entries_matching_predicate() {
299 let mut list = HeaderList::from_pairs([
300 ("authorization", "bearer"),
301 ("x-token", "abc"),
302 ("x-trace", "trace"),
303 ]);
304 list.retain(|name, _| !name.as_ref().eq_ignore_ascii_case(b"authorization"));
305 assert_eq!(list.len(), 2);
306 assert!(list.get("authorization").is_none());
307 }
308
309 #[test]
310 fn iter_preserves_insertion_order() {
311 let list = HeaderList::from_pairs([("z-last", "1"), ("a-first", "2"), ("m-middle", "3")]);
312 let names: Vec<&str> = list
313 .iter()
314 .map(|(name, _)| core::str::from_utf8(name).expect("ascii"))
315 .collect();
316 assert_eq!(names, vec!["z-last", "a-first", "m-middle"]);
317 }
318
319 #[test]
320 fn serde_round_trip_preserves_string_form() {
321 let list = HeaderList::from_pairs([
322 ("content-type", "application/json"),
323 ("x-trace", "trace-deadbeef"),
324 ]);
325 let json = serde_json::to_string(&list).expect("serialize");
326 let restored: HeaderList = serde_json::from_str(&json).expect("deserialize");
327 assert_eq!(list, restored);
328 }
329
330 #[test]
331 fn into_header_bytes_static_str_does_not_copy_payload() {
332 let bytes = Bytes::from_static(b"traceparent");
336 let other = "traceparent".into_header_bytes();
337 assert_eq!(bytes, other);
338 }
339}