80 lines
2.3 KiB
Rust
80 lines
2.3 KiB
Rust
use axum::http::StatusCode;
|
|
use axum::response::{IntoResponse, Response};
|
|
use axum::Json;
|
|
use serde::Serialize;
|
|
|
|
#[derive(Serialize)]
|
|
struct ErrorBody {
|
|
error: String,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum AppError {
|
|
Internal,
|
|
NotFound,
|
|
BadRequest(String),
|
|
}
|
|
|
|
impl IntoResponse for AppError {
|
|
fn into_response(self) -> Response {
|
|
let (status, message) = match self {
|
|
AppError::Internal => (StatusCode::INTERNAL_SERVER_ERROR, "internal server error".to_string()),
|
|
AppError::NotFound => (StatusCode::NOT_FOUND, "not found".to_string()),
|
|
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
|
|
};
|
|
(status, Json(ErrorBody { error: message })).into_response()
|
|
}
|
|
}
|
|
|
|
impl From<sqlx::Error> for AppError {
|
|
fn from(e: sqlx::Error) -> Self {
|
|
tracing::error!("{e}");
|
|
AppError::Internal
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
async fn body_json(error: AppError) -> serde_json::Value {
|
|
let response = error.into_response();
|
|
let bytes = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
|
serde_json::from_slice(&bytes).unwrap()
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn internal_maps_to_500_with_error_body() {
|
|
let response = AppError::Internal.into_response();
|
|
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
|
|
|
let body = body_json(AppError::Internal).await;
|
|
assert_eq!(body["error"], "internal server error");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn not_found_maps_to_404_with_error_body() {
|
|
let response = AppError::NotFound.into_response();
|
|
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
|
|
|
let body = body_json(AppError::NotFound).await;
|
|
assert_eq!(body["error"], "not found");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn bad_request_maps_to_400_and_carries_its_message() {
|
|
let response = AppError::BadRequest("nope".to_string()).into_response();
|
|
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
|
|
|
let body = body_json(AppError::BadRequest("nope".to_string())).await;
|
|
assert_eq!(body["error"], "nope");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn sqlx_errors_map_to_internal() {
|
|
let sqlx_err = sqlx::Error::RowNotFound;
|
|
let app_err: AppError = sqlx_err.into();
|
|
assert!(matches!(app_err, AppError::Internal));
|
|
}
|
|
}
|