-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b941518
commit fb6336e
Showing
4 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
//! Apply the HTTP method if the ETag matches. | ||
|
||
use crate::conditional::VaryDirective; | ||
use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, VARY}; | ||
|
||
use std::convert::TryInto; | ||
use std::fmt::{self, Debug, Write}; | ||
use std::iter::Iterator; | ||
use std::option; | ||
use std::slice; | ||
use std::str::FromStr; | ||
|
||
/// Apply the HTTP method if the ETag matches. | ||
/// | ||
/// # Specifications | ||
/// | ||
/// - [RFC 7231, section 7.1.4: Vary](https://tools.ietf.org/html/rfc7231#section-7.1.4) | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// # fn main() -> http_types::Result<()> { | ||
/// # | ||
/// use http_types::Response; | ||
/// use http_types::conditional::Vary; | ||
/// | ||
/// let mut entries = Vary::new(); | ||
/// entries.push("User-Agent")?; | ||
/// entries.push("Accept-Encoding")?; | ||
/// | ||
/// let mut res = Response::new(200); | ||
/// entries.apply(&mut res); | ||
/// | ||
/// let entries = Vary::from_headers(res)?.unwrap(); | ||
/// let mut entries = entries.iter(); | ||
/// assert_eq!(entries.next().unwrap(), "User-Agent"); | ||
/// assert_eq!(entries.next().unwrap(), "Accept-Encoding"); | ||
/// # | ||
/// # Ok(()) } | ||
/// ``` | ||
pub struct Vary { | ||
entries: Vec<VaryDirective>, | ||
} | ||
|
||
impl Vary { | ||
/// Create a new instance of `Vary`. | ||
pub fn new() -> Self { | ||
Self { entries: vec![] } | ||
} | ||
|
||
/// Create a new instance from headers. | ||
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> { | ||
let mut entries = vec![]; | ||
let headers = match headers.as_ref().get(VARY) { | ||
Some(headers) => headers, | ||
None => return Ok(None), | ||
}; | ||
|
||
for value in headers { | ||
for part in value.as_str().trim().split(',') { | ||
let entry = VaryDirective::from_str(part)?; | ||
entries.push(entry); | ||
} | ||
} | ||
|
||
Ok(Some(Self { entries })) | ||
} | ||
|
||
/// Sets the `If-Match` header. | ||
pub fn apply(&self, mut headers: impl AsMut<Headers>) { | ||
headers.as_mut().insert(VARY, self.value()); | ||
} | ||
|
||
/// Get the `HeaderName`. | ||
pub fn name(&self) -> HeaderName { | ||
VARY | ||
} | ||
|
||
/// Get the `HeaderValue`. | ||
pub fn value(&self) -> HeaderValue { | ||
let mut output = String::new(); | ||
for (n, directive) in self.entries.iter().enumerate() { | ||
let directive: HeaderValue = directive.clone().into(); | ||
match n { | ||
0 => write!(output, "{}", directive).unwrap(), | ||
_ => write!(output, ", {}", directive).unwrap(), | ||
}; | ||
} | ||
|
||
// SAFETY: the internal string is validated to be ASCII. | ||
unsafe { HeaderValue::from_bytes_unchecked(output.into()) } | ||
} | ||
|
||
/// Push a directive into the list of entries. | ||
pub fn push( | ||
&mut self, | ||
directive: impl TryInto<VaryDirective, Error = crate::Error>, | ||
) -> crate::Result<()> { | ||
self.entries.push(directive.try_into()?); | ||
Ok(()) | ||
} | ||
|
||
/// An iterator visiting all server entries. | ||
pub fn iter(&self) -> Iter<'_> { | ||
Iter { | ||
inner: self.entries.iter(), | ||
} | ||
} | ||
|
||
/// An iterator visiting all server entries. | ||
pub fn iter_mut(&mut self) -> IterMut<'_> { | ||
IterMut { | ||
inner: self.entries.iter_mut(), | ||
} | ||
} | ||
} | ||
|
||
impl IntoIterator for Vary { | ||
type Item = VaryDirective; | ||
type IntoIter = IntoIter; | ||
|
||
#[inline] | ||
fn into_iter(self) -> Self::IntoIter { | ||
IntoIter { | ||
inner: self.entries.into_iter(), | ||
} | ||
} | ||
} | ||
|
||
impl<'a> IntoIterator for &'a Vary { | ||
type Item = &'a VaryDirective; | ||
type IntoIter = Iter<'a>; | ||
|
||
#[inline] | ||
fn into_iter(self) -> Self::IntoIter { | ||
self.iter() | ||
} | ||
} | ||
|
||
impl<'a> IntoIterator for &'a mut Vary { | ||
type Item = &'a mut VaryDirective; | ||
type IntoIter = IterMut<'a>; | ||
|
||
#[inline] | ||
fn into_iter(self) -> Self::IntoIter { | ||
self.iter_mut() | ||
} | ||
} | ||
|
||
/// A borrowing iterator over entries in `Vary`. | ||
#[derive(Debug)] | ||
pub struct IntoIter { | ||
inner: std::vec::IntoIter<VaryDirective>, | ||
} | ||
|
||
impl Iterator for IntoIter { | ||
type Item = VaryDirective; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
self.inner.next() | ||
} | ||
|
||
#[inline] | ||
fn size_hint(&self) -> (usize, Option<usize>) { | ||
self.inner.size_hint() | ||
} | ||
} | ||
|
||
/// A lending iterator over entries in `Vary`. | ||
#[derive(Debug)] | ||
pub struct Iter<'a> { | ||
inner: slice::Iter<'a, VaryDirective>, | ||
} | ||
|
||
impl<'a> Iterator for Iter<'a> { | ||
type Item = &'a VaryDirective; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
self.inner.next() | ||
} | ||
|
||
#[inline] | ||
fn size_hint(&self) -> (usize, Option<usize>) { | ||
self.inner.size_hint() | ||
} | ||
} | ||
|
||
/// A mutable iterator over entries in `Vary`. | ||
#[derive(Debug)] | ||
pub struct IterMut<'a> { | ||
inner: slice::IterMut<'a, VaryDirective>, | ||
} | ||
|
||
impl<'a> Iterator for IterMut<'a> { | ||
type Item = &'a mut VaryDirective; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
self.inner.next() | ||
} | ||
|
||
#[inline] | ||
fn size_hint(&self) -> (usize, Option<usize>) { | ||
self.inner.size_hint() | ||
} | ||
} | ||
|
||
impl ToHeaderValues for Vary { | ||
type Iter = option::IntoIter<HeaderValue>; | ||
fn to_header_values(&self) -> crate::Result<Self::Iter> { | ||
// A HeaderValue will always convert into itself. | ||
Ok(self.value().to_header_values().unwrap()) | ||
} | ||
} | ||
|
||
impl Debug for Vary { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
let mut list = f.debug_list(); | ||
for directive in &self.entries { | ||
list.entry(directive); | ||
} | ||
list.finish() | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use crate::conditional::Vary; | ||
use crate::Response; | ||
|
||
#[test] | ||
fn smoke() -> crate::Result<()> { | ||
let mut entries = Vary::new(); | ||
entries.push("User-Agent")?; | ||
entries.push("Accept-Encoding")?; | ||
|
||
let mut res = Response::new(200); | ||
entries.apply(&mut res); | ||
|
||
let entries = Vary::from_headers(res)?.unwrap(); | ||
let mut entries = entries.iter(); | ||
assert_eq!(entries.next().unwrap(), "User-Agent"); | ||
assert_eq!(entries.next().unwrap(), "Accept-Encoding"); | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
use crate::headers::{HeaderName, HeaderValue}; | ||
use std::convert::TryFrom; | ||
use std::str::FromStr; | ||
|
||
/// An HeaderName-based match directive. | ||
#[derive(Debug, Clone, PartialEq, Eq)] | ||
pub enum VaryDirective { | ||
/// An HeaderName. | ||
HeaderName(HeaderName), | ||
/// Vary any resource. | ||
Wildcard, | ||
} | ||
|
||
// impl VaryDirective { | ||
// /// Create an instance from a string slice. | ||
// // | ||
// // This is a private method rather than a trait because we assume the | ||
// // input string is a single-value only. This is upheld by the calling | ||
// // function, but we cannot guarantee this to be true in the general | ||
// // sense. | ||
// pub(crate) fn from_str(s: &str) -> crate::Result<Option<Self>> { | ||
// let s = s.trim(); | ||
|
||
// match s { | ||
// "*" => Ok(Some(VaryDirective::Wildcard)), | ||
// s => { | ||
// HeaderName::from_string(s.into()).map(|name| Some(VaryDirective::HeaderName(name))) | ||
// } | ||
// } | ||
// } | ||
// } | ||
|
||
impl From<HeaderName> for VaryDirective { | ||
fn from(name: HeaderName) -> Self { | ||
Self::HeaderName(name) | ||
} | ||
} | ||
|
||
impl PartialEq<HeaderName> for VaryDirective { | ||
fn eq(&self, other: &HeaderName) -> bool { | ||
match self { | ||
Self::HeaderName(name) => name.eq(other), | ||
Self::Wildcard => false, | ||
} | ||
} | ||
} | ||
|
||
impl<'a> PartialEq<HeaderName> for &'a VaryDirective { | ||
fn eq(&self, other: &HeaderName) -> bool { | ||
match self { | ||
VaryDirective::HeaderName(name) => name.eq(other), | ||
VaryDirective::Wildcard => false, | ||
} | ||
} | ||
} | ||
|
||
impl From<VaryDirective> for HeaderValue { | ||
fn from(directive: VaryDirective) -> Self { | ||
match directive { | ||
VaryDirective::HeaderName(name) => unsafe { | ||
HeaderValue::from_bytes_unchecked(name.to_string().into_bytes()) | ||
}, | ||
VaryDirective::Wildcard => unsafe { | ||
HeaderValue::from_bytes_unchecked("*".to_string().into_bytes()) | ||
}, | ||
} | ||
} | ||
} | ||
|
||
impl FromStr for VaryDirective { | ||
type Err = crate::Error; | ||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
match s.trim() { | ||
"*" => Ok(VaryDirective::Wildcard), | ||
s => Ok(VaryDirective::HeaderName(s.parse()?)), | ||
} | ||
} | ||
} | ||
|
||
impl<'a> TryFrom<&'a str> for VaryDirective { | ||
type Error = crate::Error; | ||
|
||
fn try_from(value: &'a str) -> Result<Self, Self::Error> { | ||
VaryDirective::from_str(value) | ||
} | ||
} | ||
|
||
impl PartialEq<str> for VaryDirective { | ||
fn eq(&self, other: &str) -> bool { | ||
match self { | ||
VaryDirective::Wildcard => "*" == other, | ||
VaryDirective::HeaderName(s) => s == other, | ||
} | ||
} | ||
} | ||
|
||
impl<'a> PartialEq<&'a str> for VaryDirective { | ||
fn eq(&self, other: &&'a str) -> bool { | ||
match self { | ||
VaryDirective::Wildcard => &"*" == other, | ||
VaryDirective::HeaderName(s) => s == other, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters