Skip to main content

reqmd_http/
header.rs

1/// A list of key-value pairs representing HTTP headers.
2/// ```rust
3/// # use reqmd_http::Headers;
4///
5/// // Prepares a new Headers instance with a specified capacity
6/// let mut headers = Headers::with_capacity(2);
7/// headers.add("Content-Type", "text/plain");
8/// headers.add("Authorization", "Bearer token");
9///
10/// assert_eq!(headers.len(), 2);
11/// assert_eq!(headers.first("Content-Type"), Some("text/plain"));
12///
13/// // Supports iterating over headers for updates
14/// for header in headers.iter_mut() {
15///     if header.key.eq_ignore_ascii_case("Authorization") {
16///         header.value = String::from("SECRET");
17///     }
18/// }
19///
20/// // Key lookup is case-insensitive
21/// assert_eq!(headers.first("authorization"), Some("SECRET"));
22///
23/// // Accessing headers by index
24/// assert_eq!(headers[0].key, "Content-Type");
25/// assert_eq!(headers[0].value, "text/plain");
26///
27/// // Creates a Headers instance from an iterator of tuples
28/// let headers = Headers::from_iter([
29///     ("X-Custom-Header", "value1"),
30///     ("X-Another-Header", "value2")
31/// ]);
32///
33/// assert_eq!(headers.first("x-custom-header"), Some("value1"));
34/// assert_eq!(headers.first("x-another-header"), Some("value2"));
35/// ```
36/// ---
37#[derive(Debug, Clone, PartialEq, Default, Eq)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39#[cfg_attr(feature = "serde", serde(transparent))]
40pub struct Headers(Vec<HeaderLine>);
41
42impl Headers {
43    /// Prepares a new `Headers` instance with a specified capacity.
44    pub fn with_capacity(capacity: usize) -> Self {
45        Headers(Vec::with_capacity(capacity))
46    }
47
48    /// Provides an iterator over all header values for a given key.
49    pub fn values_for(&self, key: &str) -> impl Iterator<Item = &str> {
50        self.0
51            .iter()
52            .filter(|header| header.key.eq_ignore_ascii_case(key))
53            .map(|header| header.value.as_str())
54    }
55
56    /// Provides the first value for a given key, if it exists.
57    pub fn first(&self, key: &str) -> Option<&str> {
58        self.values_for(key).next()
59    }
60
61    /// Adds a new header line with the specified key and value.
62    pub fn add(&mut self, key: &str, value: &str) {
63        self.0.push(HeaderLine::new(key, value));
64    }
65
66    /// Provides a mutable iterator over all header values for a given key.
67    ///
68    /// ```rust
69    /// # use reqmd_http::Headers;
70    /// let mut headers = Headers::from_iter([
71    ///     ("foo", "bar"),
72    ///     ("biz", "buz"),
73    ///     ("foo", "rab")
74    /// ]);
75    ///
76    /// for var in headers.values_for_mut("foo") {
77    ///     if var != "bar" {
78    ///         var.make_ascii_uppercase();
79    ///     }
80    /// }
81    ///
82    /// assert_eq!(headers.first("foo"), Some("bar"));
83    /// assert_eq!(headers.first("biz"), Some("buz"));
84    ///
85    /// let foos = headers.values_for("foo").collect::<Vec<_>>();
86    /// assert_eq!(foos, vec!["bar", "RAB"]);
87    /// ```
88    /// ---
89    pub fn values_for_mut(
90        &mut self,
91        key: &str,
92    ) -> impl Iterator<Item = &mut String> {
93        self.0
94            .iter_mut()
95            .filter(|header| header.key.eq_ignore_ascii_case(key))
96            .map(|header| &mut header.value)
97    }
98
99    /// Returns a mutable reference to the first value for a given
100    /// key, if it exists.
101    ///
102    /// Provides a convenient way to update the value the
103    /// first header line matching the specified key.
104    ///
105    /// ```rust
106    /// # use reqmd_http::Headers;
107    /// let mut headers = Headers::from_iter([
108    ///     ("foo", "bar"),
109    ///     ("biz", "buz"),
110    ///     ("foo", "rab")
111    /// ]);
112    ///
113    /// let Some(foo) = headers.first_mut("foo") else {
114    ///     panic!("Expected to find a header with key 'foo'");
115    /// };
116    /// foo.make_ascii_uppercase();
117    /// let foos = headers.values_for("foo").collect::<Vec<_>>();
118    /// assert_eq!(foos, vec!["BAR", "rab"]);
119    /// ```
120    /// ___
121    pub fn first_mut(&mut self, key: &str) -> Option<&mut String> {
122        self.values_for_mut(key).next()
123    }
124
125    /// Removes the first header line matching a key and returns
126    /// it's value if found.
127    ///
128    /// ```rust
129    /// # use reqmd_http::Headers;
130    /// let mut headers = Headers::from_iter([
131    ///     ("foo", "bar"),
132    ///     ("biz", "buz"),
133    ///     ("foo", "rab")
134    /// ]);
135    ///
136    /// let maybe_foo = headers.delete_first("foo");
137    /// assert_eq!(maybe_foo.as_deref(), Some("bar"));
138    /// assert_eq!(headers.first("biz"), Some("buz"));
139    /// assert_eq!(headers.first("foo"), Some("rab"));
140    ///
141    /// let maybe_foo = headers.delete_first("foo");
142    /// assert_eq!(maybe_foo.as_deref(), Some("rab"));
143    /// assert_eq!(headers.first("biz"), Some("buz"));
144    /// assert!(headers.first("foo").is_none());
145    /// ```
146    /// ---
147    pub fn delete_first(&mut self, key: &str) -> Option<String> {
148        self.0
149            .iter()
150            .position(|header| header.key.eq_ignore_ascii_case(key))
151            .map(|pos| self.0.remove(pos).value)
152    }
153
154    /// Removes all header lines matching a key and returns their values.
155    ///
156    /// ```rust
157    /// # use reqmd_http::Headers;
158    ///
159    /// let mut headers = Headers::from_iter([
160    ///     ("foo", "bar"),
161    ///     ("biz", "buz"),
162    ///     ("foo", "rab")
163    /// ]);
164    ///
165    /// let foos = headers.delete_all("foo");
166    ///
167    /// assert_eq!(foos, vec!["bar".to_string(), "rab".to_string()]);
168    /// assert_eq!(headers.first("biz"), Some("buz"));
169    /// assert!(headers.first("foo").is_none());
170    /// ```
171    /// ---
172    pub fn delete_all(&mut self, key: &str) -> Vec<String> {
173        let mut deleted_values = Vec::with_capacity(4);
174        self.0.retain_mut(|header| {
175            if header.key.eq_ignore_ascii_case(key) {
176                let mut deleted = String::new();
177                std::mem::swap(&mut deleted, &mut header.value);
178                deleted_values.push(deleted);
179                false
180            } else {
181                true
182            }
183        });
184        deleted_values
185    }
186
187    /// Adds a new header line from a tuple of key and value.
188    pub fn insert_many<I, T>(&mut self, iter: I)
189    where
190        I: IntoIterator<Item = T>,
191        T: Into<HeaderLine>,
192    {
193        self.0.extend(iter.into_iter().map(Into::into));
194    }
195
196    /// Tests if the headers collection is empty.
197    pub fn is_empty(&self) -> bool {
198        self.0.is_empty()
199    }
200
201    /// Returns the number of headers in the collection.
202    pub fn len(&self) -> usize {
203        self.0.len()
204    }
205
206    /// Reference iterator for the headers collection.
207    pub fn iter(&self) -> impl Iterator<Item = &HeaderLine> {
208        self.0.iter()
209    }
210
211    /// Mutable reference iterator for the headers collection.
212    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut HeaderLine> {
213        self.0.iter_mut()
214    }
215}
216
217/// Represents a single header line with a key and value.
218#[derive(Debug, Clone, PartialEq, Eq)]
219#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
220pub struct HeaderLine {
221    pub key: String,
222    pub value: String,
223}
224
225impl HeaderLine {
226    #[doc(hidden)]
227    pub fn new<K, V>(key: K, value: V) -> Self
228    where
229        K: Into<String>,
230        V: Into<String>,
231    {
232        HeaderLine {
233            key: key.into(),
234            value: value.into(),
235        }
236    }
237}
238
239impl<K, V> From<(K, V)> for HeaderLine
240where
241    K: Into<String>,
242    V: Into<String>,
243{
244    fn from((key, value): (K, V)) -> Self {
245        HeaderLine::new(key, value)
246    }
247}
248
249impl std::ops::Index<&str> for Headers {
250    type Output = HeaderLine;
251
252    fn index(&self, key: &str) -> &Self::Output {
253        self.0
254            .iter()
255            .find(|header| header.key.eq_ignore_ascii_case(key))
256            .expect("Header not found")
257    }
258}
259
260impl std::ops::Index<usize> for Headers {
261    type Output = HeaderLine;
262
263    fn index(&self, index: usize) -> &Self::Output {
264        &self.0[index]
265    }
266}
267
268impl IntoIterator for Headers {
269    type Item = HeaderLine;
270    type IntoIter = std::vec::IntoIter<HeaderLine>;
271
272    fn into_iter(self) -> Self::IntoIter {
273        self.0.into_iter()
274    }
275}
276
277impl<'a> IntoIterator for &'a Headers {
278    type Item = &'a HeaderLine;
279    type IntoIter = std::slice::Iter<'a, HeaderLine>;
280
281    fn into_iter(self) -> Self::IntoIter {
282        self.0.iter()
283    }
284}
285
286impl<T> FromIterator<T> for Headers
287where
288    T: Into<HeaderLine>,
289{
290    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
291        Headers(iter.into_iter().map(Into::into).collect())
292    }
293}