use axum::{
http::StatusCode,
response::{IntoResponse, Json},
};
use serde::Serialize;
#[derive(Serialize)]
struct ApiResponse<T: Serialize> {
status: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<T>,
#[serde(skip_serializing_if = "Option::is_none")]
message: Option<String>,
}
pub fn success<T: Serialize>(data: T) -> impl IntoResponse {
let response = ApiResponse {
status: "success",
data: Some(data),
message: None,
};
(StatusCode::OK, Json(response))
}
#[must_use]
pub fn error(status_code: StatusCode, message: String) -> impl IntoResponse {
let response = ApiResponse::<()> {
status: "error",
data: None,
message: Some(message),
};
(status_code, Json(response))
}
#[cfg(test)]
mod tests {
use super::*;
use axum::body::to_bytes;
use axum::response::Response;
use serde::Serialize;
use serde_json::{Value, json};
#[derive(Serialize, Debug, PartialEq)]
struct TestData {
id: u32,
name: String,
}
async fn get_body_as_json(response: Response) -> Value {
let body_bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap();
serde_json::from_slice(&body_bytes).unwrap()
}
#[tokio::test]
async fn test_success_response_format() {
let test_data = TestData {
id: 123,
name: "test-item".to_string(),
};
let response = success(test_data).into_response();
assert_eq!(response.status(), StatusCode::OK);
let body = get_body_as_json(response).await;
assert_eq!(body["status"], "success");
assert_eq!(body["data"]["id"], 123);
assert_eq!(body["data"]["name"], "test-item");
assert_eq!(body["message"], json!(null));
}
#[tokio::test]
async fn test_error_response_format() {
let error_message = "Resource not found".to_string();
let status_code = StatusCode::NOT_FOUND;
let response = error(status_code, error_message.clone()).into_response();
assert_eq!(response.status(), status_code);
let body = get_body_as_json(response).await;
assert_eq!(body["status"], "error");
assert_eq!(body["message"], error_message);
assert_eq!(body["data"], json!(null));
}
}