Skip to main content

piper_client/types/
error.rs

1//! 错误类型体系
2//!
3//! 分层错误处理,区分致命错误(Fatal)和可恢复错误(Recoverable)。
4//!
5//! # 设计目标
6//!
7//! - **分层管理**: 区分致命错误和可恢复错误
8//! - **清晰信息**: 提供详细的错误上下文
9//! - **可重试**: 标记可重试的错误
10//! - **日志友好**: 集成 tracing 日志
11//!
12//! # 示例
13//!
14//! ```rust
15//! use piper_client::types::RobotError;
16//!
17//! fn handle_error(err: RobotError) {
18//!     if err.is_fatal() {
19//!         eprintln!("致命错误: {}", err);
20//!         // 停止系统
21//!     } else if err.is_retryable() {
22//!         eprintln!("可重试错误: {}", err);
23//!         // 重试操作
24//!     } else {
25//!         eprintln!("错误: {}", err);
26//!         // 记录并继续
27//!     }
28//! }
29//! ```
30
31use super::joint::Joint;
32use thiserror::Error;
33
34/// 机器人错误类型
35///
36/// 分层错误类型,支持致命错误和可恢复错误的区分。
37#[derive(Debug, Error)]
38pub enum RobotError {
39    // ==================== Fatal Errors (不可恢复) ====================
40    /// 硬件通信失败
41    #[error("Hardware communication failed: {0}")]
42    HardwareFailure(String),
43
44    /// 状态机损坏
45    #[error("State machine poisoned: {reason}")]
46    StatePoisoned {
47        /// 损坏原因
48        reason: String,
49    },
50
51    /// 急停触发
52    #[error("Emergency stop triggered")]
53    EmergencyStop,
54
55    /// CAN 总线错误(致命)
56    #[error("CAN bus fatal error: {0}")]
57    CanBusFatal(String),
58
59    // ==================== Recoverable Errors ====================
60    /// 命令超时
61    #[error("Command timeout after {timeout_ms}ms")]
62    Timeout {
63        /// 超时时间(毫秒)
64        timeout_ms: u64,
65    },
66
67    /// 无效的状态转换
68    #[error("Invalid state transition: {from} -> {to}")]
69    InvalidTransition {
70        /// 起始状态
71        from: String,
72        /// 目标状态
73        to: String,
74    },
75
76    /// 关节限位超出
77    #[error("Joint {joint} limit exceeded: {value:.3} (limit: {limit:.3})")]
78    JointLimitExceeded {
79        /// 关节索引
80        joint: Joint,
81        /// 实际值
82        value: f64,
83        /// 限位值
84        limit: f64,
85    },
86
87    /// 速度限制超出
88    #[error("Velocity limit exceeded for joint {joint}: {value:.3} (limit: {limit:.3})")]
89    VelocityLimitExceeded {
90        /// 关节索引
91        joint: Joint,
92        /// 实际速度
93        value: f64,
94        /// 限速
95        limit: f64,
96    },
97
98    /// 力矩限制超出
99    #[error("Torque limit exceeded for joint {joint}: {value:.3} (limit: {limit:.3})")]
100    TorqueLimitExceeded {
101        /// 关节索引
102        joint: Joint,
103        /// 实际力矩
104        value: f64,
105        /// 限矩
106        limit: f64,
107    },
108
109    // ==================== I/O Errors ====================
110    /// CAN 总线 I/O 错误(可恢复)
111    #[error("CAN bus I/O error: {0}")]
112    CanIoError(String),
113
114    /// 序列化错误
115    #[error("Serialization error: {0}")]
116    SerializationError(String),
117
118    // ==================== Protocol Errors ====================
119    /// 协议错误(自动转换自 protocol::ProtocolError)
120    #[error("Protocol encoding error: {0}")]
121    Protocol(#[from] piper_protocol::ProtocolError),
122
123    /// 驱动层错误(自动转换自 driver::DriverError)
124    #[error("Driver infrastructure error: {0}")]
125    Infrastructure(#[from] piper_driver::DriverError),
126
127    /// CAN 适配器错误(自动转换自 can::CanError)
128    #[error("CAN adapter error: {0}")]
129    CanAdapter(#[from] piper_can::CanError),
130
131    /// 无效的帧ID
132    #[error("Invalid CAN frame ID: 0x{id:03X}")]
133    InvalidFrameId {
134        /// 帧 ID
135        id: u32,
136    },
137
138    /// 无效的数据长度
139    #[error("Invalid data length: expected {expected}, got {actual}")]
140    InvalidDataLength {
141        /// 期望长度
142        expected: usize,
143        /// 实际长度
144        actual: usize,
145    },
146
147    // ==================== Configuration Errors ====================
148    /// 配置错误
149    #[error("Configuration error: {0}")]
150    ConfigError(String),
151
152    /// 参数无效
153    #[error("Invalid parameter '{param}': {reason}")]
154    InvalidParameter {
155        /// 参数名
156        param: String,
157        /// 原因
158        reason: String,
159    },
160
161    // ==================== Other ====================
162    /// 未知错误
163    #[error("Unknown error: {0}")]
164    Unknown(String),
165}
166
167impl RobotError {
168    /// 是否为致命错误
169    ///
170    /// 致命错误表示系统处于不安全状态,必须立即停止。
171    pub fn is_fatal(&self) -> bool {
172        matches!(
173            self,
174            Self::HardwareFailure(_)
175                | Self::StatePoisoned { .. }
176                | Self::EmergencyStop
177                | Self::CanBusFatal(_)
178        )
179    }
180
181    /// 是否可重试
182    ///
183    /// 可重试错误表示重新执行操作可能会成功。
184    pub fn is_retryable(&self) -> bool {
185        matches!(
186            self,
187            Self::Timeout { .. } | Self::CanIoError(_) | Self::Protocol(_)
188        )
189    }
190
191    /// 是否为配置错误
192    pub fn is_config_error(&self) -> bool {
193        matches!(self, Self::ConfigError(_) | Self::InvalidParameter { .. })
194    }
195
196    /// 是否为限位错误
197    pub fn is_limit_error(&self) -> bool {
198        matches!(
199            self,
200            Self::JointLimitExceeded { .. }
201                | Self::VelocityLimitExceeded { .. }
202                | Self::TorqueLimitExceeded { .. }
203        )
204    }
205
206    /// 添加上下文信息
207    pub fn context(self, context: impl Into<String>) -> Self {
208        match self {
209            Self::Unknown(msg) => Self::Unknown(format!("{}: {}", context.into(), msg)),
210            _ => self,
211        }
212    }
213
214    /// 创建硬件故障错误
215    pub fn hardware_failure(msg: impl Into<String>) -> Self {
216        Self::HardwareFailure(msg.into())
217    }
218
219    /// 创建状态损坏错误
220    pub fn state_poisoned(reason: impl Into<String>) -> Self {
221        Self::StatePoisoned {
222            reason: reason.into(),
223        }
224    }
225
226    /// 创建超时错误
227    pub fn timeout(timeout_ms: u64) -> Self {
228        Self::Timeout { timeout_ms }
229    }
230
231    /// 创建无效状态转换错误
232    pub fn invalid_transition(from: impl Into<String>, to: impl Into<String>) -> Self {
233        Self::InvalidTransition {
234            from: from.into(),
235            to: to.into(),
236        }
237    }
238
239    /// 创建关节限位错误
240    pub fn joint_limit(joint: Joint, value: f64, limit: f64) -> Self {
241        Self::JointLimitExceeded {
242            joint,
243            value,
244            limit,
245        }
246    }
247
248    /// 创建速度限制错误
249    pub fn velocity_limit(joint: Joint, value: f64, limit: f64) -> Self {
250        Self::VelocityLimitExceeded {
251            joint,
252            value,
253            limit,
254        }
255    }
256
257    /// 创建力矩限制错误
258    pub fn torque_limit(joint: Joint, value: f64, limit: f64) -> Self {
259        Self::TorqueLimitExceeded {
260            joint,
261            value,
262            limit,
263        }
264    }
265}
266
267/// Result 类型别名
268pub type Result<T> = std::result::Result<T, RobotError>;
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    #[test]
275    fn test_error_classification() {
276        // 致命错误
277        let fatal = RobotError::EmergencyStop;
278        assert!(fatal.is_fatal());
279        assert!(!fatal.is_retryable());
280
281        let hardware_fail = RobotError::hardware_failure("connection lost");
282        assert!(hardware_fail.is_fatal());
283
284        let poisoned = RobotError::state_poisoned("state drift detected");
285        assert!(poisoned.is_fatal());
286
287        // 可恢复错误
288        let recoverable = RobotError::timeout(100);
289        assert!(!recoverable.is_fatal());
290        assert!(recoverable.is_retryable());
291
292        let can_io = RobotError::CanIoError("temporary failure".to_string());
293        assert!(!can_io.is_fatal());
294        assert!(can_io.is_retryable());
295    }
296
297    #[test]
298    fn test_limit_errors() {
299        let joint_limit = RobotError::joint_limit(Joint::J1, 3.5, std::f64::consts::PI);
300        assert!(joint_limit.is_limit_error());
301        assert!(!joint_limit.is_fatal());
302
303        let velocity_limit = RobotError::velocity_limit(Joint::J2, 10.0, 5.0);
304        assert!(velocity_limit.is_limit_error());
305
306        let torque_limit = RobotError::torque_limit(Joint::J3, 15.0, 10.0);
307        assert!(torque_limit.is_limit_error());
308    }
309
310    #[test]
311    fn test_config_errors() {
312        let config_err = RobotError::ConfigError("invalid frequency".to_string());
313        assert!(config_err.is_config_error());
314        assert!(!config_err.is_fatal());
315
316        let invalid_param = RobotError::InvalidParameter {
317            param: "max_velocity".to_string(),
318            reason: "must be positive".to_string(),
319        };
320        assert!(invalid_param.is_config_error());
321    }
322
323    #[test]
324    fn test_error_display() {
325        let err = RobotError::joint_limit(Joint::J1, 3.5, std::f64::consts::PI);
326        let msg = format!("{}", err);
327        assert!(msg.contains("J1"));
328        assert!(msg.contains("3.5"));
329        assert!(msg.contains("3.14"));
330
331        let timeout_err = RobotError::timeout(100);
332        let msg = format!("{}", timeout_err);
333        assert!(msg.contains("100"));
334        assert!(msg.contains("timeout"));
335    }
336
337    #[test]
338    fn test_error_context() {
339        let err = RobotError::Unknown("base error".to_string());
340        let err_with_context = err.context("during initialization");
341        let msg = format!("{}", err_with_context);
342        assert!(msg.contains("during initialization"));
343        assert!(msg.contains("base error"));
344    }
345
346    #[test]
347    fn test_invalid_transition() {
348        let err = RobotError::invalid_transition("Standby", "Active");
349        let msg = format!("{}", err);
350        assert!(msg.contains("Standby"));
351        assert!(msg.contains("Active"));
352    }
353
354    #[test]
355    fn test_protocol_errors() {
356        let invalid_id = RobotError::InvalidFrameId { id: 0x123 };
357        let msg = format!("{}", invalid_id);
358        assert!(msg.contains("0x123"));
359
360        let invalid_len = RobotError::InvalidDataLength {
361            expected: 8,
362            actual: 4,
363        };
364        let msg = format!("{}", invalid_len);
365        assert!(msg.contains("8"));
366        assert!(msg.contains("4"));
367    }
368
369    #[test]
370    fn test_result_type() {
371        let ok: Result<i32> = Ok(42);
372        assert!(matches!(ok, Ok(42)));
373
374        let err: Result<i32> = Err(RobotError::EmergencyStop);
375        assert!(err.is_err());
376    }
377
378    #[test]
379    fn test_error_is_send_sync() {
380        fn assert_send_sync<T: Send + Sync>() {}
381        assert_send_sync::<RobotError>();
382    }
383}