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}