headless_lms_server/domain/
error.rs

1/*!
2Contains error and result types for all the controllers.
3*/
4
5use std::{error::Error, fmt::Write};
6
7use crate::domain::authorization::AuthorizedResponse;
8use actix_web::{
9    HttpResponse, HttpResponseBuilder, error,
10    http::{StatusCode, header::ContentType},
11};
12use backtrace::Backtrace;
13use derive_more::Display;
14use headless_lms_models::{ModelError, ModelErrorType};
15use headless_lms_utils::error::{
16    backend_error::BackendError, backtrace_formatter::format_backtrace, util_error::UtilError,
17};
18use serde::{Deserialize, Serialize};
19use tracing_error::SpanTrace;
20#[cfg(feature = "ts_rs")]
21use ts_rs::TS;
22use uuid::Uuid;
23
24/**
25Used as the result types for all controllers.
26Only put information here that you want to be visible to users.
27
28See also [ControllerError] for documentation on how to return errors from controllers.
29*/
30pub type ControllerResult<T, E = ControllerError> = std::result::Result<AuthorizedResponse<T>, E>;
31
32/// The type of [ControllerError] that occured.
33#[derive(Debug, Display, Serialize, Deserialize)]
34pub enum ControllerErrorType {
35    /// HTTP status code 500.
36    #[display("Internal server error")]
37    InternalServerError,
38
39    /// HTTP status code 400.
40    #[display("Bad request")]
41    BadRequest,
42
43    /// HTTP status code 400.
44    #[display("Bad request")]
45    BadRequestWithData(ErrorData),
46
47    /// HTTP status code 404.
48    #[display("Not found")]
49    NotFound,
50
51    /// HTTP status code 401. Needs to log in.
52    #[display("Unauthorized")]
53    Unauthorized,
54
55    /// HTTP status code 403. Is logged in but is not allowed to access the resource.
56    #[display("Forbidden")]
57    Forbidden,
58}
59
60/**
61Represents error messages that are sent in responses. Used as the error type in [ControllerError], which is used by all the controllers in the application.
62
63All the information in the error is meant to be seen by the user. The type of error is determined by the [ControllerErrorType] enum, which is stored inside this struct. The type of the error determines which HTTP status code will be sent to the user.
64
65## Examples
66
67### Usage without source error
68
69```no_run
70# use headless_lms_server::prelude::*;
71# fn random_function() -> ControllerResult<web::Json<()>> {
72#    let token = skip_authorize();
73#    let erroneous_condition = 1 == 1;
74if erroneous_condition {
75    return Err(ControllerError::new(
76        ControllerErrorType::BadRequest,
77        "Cannot create a new account when signed in.".to_string(),
78        None,
79    ));
80}
81# token.authorized_ok(web::Json(()))
82# }
83```
84
85### Usage with a source error
86
87Used when calling a function that returns an error that cannot be automatically converted to an ControllerError. (See `impl From<X>` implementations on this struct.)
88
89```no_run
90# use headless_lms_server::prelude::*;
91# fn some_function_returning_an_error() -> ControllerResult<web::Json<()>> {
92#    return Err(ControllerError::new(
93#         ControllerErrorType::BadRequest,
94#         "Cannot create a new account when signed in.".to_string(),
95#         None,
96#     ));
97# }
98#
99# fn random_function() -> ControllerResult<web::Json<()>> {
100#    let token = skip_authorize();
101#    let erroneous_condition = 1 == 1;
102some_function_returning_an_error().map_err(|original_error| {
103    ControllerError::new(
104        ControllerErrorType::InternalServerError,
105        "Could not read file".to_string(),
106        Some(original_error.into()),
107    )
108})?;
109# token.authorized_ok(web::Json(()))
110# }
111```
112
113### Example HTTP response from an error
114
115```json
116{
117    "title": "Internal Server Error",
118    "message": "pool timed out while waiting for an open connection",
119    "source": "source of error"
120}
121```
122*/
123pub struct ControllerError {
124    error_type: <ControllerError as BackendError>::ErrorType,
125    message: String,
126    /// Original error that caused this error.
127    source: Option<anyhow::Error>,
128    /// A trace of tokio tracing spans, generated automatically when the error is generated.
129    span_trace: Box<SpanTrace>,
130    /// Stack trace, generated automatically when the error is created.
131    backtrace: Box<Backtrace>,
132}
133
134/// Custom formatter so that errors that get printed to the console are easy-to-read with proper context where the error is coming from.
135impl std::fmt::Debug for ControllerError {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        f.debug_struct("ControllerError")
138            .field("error_type", &self.error_type)
139            .field("message", &self.message)
140            .field("source", &self.source)
141            .finish()?;
142
143        f.write_str("\n\nOperating system thread stack backtrace:\n")?;
144        format_backtrace(&self.backtrace, f)?;
145
146        f.write_str("\n\nTokio tracing span trace:\n")?;
147        f.write_fmt(format_args!("{}\n", &self.span_trace))?;
148
149        Ok(())
150    }
151}
152
153impl std::error::Error for ControllerError {
154    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
155        self.source.as_ref().and_then(|o| o.source())
156    }
157
158    fn cause(&self) -> Option<&dyn std::error::Error> {
159        self.source()
160    }
161}
162
163impl std::fmt::Display for ControllerError {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        write!(
166            f,
167            "ControllerError {:?} {:?}",
168            self.error_type, self.message
169        )
170    }
171}
172
173impl BackendError for ControllerError {
174    type ErrorType = ControllerErrorType;
175
176    fn new<M: Into<String>, S: Into<Option<anyhow::Error>>>(
177        error_type: Self::ErrorType,
178        message: M,
179        source_error: S,
180    ) -> Self {
181        Self::new_with_traces(
182            error_type,
183            message,
184            source_error,
185            Backtrace::new(),
186            SpanTrace::capture(),
187        )
188    }
189
190    fn backtrace(&self) -> Option<&Backtrace> {
191        Some(&self.backtrace)
192    }
193
194    fn error_type(&self) -> &Self::ErrorType {
195        &self.error_type
196    }
197
198    fn message(&self) -> &str {
199        &self.message
200    }
201
202    fn span_trace(&self) -> &SpanTrace {
203        &self.span_trace
204    }
205
206    fn new_with_traces<M: Into<String>, S: Into<Option<anyhow::Error>>>(
207        error_type: Self::ErrorType,
208        message: M,
209        source_error: S,
210        backtrace: Backtrace,
211        span_trace: SpanTrace,
212    ) -> Self {
213        Self {
214            error_type,
215            message: message.into(),
216            source: source_error.into(),
217            span_trace: Box::new(span_trace),
218            backtrace: Box::new(backtrace),
219        }
220    }
221}
222
223#[derive(Debug, Serialize, Deserialize, Clone)]
224#[cfg_attr(feature = "ts_rs", derive(TS))]
225#[serde(rename_all = "snake_case")]
226pub enum ErrorData {
227    BlockId(Uuid),
228}
229
230/// The format all error messages from the API is in
231#[derive(Debug, Serialize, Deserialize)]
232#[cfg_attr(feature = "ts_rs", derive(TS))]
233pub struct ErrorResponse {
234    pub title: String,
235    pub message: String,
236    pub source: Option<String>,
237    pub data: Option<ErrorData>,
238}
239
240impl error::ResponseError for ControllerError {
241    fn error_response(&self) -> HttpResponse {
242        if let ControllerErrorType::InternalServerError = &self.error_type {
243            let mut err_string = String::new();
244            let mut source = Some(&self as &dyn Error);
245            while let Some(err) = source {
246                let res = write!(err_string, "{}\n    ", err);
247                if let Err(e) = res {
248                    error!(
249                        "Error occured while trying to construct error source string: {}",
250                        e
251                    );
252                }
253                source = err.source();
254            }
255            error!("Internal server error: {}", err_string);
256        }
257
258        let status = self.status_code();
259        let error_data = if let ControllerErrorType::BadRequestWithData(data) = &self.error_type {
260            Some(data.clone())
261        } else {
262            None
263        };
264
265        let source_message = if let Some(anyhow_err) = &self.source {
266            if let Some(controller_err) = anyhow_err.downcast_ref::<ControllerError>() {
267                Some(controller_err.message.clone())
268            } else {
269                Some(anyhow_err.to_string())
270            }
271        } else {
272            None
273        };
274
275        let error_response = ErrorResponse {
276            title: status
277                .canonical_reason()
278                .map(|o| o.to_string())
279                .unwrap_or_else(|| status.to_string()),
280            message: self.message.clone(),
281            source: source_message,
282            data: error_data,
283        };
284
285        HttpResponseBuilder::new(status)
286            .append_header(ContentType::json())
287            .body(serde_json::to_string(&error_response).unwrap_or_else(|_| r#"{"title": "Internal server error", "message": "Error occured while formatting error message."}"#.to_string()))
288    }
289
290    fn status_code(&self) -> StatusCode {
291        match self.error_type {
292            ControllerErrorType::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
293            ControllerErrorType::BadRequest => StatusCode::BAD_REQUEST,
294            ControllerErrorType::BadRequestWithData(_) => StatusCode::BAD_REQUEST,
295            ControllerErrorType::NotFound => StatusCode::NOT_FOUND,
296            ControllerErrorType::Unauthorized => StatusCode::UNAUTHORIZED,
297            ControllerErrorType::Forbidden => StatusCode::FORBIDDEN,
298        }
299    }
300}
301
302impl From<anyhow::Error> for ControllerError {
303    fn from(err: anyhow::Error) -> ControllerError {
304        if let Some(sqlx::Error::RowNotFound) = err.downcast_ref::<sqlx::Error>() {
305            return Self::new(ControllerErrorType::NotFound, err.to_string(), Some(err));
306        }
307
308        Self::new(
309            ControllerErrorType::InternalServerError,
310            err.to_string(),
311            Some(err),
312        )
313    }
314}
315
316impl From<uuid::Error> for ControllerError {
317    fn from(err: uuid::Error) -> ControllerError {
318        Self::new(
319            ControllerErrorType::BadRequest,
320            err.to_string(),
321            Some(err.into()),
322        )
323    }
324}
325
326impl From<sqlx::Error> for ControllerError {
327    fn from(err: sqlx::Error) -> ControllerError {
328        Self::new(
329            ControllerErrorType::InternalServerError,
330            err.to_string(),
331            Some(err.into()),
332        )
333    }
334}
335
336impl From<git2::Error> for ControllerError {
337    fn from(err: git2::Error) -> ControllerError {
338        Self::new(
339            ControllerErrorType::InternalServerError,
340            err.to_string(),
341            Some(err.into()),
342        )
343    }
344}
345
346impl From<actix_web::Error> for ControllerError {
347    fn from(err: actix_web::Error) -> Self {
348        Self::new(
349            ControllerErrorType::InternalServerError,
350            err.to_string(),
351            None,
352        )
353    }
354}
355
356impl From<actix_multipart::MultipartError> for ControllerError {
357    fn from(err: actix_multipart::MultipartError) -> Self {
358        Self::new(
359            ControllerErrorType::InternalServerError,
360            err.to_string(),
361            None,
362        )
363    }
364}
365
366impl From<ModelError> for ControllerError {
367    fn from(err: ModelError) -> Self {
368        let backtrace: Backtrace =
369            match headless_lms_utils::error::backend_error::BackendError::backtrace(&err) {
370                Some(backtrace) => backtrace.clone(),
371                _ => Backtrace::new(),
372            };
373        let span_trace = err.span_trace().clone();
374        match err.error_type() {
375            ModelErrorType::RecordNotFound => Self::new_with_traces(
376                ControllerErrorType::NotFound,
377                err.to_string(),
378                Some(err.into()),
379                backtrace,
380                span_trace,
381            ),
382            ModelErrorType::NotFound => Self::new_with_traces(
383                ControllerErrorType::NotFound,
384                err.to_string(),
385                Some(err.into()),
386                backtrace,
387                span_trace,
388            ),
389            ModelErrorType::PreconditionFailed => Self::new_with_traces(
390                ControllerErrorType::BadRequest,
391                err.message().to_string(),
392                Some(err.into()),
393                backtrace,
394                span_trace,
395            ),
396            ModelErrorType::PreconditionFailedWithCMSAnchorBlockId { description, id } => {
397                Self::new_with_traces(
398                    ControllerErrorType::BadRequestWithData(ErrorData::BlockId(*id)),
399                    description.to_string(),
400                    Some(err.into()),
401                    backtrace,
402                    span_trace,
403                )
404            }
405            ModelErrorType::DatabaseConstraint { description, .. } => Self::new_with_traces(
406                ControllerErrorType::BadRequest,
407                description.to_string(),
408                Some(err.into()),
409                backtrace,
410                span_trace,
411            ),
412            ModelErrorType::InvalidRequest => Self::new_with_traces(
413                ControllerErrorType::BadRequest,
414                err.message().to_string(),
415                Some(err.into()),
416                backtrace,
417                span_trace,
418            ),
419            _ => Self::new_with_traces(
420                ControllerErrorType::InternalServerError,
421                err.to_string(),
422                Some(err.into()),
423                backtrace,
424                span_trace,
425            ),
426        }
427    }
428}
429
430impl From<UtilError> for ControllerError {
431    fn from(err: UtilError) -> Self {
432        let backtrace: Backtrace =
433            match headless_lms_utils::error::backend_error::BackendError::backtrace(&err) {
434                Some(backtrace) => backtrace.clone(),
435                _ => Backtrace::new(),
436            };
437        let span_trace = err.span_trace().clone();
438        Self::new_with_traces(
439            ControllerErrorType::InternalServerError,
440            err.to_string(),
441            Some(err.into()),
442            backtrace,
443            span_trace,
444        )
445    }
446}