hyper_sync/header/common/
prefer.rs

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