Skip to content

Commit 069a75e

Browse files
committed
Add conditional::IfNoneMatch
1 parent 8caa5eb commit 069a75e

File tree

4 files changed

+248
-2
lines changed

4 files changed

+248
-2
lines changed

src/conditional/if_match.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ use std::iter::Iterator;
66
use std::option;
77
use std::slice;
88

9-
/// A Match-Control header.
9+
/// HTTP `If-Match` header.
10+
///
11+
/// # Specifications
12+
///
13+
/// - [RFC 7232, section 3.1: If-Match](https://tools.ietf.org/html/rfc7232#section-3.1)
1014
///
1115
/// # Examples
1216
///

src/conditional/if_none_match.rs

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

src/conditional/match_directive.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::conditional::ETag;
22
use crate::headers::HeaderValue;
33

4-
/// An HTTP `If-{None-,}Match` directive.
4+
/// An Entity Tag based match directive.
55
#[derive(Debug, Clone, PartialEq, Eq)]
66
pub enum MatchDirective {
77
/// An ETag.

src/conditional/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
mod etag;
1212
mod if_match;
1313
mod if_modified_since;
14+
mod if_none_match;
1415
mod if_unmodified_since;
1516
mod last_modified;
1617
mod match_directive;
1718

1819
pub use etag::ETag;
1920
pub use if_match::IfMatch;
2021
pub use if_modified_since::IfModifiedSince;
22+
pub use if_none_match::IfNoneMatch;
2123
pub use if_unmodified_since::IfUnmodifiedSince;
2224
pub use last_modified::LastModified;
2325
pub use match_directive::MatchDirective;

0 commit comments

Comments
 (0)