http_types/conditional/
if_none_match.rs

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