1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#![forbid(unsafe_code)]

#[cfg(doctest)]
doc_comment::doctest!("../README.md");

use serde::{Deserialize, Serialize};
use std::{
    convert::{TryFrom, TryInto},
    ops::Add,
    ops::AddAssign,
};
use ux_serde::i54 as ux_i54;

#[derive(thiserror::Error, Debug)]
#[allow(non_camel_case_types)]
pub enum i54Error {
    #[error("i54 conversion failed")]
    ConversionFailed,
}

pub const MAX_SAFE_INTEGER: i64 = 9007199254740991;
pub const MIN_SAFE_INTEGER: i64 = -9007199254740991;

impl std::str::FromStr for i54 {
    type Err = String;
    fn from_str(_value: &str) -> Result<Self, Self::Err> {
        unimplemented!()
    }
}

// And we define how to represent i54 as a string.
impl std::fmt::Display for i54 {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[derive(Deserialize, Serialize, Copy, Clone, Debug)]
#[allow(non_camel_case_types)]
pub struct i54(ux_i54);

impl<T> PartialEq<T> for i54
where
    T: TryInto<i54> + Copy + PartialEq + Ord,
{
    fn eq(&self, other: &T) -> bool {
        match (*other).try_into() {
            Ok(v) => v.0 == self.0,
            Err(_) => false,
        }
    }
}

impl PartialEq for i54 {
    fn eq(&self, other: &i54) -> bool {
        self.0 == other.0
    }
}

impl Eq for i54 {}

#[cfg(feature = "juniper")]
#[juniper::graphql_scalar(
    description = "i54: 54-bit signed integer abstraction; represented as `i54`/`i64` in Rust, `Float` in GraphQL, `number` in TypeScript."
)]
impl<S> GraphQLScalar for i54
where
    S: ScalarValue,
{
    // Define how to convert your custom scalar into a primitive type.
    fn resolve(&self) -> Value {
        let val: i64 = self.0.into();
        juniper::Value::scalar(val as f64)
    }

    // Define how to parse a primitive type into your custom scalar.
    fn from_input_value(v: &InputValue) -> Option<i54> {
        v.as_float_value()?.try_into().ok()
    }

    // Define how to parse a string value.
    fn from_str<'a>(value: ScalarToken<'a>) -> juniper::ParseScalarResult<'a, S> {
        <String as juniper::ParseScalarValue<S>>::from_str(value)
    }
}

impl From<i32> for i54 {
    fn from(item: i32) -> Self {
        i54(ux_i54::new(item as i64))
    }
}

impl TryFrom<i64> for i54 {
    type Error = i54Error;
    fn try_from(item: i64) -> Result<Self, Self::Error> {
        let item_i64 = item as i64;
        let item_i54 = i54(ux_i54::new(item_i64));
        if item_i54.as_i64() as i64 != item {
            return Err(i54Error::ConversionFailed);
        }

        Ok(item_i54)
    }
}

impl TryFrom<usize> for i54 {
    type Error = i54Error;
    fn try_from(item: usize) -> Result<Self, Self::Error> {
        let item_i64 = item as i64;
        let item_i54 = i54(ux_i54::new(item_i64));
        if item_i54.as_i64() as usize != item {
            return Err(i54Error::ConversionFailed);
        }

        Ok(item_i54)
    }
}

impl TryFrom<i128> for i54 {
    type Error = i54Error;
    fn try_from(item: i128) -> Result<Self, Self::Error> {
        let item_i128 = item as i128;
        let item_i54 = i54(ux_i54::new(item_i128 as i64));
        if item_i54.as_i64() as i128 != item {
            return Err(i54Error::ConversionFailed);
        }

        Ok(item_i54)
    }
}

impl TryFrom<u128> for i54 {
    type Error = i54Error;
    fn try_from(item: u128) -> Result<Self, Self::Error> {
        let item_u128 = item as u128;
        let item_i54 = i54(ux_i54::new(item_u128 as i64));
        if item_i54.as_i64() as u128 != item {
            return Err(i54Error::ConversionFailed);
        }

        Ok(item_i54)
    }
}

impl TryFrom<f64> for i54 {
    type Error = i54Error;
    fn try_from(item: f64) -> Result<Self, Self::Error> {
        let item_i64 = item as i64;
        let item_i54 = i54(ux_i54::new(item_i64));
        let item_f64 = item_i54.as_i64() as f64;
        if item_f64.to_ne_bytes() != item.to_ne_bytes() {
            return Err(i54Error::ConversionFailed);
        }

        Ok(item_i54)
    }
}

impl i54 {
    pub fn as_i64(&self) -> i64 {
        self.0.into()
    }
}

impl AddAssign for i54 {
    fn add_assign(&mut self, other: Self) {
        *self = Self(self.0 + other.0);
    }
}

impl Add for i54 {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self(self.0 + other.0)
    }
}

#[cfg(feature = "rusqlite")]
impl rusqlite::types::FromSql for i54 {
    fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
        value
            .as_i64()?
            .try_into()
            .map_err(|_| rusqlite::types::FromSqlError::InvalidType)
    }
}

#[cfg(feature = "rusqlite")]
impl rusqlite::types::ToSql for i54 {
    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
        Ok(rusqlite::types::ToSqlOutput::Owned(
            rusqlite::types::Value::Integer(self.0.into()),
        ))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_rectangle() {
        // let mut rectangle = Rectangle::new(4, 5);
        let i: i54 = 20_usize.try_into().unwrap();
        assert_eq!(i, 20_i32)
    }
}