hyper_old_types/header/common/
prefer.rs

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