ruled_router/
error.rs

1//! 错误类型定义
2//!
3//! 定义了路由解析过程中可能出现的各种错误类型
4
5use std::fmt;
6
7/// 路由状态枚举
8///
9/// 用于替代 Option<SubRouterMatch>,提供更明确的路由解析状态信息
10/// 支持 serde 序列化/反序列化
11#[derive(Debug, Clone, PartialEq)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13pub enum RouteState<T> {
14  /// 没有子路由(预期的叶子节点)
15  ///
16  /// 表示当前路由是一个叶子节点,不应该有子路由
17  NoSubRoute,
18
19  /// 有子路由
20  ///
21  /// 表示成功解析到子路由
22  SubRoute(T),
23
24  /// 解析失败
25  ///
26  /// 表示尝试解析子路由时失败,包含详细的调试信息
27  ParseFailed {
28    /// 剩余的路径
29    remaining_path: String,
30    /// 尝试匹配的模式列表
31    attempted_patterns: Vec<String>,
32    /// 最接近的匹配信息(可选)
33    closest_match: Option<ClosestMatch>,
34  },
35}
36
37/// 最接近的匹配信息
38///
39/// 用于提供更好的调试信息,帮助开发者理解为什么路由匹配失败
40#[derive(Debug, Clone, PartialEq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub struct ClosestMatch {
43  /// 匹配的模式
44  pub pattern: String,
45  /// 匹配的路径长度
46  pub matched_length: usize,
47  /// 失败的原因
48  pub failure_reason: String,
49}
50
51/// 路由调试信息
52///
53/// 包含路由解析过程中的详细信息,用于调试和错误报告
54#[derive(Debug, Clone, PartialEq)]
55#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
56pub struct RouteDebugInfo {
57  /// 失败的层级
58  pub failed_at_level: usize,
59  /// 已消费的路径
60  pub consumed_path: String,
61  /// 剩余的路径
62  pub remaining_path: String,
63  /// 可用的路由列表
64  pub available_routes: Vec<String>,
65  /// 建议的修复方案
66  pub suggestion: Option<String>,
67}
68
69/// 解析错误类型
70///
71/// 表示在路由解析过程中可能出现的各种错误情况
72#[derive(Debug, Clone, PartialEq)]
73pub enum ParseError {
74  /// 无效的路径格式
75  ///
76  /// 当路径不符合预期格式时返回此错误
77  InvalidPath(String),
78
79  /// 缺少必需的参数
80  ///
81  /// 当路径中缺少必需的参数时返回此错误
82  MissingParameter(String),
83
84  /// 类型转换失败
85  ///
86  /// 当无法将字符串参数转换为目标类型时返回此错误
87  TypeConversion(String),
88
89  /// 无效的查询参数
90  ///
91  /// 当查询参数格式不正确时返回此错误
92  InvalidQuery(String),
93
94  /// URL 编码/解码错误
95  ///
96  /// 当 URL 编码或解码失败时返回此错误
97  UrlEncoding(String),
98
99  /// 路径段数量不匹配
100  ///
101  /// 当实际路径段数量与模式不匹配时返回此错误
102  SegmentCountMismatch { expected: usize, actual: usize },
103
104  /// 路径段内容不匹配
105  ///
106  /// 当路径段的字面量内容不匹配时返回此错误
107  SegmentMismatch { expected: String, actual: String, position: usize },
108}
109
110impl fmt::Display for ParseError {
111  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112    match self {
113      ParseError::InvalidPath(msg) => {
114        write!(f, "Invalid path: {msg}")
115      }
116      ParseError::MissingParameter(param) => {
117        write!(f, "Missing required parameter: {param}")
118      }
119      ParseError::TypeConversion(msg) => {
120        write!(f, "Type conversion error: {msg}")
121      }
122      ParseError::InvalidQuery(msg) => {
123        write!(f, "Invalid query parameter: {msg}")
124      }
125      ParseError::UrlEncoding(msg) => {
126        write!(f, "URL encoding error: {msg}")
127      }
128      ParseError::SegmentCountMismatch { expected, actual } => {
129        write!(f, "Path segment count mismatch: expected {expected} segments, found {actual}")
130      }
131      ParseError::SegmentMismatch {
132        expected,
133        actual,
134        position,
135      } => {
136        write!(
137          f,
138          "Path segment mismatch at position {position}: expected '{expected}', found '{actual}'"
139        )
140      }
141    }
142  }
143}
144
145impl std::error::Error for ParseError {
146  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
147    // 目前没有嵌套的错误源
148    None
149  }
150}
151
152/// 解析结果类型别名
153///
154/// 为了方便使用,定义了常用的 Result 类型别名
155pub type ParseResult<T> = Result<T, ParseError>;
156
157/// 错误构造辅助函数
158impl ParseError {
159  /// 创建无效路径错误
160  pub fn invalid_path<S: Into<String>>(msg: S) -> Self {
161    ParseError::InvalidPath(msg.into())
162  }
163
164  /// 创建缺少参数错误
165  pub fn missing_parameter<S: Into<String>>(param: S) -> Self {
166    ParseError::MissingParameter(param.into())
167  }
168
169  /// 创建类型转换错误
170  pub fn type_conversion<S: Into<String>>(msg: S) -> Self {
171    ParseError::TypeConversion(msg.into())
172  }
173
174  /// 创建无效查询错误
175  pub fn invalid_query<S: Into<String>>(msg: S) -> Self {
176    ParseError::InvalidQuery(msg.into())
177  }
178
179  /// 创建 URL 编码错误
180  pub fn url_encoding<S: Into<String>>(msg: S) -> Self {
181    ParseError::UrlEncoding(msg.into())
182  }
183
184  /// 创建段数量不匹配错误
185  pub fn segment_count_mismatch(expected: usize, actual: usize) -> Self {
186    ParseError::SegmentCountMismatch { expected, actual }
187  }
188
189  /// 创建段内容不匹配错误
190  pub fn segment_mismatch<S: Into<String>>(expected: S, actual: S, position: usize) -> Self {
191    ParseError::SegmentMismatch {
192      expected: expected.into(),
193      actual: actual.into(),
194      position,
195    }
196  }
197}
198
199/// RouteState 的实用方法实现
200impl<T> RouteState<T> {
201  /// 创建一个没有子路由的状态
202  pub fn no_sub_route() -> Self {
203    RouteState::NoSubRoute
204  }
205
206  /// 创建一个有子路由的状态
207  pub fn sub_route(sub_router: T) -> Self {
208    RouteState::SubRoute(sub_router)
209  }
210
211  /// 创建一个解析失败的状态
212  pub fn parse_failed<S: Into<String>>(
213    remaining_path: S,
214    attempted_patterns: Vec<String>,
215    closest_match: Option<ClosestMatch>,
216  ) -> Self {
217    RouteState::ParseFailed {
218      remaining_path: remaining_path.into(),
219      attempted_patterns,
220      closest_match,
221    }
222  }
223
224  /// 检查是否没有子路由
225  pub fn is_no_sub_route(&self) -> bool {
226    matches!(self, RouteState::NoSubRoute)
227  }
228
229  /// 检查是否有子路由
230  pub fn is_sub_route(&self) -> bool {
231    matches!(self, RouteState::SubRoute(_))
232  }
233
234  /// 检查是否解析失败
235  pub fn is_parse_failed(&self) -> bool {
236    matches!(self, RouteState::ParseFailed { .. })
237  }
238
239  /// 获取子路由的引用(如果存在)
240  pub fn as_sub_route(&self) -> Option<&T> {
241    match self {
242      RouteState::SubRoute(sub) => Some(sub),
243      _ => None,
244    }
245  }
246
247  /// 获取子路由的可变引用(如果存在)
248  pub fn as_sub_route_mut(&mut self) -> Option<&mut T> {
249    match self {
250      RouteState::SubRoute(sub) => Some(sub),
251      _ => None,
252    }
253  }
254
255  /// 将 RouteState 转换为 Option(向后兼容)
256  pub fn into_option(self) -> Option<T> {
257    match self {
258      RouteState::SubRoute(sub) => Some(sub),
259      _ => None,
260    }
261  }
262
263  /// 从 Option 创建 RouteState(向后兼容)
264  pub fn from_option(option: Option<T>) -> Self {
265    match option {
266      Some(sub) => RouteState::SubRoute(sub),
267      None => RouteState::NoSubRoute,
268    }
269  }
270
271  /// 映射子路由类型
272  pub fn map<U, F>(self, f: F) -> RouteState<U>
273  where
274    F: FnOnce(T) -> U,
275  {
276    match self {
277      RouteState::NoSubRoute => RouteState::NoSubRoute,
278      RouteState::SubRoute(sub) => RouteState::SubRoute(f(sub)),
279      RouteState::ParseFailed {
280        remaining_path,
281        attempted_patterns,
282        closest_match,
283      } => RouteState::ParseFailed {
284        remaining_path,
285        attempted_patterns,
286        closest_match,
287      },
288    }
289  }
290
291  /// 获取调试信息(如果是解析失败状态)
292  pub fn debug_info(&self) -> Option<RouteDebugInfo> {
293    match self {
294      RouteState::ParseFailed {
295        remaining_path,
296        attempted_patterns,
297        closest_match,
298      } => Some(RouteDebugInfo {
299        failed_at_level: 0,           // 默认值,可以在具体使用时设置
300        consumed_path: String::new(), // 默认值,可以在具体使用时设置
301        remaining_path: remaining_path.clone(),
302        available_routes: attempted_patterns.clone(),
303        suggestion: closest_match.as_ref().map(|m| {
304          format!(
305            "Did you mean '{}'? (matched {} characters, failed because: {})",
306            m.pattern, m.matched_length, m.failure_reason
307          )
308        }),
309      }),
310      _ => None,
311    }
312  }
313}
314
315/// ClosestMatch 的实用方法实现
316impl ClosestMatch {
317  /// 创建一个新的最接近匹配信息
318  pub fn new<S1: Into<String>, S2: Into<String>>(pattern: S1, matched_length: usize, failure_reason: S2) -> Self {
319    ClosestMatch {
320      pattern: pattern.into(),
321      matched_length,
322      failure_reason: failure_reason.into(),
323    }
324  }
325}
326
327/// RouteDebugInfo 的实用方法实现
328impl RouteDebugInfo {
329  /// 创建一个新的路由调试信息
330  pub fn new<S1: Into<String>, S2: Into<String>>(
331    failed_at_level: usize,
332    consumed_path: S1,
333    remaining_path: S2,
334    available_routes: Vec<String>,
335    suggestion: Option<String>,
336  ) -> Self {
337    RouteDebugInfo {
338      failed_at_level,
339      consumed_path: consumed_path.into(),
340      remaining_path: remaining_path.into(),
341      available_routes,
342      suggestion,
343    }
344  }
345
346  /// 生成人类可读的错误消息
347  pub fn to_error_message(&self) -> String {
348    let mut message = format!(
349      "Route parsing failed at level {}: consumed '{}', remaining '{}'",
350      self.failed_at_level, self.consumed_path, self.remaining_path
351    );
352
353    if !self.available_routes.is_empty() {
354      message.push_str(&format!("\nAvailable routes: {}", self.available_routes.join(", ")));
355    }
356
357    if let Some(suggestion) = &self.suggestion {
358      message.push_str(&format!("\nSuggestion: {suggestion}"));
359    }
360
361    message
362  }
363}
364
365#[cfg(test)]
366mod tests {
367  use super::*;
368
369  #[test]
370  fn test_error_display() {
371    let error = ParseError::invalid_path("test path");
372    assert_eq!(error.to_string(), "Invalid path: test path");
373
374    let error = ParseError::missing_parameter("id");
375    assert_eq!(error.to_string(), "Missing required parameter: id");
376
377    let error = ParseError::segment_count_mismatch(3, 2);
378    assert_eq!(error.to_string(), "Path segment count mismatch: expected 3 segments, found 2");
379
380    let error = ParseError::segment_mismatch("user", "admin", 1);
381    assert_eq!(
382      error.to_string(),
383      "Path segment mismatch at position 1: expected 'user', found 'admin'"
384    );
385  }
386
387  #[test]
388  fn test_error_equality() {
389    let error1 = ParseError::invalid_path("test");
390    let error2 = ParseError::invalid_path("test");
391    let error3 = ParseError::invalid_path("other");
392
393    assert_eq!(error1, error2);
394    assert_ne!(error1, error3);
395  }
396}