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, .. }) => write!(f, "{error}"),
137            Repr::CustomMessage(_, message) => write!(f, "{message}"),
138        }
139    }
140}
141
142impl std::error::Error for Error {
143    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
144        match &self.repr {
145            Repr::Custom(Custom { error, .. }) | Repr::CustomMessage(Custom { error, .. }, ..) => {
146                Some(&**error)
147            }
148            _ => None,
149        }
150    }
151}
152
153impl From<ErrorKind> for Error {
154    fn from(kind: ErrorKind) -> Self {
155        Self {
156            repr: Repr::Simple(kind),
157        }
158    }
159}
160
161impl From<String> for Error {
162    fn from(value: String) -> Self {
163        Self::with_message(ErrorKind::Other, value)
164    }
165}
166
167impl From<Infallible> for Error {
168    fn from(_: Infallible) -> Self {
169        panic!("inconceivable")
170    }
171}
172
173impl From<std::io::Error> for Error {
174    fn from(error: std::io::Error) -> Self {
175        Self::new(ErrorKind::Io, error)
176    }
177}
178
179impl From<std::num::ParseIntError> for Error {
180    fn from(error: std::num::ParseIntError) -> Self {
181        Self::new(ErrorKind::InvalidData, error)
182    }
183}
184
185impl From<std::env::VarError> for Error {
186    fn from(error: std::env::VarError) -> Self {
187        Self::new(ErrorKind::Other, error)
188    }
189}
190
191impl From<azure_core::Error> for Error {
192    fn from(error: azure_core::Error) -> Self {
193        Self::new(ErrorKind::Other, error)
194    }
195}
196
197impl From<dotenvy::Error> for Error {
198    fn from(error: dotenvy::Error) -> Self {
199        Self::new(ErrorKind::Other, error)
200    }
201}
202
203impl From<aws_lc_rs::error::Unspecified> for Error {
204    fn from(error: aws_lc_rs::error::Unspecified) -> Self {
205        Self::new(ErrorKind::Other, error)
206    }
207}
208
209impl From<serde_json::Error> for Error {
210    fn from(error: serde_json::Error) -> Self {
211        Self::new(ErrorKind::Io, error)
212    }
213}
214
215impl From<url::ParseError> for Error {
216    fn from(error: url::ParseError) -> Self {
217        Self::new(ErrorKind::InvalidData, error)
218    }
219}
220
221#[derive(Debug)]
222enum Repr {
223    Simple(ErrorKind),
224    SimpleMessage(ErrorKind, Cow<'static, str>),
225    Custom(Custom),
226    CustomMessage(Custom, Cow<'static, str>),
227}
228
229#[derive(Debug)]
230struct Custom {
231    kind: ErrorKind,
232    error: Box<dyn std::error::Error + Send + Sync>,
233}
234
235/// Extension methods for [`Result`](std::result::Result)s.
236pub trait ResultExt<T>: private::Sealed {
237    /// Wrap an [`Error`](std::error::Error) with an [`ErrorKind`].
238    fn with_kind(self, kind: ErrorKind) -> Result<T>;
239
240    /// Wrap an [`Error`](std::error::Error) with an [`ErrorKind`] and message.
241    fn with_context<C>(self, kind: ErrorKind, message: C) -> Result<T>
242    where
243        Self: Sized,
244        C: Into<Cow<'static, str>>;
245
246    /// Wrap an [`Error`](std::error::Error) with an [`ErrorKind`] and a function that returns a message.
247    fn with_context_fn<F, C>(self, kind: ErrorKind, f: F) -> Result<T>
248    where
249        Self: Sized,
250        F: FnOnce() -> C,
251        C: Into<Cow<'static, str>>;
252}
253
254impl<T, E> ResultExt<T> for std::result::Result<T, E>
255where
256    E: std::error::Error + Send + Sync + 'static,
257{
258    fn with_kind(self, kind: ErrorKind) -> Result<T> {
259        self.map_err(|err| Error::new(kind, err))
260    }
261
262    fn with_context<C>(self, kind: ErrorKind, message: C) -> Result<T>
263    where
264        Self: Sized,
265        C: Into<Cow<'static, str>>,
266    {
267        self.map_err(|err| Error::with_error(kind, Box::new(err), message))
268    }
269
270    fn with_context_fn<F, C>(self, kind: ErrorKind, f: F) -> Result<T>
271    where
272        Self: Sized,
273        F: FnOnce() -> C,
274        C: Into<Cow<'static, str>>,
275    {
276        self.with_context(kind, f())
277    }
278}
279
280mod private {
281    pub trait Sealed {}
282
283    impl<T, E> Sealed for std::result::Result<T, E> where E: std::error::Error + Send + Sync + 'static {}
284}