mas_axum_utils/
fancy_error.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7use axum::{
8    Extension,
9    http::StatusCode,
10    response::{IntoResponse, Response},
11};
12use axum_extra::typed_header::TypedHeader;
13use headers::ContentType;
14use mas_templates::ErrorContext;
15
16use crate::sentry::SentryEventID;
17
18fn build_context(mut err: &dyn std::error::Error) -> ErrorContext {
19    let description = err.to_string();
20    let mut details = Vec::new();
21    while let Some(source) = err.source() {
22        err = source;
23        details.push(err.to_string());
24    }
25
26    ErrorContext::new()
27        .with_description(description)
28        .with_details(details.join("\n"))
29}
30
31pub struct GenericError {
32    error: Box<dyn std::error::Error + 'static>,
33    code: StatusCode,
34}
35
36impl IntoResponse for GenericError {
37    fn into_response(self) -> Response {
38        tracing::warn!(message = &*self.error);
39        let context = build_context(&*self.error);
40        let context_text = format!("{context}");
41
42        (
43            self.code,
44            TypedHeader(ContentType::text()),
45            Extension(context),
46            context_text,
47        )
48            .into_response()
49    }
50}
51
52impl GenericError {
53    pub fn new(code: StatusCode, err: impl std::error::Error + 'static) -> Self {
54        Self {
55            error: Box::new(err),
56            code,
57        }
58    }
59}
60
61pub struct InternalError {
62    error: Box<dyn std::error::Error + 'static>,
63}
64
65impl IntoResponse for InternalError {
66    fn into_response(self) -> Response {
67        tracing::error!(message = &*self.error);
68        let event_id = SentryEventID::for_last_event();
69        let context = build_context(&*self.error);
70        let context_text = format!("{context}");
71
72        (
73            StatusCode::INTERNAL_SERVER_ERROR,
74            TypedHeader(ContentType::text()),
75            event_id,
76            Extension(context),
77            context_text,
78        )
79            .into_response()
80    }
81}
82
83impl<E: std::error::Error + 'static> From<E> for InternalError {
84    fn from(err: E) -> Self {
85        Self {
86            error: Box::new(err),
87        }
88    }
89}
90
91impl InternalError {
92    /// Create a new error from a boxed error
93    #[must_use]
94    pub fn new(error: Box<dyn std::error::Error + 'static>) -> Self {
95        Self { error }
96    }
97
98    /// Create a new error from an [`anyhow::Error`]
99    #[must_use]
100    pub fn from_anyhow(err: anyhow::Error) -> Self {
101        Self {
102            error: err.into_boxed_dyn_error(),
103        }
104    }
105}