use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonResponse<T> {
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<T>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<ErrorDetail>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorDetail {
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<serde_json::Value>,
}
impl<T: Serialize> JsonResponse<T> {
pub fn success(data: T) -> Self {
Self {
success: true,
data: Some(data),
error: None,
message: None,
}
}
pub fn success_with_message(data: T, message: impl Into<String>) -> Self {
Self {
success: true,
data: Some(data),
error: None,
message: Some(message.into()),
}
}
}
impl<T> JsonResponse<T> {
#[allow(dead_code)] pub fn error(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
success: false,
data: None,
error: Some(ErrorDetail {
code: code.into(),
message: message.into(),
details: None,
}),
message: None,
}
}
#[allow(dead_code)] pub fn error_with_details(
code: impl Into<String>,
message: impl Into<String>,
details: serde_json::Value,
) -> Self {
Self {
success: false,
data: None,
error: Some(ErrorDetail {
code: code.into(),
message: message.into(),
details: Some(details),
}),
message: None,
}
}
}
impl<T: Serialize> JsonResponse<T> {
pub fn to_json_string(&self) -> String {
serde_json::to_string_pretty(self).unwrap_or_else(|e| {
format!(
r#"{{"success":false,"error":{{"code":"SERIALIZATION_ERROR","message":"{}"}}}}"#,
e
)
})
}
}
#[allow(dead_code)] pub fn result_to_json<T: Serialize, E: std::fmt::Display>(result: Result<T, E>) -> String {
match result {
Ok(data) => JsonResponse::success(data).to_json_string(),
Err(e) => JsonResponse::<()>::error("ERROR", e.to_string()).to_json_string(),
}
}
#[allow(dead_code)] pub fn result_to_json_with_message<T: Serialize, E: std::fmt::Display>(
result: Result<T, E>,
message: &str,
) -> String {
match result {
Ok(data) => JsonResponse::success_with_message(data, message).to_json_string(),
Err(e) => JsonResponse::<()>::error("ERROR", e.to_string()).to_json_string(),
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectListData {
pub projects: Vec<crate::models::Project>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectData {
pub project: crate::models::Project,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskListData {
pub tasks: Vec<crate::models::Task>,
pub count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskData {
pub task: crate::models::Task,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubtaskListData {
pub subtasks: Vec<crate::models::ChecklistItem>,
pub count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionData {
pub version: String,
pub name: String,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_success_response() {
let response = JsonResponse::success(json!({"id": "123"}));
assert!(response.success);
assert!(response.data.is_some());
assert!(response.error.is_none());
}
#[test]
fn test_success_with_message() {
let response =
JsonResponse::success_with_message(json!({"id": "123"}), "Task created successfully");
assert!(response.success);
assert_eq!(
response.message,
Some("Task created successfully".to_string())
);
}
#[test]
fn test_error_response() {
let response: JsonResponse<()> = JsonResponse::error("NOT_FOUND", "Task not found");
assert!(!response.success);
assert!(response.data.is_none());
let error = response.error.unwrap();
assert_eq!(error.code, "NOT_FOUND");
assert_eq!(error.message, "Task not found");
}
#[test]
fn test_error_with_details() {
let response: JsonResponse<()> = JsonResponse::error_with_details(
"VALIDATION_ERROR",
"Invalid input",
json!({"field": "title", "reason": "cannot be empty"}),
);
assert!(!response.success);
let error = response.error.unwrap();
assert_eq!(error.code, "VALIDATION_ERROR");
assert!(error.details.is_some());
}
#[test]
fn test_to_json_string() {
let response = JsonResponse::success(json!({"name": "Test"}));
let json_str = response.to_json_string();
assert!(json_str.contains("\"success\": true"));
assert!(json_str.contains("\"name\": \"Test\""));
}
#[test]
fn test_result_to_json_success() {
let result: Result<_, &str> = Ok(json!({"id": "123"}));
let json_str = result_to_json(result);
assert!(json_str.contains("\"success\": true"));
}
#[test]
fn test_result_to_json_error() {
let result: Result<(), _> = Err("Something went wrong");
let json_str = result_to_json(result);
assert!(json_str.contains("\"success\": false"));
assert!(json_str.contains("Something went wrong"));
}
}