http_pool/
route.rs

1use std::cmp::Ordering;
2use std::collections::HashMap;
3use std::fmt::Debug;
4
5/// 路由类型
6#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Ord, Eq)]
7pub enum RouteType {
8    /// 静态路径: /home/sub
9    Static,
10    /// 动态路径: /home/:page
11    Dynamic,
12    /// 通配符: /home/*
13    Wildcard,
14}
15
16/// 路由匹配结果
17#[derive(Clone)]
18pub enum RouteMatch<T: Clone> {
19    Matched {
20        handler: T,
21        params: HashMap<String, String>,
22    },
23    NotMatched,
24}
25
26/// 路由规则
27#[derive(Clone)]
28pub struct Route<T: Clone> {
29    path: String,
30    parts: Vec<String>,
31    handler: T,
32    route_type: RouteType,
33}
34
35/// 路由
36#[derive(Clone)]
37pub struct Router<T: Clone> {
38    routes: Vec<Route<T>>,
39}
40
41impl<T: Clone> Router<T> {
42    pub fn new() -> Self {
43        Router { routes: vec![] }
44    }
45
46    /// 添加路由规则
47    /// 支持以下路由规则
48    ///     1.静态路径匹配
49    ///     2.动态路径匹配,
50    ///     3.通配符匹配
51    pub fn add(&mut self, path: impl Into<String>, handler: T) {
52        let path = path.into();
53
54        let route_type = if path.contains("*") {
55            RouteType::Wildcard
56        } else if path.contains(":") {
57            RouteType::Dynamic
58        } else {
59            RouteType::Static
60        };
61
62        let parts = path
63            .trim_matches('/')
64            .split('/')
65            .map(|s| s.to_string())
66            .collect();
67
68        self.routes.push(Route {
69            path,
70            parts,
71            handler,
72            route_type,
73        });
74
75        // 按优先级排序(Static > Dynamic > Wildcard)
76        self.routes.sort_by(|a, b| a.route_type.cmp(&b.route_type))
77    }
78
79    /// 移除路由规则
80    pub fn remove(&mut self, path: &str) -> Option<Route<T>> {
81        for i in (0..self.routes.len()).rev() {
82            if path.cmp(&self.routes[i].path) == Ordering::Equal {
83                return Some(self.routes.remove(i));
84            }
85        }
86
87        None
88    }
89
90    /// 匹配路由规则
91    /// 支持以下路由规则
92    ///     1.静态路径匹配
93    ///     2.动态路径匹配,
94    ///     3.通配符匹配
95    pub fn go_match(&self, path: &str) -> RouteMatch<T> {
96        let parts: Vec<&str> = path.trim_matches('/').split('/').collect();
97
98        for route in &self.routes {
99            let route_parts: Vec<&str> = route.parts.iter().map(|s| s as &str).collect();
100            let mut params = HashMap::new();
101
102            if route_parts.len() != parts.len()
103                && !route_parts.iter().any(|part| part.starts_with('*'))
104            {
105                continue;
106            }
107
108            let mut matched = true;
109
110            for (i, route_part) in route_parts.iter().enumerate() {
111                if *route_part == "*" || route_part.starts_with('*') {
112                    // wildcard match
113                    let key = route_part.trim_start_matches('*');
114                    params.insert(key.to_string(), parts[i..].join("/"));
115                    break;
116                } else if route_part.starts_with(':') {
117                    // dynamic match
118                    let key = route_part.trim_start_matches(':');
119                    if let Some(val) = parts.get(i) {
120                        params.insert(key.to_string(), val.to_string());
121                    } else {
122                        matched = false;
123                        break;
124                    }
125                } else if let Some(req_part) = parts.get(i) {
126                    if route_part != req_part {
127                        matched = false;
128                        break;
129                    }
130                } else {
131                    matched = false;
132                    break;
133                }
134            }
135
136            if matched {
137                return RouteMatch::Matched {
138                    handler: route.handler.clone(),
139                    params,
140                };
141            }
142        }
143
144        RouteMatch::NotMatched
145    }
146}