use core::time::Duration;
use gload::{
net::SendError,
request::RequestConstructError,
response::{InvalidStatusCode, ResponseParseError, StatusCode},
tls::TlsError,
};
use rustls::pki_types::ServerName;
use std::process::{ExitCode, Termination};
#[derive(Debug)]
pub(crate) enum ExitReason {
AbruptClosure,
BinaryOutputWarning,
Cancelled,
ClientCertificateNotValid,
ClientCertificateRequired,
CouldNotResolveHost(url::Host),
InappropriateHandshakeMessage,
InitialConnect(std::io::Error, ServerName<'static>, u16),
InputExpected,
NoResponse,
Open(std::io::Error),
Receive(std::io::Error),
Send(std::io::Error),
ServerCertificate(rustls::CertificateError),
ServerErrorResponse(StatusCode),
ShutdownFailed(std::io::Error),
Success,
TimedOut(ServerName<'static>, u16, Duration),
TooManyRedirects { max: u8 },
UnsupportedProtocol(String),
UrlMalformed,
WeirdServerReply(String),
WriteToFile(usize),
WriteToStdout(usize),
}
impl From<TlsError> for ExitReason {
fn from(value: TlsError) -> Self {
match value {
TlsError::InappropriateHandshakeMessage => Self::InappropriateHandshakeMessage,
TlsError::InitialConnect(err, authority, port) => {
Self::InitialConnect(err, authority, port)
}
TlsError::ClosedWithoutNotify => Self::AbruptClosure,
TlsError::NoResponse => Self::NoResponse,
TlsError::Open(err) => Self::Open(err),
TlsError::Receive(err) => Self::Receive(err),
TlsError::Send(err) => Self::Send(err),
TlsError::ServerCertificate(err) => Self::ServerCertificate(err),
TlsError::ShutdownFailed(err) => Self::ShutdownFailed(err),
TlsError::TimedOut(authority, port, duration) => {
Self::TimedOut(authority, port, duration)
}
_ => todo!("ExitReason must be updated for new values of TlsError"),
}
}
}
impl From<RequestConstructError> for ExitReason {
fn from(value: RequestConstructError) -> Self {
match value {
RequestConstructError::MissingAuthority => Self::UrlMalformed,
RequestConstructError::RequestTooLongError(_) => Self::UrlMalformed,
RequestConstructError::UnsupportedProtocol(scheme) => Self::UnsupportedProtocol(scheme),
RequestConstructError::UrlParse(_) => Self::UrlMalformed,
RequestConstructError::Userinfo => Self::UrlMalformed,
}
}
}
impl<H: ::gload::net::ResponseHandler<Error = crate::PrintingError>> From<SendError<H>>
for ExitReason
{
fn from(value: SendError<H>) -> Self {
match value {
SendError::Cancelled => Self::Cancelled,
SendError::CouldNotResolveHost(host) => Self::CouldNotResolveHost(host),
SendError::Handler(crate::PrintingError::BinaryOutput) => Self::BinaryOutputWarning,
SendError::Handler(crate::PrintingError::File(count)) => Self::WriteToFile(count),
SendError::Handler(crate::PrintingError::Stdout(count)) => Self::WriteToStdout(count),
SendError::RequestConstruct(err) => err.into(),
SendError::ResponseParse(err) => err.into(),
SendError::Tls(err) => err.into(),
SendError::TooManyRedirects { max } => Self::TooManyRedirects { max },
_ => todo!("ExitReason must be updated for new values of SendError"),
}
}
}
impl From<ResponseParseError> for ExitReason {
fn from(value: ResponseParseError) -> Self {
Self::WeirdServerReply(format!("{value}"))
}
}
impl From<InvalidStatusCode> for ExitReason {
fn from(value: InvalidStatusCode) -> Self {
Self::WeirdServerReply(format!("{value}"))
}
}
impl ExitReason {
#[cfg(not(tarpaulin_include))]
const fn raw_exit_code(&self) -> u8 {
match self {
Self::Success => 0,
Self::InputExpected | Self::UnsupportedProtocol(_) => 1,
Self::UrlMalformed => 3,
Self::CouldNotResolveHost(_) => 6,
Self::InitialConnect(_, _, _) => 7,
Self::WeirdServerReply(_) => 8,
Self::ServerErrorResponse(_) => 22,
Self::BinaryOutputWarning | Self::WriteToFile(_) | Self::WriteToStdout(_) => 23,
Self::TimedOut(_, _, _) => 28,
Self::InappropriateHandshakeMessage => 35,
Self::TooManyRedirects { .. } => 47,
Self::NoResponse => 52,
Self::Open(_) | Self::Send(_) => 55,
Self::AbruptClosure | Self::Receive(_) => 56,
Self::ShutdownFailed(_) => 80,
Self::ServerCertificate(rustls::CertificateError::UnknownIssuer) => 83,
Self::ServerCertificate(_) => 91,
Self::ClientCertificateNotValid => 94,
Self::ClientCertificateRequired => 98,
Self::Cancelled => 130, }
}
#[cfg(not(tarpaulin_include))]
fn exit_code(&self) -> ExitCode {
ExitCode::from(self.raw_exit_code())
}
}
#[cfg(not(tarpaulin_include))]
impl core::fmt::Display for ExitReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AbruptClosure => write!(f, "Failure when receiving data from the peer"),
Self::BinaryOutputWarning | Self::Cancelled | Self::Success => write!(f, ""),
Self::ClientCertificateNotValid => {
write!(f, "Authentication error; SSL Client Certificate is invalid")
}
Self::ClientCertificateRequired => write!(f, "SSL Client Certificate required"),
Self::CouldNotResolveHost(host) => write!(f, "Could not resolve host: {host}"),
Self::InappropriateHandshakeMessage => write!(f, "SSL handshake failed"),
Self::InitialConnect(err, authority, port) => write!(
f,
"Failed to connect to {} port {port}: {err}",
authority.to_str()
),
Self::InputExpected => write!(
f,
"The server response expects input, but we haven't implemented that yet"
),
Self::NoResponse => write!(f, "The server didn't send any response"),
Self::Open(err) => write!(f, "Could not open a TCP socket: {err}"),
Self::Receive(err) => write!(f, "Could not read server response: {err}"),
Self::Send(err) => write!(f, "Could not send data to server: {err}"),
Self::ServerCertificate(err) => write!(f, "SSL certificate problem: {err}"),
Self::ServerErrorResponse(status) => {
write!(f, "The requested URL returned error: {}", status.as_u8())
}
Self::ShutdownFailed(err) => write!(f, "Failed to shut down the SSL connection: {err}"),
Self::TimedOut(authority, port, duration) => write!(
f,
"Failed to connect to {} port {port} after {} ms: Could not connect to server",
authority.to_str(),
duration.as_millis()
),
Self::TooManyRedirects { max } => write!(f, "Maximum ({max}) redirects followed"),
Self::UnsupportedProtocol(scheme) => write!(f, r#"Protocol "{scheme}" not supported"#),
Self::UrlMalformed => write!(f, "URL rejected: Malformed input to a URL function"),
Self::WeirdServerReply(msg) => write!(f, "{msg}"),
Self::WriteToFile(count) => {
write!(f, "client returned ERROR on write of {count} bytes")
}
Self::WriteToStdout(count) => {
write!(f, "Failure writing {count} bytes to destination")
}
}
}
}
static CRATE_NAME: &str = clap::crate_name!();
#[cfg(not(tarpaulin_include))]
impl Termination for ExitReason {
fn report(self) -> ExitCode {
#[cfg(feature = "logging")]
if !matches!(
self,
Self::BinaryOutputWarning | Self::Cancelled | Self::Success
) {
let code = self.raw_exit_code();
::log::error!("{CRATE_NAME}: ({code}) {self}");
}
self.exit_code()
}
}