turbomcp_cli/
error.rs

1//! Enhanced error types for CLI operations
2
3use std::fmt;
4use thiserror::Error;
5
6/// CLI-specific errors with rich context
7#[derive(Error, Debug)]
8#[non_exhaustive]
9pub enum CliError {
10    /// Transport layer errors
11    #[error("Transport error: {0}")]
12    Transport(#[from] turbomcp_protocol::Error),
13
14    /// Invalid command arguments
15    #[error("Invalid arguments: {0}")]
16    InvalidArguments(String),
17
18    /// Server returned an error
19    #[error("Server error [{code}]: {message}")]
20    ServerError { code: i32, message: String },
21
22    /// Operation timed out
23    #[error("Operation '{operation}' timed out after {elapsed:?}")]
24    Timeout {
25        operation: String,
26        elapsed: std::time::Duration,
27    },
28
29    /// Client not initialized
30    #[error("Client not initialized - call 'initialize' first")]
31    NotInitialized,
32
33    /// JSON parsing error
34    #[error("JSON error: {0}")]
35    Json(#[from] serde_json::Error),
36
37    /// YAML parsing error
38    #[error("YAML error: {0}")]
39    Yaml(#[from] serde_yaml::Error),
40
41    /// I/O error
42    #[error("I/O error: {0}")]
43    Io(#[from] std::io::Error),
44
45    /// Configuration error
46    #[error("Config error: {0}")]
47    Config(#[from] config::ConfigError),
48
49    /// Connection failed
50    #[error("Connection failed: {0}")]
51    ConnectionFailed(String),
52
53    /// Feature not supported
54    #[error("Feature not supported: {0}")]
55    NotSupported(String),
56
57    /// Security validation error (path traversal, invalid filenames, etc.)
58    #[error("Security validation failed: {reason}\n{details}")]
59    SecurityViolation { reason: String, details: String },
60
61    /// Generic error
62    #[error("{0}")]
63    Other(String),
64}
65
66impl CliError {
67    /// Get user-friendly suggestions for resolving the error
68    pub fn suggestions(&self) -> Vec<&'static str> {
69        match self {
70            Self::ConnectionFailed(_) => vec![
71                "Check if the server is running",
72                "Verify the connection URL",
73                "Use --transport to specify transport explicitly",
74            ],
75            Self::NotInitialized => vec![
76                "Ensure the server is started before calling operations",
77                "Check server logs for initialization errors",
78            ],
79            Self::Timeout { .. } => vec![
80                "Increase timeout with --timeout flag",
81                "Check server responsiveness",
82                "Verify network connectivity",
83            ],
84            Self::InvalidArguments(_) => vec![
85                "Check argument format (must be valid JSON)",
86                "Use --help to see expected format",
87            ],
88            _ => vec![],
89        }
90    }
91
92    /// Get the error category for colored output
93    pub fn category(&self) -> ErrorCategory {
94        match self {
95            Self::Transport(_) | Self::ConnectionFailed(_) => ErrorCategory::Connection,
96            Self::InvalidArguments(_) => ErrorCategory::User,
97            Self::ServerError { .. } => ErrorCategory::Server,
98            Self::Timeout { .. } => ErrorCategory::Timeout,
99            Self::Json(_) | Self::Yaml(_) => ErrorCategory::Parsing,
100            Self::Io(_) => ErrorCategory::System,
101            Self::Config(_) => ErrorCategory::Config,
102            Self::NotSupported(_) => ErrorCategory::NotSupported,
103            Self::SecurityViolation { .. } => ErrorCategory::Security,
104            _ => ErrorCategory::Other,
105        }
106    }
107}
108
109/// Error categories for colored output
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum ErrorCategory {
112    Connection,
113    User,
114    Server,
115    Timeout,
116    Parsing,
117    System,
118    Config,
119    NotSupported,
120    Security,
121    Other,
122}
123
124impl fmt::Display for ErrorCategory {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        match self {
127            Self::Connection => write!(f, "Connection"),
128            Self::User => write!(f, "User Input"),
129            Self::Server => write!(f, "Server"),
130            Self::Timeout => write!(f, "Timeout"),
131            Self::Parsing => write!(f, "Parsing"),
132            Self::System => write!(f, "System"),
133            Self::Config => write!(f, "Configuration"),
134            Self::NotSupported => write!(f, "Not Supported"),
135            Self::Security => write!(f, "Security"),
136            Self::Other => write!(f, "Error"),
137        }
138    }
139}
140
141/// Helper for creating CliError from strings
142impl From<String> for CliError {
143    fn from(s: String) -> Self {
144        Self::Other(s)
145    }
146}
147
148impl From<&str> for CliError {
149    fn from(s: &str) -> Self {
150        Self::Other(s.to_string())
151    }
152}
153
154impl From<Box<turbomcp_protocol::Error>> for CliError {
155    fn from(err: Box<turbomcp_protocol::Error>) -> Self {
156        Self::Transport(*err)
157    }
158}
159
160/// Result type for CLI operations
161pub type CliResult<T> = Result<T, CliError>;