Skip to main content

lambda_forge/
error.rs

1use lambda_http::{Body, Response, http::StatusCode, tracing::error};
2use std::fmt;
3
4use crate::{ApiResponseBody, EndpointMetadata};
5
6#[derive(Debug)]
7pub enum Error {
8    MissingLambdaContext,
9    InvalidEnvironmentConfig(&'static str),
10    #[cfg(feature = "db")]
11    DatabaseFailure(sqlx::Error),
12
13    BadRequest(String),
14    ServerError,
15    Unauthorized,
16    RateLimit(String),
17    Conflict(String),
18    // TODO: Support more generic HTTP errors
19}
20
21impl fmt::Display for Error {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        match self {
24            Error::MissingLambdaContext => {
25                write!(f, "`EndpointMetadata` is missing the AWS Lambda Context")
26            }
27            Error::InvalidEnvironmentConfig(err_msg) => {
28                write!(f, "Invalid Environment Config: {err_msg}")
29            }
30            Error::BadRequest(msg) => write!(f, "Bad Request: {msg}"),
31            Error::ServerError => write!(f, "Server Error"),
32            Error::Unauthorized => write!(f, "Unauthorized"),
33            Error::RateLimit(msg) => write!(f, "Rate Limit Exceeded: {msg}"),
34            Error::Conflict(msg) => write!(f, "Conflict: {msg}"),
35
36            #[cfg(feature = "db")]
37            Error::DatabaseFailure(err) => {
38                write!(f, "Database Failure: {err}")
39            }
40        }
41    }
42}
43
44impl std::error::Error for Error {
45    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
46        match self {
47            #[cfg(feature = "db")]
48            Error::DatabaseFailure(e) => Some(e),
49
50            _ => None,
51        }
52    }
53}
54
55impl Error {
56    fn http_status(&self) -> StatusCode {
57        match self {
58            Error::Unauthorized => StatusCode::UNAUTHORIZED,
59            Error::RateLimit(_) => StatusCode::TOO_MANY_REQUESTS,
60            Error::BadRequest(_) => StatusCode::BAD_REQUEST,
61            Error::Conflict(_) => StatusCode::CONFLICT,
62
63            Error::ServerError
64            | Error::MissingLambdaContext
65            | Error::InvalidEnvironmentConfig(_) => StatusCode::INTERNAL_SERVER_ERROR,
66
67            #[cfg(feature = "db")]
68            Error::DatabaseFailure(_) => StatusCode::INTERNAL_SERVER_ERROR,
69        }
70    }
71
72    fn message_for_client(&self) -> String {
73        let server_error_message =
74            "We ran into issues processing your request, please try again later.".to_owned();
75        match self {
76            Error::Unauthorized => "Invalid authorization for requested resource.".to_owned(),
77            Error::RateLimit(msg) => msg.to_owned(),
78            Error::BadRequest(msg) => msg.to_owned(),
79            Error::Conflict(msg) => msg.to_owned(),
80            Error::ServerError
81            | Error::MissingLambdaContext
82            | Error::InvalidEnvironmentConfig(_) => server_error_message,
83
84            #[cfg(feature = "db")]
85            Error::DatabaseFailure(_) => server_error_message,
86        }
87    }
88
89    /// Build a lambda_http::Response with your envelope and proper status.
90    pub fn into_lambda_response(self, endpoint: EndpointMetadata) -> Response<Body> {
91        error!("Converting error to lambda response: {self:?}");
92
93        let status = self.http_status();
94        let body = ApiResponseBody::<serde_json::Value> {
95            was_successful: false,
96            data: None,
97            message: Some(self.message_for_client()),
98        };
99
100        Response::builder()
101            .status(status)
102            .header("content-type", endpoint.response_content_type)
103            .body(Body::from(serde_json::to_string(&body).unwrap()))
104            .unwrap()
105    }
106
107    /// Build a lambda_http::Response with your envelope and proper status.
108    ///
109    /// NOTE: This version has no endpoint context, if you have context
110    /// available then prefer the `into_lambda_response` function instead.
111    pub fn into_lambda_response_no_ctx(self) -> Response<Body> {
112        error!("Converting error to lambda response: {self:?}");
113
114        let status = self.http_status();
115        let body = ApiResponseBody::<serde_json::Value> {
116            was_successful: false,
117            data: None,
118            message: Some(self.message_for_client().to_string()),
119        };
120
121        Response::builder()
122            .status(status)
123            .header("content-type", "application/json")
124            .body(Body::from(serde_json::to_string(&body).unwrap()))
125            .unwrap()
126    }
127}
128
129#[cfg(feature = "db")]
130impl From<sqlx::Error> for Error {
131    fn from(e: sqlx::Error) -> Self {
132        Error::DatabaseFailure(e)
133    }
134}