use alux_http::{
HttpMethod, HttpSelectorAlg, PathSyntaxAlg, RouteAlg, RoutePath, SelectorAlg, compose_path, describe_path,
};
use core::future::Future;
use core::pin::Pin;
use salvo::http::Method;
use salvo::routing::filters::MethodFilter;
use salvo::{Depot, FlowCtrl, Handler, Request, Response, Router, async_trait};
use std::sync::Arc;
struct SalvoPath;
impl PathSyntaxAlg for SalvoPath {
fn param(&self, name: &str) -> String {
format!("{{{name}}}")
}
fn tail(&self, name: &str) -> String {
format!("{{**{name}}}")
}
}
fn salvo_method(method: HttpMethod) -> Method {
match method {
HttpMethod::Get => Method::GET,
HttpMethod::Post => Method::POST,
HttpMethod::Put => Method::PUT,
HttpMethod::Patch => Method::PATCH,
HttpMethod::Delete => Method::DELETE,
HttpMethod::Head => Method::HEAD,
HttpMethod::Options => Method::OPTIONS,
HttpMethod::Trace => Method::TRACE,
HttpMethod::Connect => Method::CONNECT,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum SalvoSelectorPart {
Method(HttpMethod),
Path(RoutePath),
Prefix(RoutePath),
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SalvoSelector {
parts: Vec<SalvoSelectorPart>,
}
impl SalvoSelector {
pub fn path(&self) -> String {
describe_path(self.paths())
}
pub(crate) fn salvo_path(&self) -> String {
compose_path(self.paths(), &SalvoPath)
}
fn paths(&self) -> impl Iterator<Item = &RoutePath> {
self.parts.iter().filter_map(|part| match part {
SalvoSelectorPart::Path(path) | SalvoSelectorPart::Prefix(path) => Some(path),
SalvoSelectorPart::Method(_) => None,
})
}
pub fn label(&self) -> String {
let method = self.method().map_or("*", HttpMethod::label);
format!("{method} {}", self.path())
}
pub(crate) fn method(&self) -> Option<HttpMethod> {
self.parts.iter().rev().find_map(|part| match part {
SalvoSelectorPart::Method(method) => Some(*method),
SalvoSelectorPart::Path(_) | SalvoSelectorPart::Prefix(_) => None,
})
}
}
type Answer<'a> = Pin<Box<dyn Future<Output = Response> + Send + 'a>>;
pub(crate) trait SalvoReachAlg: Send + Sync + 'static {
fn reach<'a>(&'a self, request: &'a mut Request) -> Answer<'a>;
}
#[derive(Clone)]
pub struct SalvoEndpoint(Arc<dyn SalvoReachAlg>);
impl SalvoEndpoint {
pub(crate) fn new(reach: impl SalvoReachAlg) -> Self {
Self(Arc::new(reach))
}
}
#[async_trait]
impl Handler for SalvoEndpoint {
async fn handle(&self, request: &mut Request, _depot: &mut Depot, response: &mut Response, _ctrl: &mut FlowCtrl) {
*response = self.0.reach(request).await;
}
}
#[derive(Clone)]
struct SalvoRouteEntry {
selector: SalvoSelector,
endpoint: SalvoEndpoint,
}
#[derive(Clone, Default)]
pub struct SalvoRoute {
entries: Vec<SalvoRouteEntry>,
}
impl SalvoRoute {
pub fn labels(&self) -> Vec<String> {
self.entries.iter().map(|entry| entry.selector.label()).collect()
}
pub fn paths(&self) -> Vec<String> {
self.entries.iter().map(|entry| entry.selector.path()).collect()
}
pub fn into_salvo(self) -> Router {
self.entries.into_iter().fold(Router::new(), |router, entry| {
let reached = Router::with_path(entry.selector.salvo_path());
let reached = match entry.selector.method() {
Some(method) => reached.filter(MethodFilter(salvo_method(method))),
None => reached,
};
router.push(reached.goal(entry.endpoint))
})
}
}
#[derive(Debug, Default)]
pub struct SalvoRouteImpl;
impl SelectorAlg for SalvoRouteImpl {
type Selector = SalvoSelector;
fn identity(&self) -> SalvoSelector {
SalvoSelector::default()
}
fn compose(&self, mut first: SalvoSelector, second: SalvoSelector) -> SalvoSelector {
first.parts.extend(second.parts);
first
}
}
impl RouteAlg for SalvoRouteImpl {
type Route = SalvoRoute;
type Selector = SalvoSelector;
type Endpoint = SalvoEndpoint;
fn initial(&self) -> SalvoRoute {
SalvoRoute::default()
}
fn coproduct(&self, mut left: SalvoRoute, right: SalvoRoute) -> SalvoRoute {
left.entries.extend(right.entries);
left
}
fn precompose(&self, selector: SalvoSelector, mut route: SalvoRoute) -> SalvoRoute {
for entry in &mut route.entries {
entry.selector = self.compose(selector.clone(), core::mem::take(&mut entry.selector));
}
route
}
fn lift(&self, endpoint: SalvoEndpoint) -> SalvoRoute {
SalvoRoute { entries: vec![SalvoRouteEntry { selector: self.identity(), endpoint }] }
}
}
impl HttpSelectorAlg for SalvoRouteImpl {
type Selector = SalvoSelector;
fn http_method(&self, method: HttpMethod) -> SalvoSelector {
SalvoSelector { parts: vec![SalvoSelectorPart::Method(method)] }
}
fn http_path(&self, path: &RoutePath) -> SalvoSelector {
SalvoSelector { parts: vec![SalvoSelectorPart::Path(path.clone())] }
}
fn http_prefix(&self, prefix: &RoutePath) -> SalvoSelector {
SalvoSelector { parts: vec![SalvoSelectorPart::Prefix(prefix.clone())] }
}
}