extern crate alloc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DecompressError {
InvalidInput,
LimitExceeded,
}
impl core::fmt::Display for DecompressError {
fn fmt(
&self,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
match self {
Self::InvalidInput => f.write_str("invalid gzip/deflate input"),
Self::LimitExceeded => f.write_str("decompressed output exceeds size limit"),
}
}
}
impl core::error::Error for DecompressError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseError {
InvalidHttpVersion,
InvalidStatusCode,
InvalidReasonPhrase,
InvalidHeaderName,
InvalidHeaderValue,
InvalidUri,
MissingCrlf,
BareCarriageReturn,
UnexpectedEndOfInput,
InvalidWhitespace,
InvalidChunkSize,
InvalidContentLength,
ConflictingFraming,
ChunkedNotFinal,
WhitespaceBeforeHeaders,
ExtraDataAfterResponse,
MissingHostHeader,
ObsoleteFoldInHeader,
InvalidTransferEncodingForStatus,
ChunkedInTeHeader,
TeHeaderMissingConnection,
MultipleHostHeaders,
InvalidHostHeaderValue,
TransferEncodingRequiresHttp11,
ChunkedAppliedMultipleTimes,
RequestTransferEncodingUnsupported,
Decompression(DecompressError),
BodyExceedsLimit(usize),
}
impl ParseError {
const fn as_str(self) -> &'static str {
match self {
Self::InvalidHttpVersion => "invalid HTTP version",
Self::InvalidStatusCode => "invalid status code",
Self::InvalidReasonPhrase => "invalid reason phrase",
Self::InvalidHeaderName => "invalid header name",
Self::InvalidHeaderValue => "invalid header value",
Self::InvalidUri => "invalid URI",
Self::MissingCrlf => "missing CRLF",
Self::BareCarriageReturn => "bare CR not allowed",
Self::UnexpectedEndOfInput => "unexpected end of input",
Self::InvalidWhitespace => "invalid whitespace",
Self::InvalidChunkSize => "invalid chunk size",
Self::InvalidContentLength => "invalid Content-Length value",
Self::ConflictingFraming => "both Transfer-Encoding and Content-Length present",
Self::ChunkedNotFinal => "chunked must be the final Transfer-Encoding",
Self::WhitespaceBeforeHeaders => "whitespace found between start-line and first header",
Self::ExtraDataAfterResponse => "extra data found after complete response",
Self::MissingHostHeader => "Host header required for HTTP/1.1 requests",
Self::ObsoleteFoldInHeader => "header value contains obs-fold (not allowed)",
Self::InvalidTransferEncodingForStatus => "Transfer-Encoding not allowed for this status code",
Self::ChunkedInTeHeader => "TE header must not contain 'chunked'",
Self::TeHeaderMissingConnection => "TE header requires 'TE' in Connection header",
Self::MultipleHostHeaders => "multiple Host headers present",
Self::InvalidHostHeaderValue => "invalid Host header value format",
Self::TransferEncodingRequiresHttp11 => "Transfer-Encoding requires HTTP/1.1 or higher",
Self::ChunkedAppliedMultipleTimes => "chunked transfer coding applied multiple times",
Self::RequestTransferEncodingUnsupported => "Transfer-Encoding on requests is unsupported; use Content-Length",
Self::Decompression(_) => "failed to decompress response body",
Self::BodyExceedsLimit(_) => "response body exceeds size limit",
}
}
}
impl core::fmt::Display for ParseError {
fn fmt(
&self,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
match self {
Self::BodyExceedsLimit(limit) => write!(f, "response body exceeds limit of {limit} bytes"),
Self::Decompression(e) => write!(f, "failed to decompress response body: {e}"),
other => f.write_str(other.as_str()),
}
}
}
impl core::error::Error for ParseError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Decompression(e) => Some(e),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DnsError {
ResolutionFailed(i32),
NoAddressesFound,
}
impl DnsError {
const fn as_str(self) -> &'static str {
match self {
Self::ResolutionFailed(_) => "DNS resolution failed",
Self::NoAddressesFound => "no addresses found for hostname",
}
}
}
impl core::fmt::Display for DnsError {
fn fmt(
&self,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
match self {
Self::ResolutionFailed(code) => write!(f, "DNS resolution failed: {code}"),
Self::NoAddressesFound => f.write_str(self.as_str()),
}
}
}
impl core::error::Error for DnsError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SocketError {
NotConnected,
ConnectionRefused,
TimedOut,
Interrupted,
InvalidAddress,
Unsupported,
OsError(i32),
}
impl SocketError {
const fn as_str(self) -> &'static str {
match self {
Self::NotConnected => "socket not connected",
Self::ConnectionRefused => "connection refused",
Self::TimedOut => "operation timed out",
Self::Interrupted => "operation interrupted",
Self::InvalidAddress => "invalid address",
Self::Unsupported => "operation not supported",
Self::OsError(_) => "OS error",
}
}
}
impl core::fmt::Display for SocketError {
fn fmt(
&self,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
match self {
Self::OsError(code) => write!(f, "OS error: {code}"),
other => f.write_str(other.as_str()),
}
}
}
impl core::error::Error for SocketError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum InvalidRequest {
FormAndBody,
CookieOctet,
ConnectUnsupported,
}
impl InvalidRequest {
const fn as_str(self) -> &'static str {
match self {
Self::FormAndBody => "cannot set both form fields and an explicit body",
Self::CookieOctet => "cookie name or value contains illegal octets",
Self::ConnectUnsupported => "CONNECT is unsupported (RFC 9112 authority-form + tunnel; ignore CL/TE on success)",
}
}
}
impl core::fmt::Display for InvalidRequest {
fn fmt(
&self,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
impl core::error::Error for InvalidRequest {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IntoStringError {
response: alloc::boxed::Box<crate::parser::Response>,
error: core::str::Utf8Error,
}
impl IntoStringError {
pub(crate) fn new(
response: crate::parser::Response,
error: core::str::Utf8Error,
) -> Self {
Self {
response: alloc::boxed::Box::new(response),
error,
}
}
#[must_use]
pub fn response(&self) -> &crate::parser::Response {
&self.response
}
#[must_use]
pub fn into_response(self) -> crate::parser::Response {
*self.response
}
#[must_use]
pub const fn utf8_error(&self) -> core::str::Utf8Error {
self.error
}
}
impl core::fmt::Display for IntoStringError {
fn fmt(
&self,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
write!(f, "response body is not valid UTF-8: {}", self.error)
}
}
impl core::error::Error for IntoStringError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.error)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
Parse(ParseError),
Dns(DnsError),
Socket(SocketError),
InvalidUrl,
TooManyRedirects,
MissingRedirectLocation,
RedirectLoop,
RedirectFailed,
HttpStatus(u16, alloc::boxed::Box<crate::parser::Response>),
HttpsOnly,
TlsNotConfigured,
ResponseHeaderTooLarge,
BodyExceedsLimit(usize),
Utf8Error(core::str::Utf8Error),
InvalidRequest(InvalidRequest),
}
impl Error {
const fn as_str(&self) -> &'static str {
match self {
Self::Parse(_) => "parse error",
Self::Dns(_) => "DNS error",
Self::Socket(_) => "socket error",
Self::InvalidUrl => "invalid URL",
Self::TooManyRedirects => "too many redirects",
Self::MissingRedirectLocation => "redirect missing Location header",
Self::RedirectLoop => "redirect loop detected",
Self::RedirectFailed => "redirect failed",
Self::HttpStatus(_, _) => "HTTP status",
Self::HttpsOnly => "HTTPS-only policy rejected non-HTTPS URL",
Self::TlsNotConfigured => {
"TLS not configured: use a TLS-capable BlockingSocket with assume_tls_socket, or http://"
},
Self::ResponseHeaderTooLarge => "response headers too large",
Self::BodyExceedsLimit(_) => "response body exceeds size limit",
Self::Utf8Error(_) => "invalid UTF-8",
Self::InvalidRequest(_) => "invalid request",
}
}
}
impl core::fmt::Display for Error {
fn fmt(
&self,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
match self {
Self::Parse(e) => write!(f, "parse error: {e}"),
Self::Dns(e) => write!(f, "DNS error: {e}"),
Self::Socket(e) => write!(f, "socket error: {e}"),
Self::HttpStatus(code, _) => write!(f, "HTTP status {code}"),
Self::BodyExceedsLimit(limit) => write!(f, "response body exceeds limit of {limit} bytes"),
Self::Utf8Error(e) => write!(f, "invalid UTF-8: {e}"),
Self::InvalidRequest(e) => write!(f, "invalid request: {e}"),
other => f.write_str(other.as_str()),
}
}
}
impl core::error::Error for Error {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Parse(e) => Some(e),
Self::Dns(e) => Some(e),
Self::Socket(e) => Some(e),
Self::Utf8Error(e) => Some(e),
Self::InvalidRequest(e) => Some(e),
_ => None,
}
}
}
impl From<ParseError> for Error {
fn from(value: ParseError) -> Self {
match value {
ParseError::BodyExceedsLimit(n) => Self::BodyExceedsLimit(n),
other => Self::Parse(other),
}
}
}
impl From<DnsError> for Error {
fn from(value: DnsError) -> Self {
Self::Dns(value)
}
}
impl From<SocketError> for Error {
fn from(value: SocketError) -> Self {
Self::Socket(value)
}
}
impl From<InvalidRequest> for Error {
fn from(value: InvalidRequest) -> Self {
Self::InvalidRequest(value)
}
}
impl From<core::str::Utf8Error> for Error {
fn from(value: core::str::Utf8Error) -> Self {
Self::Utf8Error(value)
}
}
impl From<IntoStringError> for Error {
fn from(value: IntoStringError) -> Self {
Self::Utf8Error(value.utf8_error())
}
}