iota_graphql_rpc/types/
big_int.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::str::FromStr;
6
7use async_graphql::*;
8use move_core_types::u256::U256;
9use serde::{Deserialize, Serialize};
10
11#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
12#[serde(transparent)]
13pub(crate) struct BigInt(String);
14
15#[derive(thiserror::Error, Debug, PartialEq, Eq)]
16#[error("The provided string is not a number")]
17pub(crate) struct NotANumber;
18
19#[Scalar(use_type_description = true)]
20impl ScalarType for BigInt {
21    fn parse(value: Value) -> InputValueResult<Self> {
22        match value {
23            Value::String(s) => BigInt::from_str(&s)
24                .map_err(|_| InputValueError::custom("Not a number".to_string())),
25            _ => Err(InputValueError::expected_type(value)),
26        }
27    }
28
29    fn to_value(&self) -> Value {
30        Value::String(self.0.clone())
31    }
32}
33
34impl Description for BigInt {
35    fn description() -> &'static str {
36        "String representation of an arbitrary width, possibly signed integer."
37    }
38}
39
40impl FromStr for BigInt {
41    type Err = NotANumber;
42
43    fn from_str(s: &str) -> Result<Self, Self::Err> {
44        let mut r = s;
45        let mut signed = false;
46        // check that all are digits and first can start with -
47        if let Some(suffix) = s.strip_prefix('-') {
48            r = suffix;
49            signed = true;
50        }
51        r = r.trim_start_matches('0');
52
53        if r.is_empty() {
54            Ok(BigInt("0".to_string()))
55        } else if r.chars().all(|c| c.is_ascii_digit()) {
56            Ok(BigInt(format!("{}{}", if signed { "-" } else { "" }, r)))
57        } else {
58            Err(NotANumber)
59        }
60    }
61}
62
63macro_rules! impl_From {
64    ($($t:ident),*) => {
65        $(impl From<$t> for BigInt {
66            fn from(value: $t) -> Self {
67                BigInt(value.to_string())
68            }
69        })*
70    }
71}
72
73impl_From!(u8, u16, u32, i64, u64, i128, u128, U256);
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn from_value() {
81        assert_eq!(BigInt::from_str("123").unwrap(), BigInt("123".to_string()));
82        assert_eq!(
83            BigInt::from_str("-123").unwrap(),
84            BigInt("-123".to_string())
85        );
86        assert_eq!(
87            BigInt::from_str("00233").unwrap(),
88            BigInt("233".to_string())
89        );
90        assert_eq!(BigInt::from_str("0").unwrap(), BigInt("0".to_string()));
91        assert_eq!(BigInt::from_str("-0").unwrap(), BigInt("0".to_string()));
92        assert_eq!(BigInt::from_str("000").unwrap(), BigInt("0".to_string()));
93        assert_eq!(BigInt::from_str("-000").unwrap(), BigInt("0".to_string()));
94
95        assert!(BigInt::from_str("123a").is_err());
96        assert!(BigInt::from_str("a123").is_err());
97        assert!(BigInt::from_str("123-").is_err());
98        assert!(BigInt::from_str(" 123").is_err());
99    }
100
101    #[test]
102    fn from_primitives() {
103        assert_eq!(BigInt::from(123u8), BigInt("123".to_string()));
104
105        assert_eq!(BigInt::from(12_345u16), BigInt("12345".to_string()));
106
107        assert_eq!(BigInt::from(123_456u32), BigInt("123456".to_string()));
108
109        assert_eq!(
110            BigInt::from(-12_345_678_901i64),
111            BigInt("-12345678901".to_string()),
112        );
113
114        assert_eq!(
115            BigInt::from(12_345_678_901u64),
116            BigInt("12345678901".to_string()),
117        );
118
119        assert_eq!(
120            BigInt::from(-123_456_789_012_345_678_901i128),
121            BigInt("-123456789012345678901".to_string()),
122        );
123
124        assert_eq!(
125            BigInt::from(123_456_789_012_345_678_901u128),
126            BigInt("123456789012345678901".to_string()),
127        );
128
129        assert_eq!(
130            BigInt::from(U256::from_str("12345678901234567890123456789012345678901").unwrap()),
131            BigInt("12345678901234567890123456789012345678901".to_string())
132        );
133
134        assert_eq!(BigInt::from(1000i64 - 1200i64), BigInt("-200".to_string()));
135        assert_eq!(BigInt::from(-1200i64), BigInt("-1200".to_string()));
136    }
137}