use serde::{Deserialize, Serialize};
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ApiMeta {
pub request_id: String,
pub timestamp: String, }
impl ApiMeta {
pub fn new(request_id: impl Into<String>) -> Self {
let ts = OffsetDateTime::now_utc()
.format(&Rfc3339)
.unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_string());
Self {
request_id: request_id.into(),
timestamp: ts,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ApiErrorDetail {
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub field: Option<String>,
}
impl ApiErrorDetail {
pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
code: code.into(),
message: message.into(),
field: None,
}
}
pub fn with_field(mut self, field: impl Into<String>) -> Self {
self.field = Some(field.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ApiResponse<T> {
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<T>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub errors: Vec<ApiErrorDetail>,
pub meta: ApiMeta,
}
impl<T> ApiResponse<T> {
pub fn ok(data: T, request_id: impl Into<String>) -> Self {
Self {
success: true,
data: Some(data),
errors: vec![],
meta: ApiMeta::new(request_id),
}
}
pub fn empty_ok(request_id: impl Into<String>) -> Self {
Self {
success: true,
data: None,
errors: vec![],
meta: ApiMeta::new(request_id),
}
}
pub fn err(errors: Vec<ApiErrorDetail>, request_id: impl Into<String>) -> Self {
Self {
success: false,
data: None,
errors,
meta: ApiMeta::new(request_id),
}
}
}