explicit_error/
fault.rs

1use crate::{domain::Domain, error::Error};
2use serde::{Serialize, Serializer};
3use std::{backtrace::Backtrace, error::Error as StdError};
4
5/// Wrapper for errors that should not happen but cannot panic.
6/// It is wrapped in the [Error::Fault] variant.
7///
8/// To generate it from predicates use [Fault::new], from [Result] or [Option]
9/// import the prelude and use either [or_fault()](crate::error::ResultFault::or_fault),
10/// [or_fault_no_source()](crate::error::ResultFault::or_fault_no_source),
11/// [or_fault_force()](crate::error::ResultFault::or_fault_force),
12/// [or_fault_no_source_force()](crate::error::ResultFault::or_fault_no_source_force),
13/// [ok_or_fault()](crate::error::OptionFault::ok_or_fault)
14/// [ok_or_fault_force()](crate::error::OptionFault::ok_or_fault_force)
15#[derive(Debug, Serialize)]
16pub struct Fault {
17    #[serde(serialize_with = "serialize_source")]
18    pub source: Option<Box<dyn StdError>>,
19    #[serde(serialize_with = "serialize_backtrace")]
20    backtrace: Backtrace,
21    context: Option<String>,
22}
23
24impl<D> From<Fault> for Error<D>
25where
26    D: Domain,
27{
28    fn from(value: Fault) -> Self {
29        Error::Fault(value)
30    }
31}
32
33impl StdError for Fault {
34    fn source(&self) -> Option<&(dyn StdError + 'static)> {
35        self.source.as_ref().map(|s| s.as_ref())
36    }
37}
38
39impl std::fmt::Display for Fault {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        write!(
42            f,
43            "{}{}{}",
44            match self.backtrace.status() {
45                std::backtrace::BacktraceStatus::Captured =>
46                    format!("{}\n ----------------------- \n\n", self.backtrace),
47                _ => String::new(),
48            },
49            match &self.context {
50                Some(c) => format!("Context: {}\n", c),
51                None => String::new(),
52            },
53            match &self.source {
54                Some(s) => format!(
55                    "Source: {}, {}\n",
56                    crate::error::errors_chain_debug(s.as_ref()),
57                    s
58                ),
59                None => String::new(),
60            },
61        )
62    }
63}
64
65impl Fault {
66    /// Usefull to generate a [Fault] when a predicate is not met.
67    ///
68    /// # Examples
69    /// ```rust
70    /// # use explicit_error_http::{Result, Fault};
71    /// # fn doc() -> Result<()> {
72    /// if 1 < 2 {
73    ///     Err(Fault::new())?;
74    /// }
75    /// # Ok(())
76    /// # }
77    /// ```
78    pub fn new() -> Self {
79        Self {
80            source: None,
81            backtrace: Backtrace::capture(),
82            context: None,
83        }
84    }
85
86    /// Add an error source to a [Fault]. Usefull to generate a fault when pattern matching on an error type.
87    ///
88    /// On a [Result](std::result::Result) use [map_err_or_fault](crate::ResultFault::map_err_or_fault) to be more concise.
89    /// # Examples
90    /// ```rust
91    /// # use explicit_error_http::{prelude::*, Error, HttpError, Fault, derive::HttpError};
92    /// # use problem_details::ProblemDetails;
93    /// # use actix_web::http::StatusCode;
94    /// # use explicit_error_http::Result;
95    /// fn fetch() -> Result<()> {
96    ///     let sqlx_error = sqlx::Error::RowNotFound;
97    ///     Err(match sqlx_error {
98    ///         sqlx::Error::RowNotFound => MyEntitysError::NotFound.into(),
99    ///         _ => Fault::new().with_source(sqlx_error).into()
100    ///     })
101    /// }
102    /// # #[derive(HttpError, Debug)]
103    /// # enum MyEntitysError {
104    /// #    NotFound,
105    /// # }
106    /// # impl From<&MyEntitysError> for HttpError {
107    /// #     fn from(value: &MyEntitysError) -> Self {
108    /// #         match value {
109    /// #             MyEntitysError::NotFound => HttpError {
110    /// #               http_status_code: StatusCode::NOT_FOUND,
111    /// #                 public: Box::new(
112    /// #                     ProblemDetails::new()
113    /// #                         .with_type(http::Uri::from_static("/errors/my-entity/not-found"))
114    /// #                         .with_title("Not found"),
115    /// #                     ),
116    /// #                 context: Some("Some usefull info to debug".to_string()),
117    /// #             },
118    /// #         }
119    /// #     }
120    /// # }
121    /// ```
122    pub fn with_source<E: StdError + 'static>(self, error: E) -> Self {
123        Self {
124            source: Some(Box::new(error)),
125            backtrace: self.backtrace,
126            context: self.context,
127        }
128    }
129
130    /// Add context to a [Fault], override if one was set. The context appears in display
131    /// but not in the http response.
132    /// # Examples
133    /// ```rust
134    /// # use explicit_error_http::{Result, Fault};
135    /// # fn doc() -> Result<()> {
136    /// if 1 < 2 {
137    ///     Err(Fault::new().with_context("Some info to help debug"))?;
138    /// }
139    /// # Ok(())
140    /// # }
141    /// ```
142    pub fn with_context(self, context: impl std::fmt::Display) -> Self {
143        Self {
144            source: self.source,
145            backtrace: self.backtrace,
146            context: Some(context.to_string()),
147        }
148    }
149
150    /// Force backtrace capture using [force_capture](std::backtrace::Backtrace::force_capture)
151    ///
152    /// # Examples
153    /// ```rust
154    /// # use explicit_error_http::{Result, Fault};
155    /// # fn doc() -> Result<()> {
156    /// if 1 < 2 {
157    ///     Err(Fault::new_force())?;
158    /// }
159    /// # Ok(())
160    /// # }
161    /// ```
162    pub fn new_force() -> Self {
163        Self {
164            source: None,
165            backtrace: Backtrace::force_capture(),
166            context: None,
167        }
168    }
169}
170
171impl Default for Fault {
172    fn default() -> Self {
173        Self::new()
174    }
175}
176
177fn serialize_source<S>(source: &Option<Box<dyn StdError>>, s: S) -> Result<S::Ok, S::Error>
178where
179    S: Serializer,
180{
181    s.serialize_str(
182        &source
183            .as_ref()
184            .map(|s| format!("{}: {}", crate::error::errors_chain_debug(s.as_ref()), s))
185            .unwrap_or_default(),
186    )
187}
188
189fn serialize_backtrace<S>(backtrace: &Backtrace, s: S) -> Result<S::Ok, S::Error>
190where
191    S: Serializer,
192{
193    s.serialize_str(&backtrace.to_string())
194}