1use super::pagination::PaginationInfo;
2use chrono::{DateTime, Utc};
3use indexmap::IndexMap;
4use serde::{Deserialize, Serialize};
5
6#[cfg(feature = "web")]
7use axum::http::StatusCode;
8#[cfg(feature = "web")]
9use axum::response::IntoResponse;
10#[cfg(feature = "web")]
11use axum::Json;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ResponseLinks {
15 pub self_link: String,
16
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub next: Option<String>,
19
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub prev: Option<String>,
22
23 pub docs: String,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ResponseMeta {
28 pub timestamp: DateTime<Utc>,
29
30 pub version: String,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
33 pub pagination: Option<PaginationInfo>,
34}
35
36impl ResponseMeta {
37 pub fn new() -> Self {
38 Self {
39 timestamp: Utc::now(),
40 version: "1.0.0".to_string(),
41 pagination: None,
42 }
43 }
44
45 pub fn with_pagination(mut self, pagination: PaginationInfo) -> Self {
46 self.pagination = Some(pagination);
47 self
48 }
49}
50
51impl Default for ResponseMeta {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57#[derive(Debug, Serialize, Deserialize)]
58pub struct ApiResponse<T>
59where
60 T: 'static,
61{
62 pub data: T,
63
64 pub meta: ResponseMeta,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub links: Option<ResponseLinks>,
68}
69
70impl<T: Serialize + 'static> ApiResponse<T> {
71 pub fn new(data: T) -> Self {
72 Self {
73 data,
74 meta: ResponseMeta::new(),
75 links: None,
76 }
77 }
78
79 pub fn with_links(mut self, links: ResponseLinks) -> Self {
80 self.links = Some(links);
81 self
82 }
83
84 pub fn with_meta(mut self, meta: ResponseMeta) -> Self {
85 self.meta = meta;
86 self
87 }
88}
89
90#[derive(Debug, Serialize, Deserialize)]
91pub struct SingleResponse<T>
92where
93 T: 'static,
94{
95 pub data: T,
96
97 pub meta: ResponseMeta,
98
99 #[serde(skip_serializing_if = "Option::is_none")]
100 pub links: Option<ResponseLinks>,
101}
102
103impl<T: Serialize + 'static> SingleResponse<T> {
104 pub fn new(data: T) -> Self {
105 Self {
106 data,
107 meta: ResponseMeta::new(),
108 links: None,
109 }
110 }
111
112 pub const fn with_meta(data: T, meta: ResponseMeta) -> Self {
113 Self {
114 data,
115 meta,
116 links: None,
117 }
118 }
119
120 pub fn with_links(mut self, links: ResponseLinks) -> Self {
121 self.links = Some(links);
122 self
123 }
124}
125
126#[derive(Debug, Serialize, Deserialize)]
127pub struct CollectionResponse<T>
128where
129 T: 'static,
130{
131 pub data: Vec<T>,
132
133 pub meta: ResponseMeta,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub links: Option<ResponseLinks>,
137}
138
139impl<T: Serialize + 'static> CollectionResponse<T> {
140 pub fn new(data: Vec<T>) -> Self {
141 Self {
142 data,
143 meta: ResponseMeta::new(),
144 links: None,
145 }
146 }
147
148 pub fn paginated(data: Vec<T>, pagination: PaginationInfo) -> Self {
149 Self {
150 data,
151 meta: ResponseMeta::new().with_pagination(pagination),
152 links: None,
153 }
154 }
155
156 pub fn with_links(mut self, links: ResponseLinks) -> Self {
157 self.links = Some(links);
158 self
159 }
160}
161
162#[derive(Debug, Serialize, Deserialize)]
163pub struct SuccessResponse {
164 pub message: String,
165
166 pub meta: ResponseMeta,
167}
168
169impl SuccessResponse {
170 pub fn new(message: impl Into<String>) -> Self {
171 Self {
172 message: message.into(),
173 meta: ResponseMeta::new(),
174 }
175 }
176}
177
178#[derive(Debug, Serialize, Deserialize)]
179pub struct CreatedResponse<T>
180where
181 T: 'static,
182{
183 pub data: T,
184
185 pub meta: ResponseMeta,
186
187 pub location: String,
188}
189
190impl<T: Serialize + 'static> CreatedResponse<T> {
191 pub fn new(data: T, location: impl Into<String>) -> Self {
192 Self {
193 data,
194 meta: ResponseMeta::new(),
195 location: location.into(),
196 }
197 }
198}
199
200#[derive(Debug, Serialize, Deserialize)]
201pub struct AcceptedResponse {
202 pub message: String,
203
204 pub job_id: Option<String>,
205
206 pub status_url: Option<String>,
207
208 pub meta: ResponseMeta,
209}
210
211impl AcceptedResponse {
212 pub fn new(message: impl Into<String>) -> Self {
213 Self {
214 message: message.into(),
215 job_id: None,
216 status_url: None,
217 meta: ResponseMeta::new(),
218 }
219 }
220
221 pub fn with_job(mut self, job_id: impl Into<String>, status_url: impl Into<String>) -> Self {
222 self.job_id = Some(job_id.into());
223 self.status_url = Some(status_url.into());
224 self
225 }
226}
227
228#[derive(Debug, Serialize, Deserialize)]
229pub struct Link {
230 pub href: String,
231 #[serde(skip_serializing_if = "Option::is_none")]
232 pub title: Option<String>,
233}
234
235impl Link {
236 pub fn new(href: impl Into<String>, title: Option<String>) -> Self {
237 Self {
238 href: href.into(),
239 title,
240 }
241 }
242}
243
244#[derive(Debug, Serialize, Deserialize)]
245pub struct DiscoveryResponse<T>
246where
247 T: 'static,
248{
249 pub data: T,
250 pub meta: ResponseMeta,
251 #[serde(rename = "_links")]
252 pub links: IndexMap<String, Link>,
253}
254
255impl<T: Serialize + 'static> DiscoveryResponse<T> {
256 pub fn new(data: T, links: IndexMap<String, Link>) -> Self {
257 Self {
258 data,
259 meta: ResponseMeta::new(),
260 links,
261 }
262 }
263
264 pub fn with_meta(mut self, meta: ResponseMeta) -> Self {
265 self.meta = meta;
266 self
267 }
268}
269
270#[cfg(feature = "web")]
271impl<T: Serialize + 'static> IntoResponse for SingleResponse<T> {
272 fn into_response(self) -> axum::response::Response {
273 (StatusCode::OK, Json(self)).into_response()
274 }
275}
276
277#[cfg(feature = "web")]
278impl<T: Serialize + 'static> IntoResponse for CollectionResponse<T> {
279 fn into_response(self) -> axum::response::Response {
280 (StatusCode::OK, Json(self)).into_response()
281 }
282}
283
284#[cfg(feature = "web")]
285impl IntoResponse for SuccessResponse {
286 fn into_response(self) -> axum::response::Response {
287 (StatusCode::OK, Json(self)).into_response()
288 }
289}
290
291#[cfg(feature = "web")]
292impl<T: Serialize + 'static> IntoResponse for CreatedResponse<T> {
293 fn into_response(self) -> axum::response::Response {
294 (
295 StatusCode::CREATED,
296 [("Location", self.location.clone())],
297 Json(self),
298 )
299 .into_response()
300 }
301}
302
303#[cfg(feature = "web")]
304impl IntoResponse for AcceptedResponse {
305 fn into_response(self) -> axum::response::Response {
306 (StatusCode::ACCEPTED, Json(self)).into_response()
307 }
308}