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}