Skip to main content

aster/tools/
error.rs

1//! Tool Error Types
2//!
3//! This module defines the error types for the tool system.
4//! All tool operations return `Result<T, ToolError>` for consistent error handling.
5
6use std::time::Duration;
7use thiserror::Error;
8
9/// Tool execution error types
10///
11/// Represents all possible errors that can occur during tool operations.
12#[derive(Debug, Error)]
13pub enum ToolError {
14    /// Tool not found in registry
15    #[error("Tool not found: {0}")]
16    NotFound(String),
17
18    /// Permission denied for tool execution
19    #[error("Permission denied: {0}")]
20    PermissionDenied(String),
21
22    /// Tool execution failed
23    #[error("Execution failed: {0}")]
24    ExecutionFailed(String),
25
26    /// Tool execution timed out
27    #[error("Timeout after {0:?}")]
28    Timeout(Duration),
29
30    /// Safety check failed (e.g., dangerous command detected)
31    #[error("Safety check failed: {0}")]
32    SafetyCheckFailed(String),
33
34    /// Invalid parameters provided to tool
35    #[error("Invalid parameters: {0}")]
36    InvalidParams(String),
37
38    /// I/O error during tool execution
39    #[error("IO error: {0}")]
40    Io(#[from] std::io::Error),
41
42    /// Tool execution was cancelled
43    #[error("Cancelled")]
44    Cancelled,
45}
46
47impl ToolError {
48    /// Create a NotFound error
49    pub fn not_found(name: impl Into<String>) -> Self {
50        Self::NotFound(name.into())
51    }
52
53    /// Create a PermissionDenied error
54    pub fn permission_denied(reason: impl Into<String>) -> Self {
55        Self::PermissionDenied(reason.into())
56    }
57
58    /// Create an ExecutionFailed error
59    pub fn execution_failed(reason: impl Into<String>) -> Self {
60        Self::ExecutionFailed(reason.into())
61    }
62
63    /// Create a Timeout error
64    pub fn timeout(duration: Duration) -> Self {
65        Self::Timeout(duration)
66    }
67
68    /// Create a SafetyCheckFailed error
69    pub fn safety_check_failed(reason: impl Into<String>) -> Self {
70        Self::SafetyCheckFailed(reason.into())
71    }
72
73    /// Create an InvalidParams error
74    pub fn invalid_params(reason: impl Into<String>) -> Self {
75        Self::InvalidParams(reason.into())
76    }
77
78    /// Check if this error is retryable
79    pub fn is_retryable(&self) -> bool {
80        matches!(self, Self::Timeout(_) | Self::Io(_))
81    }
82
83    /// Check if this error is a permission error
84    pub fn is_permission_error(&self) -> bool {
85        matches!(self, Self::PermissionDenied(_))
86    }
87
88    /// Check if this error is a safety error
89    pub fn is_safety_error(&self) -> bool {
90        matches!(self, Self::SafetyCheckFailed(_))
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_not_found_error() {
100        let err = ToolError::not_found("bash");
101        assert!(matches!(err, ToolError::NotFound(_)));
102        assert_eq!(err.to_string(), "Tool not found: bash");
103    }
104
105    #[test]
106    fn test_permission_denied_error() {
107        let err = ToolError::permission_denied("Access denied to file system");
108        assert!(matches!(err, ToolError::PermissionDenied(_)));
109        assert_eq!(
110            err.to_string(),
111            "Permission denied: Access denied to file system"
112        );
113    }
114
115    #[test]
116    fn test_execution_failed_error() {
117        let err = ToolError::execution_failed("Command returned non-zero exit code");
118        assert!(matches!(err, ToolError::ExecutionFailed(_)));
119        assert_eq!(
120            err.to_string(),
121            "Execution failed: Command returned non-zero exit code"
122        );
123    }
124
125    #[test]
126    fn test_timeout_error() {
127        let err = ToolError::timeout(Duration::from_secs(30));
128        assert!(matches!(err, ToolError::Timeout(_)));
129        assert_eq!(err.to_string(), "Timeout after 30s");
130    }
131
132    #[test]
133    fn test_safety_check_failed_error() {
134        let err = ToolError::safety_check_failed("Dangerous command detected: rm -rf /");
135        assert!(matches!(err, ToolError::SafetyCheckFailed(_)));
136        assert_eq!(
137            err.to_string(),
138            "Safety check failed: Dangerous command detected: rm -rf /"
139        );
140    }
141
142    #[test]
143    fn test_invalid_params_error() {
144        let err = ToolError::invalid_params("Missing required parameter: path");
145        assert!(matches!(err, ToolError::InvalidParams(_)));
146        assert_eq!(
147            err.to_string(),
148            "Invalid parameters: Missing required parameter: path"
149        );
150    }
151
152    #[test]
153    fn test_io_error_conversion() {
154        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
155        let err: ToolError = io_err.into();
156        assert!(matches!(err, ToolError::Io(_)));
157    }
158
159    #[test]
160    fn test_cancelled_error() {
161        let err = ToolError::Cancelled;
162        assert_eq!(err.to_string(), "Cancelled");
163    }
164
165    #[test]
166    fn test_is_retryable() {
167        assert!(ToolError::timeout(Duration::from_secs(1)).is_retryable());
168        assert!(ToolError::Io(std::io::Error::other("test")).is_retryable());
169        assert!(!ToolError::not_found("test").is_retryable());
170        assert!(!ToolError::permission_denied("test").is_retryable());
171        assert!(!ToolError::safety_check_failed("test").is_retryable());
172        assert!(!ToolError::Cancelled.is_retryable());
173    }
174
175    #[test]
176    fn test_is_permission_error() {
177        assert!(ToolError::permission_denied("test").is_permission_error());
178        assert!(!ToolError::not_found("test").is_permission_error());
179        assert!(!ToolError::Cancelled.is_permission_error());
180    }
181
182    #[test]
183    fn test_is_safety_error() {
184        assert!(ToolError::safety_check_failed("test").is_safety_error());
185        assert!(!ToolError::not_found("test").is_safety_error());
186        assert!(!ToolError::permission_denied("test").is_safety_error());
187    }
188}