1use crate::error::{StatusCode, SupabaseError};
2
3#[derive(Debug)]
5pub struct SupabaseResponse<T> {
6 pub data: Vec<T>,
8 pub error: Option<SupabaseError>,
10 pub count: Option<i64>,
12 pub status: StatusCode,
14}
15
16impl<T> SupabaseResponse<T> {
17 pub fn ok(data: Vec<T>) -> Self {
19 Self {
20 data,
21 error: None,
22 count: None,
23 status: StatusCode::Ok,
24 }
25 }
26
27 pub fn ok_with_count(data: Vec<T>, count: i64) -> Self {
29 Self {
30 data,
31 error: None,
32 count: Some(count),
33 status: StatusCode::Ok,
34 }
35 }
36
37 pub fn created(data: Vec<T>) -> Self {
39 Self {
40 data,
41 error: None,
42 count: None,
43 status: StatusCode::Created,
44 }
45 }
46
47 pub fn error(err: SupabaseError) -> Self {
49 let status = match &err {
50 SupabaseError::NoRows => StatusCode::NotFound,
51 #[cfg(feature = "direct-sql")]
52 SupabaseError::Database(_) => StatusCode::InternalError,
53 _ => StatusCode::InternalError,
54 };
55 Self {
56 data: Vec::new(),
57 error: Some(err),
58 count: None,
59 status,
60 }
61 }
62
63 pub fn no_content() -> Self {
65 Self {
66 data: Vec::new(),
67 error: None,
68 count: None,
69 status: StatusCode::NoContent,
70 }
71 }
72
73 pub fn is_ok(&self) -> bool {
75 self.error.is_none()
76 }
77
78 pub fn is_err(&self) -> bool {
80 self.error.is_some()
81 }
82
83 pub fn into_result(self) -> Result<Vec<T>, SupabaseError> {
86 match self.error {
87 Some(err) => Err(err),
88 None => Ok(self.data),
89 }
90 }
91
92 pub fn first(&self) -> Option<&T> {
94 self.data.first()
95 }
96
97 pub fn into_single(self) -> Result<T, SupabaseError> {
99 if let Some(err) = self.error {
100 return Err(err);
101 }
102 let mut data = self.data;
103 match data.len() {
104 0 => Err(SupabaseError::NoRows),
105 1 => Ok(data.remove(0)),
106 n => Err(SupabaseError::MultipleRows(n)),
107 }
108 }
109
110 pub fn into_maybe_single(self) -> Result<Option<T>, SupabaseError> {
112 if let Some(err) = self.error {
113 return Err(err);
114 }
115 let mut data = self.data;
116 match data.len() {
117 0 => Ok(None),
118 1 => Ok(Some(data.remove(0))),
119 n => Err(SupabaseError::MultipleRows(n)),
120 }
121 }
122}
123
124impl<T> SupabaseResponse<T>
125where
126 T: Clone,
127{
128 pub fn with_status(mut self, status: StatusCode) -> Self {
130 self.status = status;
131 self
132 }
133
134 pub fn with_count(mut self, count: i64) -> Self {
136 self.count = Some(count);
137 self
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_ok_response() {
147 let resp = SupabaseResponse::ok(vec![1, 2, 3]);
148 assert_eq!(resp.data, vec![1, 2, 3]);
149 assert!(resp.error.is_none());
150 assert_eq!(resp.status, StatusCode::Ok);
151 assert_eq!(resp.count, None);
152 }
153
154 #[test]
155 fn test_ok_with_count() {
156 let resp = SupabaseResponse::ok_with_count(vec!["a", "b"], 10);
157 assert_eq!(resp.data, vec!["a", "b"]);
158 assert_eq!(resp.count, Some(10));
159 assert_eq!(resp.status, StatusCode::Ok);
160 assert!(resp.error.is_none());
161 }
162
163 #[test]
164 fn test_created_response() {
165 let resp = SupabaseResponse::created(vec![42]);
166 assert_eq!(resp.data, vec![42]);
167 assert_eq!(resp.status, StatusCode::Created);
168 assert!(resp.error.is_none());
169 assert_eq!(resp.count, None);
170 }
171
172 #[test]
173 fn test_error_response_no_rows() {
174 let resp = SupabaseResponse::<i32>::error(SupabaseError::NoRows);
175 assert!(resp.data.is_empty());
176 assert_eq!(resp.status, StatusCode::NotFound);
177 assert!(resp.error.is_some());
178 }
179
180 #[test]
181 fn test_error_response_generic() {
182 let resp =
183 SupabaseResponse::<i32>::error(SupabaseError::Http("connection failed".to_string()));
184 assert!(resp.data.is_empty());
185 assert_eq!(resp.status, StatusCode::InternalError);
186 assert!(resp.error.is_some());
187 }
188
189 #[test]
190 fn test_no_content_response() {
191 let resp = SupabaseResponse::<String>::no_content();
192 assert!(resp.data.is_empty());
193 assert_eq!(resp.status, StatusCode::NoContent);
194 assert!(resp.error.is_none());
195 assert_eq!(resp.count, None);
196 }
197
198 #[test]
199 fn test_is_ok_for_ok_response() {
200 let resp = SupabaseResponse::ok(vec![1]);
201 assert!(resp.is_ok());
202 assert!(!resp.is_err());
203 }
204
205 #[test]
206 fn test_is_err_for_error_response() {
207 let resp = SupabaseResponse::<i32>::error(SupabaseError::NoRows);
208 assert!(resp.is_err());
209 assert!(!resp.is_ok());
210 }
211
212 #[test]
213 fn test_into_result_ok_path() {
214 let resp = SupabaseResponse::ok(vec![10, 20, 30]);
215 let result = resp.into_result();
216 assert_eq!(result.unwrap(), vec![10, 20, 30]);
217 }
218
219 #[test]
220 fn test_into_result_error_path() {
221 let resp = SupabaseResponse::<i32>::error(SupabaseError::NoRows);
222 let result = resp.into_result();
223 assert!(result.is_err());
224 }
225
226 #[test]
227 fn test_first_non_empty() {
228 let resp = SupabaseResponse::ok(vec![100, 200]);
229 assert_eq!(resp.first(), Some(&100));
230 }
231
232 #[test]
233 fn test_first_empty() {
234 let resp = SupabaseResponse::<i32>::ok(vec![]);
235 assert_eq!(resp.first(), None);
236 }
237
238 #[test]
239 fn test_into_single_with_one_row() {
240 let resp = SupabaseResponse::ok(vec![42]);
241 let result = resp.into_single();
242 assert_eq!(result.unwrap(), 42);
243 }
244
245 #[test]
246 fn test_into_single_with_zero_rows() {
247 let resp = SupabaseResponse::<i32>::ok(vec![]);
248 let result = resp.into_single();
249 match result {
250 Err(SupabaseError::NoRows) => {} other => panic!("Expected NoRows, got {:?}", other),
252 }
253 }
254
255 #[test]
256 fn test_into_single_with_multiple_rows() {
257 let resp = SupabaseResponse::ok(vec![1, 2, 3]);
258 let result = resp.into_single();
259 match result {
260 Err(SupabaseError::MultipleRows(n)) => assert_eq!(n, 3),
261 other => panic!("Expected MultipleRows(3), got {:?}", other),
262 }
263 }
264
265 #[test]
266 fn test_into_single_with_error_set() {
267 let resp = SupabaseResponse::<i32>::error(SupabaseError::Config("bad".to_string()));
268 let result = resp.into_single();
269 match result {
270 Err(SupabaseError::Config(msg)) => assert_eq!(msg, "bad"),
271 other => panic!("Expected Config error, got {:?}", other),
272 }
273 }
274
275 #[test]
276 fn test_into_maybe_single_with_zero_rows() {
277 let resp = SupabaseResponse::<i32>::ok(vec![]);
278 let result = resp.into_maybe_single();
279 assert_eq!(result.unwrap(), None);
280 }
281
282 #[test]
283 fn test_into_maybe_single_with_one_row() {
284 let resp = SupabaseResponse::ok(vec![99]);
285 let result = resp.into_maybe_single();
286 assert_eq!(result.unwrap(), Some(99));
287 }
288
289 #[test]
290 fn test_into_maybe_single_with_multiple_rows() {
291 let resp = SupabaseResponse::ok(vec![1, 2, 3]);
292 let result = resp.into_maybe_single();
293 match result {
294 Err(SupabaseError::MultipleRows(n)) => assert_eq!(n, 3),
295 other => panic!("Expected MultipleRows(3), got {:?}", other),
296 }
297 }
298
299 #[test]
300 fn test_into_maybe_single_with_error_set() {
301 let resp =
302 SupabaseResponse::<i32>::error(SupabaseError::Storage("no bucket".to_string()));
303 let result = resp.into_maybe_single();
304 match result {
305 Err(SupabaseError::Storage(msg)) => assert_eq!(msg, "no bucket"),
306 other => panic!("Expected Storage error, got {:?}", other),
307 }
308 }
309
310 #[test]
311 fn test_with_status_changes_status() {
312 let resp = SupabaseResponse::ok(vec![1i32]).with_status(StatusCode::Created);
313 assert_eq!(resp.status, StatusCode::Created);
314 }
315
316 #[test]
317 fn test_with_count_sets_count() {
318 let resp = SupabaseResponse::ok(vec![1i32]).with_count(42);
319 assert_eq!(resp.count, Some(42));
320 }
321}