use serde::de::DeserializeOwned;
use serde_json::json;
use crate::auth::{AuthState, OAuthConfig};
use crate::error::{GraphqlError, InputError, WaveError};
const WAVE_GRAPHQL_URL: &str = "https://gql.waveapps.com/graphql/public";
#[derive(Clone)]
pub struct WaveClient {
http: reqwest::Client,
pub(crate) auth: AuthState,
}
impl WaveClient {
pub fn with_oauth(config: OAuthConfig) -> Self {
Self {
http: reqwest::Client::new(),
auth: AuthState::new(config),
}
}
pub(crate) async fn execute(
&self,
query: &str,
variables: serde_json::Value,
) -> Result<serde_json::Value, WaveError> {
let body = json!({
"query": query,
"variables": variables,
});
let resp = self.send_request(&body).await?;
if let Some(errors) = resp.get("errors") {
let gql_errors: Vec<GraphqlError> = serde_json::from_value(errors.clone())?;
if resp.get("data").is_some_and(|d| !d.is_null()) {
let is_unauth = gql_errors
.iter()
.any(|e| {
e.extensions
.as_ref()
.and_then(|ext| ext.code.as_deref())
== Some("UNAUTHENTICATED")
});
if is_unauth {
return self.retry_after_refresh(query, variables).await;
}
return Err(WaveError::GraphQL(gql_errors));
}
let is_unauth = gql_errors
.iter()
.any(|e| {
e.extensions
.as_ref()
.and_then(|ext| ext.code.as_deref())
== Some("UNAUTHENTICATED")
});
if is_unauth {
return self.retry_after_refresh(query, variables).await;
}
return Err(WaveError::GraphQL(gql_errors));
}
resp.get("data")
.cloned()
.ok_or_else(|| {
WaveError::Json(serde_json::from_str::<serde_json::Value>("\"missing data\"").unwrap_err())
})
}
async fn send_request(&self, body: &serde_json::Value) -> Result<serde_json::Value, WaveError> {
let token = self.auth.access_token().await;
let resp = self
.http
.post(WAVE_GRAPHQL_URL)
.bearer_auth(&token)
.json(body)
.send()
.await?;
if resp.status() == reqwest::StatusCode::UNAUTHORIZED {
return Err(WaveError::Auth("401 Unauthorized".into()));
}
let json: serde_json::Value = resp.json().await?;
Ok(json)
}
async fn retry_after_refresh(
&self,
query: &str,
variables: serde_json::Value,
) -> Result<serde_json::Value, WaveError> {
self.auth.refresh(&self.http).await?;
let body = json!({
"query": query,
"variables": variables,
});
let resp = self.send_request(&body).await?;
if let Some(errors) = resp.get("errors") {
let gql_errors: Vec<GraphqlError> = serde_json::from_value(errors.clone())?;
return Err(WaveError::GraphQL(gql_errors));
}
resp.get("data")
.cloned()
.ok_or_else(|| WaveError::Auth("missing data after retry".into()))
}
pub(crate) async fn execute_mutation<T: DeserializeOwned>(
&self,
query: &str,
variables: serde_json::Value,
result_key: &str,
) -> Result<T, WaveError> {
let data = self.execute(query, variables).await?;
let result = data
.get(result_key)
.ok_or_else(|| WaveError::Auth(format!("missing key '{result_key}' in response")))?;
let did_succeed = result
.get("didSucceed")
.and_then(|v| v.as_bool())
.unwrap_or(false);
if !did_succeed {
if let Some(errors) = result.get("inputErrors") {
let input_errors: Vec<InputError> = serde_json::from_value(errors.clone())?;
if !input_errors.is_empty() {
return Err(WaveError::MutationFailed(input_errors));
}
}
return Err(WaveError::MutationFailed(vec![]));
}
let parsed: T = serde_json::from_value(result.clone())?;
Ok(parsed)
}
}