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}