Skip to main content

supabase_client_storage/
error.rs

1use serde::Deserialize;
2use supabase_client_core::SupabaseError;
3
4/// Error response format from the Supabase Storage API.
5#[derive(Debug, Clone, Deserialize)]
6pub struct StorageApiErrorResponse {
7    #[serde(default)]
8    pub error: Option<String>,
9    #[serde(default)]
10    pub message: Option<String>,
11    #[serde(default, rename = "statusCode")]
12    pub status_code: Option<String>,
13}
14
15impl StorageApiErrorResponse {
16    /// Extract the most informative error message from the response.
17    pub fn error_message(&self) -> String {
18        self.message
19            .as_deref()
20            .or(self.error.as_deref())
21            .unwrap_or("Unknown error")
22            .to_string()
23    }
24}
25
26/// Storage-specific errors.
27#[derive(Debug, thiserror::Error)]
28pub enum StorageError {
29    /// HTTP transport error from reqwest.
30    #[error("HTTP error: {0}")]
31    Http(#[from] reqwest::Error),
32
33    /// Storage API returned an error response.
34    #[error("Storage API error ({status}): {message}")]
35    Api { status: u16, message: String },
36
37    /// Invalid configuration (missing URL or key).
38    #[error("Invalid storage configuration: {0}")]
39    InvalidConfig(String),
40
41    /// JSON serialization/deserialization error.
42    #[error("Serialization error: {0}")]
43    Serialization(#[from] serde_json::Error),
44
45    /// URL parsing error.
46    #[error("URL parse error: {0}")]
47    UrlParse(#[from] url::ParseError),
48}
49
50impl From<StorageError> for SupabaseError {
51    fn from(err: StorageError) -> Self {
52        SupabaseError::Storage(err.to_string())
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn error_display_api() {
62        let err = StorageError::Api {
63            status: 404,
64            message: "Not found".into(),
65        };
66        assert_eq!(err.to_string(), "Storage API error (404): Not found");
67    }
68
69    #[test]
70    fn error_display_invalid_config() {
71        let err = StorageError::InvalidConfig("missing url".into());
72        assert_eq!(
73            err.to_string(),
74            "Invalid storage configuration: missing url"
75        );
76    }
77
78    #[test]
79    fn error_converts_to_supabase_error() {
80        let err = StorageError::Api {
81            status: 500,
82            message: "Internal".into(),
83        };
84        let supa: SupabaseError = err.into();
85        match supa {
86            SupabaseError::Storage(msg) => assert!(msg.contains("Internal")),
87            other => panic!("Expected Storage variant, got: {:?}", other),
88        }
89    }
90
91    #[test]
92    fn api_error_response_deserialization() {
93        let json = r#"{"error":"Bucket not found","message":"The resource was not found","statusCode":"404"}"#;
94        let resp: StorageApiErrorResponse = serde_json::from_str(json).unwrap();
95        assert_eq!(resp.error.as_deref(), Some("Bucket not found"));
96        assert_eq!(
97            resp.message.as_deref(),
98            Some("The resource was not found")
99        );
100        assert_eq!(resp.status_code.as_deref(), Some("404"));
101        // message takes priority
102        assert_eq!(resp.error_message(), "The resource was not found");
103    }
104
105    #[test]
106    fn api_error_response_fallback_to_error() {
107        let json = r#"{"error":"Something went wrong"}"#;
108        let resp: StorageApiErrorResponse = serde_json::from_str(json).unwrap();
109        assert_eq!(resp.error_message(), "Something went wrong");
110    }
111
112    #[test]
113    fn api_error_response_unknown() {
114        let json = r#"{}"#;
115        let resp: StorageApiErrorResponse = serde_json::from_str(json).unwrap();
116        assert_eq!(resp.error_message(), "Unknown error");
117    }
118}