use crate::{h3::common::H3Error, quic::cert_verifier::CertRejected, socket::BoxedSocketError};
#[derive(Debug, thiserror::Error)]
pub enum EstablishError {
#[error("invalid socket address")]
InvalidAddress,
#[error("socket error: {0}")]
Io(BoxedSocketError),
#[error("QUIC error: {0}")]
Quic(squiche::Error),
#[error("certificate rejected: {0}")]
CertificateRejected(#[source] CertRejected),
#[error("handshake failed")]
Handshake,
#[error("ALPN mismatch: expected h3")]
AlpnMismatch,
#[error("failed to initialize the HTTP/3 layer")]
H3Init,
#[error("client closed")]
Closed,
}
impl EstablishError {
#[must_use]
pub fn is_transient(&self) -> bool {
match self {
Self::InvalidAddress => false,
Self::Io(_) => true,
Self::Quic(error) => {
!matches!(
error,
squiche::Error::TlsFail
| squiche::Error::CryptoFail
| squiche::Error::UnknownVersion
| squiche::Error::InvalidTransportParam
)
}
Self::CertificateRejected(_) => false,
Self::Handshake => true,
Self::AlpnMismatch => false,
Self::H3Init => false,
Self::Closed => false,
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum RequestError {
#[error("connection establishment failed: {0}")]
Establish(#[from] EstablishError),
#[error("connection closed")]
ConnectionClosed,
#[error("client closed locally")]
LocallyClosed,
#[error("stream reset by peer (code {0:#x})")]
Reset(u64),
#[error("h3 error: {0}")]
H3(squiche::h3::Error),
#[error("request blocked: concurrent stream limit reached")]
StreamBlocked,
}
#[derive(Debug, thiserror::Error)]
pub enum UploadError {
#[error("request body error: {0}")]
Body(Box<dyn std::error::Error + Send + Sync>),
#[error("send failed: {0}")]
Send(#[from] H3Error),
}
#[cfg(test)]
mod tests {
use super::EstablishError;
#[test]
fn a_remote_that_did_not_answer_is_transient() {
assert!(EstablishError::Handshake.is_transient());
assert!(
EstablishError::Io(Box::new(std::io::Error::new(
std::io::ErrorKind::NetworkUnreachable,
"no route to host",
)))
.is_transient()
);
assert!(EstablishError::Quic(squiche::Error::InvalidPacket).is_transient());
}
#[test]
fn a_closed_client_is_permanent() {
assert!(!EstablishError::Closed.is_transient());
}
#[test]
fn a_peer_we_cannot_talk_to_is_permanent() {
assert!(!EstablishError::Quic(squiche::Error::TlsFail).is_transient());
assert!(!EstablishError::AlpnMismatch.is_transient());
assert!(!EstablishError::InvalidAddress.is_transient());
}
}