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#[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_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 pub fn get(&self, name: &str) -> Option<&String> {
92 self.get_all(name).next()
93 }
94}
95
96#[derive(Debug, Clone)]
98pub struct ExtractParamOptions<T> {
99 pub name: &'static str,
101
102 pub ignore_case: bool,
104
105 pub default_value: Option<fn() -> T>,
107
108 pub example_value: Option<fn() -> T>,
110
111 pub explode: bool,
115
116 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#[allow(unused_variables)]
200pub trait ApiExtractor<'a>: Sized {
201 const TYPES: &'static [ApiExtractorType];
203
204 const PARAM_IS_REQUIRED: bool = false;
206
207 type ParamType;
209
210 type ParamRawType;
212
213 fn register(registry: &mut Registry) {}
215
216 fn security_schemes() -> Vec<&'static str> {
218 vec![]
219 }
220
221 fn has_security_fallback() -> bool {
223 false
224 }
225
226 fn param_in() -> Option<MetaParamIn> {
228 None
229 }
230
231 fn param_schema_ref() -> Option<MetaSchemaRef> {
233 None
234 }
235
236 fn request_meta() -> Option<MetaRequest> {
238 None
239 }
240
241 fn param_raw_type(&self) -> Option<&Self::ParamRawType> {
243 None
244 }
245
246 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 T::from_request(request, body).boxed().await
268 }
269}
270
271pub trait ResponseContent {
273 fn media_types() -> Vec<MetaMediaType>;
275
276 #[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
294pub trait ApiResponse: Sized {
331 const BAD_REQUEST_HANDLER: bool = false;
334
335 fn meta() -> MetaResponses;
337
338 fn register(registry: &mut Registry);
340
341 #[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
418pub trait Tags {
420 fn register(&self, registry: &mut Registry);
422
423 fn name(&self) -> &'static str;
425}
426
427pub trait OAuthScopes {
429 fn meta() -> Vec<MetaOAuthScope>;
431
432 fn name(&self) -> &'static str;
434}
435
436#[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
447pub trait OpenApi: Sized {
449 fn meta() -> Vec<MetaApi>;
451
452 fn register(registry: &mut Registry);
454
455 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
531pub trait Webhook: Sized {
533 fn meta() -> Vec<MetaWebhook>;
535
536 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}