Skip to main content

supabase_client_graphql/
error.rs

1use serde::Deserialize;
2use supabase_client_core::SupabaseError;
3
4/// A single error entry from the GraphQL `errors` array.
5#[derive(Debug, Clone, Deserialize)]
6pub struct GraphqlApiError {
7    pub message: String,
8    #[serde(default)]
9    pub locations: Vec<GraphqlErrorLocation>,
10    #[serde(default)]
11    pub path: Vec<serde_json::Value>,
12    #[serde(default)]
13    pub extensions: Option<serde_json::Value>,
14}
15
16/// Source location of a GraphQL error.
17#[derive(Debug, Clone, Deserialize)]
18pub struct GraphqlErrorLocation {
19    pub line: u64,
20    pub column: u64,
21}
22
23impl std::fmt::Display for GraphqlApiError {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "{}", self.message)
26    }
27}
28
29/// GraphQL-specific errors.
30#[derive(Debug, thiserror::Error)]
31pub enum GraphqlError {
32    /// HTTP transport error from reqwest.
33    #[error("HTTP error: {0}")]
34    Http(#[from] reqwest::Error),
35
36    /// The GraphQL response contained one or more errors in the `errors` array.
37    #[error("GraphQL errors: {}", format_errors(.0))]
38    GraphqlErrors(Vec<GraphqlApiError>),
39
40    /// Non-2xx HTTP status from the GraphQL endpoint.
41    #[error("GraphQL HTTP error ({status}): {message}")]
42    HttpError { status: u16, message: String },
43
44    /// Invalid configuration (missing URL or key).
45    #[error("Invalid GraphQL configuration: {0}")]
46    InvalidConfig(String),
47
48    /// JSON serialization/deserialization error.
49    #[error("Serialization error: {0}")]
50    Serialization(#[from] serde_json::Error),
51
52    /// URL parsing error.
53    #[error("URL parse error: {0}")]
54    UrlParse(#[from] url::ParseError),
55}
56
57fn format_errors(errors: &[GraphqlApiError]) -> String {
58    errors
59        .iter()
60        .map(|e| e.message.as_str())
61        .collect::<Vec<_>>()
62        .join("; ")
63}
64
65impl From<GraphqlError> for SupabaseError {
66    fn from(err: GraphqlError) -> Self {
67        SupabaseError::GraphQL(err.to_string())
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn error_display_http_error() {
77        let err = GraphqlError::HttpError {
78            status: 400,
79            message: "Bad Request".into(),
80        };
81        assert_eq!(err.to_string(), "GraphQL HTTP error (400): Bad Request");
82    }
83
84    #[test]
85    fn error_display_invalid_config() {
86        let err = GraphqlError::InvalidConfig("missing url".into());
87        assert_eq!(
88            err.to_string(),
89            "Invalid GraphQL configuration: missing url"
90        );
91    }
92
93    #[test]
94    fn error_display_graphql_errors() {
95        let err = GraphqlError::GraphqlErrors(vec![
96            GraphqlApiError {
97                message: "field not found".into(),
98                locations: vec![],
99                path: vec![],
100                extensions: None,
101            },
102            GraphqlApiError {
103                message: "type mismatch".into(),
104                locations: vec![],
105                path: vec![],
106                extensions: None,
107            },
108        ]);
109        assert_eq!(
110            err.to_string(),
111            "GraphQL errors: field not found; type mismatch"
112        );
113    }
114
115    #[test]
116    fn error_converts_to_supabase_error() {
117        let err = GraphqlError::HttpError {
118            status: 500,
119            message: "Internal".into(),
120        };
121        let supa: SupabaseError = err.into();
122        match supa {
123            SupabaseError::GraphQL(msg) => assert!(msg.contains("Internal")),
124            other => panic!("Expected GraphQL variant, got: {:?}", other),
125        }
126    }
127
128    #[test]
129    fn api_error_deserialization() {
130        let json = r#"{"message":"Column not found","locations":[{"line":1,"column":5}]}"#;
131        let err: GraphqlApiError = serde_json::from_str(json).unwrap();
132        assert_eq!(err.message, "Column not found");
133        assert_eq!(err.locations.len(), 1);
134        assert_eq!(err.locations[0].line, 1);
135        assert_eq!(err.locations[0].column, 5);
136    }
137}