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
4use std::{
5    borrow::{Borrow, Cow},
6    convert::Infallible,
7    fmt,
8};
9
10pub type Result<T> = std::result::Result<T, Error>;
11
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub enum ErrorKind {
14    InvalidData,
15    Io,
16    Other,
17}
18
19impl fmt::Display for ErrorKind {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        // cspell:ignore errno
22        match self {
23            ErrorKind::InvalidData => f.write_str("InvalidData"),
24            ErrorKind::Io => f.write_str("Io"),
25            ErrorKind::Other => f.write_str("Other"),
26        }
27    }
28}
29
30#[derive(Debug)]
31pub struct Error {
32    repr: Repr,
33}
34
35impl Error {
36    /// Constructs a new `Error` boxing another [`std::error::Error`].
37    pub fn new<E>(kind: ErrorKind, error: E) -> Self
38    where
39        E: Into<Box<dyn std::error::Error + Send + Sync>>,
40    {
41        Self {
42            repr: Repr::Custom(Custom {
43                kind,
44                error: error.into(),
45            }),
46        }
47    }
48
49    /// The [`ErrorKind`] of this `Error`.
50    pub fn kind(&self) -> &ErrorKind {
51        match &self.repr {
52            Repr::Simple(kind)
53            | Repr::SimpleMessage(kind, ..)
54            | Repr::Custom(Custom { kind, .. })
55            | Repr::CustomMessage(Custom { kind, .. }, ..) => kind,
56        }
57    }
58
59    /// The message provided when this `Error` was constructed, or `None`.
60    pub fn message(&self) -> Option<&str> {
61        match &self.repr {
62            Repr::SimpleMessage(_, message) | Repr::CustomMessage(_, message) => {
63                Some(message.borrow())
64            }
65            _ => None,
66        }
67    }
68
69    #[must_use]
70    pub fn with_message<C>(kind: ErrorKind, message: C) -> Self
71    where
72        C: Into<Cow<'static, str>>,
73    {
74        Self {
75            repr: Repr::SimpleMessage(kind, message.into()),
76        }
77    }
78
79    #[must_use]
80    pub fn with_message_fn<F, C>(kind: ErrorKind, message: F) -> Self
81    where
82        Self: Sized,
83        F: FnOnce() -> C,
84        C: Into<Cow<'static, str>>,
85    {
86        Self::with_message(kind, message())
87    }
88
89    #[must_use]
90    pub fn with_error<E, C>(kind: ErrorKind, error: E, message: C) -> Self
91    where
92        E: Into<Box<dyn std::error::Error + Send + Sync>>,
93        C: Into<Cow<'static, str>>,
94    {
95        Self {
96            repr: Repr::CustomMessage(
97                Custom {
98                    kind,
99                    error: error.into(),
100                },
101                message.into(),
102            ),
103        }
104    }
105
106    #[must_use]
107    pub fn with_error_fn<E, F, C>(kind: ErrorKind, error: E, message: F) -> Self
108    where
109        E: Into<Box<dyn std::error::Error + Send + Sync>>,
110        F: FnOnce() -> C,
111        C: Into<Cow<'static, str>>,
112    {
113        Self::with_error(kind, error, message())
114    }
115}
116
117impl fmt::Display for Error {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match &self.repr {
120            Repr::Simple(kind) => write!(f, "{kind}"),
121            Repr::SimpleMessage(_, message) => write!(f, "{message}"),
122            Repr::Custom(Custom { error, .. }) => write!(f, "{error}"),
123            Repr::CustomMessage(_, message) => write!(f, "{message}"),
124        }
125    }
126}
127
128impl std::error::Error for Error {
129    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
130        match &self.repr {
131            Repr::Custom(Custom { error, .. }) | Repr::CustomMessage(Custom { error, .. }, ..) => {
132                Some(&**error)
133            }
134            _ => None,
135        }
136    }
137}
138
139impl From<ErrorKind> for Error {
140    fn from(kind: ErrorKind) -> Self {
141        Self {
142            repr: Repr::Simple(kind),
143        }
144    }
145}
146
147impl From<String> for Error {
148    fn from(value: String) -> Self {
149        Self::with_message(ErrorKind::Other, value)
150    }
151}
152
153impl From<Infallible> for Error {
154    fn from(_: Infallible) -> Self {
155        panic!("inconceivable")
156    }
157}
158
159impl From<std::io::Error> for Error {
160    fn from(error: std::io::Error) -> Self {
161        Self::new(ErrorKind::Io, error)
162    }
163}
164
165impl From<std::num::ParseIntError> for Error {
166    fn from(error: std::num::ParseIntError) -> Self {
167        Self::new(ErrorKind::InvalidData, error)
168    }
169}
170
171impl From<std::env::VarError> for Error {
172    fn from(error: std::env::VarError) -> Self {
173        Self::new(ErrorKind::Other, error)
174    }
175}
176
177impl From<azure_core::Error> for Error {
178    fn from(error: azure_core::Error) -> Self {
179        Self::new(ErrorKind::Other, error)
180    }
181}
182
183impl From<dotenvy::Error> for Error {
184    fn from(error: dotenvy::Error) -> Self {
185        Self::new(ErrorKind::Other, error)
186    }
187}
188
189impl From<openssl::error::ErrorStack> for Error {
190    fn from(error: openssl::error::ErrorStack) -> Self {
191        Self::new(ErrorKind::Other, error)
192    }
193}
194
195impl From<url::ParseError> for Error {
196    fn from(error: url::ParseError) -> Self {
197        Self::new(ErrorKind::InvalidData, error)
198    }
199}
200
201#[derive(Debug)]
202enum Repr {
203    Simple(ErrorKind),
204    SimpleMessage(ErrorKind, Cow<'static, str>),
205    Custom(Custom),
206    CustomMessage(Custom, Cow<'static, str>),
207}
208
209#[derive(Debug)]
210struct Custom {
211    kind: ErrorKind,
212    error: Box<dyn std::error::Error + Send + Sync>,
213}
214
215pub trait ResultExt<T>: private::Sealed {
216    fn with_kind(self, kind: ErrorKind) -> Result<T>;
217
218    fn with_context<C>(self, kind: ErrorKind, message: C) -> Result<T>
219    where
220        Self: Sized,
221        C: Into<Cow<'static, str>>;
222
223    fn with_context_fn<F, C>(self, kind: ErrorKind, f: F) -> Result<T>
224    where
225        Self: Sized,
226        F: FnOnce() -> C,
227        C: Into<Cow<'static, str>>;
228}
229
230impl<T, E> ResultExt<T> for std::result::Result<T, E>
231where
232    E: std::error::Error + Send + Sync + 'static,
233{
234    fn with_kind(self, kind: ErrorKind) -> Result<T> {
235        self.map_err(|err| Error::new(kind, err))
236    }
237
238    fn with_context<C>(self, kind: ErrorKind, message: C) -> Result<T>
239    where
240        Self: Sized,
241        C: Into<Cow<'static, str>>,
242    {
243        self.map_err(|err| Error::with_error(kind, Box::new(err), message))
244    }
245
246    fn with_context_fn<F, C>(self, kind: ErrorKind, f: F) -> Result<T>
247    where
248        Self: Sized,
249        F: FnOnce() -> C,
250        C: Into<Cow<'static, str>>,
251    {
252        self.with_context(kind, f())
253    }
254}
255
256mod private {
257    pub trait Sealed {}
258
259    impl<T, E> Sealed for std::result::Result<T, E> where E: std::error::Error + Send + Sync + 'static {}
260}