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 status_range: None,
340 content: vec![],
341 headers: vec![],
342 }],
343 }
344 }
345
346 fn register(_registry: &mut Registry) {}
347}
348
349impl ApiResponse for Error {
350 fn meta() -> MetaResponses {
351 MetaResponses {
352 responses: Vec::new(),
353 }
354 }
355
356 fn register(_registry: &mut Registry) {}
357}
358
359impl<T, E> ApiResponse for Result<T, E>
360where
361 T: ApiResponse,
362 E: ApiResponse + Into<Error> + Send + Sync + 'static,
363{
364 const BAD_REQUEST_HANDLER: bool = T::BAD_REQUEST_HANDLER;
365
366 fn meta() -> MetaResponses {
367 let mut meta = T::meta();
368 meta.responses.extend(E::meta().responses);
369 meta
370 }
371
372 fn register(registry: &mut Registry) {
373 T::register(registry);
374 E::register(registry);
375 }
376
377 fn from_parse_request_error(err: Error) -> Self {
378 Ok(T::from_parse_request_error(err))
379 }
380}
381
382#[cfg(feature = "websocket")]
383impl<F, Fut> ApiResponse for poem::web::websocket::WebSocketUpgraded<F>
384where
385 F: FnOnce(poem::web::websocket::WebSocketStream) -> Fut + Send + Sync + 'static,
386 Fut: std::future::Future + Send + 'static,
387{
388 fn meta() -> MetaResponses {
389 MetaResponses {
390 responses: vec![MetaResponse {
391 description: "A websocket response",
392 status: Some(101),
393 status_range: None,
394 content: vec![],
395 headers: vec![],
396 }],
397 }
398 }
399
400 fn register(_registry: &mut Registry) {}
401}
402
403pub trait Tags {
405 fn register(&self, registry: &mut Registry);
407
408 fn name(&self) -> &'static str;
410}
411
412pub trait OAuthScopes {
414 fn meta() -> Vec<MetaOAuthScope>;
416
417 fn name(&self) -> &'static str;
419}
420
421#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
423pub struct OperationId(pub &'static str);
424
425impl Display for OperationId {
426 #[inline]
427 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428 f.write_str(self.0)
429 }
430}
431
432pub trait OpenApi: Sized {
434 fn meta() -> Vec<MetaApi>;
436
437 fn register(registry: &mut Registry);
439
440 fn add_routes(self, route_table: &mut HashMap<String, HashMap<Method, BoxEndpoint<'static>>>);
442}
443
444macro_rules! impl_openapi_for_tuple {
445 (($head:ident, $hn:tt), $(($tail:ident, $tn:tt)),*) => {
446 impl<$head: OpenApi, $($tail: OpenApi),*> OpenApi for ($head, $($tail),*) {
447 fn meta() -> Vec<MetaApi> {
448 let mut metadata = $head::meta();
449 $(
450 metadata.extend($tail::meta());
451 )*
452 metadata
453 }
454
455 fn register(registry: &mut Registry) {
456 $head::register(registry);
457 $(
458 $tail::register(registry);
459 )*
460 }
461
462 fn add_routes(self, route_table: &mut HashMap<String, HashMap<Method, BoxEndpoint<'static>>>) {
463 self.$hn.add_routes(route_table);
464 $(
465 self.$tn.add_routes(route_table);
466 )*
467 }
468 }
469 };
470
471 () => {};
472}
473
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), (T16, 15));
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), (T15, 14));
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), (T14, 13));
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), (T13, 12));
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), (T12, 11));
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), (T11, 10));
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), (T10, 9));
488#[rustfmt::skip]
489impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7), (T9, 8));
490#[rustfmt::skip]
491impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7));
492#[rustfmt::skip]
493impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6));
494#[rustfmt::skip]
495impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5));
496#[rustfmt::skip]
497impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4));
498#[rustfmt::skip]
499impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3));
500#[rustfmt::skip]
501impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2));
502#[rustfmt::skip]
503impl_openapi_for_tuple!((T1, 0), (T2, 1));
504
505impl OpenApi for () {
506 fn meta() -> Vec<MetaApi> {
507 vec![]
508 }
509
510 fn register(_registry: &mut Registry) {}
511
512 fn add_routes(self, _route_table: &mut HashMap<String, HashMap<Method, BoxEndpoint<'static>>>) {
513 }
514}
515
516pub trait Webhook: Sized {
518 fn meta() -> Vec<MetaWebhook>;
520
521 fn register(registry: &mut Registry);
523}
524
525impl Webhook for () {
526 fn meta() -> Vec<MetaWebhook> {
527 vec![]
528 }
529
530 fn register(_: &mut Registry) {}
531}