Skip to main content

kitty_rc/
error.rs

1use thiserror::Error;
2
3/// Errors related to protocol message framing, encoding, and decoding
4#[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/// Errors related to command construction, validation, and execution
29#[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/// Errors related to encryption and decryption
63#[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/// Errors related to connection, transport, and I/O
91#[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/// Top-level error type for the kitty-rc-proto library
119///
120/// This enum encompasses all possible error types that can occur when
121/// interacting with the kitty terminal emulator via the remote control protocol.
122#[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}