Skip to main content

rustapi_core/router/
match_.rs

1use crate::handler::BoxedHandler;
2use crate::path_params::PathParams;
3use http::Method;
4
5/// Result of route matching
6pub enum RouteMatch<'a> {
7    Found {
8        handler: &'a BoxedHandler,
9        params: PathParams,
10    },
11    NotFound,
12    MethodNotAllowed {
13        allowed: Vec<Method>,
14    },
15}
16
17/// Convert {param} style to :param for matchit
18pub(crate) fn convert_path_params(path: &str) -> String {
19    let mut result = String::with_capacity(path.len());
20
21    for ch in path.chars() {
22        match ch {
23            '{' => {
24                result.push(':');
25            }
26            '}' => {
27                // Skip closing brace
28            }
29            _ => {
30                result.push(ch);
31            }
32        }
33    }
34
35    result
36}
37
38/// Normalize a path for conflict comparison by replacing parameter names with a placeholder
39pub(crate) fn normalize_path_for_comparison(path: &str) -> String {
40    let mut result = String::with_capacity(path.len());
41    let mut in_param = false;
42
43    for ch in path.chars() {
44        match ch {
45            ':' => {
46                in_param = true;
47                result.push_str(":_");
48            }
49            '/' => {
50                in_param = false;
51                result.push('/');
52            }
53            _ if in_param => {
54                // Skip parameter name characters
55            }
56            _ => {
57                result.push(ch);
58            }
59        }
60    }
61
62    result
63}
64
65/// Normalize a prefix for router nesting.
66///
67/// Ensures the prefix:
68/// - Starts with exactly one leading slash
69/// - Has no trailing slash (unless it's just "/")
70/// - Has no double slashes
71///
72/// # Examples
73///
74/// ```ignore
75/// assert_eq!(normalize_prefix("api"), "/api");
76/// assert_eq!(normalize_prefix("/api"), "/api");
77/// assert_eq!(normalize_prefix("/api/"), "/api");
78/// assert_eq!(normalize_prefix("//api//"), "/api");
79/// assert_eq!(normalize_prefix(""), "/");
80/// ```
81pub(crate) fn normalize_prefix(prefix: &str) -> String {
82    // Handle empty string
83    if prefix.is_empty() {
84        return "/".to_string();
85    }
86
87    // Split by slashes and filter out empty segments (handles multiple slashes)
88    let segments: Vec<&str> = prefix.split('/').filter(|s| !s.is_empty()).collect();
89
90    // If no segments after filtering, return root
91    if segments.is_empty() {
92        return "/".to_string();
93    }
94
95    // Build the normalized prefix with leading slash
96    let mut result = String::with_capacity(prefix.len() + 1);
97    for segment in segments {
98        result.push('/');
99        result.push_str(segment);
100    }
101
102    result
103}