Skip to main content

sui_graphql/client/
mod.rs

1//! GraphQL client for Sui blockchain.
2
3pub(crate) mod chain;
4pub(crate) mod checkpoints;
5pub(crate) mod coins;
6pub(crate) mod dynamic_fields;
7pub(crate) mod execution;
8pub(crate) mod objects;
9pub(crate) mod transactions;
10
11use reqwest::Url;
12use serde::Deserialize;
13use serde::Serialize;
14use serde::de::DeserializeOwned;
15
16use crate::error::Error;
17use crate::error::GraphQLError;
18use crate::response::Response;
19
20/// GraphQL client for Sui blockchain.
21#[derive(Clone, Debug)]
22pub struct Client {
23    endpoint: Url,
24    http: reqwest::Client,
25}
26
27impl Client {
28    /// URL for the Sui Foundation provided GraphQL service for mainnet.
29    pub const MAINNET: &str = "https://graphql.mainnet.sui.io/graphql";
30
31    /// URL for the Sui Foundation provided GraphQL service for testnet.
32    pub const TESTNET: &str = "https://graphql.testnet.sui.io/graphql";
33
34    /// URL for the Sui Foundation provided GraphQL service for devnet.
35    pub const DEVNET: &str = "https://graphql.devnet.sui.io/graphql";
36
37    /// Create a new GraphQL client with the given endpoint.
38    ///
39    /// # Example
40    ///
41    /// ```no_run
42    /// use sui_graphql::Client;
43    ///
44    /// let client = Client::new(Client::MAINNET).unwrap();
45    /// ```
46    pub fn new(endpoint: &str) -> Result<Self, Error> {
47        let endpoint = Url::parse(endpoint)?;
48        Ok(Self {
49            endpoint,
50            http: reqwest::Client::new(),
51        })
52    }
53
54    /// Execute a GraphQL query and return the response.
55    ///
56    /// The response contains both data and any errors (GraphQL supports partial success).
57    ///
58    /// # Example
59    ///
60    /// ```no_run
61    /// use serde::Deserialize;
62    /// use sui_graphql::Client;
63    ///
64    /// #[derive(Deserialize)]
65    /// struct MyResponse {
66    ///     #[serde(rename = "chainIdentifier")]
67    ///     chain_identifier: String,
68    /// }
69    ///
70    /// #[tokio::main]
71    /// async fn main() -> Result<(), sui_graphql::Error> {
72    ///     let client = Client::new(Client::MAINNET)?;
73    ///     let response = client
74    ///         .query::<MyResponse>("query { chainIdentifier }", serde_json::json!({}))
75    ///         .await?;
76    ///
77    ///     // Check for partial errors
78    ///     if response.has_errors() {
79    ///         for err in response.errors() {
80    ///             eprintln!("GraphQL error: {}", err.message());
81    ///         }
82    ///     }
83    ///
84    ///     // Access the data
85    ///     if let Some(data) = response.data() {
86    ///         println!("Chain: {}", data.chain_identifier);
87    ///     }
88    ///
89    ///     Ok(())
90    /// }
91    /// ```
92    pub async fn query<T: DeserializeOwned>(
93        &self,
94        query: &str,
95        variables: serde_json::Value,
96    ) -> Result<Response<T>, Error> {
97        #[derive(Serialize)]
98        struct GraphQLRequest<'a> {
99            query: &'a str,
100            variables: serde_json::Value,
101        }
102
103        #[derive(Deserialize)]
104        struct GraphQLResponse<T> {
105            data: Option<T>,
106            errors: Option<Vec<GraphQLError>>,
107        }
108
109        let request = GraphQLRequest { query, variables };
110
111        let raw: GraphQLResponse<T> = self
112            .http
113            .post(self.endpoint.clone())
114            .json(&request)
115            .send()
116            .await?
117            .json()
118            .await?;
119
120        Ok(Response::new(raw.data, raw.errors.unwrap_or_default()))
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn test_client_new() {
130        let client = Client::new("https://example.com/graphql").unwrap();
131        assert_eq!(client.endpoint.as_str(), "https://example.com/graphql");
132    }
133
134    #[test]
135    fn test_client_new_invalid_url() {
136        let result = Client::new("not a valid url");
137        assert!(matches!(result, Err(Error::InvalidUrl(_))));
138    }
139}