1use std::fmt;
4use std::io;
5
6pub type Result<T> = std::result::Result<T, Error>;
8
9#[derive(Debug)]
11pub enum Error {
12 Io(io::Error),
14 InvalidFrame(&'static str),
16 InvalidUtf8,
18 Protocol(&'static str),
20 ConnectionClosed,
22 MessageTooLarge,
24 FrameTooLarge,
26 InvalidHttp(&'static str),
28 HandshakeFailed(&'static str),
30 BufferFull,
32 WouldBlock,
34 ConnectionReset,
36 InvalidState(&'static str),
38 Closed(Option<CloseReason>),
40 InvalidCloseCode(u16),
42 Capacity(&'static str),
44 Compression(String),
46
47 #[cfg(feature = "http2")]
52 Http2(h2::Error),
53
54 #[cfg(feature = "http3")]
56 Http3(String),
57
58 #[cfg(feature = "http3")]
60 Quic(quinn::ConnectionError),
61
62 #[cfg(feature = "http3")]
64 QuicWrite(quinn::WriteError),
65
66 ExtendedConnectNotSupported,
68
69 StreamReset,
71}
72
73#[derive(Debug, Clone)]
75pub struct CloseReason {
76 pub code: u16,
78 pub reason: String,
80}
81
82impl CloseReason {
83 pub const NORMAL: u16 = 1000;
85 pub const GOING_AWAY: u16 = 1001;
87 pub const PROTOCOL_ERROR: u16 = 1002;
89 pub const UNSUPPORTED: u16 = 1003;
91 pub const NO_STATUS: u16 = 1005;
93 pub const ABNORMAL: u16 = 1006;
95 pub const INVALID_PAYLOAD: u16 = 1007;
97 pub const POLICY: u16 = 1008;
99 pub const TOO_BIG: u16 = 1009;
101 pub const EXTENSION: u16 = 1010;
103 pub const INTERNAL: u16 = 1011;
105
106 pub fn new(code: u16, reason: impl Into<String>) -> Self {
108 Self {
109 code,
110 reason: reason.into(),
111 }
112 }
113
114 pub fn is_valid_code(code: u16) -> bool {
116 matches!(code, 1000..=1003 | 1007..=1011 | 3000..=4999)
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
126pub enum ErrorKind {
127 Io,
129 Protocol,
131 Handshake,
133 Validation,
135 Capacity,
137 Compression,
139 Connection,
141 Transport,
143}
144
145impl Error {
146 pub fn kind(&self) -> ErrorKind {
150 match self {
151 Error::Io(_) => ErrorKind::Io,
152 Error::InvalidFrame(_) => ErrorKind::Validation,
153 Error::InvalidUtf8 => ErrorKind::Validation,
154 Error::Protocol(_) => ErrorKind::Protocol,
155 Error::ConnectionClosed => ErrorKind::Connection,
156 Error::MessageTooLarge => ErrorKind::Capacity,
157 Error::FrameTooLarge => ErrorKind::Capacity,
158 Error::InvalidHttp(_) => ErrorKind::Handshake,
159 Error::HandshakeFailed(_) => ErrorKind::Handshake,
160 Error::BufferFull => ErrorKind::Capacity,
161 Error::WouldBlock => ErrorKind::Io,
162 Error::ConnectionReset => ErrorKind::Connection,
163 Error::InvalidState(_) => ErrorKind::Protocol,
164 Error::Closed(_) => ErrorKind::Connection,
165 Error::InvalidCloseCode(_) => ErrorKind::Validation,
166 Error::Capacity(_) => ErrorKind::Capacity,
167 Error::Compression(_) => ErrorKind::Compression,
168 #[cfg(feature = "http2")]
169 Error::Http2(_) => ErrorKind::Transport,
170 #[cfg(feature = "http3")]
171 Error::Http3(_) => ErrorKind::Transport,
172 #[cfg(feature = "http3")]
173 Error::Quic(_) => ErrorKind::Transport,
174 #[cfg(feature = "http3")]
175 Error::QuicWrite(_) => ErrorKind::Transport,
176 Error::ExtendedConnectNotSupported => ErrorKind::Handshake,
177 Error::StreamReset => ErrorKind::Connection,
178 }
179 }
180
181 #[inline]
186 pub fn is_fatal(&self) -> bool {
187 matches!(
188 self,
189 Error::ConnectionClosed
190 | Error::ConnectionReset
191 | Error::Protocol(_)
192 | Error::InvalidUtf8
193 | Error::FrameTooLarge
194 | Error::MessageTooLarge
195 | Error::StreamReset
196 | Error::InvalidCloseCode(_)
197 )
198 }
199
200 #[inline]
204 pub fn is_recoverable(&self) -> bool {
205 matches!(self, Error::WouldBlock | Error::BufferFull)
206 }
207
208 #[inline]
213 pub fn is_timeout(&self) -> bool {
214 if let Error::Io(e) = self {
215 return e.kind() == io::ErrorKind::TimedOut;
216 }
217 false
218 }
219
220 #[inline]
222 pub fn is_connection_error(&self) -> bool {
223 self.kind() == ErrorKind::Connection
224 }
225
226 #[inline]
228 pub fn is_protocol_error(&self) -> bool {
229 self.kind() == ErrorKind::Protocol
230 }
231
232 pub fn metric_name(&self) -> &'static str {
237 match self {
238 Error::Io(_) => "io_error",
239 Error::InvalidFrame(_) => "invalid_frame",
240 Error::InvalidUtf8 => "invalid_utf8",
241 Error::Protocol(_) => "protocol_error",
242 Error::ConnectionClosed => "connection_closed",
243 Error::MessageTooLarge => "message_too_large",
244 Error::FrameTooLarge => "frame_too_large",
245 Error::InvalidHttp(_) => "invalid_http",
246 Error::HandshakeFailed(_) => "handshake_failed",
247 Error::BufferFull => "buffer_full",
248 Error::WouldBlock => "would_block",
249 Error::ConnectionReset => "connection_reset",
250 Error::InvalidState(_) => "invalid_state",
251 Error::Closed(_) => "closed",
252 Error::InvalidCloseCode(_) => "invalid_close_code",
253 Error::Capacity(_) => "capacity_exceeded",
254 Error::Compression(_) => "compression_error",
255 #[cfg(feature = "http2")]
256 Error::Http2(_) => "http2_error",
257 #[cfg(feature = "http3")]
258 Error::Http3(_) => "http3_error",
259 #[cfg(feature = "http3")]
260 Error::Quic(_) => "quic_error",
261 #[cfg(feature = "http3")]
262 Error::QuicWrite(_) => "quic_write_error",
263 Error::ExtendedConnectNotSupported => "extended_connect_not_supported",
264 Error::StreamReset => "stream_reset",
265 }
266 }
267
268 pub fn suggested_http_status(&self) -> u16 {
273 match self {
274 Error::InvalidHttp(_) | Error::InvalidFrame(_) | Error::InvalidCloseCode(_) => 400, Error::Protocol(_) | Error::InvalidUtf8 => 400, Error::HandshakeFailed(_) => 400, Error::MessageTooLarge | Error::FrameTooLarge | Error::Capacity(_) => 413, Error::BufferFull => 503, Error::ExtendedConnectNotSupported => 501, _ => 500, }
282 }
283}
284
285impl fmt::Display for Error {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 match self {
288 Error::Io(e) => write!(f, "I/O error: {}", e),
289 Error::InvalidFrame(msg) => write!(f, "Invalid frame: {}", msg),
290 Error::InvalidUtf8 => write!(f, "Invalid UTF-8 in text message"),
291 Error::Protocol(msg) => write!(f, "Protocol error: {}", msg),
292 Error::ConnectionClosed => write!(f, "Connection closed"),
293 Error::MessageTooLarge => write!(f, "Message too large"),
294 Error::FrameTooLarge => write!(f, "Frame too large"),
295 Error::InvalidHttp(msg) => write!(f, "Invalid HTTP: {}", msg),
296 Error::HandshakeFailed(msg) => write!(f, "Handshake failed: {}", msg),
297 Error::BufferFull => write!(f, "Buffer full"),
298 Error::WouldBlock => write!(f, "Would block"),
299 Error::ConnectionReset => write!(f, "Connection reset by peer"),
300 Error::InvalidState(msg) => write!(f, "Invalid state: {}", msg),
301 Error::Closed(reason) => {
302 if let Some(r) = reason {
303 write!(f, "Connection closed: {} ({})", r.code, r.reason)
304 } else {
305 write!(f, "Connection closed")
306 }
307 }
308 Error::InvalidCloseCode(code) => write!(f, "Invalid close code: {}", code),
309 Error::Capacity(msg) => write!(f, "Capacity exceeded: {}", msg),
310 Error::Compression(msg) => write!(f, "Compression error: {}", msg),
311 #[cfg(feature = "http2")]
312 Error::Http2(e) => write!(f, "HTTP/2 error: {}", e),
313 #[cfg(feature = "http3")]
314 Error::Http3(msg) => write!(f, "HTTP/3 error: {}", msg),
315 #[cfg(feature = "http3")]
316 Error::Quic(e) => write!(f, "QUIC error: {}", e),
317 #[cfg(feature = "http3")]
318 Error::QuicWrite(e) => write!(f, "QUIC write error: {}", e),
319 Error::ExtendedConnectNotSupported => {
320 write!(f, "Extended CONNECT protocol not supported by server")
321 }
322 Error::StreamReset => write!(f, "Stream was reset by peer"),
323 }
324 }
325}
326
327impl std::error::Error for Error {
328 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
329 match self {
330 Error::Io(e) => Some(e),
331 #[cfg(feature = "http2")]
332 Error::Http2(e) => Some(e),
333 #[cfg(feature = "http3")]
334 Error::Quic(e) => Some(e),
335 #[cfg(feature = "http3")]
336 Error::QuicWrite(e) => Some(e),
337 _ => None,
338 }
339 }
340}
341
342impl From<io::Error> for Error {
343 fn from(e: io::Error) -> Self {
344 match e.kind() {
345 io::ErrorKind::WouldBlock => Error::WouldBlock,
346 io::ErrorKind::ConnectionReset => Error::ConnectionReset,
347 io::ErrorKind::BrokenPipe => Error::ConnectionClosed,
348 io::ErrorKind::UnexpectedEof => Error::ConnectionClosed,
349 _ => Error::Io(e),
350 }
351 }
352}
353
354impl From<Error> for io::Error {
355 fn from(e: Error) -> Self {
356 match e {
357 Error::Io(e) => e,
358 Error::WouldBlock => io::Error::new(io::ErrorKind::WouldBlock, "would block"),
359 Error::ConnectionReset => {
360 io::Error::new(io::ErrorKind::ConnectionReset, "connection reset")
361 }
362 Error::ConnectionClosed => {
363 io::Error::new(io::ErrorKind::BrokenPipe, "connection closed")
364 }
365 other => io::Error::other(other.to_string()),
366 }
367 }
368}
369
370#[cfg(feature = "http2")]
375impl From<h2::Error> for Error {
376 fn from(e: h2::Error) -> Self {
377 if e.is_io() {
378 let err_string = e.to_string();
380 if let Some(io_err) = e.into_io() {
381 return Error::Io(io_err);
382 }
383 return Error::Io(std::io::Error::other(err_string));
385 }
386 Error::Http2(e)
387 }
388}
389
390#[cfg(feature = "http3")]
391impl From<quinn::ConnectionError> for Error {
392 fn from(e: quinn::ConnectionError) -> Self {
393 Error::Quic(e)
394 }
395}
396
397#[cfg(feature = "http3")]
398impl From<quinn::WriteError> for Error {
399 fn from(e: quinn::WriteError) -> Self {
400 Error::QuicWrite(e)
401 }
402}
403
404#[cfg(feature = "http3")]
405impl From<h3::error::ConnectionError> for Error {
406 fn from(e: h3::error::ConnectionError) -> Self {
407 Error::Http3(e.to_string())
408 }
409}
410
411#[cfg(feature = "http3")]
412impl From<h3::error::StreamError> for Error {
413 fn from(e: h3::error::StreamError) -> Self {
414 Error::Http3(e.to_string())
415 }
416}