ruled_router/parser/
path.rs1use crate::error::{ParseError, ParseResult};
6use crate::utils::{split_path_segments, url_decode};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
13pub struct PathParser {
14 pattern: String,
16 pattern_segments: Vec<PathSegment>,
18}
19
20#[derive(Debug, Clone, PartialEq)]
22pub enum PathSegment {
23 Literal(String),
25 Parameter(String),
27 OptionalParameter(String),
29 Wildcard(String),
31}
32
33impl PathParser {
34 pub fn new(pattern: &str) -> ParseResult<Self> {
52 let pattern_segments = Self::parse_pattern(pattern)?;
53 Ok(Self {
54 pattern: pattern.to_string(),
55 pattern_segments,
56 })
57 }
58
59 fn parse_pattern(pattern: &str) -> ParseResult<Vec<PathSegment>> {
61 let segments = split_path_segments(pattern);
62 let mut parsed_segments = Vec::new();
63
64 for segment in segments {
65 if segment.contains("?:") && segment.starts_with(':') {
67 let parts: Vec<&str> = segment.split("?:").collect();
69 if parts.len() == 2 {
70 let param_name = parts[0].strip_prefix(':').unwrap();
72 if param_name.is_empty() {
73 return Err(ParseError::invalid_path("Parameter must have a name"));
74 }
75 parsed_segments.push(PathSegment::Parameter(param_name.to_string()));
76
77 let optional_name = parts[1];
79 if optional_name.is_empty() {
80 return Err(ParseError::invalid_path("Optional parameter must have a name"));
81 }
82 parsed_segments.push(PathSegment::OptionalParameter(optional_name.to_string()));
83 continue;
84 }
85 }
86
87 let parsed_segment = if let Some(name) = segment.strip_prefix('*') {
88 if name.is_empty() {
90 return Err(ParseError::invalid_path("Wildcard segment must have a name"));
91 }
92 PathSegment::Wildcard(name.to_string())
93 } else if let Some(name) = segment.strip_prefix("?:") {
94 if name.is_empty() {
96 return Err(ParseError::invalid_path("Optional parameter must have a name"));
97 }
98 PathSegment::OptionalParameter(name.to_string())
99 } else if let Some(name) = segment.strip_prefix(':') {
100 if name.is_empty() {
102 return Err(ParseError::invalid_path("Parameter must have a name"));
103 }
104 PathSegment::Parameter(name.to_string())
105 } else if segment.starts_with('{') && segment.ends_with('}') {
106 let name = &segment[1..segment.len() - 1];
108 if name.is_empty() {
109 return Err(ParseError::invalid_path("Parameter must have a name"));
110 }
111 PathSegment::Parameter(name.to_string())
112 } else {
113 PathSegment::Literal(segment.to_string())
115 };
116
117 parsed_segments.push(parsed_segment);
118 }
119
120 Ok(parsed_segments)
121 }
122
123 pub fn match_path(&self, path: &str) -> ParseResult<HashMap<String, String>> {
143 let path_segments = split_path_segments(path);
144 let mut params = HashMap::new();
145 let mut path_index = 0;
146
147 for (pattern_index, pattern_segment) in self.pattern_segments.iter().enumerate() {
148 match pattern_segment {
149 PathSegment::Literal(expected) => {
150 if path_index >= path_segments.len() {
151 return Err(ParseError::segment_count_mismatch(self.pattern_segments.len(), path_segments.len()));
152 }
153
154 let actual = path_segments[path_index];
155 if actual != expected {
156 return Err(ParseError::segment_mismatch(expected.clone(), actual.to_string(), pattern_index));
157 }
158 path_index += 1;
159 }
160 PathSegment::Parameter(name) => {
161 if path_index >= path_segments.len() {
162 return Err(ParseError::missing_parameter(name.clone()));
163 }
164
165 let value = url_decode(path_segments[path_index])?;
166 params.insert(name.clone(), value);
167 path_index += 1;
168 }
169 PathSegment::OptionalParameter(name) => {
170 if path_index < path_segments.len() {
171 let value = url_decode(path_segments[path_index])?;
172 params.insert(name.clone(), value);
173 path_index += 1;
174 }
175 }
177 PathSegment::Wildcard(name) => {
178 let remaining_segments: Vec<String> = path_segments[path_index..]
180 .iter()
181 .map(|s| url_decode(s))
182 .collect::<ParseResult<Vec<_>>>()?;
183
184 let wildcard_path = remaining_segments.join("/");
185 params.insert(name.clone(), wildcard_path);
186
187 path_index = path_segments.len();
189 break;
190 }
191 }
192 }
193
194 if path_index < path_segments.len() {
196 return Err(ParseError::segment_count_mismatch(self.pattern_segments.len(), path_segments.len()));
197 }
198
199 Ok(params)
200 }
201
202 pub fn format_path(&self, params: &HashMap<String, String>) -> ParseResult<String> {
227 let mut segments = Vec::new();
228
229 for segment in &self.pattern_segments {
230 match segment {
231 PathSegment::Literal(literal) => {
232 segments.push(literal.clone());
233 }
234 PathSegment::Parameter(name) => {
235 let value = params.get(name).ok_or_else(|| ParseError::missing_parameter(name.clone()))?;
236 segments.push(crate::utils::url_encode(value));
237 }
238 PathSegment::OptionalParameter(name) => {
239 if let Some(value) = params.get(name) {
240 segments.push(crate::utils::url_encode(value));
241 }
242 }
243 PathSegment::Wildcard(name) => {
244 let value = params.get(name).ok_or_else(|| ParseError::missing_parameter(name.clone()))?;
245 segments.push(crate::utils::url_encode(value));
247 }
248 }
249 }
250
251 if segments.is_empty() {
252 Ok("/".to_string())
253 } else {
254 Ok(format!("/{}", segments.join("/")))
255 }
256 }
257
258 pub fn pattern(&self) -> &str {
260 &self.pattern
261 }
262
263 pub fn segments(&self) -> &[PathSegment] {
265 &self.pattern_segments
266 }
267
268 pub fn has_wildcard(&self) -> bool {
270 self.pattern_segments.iter().any(|s| matches!(s, PathSegment::Wildcard(_)))
271 }
272
273 pub fn parameter_names(&self) -> Vec<&str> {
275 self
276 .pattern_segments
277 .iter()
278 .filter_map(|s| match s {
279 PathSegment::Parameter(name) | PathSegment::OptionalParameter(name) | PathSegment::Wildcard(name) => Some(name.as_str()),
280 PathSegment::Literal(_) => None,
281 })
282 .collect()
283 }
284
285 pub fn consumed_length(&self, path: &str) -> ParseResult<usize> {
295 let path_segments = split_path_segments(path);
296 let mut consumed_segments = 0;
297
298 for pattern_segment in &self.pattern_segments {
299 match pattern_segment {
300 PathSegment::Literal(_) | PathSegment::Parameter(_) => {
301 if consumed_segments >= path_segments.len() {
302 break;
303 }
304 consumed_segments += 1;
305 }
306 PathSegment::OptionalParameter(_) => {
307 if consumed_segments < path_segments.len() {
308 consumed_segments += 1;
309 }
310 }
311 PathSegment::Wildcard(_) => {
312 consumed_segments = path_segments.len();
314 break;
315 }
316 }
317 }
318
319 if consumed_segments == 0 {
321 return Ok(0);
322 }
323
324 let consumed_path_segments = &path_segments[..consumed_segments];
325 let consumed_length = if consumed_path_segments.is_empty() {
326 0
327 } else {
328 let segments_length: usize = consumed_path_segments.iter().map(|s| s.len()).sum();
330 let separators_length = consumed_segments; segments_length + separators_length
332 };
333
334 Ok(consumed_length)
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn test_parse_simple_pattern() {
344 let parser = PathParser::new("/user/:id").unwrap();
345 assert_eq!(parser.pattern(), "/user/:id");
346
347 let segments = parser.segments();
348 assert_eq!(segments.len(), 2);
349 assert_eq!(segments[0], PathSegment::Literal("user".to_string()));
350 assert_eq!(segments[1], PathSegment::Parameter("id".to_string()));
351 }
352
353 #[test]
354 fn test_parse_complex_pattern() {
355 let parser = PathParser::new("/api/:version/users/:id?:format/*path").unwrap();
356
357 let segments = parser.segments();
358 assert_eq!(segments.len(), 6);
359 assert_eq!(segments[0], PathSegment::Literal("api".to_string()));
360 assert_eq!(segments[1], PathSegment::Parameter("version".to_string()));
361 assert_eq!(segments[2], PathSegment::Literal("users".to_string()));
362 assert_eq!(segments[3], PathSegment::Parameter("id".to_string()));
363 assert_eq!(segments[4], PathSegment::OptionalParameter("format".to_string()));
364 assert_eq!(segments[5], PathSegment::Wildcard("path".to_string()));
365 }
366
367 #[test]
368 fn test_match_simple_path() {
369 let parser = PathParser::new("/user/:id").unwrap();
370 let params = parser.match_path("/user/123").unwrap();
371
372 assert_eq!(params.get("id"), Some(&"123".to_string()));
373 }
374
375 #[test]
376 fn test_match_complex_path() {
377 let parser = PathParser::new("/api/:version/users/:id").unwrap();
378 let params = parser.match_path("/api/v1/users/456").unwrap();
379
380 assert_eq!(params.get("version"), Some(&"v1".to_string()));
381 assert_eq!(params.get("id"), Some(&"456".to_string()));
382 }
383
384 #[test]
385 fn test_match_with_optional() {
386 let parser = PathParser::new("/user/:id?:format").unwrap();
387
388 let params = parser.match_path("/user/123/json").unwrap();
390 assert_eq!(params.get("id"), Some(&"123".to_string()));
391 assert_eq!(params.get("format"), Some(&"json".to_string()));
392
393 let params = parser.match_path("/user/123").unwrap();
395 assert_eq!(params.get("id"), Some(&"123".to_string()));
396 assert_eq!(params.get("format"), None);
397 }
398
399 #[test]
400 fn test_match_with_wildcard() {
401 let parser = PathParser::new("/files/*path").unwrap();
402 let params = parser.match_path("/files/docs/readme.txt").unwrap();
403
404 assert_eq!(params.get("path"), Some(&"docs/readme.txt".to_string()));
405 }
406
407 #[test]
408 fn test_format_path() {
409 let parser = PathParser::new("/user/:id/profile").unwrap();
410 let mut params = HashMap::new();
411 params.insert("id".to_string(), "123".to_string());
412
413 let path = parser.format_path(¶ms).unwrap();
414 assert_eq!(path, "/user/123/profile");
415 }
416
417 #[test]
418 fn test_match_errors() {
419 let parser = PathParser::new("/user/:id").unwrap();
420
421 assert!(parser.match_path("/user").is_err());
423 assert!(parser.match_path("/user/123/extra").is_err());
424
425 assert!(parser.match_path("/admin/123").is_err());
427 }
428
429 #[test]
430 fn test_parameter_names() {
431 let parser = PathParser::new("/api/:version/users/:id?:format/*path").unwrap();
432 let names = parser.parameter_names();
433
434 assert_eq!(names, vec!["version", "id", "format", "path"]);
435 }
436
437 #[test]
438 fn test_has_wildcard() {
439 let parser1 = PathParser::new("/user/:id").unwrap();
440 assert!(!parser1.has_wildcard());
441
442 let parser2 = PathParser::new("/files/*path").unwrap();
443 assert!(parser2.has_wildcard());
444 }
445}