Skip to main content

akv_cli/
error.rs

1// Copyright 2024 Heath Stewart.
2// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
3
4//! Crate errors.
5
6use std::{
7    borrow::{Borrow, Cow},
8    convert::Infallible,
9    fmt,
10};
11
12/// Crate-specific `Result`.
13pub type Result<T> = std::result::Result<T, Error>;
14
15/// The kind of [`Error`].
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub enum ErrorKind {
18    /// Invalid data.
19    InvalidData,
20
21    /// I/O error.
22    Io,
23
24    /// Other error.
25    Other,
26}
27
28impl fmt::Display for ErrorKind {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        // cspell:ignore errno
31        match self {
32            ErrorKind::InvalidData => f.write_str("InvalidData"),
33            ErrorKind::Io => f.write_str("Io"),
34            ErrorKind::Other => f.write_str("Other"),
35        }
36    }
37}
38
39/// Crate-specific `Error`.
40#[derive(Debug)]
41pub struct Error {
42    repr: Repr,
43}
44
45impl Error {
46    /// Constructs a new `Error` boxing another [`std::error::Error`].
47    pub fn new<E>(kind: ErrorKind, error: E) -> Self
48    where
49        E: Into<Box<dyn std::error::Error + Send + Sync>>,
50    {
51        Self {
52            repr: Repr::Custom(Custom {
53                kind,
54                error: error.into(),
55            }),
56        }
57    }
58
59    /// The [`ErrorKind`] of this `Error`.
60    pub fn kind(&self) -> &ErrorKind {
61        match &self.repr {
62            Repr::Simple(kind)
63            | Repr::SimpleMessage(kind, ..)
64            | Repr::Custom(Custom { kind, .. })
65            | Repr::CustomMessage(Custom { kind, .. }, ..) => kind,
66        }
67    }
68
69    /// The message provided when this `Error` was constructed, or `None`.
70    pub fn message(&self) -> Option<&str> {
71        match &self.repr {
72            Repr::SimpleMessage(_, message) | Repr::CustomMessage(_, message) => {
73                Some(message.borrow())
74            }
75            _ => None,
76        }
77    }
78
79    /// Create an `Error` with a message.
80    #[must_use]
81    pub fn with_message<C>(kind: ErrorKind, message: C) -> Self
82    where
83        C: Into<Cow<'static, str>>,
84    {
85        Self {
86            repr: Repr::SimpleMessage(kind, message.into()),
87        }
88    }
89
90    /// Create an `Error` with a function that returns a message.
91    #[must_use]
92    pub fn with_message_fn<F, C>(kind: ErrorKind, message: F) -> Self
93    where
94        Self: Sized,
95        F: FnOnce() -> C,
96        C: Into<Cow<'static, str>>,
97    {
98        Self::with_message(kind, message())
99    }
100
101    /// Create an `Error` that wraps another [`Error`](std::error::Error) and a message.
102    #[must_use]
103    pub fn with_error<E, C>(kind: ErrorKind, error: E, message: C) -> Self
104    where
105        E: Into<Box<dyn std::error::Error + Send + Sync>>,
106        C: Into<Cow<'static, str>>,
107    {
108        Self {
109            repr: Repr::CustomMessage(
110                Custom {
111                    kind,
112                    error: error.into(),
113                },
114                message.into(),
115            ),
116        }
117    }
118
119    #[must_use]
120    /// Create an `Error` that wraps another [`Error`](std::error::Error) and a function that returns a message.
121    pub fn with_error_fn<E, F, C>(kind: ErrorKind, error: E, message: F) -> Self
122    where
123        E: Into<Box<dyn std::error::Error + Send + Sync>>,
124        F: FnOnce() -> C,
125        C: Into<Cow<'static, str>>,
126    {
127        Self::with_error(kind, error, message())
128    }
129}
130
131impl fmt::Display for Error {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        match &self.repr {
134            Repr::Simple(kind) => write!(f, "{kind}"),
135            Repr::SimpleMessage(_, message) => write!(f, "{message}"),
136            Repr::Custom(Custom { error, .. }) => {
137                if f.alternate() {
138                    return display(f, &**error);
139                }
140                write!(f, "{error}")
141            }
142            Repr::CustomMessage(Custom { error, .. }, message) => {
143                if f.alternate() {
144                    write!(f, "{message}: ")?;
145                    return display(f, &**error);
146                }
147                write!(f, "{message}")
148            }
149        }
150    }
151}
152
153fn display(f: &mut fmt::Formatter<'_>, error: &(dyn std::error::Error + 'static)) -> fmt::Result {
154    write!(f, "{error}")?;
155
156    if let Some(source) = error.source() {
157        write!(f, ": ")?;
158        display(f, source)?;
159    }
160
161    Ok(())
162}
163
164impl std::error::Error for Error {
165    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
166        match &self.repr {
167            Repr::Custom(Custom { error, .. }) | Repr::CustomMessage(Custom { error, .. }, ..) => {
168                Some(&**error)
169            }
170            _ => None,
171        }
172    }
173}
174
175impl From<ErrorKind> for Error {
176    fn from(kind: ErrorKind) -> Self {
177        Self {
178            repr: Repr::Simple(kind),
179        }
180    }
181}
182
183impl From<String> for Error {
184    fn from(value: String) -> Self {
185        Self::with_message(ErrorKind::Other, value)
186    }
187}
188
189impl From<Infallible> for Error {
190    fn from(_: Infallible) -> Self {
191        panic!("inconceivable")
192    }
193}
194
195impl From<std::io::Error> for Error {
196    fn from(error: std::io::Error) -> Self {
197        Self::new(ErrorKind::Io, error)
198    }
199}
200
201impl From<std::num::ParseIntError> for Error {
202    fn from(error: std::num::ParseIntError) -> Self {
203        Self::new(ErrorKind::InvalidData, error)
204    }
205}
206
207impl From<std::env::VarError> for Error {
208    fn from(error: std::env::VarError) -> Self {
209        Self::new(ErrorKind::Other, error)
210    }
211}
212
213impl From<azure_core::Error> for Error {
214    fn from(error: azure_core::Error) -> Self {
215        Self::new(ErrorKind::Other, error)
216    }
217}
218
219impl From<dotenvy::Error> for Error {
220    fn from(error: dotenvy::Error) -> Self {
221        Self::new(ErrorKind::Other, error)
222    }
223}
224
225impl From<aws_lc_rs::error::Unspecified> for Error {
226    fn from(error: aws_lc_rs::error::Unspecified) -> Self {
227        Self::new(ErrorKind::Other, error)
228    }
229}
230
231impl From<serde_json::Error> for Error {
232    fn from(error: serde_json::Error) -> Self {
233        Self::new(ErrorKind::Io, error)
234    }
235}
236
237impl From<url::ParseError> for Error {
238    fn from(error: url::ParseError) -> Self {
239        Self::new(ErrorKind::InvalidData, error)
240    }
241}
242
243#[derive(Debug)]
244enum Repr {
245    Simple(ErrorKind),
246    SimpleMessage(ErrorKind, Cow<'static, str>),
247    Custom(Custom),
248    CustomMessage(Custom, Cow<'static, str>),
249}
250
251#[derive(Debug)]
252struct Custom {
253    kind: ErrorKind,
254    error: Box<dyn std::error::Error + Send + Sync>,
255}
256
257/// Extension methods for [`Result`](std::result::Result)s.
258pub trait ResultExt<T>: private::Sealed {
259    /// Wrap an [`Error`](std::error::Error) with an [`ErrorKind`].
260    fn with_kind(self, kind: ErrorKind) -> Result<T>;
261
262    /// Wrap an [`Error`](std::error::Error) with an [`ErrorKind`] and message.
263    fn with_context<C>(self, kind: ErrorKind, message: C) -> Result<T>
264    where
265        Self: Sized,
266        C: Into<Cow<'static, str>>;
267
268    /// Wrap an [`Error`](std::error::Error) with an [`ErrorKind`] and a function that returns a message.
269    fn with_context_fn<F, C>(self, kind: ErrorKind, f: F) -> Result<T>
270    where
271        Self: Sized,
272        F: FnOnce() -> C,
273        C: Into<Cow<'static, str>>;
274}
275
276impl<T, E> ResultExt<T> for std::result::Result<T, E>
277where
278    E: std::error::Error + Send + Sync + 'static,
279{
280    fn with_kind(self, kind: ErrorKind) -> Result<T> {
281        self.map_err(|err| Error::new(kind, err))
282    }
283
284    fn with_context<C>(self, kind: ErrorKind, message: C) -> Result<T>
285    where
286        Self: Sized,
287        C: Into<Cow<'static, str>>,
288    {
289        self.map_err(|err| Error::with_error(kind, Box::new(err), message))
290    }
291
292    fn with_context_fn<F, C>(self, kind: ErrorKind, f: F) -> Result<T>
293    where
294        Self: Sized,
295        F: FnOnce() -> C,
296        C: Into<Cow<'static, str>>,
297    {
298        self.with_context(kind, f())
299    }
300}
301
302mod private {
303    pub trait Sealed {}
304
305    impl<T, E> Sealed for std::result::Result<T, E> where E: std::error::Error + Send + Sync + 'static {}
306}
307
308#[cfg(test)]
309mod tests {
310    use super::{Error, ErrorKind};
311    use std::fmt;
312
313    #[derive(Debug)]
314    struct ChildError;
315
316    impl fmt::Display for ChildError {
317        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318            f.write_str("child")
319        }
320    }
321
322    impl std::error::Error for ChildError {}
323
324    #[derive(Debug)]
325    struct ParentError {
326        source: ChildError,
327    }
328
329    impl fmt::Display for ParentError {
330        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331            f.write_str("parent")
332        }
333    }
334
335    impl std::error::Error for ParentError {
336        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
337            Some(&self.source)
338        }
339    }
340
341    #[test]
342    fn alternate_format_includes_third_party_sources() {
343        let err = Error::with_error(
344            ErrorKind::Other,
345            ParentError { source: ChildError },
346            "outer",
347        );
348
349        assert_eq!(format!("{err}"), "outer");
350        assert_eq!(format!("{err:#}"), "outer: parent: child");
351    }
352
353    #[test]
354    fn alternate_format_includes_nested_error_sources() {
355        let inner = Error::with_error(
356            ErrorKind::Other,
357            ParentError { source: ChildError },
358            "inner",
359        );
360        let err = Error::with_error(ErrorKind::Other, inner, "outer");
361
362        assert_eq!(format!("{err:#}"), "outer: inner: parent: child");
363    }
364}