use std::io;
use thiserror::Error;
use super::message::CloseFrame;
#[derive(Debug, Error)]
pub enum TransportError {
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("handshake failed: {0}")]
Handshake(String),
#[error("invalid URL: {0}")]
InvalidUrl(String),
#[error("TLS error: {0}")]
Tls(String),
#[error("protocol error: {0}")]
Protocol(String),
#[error("connection closed by peer")]
ClosedByPeer(Option<CloseFrame>),
#[error("connection closed")]
ConnectionClosed,
#[error("connection reset")]
ConnectionReset,
#[error("message too large")]
MessageTooLarge,
#[error("frame too large")]
FrameTooLarge,
#[error("invalid UTF-8 in text frame")]
InvalidUtf8,
#[error("transport error: {0}")]
Other(String),
}
impl TransportError {
#[must_use]
pub fn is_fatal(&self) -> bool {
!matches!(self, Self::InvalidUrl(_))
}
#[must_use]
pub fn is_closed(&self) -> bool {
matches!(
self,
Self::ConnectionClosed | Self::ConnectionReset | Self::ClosedByPeer(_)
)
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
fn io_error_is_fatal() {
let err = TransportError::Io(io::Error::other("boom"));
assert!(err.is_fatal());
assert!(!err.is_closed());
}
#[rstest]
fn other_error_is_fatal() {
let err = TransportError::Other("unexpected".into());
assert!(err.is_fatal());
assert!(!err.is_closed());
}
#[rstest]
fn invalid_url_is_not_fatal() {
let err = TransportError::InvalidUrl("ws://".into());
assert!(!err.is_fatal());
assert!(!err.is_closed());
}
#[rstest]
fn closed_variants_are_closed_and_fatal() {
let err = TransportError::ConnectionClosed;
assert!(err.is_fatal());
assert!(err.is_closed());
let err = TransportError::ConnectionReset;
assert!(err.is_fatal());
assert!(err.is_closed());
let err = TransportError::ClosedByPeer(Some(CloseFrame::new(1000, "bye")));
assert!(err.is_fatal());
assert!(err.is_closed());
}
#[rstest]
fn protocol_error_is_fatal() {
let err = TransportError::Protocol("bad opcode".into());
assert!(err.is_fatal());
assert!(!err.is_closed());
}
#[rstest]
fn capacity_and_handshake_variants_are_fatal() {
for err in [
TransportError::MessageTooLarge,
TransportError::FrameTooLarge,
TransportError::InvalidUtf8,
TransportError::Tls("bad".into()),
TransportError::Handshake("bad".into()),
] {
assert!(err.is_fatal(), "expected fatal: {err:?}");
assert!(!err.is_closed(), "expected not closed: {err:?}");
}
}
}