http_types/conditional/
if_match.rs1use 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
11pub struct IfMatch {
40 entries: Vec<ETag>,
41 wildcard: bool,
42}
43
44impl IfMatch {
45 pub fn new() -> Self {
47 Self {
48 entries: vec![],
49 wildcard: false,
50 }
51 }
52
53 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 pub fn apply(&self, mut headers: impl AsMut<Headers>) {
78 headers.as_mut().insert(IF_MATCH, self.value());
79 }
80
81 pub fn name(&self) -> HeaderName {
83 IF_MATCH
84 }
85
86 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 unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
105 }
106
107 pub fn push(&mut self, directive: impl Into<ETag>) {
109 self.entries.push(directive.into());
110 }
111
112 pub fn wildcard(&self) -> bool {
114 self.wildcard
115 }
116
117 pub fn set_wildcard(&mut self, wildcard: bool) {
119 self.wildcard = wildcard
120 }
121
122 pub fn iter(&self) -> Iter<'_> {
124 Iter {
125 inner: self.entries.iter(),
126 }
127 }
128
129 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#[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#[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#[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 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}