1use std::fmt;
2
3#[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
85pub type SupabaseResult<T> = Result<T, SupabaseError>;
87
88#[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 #[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 #[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 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 #[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 #[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 #[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; assert_eq!(s, s2);
344 }
345}