Skip to main content

road_runner_common/
response.rs

1use serde::{Deserialize, Serialize};
2use time::{format_description::well_known::Rfc3339, OffsetDateTime};
3
4#[cfg(feature = "openapi")]
5use utoipa::ToSchema;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8#[cfg_attr(feature = "openapi", derive(ToSchema))]
9pub struct ApiMeta {
10    pub request_id: String,
11    pub timestamp: String, // RFC3339
12}
13
14impl ApiMeta {
15    pub fn new(request_id: impl Into<String>) -> Self {
16        let ts = OffsetDateTime::now_utc()
17            .format(&Rfc3339)
18            .unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_string());
19
20        Self {
21            request_id: request_id.into(),
22            timestamp: ts,
23        }
24    }
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28#[cfg_attr(feature = "openapi", derive(ToSchema))]
29pub struct ApiErrorDetail {
30    pub code: String,
31    pub message: String,
32
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub field: Option<String>,
35}
36
37impl ApiErrorDetail {
38    pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
39        Self {
40            code: code.into(),
41            message: message.into(),
42            field: None,
43        }
44    }
45
46    pub fn with_field(mut self, field: impl Into<String>) -> Self {
47        self.field = Some(field.into());
48        self
49    }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53#[cfg_attr(feature = "openapi", derive(ToSchema))]
54pub struct ApiResponse<T> {
55    pub success: bool,
56
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub data: Option<T>,
59
60    #[serde(skip_serializing_if = "Vec::is_empty", default)]
61    pub errors: Vec<ApiErrorDetail>,
62
63    pub meta: ApiMeta,
64}
65
66impl<T> ApiResponse<T> {
67    pub fn ok(data: T, request_id: impl Into<String>) -> Self {
68        Self {
69            success: true,
70            data: Some(data),
71            errors: vec![],
72            meta: ApiMeta::new(request_id),
73        }
74    }
75
76    pub fn empty_ok(request_id: impl Into<String>) -> Self {
77        Self {
78            success: true,
79            data: None,
80            errors: vec![],
81            meta: ApiMeta::new(request_id),
82        }
83    }
84
85    pub fn err(errors: Vec<ApiErrorDetail>, request_id: impl Into<String>) -> Self {
86        Self {
87            success: false,
88            data: None,
89            errors,
90            meta: ApiMeta::new(request_id),
91        }
92    }
93}