1use regex::Regex;
4
5use crate::error::Result;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum McpType {
10 #[default]
12 Tool,
13 Resource,
15 Skip,
17}
18
19#[derive(Debug, Clone)]
21pub struct RouteRule {
22 pub methods: Vec<String>,
24 pub pattern: Option<Regex>,
26 pub mcp_type: McpType,
28 pub priority: i32,
30}
31
32impl RouteRule {
33 pub fn new(mcp_type: McpType) -> Self {
35 Self {
36 methods: Vec::new(),
37 pattern: None,
38 mcp_type,
39 priority: 0,
40 }
41 }
42
43 #[must_use]
45 pub fn methods<I, S>(mut self, methods: I) -> Self
46 where
47 I: IntoIterator<Item = S>,
48 S: Into<String>,
49 {
50 self.methods = methods.into_iter().map(Into::into).collect();
51 self
52 }
53
54 pub fn pattern(mut self, pattern: &str) -> Result<Self> {
56 self.pattern = Some(Regex::new(pattern)?);
57 Ok(self)
58 }
59
60 #[must_use]
62 pub fn priority(mut self, priority: i32) -> Self {
63 self.priority = priority;
64 self
65 }
66
67 pub fn matches(&self, method: &str, path: &str) -> bool {
69 if !self.methods.is_empty() && !self.methods.iter().any(|m| m.eq_ignore_ascii_case(method))
71 {
72 return false;
73 }
74
75 if let Some(ref pattern) = self.pattern
77 && !pattern.is_match(path)
78 {
79 return false;
80 }
81
82 true
83 }
84}
85
86#[derive(Debug, Clone, Default)]
88pub struct RouteMapping {
89 rules: Vec<RouteRule>,
90}
91
92impl RouteMapping {
93 pub fn new() -> Self {
95 Self::default()
96 }
97
98 pub fn default_rules() -> Self {
102 Self::new()
103 .map_methods(["GET"], McpType::Resource)
104 .map_methods(["POST", "PUT", "PATCH", "DELETE"], McpType::Tool)
105 }
106
107 #[must_use]
109 pub fn map_methods<I, S>(mut self, methods: I, mcp_type: McpType) -> Self
110 where
111 I: IntoIterator<Item = S>,
112 S: Into<String>,
113 {
114 self.rules.push(RouteRule::new(mcp_type).methods(methods));
115 self
116 }
117
118 #[must_use]
120 pub fn map_method(self, method: &str, mcp_type: McpType) -> Self {
121 self.map_methods([method], mcp_type)
122 }
123
124 pub fn map_pattern(mut self, pattern: &str, mcp_type: McpType) -> Result<Self> {
126 self.rules.push(RouteRule::new(mcp_type).pattern(pattern)?);
127 Ok(self)
128 }
129
130 pub fn map_rule<I, S>(
132 mut self,
133 methods: I,
134 pattern: &str,
135 mcp_type: McpType,
136 priority: i32,
137 ) -> Result<Self>
138 where
139 I: IntoIterator<Item = S>,
140 S: Into<String>,
141 {
142 self.rules.push(
143 RouteRule::new(mcp_type)
144 .methods(methods)
145 .pattern(pattern)?
146 .priority(priority),
147 );
148 Ok(self)
149 }
150
151 #[must_use]
153 pub fn add_rule(mut self, rule: RouteRule) -> Self {
154 self.rules.push(rule);
155 self
156 }
157
158 pub fn skip_pattern(self, pattern: &str) -> Result<Self> {
160 self.map_pattern(pattern, McpType::Skip)
161 }
162
163 pub fn get_mcp_type(&self, method: &str, path: &str) -> McpType {
168 let mut sorted_rules: Vec<_> = self.rules.iter().collect();
170 sorted_rules.sort_by(|a, b| b.priority.cmp(&a.priority));
171
172 for rule in sorted_rules {
173 if rule.matches(method, path) {
174 return rule.mcp_type;
175 }
176 }
177
178 match method.to_uppercase().as_str() {
180 "GET" => McpType::Resource,
181 _ => McpType::Tool,
182 }
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_default_rules() {
192 let mapping = RouteMapping::default_rules();
193
194 assert_eq!(mapping.get_mcp_type("GET", "/users"), McpType::Resource);
195 assert_eq!(mapping.get_mcp_type("POST", "/users"), McpType::Tool);
196 assert_eq!(mapping.get_mcp_type("PUT", "/users/1"), McpType::Tool);
197 assert_eq!(mapping.get_mcp_type("DELETE", "/users/1"), McpType::Tool);
198 }
199
200 #[test]
201 fn test_pattern_matching() {
202 let mapping = RouteMapping::new()
203 .map_pattern(r"/admin/.*", McpType::Skip)
204 .unwrap()
205 .map_methods(["GET"], McpType::Resource);
206
207 assert_eq!(mapping.get_mcp_type("GET", "/admin/users"), McpType::Skip);
208 assert_eq!(mapping.get_mcp_type("GET", "/users"), McpType::Resource);
209 }
210
211 #[test]
212 fn test_priority() {
213 let mapping = RouteMapping::new()
214 .add_rule(
215 RouteRule::new(McpType::Resource)
216 .methods(["GET"])
217 .priority(0),
218 )
219 .add_rule(
220 RouteRule::new(McpType::Tool)
221 .pattern(r"/api/.*")
222 .unwrap()
223 .priority(10),
224 );
225
226 assert_eq!(mapping.get_mcp_type("GET", "/api/users"), McpType::Tool);
228 assert_eq!(mapping.get_mcp_type("GET", "/users"), McpType::Resource);
230 }
231
232 #[test]
233 fn test_route_rule_matches() {
234 let rule = RouteRule::new(McpType::Tool)
235 .methods(["POST", "PUT"])
236 .pattern(r"/users/\d+")
237 .unwrap();
238
239 assert!(rule.matches("POST", "/users/123"));
240 assert!(rule.matches("PUT", "/users/456"));
241 assert!(!rule.matches("GET", "/users/123")); assert!(!rule.matches("POST", "/users/abc")); }
244}