headless_lms_server/domain/
error.rs1use 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
24pub type ControllerResult<T, E = ControllerError> = std::result::Result<AuthorizedResponse<T>, E>;
31
32#[derive(Debug, Display, Serialize, Deserialize)]
34pub enum ControllerErrorType {
35 #[display("Internal server error")]
37 InternalServerError,
38
39 #[display("Bad request")]
41 BadRequest,
42
43 #[display("Bad request")]
45 BadRequestWithData(ErrorData),
46
47 #[display("Not found")]
49 NotFound,
50
51 #[display("Unauthorized")]
53 Unauthorized,
54
55 #[display("Forbidden")]
57 Forbidden,
58}
59
60pub struct ControllerError {
124 error_type: <ControllerError as BackendError>::ErrorType,
125 message: String,
126 source: Option<anyhow::Error>,
128 span_trace: Box<SpanTrace>,
130 backtrace: Box<Backtrace>,
132}
133
134impl 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#[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}