Skip to main content

systemprompt_models/api/responses/
envelopes.rs

1//! Primary HATEOAS-style response envelopes.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::api::pagination::PaginationInfo;
7
8#[cfg(feature = "web")]
9use axum::Json;
10#[cfg(feature = "web")]
11use axum::http::StatusCode;
12#[cfg(feature = "web")]
13use axum::response::IntoResponse;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ResponseLinks {
17    pub self_link: String,
18
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub next: Option<String>,
21
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub prev: Option<String>,
24
25    pub docs: String,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ResponseMeta {
30    pub timestamp: DateTime<Utc>,
31
32    pub version: String,
33
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub pagination: Option<PaginationInfo>,
36}
37
38impl ResponseMeta {
39    #[must_use]
40    pub fn new() -> Self {
41        Self {
42            timestamp: Utc::now(),
43            version: "1.0.0".to_string(),
44            pagination: None,
45        }
46    }
47
48    #[must_use]
49    pub fn with_pagination(mut self, pagination: PaginationInfo) -> Self {
50        self.pagination = Some(pagination);
51        self
52    }
53}
54
55impl Default for ResponseMeta {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61#[derive(Debug, Serialize, Deserialize)]
62pub struct ApiResponse<T>
63where
64    T: 'static,
65{
66    pub data: T,
67
68    pub meta: ResponseMeta,
69
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub links: Option<ResponseLinks>,
72}
73
74impl<T: Serialize + 'static> ApiResponse<T> {
75    pub fn new(data: T) -> Self {
76        Self {
77            data,
78            meta: ResponseMeta::new(),
79            links: None,
80        }
81    }
82
83    #[must_use]
84    pub fn with_links(mut self, links: ResponseLinks) -> Self {
85        self.links = Some(links);
86        self
87    }
88
89    #[must_use]
90    pub fn with_meta(mut self, meta: ResponseMeta) -> Self {
91        self.meta = meta;
92        self
93    }
94}
95
96#[derive(Debug, Serialize, Deserialize)]
97pub struct SingleResponse<T>
98where
99    T: 'static,
100{
101    pub data: T,
102
103    pub meta: ResponseMeta,
104
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub links: Option<ResponseLinks>,
107}
108
109impl<T: Serialize + 'static> SingleResponse<T> {
110    pub fn new(data: T) -> Self {
111        Self {
112            data,
113            meta: ResponseMeta::new(),
114            links: None,
115        }
116    }
117
118    pub const fn with_meta(data: T, meta: ResponseMeta) -> Self {
119        Self {
120            data,
121            meta,
122            links: None,
123        }
124    }
125
126    #[must_use]
127    pub fn with_links(mut self, links: ResponseLinks) -> Self {
128        self.links = Some(links);
129        self
130    }
131}
132
133#[derive(Debug, Serialize, Deserialize)]
134pub struct CollectionResponse<T>
135where
136    T: 'static,
137{
138    pub data: Vec<T>,
139
140    pub meta: ResponseMeta,
141
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub links: Option<ResponseLinks>,
144}
145
146impl<T: Serialize + 'static> CollectionResponse<T> {
147    pub fn new(data: Vec<T>) -> Self {
148        Self {
149            data,
150            meta: ResponseMeta::new(),
151            links: None,
152        }
153    }
154
155    pub fn paginated(data: Vec<T>, pagination: PaginationInfo) -> Self {
156        Self {
157            data,
158            meta: ResponseMeta::new().with_pagination(pagination),
159            links: None,
160        }
161    }
162
163    #[must_use]
164    pub fn with_links(mut self, links: ResponseLinks) -> Self {
165        self.links = Some(links);
166        self
167    }
168}
169
170#[cfg(feature = "web")]
171impl<T: Serialize + 'static> IntoResponse for SingleResponse<T> {
172    fn into_response(self) -> axum::response::Response {
173        (StatusCode::OK, Json(self)).into_response()
174    }
175}
176
177#[cfg(feature = "web")]
178impl<T: Serialize + 'static> IntoResponse for CollectionResponse<T> {
179    fn into_response(self) -> axum::response::Response {
180        (StatusCode::OK, Json(self)).into_response()
181    }
182}