1use std::fmt;
4use thiserror::Error;
5
6#[derive(Error, Debug)]
8#[non_exhaustive]
9pub enum CliError {
10 #[error("Transport error: {0}")]
12 Transport(#[from] turbomcp_protocol::Error),
13
14 #[error("Invalid arguments: {0}")]
16 InvalidArguments(String),
17
18 #[error("Server error [{code}]: {message}")]
20 ServerError { code: i32, message: String },
21
22 #[error("Operation '{operation}' timed out after {elapsed:?}")]
24 Timeout {
25 operation: String,
26 elapsed: std::time::Duration,
27 },
28
29 #[error("Client not initialized - call 'initialize' first")]
31 NotInitialized,
32
33 #[error("JSON error: {0}")]
35 Json(#[from] serde_json::Error),
36
37 #[error("YAML error: {0}")]
39 Yaml(#[from] serde_yaml::Error),
40
41 #[error("I/O error: {0}")]
43 Io(#[from] std::io::Error),
44
45 #[error("Config error: {0}")]
47 Config(#[from] config::ConfigError),
48
49 #[error("Connection failed: {0}")]
51 ConnectionFailed(String),
52
53 #[error("Feature not supported: {0}")]
55 NotSupported(String),
56
57 #[error("Security validation failed: {reason}\n{details}")]
59 SecurityViolation { reason: String, details: String },
60
61 #[error("{0}")]
63 Other(String),
64}
65
66impl CliError {
67 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 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#[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
141impl 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
160pub type CliResult<T> = Result<T, CliError>;