1use std::io;
4use thiserror::Error;
5
6pub type Result<T> = std::result::Result<T, Error>;
8
9#[derive(Debug, Error)]
11pub enum Error {
12 #[error("Failed to connect to {host}:{port}: {source}")]
14 ConnectionFailed {
15 host: String,
16 port: u16,
17 #[source]
18 source: io::Error,
19 },
20
21 #[error("SSH handshake failed: {0}")]
23 HandshakeFailed(#[from] ssh2::Error),
24
25 #[error("Authentication failed for user '{username}': {reason}")]
27 AuthenticationFailed { username: String, reason: String },
28
29 #[error("Failed to open SSH channel: {0}")]
31 ChannelFailed(String),
32
33 #[error("Command execution failed: {0}")]
35 ExecutionFailed(String),
36
37 #[error("I/O error: {0}")]
39 Io(#[from] io::Error),
40
41 #[error("Invalid configuration: {0}")]
43 InvalidConfig(String),
44
45 #[error("Client is not connected. Call connect() first.")]
47 NotConnected,
48
49 #[error("Private key file not found or unreadable: {path}")]
51 PrivateKeyNotFound { path: String },
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn test_error_display() {
60 let err = Error::NotConnected;
61 assert_eq!(
62 err.to_string(),
63 "Client is not connected. Call connect() first."
64 );
65 }
66
67 #[test]
68 fn test_authentication_error() {
69 let err = Error::AuthenticationFailed {
70 username: "testuser".to_string(),
71 reason: "Invalid password".to_string(),
72 };
73 assert!(err.to_string().contains("testuser"));
74 assert!(err.to_string().contains("Invalid password"));
75 }
76
77 #[test]
78 fn test_connection_error() {
79 let io_err = io::Error::new(io::ErrorKind::ConnectionRefused, "refused");
80 let err = Error::ConnectionFailed {
81 host: "example.com".to_string(),
82 port: 22,
83 source: io_err,
84 };
85 assert!(err.to_string().contains("example.com"));
86 assert!(err.to_string().contains("22"));
87 }
88}