Skip to content

Commit 6ce3d55

Browse files
committed
Add conditional::Vary
1 parent daf3065 commit 6ce3d55

File tree

4 files changed

+363
-0
lines changed

4 files changed

+363
-0
lines changed

src/conditional/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@ mod if_modified_since;
1313
mod if_unmodified_since;
1414
mod last_modified;
1515
mod match_directive;
16+
mod vary;
17+
mod vary_directive;
1618

1719
pub mod if_match;
1820
pub mod if_none_match;
1921

2022
pub use etag::ETag;
2123
pub use match_directive::MatchDirective;
24+
pub use vary::Vary;
25+
pub use vary_directive::VaryDirective;
2226

2327
#[doc(inline)]
2428
pub use if_match::IfMatch;

src/conditional/vary.rs

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

src/conditional/vary_directive.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use crate::headers::{HeaderName, HeaderValue};
2+
use std::convert::TryFrom;
3+
use std::str::FromStr;
4+
5+
/// An HeaderName-based match directive.
6+
#[derive(Debug, Clone, PartialEq, Eq)]
7+
pub enum VaryDirective {
8+
/// An HeaderName.
9+
HeaderName(HeaderName),
10+
/// Vary any resource.
11+
Wildcard,
12+
}
13+
14+
// impl VaryDirective {
15+
// /// Create an instance from a string slice.
16+
// //
17+
// // This is a private method rather than a trait because we assume the
18+
// // input string is a single-value only. This is upheld by the calling
19+
// // function, but we cannot guarantee this to be true in the general
20+
// // sense.
21+
// pub(crate) fn from_str(s: &str) -> crate::Result<Option<Self>> {
22+
// let s = s.trim();
23+
24+
// match s {
25+
// "*" => Ok(Some(VaryDirective::Wildcard)),
26+
// s => {
27+
// HeaderName::from_string(s.into()).map(|name| Some(VaryDirective::HeaderName(name)))
28+
// }
29+
// }
30+
// }
31+
// }
32+
33+
impl From<HeaderName> for VaryDirective {
34+
fn from(name: HeaderName) -> Self {
35+
Self::HeaderName(name)
36+
}
37+
}
38+
39+
impl PartialEq<HeaderName> for VaryDirective {
40+
fn eq(&self, other: &HeaderName) -> bool {
41+
match self {
42+
Self::HeaderName(name) => name.eq(other),
43+
Self::Wildcard => false,
44+
}
45+
}
46+
}
47+
48+
impl<'a> PartialEq<HeaderName> for &'a VaryDirective {
49+
fn eq(&self, other: &HeaderName) -> bool {
50+
match self {
51+
VaryDirective::HeaderName(name) => name.eq(other),
52+
VaryDirective::Wildcard => false,
53+
}
54+
}
55+
}
56+
57+
impl From<VaryDirective> for HeaderValue {
58+
fn from(directive: VaryDirective) -> Self {
59+
match directive {
60+
VaryDirective::HeaderName(name) => unsafe {
61+
HeaderValue::from_bytes_unchecked(name.to_string().into_bytes())
62+
},
63+
VaryDirective::Wildcard => unsafe {
64+
HeaderValue::from_bytes_unchecked("*".to_string().into_bytes())
65+
},
66+
}
67+
}
68+
}
69+
70+
impl FromStr for VaryDirective {
71+
type Err = crate::Error;
72+
73+
fn from_str(s: &str) -> Result<Self, Self::Err> {
74+
match s.trim() {
75+
"*" => Ok(VaryDirective::Wildcard),
76+
s => Ok(VaryDirective::HeaderName(s.parse()?)),
77+
}
78+
}
79+
}
80+
81+
impl<'a> TryFrom<&'a str> for VaryDirective {
82+
type Error = crate::Error;
83+
84+
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
85+
VaryDirective::from_str(value)
86+
}
87+
}
88+
89+
impl PartialEq<str> for VaryDirective {
90+
fn eq(&self, other: &str) -> bool {
91+
match self {
92+
VaryDirective::Wildcard => "*" == other,
93+
VaryDirective::HeaderName(s) => s == other,
94+
}
95+
}
96+
}
97+
98+
impl<'a> PartialEq<&'a str> for VaryDirective {
99+
fn eq(&self, other: &&'a str) -> bool {
100+
match self {
101+
VaryDirective::Wildcard => &"*" == other,
102+
VaryDirective::HeaderName(s) => s == other,
103+
}
104+
}
105+
}

src/headers/header_name.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ impl HeaderName {
2323
Ok(HeaderName(Cow::Owned(string)))
2424
}
2525

26+
/// Create a new `HeaderName` from an ASCII string.
27+
///
28+
/// # Error
29+
///
30+
/// This function will error if the string is not valid ASCII.
31+
pub fn from_string(s: String) -> Result<Self, Error> {
32+
Self::from_bytes(s.into_bytes())
33+
}
34+
2635
/// Returns the header name as a `&str`.
2736
pub fn as_str(&self) -> &'_ str {
2837
&self.0

0 commit comments

Comments
 (0)