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}