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("{0}")]
59 Other(String),
60}
61
62impl CliError {
63 pub fn suggestions(&self) -> Vec<&'static str> {
65 match self {
66 Self::ConnectionFailed(_) => vec![
67 "Check if the server is running",
68 "Verify the connection URL",
69 "Use --transport to specify transport explicitly",
70 ],
71 Self::NotInitialized => vec![
72 "Ensure the server is started before calling operations",
73 "Check server logs for initialization errors",
74 ],
75 Self::Timeout { .. } => vec![
76 "Increase timeout with --timeout flag",
77 "Check server responsiveness",
78 "Verify network connectivity",
79 ],
80 Self::InvalidArguments(_) => vec![
81 "Check argument format (must be valid JSON)",
82 "Use --help to see expected format",
83 ],
84 _ => vec![],
85 }
86 }
87
88 pub fn category(&self) -> ErrorCategory {
90 match self {
91 Self::Transport(_) | Self::ConnectionFailed(_) => ErrorCategory::Connection,
92 Self::InvalidArguments(_) => ErrorCategory::User,
93 Self::ServerError { .. } => ErrorCategory::Server,
94 Self::Timeout { .. } => ErrorCategory::Timeout,
95 Self::Json(_) | Self::Yaml(_) => ErrorCategory::Parsing,
96 Self::Io(_) => ErrorCategory::System,
97 Self::Config(_) => ErrorCategory::Config,
98 Self::NotSupported(_) => ErrorCategory::NotSupported,
99 _ => ErrorCategory::Other,
100 }
101 }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum ErrorCategory {
107 Connection,
108 User,
109 Server,
110 Timeout,
111 Parsing,
112 System,
113 Config,
114 NotSupported,
115 Other,
116}
117
118impl fmt::Display for ErrorCategory {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 match self {
121 Self::Connection => write!(f, "Connection"),
122 Self::User => write!(f, "User Input"),
123 Self::Server => write!(f, "Server"),
124 Self::Timeout => write!(f, "Timeout"),
125 Self::Parsing => write!(f, "Parsing"),
126 Self::System => write!(f, "System"),
127 Self::Config => write!(f, "Configuration"),
128 Self::NotSupported => write!(f, "Not Supported"),
129 Self::Other => write!(f, "Error"),
130 }
131 }
132}
133
134impl From<String> for CliError {
136 fn from(s: String) -> Self {
137 Self::Other(s)
138 }
139}
140
141impl From<&str> for CliError {
142 fn from(s: &str) -> Self {
143 Self::Other(s.to_string())
144 }
145}
146
147impl From<Box<turbomcp_protocol::Error>> for CliError {
148 fn from(err: Box<turbomcp_protocol::Error>) -> Self {
149 Self::Transport(*err)
150 }
151}
152
153pub type CliResult<T> = Result<T, CliError>;