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