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}