mcp_execution_core/
error.rs

1//! Error types for MCP Code Execution.
2//!
3//! This module provides a comprehensive error hierarchy with contextual information
4//! following Microsoft Rust Guidelines for error handling.
5//!
6//! # Examples
7//!
8//! ```
9//! use mcp_execution_core::{Error, Result};
10//!
11//! fn connect_to_server(name: &str) -> Result<()> {
12//!     if name.is_empty() {
13//!         return Err(Error::ConfigError {
14//!             message: "Server name cannot be empty".to_string(),
15//!         });
16//!     }
17//!     Ok(())
18//! }
19//!
20//! let err = connect_to_server("").unwrap_err();
21//! assert!(err.is_config_error());
22//! ```
23
24use thiserror::Error;
25
26/// Main error type for MCP Code Execution.
27///
28/// All errors in the system use this type, providing consistent error handling
29/// across all crates in the workspace.
30#[derive(Error, Debug)]
31pub enum Error {
32    /// MCP server connection failed.
33    ///
34    /// This error occurs when attempting to connect to an MCP server and
35    /// the connection fails due to network issues, authentication failures,
36    /// or server unavailability.
37    #[error("MCP server connection failed: {server}")]
38    ConnectionFailed {
39        /// Name or identifier of the server that failed to connect
40        server: String,
41        /// Underlying error cause
42        #[source]
43        source: Box<dyn std::error::Error + Send + Sync>,
44    },
45
46    /// Security policy violation.
47    ///
48    /// Raised when an operation violates configured security policies,
49    /// such as attempting to access forbidden resources or exceeding
50    /// resource limits.
51    #[error("Security policy violation: {reason}")]
52    SecurityViolation {
53        /// Description of the security violation
54        reason: String,
55    },
56
57    /// Resource not found error.
58    ///
59    /// Occurs when attempting to access a resource (file, tool, server)
60    /// that does not exist.
61    #[error("Resource not found: {resource}")]
62    ResourceNotFound {
63        /// Identifier of the missing resource
64        resource: String,
65    },
66
67    /// Configuration error.
68    ///
69    /// Raised when configuration is invalid, missing required fields,
70    /// or contains contradictory settings.
71    #[error("Configuration error: {message}")]
72    ConfigError {
73        /// Description of the configuration problem
74        message: String,
75    },
76
77    /// Timeout error.
78    ///
79    /// Occurs when an operation exceeds its configured timeout limit.
80    #[error("Operation timed out after {duration_secs}s: {operation}")]
81    Timeout {
82        /// Name of the operation that timed out
83        operation: String,
84        /// Duration in seconds before timeout occurred
85        duration_secs: u64,
86    },
87
88    /// Serialization/deserialization error.
89    ///
90    /// Raised when JSON or other data format conversion fails.
91    #[error("Serialization error: {message}")]
92    SerializationError {
93        /// Description of the serialization failure
94        message: String,
95        /// Underlying serde error
96        #[source]
97        source: Option<serde_json::Error>,
98    },
99
100    /// Invalid argument error.
101    ///
102    /// Raised when CLI arguments or function parameters are invalid.
103    #[error("Invalid argument: {0}")]
104    InvalidArgument(String),
105
106    /// Validation error for domain types.
107    ///
108    /// Raised when creating or validating domain types like `SkillName`,
109    /// `SkillDescription`, etc. that have specific format requirements.
110    #[error("Validation error in {field}: {reason}")]
111    ValidationError {
112        /// The field that failed validation
113        field: String,
114        /// Detailed reason for the validation failure
115        reason: String,
116    },
117
118    /// Script generation failed.
119    ///
120    /// Raised when generating TypeScript scripts from tool schemas fails.
121    #[error("Script generation failed for tool '{tool}': {message}")]
122    ScriptGenerationError {
123        /// The tool name that failed to generate
124        tool: String,
125        /// Description of the generation failure
126        message: String,
127        /// Optional underlying error
128        #[source]
129        source: Option<Box<dyn std::error::Error + Send + Sync>>,
130    },
131}
132
133impl Error {
134    /// Returns `true` if this is a connection error.
135    ///
136    /// # Examples
137    ///
138    /// ```
139    /// use mcp_execution_core::Error;
140    ///
141    /// let err = Error::ConnectionFailed {
142    ///     server: "test".to_string(),
143    ///     source: "connection refused".into(),
144    /// };
145    /// assert!(err.is_connection_error());
146    /// ```
147    #[must_use]
148    pub const fn is_connection_error(&self) -> bool {
149        matches!(self, Self::ConnectionFailed { .. })
150    }
151
152    /// Returns `true` if this is a security violation error.
153    ///
154    /// # Examples
155    ///
156    /// ```
157    /// use mcp_execution_core::Error;
158    ///
159    /// let err = Error::SecurityViolation {
160    ///     reason: "Unauthorized access".to_string(),
161    /// };
162    /// assert!(err.is_security_error());
163    /// ```
164    #[must_use]
165    pub const fn is_security_error(&self) -> bool {
166        matches!(self, Self::SecurityViolation { .. })
167    }
168
169    /// Returns `true` if this is a resource not found error.
170    ///
171    /// # Examples
172    ///
173    /// ```
174    /// use mcp_execution_core::Error;
175    ///
176    /// let err = Error::ResourceNotFound {
177    ///     resource: "tool:example".to_string(),
178    /// };
179    /// assert!(err.is_not_found());
180    /// ```
181    #[must_use]
182    pub const fn is_not_found(&self) -> bool {
183        matches!(self, Self::ResourceNotFound { .. })
184    }
185
186    /// Returns `true` if this is a configuration error.
187    ///
188    /// # Examples
189    ///
190    /// ```
191    /// use mcp_execution_core::Error;
192    ///
193    /// let err = Error::ConfigError {
194    ///     message: "Invalid port".to_string(),
195    /// };
196    /// assert!(err.is_config_error());
197    /// ```
198    #[must_use]
199    pub const fn is_config_error(&self) -> bool {
200        matches!(self, Self::ConfigError { .. })
201    }
202
203    /// Returns `true` if this is a timeout error.
204    ///
205    /// # Examples
206    ///
207    /// ```
208    /// use mcp_execution_core::Error;
209    ///
210    /// let err = Error::Timeout {
211    ///     operation: "execute_code".to_string(),
212    ///     duration_secs: 30,
213    /// };
214    /// assert!(err.is_timeout());
215    /// ```
216    #[must_use]
217    pub const fn is_timeout(&self) -> bool {
218        matches!(self, Self::Timeout { .. })
219    }
220
221    /// Returns `true` if this is a validation error.
222    ///
223    /// # Examples
224    ///
225    /// ```
226    /// use mcp_execution_core::Error;
227    ///
228    /// let err = Error::ValidationError {
229    ///     field: "skill_name".to_string(),
230    ///     reason: "Invalid characters".to_string(),
231    /// };
232    /// assert!(err.is_validation_error());
233    /// ```
234    #[must_use]
235    pub const fn is_validation_error(&self) -> bool {
236        matches!(self, Self::ValidationError { .. })
237    }
238
239    /// Returns `true` if this is a script generation error.
240    ///
241    /// # Examples
242    ///
243    /// ```
244    /// use mcp_execution_core::Error;
245    ///
246    /// let err = Error::ScriptGenerationError {
247    ///     tool: "send_message".to_string(),
248    ///     message: "Template rendering failed".to_string(),
249    ///     source: None,
250    /// };
251    /// assert!(err.is_script_generation_error());
252    /// ```
253    #[must_use]
254    pub const fn is_script_generation_error(&self) -> bool {
255        matches!(self, Self::ScriptGenerationError { .. })
256    }
257}
258
259/// Result type alias for MCP operations.
260///
261/// This is a convenience alias for `Result<T, Error>` used throughout
262/// the codebase.
263///
264/// # Examples
265///
266/// ```
267/// use mcp_execution_core::{Result, Error};
268///
269/// fn validate_input(value: i32) -> Result<i32> {
270///     if value < 0 {
271///         return Err(Error::ConfigError {
272///             message: "Value must be non-negative".to_string(),
273///         });
274///     }
275///     Ok(value)
276/// }
277///
278/// assert!(validate_input(5).is_ok());
279/// assert!(validate_input(-1).is_err());
280/// ```
281pub type Result<T> = std::result::Result<T, Error>;
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn test_connection_error_detection() {
289        let err = Error::ConnectionFailed {
290            server: "test-server".to_string(),
291            source: "network error".into(),
292        };
293        assert!(err.is_connection_error());
294        assert!(!err.is_security_error());
295    }
296
297    #[test]
298    fn test_security_error_detection() {
299        let err = Error::SecurityViolation {
300            reason: "Access denied".to_string(),
301        };
302        assert!(err.is_security_error());
303        assert!(!err.is_connection_error());
304    }
305
306    #[test]
307    fn test_not_found_error_detection() {
308        let err = Error::ResourceNotFound {
309            resource: "missing-tool".to_string(),
310        };
311        assert!(err.is_not_found());
312        assert!(!err.is_timeout());
313    }
314
315    #[test]
316    fn test_config_error_detection() {
317        let err = Error::ConfigError {
318            message: "Invalid configuration".to_string(),
319        };
320        assert!(err.is_config_error());
321        assert!(!err.is_timeout());
322    }
323
324    #[test]
325    fn test_timeout_error_detection() {
326        let err = Error::Timeout {
327            operation: "long_operation".to_string(),
328            duration_secs: 60,
329        };
330        assert!(err.is_timeout());
331        assert!(!err.is_not_found());
332    }
333
334    #[test]
335    fn test_error_display() {
336        let err = Error::SecurityViolation {
337            reason: "Unauthorized".to_string(),
338        };
339        let display = format!("{err}");
340        assert!(display.contains("Security policy violation"));
341        assert!(display.contains("Unauthorized"));
342    }
343
344    #[test]
345    fn test_result_alias() {
346        #[allow(clippy::unnecessary_wraps)]
347        fn returns_ok() -> Result<i32> {
348            Ok(42)
349        }
350
351        fn returns_err() -> Result<i32> {
352            Err(Error::ConfigError {
353                message: "test error".to_string(),
354            })
355        }
356
357        assert_eq!(returns_ok().unwrap(), 42);
358        assert!(returns_err().is_err());
359    }
360}