use std::{
collections::HashMap,
fmt::{self, Debug, Display},
future::Future,
ops::Deref,
};
use futures_util::FutureExt;
use poem::{Error, FromRequest, Request, RequestBody, Result, endpoint::BoxEndpoint, http::Method};
use serde::Serialize;
use crate::{
payload::Payload,
registry::{
MetaApi, MetaMediaType, MetaOAuthScope, MetaParamIn, MetaRequest, MetaResponse,
MetaResponses, MetaSchemaRef, MetaWebhook, Registry,
},
};
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum ParameterStyle {
Label,
Matrix,
Form,
Simple,
SpaceDelimited,
PipeDelimited,
DeepObject,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ApiExtractorType {
RequestObject,
Parameter,
SecurityScheme,
PoemExtractor,
}
#[doc(hidden)]
#[derive(Clone)]
pub struct UrlQuery(pub Vec<(String, String)>);
impl Deref for UrlQuery {
type Target = Vec<(String, String)>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl UrlQuery {
pub fn get_all<'a, 'b: 'a>(&'b self, name: &'a str) -> impl Iterator<Item = &'b String> + 'a {
self.0
.iter()
.filter(move |(n, _)| n == name)
.map(|(_, value)| value)
}
pub fn get_all_by<'a, 'b: 'a>(
&'b self,
mut f: impl FnMut(&str) -> bool + 'a,
) -> impl Iterator<Item = &'b String> + 'a {
self.0
.iter()
.filter(move |(n, _)| f(n))
.map(|(_, value)| value)
}
pub fn get(&self, name: &str) -> Option<&String> {
self.get_all(name).next()
}
}
#[derive(Debug, Clone)]
pub struct ExtractParamOptions<T> {
pub name: &'static str,
pub ignore_case: bool,
pub default_value: Option<fn() -> T>,
pub example_value: Option<fn() -> T>,
pub explode: bool,
pub style: Option<ParameterStyle>,
}
impl<T> Default for ExtractParamOptions<T> {
fn default() -> Self {
Self {
name: "",
ignore_case: false,
default_value: None,
example_value: None,
explode: true,
style: None,
}
}
}
#[allow(unused_variables)]
pub trait ApiExtractor<'a>: Sized {
const TYPES: &'static [ApiExtractorType];
const PARAM_IS_REQUIRED: bool = false;
type ParamType;
type ParamRawType;
fn register(registry: &mut Registry) {}
fn security_schemes() -> Vec<&'static str> {
vec![]
}
fn has_security_fallback() -> bool {
false
}
fn param_in() -> Option<MetaParamIn> {
None
}
fn param_schema_ref() -> Option<MetaSchemaRef> {
None
}
fn request_meta() -> Option<MetaRequest> {
None
}
fn param_raw_type(&self) -> Option<&Self::ParamRawType> {
None
}
fn from_request(
request: &'a Request,
body: &mut RequestBody,
param_opts: ExtractParamOptions<Self::ParamType>,
) -> impl Future<Output = Result<Self>> + Send;
}
impl<'a, T: FromRequest<'a>> ApiExtractor<'a> for T {
const TYPES: &'static [ApiExtractorType] = &[ApiExtractorType::PoemExtractor];
type ParamType = ();
type ParamRawType = ();
async fn from_request(
request: &'a Request,
body: &mut RequestBody,
_param_opts: ExtractParamOptions<Self::ParamType>,
) -> Result<Self> {
T::from_request(request, body).boxed().await
}
}
pub trait ResponseContent {
fn media_types() -> Vec<MetaMediaType>;
#[allow(unused_variables)]
fn register(registry: &mut Registry) {}
}
impl<T: Payload> ResponseContent for T {
fn media_types() -> Vec<MetaMediaType> {
vec![MetaMediaType {
content_type: T::CONTENT_TYPE,
schema: T::schema_ref(),
}]
}
fn register(registry: &mut Registry) {
T::register(registry);
}
}
pub trait ApiResponse: Sized {
const BAD_REQUEST_HANDLER: bool = false;
fn meta() -> MetaResponses;
fn register(registry: &mut Registry);
#[allow(unused_variables)]
fn from_parse_request_error(err: Error) -> Self {
unreachable!()
}
}
impl ApiResponse for () {
fn meta() -> MetaResponses {
MetaResponses {
responses: vec![MetaResponse {
description: "",
status: Some(200),
status_range: None,
content: vec![],
headers: vec![],
}],
}
}
fn register(_registry: &mut Registry) {}
}
impl ApiResponse for Error {
fn meta() -> MetaResponses {
MetaResponses {
responses: Vec::new(),
}
}
fn register(_registry: &mut Registry) {}
}
impl<T, E> ApiResponse for Result<T, E>
where
T: ApiResponse,
E: ApiResponse + Into<Error> + Send + Sync + 'static,
{
const BAD_REQUEST_HANDLER: bool = T::BAD_REQUEST_HANDLER;
fn meta() -> MetaResponses {
let mut meta = T::meta();
meta.responses.extend(E::meta().responses);
meta
}
fn register(registry: &mut Registry) {
T::register(registry);
E::register(registry);
}
fn from_parse_request_error(err: Error) -> Self {
Ok(T::from_parse_request_error(err))
}
}
#[cfg(feature = "websocket")]
impl<F, Fut> ApiResponse for poem::web::websocket::WebSocketUpgraded<F>
where
F: FnOnce(poem::web::websocket::WebSocketStream) -> Fut + Send + Sync + 'static,
Fut: std::future::Future + Send + 'static,
{
fn meta() -> MetaResponses {
MetaResponses {
responses: vec![MetaResponse {
description: "A websocket response",
status: Some(101),
status_range: None,
content: vec![],
headers: vec![],
}],
}
}
fn register(_registry: &mut Registry) {}
}
pub trait Tags {
fn register(&self, registry: &mut Registry);
fn name(&self) -> &'static str;
}
pub trait OAuthScopes {
fn meta() -> Vec<MetaOAuthScope>;
fn name(&self) -> &'static str;
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct OperationId(pub &'static str);
impl Display for OperationId {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0)
}
}
pub trait OpenApi: Sized {
fn meta() -> Vec<MetaApi>;
fn register(registry: &mut Registry);
fn add_routes(self, route_table: &mut HashMap<String, HashMap<Method, BoxEndpoint<'static>>>);
}
macro_rules! impl_openapi_for_tuple {
(($head:ident, $hn:tt), $(($tail:ident, $tn:tt)),*) => {
impl<$head: OpenApi, $($tail: OpenApi),*> OpenApi for ($head, $($tail),*) {
fn meta() -> Vec<MetaApi> {
let mut metadata = $head::meta();
$(
metadata.extend($tail::meta());
)*
metadata
}
fn register(registry: &mut Registry) {
$head::register(registry);
$(
$tail::register(registry);
)*
}
fn add_routes(self, route_table: &mut HashMap<String, HashMap<Method, BoxEndpoint<'static>>>) {
self.$hn.add_routes(route_table);
$(
self.$tn.add_routes(route_table);
)*
}
}
};
() => {};
}
#[rustfmt::skip]
impl_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));
#[rustfmt::skip]
impl_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));
#[rustfmt::skip]
impl_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));
#[rustfmt::skip]
impl_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));
#[rustfmt::skip]
impl_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));
#[rustfmt::skip]
impl_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));
#[rustfmt::skip]
impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7), (T9, 8), (T10, 9));
#[rustfmt::skip]
impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7), (T9, 8));
#[rustfmt::skip]
impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6), (T8, 7));
#[rustfmt::skip]
impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5), (T7, 6));
#[rustfmt::skip]
impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4), (T6, 5));
#[rustfmt::skip]
impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3), (T5, 4));
#[rustfmt::skip]
impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2), (T4, 3));
#[rustfmt::skip]
impl_openapi_for_tuple!((T1, 0), (T2, 1), (T3, 2));
#[rustfmt::skip]
impl_openapi_for_tuple!((T1, 0), (T2, 1));
impl OpenApi for () {
fn meta() -> Vec<MetaApi> {
vec![]
}
fn register(_registry: &mut Registry) {}
fn add_routes(self, _route_table: &mut HashMap<String, HashMap<Method, BoxEndpoint<'static>>>) {
}
}
pub trait Webhook: Sized {
fn meta() -> Vec<MetaWebhook>;
fn register(registry: &mut Registry);
}
impl Webhook for () {
fn meta() -> Vec<MetaWebhook> {
vec![]
}
fn register(_: &mut Registry) {}
}