use actix_web::http::Method;
use actix_web::web::ServiceConfig;
use actix_web::{HttpResponse, Route};
use alux_http::{
HttpMethod, HttpSelectorAlg, PathSyntaxAlg, RouteAlg, RoutePath, SelectorAlg, compose_path, describe_path,
};
use std::collections::BTreeMap;
use std::sync::Arc;
struct ActixPath;
impl PathSyntaxAlg for ActixPath {
fn param(&self, name: &str) -> String {
format!("{{{name}}}")
}
fn tail(&self, name: &str) -> String {
format!("{{{name}:.*}}")
}
}
fn actix_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 ActixSelectorPart {
Method(HttpMethod),
Path(RoutePath),
Prefix(RoutePath),
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ActixSelector {
parts: Vec<ActixSelectorPart>,
}
impl ActixSelector {
pub fn path(&self) -> String {
describe_path(self.paths())
}
pub(crate) fn actix_path(&self) -> String {
compose_path(self.paths(), &ActixPath)
}
fn paths(&self) -> impl Iterator<Item = &RoutePath> {
self.parts.iter().filter_map(|part| match part {
ActixSelectorPart::Path(path) | ActixSelectorPart::Prefix(path) => Some(path),
ActixSelectorPart::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 {
ActixSelectorPart::Method(method) => Some(*method),
ActixSelectorPart::Path(_) | ActixSelectorPart::Prefix(_) => None,
})
}
}
#[derive(Clone)]
pub struct ActixEndpoint(Arc<dyn Fn() -> Route + Send + Sync>);
impl ActixEndpoint {
pub fn new<Make>(make: Make) -> Self
where
Make: Fn() -> Route + Send + Sync + 'static,
{
Self(Arc::new(make))
}
}
#[derive(Clone)]
struct ActixRouteEntry {
selector: ActixSelector,
endpoint: ActixEndpoint,
}
#[derive(Clone, Default)]
pub struct ActixRoute {
entries: Vec<ActixRouteEntry>,
}
impl ActixRoute {
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_actix(self) -> impl Fn(&mut ServiceConfig) + Clone + Send + 'static {
let mut paths = BTreeMap::<String, Vec<(Option<HttpMethod>, ActixEndpoint)>>::new();
for entry in self.entries {
paths.entry(entry.selector.actix_path()).or_default().push((entry.selector.method(), entry.endpoint));
}
move |service| {
for (path, entries) in &paths {
for (method, endpoint) in entries {
let route = (endpoint.0)();
let route = match method {
Some(method) => route.method(actix_method(*method)),
None => route,
};
service.route(path, route);
}
}
}
}
}
pub fn not_found() -> HttpResponse {
HttpResponse::NotFound().finish()
}
#[derive(Debug, Default)]
pub struct ActixRouteImpl;
impl SelectorAlg for ActixRouteImpl {
type Selector = ActixSelector;
fn identity(&self) -> ActixSelector {
ActixSelector::default()
}
fn compose(&self, mut first: ActixSelector, second: ActixSelector) -> ActixSelector {
first.parts.extend(second.parts);
first
}
}
impl RouteAlg for ActixRouteImpl {
type Route = ActixRoute;
type Selector = ActixSelector;
type Endpoint = ActixEndpoint;
fn initial(&self) -> ActixRoute {
ActixRoute::default()
}
fn coproduct(&self, mut left: ActixRoute, right: ActixRoute) -> ActixRoute {
left.entries.extend(right.entries);
left
}
fn precompose(&self, selector: ActixSelector, mut route: ActixRoute) -> ActixRoute {
for entry in &mut route.entries {
entry.selector = self.compose(selector.clone(), core::mem::take(&mut entry.selector));
}
route
}
fn lift(&self, endpoint: ActixEndpoint) -> ActixRoute {
ActixRoute { entries: vec![ActixRouteEntry { selector: self.identity(), endpoint }] }
}
}
impl HttpSelectorAlg for ActixRouteImpl {
type Selector = ActixSelector;
fn http_method(&self, method: HttpMethod) -> ActixSelector {
ActixSelector { parts: vec![ActixSelectorPart::Method(method)] }
}
fn http_path(&self, path: &RoutePath) -> ActixSelector {
ActixSelector { parts: vec![ActixSelectorPart::Path(path.clone())] }
}
fn http_prefix(&self, prefix: &RoutePath) -> ActixSelector {
ActixSelector { parts: vec![ActixSelectorPart::Prefix(prefix.clone())] }
}
}