explicit_error/error.rs
1use crate::domain::Domain;
2use crate::fault::*;
3use crate::unwrap_failed;
4use std::{error::Error as StdError, fmt::Display};
5
6/// Use `Result<T, explicit_error::Error>` as the return type of any binary crate
7/// faillible function returning errors.
8/// The [Error::Fault] variant is for errors that should not happen but cannot panic.
9/// The [Error::Domain] variant is for domain errors that provide feedbacks to the user.
10/// For library or functions that require the caller to pattern match on the returned error, a dedicated type is prefered.
11#[derive(Debug)]
12pub enum Error<D> {
13 Domain(Box<D>), // Box for size: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold
14 Fault(Fault),
15}
16
17impl<D> StdError for Error<D>
18where
19 D: StdError + 'static,
20{
21 fn source(&self) -> Option<&(dyn StdError + 'static)> {
22 match self {
23 Error::Domain(explicit_error) => {
24 explicit_error.source().or(Some(explicit_error.as_ref()))
25 }
26 Error::Fault(fault) => fault.source().or(Some(fault)),
27 }
28 }
29}
30
31impl<D> Display for Error<D>
32where
33 D: StdError,
34{
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 match self {
37 Error::Domain(explicit_error) => Display::fmt(&explicit_error, f),
38 Error::Fault(fault) => fault.fmt(f),
39 }
40 }
41}
42
43impl<D> Error<D>
44where
45 D: StdError + 'static,
46{
47 /// Return true if it's a [Error::Domain] variant
48 pub fn is_domain(&self) -> bool {
49 matches!(*self, Error::Domain(_))
50 }
51
52 /// Return true if it's a [Error::Fault] variant
53 pub fn is_fault(&self) -> bool {
54 !self.is_domain()
55 }
56
57 /// Unwrap the [Error::Domain] variant, panic otherwise
58 pub fn unwrap(self) -> D {
59 match self {
60 Self::Domain(e) => *e,
61 Self::Fault(f) => unwrap_failed("called `Error::unwrap()` on an `Fault` value", &f),
62 }
63 }
64
65 /// Unwrap the [Error::Fault] variant, panic otherwise
66 pub fn unwrap_fault(self) -> Fault {
67 match self {
68 Self::Fault(b) => b,
69 Self::Domain(e) => {
70 unwrap_failed("called `Error::unwrap_err()` on an `Domain` value", &e)
71 }
72 }
73 }
74
75 /// Try to downcast the source of the type wrapped in either [Error::Domain] or [Error::Fault] variant.
76 /// If it is not set try to downcast the type wrapped.
77 /// Usefull to assert_eq! in tests
78 /// # Examples
79 /// ```rust
80 /// use explicit_error_exit::{ExitError, derive::ExitError, Error};
81 /// # use std::process::ExitCode;
82 /// #[test]
83 /// fn test() {
84 /// assert_eq!(to_test().unwrap_err().downcast_source_ref()::<MyError>().unwrap(), &MyError::Foo);
85 /// }
86 ///
87 /// #[derive(ExitError, Debug)]
88 /// enum MyError {
89 /// Foo,
90 /// }
91 ///
92 /// # impl From<&MyError> for ExitError {
93 /// # fn from(value: &MyError) -> Self {
94 /// # match value {
95 /// # MyError::Foo => ExitError::new(
96 /// # "Something went wrong because ..",
97 /// # ExitCode::from(42)
98 /// # ),
99 /// # }
100 /// # }
101 /// # }
102 ///
103 /// fn to_test() -> Result<(), Error> {
104 /// Err(MyError::Foo)?;
105 /// Ok(())
106 /// }
107 /// ```
108 pub fn downcast_source_ref<E>(&self) -> Option<&E>
109 where
110 E: StdError + 'static,
111 {
112 match self {
113 Error::Domain(domain) => match domain.source() {
114 Some(_) => domain.source().unwrap(),
115 None => domain as &dyn StdError,
116 },
117 Error::Fault(fault) => match fault.source() {
118 Some(_) => fault.source().unwrap(),
119 None => fault as &dyn StdError,
120 },
121 }
122 .downcast_ref::<E>()
123 }
124}
125
126impl<D> Error<D>
127where
128 D: Domain,
129{
130 /// Try to downcast the source of the type wrapped in either [Error::Domain] or [Error::Fault] variant.
131 /// If it is not set try to downcast the type wrapped.
132 /// Usefull to assert_eq! in tests
133 /// # Examples
134 /// ```rust
135 /// use explicit_error_exit::{ExitError, derive::ExitError, Error};
136 /// # use std::process::ExitCode;
137 /// #[test]
138 /// fn test() {
139 /// assert_eq!(to_test().unwrap_err().downcast_source::<MyError>().unwrap(), MyError::Foo);
140 /// }
141 ///
142 /// #[derive(ExitError, Debug)]
143 /// enum MyError {
144 /// Foo,
145 /// }
146 ///
147 /// # impl From<&MyError> for ExitError {
148 /// # fn from(value: &MyError) -> Self {
149 /// # match value {
150 /// # MyError::Foo => ExitError::new(
151 /// # "Something went wrong because ..",
152 /// # ExitCode::from(42)
153 /// # ),
154 /// # }
155 /// # }
156 /// # }
157 ///
158 /// fn to_test() -> Result<(), Error> {
159 /// Err(MyError::Foo)?;
160 /// Ok(())
161 /// }
162 /// ```
163 pub fn downcast_source<E>(self) -> Result<E, Box<dyn std::error::Error + 'static + Send + Sync>>
164 where
165 E: StdError + 'static,
166 {
167 match self {
168 Error::Domain(domain) => match domain.source() {
169 Some(_) => domain.into_source().unwrap(),
170 None => domain,
171 },
172 Error::Fault(fault) => match fault.source() {
173 Some(_) => fault.source.unwrap(),
174 None => Box::new(fault),
175 },
176 }
177 .downcast::<E>()
178 .map(|o| *o)
179 }
180
181 /// Add context of either [Error::Domain] or [Error::Fault] variant.
182 /// Override existing context
183 pub fn with_context(self, context: impl Display) -> Self {
184 match self {
185 Error::Domain(d) => Error::Domain(Box::new(d.with_context(context))),
186 Error::Fault(fault) => Error::Fault(fault.with_context(context)),
187 }
188 }
189
190 /// Return the context of either [Error::Domain] or [Error::Fault] variant.
191 pub fn context(&self) -> Option<&str> {
192 match self {
193 Error::Domain(d) => d.context(),
194 Error::Fault(fault) => fault.context(),
195 }
196 }
197}
198
199pub fn errors_chain_debug(source: &dyn StdError) -> String {
200 use std::fmt::Write;
201 let mut source = source;
202 let mut str = format!("{:?}", source);
203
204 while source.source().is_some() {
205 source = source.source().unwrap();
206 let _ = write!(&mut str, "->{:?}", source);
207 }
208
209 str
210}
211
212/// To use this trait on [Result] import the prelude `use explicit_error::prelude::*`
213pub trait ResultFault<T, S> {
214 /// Convert with a closure any error wrapped in a [Result] to an [Error]. Returning an [Ok] convert the wrapped type to
215 /// [Error::Domain].
216 /// Returning an [Err] generates a [Fault] with the orginal error has its source.
217 /// # Examples
218 /// Pattern match to convert to an [Error::Domain]
219 /// ```rust
220 /// # use http::StatusCode;
221 /// # use problem_details::ProblemDetails;
222 /// # use http::Uri;
223 /// # use explicit_error_http::{Error, prelude::*, HttpError, derive::HttpError};
224 /// fn authz_middleware(public_identifier: &str) -> Result<(), Error> {
225 /// let entity = fetch_bar(&public_identifier).map_err_or_fault(|e|
226 /// match e {
227 /// sqlx::Error::RowNotFound => Ok(
228 /// NotFoundError::Bar(
229 /// public_identifier.to_string())),
230 /// _ => Err(e), // Convert to Error::Fault
231 /// }
232 /// )?;
233 ///
234 /// Ok(entity)
235 /// }
236 /// # fn fetch_bar(public_identifier: &str) -> Result<(), sqlx::Error> {
237 /// # Err(sqlx::Error::RowNotFound)
238 /// # }
239 /// # #[derive(HttpError, Debug)]
240 /// # enum NotFoundError {
241 /// # Bar(String)
242 /// # }
243 /// # impl From<&NotFoundError> for HttpError {
244 /// # fn from(value: &NotFoundError) -> Self {
245 /// # let (label, id) = match value {
246 /// # NotFoundError::Bar(public_identifier) => ("Bar", public_identifier)
247 /// # };
248 /// # HttpError::new(
249 /// # StatusCode::NOT_FOUND,
250 /// # ProblemDetails::new()
251 /// # .with_type(Uri::from_static("/errors/not-found"))
252 /// # .with_title("Not found")
253 /// # .with_detail(format!("Unknown {label} with identifier {id}."))
254 /// # )
255 /// # }
256 /// # }
257 /// ```
258 fn map_err_or_fault<F, E, D>(self, op: F) -> Result<T, Error<D>>
259 where
260 F: FnOnce(S) -> Result<E, S>,
261 E: Into<Error<D>>,
262 S: StdError + 'static + Send + Sync,
263 D: Into<Error<D>>;
264
265 /// Convert any [Result::Err] into a [Result::Err] wrapping a [Fault]
266 /// Use [fault](ResultFault::or_fault) instead if the error implements [std::error::Error]
267 /// ```rust
268 /// # use std::fs::File;
269 /// # use explicit_error_exit::{Error, prelude::*};
270 /// fn foo() -> Result<(), Error> {
271 /// let file: Result<File, std::io::Error> = File::open("foo.conf");
272 /// file.or_fault_no_source().with_context("Configuration file foo.conf is missing.")?;
273 ///
274 /// Err("error message").or_fault_no_source()?;
275 /// # Ok(())
276 /// }
277 /// ```
278 fn or_fault_no_source(self) -> Result<T, Fault>;
279
280 /// Convert any [Result::Err] wrapping an error that implements
281 /// [std::error::Error] into a [Result::Err] wrapping a [Fault]
282 /// ```rust
283 /// # use std::fs::File;
284 /// # use explicit_error_exit::{Error, prelude::*};
285 /// fn foo() -> Result<(), Error> {
286 /// Err(sqlx::Error::RowNotFound)
287 /// .or_fault()
288 /// .with_context("Configuration file foo.conf is missing.")?;
289 /// # Ok(())
290 /// }
291 /// ```
292 fn or_fault(self) -> Result<T, Fault>
293 where
294 S: StdError + 'static + Send + Sync;
295
296 /// Convert any [Result::Err] into a [Result::Err] wrapping a [Fault] forcing backtrace capture
297 /// Use [or_fault_force](ResultFault::or_fault_force) instead if the error implements [std::error::Error]
298 /// ```rust
299 /// # use std::fs::File;
300 /// # use explicit_error_exit::{Error, prelude::*};
301 /// fn foo() -> Result<(), Error> {
302 /// let file: Result<File, std::io::Error> = File::open("foo.conf");
303 /// file.or_fault_force().with_context("Configuration file foo.conf is missing.")?;
304 /// # Ok(())
305 /// }
306 /// ```
307 fn or_fault_no_source_force(self) -> Result<T, Fault>;
308
309 /// Convert any [Result::Err] wrapping an error that implements
310 /// [std::error::Error] into a [Result::Err] wrapping a [Fault] forcing backtrace capture
311 /// ```rust
312 /// # use std::fs::File;
313 /// # use explicit_error_exit::{Error, prelude::*};
314 /// fn foo() -> Result<(), Error> {
315 /// Err(sqlx::Error::RowNotFound)
316 /// .or_fault_force()
317 /// .with_context("Configuration file foo.conf is missing.")?;
318 /// # Ok(())
319 /// }
320 /// ```
321 fn or_fault_force(self) -> Result<T, Fault>
322 where
323 S: StdError + 'static + Send + Sync;
324}
325
326impl<T, S> ResultFault<T, S> for Result<T, S> {
327 fn map_err_or_fault<F, E, D>(self, op: F) -> Result<T, Error<D>>
328 where
329 F: FnOnce(S) -> Result<E, S>,
330 E: Into<Error<D>>,
331 S: StdError + 'static + Send + Sync,
332 D: Into<Error<D>>,
333 {
334 match self {
335 Ok(ok) => Ok(ok),
336 Err(error) => Err(match op(error) {
337 Ok(d) => d.into(),
338 Err(e) => Fault::new().with_source(e).into(),
339 }),
340 }
341 }
342
343 fn or_fault_no_source(self) -> Result<T, Fault> {
344 match self {
345 Ok(ok) => Ok(ok),
346 Err(_) => Err(Fault::new()),
347 }
348 }
349
350 fn or_fault_no_source_force(self) -> Result<T, Fault> {
351 match self {
352 Ok(ok) => Ok(ok),
353 Err(_) => Err(Fault::new_force()),
354 }
355 }
356
357 fn or_fault(self) -> Result<T, Fault>
358 where
359 S: StdError + 'static + Send + Sync,
360 {
361 match self {
362 Ok(ok) => Ok(ok),
363 Err(error) => Err(Fault::new().with_source(error)),
364 }
365 }
366
367 fn or_fault_force(self) -> Result<T, Fault>
368 where
369 S: StdError + 'static + Send + Sync,
370 {
371 match self {
372 Ok(ok) => Ok(ok),
373 Err(error) => Err(Fault::new_force().with_source(error)),
374 }
375 }
376}
377
378/// To use this trait on [Result] import the prelude `use explicit_error::prelude::*`
379pub trait ResultError<T, D>
380where
381 D: Domain,
382{
383 /// Pattern match on the [Error] source from either the [Error::Fault] or [Error::Domain] variant
384 /// if its type is the closure's parameter type.
385 /// # Examples
386 /// ```rust
387 /// # use http::StatusCode;
388 /// # use http::Uri;
389 /// # use problem_details::ProblemDetails;
390 /// # use explicit_error_http::{prelude::*, HttpError, Result, derive::HttpError};
391 /// # #[derive(HttpError, Debug)]
392 /// # enum MyError {
393 /// # Foo,
394 /// # Bar,
395 /// # }
396 /// # impl From<&MyError> for HttpError {
397 /// # fn from(value: &MyError) -> Self {
398 /// # match value {
399 /// # MyError::Foo | MyError::Bar => HttpError::new(
400 /// # StatusCode::BAD_REQUEST,
401 /// # ProblemDetails::new()
402 /// # .with_type(Uri::from_static("/errors/my-domain/foo"))
403 /// # .with_title("Foo format incorrect.")
404 /// # ),
405 /// # }
406 /// # }
407 /// # }
408 /// # fn handler() -> Result<()> {
409 /// let err: Result<()> = Err(MyError::Foo)?;
410 ///
411 /// // Do the map if the source's type of the Error is MyError
412 /// err.try_map_on_source(|e| {
413 /// match e {
414 /// MyError::Foo => HttpError::new(
415 /// StatusCode::FORBIDDEN,
416 /// ProblemDetails::new()
417 /// .with_type(Uri::from_static("/errors/forbidden"))
418 /// ),
419 /// MyError::Bar => HttpError::new(
420 /// StatusCode::UNAUTHORIZED,
421 /// ProblemDetails::new()
422 /// .with_type(Uri::from_static("/errors/unauthorized"))
423 /// ),
424 /// }
425 /// })?;
426 /// # Ok(())
427 /// # }
428 /// ```
429 fn try_map_on_source<F, S, E>(self, op: F) -> Result<T, Error<D>>
430 where
431 F: FnOnce(S) -> E,
432 S: StdError + 'static,
433 E: Into<Error<D>>;
434
435 /// Add a context to any variant of an [Error] wrapped in a [Result::Err]
436 /// # Examples
437 /// ```rust
438 /// use explicit_error::{prelude::*, Fault};
439 /// Err::<(), _>(Fault::new()).with_context("Foo bar");
440 /// ```
441 fn with_context(self, context: impl Display) -> Result<T, Error<D>>;
442}
443
444impl<T, D> ResultError<T, D> for Result<T, Error<D>>
445where
446 D: Domain,
447 T: std::fmt::Debug,
448{
449 fn try_map_on_source<F, S, E>(self, op: F) -> Result<T, Error<D>>
450 where
451 F: FnOnce(S) -> E,
452 S: StdError + 'static,
453 E: Into<Error<D>>,
454 {
455 match self {
456 Ok(ok) => Ok(ok),
457 Err(error) => match error {
458 Error::Domain(d) => {
459 if d.source().is_some() && (d.source().as_ref().unwrap()).is::<S>() {
460 return Err(op(*d.into_source().unwrap().downcast::<S>().unwrap()).into());
461 }
462
463 Err(Error::Domain(d))
464 }
465 Error::Fault(b) => {
466 if let Some(s) = &b.source {
467 if s.is::<S>() {
468 return Err(op(*b.source.unwrap().downcast::<S>().unwrap()).into());
469 }
470 }
471
472 Err(Error::Fault(b))
473 }
474 },
475 }
476 }
477
478 fn with_context(self, context: impl Display) -> Result<T, Error<D>> {
479 match self {
480 Ok(ok) => Ok(ok),
481 Err(error) => Err(match error {
482 Error::Domain(explicit_error) => explicit_error.with_context(context).into(),
483 Error::Fault(fault) => fault.with_context(context).into(),
484 }),
485 }
486 }
487}
488
489/// To use this trait on [Option] import the prelude `use explicit_error::prelude::*`
490pub trait OptionFault<T> {
491 /// Transforms the `Option<T>` into a `Result<T, Fault>`, mapping Some(v) to Ok(v) and None to Err(Fault)
492 /// ```rust
493 /// # use std::fs::File;
494 /// # use explicit_error_exit::{Error, prelude::*};
495 /// fn foo() -> Result<(), Error> {
496 /// let option: Option<u8> = None;
497 /// option.ok_or_fault().with_context("Help debugging")?;
498 /// # Ok(())
499 /// }
500 /// ```
501 fn ok_or_fault(self) -> Result<T, Fault>;
502
503 /// Transforms the `Option<T>` into a `Result<T, Fault>`, mapping Some(v) to Ok(v) and None to Err(Fault)
504 /// forcing backtrace capture
505 /// ```rust
506 /// # use std::fs::File;
507 /// # use explicit_error_exit::{Error, prelude::*};
508 /// fn foo() -> Result<(), Error> {
509 /// let option: Option<u8> = None;
510 /// option.ok_or_fault_force().with_context("Help debugging")?;
511 /// # Ok(())
512 /// }
513 /// ```
514 fn ok_or_fault_force(self) -> Result<T, Fault>;
515}
516
517impl<T> OptionFault<T> for Option<T> {
518 fn ok_or_fault(self) -> Result<T, Fault> {
519 match self {
520 Some(ok) => Ok(ok),
521 None => Err(Fault::new()),
522 }
523 }
524
525 fn ok_or_fault_force(self) -> Result<T, Fault> {
526 match self {
527 Some(ok) => Ok(ok),
528 None => Err(Fault::new_force()),
529 }
530 }
531}
532
533/// To use this trait on [Result] import the prelude `use explicit_error::prelude::*`
534pub trait ResultFaultWithContext<T> {
535 /// Add a context to the [Fault] wrapped in a [Result::Err]
536 /// # Examples
537 /// ```rust
538 /// # use explicit_error::{prelude::*, Fault};
539 /// Err::<(), _>(Fault::new()).with_context("Foo bar");
540 /// ```
541 fn with_context(self, context: impl Display) -> Result<T, Fault>;
542}
543
544impl<T> ResultFaultWithContext<T> for Result<T, Fault> {
545 fn with_context(self, context: impl Display) -> Result<T, Fault> {
546 match self {
547 Ok(ok) => Ok(ok),
548 Err(b) => Err(b.with_context(context)),
549 }
550 }
551}
552
553#[cfg(test)]
554mod test;