1use std::{borrow::Cow, collections::HashMap, panic::Location};
28
29use backtrace::Backtrace;
30use eyre::EyreContext;
31use parking_lot::Once;
32use serde::ser::SerializeMap;
33
34mod macros;
35
36#[cfg(feature = "actix")]
37mod actix;
38#[cfg(feature = "axum")]
39mod axum;
40mod commons;
41mod custom;
42pub(crate) use self::custom::*;
43mod ext;
44pub mod http;
46pub mod reporter;
48use self::reporter::Report;
49#[cfg(feature = "sql")]
50pub mod sql;
52
53pub mod prelude {
55 #[cfg(feature = "actix")]
56 pub use super::actix::*;
57 #[cfg(feature = "axum")]
58 pub use super::axum::*;
59 #[cfg(feature = "sql")]
60 pub use super::sql::*;
61 pub use super::{commons::*, custom::*, ext::*, http, Problem, Result};
62}
63
64pub(crate) fn blank_type_uri() -> custom::Uri {
65 custom::Uri::from_static("about:blank")
66}
67
68pub type CowStr = Cow<'static, str>;
70
71pub type Result<T, E = Problem> = std::result::Result<T, E>;
73
74fn install() {
75 static HOOK_INSTALLED: Once = Once::new();
76
77 HOOK_INSTALLED.call_once(|| {
78 eyre::set_hook(Box::new(crate::reporter::capture_handler))
79 .expect("Failed to set error hook, maybe install was already called?");
80 })
81}
82
83#[derive(Default)]
139pub struct Problem {
140 inner: Box<ProblemInner>,
141}
142
143#[derive(Debug)]
144struct ProblemInner {
145 r#type: Uri,
146 title: CowStr,
147 status: StatusCode,
148 details: CowStr,
149 cause: eyre::Report,
150 extensions: Extensions,
151}
152
153impl Default for ProblemInner {
154 fn default() -> Self {
155 Self {
156 r#type: blank_type_uri(),
157 title: Cow::Borrowed(""),
158 status: StatusCode::default(),
159 details: Cow::Borrowed(""),
160 cause: eyre::Report::msg(""),
161 extensions: Extensions::default(),
162 }
163 }
164}
165
166impl ProblemInner {
167 fn report(&self) -> &Report {
168 self.cause
169 .handler()
170 .downcast_ref::<Report>()
171 .expect("Problem used without installation")
172 }
173}
174
175impl serde::Serialize for Problem {
176 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
177 where
178 S: serde::Serializer,
179 {
180 let mut map = serializer.serialize_map(None)?;
181
182 map.serialize_entry(&"status", &self.status().as_u16())?;
183
184 if !matches!(self.type_().scheme_str(), None | Some("about")) {
185 map.serialize_entry(&"type", &format_args!("{}", self.type_()))?;
186 }
187
188 map.serialize_entry(&"title", &self.title())?;
189 map.serialize_entry(&"detail", &self.details())?;
190
191 for (k, v) in &self.extensions().inner {
192 map.serialize_entry(k, v)?;
193 }
194
195 map.end()
196 }
197}
198
199impl std::fmt::Debug for Problem {
200 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201 self.inner.report().debug(self.cause(), f)
202 }
203}
204
205impl std::fmt::Display for Problem {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 use eyre::EyreHandler;
208
209 writeln!(
210 f,
211 "{} - {}: {}",
212 self.status(),
213 self.title(),
214 self.details()
215 )?;
216 self.inner.report().display(&*self.inner.cause, f)?;
217
218 Ok(())
219 }
220}
221
222impl std::error::Error for Problem {
223 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
224 Some(self.cause())
225 }
226}
227
228impl Problem {
229 pub(crate) fn report_as_error(&self) {
230 if let Some(reporter) = self::reporter::global_reporter() {
231 if reporter.should_report_error(self) {
232 reporter.report_error(self);
233 }
234 }
235 }
236}
237
238impl Problem {
240 #[track_caller]
244 pub fn custom(status: StatusCode, r#type: Uri) -> Self {
245 let mut problem = Self::from_status(status);
246 problem.inner.r#type = r#type;
247 problem
248 }
249
250 #[track_caller]
252 pub fn from_status(status: StatusCode) -> Self {
253 install();
254
255 let title = status.canonical_reason().unwrap();
256 Self {
257 inner: Box::new(ProblemInner {
258 title: title.into(),
259 cause: eyre::Report::msg(title),
260 status,
261 ..ProblemInner::default()
262 }),
263 }
264 }
265
266 #[must_use]
272 pub fn with_title(mut self, title: impl Into<CowStr>) -> Self {
273 self.inner.title = title.into();
274 self
275 }
276
277 #[must_use]
279 pub fn with_detail(mut self, detail: impl Into<CowStr>) -> Self {
280 self.inner.details = detail.into();
281 self
282 }
283
284 #[must_use]
286 #[track_caller]
287 pub fn with_cause<E>(mut self, cause: E) -> Self
288 where
289 E: std::error::Error + Send + Sync + 'static,
290 {
291 self.inner.cause = eyre::Report::new(cause);
292 self
293 }
294
295 #[must_use]
304 pub fn with_extension<E, V>(mut self, extension: E, value: V) -> Self
305 where
306 E: Into<CowStr>,
307 V: serde::Serialize,
308 {
309 let extension = extension.into();
310 match extension.as_ref() {
311 "type" | "status" | "details" | "cause" | "" => {
312 panic!("Invalid extension received: {extension}")
313 }
314 _ => self.inner.extensions.insert(extension, value),
315 }
316
317 self
318 }
319}
320
321impl Problem {
323 pub const fn type_(&self) -> &Uri {
331 &self.inner.r#type
332 }
333
334 pub fn title(&self) -> &str {
338 &self.inner.title
339 }
340
341 pub const fn status(&self) -> StatusCode {
344 self.inner.status
345 }
346
347 pub fn details(&self) -> &str {
350 &self.inner.details
351 }
352
353 pub const fn extensions(&self) -> &Extensions {
356 &self.inner.extensions
357 }
358
359 pub fn extensions_mut(&mut self) -> &mut Extensions {
362 &mut self.inner.extensions
363 }
364
365 pub fn cause(&self) -> &(dyn std::error::Error + 'static) {
367 &*self.inner.cause
368 }
369}
370
371impl Problem {
373 #[must_use]
375 pub fn report(&self) -> &Report {
376 self.inner.report()
377 }
378
379 pub fn backtrace(&self) -> Backtrace {
381 (*self.inner.report().backtrace()).clone()
382 }
383
384 pub fn location(&self) -> &'static Location<'static> {
386 self.inner.report().location()
387 }
388
389 pub fn is<E>(&self) -> bool
393 where
394 E: std::error::Error + Send + Sync + 'static,
395 {
396 self.inner.cause.is::<E>()
397 }
398
399 pub fn downcast<E>(mut self) -> Result<E, Self>
406 where
407 E: std::error::Error + Send + Sync + 'static,
408 {
409 match self.inner.cause.downcast() {
410 Ok(err) => Ok(err),
411 Err(cause) => {
412 self.inner.cause = cause;
413 Err(self)
414 }
415 }
416 }
417
418 pub fn downcast_ref<E>(&self) -> Option<&E>
420 where
421 E: std::error::Error + Send + Sync + 'static,
422 {
423 self.inner.cause.downcast_ref()
424 }
425
426 pub fn isolate<E>(self) -> Result<Self, Self>
438 where
439 E: std::error::Error + Send + Sync + 'static,
440 {
441 if self.is::<E>() {
442 Err(self)
443 } else {
444 Ok(self)
445 }
446 }
447}
448
449#[derive(Debug, Clone, Default, serde::Serialize)]
451#[serde(transparent)]
452pub struct Extensions {
453 inner: HashMap<CowStr, serde_json::Value>,
454}
455
456impl Extensions {
457 pub fn insert<K, V>(&mut self, key: K, value: V)
463 where
464 K: Into<CowStr>,
465 V: serde::Serialize,
466 {
467 self.inner.insert(key.into(), serde_json::json!(value));
468 }
469
470 pub fn len(&self) -> usize {
472 self.inner.len()
473 }
474
475 pub fn is_empty(&self) -> bool {
477 self.inner.is_empty()
478 }
479}
480
481impl<'e> IntoIterator for &'e Extensions {
482 type IntoIter = ExtensionsIter<'e>;
483 type Item = (&'e str, &'e serde_json::Value);
484
485 fn into_iter(self) -> Self::IntoIter {
486 ExtensionsIter(self.inner.iter().map(|(k, v)| (&**k, v)))
487 }
488}
489
490use std::{collections::hash_map::Iter, iter::Map};
491
492#[doc(hidden)]
493#[allow(clippy::type_complexity)]
494pub struct ExtensionsIter<'e>(
495 Map<
496 Iter<'e, Cow<'e, str>, serde_json::Value>,
497 for<'a> fn((&'a Cow<'a, str>, &'a serde_json::Value)) -> (&'a str, &'a serde_json::Value),
498 >,
499);
500
501impl<'e> Iterator for ExtensionsIter<'e> {
502 type Item = (&'e str, &'e serde_json::Value);
503
504 fn next(&mut self) -> Option<Self::Item> {
505 self.0.next()
506 }
507}
508
509#[cfg(test)]
510mod tests {
511 use std::error::Error;
512
513 use serde_json::json;
514
515 use super::*;
516
517 #[test]
518 fn test_extensions() {
519 let mut ext = Extensions::default();
520
521 assert!(ext.is_empty());
522 assert_eq!(ext.len(), 0);
523 assert!(ext.into_iter().next().is_none());
524
525 ext.insert("bla", "bla");
526
527 assert_eq!(ext.len(), 1);
528 assert!(!ext.is_empty());
529 assert_eq!(ext.into_iter().next(), Some(("bla", &json!("bla"))));
530
531 assert_eq!(json!(ext), json!({ "bla": "bla" }));
532 }
533
534 #[test]
535 fn test_problem_with_extensions_good() {
536 let mut error = http::failed_precondition();
537
538 for (key, value) in [
539 ("bla", json!("bla")),
540 ("foo", json!(1)),
541 ("bar", json!(1.2)),
542 ("baz", json!([1.2])),
543 ] {
544 error = error.with_extension(key, value);
545 }
546
547 assert_eq!(error.extensions().len(), 4);
548 }
549
550 macro_rules! test_invalid_extension {
551 ($test_fn: ident, $ext: literal) => {
552 #[test]
553 #[should_panic = concat!("Invalid extension received: ", $ext)]
554 fn $test_fn() {
555 let _res = http::failed_precondition().with_extension($ext, json!(1));
556 }
557 };
558 }
559
560 test_invalid_extension!(test_problem_with_extension_type, "type");
561 test_invalid_extension!(test_problem_with_extension_status, "status");
562 test_invalid_extension!(test_problem_with_extension_details, "details");
563 test_invalid_extension!(test_problem_with_extension_cause, "cause");
564 test_invalid_extension!(test_problem_with_extension_empty, "");
565
566 #[test]
567 fn test_problem_getter_type_() {
568 assert_eq!(http::failed_precondition().type_(), "about:blank");
569 }
570
571 #[test]
572 fn test_problem_getter_report() {
573 let err = http::failed_precondition();
574 let report = err.report();
575
576 assert_eq!(err.location(), report.location());
577 }
578
579 #[test]
580 fn test_problem_error_handling() {
581 let err = http::failed_precondition();
582
583 assert!(err.is::<http::PreconditionFailed>());
584 assert!(err.downcast_ref::<http::PreconditionFailed>().is_some());
585 assert!(err.isolate::<http::PreconditionFailed>().is_err());
586
587 let err = http::failed_precondition();
588 assert!(!err.is::<http::NotFound>());
589 assert!(err.downcast_ref::<http::NotFound>().is_none());
590 assert!(err.isolate::<http::NotFound>().is_ok());
591
592 let err = http::failed_precondition();
593 assert!(err.downcast::<http::PreconditionFailed>().is_ok());
594
595 let err = http::failed_precondition();
596 assert!(err.downcast::<http::NotFound>().is_err());
597 }
598
599 #[test]
600 fn test_problem_source() {
601 let err = http::failed_precondition();
602 let source = err.source().unwrap() as *const dyn Error as *const ();
603 let cause = err.cause() as *const dyn Error as *const ();
604
605 assert!(core::ptr::eq(source, cause));
606 }
607
608 #[test]
609 fn test_problem_serialize_no_type() {
610 let err = http::failed_precondition()
611 .with_detail("Failed a precondition")
612 .with_extension("foo", "bar");
613
614 assert_eq!(
615 json!(err),
616 json!({
617 "detail": "Failed a precondition",
618 "foo": "bar",
619 "status": 412,
620 "title": "Precondition Failed",
621 })
622 );
623 }
624
625 #[test]
626 fn test_problem_serialize_type() {
627 let err = Problem::custom(
628 StatusCode::PRECONDITION_FAILED,
629 Uri::from_static("https://my.beautiful.error"),
630 )
631 .with_detail("Failed a precondition")
632 .with_extension("foo", "bar");
633
634 assert_eq!(
635 json!(err),
636 json!({
637 "detail": "Failed a precondition",
638 "foo": "bar",
639 "status": 412,
640 "title": "Precondition Failed",
641 "type": "https://my.beautiful.error/",
642 })
643 );
644 }
645}