use anyhow::anyhow;
use reqwest::{IntoUrl, Url, header, header::HeaderMap};
use serde::de::DeserializeOwned;
use serde_json::{Value, json};
#[derive(Debug, Clone)]
pub struct IndexerGraphQLClient {
client: reqwest::Client,
endpoint: Url,
request_id: i64,
}
impl IndexerGraphQLClient {
pub fn connect<T: IntoUrl>(endpoint: T) -> Result<Self, anyhow::Error> {
let client = reqwest::Client::builder()
.default_headers({
let mut headers = HeaderMap::with_capacity(1);
headers.insert(header::CONTENT_TYPE, "applications/json".parse().unwrap());
headers
})
.build()?;
Ok(Self {
client,
endpoint: endpoint.into_url()?,
request_id: 0,
})
}
pub fn next_request_id(&mut self) -> i64 {
self.request_id += 1;
self.request_id
}
pub async fn send_request<R: DeserializeOwned>(
&mut self,
query: &str,
variables: Option<Value>,
headers: Option<HeaderMap>,
) -> Result<R, anyhow::Error> {
let body = json!({
"query": query,
"variables": variables
});
let mut req: reqwest::RequestBuilder = self.client.post(self.endpoint.clone());
if let Some(headers) = headers {
req = req.headers(headers);
}
let resp = req.json(&body).send().await?;
let val = resp.json::<Value>().await?;
let data: Value = graphql_data(&body, &val)?;
serde_json::from_value::<R>(data).map_err(|e| anyhow!("Failed to deserialize response {}: {}", val, e))
}
}
fn graphql_data(req: &Value, val: &Value) -> Result<Value, anyhow::Error> {
if let Some(err) = val.get("errors") {
if let Some(errors) = err.as_array() {
let mut msgs = Vec::new();
for err in errors {
let message = err.get("message").and_then(|m| m.as_str()).unwrap_or("Unknown error");
msgs.push(message);
}
return Err(anyhow!("Request failed:\n{}\n\nGraphQL error {}", req, msgs.join("\n")));
}
return Err(anyhow!("Request failed:\n{}\n\nGraphQL error {}", req, err));
}
let result = val.get("data").ok_or_else(|| anyhow!("Missing result field"))?;
Ok(result.clone())
}