use axum::{
extract::{FromRequest, Request},
response::{IntoResponse, Response},
http::{StatusCode, HeaderMap},
Json,
};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::ops::{Deref, DerefMut};
use crate::error::{HttpError, HttpResult};
use crate::response::ElifResponse;
#[derive(Debug)]
pub struct ElifJson<T>(pub T);
impl<T> ElifJson<T> {
pub fn new(data: T) -> Self {
Self(data)
}
pub fn into_inner(self) -> T {
self.0
}
}
impl<T> Deref for ElifJson<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for ElifJson<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T> From<T> for ElifJson<T> {
fn from(data: T) -> Self {
Self(data)
}
}
#[axum::async_trait]
impl<T, S> FromRequest<S> for ElifJson<T>
where
T: DeserializeOwned,
S: Send + Sync,
{
type Rejection = JsonError;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
match Json::<T>::from_request(req, state).await {
Ok(Json(data)) => Ok(ElifJson(data)),
Err(rejection) => Err(JsonError::from_axum_json_rejection(rejection)),
}
}
}
impl<T> IntoResponse for ElifJson<T>
where
T: Serialize,
{
fn into_response(self) -> Response {
match serde_json::to_vec(&self.0) {
Ok(bytes) => {
let mut response = Response::new(bytes.into());
response.headers_mut().insert(
axum::http::header::CONTENT_TYPE,
axum::http::HeaderValue::from_static("application/json"),
);
response
}
Err(err) => {
tracing::error!("JSON serialization failed: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
"Internal server error: JSON serialization failed"
).into_response()
}
}
}
}
#[derive(Debug)]
pub struct JsonError {
pub status: StatusCode,
pub message: String,
pub details: Option<String>,
}
impl JsonError {
pub fn new(status: StatusCode, message: String) -> Self {
Self {
status,
message,
details: None,
}
}
pub fn with_details(status: StatusCode, message: String, details: String) -> Self {
Self {
status,
message,
details: Some(details),
}
}
pub fn from_axum_json_rejection(rejection: axum::extract::rejection::JsonRejection) -> Self {
use axum::extract::rejection::JsonRejection::*;
match rejection {
JsonDataError(err) => {
Self::with_details(
StatusCode::BAD_REQUEST,
"Invalid JSON data".to_string(),
err.to_string(),
)
}
JsonSyntaxError(err) => {
Self::with_details(
StatusCode::BAD_REQUEST,
"JSON syntax error".to_string(),
err.to_string(),
)
}
MissingJsonContentType(_) => {
Self::new(
StatusCode::BAD_REQUEST,
"Missing 'Content-Type: application/json' header".to_string(),
)
}
BytesRejection(err) => {
Self::with_details(
StatusCode::BAD_REQUEST,
"Failed to read request body".to_string(),
err.to_string(),
)
}
_ => {
Self::new(
StatusCode::BAD_REQUEST,
"Invalid JSON request".to_string(),
)
}
}
}
}
impl IntoResponse for JsonError {
fn into_response(self) -> Response {
let error_body = if let Some(details) = self.details {
serde_json::json!({
"error": {
"code": self.status.as_u16(),
"message": self.message,
"details": details
}
})
} else {
serde_json::json!({
"error": {
"code": self.status.as_u16(),
"message": self.message
}
})
};
match ElifResponse::with_status(self.status)
.json_value(error_body)
.build()
{
Ok(response) => response,
Err(_) => {
(self.status, self.message).into_response()
}
}
}
}
pub struct JsonResponse;
impl JsonResponse {
pub fn ok<T: Serialize>(data: &T) -> HttpResult<Response> {
ElifResponse::json_ok(data)
}
pub fn with_status<T: Serialize>(status: StatusCode, data: &T) -> HttpResult<Response> {
ElifResponse::with_status(status).json(data)?.build()
}
pub fn paginated<T: Serialize>(
data: &[T],
page: u32,
per_page: u32,
total: u64,
) -> HttpResult<Response> {
let total_pages = (total as f64 / per_page as f64).ceil() as u32;
let response_data = serde_json::json!({
"data": data,
"pagination": {
"page": page,
"per_page": per_page,
"total": total,
"total_pages": total_pages,
"has_next": page < total_pages,
"has_prev": page > 1
}
});
ElifResponse::ok().json_value(response_data).build()
}
pub fn error(status: StatusCode, message: &str) -> HttpResult<Response> {
ElifResponse::json_error(status, message)
}
pub fn validation_error<T: Serialize>(errors: &T) -> HttpResult<Response> {
ElifResponse::validation_error(errors)
}
pub fn success_message(message: &str) -> HttpResult<Response> {
let response_data = serde_json::json!({
"success": true,
"message": message
});
ElifResponse::ok().json_value(response_data).build()
}
pub fn created<T: Serialize>(data: &T) -> HttpResult<Response> {
ElifResponse::created().json(data)?.build()
}
pub fn no_content() -> HttpResult<Response> {
ElifResponse::no_content().build()
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ValidationErrors {
pub errors: std::collections::HashMap<String, Vec<String>>,
}
impl ValidationErrors {
pub fn new() -> Self {
Self {
errors: std::collections::HashMap::new(),
}
}
pub fn add_error(&mut self, field: String, error: String) {
self.errors.entry(field).or_insert_with(Vec::new).push(error);
}
pub fn add_errors(&mut self, field: String, errors: Vec<String>) {
self.errors.entry(field).or_insert_with(Vec::new).extend(errors);
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn error_count(&self) -> usize {
self.errors.values().map(|v| v.len()).sum()
}
pub fn to_response(self) -> HttpResult<Response> {
JsonResponse::validation_error(&self)
}
}
impl Default for ValidationErrors {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Serialize)]
pub struct ApiResponse<T> {
pub success: bool,
pub data: Option<T>,
pub message: Option<String>,
pub errors: Option<serde_json::Value>,
}
impl<T: Serialize> ApiResponse<T> {
pub fn success(data: T) -> Self {
Self {
success: true,
data: Some(data),
message: None,
errors: None,
}
}
pub fn success_with_message(data: T, message: String) -> Self {
Self {
success: true,
data: Some(data),
message: Some(message),
errors: None,
}
}
pub fn error(message: String) -> ApiResponse<()> {
ApiResponse {
success: false,
data: None,
message: Some(message),
errors: None,
}
}
pub fn validation_error(message: String, errors: serde_json::Value) -> ApiResponse<()> {
ApiResponse {
success: false,
data: None,
message: Some(message),
errors: Some(errors),
}
}
pub fn to_response(self) -> HttpResult<Response> {
let status = if self.success {
StatusCode::OK
} else {
StatusCode::BAD_REQUEST
};
ElifResponse::with_status(status).json(&self)?.build()
}
}
impl<T: Serialize> IntoResponse for ApiResponse<T> {
fn into_response(self) -> Response {
match self.to_response() {
Ok(response) => response,
Err(e) => {
tracing::error!("Failed to create API response: {}", e);
(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
struct TestData {
name: String,
age: u32,
}
#[test]
fn test_elif_json_wrapper() {
let data = TestData {
name: "John".to_string(),
age: 30,
};
let json_data = ElifJson::new(data.clone());
assert_eq!(json_data.name, data.name);
assert_eq!(json_data.age, data.age);
let extracted = json_data.into_inner();
assert_eq!(extracted, data);
}
#[test]
fn test_validation_errors() {
let mut errors = ValidationErrors::new();
errors.add_error("name".to_string(), "Name is required".to_string());
errors.add_error("age".to_string(), "Age must be positive".to_string());
assert!(errors.has_errors());
assert_eq!(errors.error_count(), 2);
}
#[test]
fn test_api_response() {
let data = TestData {
name: "Jane".to_string(),
age: 25,
};
let success_response = ApiResponse::success(data);
assert!(success_response.success);
assert!(success_response.data.is_some());
let error_response = ApiResponse::<()>::error("Something went wrong".to_string());
assert!(!error_response.success);
assert!(error_response.data.is_none());
assert_eq!(error_response.message, Some("Something went wrong".to_string()));
}
#[test]
fn test_json_response_helpers() {
let data = vec![
TestData { name: "User1".to_string(), age: 20 },
TestData { name: "User2".to_string(), age: 25 },
];
let response = JsonResponse::paginated(&data, 1, 10, 25);
assert!(response.is_ok());
}
}