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}