Merge pull request #750 from calebmer/feat/prefer
feat(header): add prefer and preference applied headers
This commit is contained in:
@@ -44,6 +44,8 @@ pub use self::if_range::IfRange;
|
|||||||
pub use self::last_modified::LastModified;
|
pub use self::last_modified::LastModified;
|
||||||
pub use self::location::Location;
|
pub use self::location::Location;
|
||||||
pub use self::pragma::Pragma;
|
pub use self::pragma::Pragma;
|
||||||
|
pub use self::prefer::{Prefer, Preference};
|
||||||
|
pub use self::preference_applied::PreferenceApplied;
|
||||||
pub use self::range::{Range, ByteRangeSpec};
|
pub use self::range::{Range, ByteRangeSpec};
|
||||||
pub use self::referer::Referer;
|
pub use self::referer::Referer;
|
||||||
pub use self::server::Server;
|
pub use self::server::Server;
|
||||||
@@ -404,6 +406,8 @@ mod if_unmodified_since;
|
|||||||
mod last_modified;
|
mod last_modified;
|
||||||
mod location;
|
mod location;
|
||||||
mod pragma;
|
mod pragma;
|
||||||
|
mod prefer;
|
||||||
|
mod preference_applied;
|
||||||
mod range;
|
mod range;
|
||||||
mod referer;
|
mod referer;
|
||||||
mod server;
|
mod server;
|
||||||
|
|||||||
203
src/header/common/prefer.rs
Normal file
203
src/header/common/prefer.rs
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use header::{Header, HeaderFormat};
|
||||||
|
use header::parsing::{from_comma_delimited, fmt_comma_delimited};
|
||||||
|
|
||||||
|
/// `Prefer` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240)
|
||||||
|
///
|
||||||
|
/// The `Prefer` header field is HTTP header field that can be used by a
|
||||||
|
/// client to request that certain behaviors be employed by a server
|
||||||
|
/// while processing a request.
|
||||||
|
///
|
||||||
|
/// # ABNF
|
||||||
|
/// ```plain
|
||||||
|
/// Prefer = "Prefer" ":" 1#preference
|
||||||
|
/// preference = token [ BWS "=" BWS word ]
|
||||||
|
/// *( OWS ";" [ OWS parameter ] )
|
||||||
|
/// parameter = token [ BWS "=" BWS word ]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Example values
|
||||||
|
/// * `respond-async`
|
||||||
|
/// * `return=minimal`
|
||||||
|
/// * `wait=30`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// use hyper::header::{Headers, Prefer, Preference};
|
||||||
|
///
|
||||||
|
/// let mut headers = Headers::new();
|
||||||
|
/// headers.set(
|
||||||
|
/// Prefer(vec![Preference::RespondAsync])
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
/// ```
|
||||||
|
/// use hyper::header::{Headers, Prefer, Preference};
|
||||||
|
///
|
||||||
|
/// let mut headers = Headers::new();
|
||||||
|
/// headers.set(
|
||||||
|
/// Prefer(vec![
|
||||||
|
/// Preference::RespondAsync,
|
||||||
|
/// Preference::ReturnRepresentation,
|
||||||
|
/// Preference::Wait(10u32),
|
||||||
|
/// Preference::Extension("foo".to_owned(),
|
||||||
|
/// "bar".to_owned(),
|
||||||
|
/// vec![]),
|
||||||
|
/// ])
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
|
pub struct Prefer(pub Vec<Preference>);
|
||||||
|
|
||||||
|
__hyper__deref!(Prefer => Vec<Preference>);
|
||||||
|
|
||||||
|
impl Header for Prefer {
|
||||||
|
fn header_name() -> &'static str {
|
||||||
|
"Prefer"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_header(raw: &[Vec<u8>]) -> ::Result<Prefer> {
|
||||||
|
let preferences = try!(from_comma_delimited(raw));
|
||||||
|
if !preferences.is_empty() {
|
||||||
|
Ok(Prefer(preferences))
|
||||||
|
} else {
|
||||||
|
Err(::Error::Header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HeaderFormat for Prefer {
|
||||||
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt_comma_delimited(f, &self[..])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer contains a list of these preferences.
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
|
pub enum Preference {
|
||||||
|
/// "respond-async"
|
||||||
|
RespondAsync,
|
||||||
|
/// "return=representation"
|
||||||
|
ReturnRepresentation,
|
||||||
|
/// "return=minimal"
|
||||||
|
ReturnMinimal,
|
||||||
|
/// "handling=strict"
|
||||||
|
HandlingStrict,
|
||||||
|
/// "handling=leniant"
|
||||||
|
HandlingLeniant,
|
||||||
|
/// "wait=delta"
|
||||||
|
Wait(u32),
|
||||||
|
|
||||||
|
/// Extension preferences. Always has a value, if none is specified it is
|
||||||
|
/// just "". A preference can also have a list of parameters.
|
||||||
|
Extension(String, String, Vec<(String, String)>)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Preference {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use self::Preference::*;
|
||||||
|
fmt::Display::fmt(match *self {
|
||||||
|
RespondAsync => "respond-async",
|
||||||
|
ReturnRepresentation => "return=representation",
|
||||||
|
ReturnMinimal => "return=minimal",
|
||||||
|
HandlingStrict => "handling=strict",
|
||||||
|
HandlingLeniant => "handling=leniant",
|
||||||
|
|
||||||
|
Wait(secs) => return write!(f, "wait={}", secs),
|
||||||
|
|
||||||
|
Extension(ref name, ref value, ref params) => {
|
||||||
|
try!(write!(f, "{}", name));
|
||||||
|
if value != "" { try!(write!(f, "={}", value)); }
|
||||||
|
if params.len() > 0 {
|
||||||
|
for &(ref name, ref value) in params {
|
||||||
|
try!(write!(f, "; {}", name));
|
||||||
|
if value != "" { try!(write!(f, "={}", value)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Preference {
|
||||||
|
type Err = Option<<u32 as FromStr>::Err>;
|
||||||
|
fn from_str(s: &str) -> Result<Preference, Option<<u32 as FromStr>::Err>> {
|
||||||
|
use self::Preference::*;
|
||||||
|
let mut params = s.split(';').map(|p| {
|
||||||
|
let mut param = p.splitn(2, '=');
|
||||||
|
match (param.next(), param.next()) {
|
||||||
|
(Some(name), Some(value)) => (name.trim(), value.trim().trim_matches('"')),
|
||||||
|
(Some(name), None) => (name.trim(), ""),
|
||||||
|
// This can safely be unreachable because the [`splitn`][1]
|
||||||
|
// function (used above) will always have at least one value.
|
||||||
|
//
|
||||||
|
// [1]: http://doc.rust-lang.org/std/primitive.str.html#method.splitn
|
||||||
|
_ => { unreachable!(); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
match params.nth(0) {
|
||||||
|
Some(param) => {
|
||||||
|
let rest: Vec<(String, String)> = params.map(|(l, r)| (l.to_owned(), r.to_owned())).collect();
|
||||||
|
match param {
|
||||||
|
("respond-async", "") => if rest.len() == 0 { Ok(RespondAsync) } else { Err(None) },
|
||||||
|
("return", "representation") => if rest.len() == 0 { Ok(ReturnRepresentation) } else { Err(None) },
|
||||||
|
("return", "minimal") => if rest.len() == 0 { Ok(ReturnMinimal) } else { Err(None) },
|
||||||
|
("handling", "strict") => if rest.len() == 0 { Ok(HandlingStrict) } else { Err(None) },
|
||||||
|
("handling", "leniant") => if rest.len() == 0 { Ok(HandlingLeniant) } else { Err(None) },
|
||||||
|
("wait", secs) => if rest.len() == 0 { secs.parse().map(Wait).map_err(Some) } else { Err(None) },
|
||||||
|
(left, right) => Ok(Extension(left.to_owned(), right.to_owned(), rest))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => Err(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use header::Header;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_multiple_headers() {
|
||||||
|
let prefer = Header::parse_header(&[b"respond-async, return=representation".to_vec()]);
|
||||||
|
assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::RespondAsync,
|
||||||
|
Preference::ReturnRepresentation])))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_argument() {
|
||||||
|
let prefer = Header::parse_header(&[b"wait=100, handling=leniant, respond-async".to_vec()]);
|
||||||
|
assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::Wait(100),
|
||||||
|
Preference::HandlingLeniant,
|
||||||
|
Preference::RespondAsync])))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_quote_form() {
|
||||||
|
let prefer = Header::parse_header(&[b"wait=\"200\", handling=\"strict\"".to_vec()]);
|
||||||
|
assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::Wait(200),
|
||||||
|
Preference::HandlingStrict])))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_extension() {
|
||||||
|
let prefer = Header::parse_header(&[b"foo, bar=baz, baz; foo; bar=baz, bux=\"\"; foo=\"\", buz=\"some parameter\"".to_vec()]);
|
||||||
|
assert_eq!(prefer.ok(), Some(Prefer(vec![
|
||||||
|
Preference::Extension("foo".to_owned(), "".to_owned(), vec![]),
|
||||||
|
Preference::Extension("bar".to_owned(), "baz".to_owned(), vec![]),
|
||||||
|
Preference::Extension("baz".to_owned(), "".to_owned(), vec![("foo".to_owned(), "".to_owned()), ("bar".to_owned(), "baz".to_owned())]),
|
||||||
|
Preference::Extension("bux".to_owned(), "".to_owned(), vec![("foo".to_owned(), "".to_owned())]),
|
||||||
|
Preference::Extension("buz".to_owned(), "some parameter".to_owned(), vec![])])))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fail_with_args() {
|
||||||
|
let prefer: ::Result<Prefer> = Header::parse_header(&[b"respond-async; foo=bar".to_vec()]);
|
||||||
|
assert_eq!(prefer.ok(), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bench_header!(normal,
|
||||||
|
Prefer, { vec![b"respond-async, return=representation".to_vec(), b"wait=100".to_vec()] });
|
||||||
100
src/header/common/preference_applied.rs
Normal file
100
src/header/common/preference_applied.rs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use header::{Header, HeaderFormat, Preference};
|
||||||
|
use header::parsing::{from_comma_delimited, fmt_comma_delimited};
|
||||||
|
|
||||||
|
/// `Preference-Applied` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240)
|
||||||
|
///
|
||||||
|
/// The `Preference-Applied` response header may be included within a
|
||||||
|
/// response message as an indication as to which `Prefer` header tokens were
|
||||||
|
/// honored by the server and applied to the processing of a request.
|
||||||
|
///
|
||||||
|
/// # ABNF
|
||||||
|
/// ```plain
|
||||||
|
/// Preference-Applied = "Preference-Applied" ":" 1#applied-pref
|
||||||
|
/// applied-pref = token [ BWS "=" BWS word ]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Example values
|
||||||
|
/// * `respond-async`
|
||||||
|
/// * `return=minimal`
|
||||||
|
/// * `wait=30`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// use hyper::header::{Headers, PreferenceApplied, Preference};
|
||||||
|
///
|
||||||
|
/// let mut headers = Headers::new();
|
||||||
|
/// headers.set(
|
||||||
|
/// PreferenceApplied(vec![Preference::RespondAsync])
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
/// ```
|
||||||
|
/// use hyper::header::{Headers, PreferenceApplied, Preference};
|
||||||
|
///
|
||||||
|
/// let mut headers = Headers::new();
|
||||||
|
/// headers.set(
|
||||||
|
/// PreferenceApplied(vec![
|
||||||
|
/// Preference::RespondAsync,
|
||||||
|
/// Preference::ReturnRepresentation,
|
||||||
|
/// Preference::Wait(10u32),
|
||||||
|
/// Preference::Extension("foo".to_owned(),
|
||||||
|
/// "bar".to_owned(),
|
||||||
|
/// vec![]),
|
||||||
|
/// ])
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
|
pub struct PreferenceApplied(pub Vec<Preference>);
|
||||||
|
|
||||||
|
__hyper__deref!(PreferenceApplied => Vec<Preference>);
|
||||||
|
|
||||||
|
impl Header for PreferenceApplied {
|
||||||
|
fn header_name() -> &'static str {
|
||||||
|
"Preference-Applied"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_header(raw: &[Vec<u8>]) -> ::Result<PreferenceApplied> {
|
||||||
|
let preferences = try!(from_comma_delimited(raw));
|
||||||
|
if !preferences.is_empty() {
|
||||||
|
Ok(PreferenceApplied(preferences))
|
||||||
|
} else {
|
||||||
|
Err(::Error::Header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HeaderFormat for PreferenceApplied {
|
||||||
|
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let preferences: Vec<_> = self.0.iter().map(|pref| match pref {
|
||||||
|
// The spec ignores parameters in `Preferences-Applied`
|
||||||
|
&Preference::Extension(ref name, ref value, _) => Preference::Extension(
|
||||||
|
name.to_owned(),
|
||||||
|
value.to_owned(),
|
||||||
|
vec![]
|
||||||
|
),
|
||||||
|
preference @ _ => preference.clone()
|
||||||
|
}).collect();
|
||||||
|
fmt_comma_delimited(f, &preferences)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use header::{HeaderFormat, Preference};
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_format_ignore_parameters() {
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", &PreferenceApplied(vec![Preference::Extension(
|
||||||
|
"foo".to_owned(),
|
||||||
|
"bar".to_owned(),
|
||||||
|
vec![("bar".to_owned(), "foo".to_owned()), ("buz".to_owned(), "".to_owned())]
|
||||||
|
)]) as &(HeaderFormat + Send + Sync)),
|
||||||
|
"foo=bar".to_owned()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bench_header!(normal,
|
||||||
|
PreferenceApplied, { vec![b"respond-async, return=representation".to_vec(), b"wait=100".to_vec()] });
|
||||||
Reference in New Issue
Block a user