Skip to main content

supabase_client_graphql/
types.rs

1use serde::{Deserialize, Serialize};
2
3use crate::error::GraphqlApiError;
4
5/// Raw GraphQL response envelope.
6///
7/// Every response from the `/graphql/v1` endpoint has this shape.
8#[derive(Debug, Clone, Deserialize)]
9pub struct GraphqlResponse<T> {
10    pub data: Option<T>,
11    #[serde(default)]
12    pub errors: Vec<GraphqlApiError>,
13}
14
15/// Relay-style connection returned by pg_graphql collection queries.
16///
17/// `T` is the row type (e.g., `BlogRow`).
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct Connection<T> {
21    pub edges: Vec<Edge<T>>,
22    pub page_info: PageInfo,
23    #[serde(default)]
24    pub total_count: Option<i64>,
25}
26
27/// A single edge in a Relay connection.
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct Edge<T> {
30    pub cursor: String,
31    pub node: T,
32}
33
34/// Pagination metadata for a Relay connection.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct PageInfo {
38    pub has_next_page: bool,
39    pub has_previous_page: bool,
40    #[serde(default)]
41    pub start_cursor: Option<String>,
42    #[serde(default)]
43    pub end_cursor: Option<String>,
44}
45
46/// Result of a mutation (insert, update, delete).
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct MutationResult<T> {
50    pub affected_count: i64,
51    pub records: Vec<T>,
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use serde_json::json;
58
59    #[test]
60    fn deserialize_connection() {
61        let json = json!({
62            "edges": [
63                {
64                    "cursor": "abc123",
65                    "node": { "id": 1, "title": "Hello" }
66                }
67            ],
68            "pageInfo": {
69                "hasNextPage": true,
70                "hasPreviousPage": false,
71                "startCursor": "abc123",
72                "endCursor": "abc123"
73            },
74            "totalCount": 42
75        });
76
77        let conn: Connection<serde_json::Value> = serde_json::from_value(json).unwrap();
78        assert_eq!(conn.edges.len(), 1);
79        assert_eq!(conn.edges[0].cursor, "abc123");
80        assert!(conn.page_info.has_next_page);
81        assert!(!conn.page_info.has_previous_page);
82        assert_eq!(conn.total_count, Some(42));
83    }
84
85    #[test]
86    fn deserialize_connection_no_total_count() {
87        let json = json!({
88            "edges": [],
89            "pageInfo": {
90                "hasNextPage": false,
91                "hasPreviousPage": false
92            }
93        });
94
95        let conn: Connection<serde_json::Value> = serde_json::from_value(json).unwrap();
96        assert!(conn.edges.is_empty());
97        assert_eq!(conn.total_count, None);
98    }
99
100    #[test]
101    fn deserialize_mutation_result() {
102        let json = json!({
103            "affectedCount": 2,
104            "records": [
105                { "id": 1, "title": "A" },
106                { "id": 2, "title": "B" }
107            ]
108        });
109
110        let result: MutationResult<serde_json::Value> = serde_json::from_value(json).unwrap();
111        assert_eq!(result.affected_count, 2);
112        assert_eq!(result.records.len(), 2);
113    }
114
115    #[test]
116    fn deserialize_graphql_response_with_data() {
117        let json = json!({
118            "data": { "blogCollection": { "edges": [] } }
119        });
120
121        let resp: GraphqlResponse<serde_json::Value> = serde_json::from_value(json).unwrap();
122        assert!(resp.data.is_some());
123        assert!(resp.errors.is_empty());
124    }
125
126    #[test]
127    fn deserialize_graphql_response_with_errors() {
128        let json = json!({
129            "data": null,
130            "errors": [
131                { "message": "Column not found" }
132            ]
133        });
134
135        let resp: GraphqlResponse<serde_json::Value> = serde_json::from_value(json).unwrap();
136        assert!(resp.data.is_none());
137        assert_eq!(resp.errors.len(), 1);
138        assert_eq!(resp.errors[0].message, "Column not found");
139    }
140}