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 }
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 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 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}