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