explicit_error/error.rs
1use crate::bug::*;
2use crate::domain::Domain;
3use crate::unwrap_failed;
4use std::{error::Error as StdError, fmt::Display};
5
6/// Use `Result<T, explicit_error::Error>` as the return type of any binary crate
7/// faillible function returning errors.
8/// The [Error::Bug] variant is for errors that should not happen but cannot panic.
9/// The [Error::Domain] variant is for domain errors that provide feedbacks to the user.
10/// For library or functions that require the caller to pattern match on the returned error, a dedicated type is prefered.
11#[derive(Debug)]
12pub enum Error<D: Domain> {
13 Domain(Box<D>), // Box for size: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold
14 Bug(Bug),
15}
16
17impl<D> StdError for Error<D>
18where
19 D: Domain,
20{
21 fn source(&self) -> Option<&(dyn StdError + 'static)> {
22 match self {
23 Error::Domain(explicit_error) => Some(explicit_error.as_ref()),
24 Error::Bug(bug) => bug.source.as_ref().map(|e| e.as_ref()),
25 }
26 }
27}
28
29impl<D> Display for Error<D>
30where
31 D: Domain,
32{
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self {
35 Error::Domain(explicit_error) => Display::fmt(&explicit_error, f),
36 Error::Bug(bug) => bug.fmt(f),
37 }
38 }
39}
40
41impl<D> Error<D>
42where
43 D: Domain,
44{
45 pub fn is_domain(&self) -> bool {
46 matches!(*self, Error::Domain(_))
47 }
48
49 pub fn is_bug(&self) -> bool {
50 !self.is_domain()
51 }
52 pub fn unwrap(self) -> D {
53 match self {
54 Self::Domain(e) => *e,
55 Self::Bug(b) => unwrap_failed("called `Error::unwrap()` on an `Bug` value", &b),
56 }
57 }
58
59 pub fn unwrap_bug(self) -> Bug {
60 match self {
61 Self::Bug(b) => b,
62 Self::Domain(e) => {
63 unwrap_failed("called `Error::unwrap_err()` on an `Domain` value", &e)
64 }
65 }
66 }
67}
68
69pub fn errors_chain_debug(source: &dyn StdError) -> String {
70 use std::fmt::Write;
71 let mut source = source;
72 let mut str = format!("{:?}", source);
73
74 while source.source().is_some() {
75 source = source.source().unwrap();
76 let _ = write!(&mut str, "->{:?}", source);
77 }
78
79 str
80}
81
82/// To use this trait on [Result] import the prelude `use explicit_error::prelude::*`
83pub trait ResultBug<T, S> {
84 /// Convert with a closure any error wrapped in a [Result] to an [Error]. Returning an [Ok] convert the wrapped type to
85 /// [Error::Domain].
86 /// Returning an [Err] generates a [Bug] with the orginal error has its source.
87 /// # Examples
88 /// Pattern match to convert to an [Error::Domain]
89 /// ```rust
90 /// # use actix_web::http::StatusCode;
91 /// # use problem_details::ProblemDetails;
92 /// # use http::Uri;
93 /// # use explicit_error_http::{Error, prelude::*, HttpError, derive::HttpError};
94 /// fn authz_middleware(public_identifier: &str) -> Result<(), Error> {
95 /// let entity = fetch_bar(&public_identifier).map_err_or_bug(|e|
96 /// match e {
97 /// sqlx::Error::RowNotFound => Ok(
98 /// NotFoundError::Bar(
99 /// public_identifier.to_string())),
100 /// _ => Err(e), // Convert to Error::Bug
101 /// }
102 /// )?;
103 ///
104 /// Ok(entity)
105 /// }
106 /// # fn fetch_bar(public_identifier: &str) -> Result<(), sqlx::Error> {
107 /// # Err(sqlx::Error::RowNotFound)
108 /// # }
109 /// # #[derive(HttpError, Debug)]
110 /// # enum NotFoundError {
111 /// # Bar(String)
112 /// # }
113 /// # impl From<&NotFoundError> for HttpError {
114 /// # fn from(value: &NotFoundError) -> Self {
115 /// # let (label, id) = match value {
116 /// # NotFoundError::Bar(public_identifier) => ("Bar", public_identifier)
117 /// # };
118 /// # HttpError::new(
119 /// # StatusCode::NOT_FOUND,
120 /// # ProblemDetails::new()
121 /// # .with_type(Uri::from_static("/errors/not-found"))
122 /// # .with_title("Not found")
123 /// # .with_detail(format!("Unknown {label} with identifier {id}."))
124 /// # )
125 /// # }
126 /// # }
127 /// ```
128 fn map_err_or_bug<F, E, D>(self, op: F) -> Result<T, Error<D>>
129 where
130 F: FnOnce(S) -> Result<E, S>,
131 E: Into<Error<D>>,
132 S: StdError + 'static,
133 D: Domain;
134
135 /// Convert any [Result::Err] into a [Result::Err] wrapping a [Bug]
136 /// Use [bug](ResultBug::bug) instead if the error implements [std::error::Error]
137 /// ```rust
138 /// # use std::fs::File;
139 /// # use explicit_error_exit::{Error, prelude::*};
140 /// fn foo() -> Result<(), Error> {
141 /// let file: Result<File, std::io::Error> = File::open("foo.conf");
142 /// file.bug_no_source().with_context("Configuration file foo.conf is missing.")?;
143 ///
144 /// Err("error message").bug_no_source()?;
145 /// # Ok(())
146 /// }
147 /// ```
148 fn bug_no_source(self) -> Result<T, Bug>;
149
150 /// Convert any [Result::Err] wrapping an error that implements
151 /// [std::error::Error] into a [Result::Err] wrapping a [Bug]
152 /// ```rust
153 /// # use std::fs::File;
154 /// # use explicit_error_exit::{Error, prelude::*};
155 /// fn foo() -> Result<(), Error> {
156 /// Err(sqlx::Error::RowNotFound)
157 /// .bug()
158 /// .with_context("Configuration file foo.conf is missing.")?;
159 /// # Ok(())
160 /// }
161 /// ```
162 fn bug(self) -> Result<T, Bug>
163 where
164 S: StdError + 'static;
165
166 /// Convert any [Result::Err] into a [Result::Err] wrapping a [Bug] forcing backtrace capture
167 /// Use [bug_force](ResultBug::bug_force) instead if the error implements [std::error::Error]
168 /// ```rust
169 /// # use std::fs::File;
170 /// # use explicit_error_exit::{Error, prelude::*};
171 /// fn foo() -> Result<(), Error> {
172 /// let file: Result<File, std::io::Error> = File::open("foo.conf");
173 /// file.bug_force().with_context("Configuration file foo.conf is missing.")?;
174 /// # Ok(())
175 /// }
176 /// ```
177 fn bug_no_source_force(self) -> Result<T, Bug>;
178
179 /// Convert any [Result::Err] wrapping an error that implements
180 /// [std::error::Error] into a [Result::Err] wrapping a [Bug] forcing backtrace capture
181 /// ```rust
182 /// # use std::fs::File;
183 /// # use explicit_error_exit::{Error, prelude::*};
184 /// fn foo() -> Result<(), Error> {
185 /// Err(sqlx::Error::RowNotFound)
186 /// .bug_force()
187 /// .with_context("Configuration file foo.conf is missing.")?;
188 /// # Ok(())
189 /// }
190 /// ```
191 fn bug_force(self) -> Result<T, Bug>
192 where
193 S: StdError + 'static;
194}
195
196impl<T, S> ResultBug<T, S> for Result<T, S> {
197 fn map_err_or_bug<F, E, D>(self, op: F) -> Result<T, Error<D>>
198 where
199 F: FnOnce(S) -> Result<E, S>,
200 E: Into<Error<D>>,
201 S: StdError + 'static,
202 D: Domain,
203 {
204 match self {
205 Ok(ok) => Ok(ok),
206 Err(error) => Err(match op(error) {
207 Ok(d) => d.into(),
208 Err(e) => Bug::new().with_source(e).into(),
209 }),
210 }
211 }
212
213 fn bug_no_source(self) -> Result<T, Bug> {
214 match self {
215 Ok(ok) => Ok(ok),
216 Err(_) => Err(Bug::new()),
217 }
218 }
219
220 fn bug_no_source_force(self) -> Result<T, Bug> {
221 match self {
222 Ok(ok) => Ok(ok),
223 Err(_) => Err(Bug::new_force()),
224 }
225 }
226
227 fn bug(self) -> Result<T, Bug>
228 where
229 S: StdError + 'static,
230 {
231 match self {
232 Ok(ok) => Ok(ok),
233 Err(error) => Err(Bug::new().with_source(error)),
234 }
235 }
236
237 fn bug_force(self) -> Result<T, Bug>
238 where
239 S: StdError + 'static,
240 {
241 match self {
242 Ok(ok) => Ok(ok),
243 Err(error) => Err(Bug::new_force().with_source(error)),
244 }
245 }
246}
247
248/// To use this trait on [Result] import the prelude `use explicit_error::prelude::*`
249pub trait ResultError<T, D>
250where
251 D: Domain,
252{
253 /// Pattern match on the [Error] source from either the [Error::Bug] or [Error::Domain] variant
254 /// if its type is the closure's parameter type.
255 /// # Examples
256 /// ```rust
257 /// # use actix_web::http::StatusCode;
258 /// # use http::Uri;
259 /// # use problem_details::ProblemDetails;
260 /// # use explicit_error_http::{prelude::*, HttpError, Result, derive::HttpError};
261 /// # #[derive(HttpError, Debug)]
262 /// # enum MyError {
263 /// # Foo,
264 /// # Bar,
265 /// # }
266 /// # impl From<&MyError> for HttpError {
267 /// # fn from(value: &MyError) -> Self {
268 /// # match value {
269 /// # MyError::Foo | MyError::Bar => HttpError::new(
270 /// # StatusCode::BAD_REQUEST,
271 /// # ProblemDetails::new()
272 /// # .with_type(Uri::from_static("/errors/my-domain/foo"))
273 /// # .with_title("Foo format incorrect.")
274 /// # ),
275 /// # }
276 /// # }
277 /// # }
278 /// # fn handler() -> Result<()> {
279 /// let err: Result<()> = Err(MyError::Foo)?;
280 ///
281 /// // Do the map if the source's type of the Error is MyError
282 /// err.try_map_on_source(|e| {
283 /// match e {
284 /// MyError::Foo => HttpError::new(
285 /// StatusCode::FORBIDDEN,
286 /// ProblemDetails::new()
287 /// .with_type(Uri::from_static("/errors/forbidden"))
288 /// ),
289 /// MyError::Bar => HttpError::new(
290 /// StatusCode::UNAUTHORIZED,
291 /// ProblemDetails::new()
292 /// .with_type(Uri::from_static("/errors/unauthorized"))
293 /// ),
294 /// }
295 /// })?;
296 /// # Ok(())
297 /// # }
298 /// ```
299 fn try_map_on_source<F, S, E>(self, op: F) -> Result<T, Error<D>>
300 where
301 F: FnOnce(S) -> E,
302 S: StdError + 'static,
303 E: Into<Error<D>>;
304
305 /// Add a context to any variant of an [Error] wrapped in a [Result::Err]
306 /// # Examples
307 /// ```rust
308 /// use explicit_error::{prelude::*, Bug};
309 /// Err::<(), _>(Bug::new()).with_context("Foo bar");
310 /// ```
311 fn with_context(self, context: impl Display) -> Result<T, Error<D>>;
312}
313
314impl<T, D> ResultError<T, D> for Result<T, Error<D>>
315where
316 D: Domain,
317{
318 fn try_map_on_source<F, S, E>(self, op: F) -> Result<T, Error<D>>
319 where
320 F: FnOnce(S) -> E,
321 S: StdError + 'static,
322 E: Into<Error<D>>,
323 {
324 match self {
325 Ok(ok) => Ok(ok),
326 Err(error) => match error {
327 Error::Domain(d) => {
328 if d.source().is_some() && (d.source().as_ref().unwrap()).is::<S>() {
329 return Err(op(*d.into_source().unwrap().downcast::<S>().unwrap()).into());
330 }
331
332 Err(Error::Domain(d))
333 }
334 Error::Bug(b) => {
335 if let Some(s) = &b.source {
336 if s.is::<S>() {
337 return Err(op(*b.source.unwrap().downcast::<S>().unwrap()).into());
338 }
339 }
340
341 Err(Error::Bug(b))
342 }
343 },
344 }
345 }
346
347 fn with_context(self, context: impl Display) -> Result<T, Error<D>> {
348 match self {
349 Ok(ok) => Ok(ok),
350 Err(error) => Err(match error {
351 Error::Domain(explicit_error) => explicit_error.with_context(context).into(),
352 Error::Bug(bug) => bug.with_context(context).into(),
353 }),
354 }
355 }
356}
357
358/// To use this trait on [Option] import the prelude `use explicit_error::prelude::*`
359pub trait OptionBug<T> {
360 /// Convert an [Option::None] into a [Result::Err] wrapping a [Bug]
361 /// ```rust
362 /// # use std::fs::File;
363 /// # use explicit_error_exit::{Error, prelude::*};
364 /// fn foo() -> Result<(), Error> {
365 /// let option: Option<u8> = None;
366 /// option.bug().with_context("Help debugging")?;
367 /// # Ok(())
368 /// }
369 /// ```
370 fn bug(self) -> Result<T, Bug>;
371
372 /// Convert an [Option::None] into a [Result::Err] wrapping a [Bug] forcing backtrace capture
373 /// ```rust
374 /// # use std::fs::File;
375 /// # use explicit_error_exit::{Error, prelude::*};
376 /// fn foo() -> Result<(), Error> {
377 /// let option: Option<u8> = None;
378 /// option.bug_force().with_context("Help debugging")?;
379 /// # Ok(())
380 /// }
381 /// ```
382 fn bug_force(self) -> Result<T, Bug>;
383}
384
385impl<T> OptionBug<T> for Option<T> {
386 fn bug(self) -> Result<T, Bug> {
387 match self {
388 Some(ok) => Ok(ok),
389 None => Err(Bug::new()),
390 }
391 }
392
393 fn bug_force(self) -> Result<T, Bug> {
394 match self {
395 Some(ok) => Ok(ok),
396 None => Err(Bug::new_force()),
397 }
398 }
399}
400
401/// To use this trait on [Result] import the prelude `use explicit_error::prelude::*`
402pub trait ResultBugWithContext<T> {
403 /// Add a context to the [Bug] wrapped in a [Result::Err]
404 /// # Examples
405 /// ```rust
406 /// # use explicit_error::{prelude::*, Bug};
407 /// Err::<(), _>(Bug::new()).with_context("Foo bar");
408 /// ```
409 fn with_context(self, context: impl Display) -> Result<T, Bug>;
410}
411
412impl<T> ResultBugWithContext<T> for Result<T, Bug> {
413 fn with_context(self, context: impl Display) -> Result<T, Bug> {
414 match self {
415 Ok(ok) => Ok(ok),
416 Err(b) => Err(b.with_context(context)),
417 }
418 }
419}