1use std::{
2 collections::HashMap,
3 fmt::{self, Debug, Display},
4 future::Future,
5 ops::Deref,
6};
7
8use futures_util::FutureExt;
9use poem::{endpoint::BoxEndpoint, http::Method, Error, FromRequest, Request, RequestBody, Result};
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#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize)]
22#[serde(rename_all = "camelCase")]
23pub enum ParameterStyle {
24 Label,
26 Matrix,
28 Form,
30 Simple,
32 SpaceDelimited,
34 PipeDelimited,
37 DeepObject,
40}
41
42#[derive(Debug, Copy, Clone, Eq, PartialEq)]
44pub enum ApiExtractorType {
45 RequestObject,
47
48 Parameter,
50
51 SecurityScheme,
53
54 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 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 pub fn get(&self, name: &str) -> Option<&String> {
81 self.get_all(name).next()
82 }
83}
84
85#[derive(Clone)]
87pub struct ExtractParamOptions<T> {
88 pub name: &'static str,
90
91 pub default_value: Option<fn() -> T>,
93
94 pub example_value: Option<fn() -> T>,
96
97 pub explode: bool,
101
102 pub style: Option<ParameterStyle>,
104}
105
106impl<T> Default for ExtractParamOptions<T> {
107 fn default() -> Self {
108 Self {
109 name: "",
110 default_value: None,
111 example_value: None,
112 explode: true,
113 style: None,
114 }
115 }
116}
117
118#[allow(unused_variables)]
185pub trait ApiExtractor<'a>: Sized {
186 const TYPES: &'static [ApiExtractorType];
188
189 const PARAM_IS_REQUIRED: bool = false;
191
192 type ParamType;
194
195 type ParamRawType;
197
198 fn register(registry: &mut Registry) {}
200
201 fn security_schemes() -> Vec<&'static str> {
203 vec![]
204 }
205
206 fn has_security_fallback() -> bool {
208 false
209 }
210
211 fn param_in() -> Option<MetaParamIn> {
213 None
214 }
215
216 fn param_schema_ref() -> Option<MetaSchemaRef> {
218 None
219 }
220
221 fn request_meta() -> Option<MetaRequest> {
223 None
224 }
225
226 fn param_raw_type(&self) -> Option<&Self::ParamRawType> {
228 None
229 }
230
231 fn from_request(
233 request: &'a Request,
234 body: &mut RequestBody,
235 param_opts: ExtractParamOptions<Self::ParamType>,
236 ) -> impl Future<Output = Result<Self>> + Send;
237}
238
239impl<'a, T: FromRequest<'a>> ApiExtractor<'a> for T {
240 const TYPES: &'static [ApiExtractorType] = &[ApiExtractorType::PoemExtractor];
241
242 type ParamType = ();
243 type ParamRawType = ();
244
245 async fn from_request(
246 request: &'a Request,
247 body: &mut RequestBody,
248 _param_opts: ExtractParamOptions<Self::ParamType>,
249 ) -> Result<Self> {
250 T::from_request(request, body).boxed().await
253 }
254}
255
256pub trait ResponseContent {
258 fn media_types() -> Vec<MetaMediaType>;
260
261 #[allow(unused_variables)]
263 fn register(registry: &mut Registry) {}
264}
265
266impl<T: Payload> ResponseContent for T {
267 fn media_types() -> Vec<MetaMediaType> {
268 vec![MetaMediaType {
269 content_type: T::CONTENT_TYPE,
270 schema: T::schema_ref(),
271 }]
272 }
273
274 fn register(registry: &mut Registry) {
275 T::register(registry);
276 }
277}
278
279pub trait ApiResponse: Sized {
316 const BAD_REQUEST_HANDLER: bool = false;
319
320 fn meta() -> MetaResponses;
322
323 fn register(registry: &mut Registry);
325
326 #[allow(unused_variables)]
328 fn from_parse_request_error(err: Error) -> Self {
329 unreachable!()
330 }
331}
332
333impl ApiResponse for () {
334 fn meta() -> MetaResponses {
335 MetaResponses {
336 responses: vec![MetaResponse {
337 description: "",
338 status: Some(200),
339 content: vec![],
340 headers: vec![],
341 }],
342 }
343 }
344
345 fn register(_registry: &mut Registry) {}
346}
347
348impl ApiResponse for Error {
349 fn meta() -> MetaResponses {
350 MetaResponses {
351 responses: Vec::new(),
352 }
353 }
354
355 fn register(_registry: &mut Registry) {}
356}
357
358impl<T, E> ApiResponse for Result<T, E>
359where
360 T: ApiResponse,
361 E: ApiResponse + Into<Error> + Send + Sync + 'static,
362{
363 const BAD_REQUEST_HANDLER: bool = T::BAD_REQUEST_HANDLER;
364
365 fn meta() -> MetaResponses {
366 let mut meta = T::meta();
367 meta.responses.extend(E::meta().responses);
368 meta
369 }
370
371 fn register(registry: &mut Registry) {
372 T::register(registry);
373 E::register(registry);
374 }
375
376 fn from_parse_request_error(err: Error) -> Self {
377 Ok(T::from_parse_request_error(err))
378 }
379}
380
381#[cfg(feature = "websocket")]
382impl<F, Fut> ApiResponse for poem::web::websocket::WebSocketUpgraded<F>
383where
384 F: FnOnce(poem::web::websocket::WebSocketStream) -> Fut + Send + Sync + 'static,
385 Fut: std::future::Future + Send + 'static,
386{
387 fn meta() -> MetaResponses {
388 MetaResponses {
389 responses: vec![MetaResponse {
390 description: "A websocket response",
391 status: Some(101),
392 content: vec![],
393 headers: vec![],
394 }],
395 }
396 }
397
398 fn register(_registry: &mut Registry) {}
399}
400
401pub trait Tags {
403 fn register(&self, registry: &mut Registry);
405
406 fn name(&self) -> &'static str;
408}
409
410pub trait OAuthScopes {
412 fn meta() -> Vec<MetaOAuthScope>;
414
415 fn name(&self) -> &'static str;
417}
418
419#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
421pub struct OperationId(pub &'static str);
422
423impl Display for OperationId {
424 #[inline]
425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426 f.write_str(self.0)
427 }
428}
429
430pub trait OpenApi: Sized {
432 fn meta() -> Vec<MetaApi>;
434
435 fn register(registry: &mut Registry);
437
438 fn add_routes(self, route_table: &mut HashMap<String, HashMap<Method, BoxEndpoint<'static>>>);
440}
441
442macro_rules! impl_openapi_for_tuple {
443 (($head:ident, $hn:tt), $(($tail:ident, $tn:tt)),*) => {
444 impl<$head: OpenApi, $($tail: OpenApi),*> OpenApi for ($head, $($tail),*) {
445 fn meta() -> Vec<MetaApi> {
446 let mut metadata = $head::meta();
447 $(
448 metadata.extend($tail::meta());
449 )*
450 metadata
451 }
452
453 fn register(registry: &mut Registry) {
454 $head::register(registry);
455 $(
456 $tail::register(registry);
457 )*
458 }
459
460 fn add_routes(self, route_table: &mut HashMap<String, HashMap<Method, BoxEndpoint<'static>>>) {
461 self.$hn.add_routes(route_table);
462 $(
463 self.$tn.add_routes(route_table);
464 )*
465 }
466 }
467 };
468
469 () => {};
470}
471
472#[rustfmt::skip]
473impl_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));
474#[rustfmt::skip]
475impl_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));
476#[rustfmt::skip]
477impl_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));
478#[rustfmt::skip]
479impl_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));
480#[rustfmt::skip]
481impl_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));
482#[rustfmt::skip]
483impl_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));
484#[rustfmt::skip]
485impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7), (T9, 8), (T10, 9));
486#[rustfmt::skip]
487impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7), (T9, 8));
488#[rustfmt::skip]
489impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7));
490#[rustfmt::skip]
491impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6));
492#[rustfmt::skip]
493impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5));
494#[rustfmt::skip]
495impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4));
496#[rustfmt::skip]
497impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3));
498#[rustfmt::skip]
499impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2));
500#[rustfmt::skip]
501impl_openapi_for_tuple!((T1, 0), (T2, 1));
502
503impl OpenApi for () {
504 fn meta() -> Vec<MetaApi> {
505 vec![]
506 }
507
508 fn register(_registry: &mut Registry) {}
509
510 fn add_routes(self, _route_table: &mut HashMap<String, HashMap<Method, BoxEndpoint<'static>>>) {
511 }
512}
513
514pub trait Webhook: Sized {
516 fn meta() -> Vec<MetaWebhook>;
518
519 fn register(registry: &mut Registry);
521}
522
523impl Webhook for () {
524 fn meta() -> Vec<MetaWebhook> {
525 vec![]
526 }
527
528 fn register(_: &mut Registry) {}
529}