use std::fmt;
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
Image(image::ImageError),
Capture {
message: String,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
}
impl Error {
pub fn capture(message: impl Into<String>) -> Self {
Error::Capture {
message: message.into(),
source: None,
}
}
pub fn capture_from(
message: impl Into<String>,
source: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Error::Capture {
message: message.into(),
source: Some(Box::new(source)),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Io(e) => write!(f, "io error: {e}"),
Error::Image(e) => write!(f, "image error: {e}"),
Error::Capture { message, source } => match source {
Some(src) => write!(f, "capture error: {message}: {src}"),
None => write!(f, "capture error: {message}"),
},
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Io(e) => Some(e),
Error::Image(e) => Some(e),
Error::Capture { source, .. } => source
.as_ref()
.map(|src| &**src as &(dyn std::error::Error + 'static)),
}
}
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::Io(e)
}
}
impl From<image::ImageError> for Error {
fn from(e: image::ImageError) -> Self {
Error::Image(e)
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error as _;
fn root_io_kind(err: &Error) -> Option<std::io::ErrorKind> {
let mut cur = err.source();
while let Some(e) = cur {
if let Some(io) = e.downcast_ref::<std::io::Error>() {
return Some(io.kind());
}
cur = e.source();
}
None
}
#[test]
fn capture_without_source_reads_as_plain_message() {
let err = Error::capture("no display found");
assert_eq!(err.to_string(), "capture error: no display found");
assert!(err.source().is_none());
}
#[test]
fn capture_from_keeps_the_source_chain() {
let inner = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "pipewire denied");
let err = Error::capture_from("xcap Monitor::capture_image()", inner);
let text = err.to_string();
assert!(text.contains("Monitor::capture_image"), "{text}");
assert!(text.contains("pipewire denied"), "{text}");
assert_eq!(
root_io_kind(&err),
Some(std::io::ErrorKind::PermissionDenied)
);
}
}