Skip to main content

supabase_client_core/
error.rs

1use std::fmt;
2
3/// All errors that can occur in the supabase-client crate.
4#[derive(Debug, thiserror::Error)]
5pub enum SupabaseError {
6    #[cfg(feature = "direct-sql")]
7    #[error("Database error: {0}")]
8    Database(#[from] sqlx::Error),
9
10    #[error("PostgREST error ({status}): {message}")]
11    PostgRest {
12        status: u16,
13        message: String,
14        code: Option<String>,
15    },
16
17    #[error("HTTP error: {0}")]
18    Http(String),
19
20    #[error("Query builder error: {0}")]
21    QueryBuilder(String),
22
23    #[error("Serialization error: {0}")]
24    Serialization(String),
25
26    #[error("Expected exactly one row, but got none")]
27    NoRows,
28
29    #[error("Expected at most one row, but got {0}")]
30    MultipleRows(usize),
31
32    #[error("Configuration error: {0}")]
33    Config(String),
34
35    #[error("Auth error: {0}")]
36    Auth(String),
37
38    #[error("Storage error: {0}")]
39    Storage(String),
40
41    #[error("Realtime error: {0}")]
42    Realtime(String),
43
44    #[error("Functions error: {0}")]
45    Functions(String),
46
47    #[error("GraphQL error: {0}")]
48    GraphQL(String),
49}
50
51impl SupabaseError {
52    pub fn query_builder(msg: impl Into<String>) -> Self {
53        Self::QueryBuilder(msg.into())
54    }
55
56    pub fn serialization(msg: impl Into<String>) -> Self {
57        Self::Serialization(msg.into())
58    }
59
60    pub fn config(msg: impl Into<String>) -> Self {
61        Self::Config(msg.into())
62    }
63
64    pub fn postgrest(status: u16, message: impl Into<String>, code: Option<String>) -> Self {
65        Self::PostgRest {
66            status,
67            message: message.into(),
68            code,
69        }
70    }
71}
72
73impl From<serde_json::Error> for SupabaseError {
74    fn from(e: serde_json::Error) -> Self {
75        Self::Serialization(e.to_string())
76    }
77}
78
79impl From<reqwest::Error> for SupabaseError {
80    fn from(e: reqwest::Error) -> Self {
81        Self::Http(e.to_string())
82    }
83}
84
85/// Result alias using SupabaseError.
86pub type SupabaseResult<T> = Result<T, SupabaseError>;
87
88/// HTTP-like status codes for response metadata.
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub enum StatusCode {
91    Ok = 200,
92    Created = 201,
93    NoContent = 204,
94    NotFound = 404,
95    Conflict = 409,
96    InternalError = 500,
97}
98
99impl fmt::Display for StatusCode {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        match self {
102            Self::Ok => write!(f, "200 OK"),
103            Self::Created => write!(f, "201 Created"),
104            Self::NoContent => write!(f, "204 No Content"),
105            Self::NotFound => write!(f, "404 Not Found"),
106            Self::Conflict => write!(f, "409 Conflict"),
107            Self::InternalError => write!(f, "500 Internal Server Error"),
108        }
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    // --- Constructor tests ---
117
118    #[test]
119    fn test_query_builder_constructor() {
120        let err = SupabaseError::query_builder("bad query");
121        match err {
122            SupabaseError::QueryBuilder(msg) => assert_eq!(msg, "bad query"),
123            other => panic!("Expected QueryBuilder, got {:?}", other),
124        }
125    }
126
127    #[test]
128    fn test_query_builder_constructor_with_string() {
129        let err = SupabaseError::query_builder(String::from("owned message"));
130        match err {
131            SupabaseError::QueryBuilder(msg) => assert_eq!(msg, "owned message"),
132            other => panic!("Expected QueryBuilder, got {:?}", other),
133        }
134    }
135
136    #[test]
137    fn test_serialization_constructor() {
138        let err = SupabaseError::serialization("invalid json");
139        match err {
140            SupabaseError::Serialization(msg) => assert_eq!(msg, "invalid json"),
141            other => panic!("Expected Serialization, got {:?}", other),
142        }
143    }
144
145    #[test]
146    fn test_config_constructor() {
147        let err = SupabaseError::config("missing url");
148        match err {
149            SupabaseError::Config(msg) => assert_eq!(msg, "missing url"),
150            other => panic!("Expected Config, got {:?}", other),
151        }
152    }
153
154    #[test]
155    fn test_postgrest_constructor_with_code() {
156        let err = SupabaseError::postgrest(404, "not found", Some("PGRST116".to_string()));
157        match err {
158            SupabaseError::PostgRest {
159                status,
160                message,
161                code,
162            } => {
163                assert_eq!(status, 404);
164                assert_eq!(message, "not found");
165                assert_eq!(code, Some("PGRST116".to_string()));
166            }
167            other => panic!("Expected PostgRest, got {:?}", other),
168        }
169    }
170
171    #[test]
172    fn test_postgrest_constructor_without_code() {
173        let err = SupabaseError::postgrest(500, "server error", None);
174        match err {
175            SupabaseError::PostgRest {
176                status,
177                message,
178                code,
179            } => {
180                assert_eq!(status, 500);
181                assert_eq!(message, "server error");
182                assert_eq!(code, None);
183            }
184            other => panic!("Expected PostgRest, got {:?}", other),
185        }
186    }
187
188    // --- From conversion tests ---
189
190    #[test]
191    fn test_from_serde_json_error() {
192        let json_err = serde_json::from_str::<String>("not valid json").unwrap_err();
193        let err: SupabaseError = json_err.into();
194        match err {
195            SupabaseError::Serialization(msg) => {
196                assert!(!msg.is_empty(), "Error message should not be empty");
197            }
198            other => panic!("Expected Serialization, got {:?}", other),
199        }
200    }
201
202    #[test]
203    fn test_from_reqwest_error() {
204        // Create a reqwest error by trying to build a request with an invalid URL
205        let reqwest_err = reqwest::Client::new()
206            .get("://invalid-url")
207            .build()
208            .unwrap_err();
209        let err: SupabaseError = reqwest_err.into();
210        match err {
211            SupabaseError::Http(msg) => {
212                assert!(!msg.is_empty(), "Error message should not be empty");
213            }
214            other => panic!("Expected Http, got {:?}", other),
215        }
216    }
217
218    // --- StatusCode Display tests ---
219
220    #[test]
221    fn test_status_code_display_ok() {
222        assert_eq!(StatusCode::Ok.to_string(), "200 OK");
223    }
224
225    #[test]
226    fn test_status_code_display_created() {
227        assert_eq!(StatusCode::Created.to_string(), "201 Created");
228    }
229
230    #[test]
231    fn test_status_code_display_no_content() {
232        assert_eq!(StatusCode::NoContent.to_string(), "204 No Content");
233    }
234
235    #[test]
236    fn test_status_code_display_not_found() {
237        assert_eq!(StatusCode::NotFound.to_string(), "404 Not Found");
238    }
239
240    #[test]
241    fn test_status_code_display_conflict() {
242        assert_eq!(StatusCode::Conflict.to_string(), "409 Conflict");
243    }
244
245    #[test]
246    fn test_status_code_display_internal_error() {
247        assert_eq!(
248            StatusCode::InternalError.to_string(),
249            "500 Internal Server Error"
250        );
251    }
252
253    // --- SupabaseError Display tests ---
254
255    #[test]
256    fn test_display_graphql() {
257        let err = SupabaseError::GraphQL("query failed".to_string());
258        assert_eq!(err.to_string(), "GraphQL error: query failed");
259    }
260
261    #[test]
262    fn test_display_no_rows() {
263        let err = SupabaseError::NoRows;
264        assert_eq!(err.to_string(), "Expected exactly one row, but got none");
265    }
266
267    #[test]
268    fn test_display_multiple_rows() {
269        let err = SupabaseError::MultipleRows(5);
270        assert_eq!(err.to_string(), "Expected at most one row, but got 5");
271    }
272
273    #[test]
274    fn test_display_auth() {
275        let err = SupabaseError::Auth("invalid token".to_string());
276        assert_eq!(err.to_string(), "Auth error: invalid token");
277    }
278
279    #[test]
280    fn test_display_storage() {
281        let err = SupabaseError::Storage("bucket not found".to_string());
282        assert_eq!(err.to_string(), "Storage error: bucket not found");
283    }
284
285    #[test]
286    fn test_display_realtime() {
287        let err = SupabaseError::Realtime("connection lost".to_string());
288        assert_eq!(err.to_string(), "Realtime error: connection lost");
289    }
290
291    #[test]
292    fn test_display_functions() {
293        let err = SupabaseError::Functions("timeout".to_string());
294        assert_eq!(err.to_string(), "Functions error: timeout");
295    }
296
297    #[test]
298    fn test_display_http() {
299        let err = SupabaseError::Http("connection refused".to_string());
300        assert_eq!(err.to_string(), "HTTP error: connection refused");
301    }
302
303    #[test]
304    fn test_display_query_builder() {
305        let err = SupabaseError::QueryBuilder("invalid filter".to_string());
306        assert_eq!(err.to_string(), "Query builder error: invalid filter");
307    }
308
309    #[test]
310    fn test_display_serialization() {
311        let err = SupabaseError::Serialization("parse failed".to_string());
312        assert_eq!(err.to_string(), "Serialization error: parse failed");
313    }
314
315    #[test]
316    fn test_display_config() {
317        let err = SupabaseError::Config("missing key".to_string());
318        assert_eq!(err.to_string(), "Configuration error: missing key");
319    }
320
321    #[test]
322    fn test_display_postgrest() {
323        let err = SupabaseError::PostgRest {
324            status: 400,
325            message: "bad request".to_string(),
326            code: Some("PGRST100".to_string()),
327        };
328        assert_eq!(err.to_string(), "PostgREST error (400): bad request");
329    }
330
331    // --- StatusCode equality and copy ---
332
333    #[test]
334    fn test_status_code_equality() {
335        assert_eq!(StatusCode::Ok, StatusCode::Ok);
336        assert_ne!(StatusCode::Ok, StatusCode::Created);
337    }
338
339    #[test]
340    fn test_status_code_copy() {
341        let s = StatusCode::Ok;
342        let s2 = s; // copy
343        assert_eq!(s, s2);
344    }
345}