Skip to main content

kynos_openapi/model/paths/
method.rs

1//! The HTTP methods a Path Item has a dedicated field for.
2
3use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7/// An HTTP method that has a dedicated Path Item field.
8/// `#[non_exhaustive]` because OpenAPI 3.2 adds to this and the addition is
9/// `#[cfg]`-gated. Cargo unifies features across a dependency graph, so any
10/// crate enabling `openapi32` enables it for every crate in the build -- and
11/// without this attribute that would turn a downstream exhaustive `match` into
12/// a compile error, which is not what "purely additive" is supposed to mean.
13#[non_exhaustive]
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum Method {
17    /// `GET`.
18    Get,
19    /// `PUT`.
20    Put,
21    /// `POST`.
22    Post,
23    /// `DELETE`.
24    Delete,
25    /// `OPTIONS`.
26    Options,
27    /// `HEAD`.
28    Head,
29    /// `PATCH`.
30    Patch,
31    /// `TRACE`.
32    Trace,
33    /// `QUERY`, as defined by the HTTP QUERY method draft.
34    ///
35    /// Introduced in OpenAPI 3.2.
36    #[cfg(feature = "openapi32")]
37    Query,
38}
39
40impl Method {
41    /// Every method with a dedicated Path Item field.
42    #[must_use]
43    pub fn all() -> &'static [Self] {
44        &[
45            Self::Get,
46            Self::Put,
47            Self::Post,
48            Self::Delete,
49            Self::Options,
50            Self::Head,
51            Self::Patch,
52            Self::Trace,
53            #[cfg(feature = "openapi32")]
54            Self::Query,
55        ]
56    }
57
58    /// The method name as it appears on the wire.
59    #[must_use]
60    pub fn as_wire_str(self) -> &'static str {
61        match self {
62            Self::Get => "GET",
63            Self::Put => "PUT",
64            Self::Post => "POST",
65            Self::Delete => "DELETE",
66            Self::Options => "OPTIONS",
67            Self::Head => "HEAD",
68            Self::Patch => "PATCH",
69            Self::Trace => "TRACE",
70            #[cfg(feature = "openapi32")]
71            Self::Query => "QUERY",
72        }
73    }
74
75    /// The method with this wire spelling, if it has a Path Item field.
76    ///
77    /// Case-sensitive: HTTP method tokens are, and a description that spelled
78    /// one differently would not be describing the same request. Returns
79    /// `None` for a method OpenAPI has no field for, which is a different
80    /// answer from "not a method" — under `openapi32` those reach a Path Item
81    /// through `additionalOperations` instead.
82    #[must_use]
83    pub fn from_wire_str(name: &str) -> Option<Self> {
84        Some(match name {
85            "GET" => Self::Get,
86            "PUT" => Self::Put,
87            "POST" => Self::Post,
88            "DELETE" => Self::Delete,
89            "OPTIONS" => Self::Options,
90            "HEAD" => Self::Head,
91            "PATCH" => Self::Patch,
92            "TRACE" => Self::Trace,
93            #[cfg(feature = "openapi32")]
94            "QUERY" => Self::Query,
95            _ => return None,
96        })
97    }
98}
99
100impl fmt::Display for Method {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        f.write_str(self.as_wire_str())
103    }
104}
105
106/// A described method has a wire spelling, so this direction never fails.
107///
108/// The pair exists because `kynos` exposes both this type and `http::Method`
109/// in adjacent APIs — an `Observer` receives a request's method and a route's
110/// — and could not write the conversion itself: both types are foreign to it,
111/// so the orphan rule puts these here.
112#[cfg(feature = "http")]
113impl From<Method> for http::Method {
114    fn from(method: Method) -> Self {
115        // `as_wire_str` returns a token from a closed set, all of which are
116        // valid method tokens, so this cannot fail.
117        Self::from_bytes(method.as_wire_str().as_bytes())
118            .expect("every described method is a valid HTTP method token")
119    }
120}
121
122/// Not every HTTP method is one a Path Item has a field for.
123///
124/// Fallible on purpose, and in two ways worth telling apart in a message
125/// rather than in the type: an extension method has no variant at all, and
126/// `QUERY` has one only under `openapi32`. Both reach a Path Item through
127/// `additionalOperations`, which is 3.2's answer and not a conversion.
128#[cfg(feature = "http")]
129impl TryFrom<&http::Method> for Method {
130    type Error = UnnamedMethod;
131
132    fn try_from(method: &http::Method) -> Result<Self, Self::Error> {
133        Method::from_wire_str(method.as_str()).ok_or_else(|| UnnamedMethod {
134            method: method.as_str().to_owned(),
135        })
136    }
137}
138
139#[cfg(feature = "http")]
140impl TryFrom<http::Method> for Method {
141    type Error = UnnamedMethod;
142
143    fn try_from(method: http::Method) -> Result<Self, Self::Error> {
144        Self::try_from(&method)
145    }
146}
147
148/// An HTTP method no Path Item field names.
149#[cfg(feature = "http")]
150#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
151#[error(
152    "`{method}` is not a method a Path Item has a field for; 3.2 describes one through \
153     `additionalOperations`"
154)]
155pub struct UnnamedMethod {
156    /// The method's wire spelling.
157    pub method: String,
158}