http_types/conditional/
if_match.rs

1//! Apply the HTTP method if the ETag matches.
2
3use crate::conditional::ETag;
4use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, IF_MATCH};
5
6use std::fmt::{self, Debug, Write};
7use std::iter::Iterator;
8use std::option;
9use std::slice;
10
11/// Apply the HTTP method if the ETag matches.
12///
13/// # Specifications
14///
15/// - [RFC 7232, section 3.1: If-Match](https://tools.ietf.org/html/rfc7232#section-3.1)
16///
17/// # Examples
18///
19/// ```
20/// # fn main() -> http_types::Result<()> {
21/// #
22/// use http_types::Response;
23/// use http_types::conditional::{IfMatch, ETag};
24///
25/// let mut entries = IfMatch::new();
26/// entries.push(ETag::new("0xcafebeef".to_string()));
27/// entries.push(ETag::new("0xbeefcafe".to_string()));
28///
29/// let mut res = Response::new(200);
30/// entries.apply(&mut res);
31///
32/// let entries = IfMatch::from_headers(res)?.unwrap();
33/// let mut entries = entries.iter();
34/// assert_eq!(entries.next().unwrap(), &ETag::new("0xcafebeef".to_string()));
35/// assert_eq!(entries.next().unwrap(), &ETag::new("0xbeefcafe".to_string()));
36/// #
37/// # Ok(()) }
38/// ```
39pub struct IfMatch {
40    entries: Vec<ETag>,
41    wildcard: bool,
42}
43
44impl IfMatch {
45    /// Create a new instance of `IfMatch`.
46    pub fn new() -> Self {
47        Self {
48            entries: vec![],
49            wildcard: false,
50        }
51    }
52
53    /// Create a new instance from headers.
54    pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
55        let mut entries = vec![];
56        let headers = match headers.as_ref().get(IF_MATCH) {
57            Some(headers) => headers,
58            None => return Ok(None),
59        };
60
61        let mut wildcard = false;
62        for value in headers {
63            for part in value.as_str().trim().split(',') {
64                let part = part.trim();
65                if part == "*" {
66                    wildcard = true;
67                    continue;
68                }
69                entries.push(ETag::from_str(part)?);
70            }
71        }
72
73        Ok(Some(Self { entries, wildcard }))
74    }
75
76    /// Sets the `If-Match` header.
77    pub fn apply(&self, mut headers: impl AsMut<Headers>) {
78        headers.as_mut().insert(IF_MATCH, self.value());
79    }
80
81    /// Get the `HeaderName`.
82    pub fn name(&self) -> HeaderName {
83        IF_MATCH
84    }
85
86    /// Get the `HeaderValue`.
87    pub fn value(&self) -> HeaderValue {
88        let mut output = String::new();
89        for (n, etag) in self.entries.iter().enumerate() {
90            match n {
91                0 => write!(output, "{}", etag.to_string()).unwrap(),
92                _ => write!(output, ", {}", etag.to_string()).unwrap(),
93            };
94        }
95
96        if self.wildcard {
97            match output.len() {
98                0 => write!(output, "*").unwrap(),
99                _ => write!(output, ", *").unwrap(),
100            };
101        }
102
103        // SAFETY: the internal string is validated to be ASCII.
104        unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
105    }
106
107    /// Push a directive into the list of entries.
108    pub fn push(&mut self, directive: impl Into<ETag>) {
109        self.entries.push(directive.into());
110    }
111
112    /// Returns `true` if a wildcard directive was set.
113    pub fn wildcard(&self) -> bool {
114        self.wildcard
115    }
116
117    /// Set the wildcard directive.
118    pub fn set_wildcard(&mut self, wildcard: bool) {
119        self.wildcard = wildcard
120    }
121
122    /// An iterator visiting all server entries.
123    pub fn iter(&self) -> Iter<'_> {
124        Iter {
125            inner: self.entries.iter(),
126        }
127    }
128
129    /// An iterator visiting all server entries.
130    pub fn iter_mut(&mut self) -> IterMut<'_> {
131        IterMut {
132            inner: self.entries.iter_mut(),
133        }
134    }
135}
136
137impl IntoIterator for IfMatch {
138    type Item = ETag;
139    type IntoIter = IntoIter;
140
141    #[inline]
142    fn into_iter(self) -> Self::IntoIter {
143        IntoIter {
144            inner: self.entries.into_iter(),
145        }
146    }
147}
148
149impl<'a> IntoIterator for &'a IfMatch {
150    type Item = &'a ETag;
151    type IntoIter = Iter<'a>;
152
153    #[inline]
154    fn into_iter(self) -> Self::IntoIter {
155        self.iter()
156    }
157}
158
159impl<'a> IntoIterator for &'a mut IfMatch {
160    type Item = &'a mut ETag;
161    type IntoIter = IterMut<'a>;
162
163    #[inline]
164    fn into_iter(self) -> Self::IntoIter {
165        self.iter_mut()
166    }
167}
168
169/// A borrowing iterator over entries in `IfMatch`.
170#[derive(Debug)]
171pub struct IntoIter {
172    inner: std::vec::IntoIter<ETag>,
173}
174
175impl Iterator for IntoIter {
176    type Item = ETag;
177
178    fn next(&mut self) -> Option<Self::Item> {
179        self.inner.next()
180    }
181
182    #[inline]
183    fn size_hint(&self) -> (usize, Option<usize>) {
184        self.inner.size_hint()
185    }
186}
187
188/// A lending iterator over entries in `IfMatch`.
189#[derive(Debug)]
190pub struct Iter<'a> {
191    inner: slice::Iter<'a, ETag>,
192}
193
194impl<'a> Iterator for Iter<'a> {
195    type Item = &'a ETag;
196
197    fn next(&mut self) -> Option<Self::Item> {
198        self.inner.next()
199    }
200
201    #[inline]
202    fn size_hint(&self) -> (usize, Option<usize>) {
203        self.inner.size_hint()
204    }
205}
206
207/// A mutable iterator over entries in `IfMatch`.
208#[derive(Debug)]
209pub struct IterMut<'a> {
210    inner: slice::IterMut<'a, ETag>,
211}
212
213impl<'a> Iterator for IterMut<'a> {
214    type Item = &'a mut ETag;
215
216    fn next(&mut self) -> Option<Self::Item> {
217        self.inner.next()
218    }
219
220    #[inline]
221    fn size_hint(&self) -> (usize, Option<usize>) {
222        self.inner.size_hint()
223    }
224}
225
226impl ToHeaderValues for IfMatch {
227    type Iter = option::IntoIter<HeaderValue>;
228    fn to_header_values(&self) -> crate::Result<Self::Iter> {
229        // A HeaderValue will always convert into itself.
230        Ok(self.value().to_header_values().unwrap())
231    }
232}
233
234impl Debug for IfMatch {
235    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236        let mut list = f.debug_list();
237        for directive in &self.entries {
238            list.entry(directive);
239        }
240        list.finish()
241    }
242}
243
244#[cfg(test)]
245mod test {
246    use crate::conditional::{ETag, IfMatch};
247    use crate::Response;
248
249    #[test]
250    fn smoke() -> crate::Result<()> {
251        let mut entries = IfMatch::new();
252        entries.push(ETag::new("0xcafebeef".to_string()));
253        entries.push(ETag::new("0xbeefcafe".to_string()));
254
255        let mut res = Response::new(200);
256        entries.apply(&mut res);
257
258        let entries = IfMatch::from_headers(res)?.unwrap();
259        let mut entries = entries.iter();
260        assert_eq!(
261            entries.next().unwrap(),
262            &ETag::new("0xcafebeef".to_string())
263        );
264        assert_eq!(
265            entries.next().unwrap(),
266            &ETag::new("0xbeefcafe".to_string())
267        );
268        Ok(())
269    }
270
271    #[test]
272    fn wildcard() -> crate::Result<()> {
273        let mut entries = IfMatch::new();
274        entries.push(ETag::new("0xcafebeef".to_string()));
275        entries.set_wildcard(true);
276
277        let mut res = Response::new(200);
278        entries.apply(&mut res);
279
280        let entries = IfMatch::from_headers(res)?.unwrap();
281        assert!(entries.wildcard());
282        let mut entries = entries.iter();
283        assert_eq!(
284            entries.next().unwrap(),
285            &ETag::new("0xcafebeef".to_string())
286        );
287        Ok(())
288    }
289}