routing-match 0.1.0

A simple route match utils
Documentation
use std::cmp::Ordering;
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::sync::RwLock;

/// 简单的路由匹配工具

/// 路由类型
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Ord, Eq)]
pub enum RouteType {
    /// 静态路径: /home/sub
    Static,
    /// 动态路径: /home/:page
    Dynamic,
    /// 通配符: /home/*
    Wildcard,
}

/// 路由匹配结果
#[derive(Clone)]
pub enum RouteMatch<T: Clone> {
    Matched {
        path: String,
        handler: T,
        params: HashMap<String, String>,
    },
    NotMatched,
}

/// 一条路由规则
#[derive(Clone)]
pub struct Route<T: Clone> {
    pub path: String,
    pub parts: Vec<String>,
    pub handler: T,
    pub route_type: RouteType,
}

/// 路由
pub struct Router<T: Clone> {
    routes: RwLock<Vec<Route<T>>>,
}

impl<T: Clone> Router<T> {
    pub fn new() -> Self {
        Router {
            routes: RwLock::new(vec![]),
        }
    }

    /// 添加路由规则
    /// 支持以下路由规则
    ///     1.静态路径匹配
    ///     2.动态路径匹配,
    ///     3.通配符匹配
    pub fn add(&self, path: impl Into<String>, handler: T) {
        let path = path.into();

        let route_type = if path.contains("*") {
            RouteType::Wildcard
        } else if path.contains(":") {
            RouteType::Dynamic
        } else {
            RouteType::Static
        };

        let parts = path
            .trim_matches('/')
            .split('/')
            .map(|s| s.to_string())
            .collect();

        let mut wg = self.routes.write().unwrap();
        wg.push(Route {
            path,
            parts,
            handler,
            route_type,
        });

        // 按优先级排序(Static > Dynamic > Wildcard)
        wg.sort_by(|a, b| a.route_type.cmp(&b.route_type))
    }

    /// 移除路由规则
    pub fn remove(&self, path: &str) -> Option<Route<T>> {
        let mut wg = self.routes.write().unwrap();
        for i in (0..wg.len()).rev() {
            if path.cmp(&wg[i].path) == Ordering::Equal {
                return Some(wg.remove(i));
            }
        }

        None
    }

    /// 匹配路由规则
    /// 支持以下路由规则
    ///     1.静态路径匹配
    ///     2.动态路径匹配,
    ///     3.通配符匹配
    pub fn go_match(&self, path: &str) -> RouteMatch<T> {
        let parts: Vec<&str> = path.trim_matches('/').split('/').collect();

        // 拷贝一份
        let routes = {
            let wg = self.routes.read().unwrap();
            let routes: Vec<Route<T>> = wg.clone();
            routes
        };

        for route in &routes {
            let route_parts: Vec<&str> = route.parts.iter().map(|s| s as &str).collect();
            let mut params = HashMap::new();

            if route_parts.len() != parts.len()
                && !route_parts.iter().any(|part| part.starts_with('*'))
            {
                continue;
            }

            let mut matched = true;

            for (i, route_part) in route_parts.iter().enumerate() {
                if *route_part == "*" || route_part.starts_with('*') {
                    // wildcard match
                    let key = route_part.trim_start_matches('*');
                    params.insert(key.to_string(), parts[i..].join("/"));
                    break;
                } else if route_part.starts_with(':') {
                    // dynamic match
                    let key = route_part.trim_start_matches(':');
                    if let Some(val) = parts.get(i) {
                        params.insert(key.to_string(), val.to_string());
                    } else {
                        matched = false;
                        break;
                    }
                } else if let Some(req_part) = parts.get(i) {
                    if route_part != req_part {
                        matched = false;
                        break;
                    }
                } else {
                    matched = false;
                    break;
                }
            }

            if matched {
                return RouteMatch::Matched {
                    path: route.path.clone(),
                    handler: route.handler.clone(),
                    params,
                };
            }
        }

        RouteMatch::NotMatched
    }
}

impl<T: Clone> Debug for RouteMatch<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            RouteMatch::Matched {
                path,
                handler: _,
                params,
            } => f
                .debug_struct("Matched")
                .field("path", path)
                .field("params", params)
                .finish(),
            RouteMatch::NotMatched => f.write_str("NotMatched"),
        }
    }
}

impl<T: Clone> Debug for Route<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Route")
            .field("path", &self.path)
            .field("parts", &self.parts)
            .field("route_type", &self.route_type)
            .finish()
    }
}

#[test]
fn test() {
    let r = Router::<()>::new();
    // static
    r.add("/static", ());
    // wildcard
    r.add("/home/*", ());
    // Dynamic
    r.add("/dynamic/:page", ());
    
    println!("{:?}", r.go_match("/static"));
    println!("{:?}", r.go_match("/static2"));
    println!("{:?}", r.go_match("/dynamic/wo"));
    println!("{:?}", r.go_match("/home/path1/path2"));
    println!("{:?}", r.remove("/home"));
    println!("{:?}", r.remove("/home/*"));
}