explicit_error/
fault.rs

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