1use thiserror::Error;
2
3#[derive(Error, Debug)]
5pub enum ProtocolError {
6 #[error("Invalid message format: {0}")]
7 InvalidMessageFormat(String),
8
9 #[error("Failed to encode/decode JSON: {0}")]
10 JsonError(#[from] serde_json::Error),
11
12 #[error("Missing required field '{0}' in message")]
13 MissingField(String),
14
15 #[error("Invalid escape sequence in message")]
16 InvalidEscapeSequence,
17
18 #[error("Response envelope parsing failed: {0}")]
19 EnvelopeParseError(String),
20
21 #[error("Message payload validation failed: {0}")]
22 PayloadValidationError(String),
23
24 #[error("Unsupported protocol version: {0:?}")]
25 UnsupportedVersion(Vec<u32>),
26}
27
28#[derive(Error, Debug)]
30pub enum CommandError {
31 #[error("Invalid command name: '{0}' is not a recognized kitty command")]
32 InvalidCommand(String),
33
34 #[error("Missing required parameter '{0}' for command '{1}'")]
35 MissingParameter(String, String),
36
37 #[error("Invalid parameter value for '{0}': {1}")]
38 InvalidParameter(String, String),
39
40 #[error("Command validation failed: {0}")]
41 ValidationError(String),
42
43 #[error("Invalid window match specification: {0}")]
44 InvalidWindowMatch(String),
45
46 #[error("Invalid tab match specification: {0}")]
47 InvalidTabMatch(String),
48
49 #[error("Invalid layout specification: {0}")]
50 InvalidLayout(String),
51
52 #[error("Kitty returned error for command '{0}': {1}")]
53 KittyError(String, String),
54
55 #[error("Command execution failed with status: {0}")]
56 ExecutionFailed(String),
57
58 #[error("Async command '{0}' was cancelled")]
59 AsyncCancelled(String),
60}
61
62#[derive(Error, Debug)]
64pub enum EncryptionError {
65 #[error("Encryption not yet implemented")]
66 NotImplemented,
67
68 #[error("KITTY_PUBLIC_KEY environment variable not set")]
69 MissingPublicKey,
70
71 #[error("Failed to decode Base85 public key: {0}")]
72 InvalidPublicKey(String),
73
74 #[error("Public key too short: expected {expected} bytes, got {actual}")]
75 PublicKeyTooShort { expected: usize, actual: usize },
76
77 #[error("Encryption failed: {0}")]
78 EncryptionFailed(String),
79
80 #[error("Decryption failed: {0}")]
81 DecryptionFailed(String),
82
83 #[error("Invalid public key format")]
84 InvalidPublicKeyFormat,
85
86 #[error("Public key database query failed: {0}")]
87 PublicKeyDatabaseError(String),
88}
89
90#[derive(Error, Debug)]
92pub enum ConnectionError {
93 #[error("Failed to connect to socket '{0}': {1}")]
94 ConnectionFailed(String, #[source] std::io::Error),
95
96 #[error("Connection timeout after {0:?}")]
97 TimeoutError(std::time::Duration),
98
99 #[error("Failed to send message: {0}")]
100 SendError(String),
101
102 #[error("Failed to receive response: {0}")]
103 ReceiveError(String),
104
105 #[error("Connection closed unexpectedly")]
106 ConnectionClosed,
107
108 #[error("Socket path '{0}' does not exist")]
109 SocketNotFound(String),
110
111 #[error("Permission denied for socket '{0}'")]
112 PermissionDenied(String),
113
114 #[error("Maximum retry attempts ({0}) exceeded")]
115 MaxRetriesExceeded(usize),
116}
117
118#[derive(Error, Debug)]
123pub enum KittyError {
124 #[error("Protocol error: {0}")]
125 Protocol(#[from] ProtocolError),
126
127 #[error("Command error: {0}")]
128 Command(#[from] CommandError),
129
130 #[error("Connection error: {0}")]
131 Connection(#[from] ConnectionError),
132
133 #[error("Encryption error: {0}")]
134 Encryption(#[from] EncryptionError),
135
136 #[error("IO error: {0}")]
137 Io(#[from] std::io::Error),
138}
139
140impl From<std::io::Error> for ConnectionError {
141 fn from(err: std::io::Error) -> Self {
142 match err.kind() {
143 std::io::ErrorKind::NotFound => ConnectionError::SocketNotFound("unknown".to_string()),
144 std::io::ErrorKind::PermissionDenied => {
145 ConnectionError::PermissionDenied("unknown".to_string())
146 }
147 std::io::ErrorKind::ConnectionRefused => {
148 ConnectionError::ConnectionFailed("connection refused".to_string(), err)
149 }
150 _ => ConnectionError::ConnectionFailed("unknown".to_string(), err),
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_protocol_error_display() {
161 let err = ProtocolError::InvalidEscapeSequence;
162 assert!(err.to_string().contains("escape sequence"));
163 }
164
165 #[test]
166 fn test_command_error_display() {
167 let err = CommandError::InvalidCommand("fake-cmd".to_string());
168 assert!(err.to_string().contains("fake-cmd"));
169 }
170
171 #[test]
172 fn test_connection_error_display() {
173 let err = ConnectionError::ConnectionClosed;
174 assert_eq!(err.to_string(), "Connection closed unexpectedly");
175 }
176
177 #[test]
178 fn test_error_conversion_chain() {
179 let json_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
180 let proto_err = ProtocolError::from(json_err);
181 let kitty_err: KittyError = proto_err.into();
182
183 assert!(kitty_err.to_string().contains("JSON"));
184 }
185
186 #[test]
187 fn test_encryption_error_display() {
188 let err = EncryptionError::NotImplemented;
189 assert!(err.to_string().contains("not yet implemented"));
190 }
191
192 #[test]
193 fn test_top_level_error() {
194 let cmd_err = CommandError::ValidationError("test".to_string());
195 let kitty_err: KittyError = cmd_err.into();
196
197 assert!(kitty_err.to_string().contains("validation"));
198 }
199
200 #[test]
201 fn test_io_error_conversion() {
202 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "test");
203 let conn_err = ConnectionError::from(io_err);
204
205 match conn_err {
206 ConnectionError::SocketNotFound(_) => assert!(true),
207 _ => panic!("Expected SocketNotFound error"),
208 }
209 }
210
211 #[test]
212 fn test_missing_field_error() {
213 let err = ProtocolError::MissingField("cmd".to_string());
214 assert!(err.to_string().contains("cmd"));
215 }
216
217 #[test]
218 fn test_parameter_validation_error() {
219 let err = CommandError::MissingParameter("match".to_string(), "ls".to_string());
220 let msg = err.to_string();
221 assert!(msg.contains("match") && msg.contains("ls"));
222 }
223}