use std::any::Any;
use std::cmp::Ordering;
use std::collections::{BTreeSet, HashMap};
use std::fmt::{Debug, Formatter};
use crate::serializer::{Deserializer, Serializer};
use crate::request::{Request, Context, Response};
use dce_util::mixed::DceResult;
#[cfg(feature = "async")]
use std::future::Future;
use std::ops::Deref;
#[cfg(feature = "async")]
use std::pin::Pin;
#[cfg(feature = "async")]
use async_trait::async_trait;
use crate::protocol::RoutableProtocol;
use crate::router::{PATH_PART_SEPARATOR, SUFFIX_BOUNDARY};
const SUFFIX_SEPARATOR: char = '|';
#[derive(Debug)]
pub struct Api<Rp, ReqDto, RespDto>
where Rp: RoutableProtocol + Send + Sync + Debug + 'static,
ReqDto: 'static,
RespDto: 'static
{
controller: Controller<Rp, ReqDto, RespDto>,
deserializers: Vec<Box<dyn Deserializer<ReqDto> + Send + Sync>>,
serializers: Vec<Box<dyn Serializer<RespDto> + Send + Sync>>,
method: Option<Box<dyn Method<Rp> + Send + Sync>>,
path: &'static str,
suffixes: BTreeSet<Suffix>,
id: &'static str,
omission: bool,
redirect: &'static str,
name: &'static str,
unresponsive: bool,
extras: HashMap<&'static str, Box<dyn Any + Send + Sync>>,
}
impl<Rp, ReqDto, RespDto> Api<Rp, ReqDto, RespDto>
where Rp: RoutableProtocol + Debug + Send + Sync + 'static,
ReqDto: 'static,
RespDto: 'static
{
pub fn new(
controller: Controller<Rp, ReqDto, RespDto>,
deserializers: Vec<Box<dyn Deserializer<ReqDto> + Send + Sync>>,
serializers: Vec<Box<dyn Serializer<RespDto> + Send + Sync>>,
method: Option<Box<dyn Method<Rp> + Send + Sync>>,
path: &'static str,
id: &'static str,
omission: bool,
redirect: &'static str,
name: &'static str,
unresponsive: bool,
extras: HashMap<&'static str, Box<dyn Any + Send + Sync>>,
) -> Self {
let mut path = path.trim_matches(PATH_PART_SEPARATOR);
let mut suffixes = BTreeSet::from([Suffix("")]);
if let Some(last_part_from) = path.rfind(PATH_PART_SEPARATOR).map_or_else(|| Some(0), |i| Some(i + 1)) {
let last_part = &path[last_part_from..];
if let Some(bound_index) = last_part.find(SUFFIX_BOUNDARY) {
suffixes = last_part[bound_index + 1 ..].split(SUFFIX_SEPARATOR).map(Suffix).collect();
path = &path[0.. last_part_from + bound_index];
}
}
Api { controller, deserializers, serializers, method, path, suffixes, id, omission, redirect, name, unresponsive, extras, }
}
pub fn controller(&self) -> &Controller<Rp, ReqDto, RespDto> {
&self.controller
}
pub fn deserializers(&self) -> &Vec<Box<dyn Deserializer<ReqDto> + Send + Sync>> {
&self.deserializers
}
pub fn serializers(&self) -> &Vec<Box<dyn Serializer<RespDto> + Send + Sync>> {
&self.serializers
}
}
#[cfg_attr(feature = "async", async_trait)]
pub trait ApiTrait<Rp: RoutableProtocol> {
fn method(&self) -> &Option<Box<dyn Method<Rp> + Send + Sync>>;
fn path(&self) -> &'static str;
fn suffixes(&self) -> &BTreeSet<Suffix>;
fn id(&self) -> &'static str;
fn omission(&self) -> bool;
fn redirect(&self) -> &'static str;
fn name(&self) -> &'static str;
fn unresponsive(&self) -> bool;
fn extras(&self) -> &HashMap<&'static str, Box<dyn Any + Send + Sync>>;
fn method_match(&self, rp: &Rp) -> bool;
#[cfg(feature = "async")]
async fn call_controller<'a>(&'static self, context: &'a mut Context<Rp>) -> DceResult<()>;
#[cfg(not(feature = "async"))]
fn call_controller<'a>(&'static self, context: &'a mut Context<Rp>) -> DceResult<()>;
}
#[cfg_attr(feature = "async", async_trait)]
impl<Rp, ReqDto, RespDto> ApiTrait<Rp> for Api<Rp, ReqDto, RespDto>
where Rp: RoutableProtocol + Send + Sync + Debug + 'static,
ReqDto: 'static,
RespDto: 'static
{
fn method(&self) -> &Option<Box<dyn Method<Rp> + Send + Sync>> {
&self.method
}
fn path(&self) -> &'static str {
self.path
}
fn suffixes(&self) -> &BTreeSet<Suffix> {
&self.suffixes
}
fn id(&self) -> &'static str {
self.id
}
fn omission(&self) -> bool {
self.omission
}
fn redirect(&self) -> &'static str {
self.redirect
}
fn name(&self) -> &'static str {
self.name
}
fn unresponsive(&self) -> bool {
self.unresponsive
}
fn extras(&self) -> &HashMap<&'static str, Box<dyn Any + Send + Sync>> {
&self.extras
}
fn method_match(&self, rp: &Rp) -> bool {
match &self.method {
Some(method) => method.req_match(rp),
_ => true
}
}
#[cfg(feature = "async")]
async fn call_controller<'a>(&'static self, context: &'a mut Context<Rp>) -> DceResult<()> {
if context.router().before_controller().is_some() {
match context.router().clone().before_controller() {
Some(EventHandler::Sync(func)) => func(context)?,
Some(EventHandler::Async(func)) => func(context).await?,
_ => {},
};
}
let req = Request::new(self, context);
*context.rp_mut().resp_mut() = match &self.controller {
Controller::Async(controller) => controller(req).await,
Controller::Sync(controller) => controller(req),
}?;
if context.router().after_controller().is_some() {
match context.router().clone().after_controller() {
Some(EventHandler::Sync(func)) => func(context)?,
Some(EventHandler::Async(func)) => func(context).await?,
_ => {},
};
}
Ok(())
}
#[cfg(not(feature = "async"))]
fn call_controller<'a>(&'static self, context: &'a mut Context<Rp>) -> DceResult<()> {
if context.router().before_controller().is_some() {
if let Some(EventHandler::Sync(func)) = context.router().clone().before_controller() { func(context)?; }
}
let req = Request::new(self, context);
let Controller::Sync(controller) = &self.controller;
*context.rp_mut().resp_mut() = controller(req)?;
if context.router().after_controller().is_some() {
if let Some(crate::api::EventHandler::Sync(func)) = context.router().clone().after_controller() { func(context)?; }
}
Ok(())
}
}
impl<Rp: RoutableProtocol> Debug for dyn ApiTrait<Rp> + Send + Sync + 'static {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(format!(r#"Api{{method: {:?}, path: "{}", suffixes: {:?}, id: "{}", omission: {}, redirect: "{}", name: "{}", unresponsive: {}, extras: {:?}}}"#,
self.method(), self.path(), self.suffixes(), self.id(), self.omission(), self.redirect(), self.name(), self.unresponsive(), self.extras()).as_str())
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Suffix(&'static str);
impl Deref for Suffix {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl PartialOrd<Self> for Suffix {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Suffix {
fn cmp(&self, other: &Self) -> Ordering {
let compound_diff = self.0.chars().filter(|c| SUFFIX_BOUNDARY.eq_ignore_ascii_case(c)).count() as isize
- other.0.chars().filter(|c| SUFFIX_BOUNDARY.eq_ignore_ascii_case(c)).count() as isize;
if compound_diff > 0 {
return Ordering::Less;
} else if compound_diff < 0 {
return Ordering::Greater;
}
self.0.cmp(other.0)
}
}
pub trait Method<Rp> {
fn to_string(&self) -> String;
fn req_match(&self, raw: &Rp) -> bool;
}
impl<Rp> Debug for dyn Method<Rp> + Send + Sync + 'static {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.to_string().as_str())
}
}
pub enum Controller<Rp, ReqDto, RespDto>
where Rp: RoutableProtocol + Debug + Send + Sync + 'static,
ReqDto: 'static,
RespDto: 'static {
Sync(fn(Request<'_, Rp, ReqDto, RespDto>) -> DceResult<Option<Response<Rp::Resp>>>),
#[cfg(feature = "async")]
Async(Box<dyn Fn(Request<'_, Rp, ReqDto, RespDto>) -> Pin<Box<dyn Future<Output = DceResult<Option<Response<Rp::Resp>>>> + Send + '_>> + Send + Sync>),
}
impl<Rp, ReqDto, RespDto> Debug for Controller<Rp, ReqDto, RespDto>
where Rp: RoutableProtocol + Debug + Send + Sync + 'static,
ReqDto: 'static,
RespDto: 'static {
fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
formatter.write_str(format!("{} controller", match &self {
Controller::Sync(_) => "A sync",
#[cfg(feature = "async")]
_ => "An async"
}).as_str())
}
}
pub enum EventHandler<Rp: RoutableProtocol + 'static> {
Sync(fn(&mut Context<Rp>) -> DceResult<()>),
#[cfg(feature = "async")]
Async(Box<dyn for <'a> Fn(&'a mut Context<Rp>) -> Pin<Box<dyn Future<Output = DceResult<()>> + Send + 'a>> + Send + Sync>),
}
impl<Rp: RoutableProtocol + 'static> Debug for EventHandler<Rp> {
fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
formatter.write_str(format!("{} function", match &self {
EventHandler::Sync(_) => "A sync",
#[cfg(feature = "async")]
_ => "An async"
}).as_str())
}
}