supabase_client_graphql/
error.rs1use serde::Deserialize;
2use supabase_client_core::SupabaseError;
3
4#[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#[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#[derive(Debug, thiserror::Error)]
31pub enum GraphqlError {
32 #[error("HTTP error: {0}")]
34 Http(#[from] reqwest::Error),
35
36 #[error("GraphQL errors: {}", format_errors(.0))]
38 GraphqlErrors(Vec<GraphqlApiError>),
39
40 #[error("GraphQL HTTP error ({status}): {message}")]
42 HttpError { status: u16, message: String },
43
44 #[error("Invalid GraphQL configuration: {0}")]
46 InvalidConfig(String),
47
48 #[error("Serialization error: {0}")]
50 Serialization(#[from] serde_json::Error),
51
52 #[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}