// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// A request method (RFC 9110 §9.1). The eight of RFC 9110 and PATCH (RFC 5789)
/// have their own constructors; any other token is `Other`.
///
/// Method names are case-sensitive, so `get` is an extension method and not
/// `Get`. Build a method from its name with `Method::of`, which picks the
/// constructor: `Other("GET")` written by hand is not equal to `Get`.
pub(all) enum Method {
Get
Head
Post
Put
Delete
Connect
Options
Trace
Patch
Other(String)
} derive(Eq, Debug)
///|
pub extend Method with Eq::{equal, not_equal}
///|
pub extend Method with Debug::{to_repr}
///|
/// Why a name is not a method.
pub(all) suberror Refused {
/// Empty, or holding a character outside `tchar` (RFC 9110 §5.6.2).
Token(String)
} derive(Eq, Debug)
///|
pub extend Refused with Eq::{equal, not_equal}
///|
pub extend Refused with Debug::{to_repr}
///|
fn tchar(c : Char) -> Bool {
match c {
'a'..<='z' | 'A'..<='Z' | '0'..<='9' => true
'!'
| '#'
| '$'
| '%'
| '&'
| '\''
| '*'
| '+'
| '-'
| '.'
| '^'
| '_'
| '`'
| '|'
| '~' => true
_ => false
}
}
///|
/// The method named `name`.
pub fn Method::of(name : StringView) -> Method raise Refused {
match name {
"GET" => Get
"HEAD" => Head
"POST" => Post
"PUT" => Put
"DELETE" => Delete
"CONNECT" => Connect
"OPTIONS" => Options
"TRACE" => Trace
"PATCH" => Patch
_ => {
if name.is_empty() || !name.iter().all(tchar) {
raise Token(name.to_owned())
}
Other(name.to_owned())
}
}
}
///|
/// The name sent on the request line or in `:method`.
pub fn Method::name(self : Method) -> String {
match self {
Get => "GET"
Head => "HEAD"
Post => "POST"
Put => "PUT"
Delete => "DELETE"
Connect => "CONNECT"
Options => "OPTIONS"
Trace => "TRACE"
Patch => "PATCH"
Other(name) => name
}
}
///|
/// Whether the method is read-only (RFC 9110 §9.2.1), so a crawler, a prefetch
/// or a cache may send it without the user asking. An extension method is not
/// assumed safe.
pub fn Method::is_safe(self : Method) -> Bool {
self is (Get | Head | Options | Trace)
}
///|
/// Whether sending the request twice has the effect of sending it once (RFC 9110
/// §9.2.2), so a client may retry it after a connection fails before the
/// response arrives. An extension method is not assumed idempotent.
pub fn Method::is_idempotent(self : Method) -> Bool {
self.is_safe() || self is (Put | Delete)
}