use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiResponse<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<String>,
pub timestamp: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, serde_json::Value>>,
}
impl<T> ApiResponse<T> {
pub fn success(data: T) -> Self {
Self {
success: true,
data: Some(data),
error: None,
timestamp: Utc::now(),
metadata: None,
}
}
pub fn success_with_metadata(data: T, metadata: HashMap<String, serde_json::Value>) -> Self {
Self {
success: true,
data: Some(data),
error: None,
timestamp: Utc::now(),
metadata: Some(metadata),
}
}
pub fn error(error: String) -> Self {
Self {
success: false,
data: None,
error: Some(error),
timestamp: Utc::now(),
metadata: None,
}
}
pub fn error_with_metadata(
error: String,
metadata: HashMap<String, serde_json::Value>,
) -> Self {
Self {
success: false,
data: None,
error: Some(error),
timestamp: Utc::now(),
metadata: Some(metadata),
}
}
pub fn with_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
self.metadata = Some(metadata);
self
}
pub fn with_meta(mut self, key: String, value: serde_json::Value) -> Self {
let mut metadata = self.metadata.unwrap_or_default();
metadata.insert(key, value);
self.metadata = Some(metadata);
self
}
}
pub use crate::utils::pagination::PaginatedResponse as ApiPaginatedResponse;
pub trait ToDto<T> {
fn to_dto(&self) -> T;
}
pub trait FromDto<T> {
fn from_dto(dto: T) -> Self;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct PartialUpdate<T> {
fields: HashMap<String, serde_json::Value>,
#[serde(skip)]
_phantom: std::marker::PhantomData<T>,
}
impl<T> PartialUpdate<T> {
pub fn new() -> Self {
Self {
fields: HashMap::new(),
_phantom: std::marker::PhantomData,
}
}
pub fn set<V: Serialize>(mut self, key: impl Into<String>, value: V) -> Self {
let json_value = serde_json::to_value(value).expect("Failed to serialize value");
self.fields.insert(key.into(), json_value);
self
}
pub fn remove(mut self, key: impl Into<String>) -> Self {
self.fields.insert(key.into(), serde_json::Value::Null);
self
}
pub fn fields(&self) -> &HashMap<String, serde_json::Value> {
&self.fields
}
pub fn has_field(&self, key: &str) -> bool {
self.fields.contains_key(key)
}
pub fn get_field(&self, key: &str) -> Option<&serde_json::Value> {
self.fields.get(key)
}
pub fn apply_to(&self, mut target: serde_json::Value) -> serde_json::Value {
if let serde_json::Value::Object(ref mut map) = target {
for (key, value) in &self.fields {
if value.is_null() {
map.remove(key);
} else {
map.insert(key.clone(), value.clone());
}
}
}
target
}
}
impl<T> Default for PartialUpdate<T> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchResult<T> {
pub succeeded: Vec<T>,
pub failed: Vec<BatchError>,
pub total: usize,
pub success_count: usize,
pub failure_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchError {
pub index: usize,
pub error: String,
}
impl<T> BatchResult<T> {
pub fn new() -> Self {
Self {
succeeded: Vec::new(),
failed: Vec::new(),
total: 0,
success_count: 0,
failure_count: 0,
}
}
pub fn add_success(&mut self, item: T) {
self.succeeded.push(item);
self.success_count += 1;
self.total += 1;
}
pub fn add_failure(&mut self, index: usize, error: String) {
self.failed.push(BatchError { index, error });
self.failure_count += 1;
self.total += 1;
}
pub fn all_succeeded(&self) -> bool {
self.failure_count == 0
}
pub fn success_rate(&self) -> f64 {
if self.total == 0 {
0.0
} else {
(self.success_count as f64 / self.total as f64) * 100.0
}
}
}
impl<T> Default for BatchResult<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_api_response_success() {
let response = ApiResponse::success("test data");
assert!(response.success);
assert_eq!(response.data, Some("test data"));
assert!(response.error.is_none());
}
#[test]
fn test_api_response_error() {
let response: ApiResponse<String> = ApiResponse::error("test error".to_string());
assert!(!response.success);
assert!(response.data.is_none());
assert_eq!(response.error, Some("test error".to_string()));
}
#[test]
fn test_api_response_with_metadata() {
let mut metadata = HashMap::new();
metadata.insert("key".to_string(), json!("value"));
let response = ApiResponse::success("test").with_metadata(metadata.clone());
assert!(response.metadata.is_some());
assert_eq!(response.metadata.unwrap().get("key"), metadata.get("key"));
}
#[test]
fn test_partial_update() {
let update = PartialUpdate::<String>::new()
.set("name", "John")
.set("age", 30);
assert!(update.has_field("name"));
assert!(update.has_field("age"));
assert!(!update.has_field("email"));
}
#[test]
fn test_partial_update_apply() {
let update = PartialUpdate::<String>::new()
.set("name", "John")
.remove("old_field");
let original = json!({
"name": "Jane",
"age": 25,
"old_field": "remove me"
});
let updated = update.apply_to(original);
assert_eq!(updated["name"], "John");
assert_eq!(updated["age"], 25);
assert!(updated.get("old_field").is_none());
}
#[test]
fn test_batch_result() {
let mut result = BatchResult::<i32>::new();
result.add_success(1);
result.add_success(2);
result.add_failure(2, "Error processing item".to_string());
assert_eq!(result.total, 3);
assert_eq!(result.success_count, 2);
assert_eq!(result.failure_count, 1);
assert!(!result.all_succeeded());
assert!((result.success_rate() - 66.67).abs() < 0.1);
}
}