hyperx/
method.rs

1//! The HTTP request method
2use std::fmt;
3use std::str::FromStr;
4use std::convert::{AsRef, TryFrom};
5
6use http;
7
8use error::Error;
9use self::Method::{Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch,
10                   Extension};
11
12/// The Request Method (VERB)
13///
14/// Currently includes 8 variants representing the 8 methods defined in
15/// [RFC 7230](https://tools.ietf.org/html/rfc7231#section-4.1), plus PATCH,
16/// and an Extension variant for all extensions.
17///
18/// It may make sense to grow this to include all variants currently
19/// registered with IANA, if they are at all common to use.
20#[derive(Clone, PartialEq, Eq, Hash, Debug)]
21pub enum Method {
22    /// OPTIONS
23    Options,
24    /// GET
25    Get,
26    /// POST
27    Post,
28    /// PUT
29    Put,
30    /// DELETE
31    Delete,
32    /// HEAD
33    Head,
34    /// TRACE
35    Trace,
36    /// CONNECT
37    Connect,
38    /// PATCH
39    Patch,
40    /// Method extensions. An example would be `let m = Extension("FOO".to_string())`.
41    Extension(String)
42}
43
44impl AsRef<str> for Method {
45    fn as_ref(&self) -> &str {
46        match *self {
47            Options => "OPTIONS",
48            Get => "GET",
49            Post => "POST",
50            Put => "PUT",
51            Delete => "DELETE",
52            Head => "HEAD",
53            Trace => "TRACE",
54            Connect => "CONNECT",
55            Patch => "PATCH",
56            Extension(ref s) => s.as_ref()
57        }
58    }
59}
60
61impl Method {
62    /// Whether a method is considered "safe", meaning the request is
63    /// essentially read-only.
64    ///
65    /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.1)
66    /// for more words.
67    pub fn safe(&self) -> bool {
68        match *self {
69            Get | Head | Options | Trace => true,
70            _ => false
71        }
72    }
73
74    /// Whether a method is considered "idempotent", meaning the request has
75    /// the same result if executed multiple times.
76    ///
77    /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.2) for
78    /// more words.
79    pub fn idempotent(&self) -> bool {
80        if self.safe() {
81            true
82        } else {
83            match *self {
84                Put | Delete => true,
85                _ => false
86            }
87        }
88    }
89}
90
91macro_rules! from_str {
92    ($s:ident, { $($n:pat => { $($text:pat => $var:ident,)* },)* }) => ({
93        let s = $s;
94        match s.len() {
95            $(
96            $n => match s {
97                $(
98                $text => return Ok($var),
99                )*
100                _ => {},
101            },
102            )*
103            0 => return Err(::Error::Method),
104            _ => {},
105        }
106        Ok(Extension(s.to_owned()))
107    })
108}
109
110impl FromStr for Method {
111    type Err = Error;
112    fn from_str(s: &str) -> Result<Method, Error> {
113        from_str!(s, {
114            3 => {
115                "GET" => Get,
116                "PUT" => Put,
117            },
118            4 => {
119                "HEAD" => Head,
120                "POST" => Post,
121            },
122            5 => {
123                "PATCH" => Patch,
124                "TRACE" => Trace,
125            },
126            6 => {
127                "DELETE" => Delete,
128            },
129            7 => {
130                "OPTIONS" => Options,
131                "CONNECT" => Connect,
132            },
133        })
134    }
135}
136
137impl fmt::Display for Method {
138    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
139        fmt.write_str(match *self {
140            Options => "OPTIONS",
141            Get => "GET",
142            Post => "POST",
143            Put => "PUT",
144            Delete => "DELETE",
145            Head => "HEAD",
146            Trace => "TRACE",
147            Connect => "CONNECT",
148            Patch => "PATCH",
149            Extension(ref s) => s.as_ref()
150        })
151    }
152}
153
154impl Default for Method {
155    fn default() -> Method {
156        Method::Get
157    }
158}
159
160impl From<http::Method> for Method {
161    fn from(method: http::Method) -> Method {
162        match method {
163            http::Method::GET =>
164                Method::Get,
165            http::Method::POST =>
166                Method::Post,
167            http::Method::PUT =>
168                Method::Put,
169            http::Method::DELETE =>
170                Method::Delete,
171            http::Method::HEAD =>
172                Method::Head,
173            http::Method::OPTIONS =>
174                Method::Options,
175            http::Method::CONNECT =>
176                Method::Connect,
177            http::Method::PATCH =>
178                Method::Patch,
179            http::Method::TRACE =>
180                Method::Trace,
181            _ => {
182                method.as_ref().parse()
183                    .expect("attempted to convert invalid method")
184            }
185        }
186    }
187}
188
189impl From<Method> for http::Method {
190    fn from(method: Method) -> http::Method {
191
192        match method {
193            Method::Get =>
194                http::Method::GET,
195            Method::Post =>
196                http::Method::POST,
197            Method::Put =>
198                http::Method::PUT,
199            Method::Delete =>
200                http::Method::DELETE,
201            Method::Head =>
202                http::Method::HEAD,
203            Method::Options =>
204                http::Method::OPTIONS,
205            Method::Connect =>
206                http::Method::CONNECT,
207            Method::Patch =>
208                http::Method::PATCH,
209            Method::Trace =>
210                http::Method::TRACE,
211            Method::Extension(s) => {
212                http::Method::try_from(s.as_str())
213                    .expect("attempted to convert invalid method")
214            }
215        }
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use std::collections::HashMap;
222    use std::convert::TryFrom;
223    use std::str::FromStr;
224    use error::Error;
225    use super::Method;
226    use super::Method::{Get, Post, Put, Extension};
227
228    #[test]
229    fn test_safe() {
230        assert_eq!(true, Get.safe());
231        assert_eq!(false, Post.safe());
232    }
233
234    #[test]
235    fn test_idempotent() {
236        assert_eq!(true, Get.idempotent());
237        assert_eq!(true, Put.idempotent());
238        assert_eq!(false, Post.idempotent());
239    }
240
241    #[test]
242    fn test_from_str() {
243        assert_eq!(Get, FromStr::from_str("GET").unwrap());
244        assert_eq!(Extension("MOVE".to_owned()),
245                   FromStr::from_str("MOVE").unwrap());
246        let x: Result<Method, _> = FromStr::from_str("");
247        if let Err(Error::Method) = x {
248        } else {
249            panic!("An empty method is invalid!")
250        }
251    }
252
253    #[test]
254    fn test_fmt() {
255        assert_eq!("GET".to_owned(), format!("{}", Get));
256        assert_eq!("MOVE".to_owned(),
257                   format!("{}", Extension("MOVE".to_owned())));
258    }
259
260    #[test]
261    fn test_hashable() {
262        let mut counter: HashMap<Method,usize> = HashMap::new();
263        counter.insert(Get, 1);
264        assert_eq!(Some(&1), counter.get(&Get));
265    }
266
267    #[test]
268    fn test_as_str() {
269        assert_eq!(Get.as_ref(), "GET");
270        assert_eq!(Post.as_ref(), "POST");
271        assert_eq!(Put.as_ref(), "PUT");
272        assert_eq!(Extension("MOVE".to_owned()).as_ref(), "MOVE");
273    }
274
275    #[test]
276    fn test_compat() {
277        let methods = vec![
278            "GET",
279            "POST",
280            "PUT",
281            "MOVE"
282        ];
283        for method in methods {
284            let orig_hyper_method = Method::from_str(method).unwrap();
285            let orig_http_method = http::Method::try_from(method).unwrap();
286            let conv_hyper_method: Method = orig_http_method.clone().into();
287            let conv_http_method: http::Method = orig_hyper_method.clone().into();
288            assert_eq!(orig_hyper_method, conv_hyper_method);
289            assert_eq!(orig_http_method, conv_http_method);
290        }
291    }
292}