cogo_http/header/common/
prefer.rs

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