explicit_error/fault.rs
1use crate::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 + Send + Sync>>,
22 #[serde(serialize_with = "serialize_backtrace")]
23 backtrace: Backtrace,
24 context: Option<String>,
25}
26
27impl<D> From<Fault> for Error<D> {
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_deref().map(|s| s as _)
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 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 + Send + Sync>(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 /// Return the status of the backtrace
171 pub fn backtrace_status(&self) -> BacktraceStatus {
172 self.backtrace.status()
173 }
174
175 /// Return the context
176 pub fn context(&self) -> Option<&str> {
177 self.context.as_deref()
178 }
179}
180
181impl Default for Fault {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187fn serialize_source<S>(
188 source: &Option<Box<dyn StdError + Send + Sync>>,
189 s: S,
190) -> 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;