road_runner_common/
response.rs1use 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, }
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}