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;