1use actix_web::http::StatusCode;
2use actix_web::ResponseError;
3use log::{error, info};
4use serde::{Deserialize, Serialize};
5use serde_json::Value as JsonValue;
6use std::{borrow::Cow, error::Error as StdError};
7use utoipa::ToSchema;
8pub const MAX_REPORTED_PARSE_ERRORS: usize = 1_000;
9
10#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ToSchema)]
12pub struct ErrorResponse {
13 #[schema(example = "Explanation of the error that occurred.")]
15 pub message: String,
16 #[schema(example = "CodeSpecifyingErrorType")]
18 pub error_code: Cow<'static, str>,
19 #[schema(value_type = Object)]
22 pub details: JsonValue,
23}
24
25pub trait DetailedError: StdError + ResponseError + Serialize {
28 fn error_code(&self) -> Cow<'static, str>;
30}
31
32impl<E> From<&E> for ErrorResponse
33where
34 E: DetailedError,
35{
36 fn from(error: &E) -> ErrorResponse {
42 Self::from_error(error)
43 }
44}
45
46impl ErrorResponse {
47 pub fn from_error<E>(error: &E) -> Self
48 where
49 E: DetailedError,
50 {
51 let response = Self::from_error_nolog(error);
53
54 if error.status_code().is_success() {
56 error!(
58 "[HTTP error (caused by implementation)] expected error but got success status code {} {}: {}",
59 error.status_code(),
60 response.error_code,
61 response.message
62 );
63 } else if error.status_code().is_client_error() {
64 info!(
66 "[HTTP error (caused by client)] {} {}: {}",
67 error.status_code(),
68 response.error_code,
69 response.message
70 );
71 } else if error.status_code() == StatusCode::SERVICE_UNAVAILABLE {
72 info!(
73 "[HTTP error] {} {}: {}",
74 error.status_code(),
75 response.error_code,
76 response.message
77 );
78 } else {
79 error!(
83 "[HTTP error (caused by implementation)] {} {}: {}",
84 error.status_code(),
85 response.error_code,
86 response.message
87 );
88 }
89
90 if error.status_code() == StatusCode::INTERNAL_SERVER_ERROR {
92 if let Some(backtrace) = response
93 .details
94 .get("backtrace")
95 .and_then(JsonValue::as_str)
96 {
97 error!("Error backtrace:\n{backtrace}");
98 }
99 }
100
101 response
102 }
103
104 pub fn from_error_nolog<E>(error: &E) -> Self
105 where
106 E: DetailedError,
107 {
108 let message = error.to_string();
109 let error_code = error.error_code();
110 let details = serde_json::to_value(error).unwrap_or_else(|e| {
111 JsonValue::String(format!("Failed to serialize error. Details: '{e}'"))
112 });
113
114 Self {
115 message,
116 error_code,
117 details,
118 }
119 }
120}