1use async_graphql::{
2 InputValueError,
3 InputValueResult,
4 Scalar,
5 ScalarType,
6 Value,
7 connection::CursorType,
8};
9use fuel_core_types::{
10 fuel_types::{
11 self,
12 BlockHeight,
13 },
14 tai64::Tai64,
15};
16use std::{
17 array::TryFromSliceError,
18 convert::TryInto,
19 fmt::{
20 Display,
21 Formatter,
22 },
23 str::FromStr,
24};
25pub use tx_pointer::TxPointer;
26pub use utxo_id::UtxoId;
27
28pub mod message_id;
29pub mod tx_pointer;
30pub mod utxo_id;
31
32macro_rules! number_scalar {
33 ($i:ident, $t:ty, $name:expr_2021) => {
34 #[derive(
36 Copy, Clone, Debug, derive_more::Into, derive_more::From, PartialEq, Eq,
37 )]
38 pub struct $i(pub $t);
39
40 #[Scalar(name = $name)]
41 impl ScalarType for $i {
42 fn parse(value: Value) -> InputValueResult<Self> {
43 if let Value::String(value) = &value {
44 let num: $t = value.parse().map_err(InputValueError::custom)?;
45 Ok($i(num))
46 } else {
47 Err(InputValueError::expected_type(value))
48 }
49 }
50
51 fn to_value(&self) -> Value {
52 Value::String(self.0.to_string())
53 }
54 }
55
56 impl Display for $i {
57 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
58 self.0.fmt(f)
59 }
60 }
61
62 impl FromStr for $i {
63 type Err = core::num::ParseIntError;
64
65 fn from_str(s: &str) -> Result<Self, Self::Err> {
66 Ok(Self(<$t>::from_str(s)?))
67 }
68 }
69
70 impl CursorType for $i {
71 type Error = core::num::ParseIntError;
72
73 fn decode_cursor(s: &str) -> Result<Self, Self::Error> {
74 Self::from_str(s)
75 }
76
77 fn encode_cursor(&self) -> String {
78 self.to_string()
79 }
80 }
81 };
82}
83
84number_scalar!(U128, u128, "U128");
85number_scalar!(U64, u64, "U64");
86number_scalar!(U32, u32, "U32");
87number_scalar!(U16, u16, "U16");
88number_scalar!(U8, u8, "U8");
89
90impl From<BlockHeight> for U32 {
91 fn from(h: BlockHeight) -> Self {
92 U32(*h)
93 }
94}
95
96impl From<U32> for BlockHeight {
97 fn from(u: U32) -> Self {
98 u.0.into()
99 }
100}
101
102impl From<U32> for usize {
103 fn from(u: U32) -> Self {
104 u.0 as usize
105 }
106}
107
108#[derive(Copy, Clone, Debug, derive_more::Into, derive_more::From)]
110pub struct Tai64Timestamp(pub Tai64);
111
112#[Scalar(name = "Tai64Timestamp")]
113impl ScalarType for Tai64Timestamp {
114 fn parse(value: Value) -> InputValueResult<Self> {
115 match &value {
116 Value::String(value) => {
117 let num: u64 = value.parse().map_err(InputValueError::custom)?;
118 Ok(Tai64Timestamp(Tai64(num)))
119 }
120 _ => Err(InputValueError::expected_type(value)),
121 }
122 }
123
124 fn to_value(&self) -> Value {
125 Value::String(self.0.0.to_string())
126 }
127}
128
129#[derive(Copy, Clone, Debug, PartialEq, Eq)]
130pub struct SortedTxCursor {
131 pub block_height: BlockHeight,
132 pub tx_id: Bytes32,
133}
134
135impl SortedTxCursor {
136 pub fn new(block_height: BlockHeight, tx_id: Bytes32) -> Self {
137 Self {
138 block_height,
139 tx_id,
140 }
141 }
142}
143
144impl CursorType for SortedTxCursor {
145 type Error = String;
146
147 fn decode_cursor(s: &str) -> Result<Self, Self::Error> {
148 let (block_height, tx_id) =
149 s.split_once('#').ok_or("Incorrect format provided")?;
150
151 Ok(Self::new(
152 BlockHeight::from_str(block_height)
153 .map_err(|_| "Failed to decode block_height")?,
154 Bytes32::decode_cursor(tx_id)?,
155 ))
156 }
157
158 fn encode_cursor(&self) -> String {
159 format!("{}#{}", self.block_height, self.tx_id)
160 }
161}
162
163#[derive(Clone, Debug, derive_more::Into, derive_more::From, PartialEq, Eq)]
164pub struct HexString(pub(crate) Vec<u8>);
165
166#[Scalar(name = "HexString")]
167impl ScalarType for HexString {
168 fn parse(value: Value) -> InputValueResult<Self> {
169 match &value {
170 Value::String(value) => {
171 HexString::from_str(value.as_str()).map_err(Into::into)
172 }
173 _ => Err(InputValueError::expected_type(value)),
174 }
175 }
176
177 fn to_value(&self) -> Value {
178 Value::String(self.to_string())
179 }
180}
181
182impl Display for HexString {
183 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
184 let s = format!("0x{}", hex::encode(&self.0));
185 s.fmt(f)
186 }
187}
188
189impl CursorType for HexString {
190 type Error = String;
191
192 fn decode_cursor(s: &str) -> Result<Self, Self::Error> {
193 Self::from_str(s)
194 }
195
196 fn encode_cursor(&self) -> String {
197 self.to_string()
198 }
199}
200
201impl FromStr for HexString {
202 type Err = String;
203
204 fn from_str(s: &str) -> Result<Self, Self::Err> {
205 let value = s.strip_prefix("0x").unwrap_or(s);
206 let bytes = hex::decode(value).map_err(|e| e.to_string())?;
208 Ok(HexString(bytes))
209 }
210}
211
212impl From<fuel_types::Nonce> for HexString {
213 fn from(n: fuel_types::Nonce) -> Self {
214 HexString(n.to_vec())
215 }
216}
217
218impl TryInto<fuel_types::Nonce> for HexString {
219 type Error = TryFromSliceError;
220
221 fn try_into(self) -> Result<fuel_types::Nonce, Self::Error> {
222 let bytes: [u8; 32] = self.0.as_slice().try_into()?;
223 Ok(fuel_types::Nonce::from(bytes))
224 }
225}
226
227macro_rules! fuel_type_scalar {
228 ($name:literal, $id:ident, $ft_id:ident, $len:expr_2021) => {
229 #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
230 pub struct $id(pub(crate) fuel_types::$ft_id);
231
232 #[Scalar(name = $name)]
233 impl ScalarType for $id {
234 fn parse(value: Value) -> InputValueResult<Self> {
235 if let Value::String(value) = &value {
236 $id::from_str(value.as_str()).map_err(Into::into)
237 } else {
238 Err(InputValueError::expected_type(value))
239 }
240 }
241
242 fn to_value(&self) -> Value {
243 Value::String(self.to_string())
244 }
245 }
246
247 impl FromStr for $id {
248 type Err = String;
249
250 fn from_str(s: &str) -> Result<Self, Self::Err> {
251 let value = s.strip_prefix("0x").unwrap_or(s);
253 let mut bytes = ((value.len() / 2)..$len).map(|_| 0).collect::<Vec<u8>>();
255 bytes.extend(hex::decode(value).map_err(|e| e.to_string())?);
257 let bytes: [u8; $len] = bytes.try_into().map_err(|e: Vec<u8>| {
259 format!("expected {} bytes, received {}", $len, e.len())
260 })?;
261
262 Ok(Self(bytes.into()))
263 }
264 }
265
266 impl Display for $id {
267 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
268 let s = format!("0x{}", hex::encode(self.0));
269 s.fmt(f)
270 }
271 }
272
273 impl From<$id> for fuel_types::$ft_id {
274 fn from(value: $id) -> Self {
275 value.0
276 }
277 }
278
279 impl From<fuel_types::$ft_id> for $id {
280 fn from(value: fuel_types::$ft_id) -> Self {
281 $id(value)
282 }
283 }
284
285 impl CursorType for $id {
286 type Error = String;
287
288 fn decode_cursor(s: &str) -> Result<Self, Self::Error> {
289 Self::from_str(s)
290 }
291
292 fn encode_cursor(&self) -> String {
293 self.to_string()
294 }
295 }
296 };
297}
298
299fuel_type_scalar!("Bytes32", Bytes32, Bytes32, 32);
300fuel_type_scalar!("Address", Address, Address, 32);
301fuel_type_scalar!("BlockId", BlockId, Bytes32, 32);
302fuel_type_scalar!("AssetId", AssetId, AssetId, 32);
303fuel_type_scalar!("BlobId", BlobId, BlobId, 32);
304fuel_type_scalar!("ContractId", ContractId, ContractId, 32);
305fuel_type_scalar!("SubId", SubId, SubAssetId, 32);
306fuel_type_scalar!("Salt", Salt, Salt, 32);
307fuel_type_scalar!("TransactionId", TransactionId, Bytes32, 32);
308fuel_type_scalar!("RelayedTransactionId", RelayedTransactionId, Bytes32, 32);
309fuel_type_scalar!("MessageId", MessageId, MessageId, 32);
310fuel_type_scalar!("Nonce", Nonce, Nonce, 32);
311fuel_type_scalar!("Signature", Signature, Bytes64, 64);
312
313impl From<fuel_core_types::fuel_vm::Signature> for Signature {
314 fn from(s: fuel_core_types::fuel_vm::Signature) -> Self {
315 Self(<[u8; 64]>::from(s).into())
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn transaction_id_parseable_with_0x_prefix() {
325 let id = "0x0101010101010101010101010101010101010101010101010101010101010101";
326 let tx_id = TransactionId::from_str(id).expect("parseable with 0x");
327 assert_eq!(*tx_id.0, [0x01; 32]);
328 }
329
330 #[test]
331 fn transaction_id_parseable_without_0x_prefix() {
332 let id = "0101010101010101010101010101010101010101010101010101010101010101";
333 let tx_id = TransactionId::from_str(id).expect("parseable without 0x");
334 assert_eq!(*tx_id.0, [0x01; 32]);
335 }
336
337 #[test]
338 fn transaction_id_only_parses_valid_hex() {
339 let id = "0xZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
340 let res = TransactionId::from_str(id);
341 assert!(res.is_err());
342 }
343
344 #[test]
345 fn hex_string_parses_with_0x_prefix() {
346 let hex_data = "0x0101";
347 let parsed_data = HexString::from_str(hex_data).expect("parseable with 0x");
348 assert_eq!(*parsed_data.0, [0x01; 2]);
349 }
350
351 #[test]
352 fn hex_string_parses_without_0x_prefix() {
353 let hex_data = "0101";
354 let parsed_data = HexString::from_str(hex_data).expect("parseable without 0x");
355 assert_eq!(*parsed_data.0, [0x01; 2]);
356 }
357
358 #[test]
359 fn hex_string_only_parses_valid_hex() {
360 let hex_data = "0xZZZZ";
361 let res = HexString::from_str(hex_data);
362 assert!(res.is_err());
363 }
364}