ruled_router/parser/
path.rs

1//! 路径解析器
2//!
3//! 提供路径模式匹配和参数提取功能
4
5use crate::error::{ParseError, ParseResult};
6use crate::utils::{split_path_segments, url_decode};
7use std::collections::HashMap;
8
9/// 路径解析器
10///
11/// 负责解析路径模式并从实际路径中提取参数
12#[derive(Debug, Clone)]
13pub struct PathParser {
14  /// 路径模式,例如 "/user/:id/profile"
15  pattern: String,
16  /// 解析后的模式段
17  pattern_segments: Vec<PathSegment>,
18}
19
20/// 路径段类型
21#[derive(Debug, Clone, PartialEq)]
22pub enum PathSegment {
23  /// 字面量段,例如 "user"
24  Literal(String),
25  /// 参数段,例如 ":id"
26  Parameter(String),
27  /// 可选参数段,例如 "?:optional"
28  OptionalParameter(String),
29  /// 通配符段,例如 "*path"
30  Wildcard(String),
31}
32
33impl PathParser {
34  /// 创建新的路径解析器
35  ///
36  /// # 参数
37  ///
38  /// * `pattern` - 路径模式字符串
39  ///
40  /// # 返回值
41  ///
42  /// 解析器实例,如果模式无效则返回错误
43  ///
44  /// # 示例
45  ///
46  /// ```rust
47  /// use ruled_router::parser::PathParser;
48  ///
49  /// let parser = PathParser::new("/user/:id/profile").unwrap();
50  /// ```
51  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  /// 解析路径模式
60  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      // 处理复合段,如 ":id?:format"
66      if segment.contains("?:") && segment.starts_with(':') {
67        // 分割复合段
68        let parts: Vec<&str> = segment.split("?:").collect();
69        if parts.len() == 2 {
70          // 第一部分是必需参数
71          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          // 第二部分是可选参数
78          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        // 通配符段
89        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        // 可选参数段 (?:name)
95        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        // 冒号参数段 (:name)
101        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        // 大括号参数段 ({name})
107        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        // 字面量段
114        PathSegment::Literal(segment.to_string())
115      };
116
117      parsed_segments.push(parsed_segment);
118    }
119
120    Ok(parsed_segments)
121  }
122
123  /// 匹配路径并提取参数
124  ///
125  /// # 参数
126  ///
127  /// * `path` - 要匹配的路径字符串
128  ///
129  /// # 返回值
130  ///
131  /// 提取的参数映射,如果匹配失败则返回错误
132  ///
133  /// # 示例
134  ///
135  /// ```rust
136  /// use ruled_router::parser::PathParser;
137  ///
138  /// let parser = PathParser::new("/user/:id/profile").unwrap();
139  /// let params = parser.match_path("/user/123/profile").unwrap();
140  /// assert_eq!(params.get("id"), Some(&"123".to_string()));
141  /// ```
142  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          // 可选参数,如果没有对应的路径段也不报错
176        }
177        PathSegment::Wildcard(name) => {
178          // 通配符匹配剩余的所有段
179          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          // 通配符消耗所有剩余段
188          path_index = path_segments.len();
189          break;
190        }
191      }
192    }
193
194    // 检查是否还有未匹配的路径段
195    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  /// 格式化路径
203  ///
204  /// 根据参数映射生成路径字符串
205  ///
206  /// # 参数
207  ///
208  /// * `params` - 参数映射
209  ///
210  /// # 返回值
211  ///
212  /// 格式化后的路径字符串
213  ///
214  /// # 示例
215  ///
216  /// ```rust
217  /// use ruled_router::parser::PathParser;
218  /// use std::collections::HashMap;
219  ///
220  /// let parser = PathParser::new("/user/:id/profile").unwrap();
221  /// let mut params = HashMap::new();
222  /// params.insert("id".to_string(), "123".to_string());
223  /// let path = parser.format_path(&params).unwrap();
224  /// assert_eq!(path, "/user/123/profile");
225  /// ```
226  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          // 通配符值可能包含多个段,用 '/' 分隔
246          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  /// 获取路径模式
259  pub fn pattern(&self) -> &str {
260    &self.pattern
261  }
262
263  /// 获取模式段
264  pub fn segments(&self) -> &[PathSegment] {
265    &self.pattern_segments
266  }
267
268  /// 检查模式是否包含通配符
269  pub fn has_wildcard(&self) -> bool {
270    self.pattern_segments.iter().any(|s| matches!(s, PathSegment::Wildcard(_)))
271  }
272
273  /// 获取所有参数名
274  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  /// 计算路径消费的长度
286  ///
287  /// # 参数
288  ///
289  /// * `path` - 要匹配的路径字符串
290  ///
291  /// # 返回值
292  ///
293  /// 当前模式消费的路径长度
294  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          // 通配符消耗所有剩余段
313          consumed_segments = path_segments.len();
314          break;
315        }
316      }
317    }
318
319    // 计算实际消费的字符长度
320    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      // 计算消费的字符数:段长度 + 分隔符
329      let segments_length: usize = consumed_path_segments.iter().map(|s| s.len()).sum();
330      let separators_length = consumed_segments; // 每个段前面有一个 '/'
331      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    // 有可选参数
389    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    // 没有可选参数
394    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(&params).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    // 段数不匹配
422    assert!(parser.match_path("/user").is_err());
423    assert!(parser.match_path("/user/123/extra").is_err());
424
425    // 字面量不匹配
426    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}