Skip to main content

easy_install/
error.rs

1//! Crate-wide error type built on [`thiserror`].
2//!
3//! This replaces `anyhow` with an explicit error enum. The [`Result`] alias
4//! defaults to [`Error`], and the [`Context`] trait provides `anyhow`-style
5//! `.context(..)` on both `Result` and `Option`. The [`err!`] and [`bail!`]
6//! macros replace `anyhow!` / `bail!`.
7
8use thiserror::Error;
9
10/// Crate-wide result alias.
11pub type Result<T, E = Error> = std::result::Result<T, E>;
12
13/// Crate-wide error type.
14#[derive(Debug, Error)]
15pub enum Error {
16    /// A plain error message (replaces `anyhow!("...")`).
17    #[error("{0}")]
18    Msg(String),
19
20    /// A context message attached to an underlying error
21    /// (replaces `anyhow::Context::context`).
22    #[error("{context}: {inner}")]
23    WithContext {
24        /// Human-readable description of the failed operation.
25        context: String,
26        /// The underlying error.
27        inner: Box<dyn std::error::Error + Send + Sync + 'static>,
28    },
29
30    /// I/O error.
31    #[error(transparent)]
32    Io(#[from] std::io::Error),
33
34    /// HTTP client error.
35    #[error(transparent)]
36    Reqwest(#[from] reqwest::Error),
37
38    /// JSON (de)serialization error.
39    #[error(transparent)]
40    Json(#[from] serde_json::Error),
41
42    /// Archive decode/encode error.
43    #[error(transparent)]
44    Archive(#[from] easy_archive::ArchiveError),
45
46    /// Integer parsing error.
47    #[error(transparent)]
48    ParseInt(#[from] std::num::ParseIntError),
49
50    /// Failed to join a spawned task.
51    #[error(transparent)]
52    Join(#[from] tokio::task::JoinError),
53}
54
55impl Error {
56    /// Create a plain message error.
57    pub fn msg(message: impl Into<String>) -> Self {
58        Error::Msg(message.into())
59    }
60}
61
62/// `anyhow`-style context extension for `Result` and `Option`.
63pub trait Context<T> {
64    /// Wrap the error with additional context.
65    fn context(self, context: impl std::fmt::Display) -> Result<T>;
66
67    /// Lazily wrap the error with additional context.
68    fn with_context<F, D>(self, f: F) -> Result<T>
69    where
70        F: FnOnce() -> D,
71        D: std::fmt::Display;
72}
73
74impl<T, E> Context<T> for std::result::Result<T, E>
75where
76    E: std::error::Error + Send + Sync + 'static,
77{
78    fn context(self, context: impl std::fmt::Display) -> Result<T> {
79        self.map_err(|e| Error::WithContext {
80            context: context.to_string(),
81            inner: Box::new(e),
82        })
83    }
84
85    fn with_context<F, D>(self, f: F) -> Result<T>
86    where
87        F: FnOnce() -> D,
88        D: std::fmt::Display,
89    {
90        self.map_err(|e| Error::WithContext {
91            context: f().to_string(),
92            inner: Box::new(e),
93        })
94    }
95}
96
97impl<T> Context<T> for Option<T> {
98    fn context(self, context: impl std::fmt::Display) -> Result<T> {
99        self.ok_or_else(|| Error::msg(context.to_string()))
100    }
101
102    fn with_context<F, D>(self, f: F) -> Result<T>
103    where
104        F: FnOnce() -> D,
105        D: std::fmt::Display,
106    {
107        self.ok_or_else(|| Error::msg(f().to_string()))
108    }
109}
110
111/// Construct an [`Error::Msg`] (like `anyhow!`).
112macro_rules! err {
113    ($($arg:tt)*) => {
114        $crate::error::Error::msg(format!($($arg)*))
115    };
116}
117
118/// Return early with an [`Error::Msg`] (like `bail!`).
119macro_rules! bail {
120    ($($arg:tt)*) => {
121        return Err($crate::error::Error::msg(format!($($arg)*)))
122    };
123}
124
125pub(crate) use bail;
126pub(crate) use err;