1use std::{
7 borrow::{Borrow, Cow},
8 convert::Infallible,
9 fmt,
10};
11
12pub type Result<T> = std::result::Result<T, Error>;
14
15#[derive(Clone, Debug, PartialEq, Eq)]
17pub enum ErrorKind {
18 InvalidData,
20
21 Io,
23
24 Other,
26}
27
28impl fmt::Display for ErrorKind {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 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#[derive(Debug)]
41pub struct Error {
42 repr: Repr,
43}
44
45impl Error {
46 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 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 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 #[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 #[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 #[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 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
257pub trait ResultExt<T>: private::Sealed {
259 fn with_kind(self, kind: ErrorKind) -> Result<T>;
261
262 fn with_context<C>(self, kind: ErrorKind, message: C) -> Result<T>
264 where
265 Self: Sized,
266 C: Into<Cow<'static, str>>;
267
268 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}