vantus 0.2.0

Macro-first async Rust web platform with typed extraction, DI, and configuration binding.
Documentation
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

use crate::app::{Config, Service, ServiceContainer, ServiceScope};
use crate::config::{Configuration, FromConfiguration};
use crate::core::errors::FrameworkError;
use crate::core::http::{Method, Request, Response};
use crate::middleware::Middleware;

pub type HandlerFuture = Pin<Box<dyn Future<Output = HandlerResult> + Send>>;
pub type HandlerResult = Result<Response, FrameworkError>;

#[derive(Clone)]
pub struct Handler(Arc<dyn Fn(RequestContext) -> HandlerFuture + Send + Sync>);

impl Handler {
    pub fn new<F, Fut>(handler: F) -> Self
    where
        F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
        Fut: Future<Output = HandlerResult> + Send + 'static,
    {
        Self(Arc::new(move |ctx| Box::pin(handler(ctx))))
    }

    pub async fn call(&self, ctx: RequestContext) -> HandlerResult {
        (self.0)(ctx).await
    }
}

pub trait RouteRegistrar {
    fn add_route(&mut self, definition: RouteDefinition) -> Result<(), FrameworkError>;
}

pub trait RouteRegistration {
    fn register_definition(&mut self, definition: RouteDefinition) -> &mut Self;
}

#[derive(Clone)]
pub struct RequestContext {
    request: Request,
    path_params: HashMap<String, String>,
    scope: Arc<ServiceScope>,
    configuration: Arc<Configuration>,
}

impl RequestContext {
    pub fn new(
        request: Request,
        path_params: HashMap<String, String>,
        services: Arc<ServiceContainer>,
        configuration: Arc<Configuration>,
    ) -> Self {
        let scope = Arc::new(services.create_scope());
        Self {
            request,
            path_params,
            scope,
            configuration,
        }
    }

    pub fn request(&self) -> &Request {
        &self.request
    }

    pub fn path_params(&self) -> &HashMap<String, String> {
        &self.path_params
    }

    pub fn scope(&self) -> Arc<ServiceScope> {
        Arc::clone(&self.scope)
    }

    pub fn service<T>(&self) -> Result<Service<T>, FrameworkError>
    where
        T: Send + Sync + 'static,
    {
        self.scope
            .resolve::<T>()
            .map(Service::from)
            .map_err(FrameworkError::from)
    }

    pub fn config<T>(&self) -> Result<Config<T>, FrameworkError>
    where
        T: Send + Sync + 'static,
    {
        self.scope
            .resolve::<T>()
            .map(Config::from)
            .map_err(FrameworkError::from)
    }

    pub fn configuration(&self) -> &Configuration {
        self.configuration.as_ref()
    }

    pub fn bind_config<T>(&self) -> Result<T, FrameworkError>
    where
        T: FromConfiguration,
    {
        T::from_configuration(self.configuration.as_ref()).map_err(FrameworkError::from)
    }
}

pub struct Router {
    routes: Vec<Route>,
}

impl Router {
    pub fn new() -> Self {
        Self { routes: Vec::new() }
    }

    pub fn add_definition(&mut self, definition: RouteDefinition) {
        self.ensure_unique_route(&definition.method, &definition.path);
        self.routes.push(Route::from_definition(definition));
    }

    pub fn route(&self, method: &Method, path: &str) -> Option<RouteMatch> {
        for route in &self.routes {
            if &route.method != method {
                continue;
            }

            if let Some(path_params) = route.template.match_path(path) {
                return Some(RouteMatch {
                    handler: route.handler.clone(),
                    middleware: route.middleware.clone(),
                    path_params,
                });
            }
        }
        None
    }

    fn ensure_unique_route(&self, method: &Method, template: &str) {
        if self
            .routes
            .iter()
            .any(|route| &route.method == method && route.template.raw == template)
        {
            panic!("duplicate route registration for {method} {template}");
        }
    }
}

impl Default for Router {
    fn default() -> Self {
        Self::new()
    }
}

impl RouteRegistrar for Router {
    fn add_route(&mut self, definition: RouteDefinition) -> Result<(), FrameworkError> {
        self.add_definition(definition);
        Ok(())
    }
}

#[derive(Clone)]
pub struct RouteDefinition {
    pub method: Method,
    pub path: String,
    pub handler: Handler,
    pub middleware: Vec<Arc<dyn Middleware>>,
}

impl RouteDefinition {
    pub fn new(method: Method, path: impl Into<String>, handler: Handler) -> Self {
        Self {
            method,
            path: path.into(),
            handler,
            middleware: Vec::new(),
        }
    }

    pub fn with_middleware(mut self, middleware: Vec<Arc<dyn Middleware>>) -> Self {
        self.middleware = middleware;
        self
    }
}

#[derive(Clone)]
pub struct RouteMatch {
    pub handler: Handler,
    pub middleware: Vec<Arc<dyn Middleware>>,
    pub path_params: HashMap<String, String>,
}

struct Route {
    method: Method,
    template: PathTemplate,
    handler: Handler,
    middleware: Vec<Arc<dyn Middleware>>,
}

impl Route {
    fn from_definition(definition: RouteDefinition) -> Self {
        Self {
            method: definition.method,
            template: PathTemplate::new(definition.path),
            handler: definition.handler,
            middleware: definition.middleware,
        }
    }
}

struct PathTemplate {
    raw: String,
    segments: Vec<PathSegment>,
}

impl PathTemplate {
    fn new(raw: String) -> Self {
        let segments = raw
            .trim_matches('/')
            .split('/')
            .filter(|segment| !segment.is_empty())
            .map(PathSegment::from)
            .collect();
        Self { raw, segments }
    }

    fn match_path(&self, candidate: &str) -> Option<HashMap<String, String>> {
        let candidate_segments: Vec<&str> = candidate
            .trim_matches('/')
            .split('/')
            .filter(|segment| !segment.is_empty())
            .collect();

        if self.segments.len() != candidate_segments.len() {
            return None;
        }

        let mut params = HashMap::new();
        for (segment, candidate) in self.segments.iter().zip(candidate_segments.iter()) {
            match segment {
                PathSegment::Literal(value) if value != candidate => return None,
                PathSegment::Literal(_) => {}
                PathSegment::Param(name) => {
                    params.insert(name.clone(), (*candidate).to_string());
                }
            }
        }

        Some(params)
    }
}

enum PathSegment {
    Literal(String),
    Param(String),
}

impl From<&str> for PathSegment {
    fn from(value: &str) -> Self {
        if value.starts_with('{') && value.ends_with('}') {
            Self::Param(value.trim_matches(|c| c == '{' || c == '}').to_string())
        } else {
            Self::Literal(value.to_string())
        }
    }
}