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