rocket_http_community/method.rs
1use std::fmt;
2use std::str::FromStr;
3
4self::define_methods! {
5 // enum variant method name body safe idempotent [RFC,section]
6 Get "GET" maybe yes yes [9110,9.3.1]
7 Head "HEAD" maybe yes yes [9110,9.3.2]
8 Post "POST" yes no no [9110,9.3.3]
9 Put "PUT" yes no yes [9110,9.3.4]
10 Delete "DELETE" maybe no yes [9110,9.3.5]
11 Connect "CONNECT" maybe no no [9110,9.3.6]
12 Options "OPTIONS" maybe yes yes [9110,9.3.7]
13 Trace "TRACE" no yes yes [9110,9.3.8]
14 Patch "PATCH" yes no no [5789,2]
15
16 Acl "ACL" yes no yes [3744,8.1]
17 BaselineControl "BASELINE-CONTROL" yes no yes [3253,12.6]
18 Bind "BIND" yes no yes [5842,4]
19 CheckIn "CHECKIN" yes no yes [3253,4.4]
20 CheckOut "CHECKOUT" maybe no yes [3253,4.3]
21 Copy "COPY" maybe no yes [4918,9.8]
22 Label "LABEL" yes no yes [3253,8.2]
23 Link "LINK" maybe no yes [2068,19.6.1.2]
24 Lock "LOCK" yes no no [4918,9.10]
25 Merge "MERGE" yes no yes [3253,11.2]
26 MkActivity "MKACTIVITY" yes no yes [3253,13.5]
27 MkCalendar "MKCALENDAR" yes no yes [4791,5.3.1][8144,2.3]
28 MkCol "MKCOL" yes no yes [4918,9.3][5689,3][8144,2.3]
29 MkRedirectRef "MKREDIRECTREF" yes no yes [4437,6]
30 MkWorkspace "MKWORKSPACE" yes no yes [3253,6.3]
31 Move "MOVE" maybe no yes [4918,9.9]
32 OrderPatch "ORDERPATCH" yes no yes [3648,7]
33 PropFind "PROPFIND" yes yes yes [4918,9.1][8144,2.1]
34 PropPatch "PROPPATCH" yes no yes [4918,9.2][8144,2.2]
35 Rebind "REBIND" yes no yes [5842,6]
36 Report "REPORT" yes yes yes [3253,3.6][8144,2.1]
37 Search "SEARCH" yes yes yes [5323,2]
38 Unbind "UNBIND" yes no yes [5842,5]
39 Uncheckout "UNCHECKOUT" maybe no yes [3253,4.5]
40 Unlink "UNLINK" maybe no yes [2068,19.6.1.3]
41 Unlock "UNLOCK" maybe no yes [4918,9.11]
42 Update "UPDATE" yes no yes [3253,7.1]
43 UpdateRedirectRef "UPDATEREDIRECTREF" yes no yes [4437,7]
44 VersionControl "VERSION-CONTROL" yes no yes [3253,3.5]
45}
46
47#[doc(hidden)]
48#[macro_export]
49macro_rules! define_methods {
50 ($($V:ident $name:tt $body:ident $safe:ident $idem:ident $([$n:expr,$s:expr])+)*) => {
51 /// An HTTP method.
52 ///
53 /// Each variant corresponds to a method in the [HTTP Method Registry].
54 /// The string form of the method can be obtained via
55 /// [`Method::as_str()`] and parsed via the `FromStr` or
56 /// `TryFrom<&[u8]>` implementations. The parse implementations parse
57 /// both the case-sensitive string form as well as a lowercase version
58 /// of the string, but _not_ mixed-case versions.
59 ///
60 /// [HTTP Method Registry]: https://www.iana.org/assignments/http-methods/http-methods.xhtml
61 ///
62 /// # (De)Serialization
63 ///
64 /// `Method` is both `Serialize` and `Deserialize`.
65 ///
66 /// - `Method` _serializes_ as the specification-defined string form
67 /// of the method, equivalent to the value returned from
68 /// [`Method::as_str()`].
69 /// - `Method` _deserializes_ from method's string form _or_ from a
70 /// lowercased string, equivalent to the `FromStr` implementation.
71 ///
72 /// For example, [`Method::Get`] serializes to `"GET"` and deserializes
73 /// from either `"GET"` or `"get"` but not `"GeT"`.
74 ///
75 /// ```rust
76 /// # #[cfg(feature = "serde")] mod serde_impl {
77 /// use rocket::http::Method;
78 /// use rocket::serde::{Serialize, Deserialize};
79 ///
80 /// #[derive(Deserialize, Serialize)]
81 /// #[serde(crate = "rocket::serde")]
82 /// struct Foo {
83 /// method: Method,
84 /// }
85 /// # }
86 /// ```
87 #[non_exhaustive]
88 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
89 pub enum Method {$(
90 #[doc = concat!("The `", $name, "` method.")]
91 #[doc = concat!("Defined in" $(,
92 " [RFC", stringify!($n), " §", stringify!($s), "]",
93 "(https://www.rfc-editor.org/rfc/rfc", stringify!($n), ".html",
94 "#section-", stringify!($s), ")",
95 )","+ ".")]
96 ///
97 #[doc = concat!("* safe: `", stringify!($safe), "`")]
98 #[doc = concat!("* idempotent: `", stringify!($idem), "`")]
99 #[doc = concat!("* request body: `", stringify!($body), "`")]
100 $V
101 ),*}
102
103 macro_rules! lowercase {
104 ($str:literal) => {{
105 const BYTES: [u8; $str.len()] = {
106 let mut i = 0;
107 let _: &str = $str;
108 let mut result = [0; $str.len()];
109 while i < $str.len() {
110 result[i] = $str.as_bytes()[i].to_ascii_lowercase();
111 i += 1;
112 }
113
114 result
115 };
116
117 unsafe { std::str::from_utf8_unchecked(&BYTES) }
118 }};
119 }
120
121 #[allow(non_upper_case_globals)]
122 impl Method {
123 /// A slice containing every defined method string.
124 #[doc(hidden)]
125 pub const ALL: &'static [&'static str] = &[$($name),*];
126
127 /// A slice containing every defined method variant.
128 #[doc(hidden)]
129 pub const ALL_VARIANTS: &'static [Method] = &[$(Self::$V),*];
130
131 /// Whether the method is considered "safe".
132 ///
133 /// From [RFC9110 §9.2.1](https://www.rfc-editor.org/rfc/rfc9110#section-9.2.1):
134 ///
135 /// > Request methods are considered "safe" if their defined
136 /// > semantics are essentially read-only; i.e., the client does not
137 /// > request, and does not expect, any state change on the origin server
138 /// > as a result of applying a safe method to a target resource.
139 /// > Likewise, reasonable use of a safe method is not expected to cause
140 /// > any harm, loss of property, or unusual burden on the origin server.
141 /// > Of the request methods defined by this specification, the GET,
142 /// > HEAD, OPTIONS, and TRACE methods are defined to be safe.
143 ///
144 /// # Example
145 ///
146 /// ```rust
147 /// use rocket::http::Method;
148 ///
149 /// assert!(Method::Get.is_safe());
150 /// assert!(Method::Head.is_safe());
151 ///
152 /// assert!(!Method::Put.is_safe());
153 /// assert!(!Method::Post.is_safe());
154 /// ```
155 pub const fn is_safe(&self) -> bool {
156 const yes: bool = true;
157 const no: bool = false;
158
159 match self {
160 $(Self::$V => $safe),*
161 }
162 }
163
164 /// Whether the method is considered "idempotent".
165 ///
166 /// From [RFC9110 §9.2.2](https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2):
167 ///
168 /// > A request method is considered "idempotent" if the intended
169 /// > effect on the server of multiple identical requests with that method
170 /// > is the same as the effect for a single such request. Of the request
171 /// > methods defined by this specification, PUT, DELETE, and safe request
172 /// > methods are idempotent.
173 ///
174 /// # Example
175 ///
176 /// ```rust
177 /// use rocket::http::Method;
178 ///
179 /// assert!(Method::Get.is_idempotent());
180 /// assert!(Method::Head.is_idempotent());
181 /// assert!(Method::Put.is_idempotent());
182 ///
183 /// assert!(!Method::Post.is_idempotent());
184 /// assert!(!Method::Patch.is_idempotent());
185 /// ```
186 pub const fn is_idempotent(&self) -> bool {
187 const yes: bool = true;
188 const no: bool = false;
189
190 match self {
191 $(Self::$V => $idem),*
192 }
193 }
194
195 /// Whether requests with this method are allowed to have a body.
196 ///
197 /// Returns:
198 /// * `Some(true)` if a request body is _always_ allowed.
199 /// * `Some(false)` if a request body is **never** allowed.
200 /// * `None` if a request body is discouraged or has no defined semantics.
201 ///
202 /// # Example
203 ///
204 /// ```rust
205 /// use rocket::http::Method;
206 ///
207 /// assert_eq!(Method::Post.allows_request_body(), Some(true));
208 /// assert_eq!(Method::Put.allows_request_body(), Some(true));
209 ///
210 /// assert_eq!(Method::Trace.allows_request_body(), Some(false));
211 ///
212 /// assert_eq!(Method::Get.allows_request_body(), None);
213 /// assert_eq!(Method::Head.allows_request_body(), None);
214 /// ```
215 pub const fn allows_request_body(self) -> Option<bool> {
216 const yes: Option<bool> = Some(true);
217 const no: Option<bool> = Some(false);
218 const maybe: Option<bool> = None;
219
220 match self {
221 $(Self::$V => $body),*
222 }
223 }
224
225 /// Returns the method's string representation.
226 ///
227 /// # Example
228 ///
229 /// ```rust
230 /// use rocket::http::Method;
231 ///
232 /// assert_eq!(Method::Get.as_str(), "GET");
233 /// assert_eq!(Method::Put.as_str(), "PUT");
234 /// assert_eq!(Method::BaselineControl.as_str(), "BASELINE-CONTROL");
235 /// ```
236 pub const fn as_str(self) -> &'static str {
237 match self {
238 $(Self::$V => $name),*
239 }
240 }
241
242 /// Returns a static reference to the method.
243 ///
244 /// # Example
245 ///
246 /// ```rust
247 /// use rocket::http::Method;
248 ///
249 /// assert_eq!(Method::Get.as_ref(), &Method::Get);
250 /// ```
251 pub const fn as_ref(self) -> &'static Method {
252 match self {
253 $(Self::$V => &Self::$V),*
254 }
255 }
256
257 #[doc(hidden)]
258 pub const fn variant_str(self) -> &'static str {
259 match self {
260 $(Self::$V => stringify!($V)),*
261 }
262 }
263 }
264
265 #[cfg(test)]
266 mod tests {
267 use super::*;
268
269 #[test]
270 #[allow(non_upper_case_globals)]
271 fn test_properties_and_parsing() {
272 const yes: bool = true;
273 const no: bool = false;
274
275 $(
276 assert_eq!(Method::$V.is_idempotent(), $idem);
277 assert_eq!(Method::$V.is_safe(), $safe);
278 assert_eq!(Method::from_str($name).unwrap(), Method::$V);
279 assert_eq!(Method::from_str(lowercase!($name)).unwrap(), Method::$V);
280 assert_eq!(Method::$V.as_ref(), Method::$V);
281 )*
282 }
283 }
284
285 impl TryFrom<&[u8]> for Method {
286 type Error = ParseMethodError;
287
288 #[inline]
289 #[allow(non_upper_case_globals)]
290 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
291 mod upper { $(pub const $V: &[u8] = $name.as_bytes();)* }
292 mod lower { $(pub const $V: &[u8] = lowercase!($name).as_bytes();)* }
293
294 match value {
295 $(upper::$V | lower::$V => Ok(Self::$V),)*
296 _ => Err(ParseMethodError)
297 }
298 }
299 }
300 };
301}
302
303impl Method {
304 /// Deprecated. Returns `self.allows_request_body() == Some(true)`.
305 ///
306 /// Use [`Method::allows_request_body()`] instead.
307 #[deprecated(since = "0.6.0", note = "use Self::allows_request_body()")]
308 pub const fn supports_payload(self) -> bool {
309 match self.allows_request_body() {
310 Some(v) => v,
311 None => false,
312 }
313 }
314}
315
316use define_methods;
317
318#[derive(Debug, PartialEq, Eq)]
319pub struct ParseMethodError;
320
321impl std::error::Error for ParseMethodError {}
322
323impl From<std::convert::Infallible> for ParseMethodError {
324 fn from(infallible: std::convert::Infallible) -> Self {
325 match infallible {}
326 }
327}
328
329impl fmt::Display for ParseMethodError {
330 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331 f.write_str("invalid HTTP method")
332 }
333}
334
335impl FromStr for Method {
336 type Err = ParseMethodError;
337
338 #[inline(always)]
339 fn from_str(s: &str) -> Result<Self, Self::Err> {
340 Self::try_from(s.as_bytes())
341 }
342}
343
344impl TryFrom<&str> for Method {
345 type Error = ParseMethodError;
346
347 fn try_from(s: &str) -> Result<Self, Self::Error> {
348 Self::try_from(s.as_bytes())
349 }
350}
351
352impl AsRef<str> for Method {
353 fn as_ref(&self) -> &str {
354 self.as_str()
355 }
356}
357
358impl fmt::Display for Method {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 self.as_str().fmt(f)
361 }
362}
363
364impl PartialEq<&Method> for Method {
365 fn eq(&self, other: &&Method) -> bool {
366 self == *other
367 }
368}
369
370impl PartialEq<Method> for &Method {
371 fn eq(&self, other: &Method) -> bool {
372 *self == other
373 }
374}
375
376#[cfg(feature = "serde")]
377mod serde_impl {
378 use super::*;
379
380 use serde::de::{Deserialize, Deserializer, Error, Unexpected, Visitor};
381 use serde::ser::{Serialize, Serializer};
382
383 impl Serialize for Method {
384 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
385 serializer.serialize_str(self.as_str())
386 }
387 }
388
389 struct DeVisitor;
390
391 impl<'de> Visitor<'de> for DeVisitor {
392 type Value = Method;
393
394 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
395 write!(formatter, "valid HTTP method string")
396 }
397
398 fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
399 Method::from_str(v).map_err(|_| E::invalid_value(Unexpected::Str(v), &self))
400 }
401 }
402
403 impl<'de> Deserialize<'de> for Method {
404 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
405 deserializer.deserialize_str(DeVisitor)
406 }
407 }
408}