Skip to main content

kcp_tokio/
error.rs

1//! Error types for KCP.
2//!
3//! [`KcpError`] extends [`kcp_core::KcpCoreError`] with I/O, timeout,
4//! config, and session variants needed by the async runtime layer.
5
6use std::fmt;
7use thiserror::Error;
8
9pub type Result<T> = std::result::Result<T, KcpError>;
10
11// ── Error types ─────────────────────────────────────────────────────────
12
13#[derive(Error, Debug)]
14pub enum KcpError {
15    #[error("I/O error: {0}")]
16    Io(#[from] std::io::Error),
17
18    #[error("Protocol error: {message}")]
19    Protocol { message: String },
20
21    #[error("Connection error: {kind}")]
22    Connection { kind: ConnectionError },
23
24    #[error("Operation timed out after {timeout_ms}ms")]
25    Timeout { timeout_ms: u64 },
26
27    #[error("Buffer error: {message}")]
28    Buffer { message: String },
29
30    #[error("Configuration error: {message}")]
31    Config { message: String },
32
33    #[error("Session error: {message}")]
34    Session { message: String },
35
36    #[error("Internal error: {message}")]
37    Internal { message: String },
38}
39
40#[derive(Debug, Clone)]
41pub enum ConnectionError {
42    Closed,
43    Reset,
44    Refused,
45    Lost,
46    InvalidConv,
47    HandshakeFailed,
48    Timeout,
49    NotConnected,
50}
51
52impl fmt::Display for ConnectionError {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            Self::Closed => write!(f, "connection closed"),
56            Self::Reset => write!(f, "connection reset by peer"),
57            Self::Refused => write!(f, "connection refused"),
58            Self::Lost => write!(f, "connection lost"),
59            Self::InvalidConv => write!(f, "invalid conversation ID"),
60            Self::HandshakeFailed => write!(f, "handshake failed"),
61            Self::Timeout => write!(f, "connection timed out"),
62            Self::NotConnected => write!(f, "not connected"),
63        }
64    }
65}
66
67// ── Bridge: kcp-core errors → KcpError ──────────────────────────────────
68
69impl From<kcp_core::KcpCoreError> for KcpError {
70    fn from(e: kcp_core::KcpCoreError) -> Self {
71        match e {
72            kcp_core::KcpCoreError::Protocol { message } => Self::Protocol { message },
73            kcp_core::KcpCoreError::Buffer { message } => Self::Buffer { message },
74            kcp_core::KcpCoreError::ConnectionLost => {
75                Self::Connection { kind: ConnectionError::Lost }
76            }
77        }
78    }
79}
80
81// ── Constructors ────────────────────────────────────────────────────────
82
83impl KcpError {
84    pub fn protocol(message: impl Into<String>) -> Self {
85        Self::Protocol { message: message.into() }
86    }
87
88    pub fn connection(kind: ConnectionError) -> Self {
89        Self::Connection { kind }
90    }
91
92    pub fn timeout(timeout_ms: u64) -> Self {
93        Self::Timeout { timeout_ms }
94    }
95
96    pub fn buffer(message: impl Into<String>) -> Self {
97        Self::Buffer { message: message.into() }
98    }
99
100    pub fn config(message: impl Into<String>) -> Self {
101        Self::Config { message: message.into() }
102    }
103
104    pub fn session(message: impl Into<String>) -> Self {
105        Self::Session { message: message.into() }
106    }
107
108    pub fn internal(message: impl Into<String>) -> Self {
109        Self::Internal { message: message.into() }
110    }
111}
112
113// ── Predicates ──────────────────────────────────────────────────────────
114
115impl KcpError {
116    pub fn is_recoverable(&self) -> bool {
117        match self {
118            Self::Io(e) => matches!(
119                e.kind(),
120                std::io::ErrorKind::WouldBlock
121                    | std::io::ErrorKind::TimedOut
122                    | std::io::ErrorKind::Interrupted
123            ),
124            Self::Timeout { .. } | Self::Buffer { .. } => true,
125            // A lost connection is terminal (see `is_fatal`), not recoverable.
126            _ => false,
127        }
128    }
129
130    pub fn is_connection_error(&self) -> bool {
131        matches!(self, Self::Connection { .. })
132    }
133
134    pub fn is_fatal(&self) -> bool {
135        matches!(
136            self,
137            Self::Connection {
138                kind: ConnectionError::Lost | ConnectionError::Closed | ConnectionError::Reset
139            } | Self::Internal { .. }
140        )
141    }
142
143    pub fn is_closed(&self) -> bool {
144        match self {
145            Self::Connection { kind } => matches!(
146                kind,
147                ConnectionError::Closed | ConnectionError::Reset | ConnectionError::Refused
148            ),
149            Self::Io(e) => matches!(
150                e.kind(),
151                std::io::ErrorKind::BrokenPipe
152                    | std::io::ErrorKind::ConnectionAborted
153                    | std::io::ErrorKind::ConnectionRefused
154                    | std::io::ErrorKind::ConnectionReset
155                    | std::io::ErrorKind::UnexpectedEof
156            ),
157            _ => false,
158        }
159    }
160}