conjure_object/
safe_long.rs

1// Copyright 2018 Palantir Technologies, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The Conjure `safelong` type.
16use serde::{de, ser};
17use std::convert::{TryFrom, TryInto};
18use std::error::Error;
19use std::fmt;
20use std::num::{ParseIntError, TryFromIntError};
21use std::ops::Deref;
22use std::str::FromStr;
23
24/// An i64 limited to a range safely representable in JSON.
25///
26/// JSON does not specify requirements of its numeric type, which can lead to issues interoperating between different
27/// JSON libraries and languages. In particular, some implementations (including Javascript) interpret numbers as double
28/// precision floating point values. Sufficiently large 64-bit integers are not exactly representable as doubles which
29/// can cause bugs as numbers change value as they're transmitted from place to place.
30///
31/// The `SafeLong` type wraps an i64, and avoids these issues by limiting its value to the range that is exactly
32/// representable in a double: values between -2<sup>53</sup> + 1 and 2<sup>53</sup> - 1.
33#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
34pub struct SafeLong(i64);
35
36impl SafeLong {
37    /// Returns the smallest valid `SafeLong`.
38    #[inline]
39    pub fn min_value() -> SafeLong {
40        SafeLong(-(1 << 53) + 1)
41    }
42
43    /// Returns the largest valid `SafeLong`.
44    #[inline]
45    pub fn max_value() -> SafeLong {
46        SafeLong((1 << 53) - 1)
47    }
48
49    /// Creates a new `SafeLong` from an `i64`.
50    ///
51    /// Returns an error if the value is out of range.
52    #[inline]
53    pub fn new(value: i64) -> Result<SafeLong, BoundsError> {
54        if value >= *SafeLong::min_value() && value <= *SafeLong::max_value() {
55            Ok(SafeLong(value))
56        } else {
57            Err(BoundsError(()))
58        }
59    }
60}
61
62impl Deref for SafeLong {
63    type Target = i64;
64
65    #[inline]
66    fn deref(&self) -> &i64 {
67        &self.0
68    }
69}
70
71impl fmt::Display for SafeLong {
72    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
73        fmt::Display::fmt(&self.0, fmt)
74    }
75}
76
77impl FromStr for SafeLong {
78    type Err = ParseError;
79
80    #[inline]
81    fn from_str(s: &str) -> Result<SafeLong, ParseError> {
82        let n = s
83            .parse()
84            .map_err(|e| ParseError(ParseErrorInner::Parse(e)))?;
85
86        SafeLong::new(n).map_err(|e| ParseError(ParseErrorInner::Bounds(e)))
87    }
88}
89
90impl ser::Serialize for SafeLong {
91    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
92    where
93        S: ser::Serializer,
94    {
95        s.serialize_i64(self.0)
96    }
97}
98
99impl<'de> de::Deserialize<'de> for SafeLong {
100    fn deserialize<D>(d: D) -> Result<SafeLong, D::Error>
101    where
102        D: de::Deserializer<'de>,
103    {
104        let value = i64::deserialize(d)?;
105        SafeLong::new(value)
106            .map_err(|_| de::Error::invalid_value(de::Unexpected::Signed(value), &"a safe long"))
107    }
108}
109
110macro_rules! impl_from {
111    ($($t:ty),*) => {
112        $(
113            impl From<$t> for SafeLong {
114                #[inline]
115                fn from(n: $t) -> SafeLong {
116                    SafeLong(i64::from(n))
117                }
118            }
119        )*
120    }
121}
122
123impl_from!(u8, i8, u16, i16, u32, i32);
124
125macro_rules! impl_into {
126    ($($t:ty),*) => {
127        $(
128            impl From<SafeLong> for $t {
129                #[inline]
130                fn from(n: SafeLong) -> $t {
131                    n.0.into()
132                }
133            }
134        )*
135    }
136}
137
138impl_into!(i64, i128);
139
140macro_rules! impl_try_from {
141    ($($t:ty),*) => {
142        $(
143            impl TryFrom<$t> for SafeLong {
144                type Error = BoundsError;
145
146                #[inline]
147                fn try_from(n: $t) -> Result<SafeLong, BoundsError> {
148                    i64::try_from(n)
149                        .map_err(|_| BoundsError(()))
150                        .and_then(SafeLong::new)
151                }
152            }
153        )*
154    }
155}
156
157impl_try_from!(u64, i64, u128, i128, usize, isize);
158
159macro_rules! impl_try_into {
160    ($($t:ty),*) => {
161        $(
162            impl TryFrom<SafeLong> for $t {
163                type Error = TryFromIntError;
164
165                #[inline]
166                fn try_from(n: SafeLong) -> Result<$t, TryFromIntError> {
167                    n.0.try_into()
168                }
169            }
170        )*
171    };
172}
173
174impl_try_into!(u8, i8, u16, i16, u32, i32, u64, u128, usize, isize);
175
176/// The error returned from constructing an out-of bounds `SafeLong`.
177#[derive(Debug, Clone)]
178pub struct BoundsError(());
179
180impl fmt::Display for BoundsError {
181    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
182        fmt.write_str("value was out of bounds of a safe long")
183    }
184}
185
186impl Error for BoundsError {}
187
188#[derive(Debug, Clone)]
189enum ParseErrorInner {
190    Parse(ParseIntError),
191    Bounds(BoundsError),
192}
193
194/// The error returned after failing to parse a string into a `SafeLong`.
195#[derive(Debug, Clone)]
196pub struct ParseError(ParseErrorInner);
197
198impl fmt::Display for ParseError {
199    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
200        match &self.0 {
201            ParseErrorInner::Parse(e) => fmt::Display::fmt(e, fmt),
202            ParseErrorInner::Bounds(e) => fmt::Display::fmt(e, fmt),
203        }
204    }
205}
206
207impl Error for ParseError {}