fuel_core/schema/
scalars.rs

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        /// Need our own scalar type since GraphQL integers are restricted to i32.
35        #[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/// Need our own u64 type since GraphQL integers are restricted to i32.
109#[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        // decode into bytes
207        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                // trim leading 0x
252                let value = s.strip_prefix("0x").unwrap_or(s);
253                // pad input to $len bytes
254                let mut bytes = ((value.len() / 2)..$len).map(|_| 0).collect::<Vec<u8>>();
255                // decode into bytes
256                bytes.extend(hex::decode(value).map_err(|e| e.to_string())?);
257                // attempt conversion to fixed length array, error if too long
258                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}