Skip to main content

systemprompt_models/api/responses/
envelopes.rs

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