hyperx/header/common/
prefer.rs

1use std::fmt;
2use std::str::FromStr;
3use header::{Header, RawLike};
4use 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 can be used by a client to request that certain
9/// behaviors be employed by a server while processing a request.
10///
11/// # ABNF
12///
13/// ```text
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/// ```
28/// # extern crate http;
29/// use hyperx::header::{Prefer, Preference, TypedHeaders};
30///
31/// let mut headers = http::HeaderMap::new();
32/// headers.insert(
33///     "prefer",
34///     Prefer(vec![Preference::RespondAsync]).to_string().parse().unwrap()
35/// );
36/// ```
37///
38/// ```
39/// # extern crate http;
40/// use hyperx::header::{Prefer, Preference, TypedHeaders};
41///
42/// let mut headers = http::HeaderMap::new();
43/// headers.insert(
44///     "prefer",
45///     Prefer(vec![
46///         Preference::RespondAsync,
47///         Preference::ReturnRepresentation,
48///         Preference::Wait(10u32),
49///         Preference::Extension("foo".to_owned(),
50///                               "bar".to_owned(),
51///                               vec![]),
52///     ]).to_string().parse().unwrap()
53/// );
54/// ```
55#[derive(PartialEq, Clone, Debug)]
56pub struct Prefer(pub Vec<Preference>);
57
58__hyper__deref!(Prefer => Vec<Preference>);
59
60impl Header for Prefer {
61    fn header_name() -> &'static str {
62        static NAME: &'static str = "Prefer";
63        NAME
64    }
65
66    fn parse_header<'a, T>(raw: &'a T) -> ::Result<Prefer>
67    where T: RawLike<'a>
68    {
69        let preferences = from_comma_delimited(raw)?;
70        if !preferences.is_empty() {
71            Ok(Prefer(preferences))
72        } else {
73            Err(::Error::Header)
74        }
75    }
76
77    fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
78        f.fmt_line(self)
79    }
80}
81
82impl fmt::Display for Prefer {
83    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84        fmt_comma_delimited(f, &self[..])
85    }
86}
87
88/// Prefer contains a list of these preferences.
89#[derive(PartialEq, Clone, Debug)]
90pub enum Preference {
91    /// "respond-async"
92    RespondAsync,
93    /// "return=representation"
94    ReturnRepresentation,
95    /// "return=minimal"
96    ReturnMinimal,
97    /// "handling=strict"
98    HandlingStrict,
99    /// "handling=lenient"
100    HandlingLenient,
101    /// "wait=delta"
102    Wait(u32),
103
104    /// Extension preferences. Always has a value, if none is specified it is
105    /// just "". A preference can also have a list of parameters.
106    Extension(String, String, Vec<(String, String)>)
107}
108
109impl fmt::Display for Preference {
110    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
111        use self::Preference::*;
112        fmt::Display::fmt(match *self {
113            RespondAsync => "respond-async",
114            ReturnRepresentation => "return=representation",
115            ReturnMinimal => "return=minimal",
116            HandlingStrict => "handling=strict",
117            HandlingLenient => "handling=lenient",
118
119            Wait(secs) => return write!(f, "wait={}", secs),
120
121            Extension(ref name, ref value, ref params) => {
122                write!(f, "{}", name)?;
123                if value != "" { write!(f, "={}", value)?; }
124                if !params.is_empty() {
125                    for &(ref name, ref value) in params {
126                        write!(f, "; {}", name)?;
127                        if value != "" { write!(f, "={}", value)?; }
128                    }
129                }
130                return Ok(());
131            }
132        }, f)
133    }
134}
135
136impl FromStr for Preference {
137    type Err = Option<<u32 as FromStr>::Err>;
138    fn from_str(s: &str) -> Result<Preference, Option<<u32 as FromStr>::Err>> {
139        use self::Preference::*;
140        let mut params = s.split(';').map(|p| {
141            let mut param = p.splitn(2, '=');
142            match (param.next(), param.next()) {
143                (Some(name), Some(value)) => (name.trim(), value.trim().trim_matches('"')),
144                (Some(name), None) => (name.trim(), ""),
145                // This can safely be unreachable because the [`splitn`][1]
146                // function (used above) will always have at least one value.
147                //
148                // [1]: http://doc.rust-lang.org/std/primitive.str.html#method.splitn
149                _ => { unreachable!(); }
150            }
151        });
152        match params.nth(0) {
153            Some(param) => {
154                let rest: Vec<(String, String)> = params.map(|(l, r)| (l.to_owned(), r.to_owned())).collect();
155                match param {
156                    ("respond-async", "") => if rest.is_empty() { Ok(RespondAsync) } else { Err(None) },
157                    ("return", "representation") => if rest.is_empty() { Ok(ReturnRepresentation) } else { Err(None) },
158                    ("return", "minimal") => if rest.is_empty() { Ok(ReturnMinimal) } else { Err(None) },
159                    ("handling", "strict") => if rest.is_empty() { Ok(HandlingStrict) } else { Err(None) },
160                    ("handling", "lenient") => if rest.is_empty() { Ok(HandlingLenient) } else { Err(None) },
161                    ("wait", secs) => if rest.is_empty() { secs.parse().map(Wait).map_err(Some) } else { Err(None) },
162                    (left, right) => Ok(Extension(left.to_owned(), right.to_owned(), rest))
163                }
164            },
165            None => Err(None)
166        }
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use header::{Header, Raw};
173    use super::*;
174
175    #[test]
176    fn test_parse_multiple_headers() {
177        let r: Raw = "respond-async, return=representation".into();
178        let prefer = Header::parse_header(&r);
179        assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::RespondAsync,
180                                           Preference::ReturnRepresentation])))
181    }
182
183    #[test]
184    fn test_parse_argument() {
185        let r: Raw = "wait=100, handling=lenient, respond-async".into();
186        let prefer = Header::parse_header(&r);
187        assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::Wait(100),
188                                           Preference::HandlingLenient,
189                                           Preference::RespondAsync])))
190    }
191
192    #[test]
193    fn test_parse_quote_form() {
194        let r: Raw = "wait=\"200\", handling=\"strict\"".into();
195        let prefer = Header::parse_header(&r);
196        assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::Wait(200),
197                                           Preference::HandlingStrict])))
198    }
199
200    #[test]
201    fn test_parse_extension() {
202        let r: Raw = "foo, bar=baz, baz; foo; bar=baz, bux=\"\"; \
203                      foo=\"\", buz=\"some parameter\"".into();
204        let prefer = Header::parse_header(&r);
205        assert_eq!(prefer.ok(), Some(Prefer(vec![
206            Preference::Extension("foo".to_owned(), "".to_owned(), vec![]),
207            Preference::Extension("bar".to_owned(), "baz".to_owned(), vec![]),
208            Preference::Extension("baz".to_owned(), "".to_owned(), vec![("foo".to_owned(), "".to_owned()), ("bar".to_owned(), "baz".to_owned())]),
209            Preference::Extension("bux".to_owned(), "".to_owned(), vec![("foo".to_owned(), "".to_owned())]),
210            Preference::Extension("buz".to_owned(), "some parameter".to_owned(), vec![])])))
211    }
212
213    #[test]
214    fn test_fail_with_args() {
215        let r: Raw = "respond-async; foo=bar".into();
216        let prefer: ::Result<Prefer> = Header::parse_header(&r);
217        assert_eq!(prefer.ok(), None);
218    }
219}
220
221bench_header!(normal,
222    Prefer, { vec![b"respond-async, return=representation".to_vec(), b"wait=100".to_vec()] });