use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("MCP server connection failed: {server}")]
ConnectionFailed {
server: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Security policy violation: {reason}")]
SecurityViolation {
reason: String,
},
#[error("Resource not found: {resource}")]
ResourceNotFound {
resource: String,
},
#[error("Configuration error: {message}")]
ConfigError {
message: String,
},
#[error("Operation timed out after {duration_secs}s: {operation}")]
Timeout {
operation: String,
duration_secs: u64,
},
#[error("Serialization error: {message}")]
SerializationError {
message: String,
#[source]
source: Option<serde_json::Error>,
},
#[error("Invalid argument: {0}")]
InvalidArgument(String),
#[error("Validation error in {field}: {reason}")]
ValidationError {
field: String,
reason: String,
},
#[error("Script generation failed for tool '{tool}': {message}")]
ScriptGenerationError {
tool: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
}
impl Error {
#[must_use]
pub const fn is_connection_error(&self) -> bool {
matches!(self, Self::ConnectionFailed { .. })
}
#[must_use]
pub const fn is_security_error(&self) -> bool {
matches!(self, Self::SecurityViolation { .. })
}
#[must_use]
pub const fn is_not_found(&self) -> bool {
matches!(self, Self::ResourceNotFound { .. })
}
#[must_use]
pub const fn is_config_error(&self) -> bool {
matches!(self, Self::ConfigError { .. })
}
#[must_use]
pub const fn is_timeout(&self) -> bool {
matches!(self, Self::Timeout { .. })
}
#[must_use]
pub const fn is_validation_error(&self) -> bool {
matches!(self, Self::ValidationError { .. })
}
#[must_use]
pub const fn is_script_generation_error(&self) -> bool {
matches!(self, Self::ScriptGenerationError { .. })
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_connection_error_detection() {
let err = Error::ConnectionFailed {
server: "test-server".to_string(),
source: "network error".into(),
};
assert!(err.is_connection_error());
assert!(!err.is_security_error());
}
#[test]
fn test_security_error_detection() {
let err = Error::SecurityViolation {
reason: "Access denied".to_string(),
};
assert!(err.is_security_error());
assert!(!err.is_connection_error());
}
#[test]
fn test_not_found_error_detection() {
let err = Error::ResourceNotFound {
resource: "missing-tool".to_string(),
};
assert!(err.is_not_found());
assert!(!err.is_timeout());
}
#[test]
fn test_config_error_detection() {
let err = Error::ConfigError {
message: "Invalid configuration".to_string(),
};
assert!(err.is_config_error());
assert!(!err.is_timeout());
}
#[test]
fn test_timeout_error_detection() {
let err = Error::Timeout {
operation: "long_operation".to_string(),
duration_secs: 60,
};
assert!(err.is_timeout());
assert!(!err.is_not_found());
}
#[test]
fn test_error_display() {
let err = Error::SecurityViolation {
reason: "Unauthorized".to_string(),
};
let display = format!("{err}");
assert!(display.contains("Security policy violation"));
assert!(display.contains("Unauthorized"));
}
#[test]
fn test_result_alias() {
#[allow(clippy::unnecessary_wraps)]
fn returns_ok() -> Result<i32> {
Ok(42)
}
fn returns_err() -> Result<i32> {
Err(Error::ConfigError {
message: "test error".to_string(),
})
}
assert_eq!(returns_ok().unwrap(), 42);
assert!(returns_err().is_err());
}
}