poem_openapi/
base.rs

1use std::{
2    collections::HashMap,
3    fmt::{self, Debug, Display},
4    future::Future,
5    ops::Deref,
6};
7
8use futures_util::FutureExt;
9use poem::{Error, FromRequest, Request, RequestBody, Result, endpoint::BoxEndpoint, http::Method};
10use serde::Serialize;
11
12use crate::{
13    payload::Payload,
14    registry::{
15        MetaApi, MetaMediaType, MetaOAuthScope, MetaParamIn, MetaRequest, MetaResponse,
16        MetaResponses, MetaSchemaRef, MetaWebhook, Registry,
17    },
18};
19
20/// The style of the passed parameter. See https://swagger.io/docs/specification/v3_0/serialization/ for details
21#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize)]
22#[serde(rename_all = "camelCase")]
23pub enum ParameterStyle {
24    /// Dot-prefixed values, also known as label expansion.
25    Label,
26    /// Semicolon-prefixed values, also known as path-style expansion.
27    Matrix,
28    /// Ampersand-separated values, also known as form-style query expansion.
29    Form,
30    /// Comma-separated values
31    Simple,
32    /// Space-separated array values. Has effect only for non-exploded arrays.
33    SpaceDelimited,
34    /// Pipeline-separated array values. Has effect only for non-exploded
35    /// arrays.
36    PipeDelimited,
37    /// Bracket-nested objects, e.g.
38    /// `paramName[prop1]=value1&paramName[prop2]=value2`
39    DeepObject,
40}
41
42/// API extractor types.
43#[derive(Debug, Copy, Clone, Eq, PartialEq)]
44pub enum ApiExtractorType {
45    /// A request object.
46    RequestObject,
47
48    /// A request parameter.
49    Parameter,
50
51    /// A security scheme.
52    SecurityScheme,
53
54    /// A poem extractor.
55    PoemExtractor,
56}
57
58#[doc(hidden)]
59#[derive(Clone)]
60pub struct UrlQuery(pub Vec<(String, String)>);
61
62impl Deref for UrlQuery {
63    type Target = Vec<(String, String)>;
64
65    fn deref(&self) -> &Self::Target {
66        &self.0
67    }
68}
69
70impl UrlQuery {
71    /// Returns all values with the specified name.
72    pub fn get_all<'a, 'b: 'a>(&'b self, name: &'a str) -> impl Iterator<Item = &'b String> + 'a {
73        self.0
74            .iter()
75            .filter(move |(n, _)| n == name)
76            .map(|(_, value)| value)
77    }
78
79    /// Returns all values uses a closure to filter the name.
80    pub fn get_all_by<'a, 'b: 'a>(
81        &'b self,
82        mut f: impl FnMut(&str) -> bool + 'a,
83    ) -> impl Iterator<Item = &'b String> + 'a {
84        self.0
85            .iter()
86            .filter(move |(n, _)| f(n))
87            .map(|(_, value)| value)
88    }
89
90    /// Returns the first value with the specified name.
91    pub fn get(&self, name: &str) -> Option<&String> {
92        self.get_all(name).next()
93    }
94}
95
96/// Options for the parameter extractor.
97#[derive(Debug, Clone)]
98pub struct ExtractParamOptions<T> {
99    /// The name of this parameter.
100    pub name: &'static str,
101
102    /// Ignore the case of the parameter name when matching.
103    pub ignore_case: bool,
104
105    /// The default value of this parameter.
106    pub default_value: Option<fn() -> T>,
107
108    /// The example value of this parameter.
109    pub example_value: Option<fn() -> T>,
110
111    /// When this is `true`, parameter values of type array or object generate
112    /// separate parameters for each value of the array or key-value pair of the
113    /// map.
114    pub explode: bool,
115
116    /// The style of the parameter.
117    pub style: Option<ParameterStyle>,
118}
119
120impl<T> Default for ExtractParamOptions<T> {
121    fn default() -> Self {
122        Self {
123            name: "",
124            ignore_case: false,
125            default_value: None,
126            example_value: None,
127            explode: true,
128            style: None,
129        }
130    }
131}
132
133/// Represents an OpenAPI extractor.
134///
135/// # Provided Implementations
136///
137/// - **Path&lt;T: Type>**
138///
139///  Extract the parameters in the request path into
140///  [`Path`](crate::param::Path).
141///
142/// - **Query&lt;T: Type>**
143///
144///  Extract the parameters in the query string into
145///  [`Query`](crate::param::Query).
146///
147/// - **Header&lt;T: Type>**
148///
149///  Extract the parameters in the request header into
150///  [`Header`](crate::param::Header).
151///
152/// - **Cookie&lt;T: Type>**
153///
154///  Extract the parameters in the cookie into
155///  [`Cookie`](crate::param::Cookie).
156///
157/// - **CookiePrivate&lt;T: Type>**
158///
159///  Extract the parameters in the private cookie into
160///  [`CookiePrivate`](crate::param::CookiePrivate).
161///
162/// - **CookieSigned&lt;T: Type>**
163///
164///  Extract the parameters in the signed cookie into
165///  [`CookieSigned`](crate::param::CookieSigned).
166///
167/// - **Binary&lt;T>**
168///
169///  Extract the request body as binary into
170///  [`Binary`](crate::payload::Binary).
171///
172/// - **Json&lt;T>**
173///
174///  Parse the request body in `JSON` format into
175///  [`Json`](crate::payload::Json).
176///
177/// - **PlainText&lt;T>**
178///
179///  Extract the request body as utf8 string into
180///  [`PlainText`](crate::payload::PlainText).
181///
182/// - **Any type derived from the [`ApiRequest`](crate::ApiRequest) macro**
183///
184///  Extract the complex request body derived from the `ApiRequest` macro.
185///
186/// - **Any type derived from the [`Multipart`](crate::Multipart) macro**
187///
188///  Extract the multipart object derived from the `Multipart` macro.
189///
190/// - **Any type derived from the [`SecurityScheme`](crate::SecurityScheme)
191///   macro**
192///
193///  Extract the authentication value derived from the `SecurityScheme`
194///  macro.
195///
196/// - **T: poem::FromRequest**
197///
198///  Use Poem's extractor.
199#[allow(unused_variables)]
200pub trait ApiExtractor<'a>: Sized {
201    /// The type of API extractor.
202    const TYPES: &'static [ApiExtractorType];
203
204    /// If it is `true`, it means that this parameter is required.
205    const PARAM_IS_REQUIRED: bool = false;
206
207    /// The parameter type.
208    type ParamType;
209
210    /// The raw parameter type for validators.
211    type ParamRawType;
212
213    /// Register related types to registry.
214    fn register(registry: &mut Registry) {}
215
216    /// Returns names of security scheme if this extractor is security scheme.
217    fn security_schemes() -> Vec<&'static str> {
218        vec![]
219    }
220
221    /// Returns `true` if the extractor is a security scheme with a fallback.
222    fn has_security_fallback() -> bool {
223        false
224    }
225
226    /// Returns the location of the parameter if this extractor is parameter.
227    fn param_in() -> Option<MetaParamIn> {
228        None
229    }
230
231    /// Returns the schema of the parameter if this extractor is parameter.
232    fn param_schema_ref() -> Option<MetaSchemaRef> {
233        None
234    }
235
236    /// Returns `MetaRequest` if this extractor is request object.
237    fn request_meta() -> Option<MetaRequest> {
238        None
239    }
240
241    /// Returns a reference to the raw type of this parameter.
242    fn param_raw_type(&self) -> Option<&Self::ParamRawType> {
243        None
244    }
245
246    /// Parse from the HTTP request.
247    fn from_request(
248        request: &'a Request,
249        body: &mut RequestBody,
250        param_opts: ExtractParamOptions<Self::ParamType>,
251    ) -> impl Future<Output = Result<Self>> + Send;
252}
253
254impl<'a, T: FromRequest<'a>> ApiExtractor<'a> for T {
255    const TYPES: &'static [ApiExtractorType] = &[ApiExtractorType::PoemExtractor];
256
257    type ParamType = ();
258    type ParamRawType = ();
259
260    async fn from_request(
261        request: &'a Request,
262        body: &mut RequestBody,
263        _param_opts: ExtractParamOptions<Self::ParamType>,
264    ) -> Result<Self> {
265        // FIXME: remove the unnecessary boxed
266        // https://github.com/rust-lang/rust/issues/100013
267        T::from_request(request, body).boxed().await
268    }
269}
270
271/// Represents an OpenAPI response content object.
272pub trait ResponseContent {
273    /// Returns the media types in this content.
274    fn media_types() -> Vec<MetaMediaType>;
275
276    /// Register the schema contained in this content to the registry.
277    #[allow(unused_variables)]
278    fn register(registry: &mut Registry) {}
279}
280
281impl<T: Payload> ResponseContent for T {
282    fn media_types() -> Vec<MetaMediaType> {
283        vec![MetaMediaType {
284            content_type: T::CONTENT_TYPE,
285            schema: T::schema_ref(),
286        }]
287    }
288
289    fn register(registry: &mut Registry) {
290        T::register(registry);
291    }
292}
293
294/// Represents an OpenAPI responses object.
295///
296/// # Provided Implementations
297///
298/// - **Binary&lt;T: Type>**
299///
300///  A binary response with content type `application/octet-stream`.
301///
302/// - **Json&lt;T: Type>**
303///
304///  A JSON response with content type `application/json`.
305///
306/// - **PlainText&lt;T: Type>**
307///
308///  A utf8 string response with content type `text/plain`.
309///
310/// - **Attachment&lt;T: Type>**
311///
312///  A file download response, the content type is
313///  `application/octet-stream`.
314///
315/// - **Response&lt;T: Type>**
316///
317///  A response type use it to modify the status code and HTTP headers.
318///
319/// - **()**
320///
321///  It means that this API does not have any response body.
322///
323/// - **poem::Result&lt;T: ApiResponse>**
324///
325///  It means that an error may occur in this API.
326///
327/// - **Any type derived from the [`ApiResponse`](crate::ApiResponse) macro**
328///
329///  A complex response  derived from the `ApiResponse` macro.
330pub trait ApiResponse: Sized {
331    /// If true, it means that the response object has a custom bad request
332    /// handler.
333    const BAD_REQUEST_HANDLER: bool = false;
334
335    /// Gets metadata of this response.
336    fn meta() -> MetaResponses;
337
338    /// Register the schema contained in this response object to the registry.
339    fn register(registry: &mut Registry);
340
341    /// Convert [`poem::Error`] to this response object.
342    #[allow(unused_variables)]
343    fn from_parse_request_error(err: Error) -> Self {
344        unreachable!()
345    }
346}
347
348impl ApiResponse for () {
349    fn meta() -> MetaResponses {
350        MetaResponses {
351            responses: vec![MetaResponse {
352                description: "",
353                status: Some(200),
354                status_range: None,
355                content: vec![],
356                headers: vec![],
357            }],
358        }
359    }
360
361    fn register(_registry: &mut Registry) {}
362}
363
364impl ApiResponse for Error {
365    fn meta() -> MetaResponses {
366        MetaResponses {
367            responses: Vec::new(),
368        }
369    }
370
371    fn register(_registry: &mut Registry) {}
372}
373
374impl<T, E> ApiResponse for Result<T, E>
375where
376    T: ApiResponse,
377    E: ApiResponse + Into<Error> + Send + Sync + 'static,
378{
379    const BAD_REQUEST_HANDLER: bool = T::BAD_REQUEST_HANDLER;
380
381    fn meta() -> MetaResponses {
382        let mut meta = T::meta();
383        meta.responses.extend(E::meta().responses);
384        meta
385    }
386
387    fn register(registry: &mut Registry) {
388        T::register(registry);
389        E::register(registry);
390    }
391
392    fn from_parse_request_error(err: Error) -> Self {
393        Ok(T::from_parse_request_error(err))
394    }
395}
396
397#[cfg(feature = "websocket")]
398impl<F, Fut> ApiResponse for poem::web::websocket::WebSocketUpgraded<F>
399where
400    F: FnOnce(poem::web::websocket::WebSocketStream) -> Fut + Send + Sync + 'static,
401    Fut: std::future::Future + Send + 'static,
402{
403    fn meta() -> MetaResponses {
404        MetaResponses {
405            responses: vec![MetaResponse {
406                description: "A websocket response",
407                status: Some(101),
408                status_range: None,
409                content: vec![],
410                headers: vec![],
411            }],
412        }
413    }
414
415    fn register(_registry: &mut Registry) {}
416}
417
418/// Represents an OpenAPI tags.
419pub trait Tags {
420    /// Register this tag type to registry.
421    fn register(&self, registry: &mut Registry);
422
423    /// Gets the tag name.
424    fn name(&self) -> &'static str;
425}
426
427/// Represents a OAuth scopes.
428pub trait OAuthScopes {
429    /// Gets metadata of this object.
430    fn meta() -> Vec<MetaOAuthScope>;
431
432    /// Get the scope name.
433    fn name(&self) -> &'static str;
434}
435
436/// A operation id that can be obtained from the response
437#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
438pub struct OperationId(pub &'static str);
439
440impl Display for OperationId {
441    #[inline]
442    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
443        f.write_str(self.0)
444    }
445}
446
447/// Represents an OpenAPI object.
448pub trait OpenApi: Sized {
449    /// Gets metadata of this API object.
450    fn meta() -> Vec<MetaApi>;
451
452    /// Register some types to the registry.
453    fn register(registry: &mut Registry);
454
455    /// Adds all API endpoints to the routing object.
456    fn add_routes(self, route_table: &mut HashMap<String, HashMap<Method, BoxEndpoint<'static>>>);
457}
458
459macro_rules! impl_openapi_for_tuple {
460    (($head:ident, $hn:tt), $(($tail:ident, $tn:tt)),*) => {
461        impl<$head: OpenApi, $($tail: OpenApi),*> OpenApi for ($head, $($tail),*) {
462            fn meta() -> Vec<MetaApi> {
463                let mut metadata = $head::meta();
464                $(
465                metadata.extend($tail::meta());
466                )*
467                metadata
468            }
469
470            fn register(registry: &mut Registry) {
471                $head::register(registry);
472                $(
473                $tail::register(registry);
474                )*
475            }
476
477            fn add_routes(self, route_table: &mut HashMap<String, HashMap<Method, BoxEndpoint<'static>>>) {
478                self.$hn.add_routes(route_table);
479                $(
480                self.$tn.add_routes(route_table);
481                )*
482            }
483        }
484    };
485
486    () => {};
487}
488
489#[rustfmt::skip]
490impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7), (T9, 8), (T10, 9), (T11, 10), (T12, 11), (T13, 12), (T14, 13), (T15, 14), (T16, 15));
491#[rustfmt::skip]
492impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7), (T9, 8), (T10, 9), (T11, 10), (T12, 11), (T13, 12), (T14, 13), (T15, 14));
493#[rustfmt::skip]
494impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7), (T9, 8), (T10, 9), (T11, 10), (T12, 11), (T13, 12), (T14, 13));
495#[rustfmt::skip]
496impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7), (T9, 8), (T10, 9), (T11, 10), (T12, 11), (T13, 12));
497#[rustfmt::skip]
498impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7), (T9, 8), (T10, 9), (T11, 10), (T12, 11));
499#[rustfmt::skip]
500impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7), (T9, 8), (T10, 9), (T11, 10));
501#[rustfmt::skip]
502impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7), (T9, 8), (T10, 9));
503#[rustfmt::skip]
504impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7), (T9, 8));
505#[rustfmt::skip]
506impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7));
507#[rustfmt::skip]
508impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6));
509#[rustfmt::skip]
510impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5));
511#[rustfmt::skip]
512impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4));
513#[rustfmt::skip]
514impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3));
515#[rustfmt::skip]
516impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2));
517#[rustfmt::skip]
518impl_openapi_for_tuple!((T1, 0), (T2, 1));
519
520impl OpenApi for () {
521    fn meta() -> Vec<MetaApi> {
522        vec![]
523    }
524
525    fn register(_registry: &mut Registry) {}
526
527    fn add_routes(self, _route_table: &mut HashMap<String, HashMap<Method, BoxEndpoint<'static>>>) {
528    }
529}
530
531/// Represents a webhook object.
532pub trait Webhook: Sized {
533    /// Gets metadata of this webhooks object.
534    fn meta() -> Vec<MetaWebhook>;
535
536    /// Register some types to the registry.
537    fn register(registry: &mut Registry);
538}
539
540impl Webhook for () {
541    fn meta() -> Vec<MetaWebhook> {
542        vec![]
543    }
544
545    fn register(_: &mut Registry) {}
546}