Skip to main content

iota_graphql_rpc/types/
uint53.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::fmt;
6
7use async_graphql::*;
8use iota_sdk_types::Version;
9use iota_types::iota_serde::BigInt;
10use serde::{Deserialize, Serialize};
11
12use crate::error::Error;
13
14/// The largest value that a `UInt53` can hold, 2^53 - 1.
15pub(crate) const MAX_UINT53: u64 = (1 << 53) - 1;
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
18#[serde(try_from = "u64", into = "u64")]
19pub(crate) struct UInt53(u64);
20
21impl UInt53 {
22    /// Creates a new value without checking the range.
23    ///
24    /// Use this only when other code verifies the limit of 2^53 - 1.
25    pub(crate) fn new_unchecked(value: u64) -> Self {
26        Self(value)
27    }
28}
29
30/// An unsigned integer that can hold values up to 2^53 - 1. This can be treated
31/// similarly to `Int`, but it is guaranteed to be non-negative, and it may be
32/// larger than 2^32 - 1.
33#[Scalar(name = "UInt53")]
34impl ScalarType for UInt53 {
35    fn parse(value: Value) -> InputValueResult<Self> {
36        let Value::Number(n) = value else {
37            return Err(InputValueError::expected_type(value));
38        };
39
40        let Some(n) = n.as_u64() else {
41            return Err(InputValueError::custom("Expected an unsigned integer."));
42        };
43
44        Self::try_from(n)
45            .map_err(|_| InputValueError::custom("Value exceeds the maximum of UInt53 (2^53 - 1)."))
46    }
47
48    fn to_value(&self) -> Value {
49        Value::Number(self.0.into())
50    }
51}
52
53impl fmt::Display for UInt53 {
54    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55        write!(f, "{}", self.0)
56    }
57}
58
59impl TryFrom<u64> for UInt53 {
60    type Error = Error;
61
62    fn try_from(value: u64) -> Result<Self, Self::Error> {
63        if value > MAX_UINT53 {
64            return Err(Error::Internal(format!(
65                "Value {value} exceeds the maximum of UInt53 (2^53 - 1)"
66            )));
67        }
68        Ok(Self(value))
69    }
70}
71
72impl From<u32> for UInt53 {
73    fn from(value: u32) -> Self {
74        Self(value.into())
75    }
76}
77
78impl From<UInt53> for Version {
79    fn from(value: UInt53) -> Self {
80        Version::from(value.0)
81    }
82}
83
84impl From<UInt53> for BigInt<u64> {
85    fn from(value: UInt53) -> Self {
86        BigInt::from(value.0)
87    }
88}
89
90impl From<UInt53> for u64 {
91    fn from(value: UInt53) -> Self {
92        value.0
93    }
94}
95
96impl From<UInt53> for i64 {
97    fn from(value: UInt53) -> Self {
98        value.0 as i64
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn parse_bounds() {
108        assert_eq!(
109            <UInt53 as ScalarType>::parse(Value::Number(MAX_UINT53.into())).unwrap(),
110            UInt53(MAX_UINT53)
111        );
112        assert!(<UInt53 as ScalarType>::parse(Value::Number((MAX_UINT53 + 1).into())).is_err());
113        assert!(<UInt53 as ScalarType>::parse(Value::Number((-1).into())).is_err());
114        assert!(<UInt53 as ScalarType>::parse(Value::String("1".to_string())).is_err());
115    }
116
117    #[test]
118    fn try_from_bounds() {
119        assert_eq!(UInt53::try_from(MAX_UINT53).unwrap(), UInt53(MAX_UINT53));
120        assert!(UInt53::try_from(MAX_UINT53 + 1).is_err());
121    }
122}