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}