use std::error::Error;
use std::{fmt, io};
pub(crate) struct PalError {
kind: PalErrorKind,
source: Option<Box<dyn Error + Send + Sync>>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub(crate) enum PalErrorKind {
Timeout,
BreakawayDenied,
InspectFailed,
NotFound,
Disconnected,
Other,
}
impl PalError {
pub(crate) fn new(kind: PalErrorKind) -> Self {
Self { kind, source: None }
}
pub(crate) fn with_source(
kind: PalErrorKind,
source: impl Into<Box<dyn Error + Send + Sync>>,
) -> Self {
Self {
kind,
source: Some(source.into()),
}
}
pub(crate) fn kind(&self) -> PalErrorKind {
self.kind
}
pub(crate) fn from_io(error: io::Error) -> Self {
let kind = match error.kind() {
io::ErrorKind::BrokenPipe
| io::ErrorKind::ConnectionReset
| io::ErrorKind::UnexpectedEof => PalErrorKind::Disconnected,
_ => PalErrorKind::Other,
};
Self::with_source(kind, error)
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg_attr(test, mutants::skip)]
impl fmt::Debug for PalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PalError")
.field("kind", &self.kind)
.field("source", &self.source)
.finish()
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg_attr(test, mutants::skip)]
impl fmt::Display for PalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let label = match self.kind {
PalErrorKind::Timeout => "timed out",
PalErrorKind::BreakawayDenied => "breakaway denied",
PalErrorKind::InspectFailed => "failed to inspect the process",
PalErrorKind::NotFound => "not found",
PalErrorKind::Disconnected => "disconnected",
PalErrorKind::Other => "platform error",
};
f.write_str(label)
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg_attr(test, mutants::skip)]
impl Error for PalError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.source
.as_deref()
.map(|error| -> &(dyn Error + 'static) { error })
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn from_io_maps_broken_pipe_to_disconnected() {
let error = PalError::from_io(io::Error::new(io::ErrorKind::BrokenPipe, "closed"));
assert_eq!(error.kind(), PalErrorKind::Disconnected);
}
#[test]
fn from_io_maps_other_kinds_to_other() {
let error = PalError::from_io(io::Error::other("platform"));
assert_eq!(error.kind(), PalErrorKind::Other);
}
#[test]
fn a_platform_failure_keeps_what_the_platform_said() {
let error = PalError::with_source(PalErrorKind::Other, io::Error::other("CreateProcessW"));
assert!(
error
.source()
.is_some_and(|source| source.to_string().contains("CreateProcessW"))
);
}
}