i54_/
lib.rs

1#![forbid(unsafe_code)]
2
3#[cfg(doctest)]
4doc_comment::doctest!("../README.md");
5
6use serde::{Deserialize, Serialize};
7use std::{
8    convert::{TryFrom, TryInto},
9    ops::Add,
10    ops::AddAssign,
11};
12use ux_serde::i54 as ux_i54;
13
14#[derive(thiserror::Error, Debug)]
15#[allow(non_camel_case_types)]
16pub enum i54Error {
17    #[error("i54 conversion failed")]
18    ConversionFailed,
19}
20
21pub const MAX_SAFE_INTEGER: i64 = 9007199254740991;
22pub const MIN_SAFE_INTEGER: i64 = -9007199254740991;
23
24impl std::str::FromStr for i54 {
25    type Err = String;
26    fn from_str(_value: &str) -> Result<Self, Self::Err> {
27        unimplemented!()
28    }
29}
30
31// And we define how to represent i54 as a string.
32impl std::fmt::Display for i54 {
33    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
34        write!(f, "{}", self.0)
35    }
36}
37
38#[derive(Deserialize, Serialize, Copy, Clone, Debug)]
39#[allow(non_camel_case_types)]
40pub struct i54(ux_i54);
41
42impl<T> PartialEq<T> for i54
43where
44    T: TryInto<i54> + Copy + PartialEq + Ord,
45{
46    fn eq(&self, other: &T) -> bool {
47        match (*other).try_into() {
48            Ok(v) => v.0 == self.0,
49            Err(_) => false,
50        }
51    }
52}
53
54impl PartialEq for i54 {
55    fn eq(&self, other: &i54) -> bool {
56        self.0 == other.0
57    }
58}
59
60impl Eq for i54 {}
61
62#[cfg(feature = "juniper")]
63#[juniper::graphql_scalar(
64    description = "i54: 54-bit signed integer abstraction; represented as `i54`/`i64` in Rust, `Float` in GraphQL, `number` in TypeScript."
65)]
66impl<S> GraphQLScalar for i54
67where
68    S: ScalarValue,
69{
70    // Define how to convert your custom scalar into a primitive type.
71    fn resolve(&self) -> Value {
72        let val: i64 = self.0.into();
73        juniper::Value::scalar(val as f64)
74    }
75
76    // Define how to parse a primitive type into your custom scalar.
77    fn from_input_value(v: &InputValue) -> Option<i54> {
78        v.as_float_value()?.try_into().ok()
79    }
80
81    // Define how to parse a string value.
82    fn from_str<'a>(value: ScalarToken<'a>) -> juniper::ParseScalarResult<'a, S> {
83        <String as juniper::ParseScalarValue<S>>::from_str(value)
84    }
85}
86
87impl From<i32> for i54 {
88    fn from(item: i32) -> Self {
89        i54(ux_i54::new(item as i64))
90    }
91}
92
93impl TryFrom<i64> for i54 {
94    type Error = i54Error;
95    fn try_from(item: i64) -> Result<Self, Self::Error> {
96        let item_i64 = item as i64;
97        let item_i54 = i54(ux_i54::new(item_i64));
98        if item_i54.as_i64() as i64 != item {
99            return Err(i54Error::ConversionFailed);
100        }
101
102        Ok(item_i54)
103    }
104}
105
106impl TryFrom<usize> for i54 {
107    type Error = i54Error;
108    fn try_from(item: usize) -> Result<Self, Self::Error> {
109        let item_i64 = item as i64;
110        let item_i54 = i54(ux_i54::new(item_i64));
111        if item_i54.as_i64() as usize != item {
112            return Err(i54Error::ConversionFailed);
113        }
114
115        Ok(item_i54)
116    }
117}
118
119impl TryFrom<i128> for i54 {
120    type Error = i54Error;
121    fn try_from(item: i128) -> Result<Self, Self::Error> {
122        let item_i128 = item as i128;
123        let item_i54 = i54(ux_i54::new(item_i128 as i64));
124        if item_i54.as_i64() as i128 != item {
125            return Err(i54Error::ConversionFailed);
126        }
127
128        Ok(item_i54)
129    }
130}
131
132impl TryFrom<u128> for i54 {
133    type Error = i54Error;
134    fn try_from(item: u128) -> Result<Self, Self::Error> {
135        let item_u128 = item as u128;
136        let item_i54 = i54(ux_i54::new(item_u128 as i64));
137        if item_i54.as_i64() as u128 != item {
138            return Err(i54Error::ConversionFailed);
139        }
140
141        Ok(item_i54)
142    }
143}
144
145impl TryFrom<f64> for i54 {
146    type Error = i54Error;
147    fn try_from(item: f64) -> Result<Self, Self::Error> {
148        let item_i64 = item as i64;
149        let item_i54 = i54(ux_i54::new(item_i64));
150        let item_f64 = item_i54.as_i64() as f64;
151        if item_f64.to_ne_bytes() != item.to_ne_bytes() {
152            return Err(i54Error::ConversionFailed);
153        }
154
155        Ok(item_i54)
156    }
157}
158
159impl i54 {
160    pub fn as_i64(&self) -> i64 {
161        self.0.into()
162    }
163}
164
165impl AddAssign for i54 {
166    fn add_assign(&mut self, other: Self) {
167        *self = Self(self.0 + other.0);
168    }
169}
170
171impl Add for i54 {
172    type Output = Self;
173
174    fn add(self, other: Self) -> Self {
175        Self(self.0 + other.0)
176    }
177}
178
179#[cfg(feature = "rusqlite")]
180impl rusqlite::types::FromSql for i54 {
181    fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
182        value
183            .as_i64()?
184            .try_into()
185            .map_err(|_| rusqlite::types::FromSqlError::InvalidType)
186    }
187}
188
189#[cfg(feature = "rusqlite")]
190impl rusqlite::types::ToSql for i54 {
191    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
192        Ok(rusqlite::types::ToSqlOutput::Owned(
193            rusqlite::types::Value::Integer(self.0.into()),
194        ))
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_rectangle() {
204        // let mut rectangle = Rectangle::new(4, 5);
205        let i: i54 = 20_usize.try_into().unwrap();
206        assert_eq!(i, 20_i32)
207    }
208}