1use std::fmt;
3use std::str::FromStr;
4use std::convert::AsRef;
5
6#[cfg(feature = "compat")]
7use http;
8
9use error::Error;
10use self::Method::{Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch,
11 Extension};
12
13
14#[derive(Clone, PartialEq, Eq, Hash, Debug)]
23pub enum Method {
24 Options,
26 Get,
28 Post,
30 Put,
32 Delete,
34 Head,
36 Trace,
38 Connect,
40 Patch,
42 Extension(String)
44}
45
46impl AsRef<str> for Method {
47 fn as_ref(&self) -> &str {
48 match *self {
49 Options => "OPTIONS",
50 Get => "GET",
51 Post => "POST",
52 Put => "PUT",
53 Delete => "DELETE",
54 Head => "HEAD",
55 Trace => "TRACE",
56 Connect => "CONNECT",
57 Patch => "PATCH",
58 Extension(ref s) => s.as_ref()
59 }
60 }
61}
62
63impl Method {
64 pub fn safe(&self) -> bool {
70 match *self {
71 Get | Head | Options | Trace => true,
72 _ => false
73 }
74 }
75
76 pub fn idempotent(&self) -> bool {
82 if self.safe() {
83 true
84 } else {
85 match *self {
86 Put | Delete => true,
87 _ => false
88 }
89 }
90 }
91}
92
93macro_rules! from_str {
94 ($s:ident, { $($n:pat => { $($text:pat => $var:ident,)* },)* }) => ({
95 let s = $s;
96 match s.len() {
97 $(
98 $n => match s {
99 $(
100 $text => return Ok($var),
101 )*
102 _ => {},
103 },
104 )*
105 0 => return Err(::Error::Method),
106 _ => {},
107 }
108 Ok(Extension(s.to_owned()))
109 })
110}
111
112impl FromStr for Method {
113 type Err = Error;
114 fn from_str(s: &str) -> Result<Method, Error> {
115 from_str!(s, {
116 3 => {
117 "GET" => Get,
118 "PUT" => Put,
119 },
120 4 => {
121 "HEAD" => Head,
122 "POST" => Post,
123 },
124 5 => {
125 "PATCH" => Patch,
126 "TRACE" => Trace,
127 },
128 6 => {
129 "DELETE" => Delete,
130 },
131 7 => {
132 "OPTIONS" => Options,
133 "CONNECT" => Connect,
134 },
135 })
136 }
137}
138
139impl fmt::Display for Method {
140 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
141 fmt.write_str(match *self {
142 Options => "OPTIONS",
143 Get => "GET",
144 Post => "POST",
145 Put => "PUT",
146 Delete => "DELETE",
147 Head => "HEAD",
148 Trace => "TRACE",
149 Connect => "CONNECT",
150 Patch => "PATCH",
151 Extension(ref s) => s.as_ref()
152 })
153 }
154}
155
156impl Default for Method {
157 fn default() -> Method {
158 Method::Get
159 }
160}
161
162#[cfg(feature = "compat")]
163impl From<http::Method> for Method {
164 fn from(method: http::Method) -> Method {
165 match method {
166 http::Method::GET =>
167 Method::Get,
168 http::Method::POST =>
169 Method::Post,
170 http::Method::PUT =>
171 Method::Put,
172 http::Method::DELETE =>
173 Method::Delete,
174 http::Method::HEAD =>
175 Method::Head,
176 http::Method::OPTIONS =>
177 Method::Options,
178 http::Method::CONNECT =>
179 Method::Connect,
180 http::Method::PATCH =>
181 Method::Patch,
182 http::Method::TRACE =>
183 Method::Trace,
184 _ => {
185 method.as_ref().parse()
186 .expect("attempted to convert invalid method")
187 }
188 }
189 }
190}
191
192#[cfg(feature = "compat")]
193impl From<Method> for http::Method {
194 fn from(method: Method) -> http::Method {
195 use http::HttpTryFrom;
196
197 match method {
198 Method::Get =>
199 http::Method::GET,
200 Method::Post =>
201 http::Method::POST,
202 Method::Put =>
203 http::Method::PUT,
204 Method::Delete =>
205 http::Method::DELETE,
206 Method::Head =>
207 http::Method::HEAD,
208 Method::Options =>
209 http::Method::OPTIONS,
210 Method::Connect =>
211 http::Method::CONNECT,
212 Method::Patch =>
213 http::Method::PATCH,
214 Method::Trace =>
215 http::Method::TRACE,
216 Method::Extension(s) => {
217 HttpTryFrom::try_from(s.as_str())
218 .expect("attempted to convert invalid method")
219 }
220 }
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use std::collections::HashMap;
227 use std::str::FromStr;
228 use error::Error;
229 use super::Method;
230 use super::Method::{Get, Post, Put, Extension};
231
232 #[test]
233 fn test_safe() {
234 assert_eq!(true, Get.safe());
235 assert_eq!(false, Post.safe());
236 }
237
238 #[test]
239 fn test_idempotent() {
240 assert_eq!(true, Get.idempotent());
241 assert_eq!(true, Put.idempotent());
242 assert_eq!(false, Post.idempotent());
243 }
244
245 #[test]
246 fn test_from_str() {
247 assert_eq!(Get, FromStr::from_str("GET").unwrap());
248 assert_eq!(Extension("MOVE".to_owned()),
249 FromStr::from_str("MOVE").unwrap());
250 let x: Result<Method, _> = FromStr::from_str("");
251 if let Err(Error::Method) = x {
252 } else {
253 panic!("An empty method is invalid!")
254 }
255 }
256
257 #[test]
258 fn test_fmt() {
259 assert_eq!("GET".to_owned(), format!("{}", Get));
260 assert_eq!("MOVE".to_owned(),
261 format!("{}", Extension("MOVE".to_owned())));
262 }
263
264 #[test]
265 fn test_hashable() {
266 let mut counter: HashMap<Method,usize> = HashMap::new();
267 counter.insert(Get, 1);
268 assert_eq!(Some(&1), counter.get(&Get));
269 }
270
271 #[test]
272 fn test_as_str() {
273 assert_eq!(Get.as_ref(), "GET");
274 assert_eq!(Post.as_ref(), "POST");
275 assert_eq!(Put.as_ref(), "PUT");
276 assert_eq!(Extension("MOVE".to_owned()).as_ref(), "MOVE");
277 }
278
279 #[test]
280 #[cfg(feature = "compat")]
281 fn test_compat() {
282 use http::{self, HttpTryFrom};
283
284 let methods = vec![
285 "GET",
286 "POST",
287 "PUT",
288 "MOVE"
289 ];
290 for method in methods {
291 let orig_hyper_method = Method::from_str(method).unwrap();
292 let orig_http_method = http::Method::try_from(method).unwrap();
293 let conv_hyper_method: Method = orig_http_method.clone().into();
294 let conv_http_method: http::Method = orig_hyper_method.clone().into();
295 assert_eq!(orig_hyper_method, conv_hyper_method);
296 assert_eq!(orig_http_method, conv_http_method);
297 }
298 }
299}