use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiMeta {
pub page: Option<u32>,
pub per_page: Option<u32>,
pub total: Option<u64>,
}
impl ApiMeta {
pub fn empty() -> Self {
Self {
page: None,
per_page: None,
total: None,
}
}
pub fn paginated(page: u32, per_page: u32, total: u64) -> Self {
Self {
page: Some(page),
per_page: Some(per_page),
total: Some(total),
}
}
}
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiResponse<T> {
pub data: T,
pub meta: Option<ApiMeta>,
}
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorEnvelope {
pub code: String,
pub message: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn api_meta_empty() {
let meta = ApiMeta::empty();
assert!(meta.page.is_none());
assert!(meta.per_page.is_none());
assert!(meta.total.is_none());
}
#[test]
fn api_meta_paginated() {
let meta = ApiMeta::paginated(2, 50, 200);
assert_eq!(meta.page, Some(2));
assert_eq!(meta.per_page, Some(50));
assert_eq!(meta.total, Some(200));
}
#[test]
fn api_response_roundtrip() {
let response = ApiResponse {
data: vec![1, 2, 3],
meta: Some(ApiMeta::paginated(1, 10, 50)),
};
let json = serde_json::to_string(&response).unwrap();
let deserialized: ApiResponse<Vec<i32>> = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.data, vec![1, 2, 3]);
assert_eq!(deserialized.meta.unwrap().total, Some(50));
}
#[test]
fn error_envelope_roundtrip() {
let envelope = ErrorEnvelope {
code: "BAD_REQUEST".to_string(),
message: "invalid input".to_string(),
};
let json = serde_json::to_string(&envelope).unwrap();
let deserialized: ErrorEnvelope = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.code, "BAD_REQUEST");
assert_eq!(deserialized.message, "invalid input");
}
}