fuel_core_client/
reqwest_ext.rs1use cynic::{
2 GraphQlResponse,
3 Operation,
4 http::CynicReqwestError,
5};
6use fuel_core_types::{
7 blockchain::header::{
8 ConsensusParametersVersion,
9 StateTransitionBytecodeVersion,
10 },
11 fuel_types::BlockHeight,
12};
13use std::{
14 future::Future,
15 marker::PhantomData,
16 pin::Pin,
17};
18
19#[derive(Debug, Clone, serde::Serialize)]
20pub struct ExtensionsRequest {
21 pub required_fuel_block_height: Option<BlockHeight>,
22}
23
24#[derive(Debug, Clone, serde::Deserialize)]
25pub struct ExtensionsResponse {
26 pub required_fuel_block_height: Option<BlockHeight>,
27 pub current_fuel_block_height: Option<BlockHeight>,
28 pub fuel_block_height_precondition_failed: Option<bool>,
29 pub current_stf_version: Option<StateTransitionBytecodeVersion>,
30 pub current_consensus_parameters_version: Option<ConsensusParametersVersion>,
31}
32
33#[derive(Debug, serde::Serialize)]
34pub struct FuelOperation<Operation> {
35 #[serde(flatten)]
36 pub operation: Operation,
37 pub extensions: ExtensionsRequest,
38}
39
40#[derive(Debug, serde::Deserialize)]
41pub struct FuelGraphQlResponse<T, ErrorExtensions = serde::de::IgnoredAny> {
42 #[serde(flatten)]
43 pub response: GraphQlResponse<T, ErrorExtensions>,
44 pub extensions: Option<ExtensionsResponse>,
45}
46
47impl<Operation> FuelOperation<Operation> {
48 pub fn new(
49 operation: Operation,
50 required_fuel_block_height: Option<BlockHeight>,
51 ) -> Self {
52 Self {
53 operation,
54 extensions: ExtensionsRequest {
55 required_fuel_block_height,
56 },
57 }
58 }
59}
60
61#[cfg(not(target_arch = "wasm32"))]
62type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
63
64#[cfg(target_arch = "wasm32")]
65type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
66
67pub trait ReqwestExt {
115 fn run_fuel_graphql<ResponseData, Vars>(
118 self,
119 operation: FuelOperation<Operation<ResponseData, Vars>>,
120 ) -> CynicReqwestBuilder<ResponseData>
121 where
122 Vars: serde::Serialize,
123 ResponseData: serde::de::DeserializeOwned + 'static;
124}
125
126pub struct CynicReqwestBuilder<ResponseData, ErrorExtensions = serde::de::IgnoredAny> {
131 builder: reqwest::RequestBuilder,
132 _marker: std::marker::PhantomData<fn() -> (ResponseData, ErrorExtensions)>,
133}
134
135impl<ResponseData, Errors> CynicReqwestBuilder<ResponseData, Errors> {
136 pub fn new(builder: reqwest::RequestBuilder) -> Self {
137 Self {
138 builder,
139 _marker: std::marker::PhantomData,
140 }
141 }
142}
143
144impl<ResponseData, Errors> IntoFuture for CynicReqwestBuilder<ResponseData, Errors>
145where
146 ResponseData: serde::de::DeserializeOwned + Send + 'static,
147 Errors: serde::de::DeserializeOwned + Send + 'static,
148{
149 type Output = Result<FuelGraphQlResponse<ResponseData, Errors>, anyhow::Error>;
150
151 type IntoFuture = BoxFuture<
152 'static,
153 Result<FuelGraphQlResponse<ResponseData, Errors>, anyhow::Error>,
154 >;
155
156 fn into_future(self) -> Self::IntoFuture {
157 Box::pin(async move {
158 let http_result = self.builder.send().await;
159 deser_gql(http_result).await
160 })
161 }
162}
163
164impl<ResponseData> CynicReqwestBuilder<ResponseData, serde::de::IgnoredAny> {
165 pub fn retain_extensions<ErrorExtensions>(
167 self,
168 ) -> CynicReqwestBuilder<ResponseData, ErrorExtensions>
169 where
170 ErrorExtensions: serde::de::DeserializeOwned,
171 {
172 let CynicReqwestBuilder { builder, _marker } = self;
173
174 CynicReqwestBuilder {
175 builder,
176 _marker: PhantomData,
177 }
178 }
179}
180
181async fn deser_gql<ResponseData, ErrorExtensions>(
182 response: Result<reqwest::Response, reqwest::Error>,
183) -> Result<FuelGraphQlResponse<ResponseData, ErrorExtensions>, anyhow::Error>
184where
185 ResponseData: serde::de::DeserializeOwned + Send + 'static,
186 ErrorExtensions: serde::de::DeserializeOwned + Send + 'static,
187{
188 let response = match response {
189 Ok(response) => response,
190 Err(e) => return Err(anyhow::anyhow!("{e}")),
191 };
192
193 let status = response.status();
194 if !status.is_success() {
195 let text = response.text().await;
196 let text = match text {
197 Ok(text) => text,
198 Err(e) => return Err(anyhow::anyhow!("{e}")),
199 };
200
201 let Ok(deserred) = serde_json::from_str(&text) else {
202 let error = CynicReqwestError::ErrorResponse(status, text);
203 return Err(anyhow::anyhow!("{error}"));
204 };
205
206 Ok(deserred)
207 } else {
208 let json = response.json().await;
209 json.map_err(|e| anyhow::anyhow!("{e}"))
210 }
211}
212
213impl ReqwestExt for reqwest::RequestBuilder {
214 fn run_fuel_graphql<ResponseData, Vars>(
215 self,
216 operation: FuelOperation<Operation<ResponseData, Vars>>,
217 ) -> CynicReqwestBuilder<ResponseData>
218 where
219 Vars: serde::Serialize,
220 ResponseData: serde::de::DeserializeOwned + 'static,
221 {
222 CynicReqwestBuilder::new(self.json(&operation))
223 }
224}