Skip to main content

feldera_types/
error.rs

1use actix_web::ResponseError;
2use actix_web::http::StatusCode;
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/// Information returned by REST API endpoints on error.
11#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ToSchema)]
12pub struct ErrorResponse {
13    /// Human-readable error message.
14    #[schema(example = "Explanation of the error that occurred.")]
15    pub message: String,
16    /// Error code is a string that specifies this error type.
17    #[schema(example = "CodeSpecifyingErrorType")]
18    pub error_code: Cow<'static, str>,
19    /// Detailed error metadata.
20    /// The contents of this field is determined by `error_code`.
21    #[schema(value_type = Object)]
22    pub details: JsonValue,
23}
24
25/// Error trait which internal errors must implement such that it
26/// can be transformed to a complete JSON error response.
27pub trait DetailedError: StdError + ResponseError + Serialize {
28    /// Identifying name of the error.
29    fn error_code(&self) -> Cow<'static, str>;
30}
31
32impl<E> From<&E> for ErrorResponse
33where
34    E: DetailedError,
35{
36    /// Transform the detailed error to a complete JSON error response.
37    /// - The message is retrieved using `to_string()` (available due to trait `StdError`)
38    /// - The status code determines the level of the log statement during this function
39    ///   (available due to trait `ResponseError`)
40    /// - The details are retrieved by serializing to JSON (available due to trait `Serialize`)
41    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        // Transform the error into a response
52        let response = Self::from_error_nolog(error);
53
54        // Log based on the status code
55        if error.status_code().is_success() {
56            // The status code should not be successful for an error response
57            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            // Client-caused error responses
65            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            // All other status responses should not occur in the implementation,
80            // and thus are logged as errors. Many of the implementation errors are
81            // represented as Internal Server Errors (500).
82            error!(
83                "[HTTP error (caused by implementation)] {} {}: {}",
84                error.status_code(),
85                response.error_code,
86                response.message
87            );
88        }
89
90        // Print error backtrace if available for an internal server error
91        if error.status_code() == StatusCode::INTERNAL_SERVER_ERROR
92            && 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        response
101    }
102
103    pub fn from_error_nolog<E>(error: &E) -> Self
104    where
105        E: DetailedError,
106    {
107        let message = error.to_string();
108        let error_code = error.error_code();
109        let details = serde_json::to_value(error).unwrap_or_else(|e| {
110            JsonValue::String(format!("Failed to serialize error. Details: '{e}'"))
111        });
112
113        Self {
114            message,
115            error_code,
116            details,
117        }
118    }
119}