Define error types for transport, auth, channel, and config layers
This commit is contained in:
@@ -0,0 +1,153 @@
|
|||||||
|
use std::io;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum TransportError {
|
||||||
|
#[error("connection failed")]
|
||||||
|
ConnectionFailed,
|
||||||
|
#[error("handshake failed")]
|
||||||
|
HandshakeFailed {
|
||||||
|
#[source]
|
||||||
|
source: io::Error,
|
||||||
|
},
|
||||||
|
#[error("transport timeout")]
|
||||||
|
Timeout,
|
||||||
|
#[error("proxy failed")]
|
||||||
|
ProxyFailed {
|
||||||
|
#[source]
|
||||||
|
source: io::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum AuthError {
|
||||||
|
#[error("key rejected")]
|
||||||
|
KeyRejected,
|
||||||
|
#[error("certificate invalid")]
|
||||||
|
CertInvalid,
|
||||||
|
#[error("certificate expired")]
|
||||||
|
CertExpired,
|
||||||
|
#[error("certificate principal mismatch")]
|
||||||
|
CertPrincipalMismatch,
|
||||||
|
#[error("no matching key")]
|
||||||
|
NoMatchingKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum ChannelError {
|
||||||
|
#[error("target unreachable")]
|
||||||
|
TargetUnreachable,
|
||||||
|
#[error("proxy connect failed")]
|
||||||
|
ProxyConnectFailed {
|
||||||
|
#[source]
|
||||||
|
source: io::Error,
|
||||||
|
},
|
||||||
|
#[error("channel closed")]
|
||||||
|
ChannelClosed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum ConfigError {
|
||||||
|
#[error("invalid flag: {name}")]
|
||||||
|
InvalidFlag { name: String },
|
||||||
|
#[error("key file not found: {path}")]
|
||||||
|
KeyFileNotFound { path: String },
|
||||||
|
#[error("bind failed")]
|
||||||
|
BindFailed {
|
||||||
|
#[source]
|
||||||
|
source: io::Error,
|
||||||
|
},
|
||||||
|
#[error("incompatible options")]
|
||||||
|
IncompatibleOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transport_error_display() {
|
||||||
|
assert_eq!(TransportError::ConnectionFailed.to_string(), "connection failed");
|
||||||
|
assert_eq!(
|
||||||
|
TransportError::HandshakeFailed {
|
||||||
|
source: io::Error::new(io::ErrorKind::ConnectionRefused, "tls failed")
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
"handshake failed"
|
||||||
|
);
|
||||||
|
assert_eq!(TransportError::Timeout.to_string(), "transport timeout");
|
||||||
|
assert_eq!(
|
||||||
|
TransportError::ProxyFailed {
|
||||||
|
source: io::Error::new(io::ErrorKind::ConnectionRefused, "proxy err")
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
"proxy failed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auth_error_display() {
|
||||||
|
assert_eq!(AuthError::KeyRejected.to_string(), "key rejected");
|
||||||
|
assert_eq!(AuthError::CertInvalid.to_string(), "certificate invalid");
|
||||||
|
assert_eq!(AuthError::CertExpired.to_string(), "certificate expired");
|
||||||
|
assert_eq!(AuthError::CertPrincipalMismatch.to_string(), "certificate principal mismatch");
|
||||||
|
assert_eq!(AuthError::NoMatchingKey.to_string(), "no matching key");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn channel_error_display() {
|
||||||
|
assert_eq!(ChannelError::TargetUnreachable.to_string(), "target unreachable");
|
||||||
|
assert_eq!(
|
||||||
|
ChannelError::ProxyConnectFailed {
|
||||||
|
source: io::Error::new(io::ErrorKind::ConnectionRefused, "refused")
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
"proxy connect failed"
|
||||||
|
);
|
||||||
|
assert_eq!(ChannelError::ChannelClosed.to_string(), "channel closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_error_display() {
|
||||||
|
assert_eq!(
|
||||||
|
ConfigError::InvalidFlag {
|
||||||
|
name: "--bad".to_string()
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
"invalid flag: --bad"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ConfigError::KeyFileNotFound {
|
||||||
|
path: "/missing".to_string()
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
"key file not found: /missing"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ConfigError::BindFailed {
|
||||||
|
source: io::Error::new(io::ErrorKind::AddrInUse, "in use")
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
"bind failed"
|
||||||
|
);
|
||||||
|
assert_eq!(ConfigError::IncompatibleOptions.to_string(), "incompatible options");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_source_chaining() {
|
||||||
|
let io_err = io::Error::new(io::ErrorKind::ConnectionRefused, "refused");
|
||||||
|
let transport_err = TransportError::HandshakeFailed { source: io_err };
|
||||||
|
assert!(transport_err.source().is_some());
|
||||||
|
|
||||||
|
let io_err = io::Error::new(io::ErrorKind::ConnectionRefused, "proxy");
|
||||||
|
let channel_err = ChannelError::ProxyConnectFailed { source: io_err };
|
||||||
|
assert!(channel_err.source().is_some());
|
||||||
|
|
||||||
|
let io_err = io::Error::new(io::ErrorKind::AddrInUse, "addr");
|
||||||
|
let config_err = ConfigError::BindFailed { source: io_err };
|
||||||
|
assert!(config_err.source().is_some());
|
||||||
|
|
||||||
|
let plain = AuthError::KeyRejected;
|
||||||
|
assert!(plain.source().is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,3 +4,8 @@ pub mod server;
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod socks5;
|
pub mod socks5;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
|
#[cfg(feature = "testutil")]
|
||||||
|
pub mod testutil;
|
||||||
|
|
||||||
|
pub use error::{AuthError, ChannelError, ConfigError, TransportError};
|
||||||
Reference in New Issue
Block a user