use cynic::{
GraphQlResponse,
Operation,
http::CynicReqwestError,
};
use fuel_core_types::{
blockchain::header::{
ConsensusParametersVersion,
StateTransitionBytecodeVersion,
},
fuel_types::BlockHeight,
};
use std::{
future::Future,
marker::PhantomData,
pin::Pin,
};
#[derive(Debug, Clone, serde::Serialize)]
pub struct ExtensionsRequest {
pub required_fuel_block_height: Option<BlockHeight>,
}
#[derive(Debug, Clone, serde::Deserialize)]
pub struct ExtensionsResponse {
pub required_fuel_block_height: Option<BlockHeight>,
pub current_fuel_block_height: Option<BlockHeight>,
pub fuel_block_height_precondition_failed: Option<bool>,
pub current_stf_version: Option<StateTransitionBytecodeVersion>,
pub current_consensus_parameters_version: Option<ConsensusParametersVersion>,
}
#[derive(Debug, serde::Serialize)]
pub struct FuelOperation<Operation> {
#[serde(flatten)]
pub operation: Operation,
pub extensions: ExtensionsRequest,
}
#[derive(Debug, serde::Deserialize)]
pub struct FuelGraphQlResponse<T, ErrorExtensions = serde::de::IgnoredAny> {
#[serde(flatten)]
pub response: GraphQlResponse<T, ErrorExtensions>,
pub extensions: Option<ExtensionsResponse>,
}
impl<Operation> FuelOperation<Operation> {
pub fn new(
operation: Operation,
required_fuel_block_height: Option<BlockHeight>,
) -> Self {
Self {
operation,
extensions: ExtensionsRequest {
required_fuel_block_height,
},
}
}
}
#[cfg(not(target_arch = "wasm32"))]
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[cfg(target_arch = "wasm32")]
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
pub trait ReqwestExt {
fn run_fuel_graphql<ResponseData, Vars>(
self,
operation: FuelOperation<Operation<ResponseData, Vars>>,
) -> CynicReqwestBuilder<ResponseData>
where
Vars: serde::Serialize,
ResponseData: serde::de::DeserializeOwned + 'static;
}
pub struct CynicReqwestBuilder<ResponseData, ErrorExtensions = serde::de::IgnoredAny> {
builder: reqwest::RequestBuilder,
_marker: std::marker::PhantomData<fn() -> (ResponseData, ErrorExtensions)>,
}
impl<ResponseData, Errors> CynicReqwestBuilder<ResponseData, Errors> {
pub fn new(builder: reqwest::RequestBuilder) -> Self {
Self {
builder,
_marker: std::marker::PhantomData,
}
}
}
impl<ResponseData, Errors> IntoFuture for CynicReqwestBuilder<ResponseData, Errors>
where
ResponseData: serde::de::DeserializeOwned + Send + 'static,
Errors: serde::de::DeserializeOwned + Send + 'static,
{
type Output = Result<FuelGraphQlResponse<ResponseData, Errors>, anyhow::Error>;
type IntoFuture = BoxFuture<
'static,
Result<FuelGraphQlResponse<ResponseData, Errors>, anyhow::Error>,
>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let http_result = self.builder.send().await;
deser_gql(http_result).await
})
}
}
impl<ResponseData> CynicReqwestBuilder<ResponseData, serde::de::IgnoredAny> {
pub fn retain_extensions<ErrorExtensions>(
self,
) -> CynicReqwestBuilder<ResponseData, ErrorExtensions>
where
ErrorExtensions: serde::de::DeserializeOwned,
{
let CynicReqwestBuilder { builder, _marker } = self;
CynicReqwestBuilder {
builder,
_marker: PhantomData,
}
}
}
async fn deser_gql<ResponseData, ErrorExtensions>(
response: Result<reqwest::Response, reqwest::Error>,
) -> Result<FuelGraphQlResponse<ResponseData, ErrorExtensions>, anyhow::Error>
where
ResponseData: serde::de::DeserializeOwned + Send + 'static,
ErrorExtensions: serde::de::DeserializeOwned + Send + 'static,
{
let response = match response {
Ok(response) => response,
Err(e) => return Err(anyhow::anyhow!("{e}")),
};
let status = response.status();
if !status.is_success() {
let text = response.text().await;
let text = match text {
Ok(text) => text,
Err(e) => return Err(anyhow::anyhow!("{e}")),
};
let Ok(deserred) = serde_json::from_str(&text) else {
let error = CynicReqwestError::ErrorResponse(status, text);
return Err(anyhow::anyhow!("{error}"));
};
Ok(deserred)
} else {
let json = response.json().await;
json.map_err(|e| anyhow::anyhow!("{e}"))
}
}
impl ReqwestExt for reqwest::RequestBuilder {
fn run_fuel_graphql<ResponseData, Vars>(
self,
operation: FuelOperation<Operation<ResponseData, Vars>>,
) -> CynicReqwestBuilder<ResponseData>
where
Vars: serde::Serialize,
ResponseData: serde::de::DeserializeOwned + 'static,
{
CynicReqwestBuilder::new(self.json(&operation))
}
}