Skip to content

Commit 6f64930

Browse files
committed
feat(header): add prefer and preference applied headers
Closes #747
1 parent 028f586 commit 6f64930

File tree

3 files changed

+307
-0
lines changed

3 files changed

+307
-0
lines changed

src/header/common/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ pub use self::if_range::IfRange;
4444
pub use self::last_modified::LastModified;
4545
pub use self::location::Location;
4646
pub use self::pragma::Pragma;
47+
pub use self::prefer::{Prefer, Preference};
48+
pub use self::preference_applied::PreferenceApplied;
4749
pub use self::range::{Range, ByteRangeSpec};
4850
pub use self::referer::Referer;
4951
pub use self::server::Server;
@@ -392,6 +394,8 @@ mod if_unmodified_since;
392394
mod last_modified;
393395
mod location;
394396
mod pragma;
397+
mod prefer;
398+
mod preference_applied;
395399
mod range;
396400
mod referer;
397401
mod server;

src/header/common/prefer.rs

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
use std::fmt;
2+
use std::str::FromStr;
3+
use header::{Header, HeaderFormat};
4+
use header::parsing::{from_comma_delimited, fmt_comma_delimited};
5+
6+
/// `Prefer` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240)
7+
///
8+
/// The `Prefer` header field is HTTP header field that can be used by a
9+
/// client to request that certain behaviors be employed by a server
10+
/// while processing a request.
11+
///
12+
/// # ABNF
13+
/// ```plain
14+
/// Prefer = "Prefer" ":" 1#preference
15+
/// preference = token [ BWS "=" BWS word ]
16+
/// *( OWS ";" [ OWS parameter ] )
17+
/// parameter = token [ BWS "=" BWS word ]
18+
/// ```
19+
///
20+
/// # Example values
21+
/// * `respond-async`
22+
/// * `return=minimal`
23+
/// * `wait=30`
24+
///
25+
/// # Examples
26+
/// ```
27+
/// use hyper::header::{Headers, Prefer, Preference};
28+
///
29+
/// let mut headers = Headers::new();
30+
/// headers.set(
31+
/// Prefer(vec![Preference::RespondAsync])
32+
/// );
33+
/// ```
34+
/// ```
35+
/// use hyper::header::{Headers, Prefer, Preference};
36+
///
37+
/// let mut headers = Headers::new();
38+
/// headers.set(
39+
/// Prefer(vec![
40+
/// Preference::RespondAsync,
41+
/// Preference::ReturnRepresentation,
42+
/// Preference::Wait(10u32),
43+
/// Preference::Extension("foo".to_owned(),
44+
/// "bar".to_owned(),
45+
/// vec![]),
46+
/// ])
47+
/// );
48+
/// ```
49+
#[derive(PartialEq, Clone, Debug)]
50+
pub struct Prefer(pub Vec<Preference>);
51+
52+
__hyper__deref!(Prefer => Vec<Preference>);
53+
54+
impl Header for Prefer {
55+
fn header_name() -> &'static str {
56+
"Prefer"
57+
}
58+
59+
fn parse_header(raw: &[Vec<u8>]) -> ::Result<Prefer> {
60+
let preferences = try!(from_comma_delimited(raw));
61+
if !preferences.is_empty() {
62+
Ok(Prefer(preferences))
63+
} else {
64+
Err(::Error::Header)
65+
}
66+
}
67+
}
68+
69+
impl HeaderFormat for Prefer {
70+
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
71+
fmt_comma_delimited(f, &self[..])
72+
}
73+
}
74+
75+
/// Prefer contains a list of these preferences.
76+
#[derive(PartialEq, Clone, Debug)]
77+
pub enum Preference {
78+
/// "respond-async"
79+
RespondAsync,
80+
/// "return=representation"
81+
ReturnRepresentation,
82+
/// "return=minimal"
83+
ReturnMinimal,
84+
/// "handling=strict"
85+
HandlingStrict,
86+
/// "handling=leniant"
87+
HandlingLeniant,
88+
/// "wait=delta"
89+
Wait(u32),
90+
91+
/// Extension preferences. Always has a value, if none is specified it is
92+
/// just "". A preference can also have a list of parameters.
93+
Extension(String, String, Vec<(String, String)>)
94+
}
95+
96+
impl fmt::Display for Preference {
97+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98+
use self::Preference::*;
99+
fmt::Display::fmt(match *self {
100+
RespondAsync => "respond-async",
101+
ReturnRepresentation => "return=representation",
102+
ReturnMinimal => "return=minimal",
103+
HandlingStrict => "handling=strict",
104+
HandlingLeniant => "handling=leniant",
105+
106+
Wait(secs) => return write!(f, "wait={}", secs),
107+
108+
Extension(ref name, ref value, ref params) => {
109+
try!(write!(f, "{}", name));
110+
if value != "" { try!(write!(f, "={}", value)); }
111+
if params.len() > 0 {
112+
for &(ref name, ref value) in params {
113+
try!(write!(f, "; {}", name));
114+
if value != "" { try!(write!(f, "={}", value)); }
115+
}
116+
}
117+
return Ok(());
118+
}
119+
}, f)
120+
}
121+
}
122+
123+
impl FromStr for Preference {
124+
type Err = Option<<u32 as FromStr>::Err>;
125+
fn from_str(s: &str) -> Result<Preference, Option<<u32 as FromStr>::Err>> {
126+
use self::Preference::*;
127+
let mut params = s.split(';').map(|p| {
128+
let mut param = p.splitn(2, '=');
129+
match (param.next(), param.next()) {
130+
(Some(name), Some(value)) => (name.trim(), value.trim().trim_matches('"')),
131+
(Some(name), None) => (name.trim(), ""),
132+
// This can safely be unreachable because the [`splitn`][1]
133+
// function (used above) will always have at least one value.
134+
//
135+
// [1]: http://doc.rust-lang.org/std/primitive.str.html#method.splitn
136+
_ => { unreachable!(); }
137+
}
138+
});
139+
match params.nth(0) {
140+
Some(param) => {
141+
let rest: Vec<(String, String)> = params.map(|(l, r)| (l.to_owned(), r.to_owned())).collect();
142+
match param {
143+
("respond-async", "") => if rest.len() == 0 { Ok(RespondAsync) } else { Err(None) },
144+
("return", "representation") => if rest.len() == 0 { Ok(ReturnRepresentation) } else { Err(None) },
145+
("return", "minimal") => if rest.len() == 0 { Ok(ReturnMinimal) } else { Err(None) },
146+
("handling", "strict") => if rest.len() == 0 { Ok(HandlingStrict) } else { Err(None) },
147+
("handling", "leniant") => if rest.len() == 0 { Ok(HandlingLeniant) } else { Err(None) },
148+
("wait", secs) => if rest.len() == 0 { secs.parse().map(Wait).map_err(Some) } else { Err(None) },
149+
(left, right) => Ok(Extension(left.to_owned(), right.to_owned(), rest))
150+
}
151+
},
152+
None => Err(None)
153+
}
154+
}
155+
}
156+
157+
#[cfg(test)]
158+
mod tests {
159+
use header::Header;
160+
use super::*;
161+
162+
#[test]
163+
fn test_parse_multiple_headers() {
164+
let prefer = Header::parse_header(&[b"respond-async, return=representation".to_vec()]);
165+
assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::RespondAsync,
166+
Preference::ReturnRepresentation])))
167+
}
168+
169+
#[test]
170+
fn test_parse_argument() {
171+
let prefer = Header::parse_header(&[b"wait=100, handling=leniant, respond-async".to_vec()]);
172+
assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::Wait(100),
173+
Preference::HandlingLeniant,
174+
Preference::RespondAsync])))
175+
}
176+
177+
#[test]
178+
fn test_parse_quote_form() {
179+
let prefer = Header::parse_header(&[b"wait=\"200\", handling=\"strict\"".to_vec()]);
180+
assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::Wait(200),
181+
Preference::HandlingStrict])))
182+
}
183+
184+
#[test]
185+
fn test_parse_extension() {
186+
let prefer = Header::parse_header(&[b"foo, bar=baz, baz; foo; bar=baz, bux=\"\"; foo=\"\", buz=\"some parameter\"".to_vec()]);
187+
assert_eq!(prefer.ok(), Some(Prefer(vec![
188+
Preference::Extension("foo".to_owned(), "".to_owned(), vec![]),
189+
Preference::Extension("bar".to_owned(), "baz".to_owned(), vec![]),
190+
Preference::Extension("baz".to_owned(), "".to_owned(), vec![("foo".to_owned(), "".to_owned()), ("bar".to_owned(), "baz".to_owned())]),
191+
Preference::Extension("bux".to_owned(), "".to_owned(), vec![("foo".to_owned(), "".to_owned())]),
192+
Preference::Extension("buz".to_owned(), "some parameter".to_owned(), vec![])])))
193+
}
194+
195+
#[test]
196+
fn test_fail_with_args() {
197+
let prefer: ::Result<Prefer> = Header::parse_header(&[b"respond-async; foo=bar".to_vec()]);
198+
assert_eq!(prefer.ok(), None);
199+
}
200+
}
201+
202+
bench_header!(normal,
203+
Prefer, { vec![b"respond-async, return=representation".to_vec(), b"wait=100".to_vec()] });
+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use std::fmt;
2+
use header::{Header, HeaderFormat, Preference};
3+
use header::parsing::{from_comma_delimited, fmt_comma_delimited};
4+
5+
/// `Preference-Applied` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240)
6+
///
7+
/// The `Preference-Applied` response header may be included within a
8+
/// response message as an indication as to which `Prefer` header tokens were
9+
/// honored by the server and applied to the processing of a request.
10+
///
11+
/// # ABNF
12+
/// ```plain
13+
/// Preference-Applied = "Preference-Applied" ":" 1#applied-pref
14+
/// applied-pref = token [ BWS "=" BWS word ]
15+
/// ```
16+
///
17+
/// # Example values
18+
/// * `respond-async`
19+
/// * `return=minimal`
20+
/// * `wait=30`
21+
///
22+
/// # Examples
23+
/// ```
24+
/// use hyper::header::{Headers, PreferenceApplied, Preference};
25+
///
26+
/// let mut headers = Headers::new();
27+
/// headers.set(
28+
/// PreferenceApplied(vec![Preference::RespondAsync])
29+
/// );
30+
/// ```
31+
/// ```
32+
/// use hyper::header::{Headers, PreferenceApplied, Preference};
33+
///
34+
/// let mut headers = Headers::new();
35+
/// headers.set(
36+
/// PreferenceApplied(vec![
37+
/// Preference::RespondAsync,
38+
/// Preference::ReturnRepresentation,
39+
/// Preference::Wait(10u32),
40+
/// Preference::Extension("foo".to_owned(),
41+
/// "bar".to_owned(),
42+
/// vec![]),
43+
/// ])
44+
/// );
45+
/// ```
46+
#[derive(PartialEq, Clone, Debug)]
47+
pub struct PreferenceApplied(pub Vec<Preference>);
48+
49+
__hyper__deref!(PreferenceApplied => Vec<Preference>);
50+
51+
impl Header for PreferenceApplied {
52+
fn header_name() -> &'static str {
53+
"Preference-Applied"
54+
}
55+
56+
fn parse_header(raw: &[Vec<u8>]) -> ::Result<PreferenceApplied> {
57+
let preferences = try!(from_comma_delimited(raw));
58+
if !preferences.is_empty() {
59+
Ok(PreferenceApplied(preferences))
60+
} else {
61+
Err(::Error::Header)
62+
}
63+
}
64+
}
65+
66+
impl HeaderFormat for PreferenceApplied {
67+
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
68+
let preferences: Vec<_> = self.0.iter().map(|pref| match pref {
69+
// The spec ignores parameters in `Preferences-Applied`
70+
&Preference::Extension(ref name, ref value, _) => Preference::Extension(
71+
name.to_owned(),
72+
value.to_owned(),
73+
vec![]
74+
),
75+
preference @ _ => preference.clone()
76+
}).collect();
77+
fmt_comma_delimited(f, &preferences)
78+
}
79+
}
80+
81+
#[cfg(test)]
82+
mod tests {
83+
use header::{HeaderFormat, Preference};
84+
use super::*;
85+
86+
#[test]
87+
fn test_format_ignore_parameters() {
88+
assert_eq!(
89+
format!("{}", &PreferenceApplied(vec![Preference::Extension(
90+
"foo".to_owned(),
91+
"bar".to_owned(),
92+
vec![("bar".to_owned(), "foo".to_owned()), ("buz".to_owned(), "".to_owned())]
93+
)]) as &(HeaderFormat + Send + Sync)),
94+
"foo=bar".to_owned()
95+
);
96+
}
97+
}
98+
99+
bench_header!(normal,
100+
PreferenceApplied, { vec![b"respond-async, return=representation".to_vec(), b"wait=100".to_vec()] });

0 commit comments

Comments
 (0)