use lambda_http::{Body, Response, http::StatusCode, tracing::error};
use std::fmt;
use crate::{ApiResponseBody, EndpointMetadata};
#[derive(Debug)]
pub enum Error {
MissingLambdaContext,
InvalidEnvironmentConfig(&'static str),
#[cfg(feature = "db")]
DatabaseFailure(sqlx::Error),
BadRequest(String),
ServerError,
Unauthorized,
RateLimit(String),
Conflict(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::MissingLambdaContext => {
write!(f, "`EndpointMetadata` is missing the AWS Lambda Context")
}
Error::InvalidEnvironmentConfig(err_msg) => {
write!(f, "Invalid Environment Config: {err_msg}")
}
Error::BadRequest(msg) => write!(f, "Bad Request: {msg}"),
Error::ServerError => write!(f, "Server Error"),
Error::Unauthorized => write!(f, "Unauthorized"),
Error::RateLimit(msg) => write!(f, "Rate Limit Exceeded: {msg}"),
Error::Conflict(msg) => write!(f, "Conflict: {msg}"),
#[cfg(feature = "db")]
Error::DatabaseFailure(err) => {
write!(f, "Database Failure: {err}")
}
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
#[cfg(feature = "db")]
Error::DatabaseFailure(e) => Some(e),
_ => None,
}
}
}
impl Error {
fn http_status(&self) -> StatusCode {
match self {
Error::Unauthorized => StatusCode::UNAUTHORIZED,
Error::RateLimit(_) => StatusCode::TOO_MANY_REQUESTS,
Error::BadRequest(_) => StatusCode::BAD_REQUEST,
Error::Conflict(_) => StatusCode::CONFLICT,
Error::ServerError
| Error::MissingLambdaContext
| Error::InvalidEnvironmentConfig(_) => StatusCode::INTERNAL_SERVER_ERROR,
#[cfg(feature = "db")]
Error::DatabaseFailure(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn message_for_client(&self) -> String {
let server_error_message =
"We ran into issues processing your request, please try again later.".to_owned();
match self {
Error::Unauthorized => "Invalid authorization for requested resource.".to_owned(),
Error::RateLimit(msg) => msg.to_owned(),
Error::BadRequest(msg) => msg.to_owned(),
Error::Conflict(msg) => msg.to_owned(),
Error::ServerError
| Error::MissingLambdaContext
| Error::InvalidEnvironmentConfig(_) => server_error_message,
#[cfg(feature = "db")]
Error::DatabaseFailure(_) => server_error_message,
}
}
pub fn into_lambda_response(self, endpoint: EndpointMetadata) -> Response<Body> {
error!("Converting error to lambda response: {self:?}");
let status = self.http_status();
let body = ApiResponseBody::<serde_json::Value> {
was_successful: false,
data: None,
message: Some(self.message_for_client()),
};
Response::builder()
.status(status)
.header("content-type", endpoint.response_content_type)
.body(Body::from(serde_json::to_string(&body).unwrap()))
.unwrap()
}
pub fn into_lambda_response_no_ctx(self) -> Response<Body> {
error!("Converting error to lambda response: {self:?}");
let status = self.http_status();
let body = ApiResponseBody::<serde_json::Value> {
was_successful: false,
data: None,
message: Some(self.message_for_client().to_string()),
};
Response::builder()
.status(status)
.header("content-type", "application/json")
.body(Body::from(serde_json::to_string(&body).unwrap()))
.unwrap()
}
}
#[cfg(feature = "db")]
impl From<sqlx::Error> for Error {
fn from(e: sqlx::Error) -> Self {
Error::DatabaseFailure(e)
}
}