use std::fmt;
use thiserror::Error;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum CliError {
#[error("Transport error: {0}")]
Transport(#[from] turbomcp_protocol::Error),
#[error("Invalid arguments: {0}")]
InvalidArguments(String),
#[error("Server error [{code}]: {message}")]
ServerError { code: i32, message: String },
#[error("Operation '{operation}' timed out after {elapsed:?}")]
Timeout {
operation: String,
elapsed: std::time::Duration,
},
#[error("Client not initialized - call 'initialize' first")]
NotInitialized,
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("YAML error: {0}")]
Yaml(#[from] serde_norway::Error),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Config error: {0}")]
Config(#[from] config::ConfigError),
#[error("Connection failed: {0}")]
ConnectionFailed(String),
#[error("Feature not supported: {0}")]
NotSupported(String),
#[error("Security validation failed: {reason}\n{details}")]
SecurityViolation { reason: String, details: String },
#[error("{0}")]
Other(String),
}
impl CliError {
pub fn suggestions(&self) -> Vec<&'static str> {
match self {
Self::ConnectionFailed(_) => vec![
"Check if the server is running",
"Verify the connection URL",
"Use --transport to specify transport explicitly",
],
Self::NotInitialized => vec![
"Ensure the server is started before calling operations",
"Check server logs for initialization errors",
],
Self::Timeout { .. } => vec![
"Increase timeout with --timeout flag",
"Check server responsiveness",
"Verify network connectivity",
],
Self::InvalidArguments(_) => vec![
"Check argument format (must be valid JSON)",
"Use --help to see expected format",
],
_ => vec![],
}
}
pub fn category(&self) -> ErrorCategory {
match self {
Self::Transport(_) | Self::ConnectionFailed(_) => ErrorCategory::Connection,
Self::InvalidArguments(_) => ErrorCategory::User,
Self::ServerError { .. } => ErrorCategory::Server,
Self::Timeout { .. } => ErrorCategory::Timeout,
Self::Json(_) | Self::Yaml(_) => ErrorCategory::Parsing,
Self::Io(_) => ErrorCategory::System,
Self::Config(_) => ErrorCategory::Config,
Self::NotSupported(_) => ErrorCategory::NotSupported,
Self::SecurityViolation { .. } => ErrorCategory::Security,
_ => ErrorCategory::Other,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCategory {
Connection,
User,
Server,
Timeout,
Parsing,
System,
Config,
NotSupported,
Security,
Other,
}
impl fmt::Display for ErrorCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Connection => write!(f, "Connection"),
Self::User => write!(f, "User Input"),
Self::Server => write!(f, "Server"),
Self::Timeout => write!(f, "Timeout"),
Self::Parsing => write!(f, "Parsing"),
Self::System => write!(f, "System"),
Self::Config => write!(f, "Configuration"),
Self::NotSupported => write!(f, "Not Supported"),
Self::Security => write!(f, "Security"),
Self::Other => write!(f, "Error"),
}
}
}
impl From<String> for CliError {
fn from(s: String) -> Self {
Self::Other(s)
}
}
impl From<&str> for CliError {
fn from(s: &str) -> Self {
Self::Other(s.to_string())
}
}
impl From<Box<turbomcp_protocol::Error>> for CliError {
fn from(err: Box<turbomcp_protocol::Error>) -> Self {
Self::Transport(*err)
}
}
pub type CliResult<T> = Result<T, CliError>;