Skip to main content

http3/error/
error.rs

1//! Public error types for the `http3` crate.
2use super::{codes::Code, internal_error::InternalConnectionError};
3use crate::quic::{ConnectionErrorIncoming, StreamId};
4
5/// This enum represents the closure of a connection because of an a closed quic connection
6/// This can be either from this endpoint because of a violation of the protocol or from the remote
7/// endpoint
8///
9/// When the code [`Code::H3_NO_ERROR`] is used bei this peer or the remote peer, the connection is
10/// closed without an error according to the [HTTP/3 specification](https://www.rfc-editor.org/rfc/rfc9114.html#name-http-3-error-codes)
11#[derive(Debug, Clone)]
12#[non_exhaustive]
13pub enum ConnectionError {
14    /// The error occurred on the local side of the connection
15    #[cfg_attr(not(feature = "unstable"), non_exhaustive)]
16    Local {
17        /// The error
18        error: LocalError,
19    },
20    /// Error returned by the quic layer
21    /// A QUIC error occurred or the remote HTTP/3 endpoint closed the connection.
22    #[cfg_attr(not(feature = "unstable"), non_exhaustive)]
23    Remote(ConnectionErrorIncoming),
24    /// Timeout occurred
25    #[cfg_attr(not(feature = "unstable"), non_exhaustive)]
26    Timeout,
27}
28
29impl ConnectionError {
30    /// Returns if the error is H3_NO_ERROR local or remote
31    pub fn is_h3_no_error(&self) -> bool {
32        match self {
33            ConnectionError::Local {
34                error:
35                    LocalError::Application {
36                        code: Code::H3_NO_ERROR,
37                        ..
38                    },
39            } => true,
40            ConnectionError::Remote(ConnectionErrorIncoming::ApplicationClose { error_code })
41                if *error_code == Code::H3_NO_ERROR.value() =>
42            {
43                true
44            }
45            _ => false,
46        }
47    }
48}
49
50/// This enum represents a local error
51#[derive(Debug, Clone, Hash)]
52#[non_exhaustive]
53pub enum LocalError {
54    #[non_exhaustive]
55    /// The application closed the connection
56    Application {
57        /// The error code
58        code: Code,
59        /// The error reason
60        reason: String,
61    },
62    #[non_exhaustive]
63    /// Graceful closing of the connection initiated by the local peer
64    Closing,
65}
66
67/// This enum represents a stream error
68#[derive(Debug)]
69#[non_exhaustive]
70pub enum StreamError {
71    /// A send operation is not valid in the current local stream state.
72    /// No frame is queued and the connection remains usable.
73    #[cfg_attr(not(feature = "unstable"), non_exhaustive)]
74    InvalidStreamState {
75        /// Why the operation cannot be performed.
76        reason: Box<str>,
77    },
78    /// A locally constructed request is invalid. No request was sent and the
79    /// connection remains usable.
80    #[cfg_attr(not(feature = "unstable"), non_exhaustive)]
81    InvalidRequest {
82        /// Why the request could not be constructed.
83        reason: Box<str>,
84    },
85    /// The server's GOAWAY guarantees this request was not processed.
86    #[cfg_attr(not(feature = "unstable"), non_exhaustive)]
87    GoawayRejected {
88        /// The rejected request stream.
89        stream_id: StreamId,
90        /// The first stream the server will not process.
91        boundary: StreamId,
92    },
93    /// The error occurred on the stream
94    #[cfg_attr(not(feature = "unstable"), non_exhaustive)]
95    StreamError {
96        /// The error code
97        code: Code,
98        /// The error reason
99        reason: String,
100    },
101    /// The remote peer terminated the corresponding stream side
102    ///
103    /// Either Reset on peers sending side or StopSending on peers receiving side
104    #[cfg_attr(not(feature = "unstable"), non_exhaustive)]
105    RemoteTerminate {
106        /// Reset code received from the peer
107        code: Code,
108    },
109    /// The error occurred on the connection
110    #[cfg_attr(not(feature = "unstable"), non_exhaustive)]
111    ConnectionError(ConnectionError),
112    /// A field section exceeds the configured size limit.
113    ///
114    /// On send, the peer's advertised limit would be exceeded. On receive, the
115    /// decoded field section is larger than the local limit.
116    ///
117    /// See [RFC 9114 Section 4.2.2](https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2.2).
118    #[cfg_attr(not(feature = "unstable"), non_exhaustive)]
119    HeaderTooBig {
120        /// The uncompressed size of the field section
121        actual_size: u64,
122        /// The applicable field section size limit
123        max_size: u64,
124    },
125    /// The connection is closing and cannot open a new request or server push.
126    ///
127    /// This can follow either a peer GOAWAY or local shutdown.
128    /// Existing requests below the peer's GOAWAY boundary can still complete.
129    #[cfg_attr(not(feature = "unstable"), non_exhaustive)]
130    ConnectionClosing,
131    /// Undefined error propagated by the quic layer
132    #[cfg_attr(not(feature = "unstable"), non_exhaustive)]
133    Undefined(Box<dyn std::error::Error + Send + Sync>),
134}
135
136impl StreamError {
137    /// Returns if the error is H3_NO_ERROR
138    pub fn is_h3_no_error(&self) -> bool {
139        match self {
140            StreamError::StreamError {
141                code: Code::H3_NO_ERROR,
142                ..
143            }
144            | StreamError::RemoteTerminate {
145                code: Code::H3_NO_ERROR,
146            } => true,
147            StreamError::ConnectionError(conn_error) => conn_error.is_h3_no_error(),
148            _ => false,
149        }
150    }
151}
152
153impl From<InternalConnectionError> for LocalError {
154    fn from(err: InternalConnectionError) -> Self {
155        LocalError::Application {
156            code: err.code,
157            reason: err.message,
158        }
159    }
160}
161
162impl std::fmt::Display for ConnectionError {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        match self {
165            ConnectionError::Local { error } => write!(f, "Local error: {:?}", error),
166            ConnectionError::Remote(err) => write!(f, "Remote error: {}", err),
167            ConnectionError::Timeout => write!(f, "Timeout"),
168        }
169    }
170}
171
172impl std::error::Error for ConnectionError {}
173
174impl std::fmt::Display for StreamError {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        match self {
177            StreamError::InvalidStreamState { reason } => {
178                write!(f, "Invalid stream state: {reason}")
179            }
180            StreamError::InvalidRequest { reason } => write!(f, "Invalid request: {reason}"),
181            StreamError::GoawayRejected {
182                stream_id,
183                boundary,
184            } => {
185                write!(
186                    f,
187                    "Request {stream_id} rejected by GOAWAY boundary {boundary}"
188                )
189            }
190            StreamError::StreamError { code, reason } => {
191                write!(f, "Stream error: {:?} - {}", code, reason)
192            }
193            StreamError::ConnectionError(err) => write!(f, "Connection error: {}", err),
194            StreamError::RemoteTerminate { code } => write!(f, "Remote reset: {}", code),
195            StreamError::HeaderTooBig {
196                actual_size,
197                max_size,
198            } => write!(
199                f,
200                "Header too big: actual size: {}, max size: {}",
201                actual_size, max_size
202            ),
203            StreamError::Undefined(err) => write!(f, "Undefined error: {}", err),
204            StreamError::ConnectionClosing => write!(f, "Connection is closing"),
205        }
206    }
207}
208
209impl std::error::Error for StreamError {}