use anyhow::anyhow;
use serde::de::value::MapDeserializer;
use serde::Deserialize;
use std::fmt;
pub struct Hasura<T: reqwest::IntoUrl> {
client: reqwest::Client,
url: reqwest::Url,
p1: std::marker::PhantomData<T>,
code: String,
}
impl<T> Hasura<T>
where
T: reqwest::IntoUrl,
{
pub fn new(url: T, code: &str) -> Result<Self, Box<dyn std::error::Error>> {
Ok(Self {
client: reqwest::Client::new(),
url: url.into_url()?,
p1: std::marker::PhantomData,
code: code.to_string(),
})
}
pub async fn send<S: serde::Serialize, D: serde::de::DeserializeOwned>(
&self,
body: S,
) -> Result<D, Box<dyn std::error::Error>> {
let req = self
.client
.post(self.url.clone())
.header("x-hasura-admin-secret", self.code.as_bytes())
.json(&body);
let res = graphql_request(req).await?;
graphql_parse(res).await.map_err(|e| e.into())
}
}
async fn graphql_request<D: serde::de::DeserializeOwned>(
req: reqwest::RequestBuilder,
) -> anyhow::Result<graphql_client::Response<D>> {
let res = req.send().await?;
let res_ok = res.error_for_status()?;
let decode: graphql_client::Response<D> = res_ok.json().await?;
Ok(decode)
}
async fn graphql_parse<D: serde::de::DeserializeOwned>(
body: graphql_client::Response<D>,
) -> anyhow::Result<D> {
match body.data {
Some(d) => Ok(d),
None => match body.errors {
Some(errors) => {
let xs: Vec<_> = errors.into_iter().map(parse_error).collect();
let ctx = HasuraErrors { errors: xs };
Err(anyhow!(ctx))
}
None => Err(anyhow!("Hasura: No data or errors")),
},
}
}
fn parse_error(e: graphql_client::Error) -> ParsedError {
let internal = e.extensions.as_ref().and_then(|ext| {
HasuraInfo::deserialize(MapDeserializer::new(ext.clone().into_iter())).ok()
});
ParsedError {
message: e.message,
error: internal,
}
}
impl fmt::Display for HasuraErrors {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
serde_json::to_string_pretty(&self.errors).map_err(|_| fmt::Error)?
)
}
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct HasuraErrors {
pub errors: Vec<ParsedError>,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct ParsedError {
pub message: String,
pub error: Option<HasuraInfo>,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct HasuraInternal {
pub description: Option<String>,
pub exec_status: String,
pub hint: Option<String>,
pub message: String,
pub status_code: String,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct HasuraInfo {
pub code: String,
pub path: String,
pub error: Option<String>,
pub internal: Option<HasuraInternalError>,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct HasuraInternalError {
pub error: HasuraInternal,
}