rivet-routing 0.1.0

Rivet framework crates and adapters.
Documentation
use std::sync::Arc;

use rivet_http::{Method, Request, Response};

pub type RouteHandler = Arc<dyn Fn(Request) -> Response + Send + Sync + 'static>;

fn default_handler(_req: Request) -> Response {
    Response::ok("ok")
}

#[derive(Clone)]
pub struct Entry {
    pub method: Method,
    pub path: String,
    handler: RouteHandler,
}

impl core::fmt::Debug for Entry {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("Entry")
            .field("method", &self.method)
            .field("path", &self.path)
            .finish()
    }
}

impl Entry {
    pub fn invoke(&self, req: Request) -> Response {
        (self.handler)(req)
    }
}

#[derive(Debug, Default, Clone)]
pub struct Registry {
    routes: Vec<Entry>,
}

#[derive(Debug, Clone)]
pub enum Match<'a> {
    Matched {
        route: &'a Entry,
        head_fallback: bool,
    },
    MethodNotAllowed {
        allow: Vec<Method>,
    },
    NotFound,
}

#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RouteRegistryError {
    #[error("duplicate route: {method:?} {path}")]
    Duplicate { method: Method, path: String },
}

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

    pub fn add_route(
        &mut self,
        method: Method,
        path: impl Into<String>,
    ) -> Result<(), RouteRegistryError> {
        self.add_route_with_handler(method, path, default_handler)
    }

    pub fn add_route_with_handler<F>(
        &mut self,
        method: Method,
        path: impl Into<String>,
        handler: F,
    ) -> Result<(), RouteRegistryError>
    where
        F: Fn(Request) -> Response + Send + Sync + 'static,
    {
        let path = path.into();

        if self
            .routes
            .iter()
            .any(|r| r.method == method && r.path == path)
        {
            return Err(RouteRegistryError::Duplicate { method, path });
        }

        self.routes.push(Entry {
            method,
            path,
            handler: Arc::new(handler),
        });
        Ok(())
    }

    pub fn get<F>(&mut self, path: impl Into<String>, handler: F) -> Result<(), RouteRegistryError>
    where
        F: Fn(Request) -> Response + Send + Sync + 'static,
    {
        self.add_route_with_handler(Method::Get, path, handler)
    }

    pub fn post<F>(&mut self, path: impl Into<String>, handler: F) -> Result<(), RouteRegistryError>
    where
        F: Fn(Request) -> Response + Send + Sync + 'static,
    {
        self.add_route_with_handler(Method::Post, path, handler)
    }

    pub fn put<F>(&mut self, path: impl Into<String>, handler: F) -> Result<(), RouteRegistryError>
    where
        F: Fn(Request) -> Response + Send + Sync + 'static,
    {
        self.add_route_with_handler(Method::Put, path, handler)
    }

    pub fn patch<F>(
        &mut self,
        path: impl Into<String>,
        handler: F,
    ) -> Result<(), RouteRegistryError>
    where
        F: Fn(Request) -> Response + Send + Sync + 'static,
    {
        self.add_route_with_handler(Method::Patch, path, handler)
    }

    pub fn delete<F>(
        &mut self,
        path: impl Into<String>,
        handler: F,
    ) -> Result<(), RouteRegistryError>
    where
        F: Fn(Request) -> Response + Send + Sync + 'static,
    {
        self.add_route_with_handler(Method::Delete, path, handler)
    }

    pub fn head<F>(&mut self, path: impl Into<String>, handler: F) -> Result<(), RouteRegistryError>
    where
        F: Fn(Request) -> Response + Send + Sync + 'static,
    {
        self.add_route_with_handler(Method::Head, path, handler)
    }

    pub fn options<F>(
        &mut self,
        path: impl Into<String>,
        handler: F,
    ) -> Result<(), RouteRegistryError>
    where
        F: Fn(Request) -> Response + Send + Sync + 'static,
    {
        self.add_route_with_handler(Method::Options, path, handler)
    }

    pub fn routes(&self) -> &[Entry] {
        &self.routes
    }

    pub fn match_request(&self, method: &Method, path: &str) -> Match<'_> {
        let path_matches: Vec<&Entry> = self.routes.iter().filter(|r| r.path == path).collect();

        if path_matches.is_empty() {
            return Match::NotFound;
        }

        if let Some(route) = path_matches.iter().find(|r| r.method == *method) {
            return Match::Matched {
                route,
                head_fallback: false,
            };
        }

        if *method == Method::Head {
            if let Some(route) = path_matches.iter().find(|r| r.method == Method::Get) {
                return Match::Matched {
                    route,
                    head_fallback: true,
                };
            }
        }

        let mut allow = Vec::new();
        for route in path_matches {
            if !allow.contains(&route.method) {
                allow.push(route.method.clone());
            }
        }

        Match::MethodNotAllowed { allow }
    }

    pub fn len(&self) -> usize {
        self.routes.len()
    }

    pub fn is_empty(&self) -> bool {
        self.routes.is_empty()
    }
}