use axum::{
Json,
response::{IntoResponse, Response},
};
use crate::error::{Error, Result};
impl IntoResponse for Error {
fn into_response(self) -> Response {
let status = self.code().to_http_status();
let body = Json(serde_json::json!({
"error": {
"code": self.code().to_string(),
"type": self.error_type(),
"message": self.message(),
"data": self.data(),
}
}));
(status, body).into_response()
}
}
pub type HttpResult<T> = Result<T>;
#[cfg(test)]
mod tests {
use super::*;
use crate::{
define_error, define_errors,
error::{ErrorCode, TypedError},
};
use axum::{http::StatusCode, response::IntoResponse};
use http_body_util::BodyExt;
use serde_json::{Value, json};
define_error!(ErrorCode::ResourceNotFound, UserNotFound);
define_error!(ErrorCode::InvalidInput, InvalidEmail);
define_error!(ErrorCode::RateLimited, RateLimitError);
#[tokio::test]
async fn test_error_into_response() {
let error = UserNotFound::error("User 123 not found");
let response = error.into_response();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
let body = response.into_body().collect().await.unwrap().to_bytes();
let json: Value = serde_json::from_slice(&body).unwrap();
assert_eq!(json["error"]["code"], "ResourceNotFound");
assert_eq!(json["error"]["type"], "UserNotFound");
assert_eq!(json["error"]["message"], "User 123 not found");
assert!(json["error"]["data"].is_null());
}
#[tokio::test]
async fn test_with_data_into_response() {
let error = RateLimitError::error_with_data(
"Too many requests",
json!({
"retry_after": 60,
"limit": 100
}),
);
let response = error.into_response();
assert_eq!(response.status(), StatusCode::TOO_MANY_REQUESTS);
let body = response.into_body().collect().await.unwrap().to_bytes();
let json: Value = serde_json::from_slice(&body).unwrap();
assert_eq!(json["error"]["code"], "RateLimited");
assert_eq!(json["error"]["type"], "RateLimitError");
assert_eq!(json["error"]["data"]["retry_after"], 60);
assert_eq!(json["error"]["data"]["limit"], 100);
}
#[tokio::test]
async fn test_all_http_status_codes() {
let test_cases = vec![
(ErrorCode::InvalidInput, StatusCode::BAD_REQUEST),
(ErrorCode::Unauthorized, StatusCode::UNAUTHORIZED),
(ErrorCode::Forbidden, StatusCode::FORBIDDEN),
(ErrorCode::ResourceNotFound, StatusCode::NOT_FOUND),
(ErrorCode::Conflict, StatusCode::CONFLICT),
(ErrorCode::RateLimited, StatusCode::TOO_MANY_REQUESTS),
(ErrorCode::Internal, StatusCode::INTERNAL_SERVER_ERROR),
(ErrorCode::Unavailable, StatusCode::SERVICE_UNAVAILABLE),
(ErrorCode::Timeout, StatusCode::GATEWAY_TIMEOUT),
];
for (code, expected_status) in test_cases {
let error = Error::new(code, "test_error", "Test message");
let response = error.into_response();
assert_eq!(
response.status(),
expected_status,
"Failed for code {:?}",
code
);
}
}
#[tokio::test]
async fn test_define_errors_macro_with_into() {
define_errors! {
ErrorCode::ResourceNotFound => [
TestUserNotFound,
],
}
let error: Error = TestUserNotFound.into();
assert_eq!(error.code(), ErrorCode::ResourceNotFound);
assert_eq!(error.error_type(), "TestUserNotFound");
let response = error.into_response();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[tokio::test]
async fn test_error_json_format() {
let error = InvalidEmail::error_with_data(
"Invalid email format",
json!({"field": "email", "pattern": ".*@.*"}),
);
let response = error.into_response();
let body = response.into_body().collect().await.unwrap().to_bytes();
let json: Value = serde_json::from_slice(&body).unwrap();
assert!(json["error"].is_object());
assert_eq!(json["error"]["code"], "InvalidInput");
assert_eq!(json["error"]["type"], "InvalidEmail");
assert_eq!(json["error"]["message"], "Invalid email format");
assert_eq!(json["error"]["data"]["field"], "email");
assert_eq!(json["error"]["data"]["pattern"], ".*@.*");
}
}