Skip to main content

supabase_client_core/
response.rs

1use crate::error::{StatusCode, SupabaseError};
2
3/// Response type matching Supabase's `{ data, error, count, status }` pattern.
4#[derive(Debug)]
5pub struct SupabaseResponse<T> {
6    /// The returned data (empty Vec on error).
7    pub data: Vec<T>,
8    /// Error, if any.
9    pub error: Option<SupabaseError>,
10    /// Row count (if count was requested).
11    pub count: Option<i64>,
12    /// HTTP-like status code.
13    pub status: StatusCode,
14}
15
16impl<T> SupabaseResponse<T> {
17    /// Create a successful response with data.
18    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    /// Create a successful response with data and count.
28    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    /// Create a created (201) response (for inserts).
38    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    /// Create an error response.
48    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    /// Create a no-content (204) response (for deletes without RETURNING).
64    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    /// Check if the response is successful.
74    pub fn is_ok(&self) -> bool {
75        self.error.is_none()
76    }
77
78    /// Check if the response has an error.
79    pub fn is_err(&self) -> bool {
80        self.error.is_some()
81    }
82
83    /// Convert into a Result, consuming the response.
84    /// Returns the data vec on success, or the error on failure.
85    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    /// Get the first item, or None if empty.
93    pub fn first(&self) -> Option<&T> {
94        self.data.first()
95    }
96
97    /// Consume and return exactly one row, or error.
98    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    /// Consume and return zero or one row.
111    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    /// Set the status code.
129    pub fn with_status(mut self, status: StatusCode) -> Self {
130        self.status = status;
131        self
132    }
133
134    /// Set the count.
135    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) => {} // expected
251            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}