flyer 3.0.7

HTTP framework for rust
Documentation
use std::collections::{HashMap, HashSet};
use url_domain_parse::Url;

use crate::{
    error::Error,
    request::Request,
    response::{HTTP_INTERNAL_SERVER_ERROR, HTTP_NOT_FOUND, Response},
    routing::{
        HttpErrorHandler, HttpHandler, Middlewares, WebsocketHandler, next::Next, route::Route,
    },
    utils::{Values, url},
};

pub struct Routes {
    pub(crate) http: Vec<Route<HttpHandler>>,
    pub(crate) websocket: Vec<Route<WebsocketHandler>>,
    pub(crate) middlewares: Middlewares,
    pub(crate) errors: Vec<HttpErrorHandler>,
    pub(crate) not_found_callback: Option<HttpHandler>,
}

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

impl Routes {
    pub fn new() -> Self {
        Self {
            http: Vec::new(),
            websocket: Vec::new(),
            middlewares: HashMap::new(),
            errors: Vec::new(),
            not_found_callback: None,
        }
    }

    pub async fn handle_http(&self, req: Request, res: Response) -> (Request, Response) {
        let (req, mut res, route) = self.handler(req, res, &self.http).await;

        let Some(route) = route else {
            if res.status_code == HTTP_NOT_FOUND {
                if let Some(cb) = &self.not_found_callback {
                    res = cb(req.clone(), res).await;
                }
            }

            return (req, res);
        };

        let res = (route.handler)(req.clone(), res).await;

        (req, res)
    }

    pub async fn handle_websocket(
        &self,
        req: Request,
        res: Response,
    ) -> (Request, Response, Option<&Route<WebsocketHandler>>) {
        let (req, mut res, route) = self.handler(req, res, &self.websocket).await;

        if route.is_none() {
            res.status_code = HTTP_NOT_FOUND;
            return (req, res, None);
        }

        (req, res, route)
    }

    pub async fn handle_error(
        &self,
        error: Error,
        mut req: Request,
        mut res: Response,
    ) -> (Request, Response) {
        res.status_code = HTTP_INTERNAL_SERVER_ERROR;

        for callback in &self.errors {
            res.next(false);
            res = callback(error.clone(), req.clone(), res, Next::new()).await;

            if !res.is_next() {
                return (req, res);
            }

            req = res.request();
        }

        (req, res)
    }

    async fn resolve_middleware(
        &self,
        mut req: Request,
        mut res: Response,
        middlewares: &HashSet<String>,
    ) -> (bool, Request, Response) {
        for middleware in middlewares {
            let Some(callback) = self.middlewares.get(middleware) else {
                continue;
            };

            res.next(false);
            res = (callback.as_ref())(req.clone(), res, Next::new()).await;

            if !res.is_next() {
                return (false, req, res);
            }

            req = res.request();
        }

        (true, req, res)
    }

    async fn handler<'h, H>(
        &self,
        mut req: Request,
        mut res: Response,
        routes: &'h [Route<H>],
    ) -> (Request, Response, Option<&'h Route<H>>) {
        let req_segments = url::clean(&req.path);
        let parsed_url = self.parse_request_url(&req.host);

        for route in routes {
            let (matches, params) = self.match_route(route, &req.method, &req_segments, parsed_url.as_ref());

            if matches {
                req.parameters = params;

                let (resolved, req, res) =
                    self.resolve_middleware(req, res, &route.middlewares).await;

                if !resolved {
                    return (req, res, None);
                }

                return (req, res, Some(route));
            }
        }

        res.status_code = HTTP_NOT_FOUND;

        (req, res, None)
    }

    fn match_route<H>(
        &self,
        route: &Route<H>,
        req_method: &str,
        req_segments: &[String],
        parsed_url: Option<&Url>,
    ) -> (bool, Values) {
        if !route.method.eq_ignore_ascii_case(req_method) {
            return (false, Default::default());
        }

        let url = parsed_url.unwrap();
        let req_sub_str = url.subdomain().unwrap_or_default();

        let mut extracted_params = Vec::with_capacity(4);

        if !self.match_subdomains(&route.subdomain, &req_sub_str, &mut extracted_params) {
            return (false, Default::default());
        }

        if !self.match_path_segments(&route.path, req_segments, &mut extracted_params) {
            return (false, Default::default());
        }

        let mut parameters = Values::new();
        for (k, v) in extracted_params {
            parameters.insert(k.to_string(), v.to_string());
        }

        (true, parameters)
    }

    fn parse_request_url(&self, host: &str) -> Option<Url> {
        if host.starts_with("http://") || host.starts_with("https://") {
            Url::parse(host).ok()
        } else {
            let host_clean = host.trim_start_matches("www.");
            let mut url_buf = String::with_capacity(7 + host_clean.len());
            url_buf.push_str("http://");
            url_buf.push_str(host_clean);
            Url::parse(&url_buf).ok()
        }
    }

    fn match_subdomains<'a>(
        &self,
        route_subdomain: &'a str,
        req_sub_str: &'a str,
        parameters: &mut Vec<(&'a str, &'a str)>,
    ) -> bool {
        let mut route_subs = route_subdomain.split('.').filter(|s| !s.is_empty());
        let mut req_subs = req_sub_str.split('.').filter(|s| !s.is_empty());

        loop {
            match (route_subs.next(), req_subs.next()) {
                (Some(r_sub), Some(q_sub)) => {
                    if r_sub == q_sub {
                        continue;
                    }
                    if let Some((k, v)) = self.dynamic_parameter_match(r_sub, q_sub) {
                        parameters.push((k, v));
                    } else {
                        return false;
                    }
                }
                (None, None) => return true,
                _ => return false,
            }
        }
    }

    fn match_path_segments<'a>(
        &self,
        route_segments: &'a [String],
        req_segments: &'a [String],
        parameters: &mut Vec<(&'a str, &'a str)>,
    ) -> bool {
        let has_wildcard = route_segments.last().map_or(false, |s| s == "*");

        if !has_wildcard && route_segments.len() != req_segments.len() {
            return false;
        }

        for (route_seg, req_seg) in route_segments.iter().zip(req_segments.iter()) {
            if route_seg == "*" {
                return true;
            }

            if route_seg == req_seg {
                continue;
            }
            if let Some((k, v)) = self.dynamic_parameter_match(route_seg, req_seg) {
                parameters.push((k, v));
            } else {
                return false;
            }
        }

        true
    }

    #[inline]
    fn dynamic_parameter_match<'a>(
        &self,
        route_seg: &'a str,
        req_seg: &'a str,
    ) -> Option<(&'a str, &'a str)> {
        if route_seg.starts_with('{') && route_seg.ends_with('}') && route_seg.len() > 2 {
            return Some((&route_seg[1..route_seg.len() - 1], req_seg));
        }
        None
    }
}