1use serde::de::DeserializeOwned;
2use serde_json::json;
3
4use crate::auth::{AuthState, OAuthConfig};
5use crate::error::{GraphqlError, InputError, WaveError};
6
7const WAVE_GRAPHQL_URL: &str = "https://gql.waveapps.com/graphql/public";
8
9#[derive(Clone)]
11pub struct WaveClient {
12 http: reqwest::Client,
13 pub(crate) auth: AuthState,
14}
15
16impl WaveClient {
17 pub fn with_oauth(config: OAuthConfig) -> Self {
19 Self {
20 http: reqwest::Client::new(),
21 auth: AuthState::new(config),
22 }
23 }
24
25 pub(crate) async fn execute(
27 &self,
28 query: &str,
29 variables: serde_json::Value,
30 ) -> Result<serde_json::Value, WaveError> {
31 let body = json!({
32 "query": query,
33 "variables": variables,
34 });
35
36 let resp = self.send_request(&body).await?;
37
38 if let Some(errors) = resp.get("errors") {
40 let gql_errors: Vec<GraphqlError> = serde_json::from_value(errors.clone())?;
41
42 if resp.get("data").is_some_and(|d| !d.is_null()) {
44 let is_unauth = gql_errors
46 .iter()
47 .any(|e| {
48 e.extensions
49 .as_ref()
50 .and_then(|ext| ext.code.as_deref())
51 == Some("UNAUTHENTICATED")
52 });
53
54 if is_unauth {
55 return self.retry_after_refresh(query, variables).await;
57 }
58
59 return Err(WaveError::GraphQL(gql_errors));
62 }
63
64 let is_unauth = gql_errors
66 .iter()
67 .any(|e| {
68 e.extensions
69 .as_ref()
70 .and_then(|ext| ext.code.as_deref())
71 == Some("UNAUTHENTICATED")
72 });
73
74 if is_unauth {
75 return self.retry_after_refresh(query, variables).await;
76 }
77
78 return Err(WaveError::GraphQL(gql_errors));
79 }
80
81 resp.get("data")
82 .cloned()
83 .ok_or_else(|| {
84 WaveError::Json(serde_json::from_str::<serde_json::Value>("\"missing data\"").unwrap_err())
85 })
86 }
87
88 async fn send_request(&self, body: &serde_json::Value) -> Result<serde_json::Value, WaveError> {
90 let token = self.auth.access_token().await;
91 let resp = self
92 .http
93 .post(WAVE_GRAPHQL_URL)
94 .bearer_auth(&token)
95 .json(body)
96 .send()
97 .await?;
98
99 if resp.status() == reqwest::StatusCode::UNAUTHORIZED {
100 return Err(WaveError::Auth("401 Unauthorized".into()));
101 }
102
103 let json: serde_json::Value = resp.json().await?;
104 Ok(json)
105 }
106
107 async fn retry_after_refresh(
109 &self,
110 query: &str,
111 variables: serde_json::Value,
112 ) -> Result<serde_json::Value, WaveError> {
113 self.auth.refresh(&self.http).await?;
114
115 let body = json!({
116 "query": query,
117 "variables": variables,
118 });
119
120 let resp = self.send_request(&body).await?;
121
122 if let Some(errors) = resp.get("errors") {
123 let gql_errors: Vec<GraphqlError> = serde_json::from_value(errors.clone())?;
124 return Err(WaveError::GraphQL(gql_errors));
125 }
126
127 resp.get("data")
128 .cloned()
129 .ok_or_else(|| WaveError::Auth("missing data after retry".into()))
130 }
131
132 pub(crate) async fn execute_mutation<T: DeserializeOwned>(
134 &self,
135 query: &str,
136 variables: serde_json::Value,
137 result_key: &str,
138 ) -> Result<T, WaveError> {
139 let data = self.execute(query, variables).await?;
140
141 let result = data
142 .get(result_key)
143 .ok_or_else(|| WaveError::Auth(format!("missing key '{result_key}' in response")))?;
144
145 let did_succeed = result
146 .get("didSucceed")
147 .and_then(|v| v.as_bool())
148 .unwrap_or(false);
149
150 if !did_succeed {
151 if let Some(errors) = result.get("inputErrors") {
152 let input_errors: Vec<InputError> = serde_json::from_value(errors.clone())?;
153 if !input_errors.is_empty() {
154 return Err(WaveError::MutationFailed(input_errors));
155 }
156 }
157 return Err(WaveError::MutationFailed(vec![]));
158 }
159
160 let parsed: T = serde_json::from_value(result.clone())?;
161 Ok(parsed)
162 }
163}