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}