oxsdatatypes/
integer.rs

1use crate::{Boolean, Decimal, Double, Float};
2use std::fmt;
3use std::num::ParseIntError;
4use std::str::FromStr;
5
6/// [XML Schema `integer` datatype](https://www.w3.org/TR/xmlschema11-2/#integer)
7///
8/// Uses internally a [`i64`].
9#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
10#[repr(transparent)]
11pub struct Integer {
12    value: i64,
13}
14
15impl Integer {
16    pub const MAX: Self = Self { value: i64::MAX };
17    pub const MIN: Self = Self { value: i64::MIN };
18
19    #[inline]
20    #[must_use]
21    pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
22        Self {
23            value: i64::from_be_bytes(bytes),
24        }
25    }
26
27    #[inline]
28    #[must_use]
29    pub fn to_be_bytes(self) -> [u8; 8] {
30        self.value.to_be_bytes()
31    }
32
33    /// [op:numeric-add](https://www.w3.org/TR/xpath-functions-31/#func-numeric-add)
34    ///
35    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
36    #[inline]
37    #[must_use]
38    pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> {
39        Some(Self {
40            value: self.value.checked_add(rhs.into().value)?,
41        })
42    }
43
44    /// [op:numeric-subtract](https://www.w3.org/TR/xpath-functions-31/#func-numeric-subtract)
45    ///
46    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
47    #[inline]
48    #[must_use]
49    pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> {
50        Some(Self {
51            value: self.value.checked_sub(rhs.into().value)?,
52        })
53    }
54
55    /// [op:numeric-multiply](https://www.w3.org/TR/xpath-functions-31/#func-numeric-multiply)
56    ///
57    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
58    #[inline]
59    #[must_use]
60    pub fn checked_mul(self, rhs: impl Into<Self>) -> Option<Self> {
61        Some(Self {
62            value: self.value.checked_mul(rhs.into().value)?,
63        })
64    }
65
66    /// [op:numeric-integer-divide](https://www.w3.org/TR/xpath-functions-31/#func-numeric-integer-divide)
67    ///
68    /// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
69    #[inline]
70    #[must_use]
71    pub fn checked_div(self, rhs: impl Into<Self>) -> Option<Self> {
72        Some(Self {
73            value: self.value.checked_div(rhs.into().value)?,
74        })
75    }
76
77    /// [op:numeric-mod](https://www.w3.org/TR/xpath-functions-31/#func-numeric-mod)
78    ///
79    /// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
80    #[inline]
81    #[must_use]
82    pub fn checked_rem(self, rhs: impl Into<Self>) -> Option<Self> {
83        Some(Self {
84            value: self.value.checked_rem(rhs.into().value)?,
85        })
86    }
87
88    /// Euclidean remainder
89    ///
90    /// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
91    #[inline]
92    #[must_use]
93    pub fn checked_rem_euclid(self, rhs: impl Into<Self>) -> Option<Self> {
94        Some(Self {
95            value: self.value.checked_rem_euclid(rhs.into().value)?,
96        })
97    }
98
99    /// [op:numeric-unary-minus](https://www.w3.org/TR/xpath-functions-31/#func-numeric-unary-minus)
100    ///
101    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
102    #[inline]
103    #[must_use]
104    pub fn checked_neg(self) -> Option<Self> {
105        Some(Self {
106            value: self.value.checked_neg()?,
107        })
108    }
109
110    /// [fn:abs](https://www.w3.org/TR/xpath-functions-31/#func-abs)
111    ///
112    /// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
113    #[inline]
114    #[must_use]
115    pub fn checked_abs(self) -> Option<Self> {
116        Some(Self {
117            value: self.value.checked_abs()?,
118        })
119    }
120
121    #[inline]
122    #[must_use]
123    pub const fn is_negative(self) -> bool {
124        self.value < 0
125    }
126
127    #[inline]
128    #[must_use]
129    pub const fn is_positive(self) -> bool {
130        self.value > 0
131    }
132
133    /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity).
134    #[inline]
135    #[must_use]
136    pub fn is_identical_with(self, other: Self) -> bool {
137        self == other
138    }
139}
140
141impl From<bool> for Integer {
142    #[inline]
143    fn from(value: bool) -> Self {
144        Self {
145            value: value.into(),
146        }
147    }
148}
149
150impl From<i8> for Integer {
151    #[inline]
152    fn from(value: i8) -> Self {
153        Self {
154            value: value.into(),
155        }
156    }
157}
158
159impl From<i16> for Integer {
160    #[inline]
161    fn from(value: i16) -> Self {
162        Self {
163            value: value.into(),
164        }
165    }
166}
167
168impl From<i32> for Integer {
169    #[inline]
170    fn from(value: i32) -> Self {
171        Self {
172            value: value.into(),
173        }
174    }
175}
176
177impl From<i64> for Integer {
178    #[inline]
179    fn from(value: i64) -> Self {
180        Self { value }
181    }
182}
183
184impl From<u8> for Integer {
185    #[inline]
186    fn from(value: u8) -> Self {
187        Self {
188            value: value.into(),
189        }
190    }
191}
192
193impl From<u16> for Integer {
194    #[inline]
195    fn from(value: u16) -> Self {
196        Self {
197            value: value.into(),
198        }
199    }
200}
201
202impl From<u32> for Integer {
203    #[inline]
204    fn from(value: u32) -> Self {
205        Self {
206            value: value.into(),
207        }
208    }
209}
210
211impl From<Boolean> for Integer {
212    #[inline]
213    fn from(value: Boolean) -> Self {
214        bool::from(value).into()
215    }
216}
217
218impl From<Integer> for i64 {
219    #[inline]
220    fn from(value: Integer) -> Self {
221        value.value
222    }
223}
224
225impl FromStr for Integer {
226    type Err = ParseIntError;
227
228    #[inline]
229    fn from_str(input: &str) -> Result<Self, Self::Err> {
230        Ok(i64::from_str(input)?.into())
231    }
232}
233
234impl fmt::Display for Integer {
235    #[inline]
236    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237        self.value.fmt(f)
238    }
239}
240
241impl TryFrom<Float> for Integer {
242    type Error = TooLargeForIntegerError;
243
244    #[inline]
245    fn try_from(value: Float) -> Result<Self, Self::Error> {
246        Decimal::try_from(value)
247            .map_err(|_| TooLargeForIntegerError)?
248            .try_into()
249    }
250}
251
252impl TryFrom<Double> for Integer {
253    type Error = TooLargeForIntegerError;
254
255    #[inline]
256    fn try_from(value: Double) -> Result<Self, Self::Error> {
257        Decimal::try_from(value)
258            .map_err(|_| TooLargeForIntegerError)?
259            .try_into()
260    }
261}
262
263/// The input is too large to fit into an [`Integer`].
264///
265/// Matches XPath [`FOCA0003` error](https://www.w3.org/TR/xpath-functions-31/#ERRFOCA0003).
266#[derive(Debug, Clone, Copy, thiserror::Error)]
267#[error("Value too large for xsd:integer internal representation")]
268pub struct TooLargeForIntegerError;
269
270#[cfg(test)]
271#[allow(clippy::panic_in_result_fn)]
272mod tests {
273    use super::*;
274
275    #[test]
276    fn from_str() -> Result<(), ParseIntError> {
277        assert_eq!(Integer::from_str("0")?.to_string(), "0");
278        assert_eq!(Integer::from_str("-0")?.to_string(), "0");
279        assert_eq!(Integer::from_str("123")?.to_string(), "123");
280        assert_eq!(Integer::from_str("-123")?.to_string(), "-123");
281        Integer::from_str("123456789123456789123456789123456789123456789").unwrap_err();
282        Ok(())
283    }
284
285    #[test]
286    fn from_float() -> Result<(), ParseIntError> {
287        assert_eq!(
288            Integer::try_from(Float::from(0.)).ok(),
289            Some(Integer::from_str("0")?)
290        );
291        assert_eq!(
292            Integer::try_from(Float::from(-0.)).ok(),
293            Some(Integer::from_str("0")?)
294        );
295        assert_eq!(
296            Integer::try_from(Float::from(-123.1)).ok(),
297            Some(Integer::from_str("-123")?)
298        );
299        Integer::try_from(Float::from(f32::NAN)).unwrap_err();
300        Integer::try_from(Float::from(f32::INFINITY)).unwrap_err();
301        Integer::try_from(Float::from(f32::NEG_INFINITY)).unwrap_err();
302        Integer::try_from(Float::from(f32::MIN)).unwrap_err();
303        Integer::try_from(Float::from(f32::MAX)).unwrap_err();
304        assert!(
305            Integer::try_from(Float::from(1_672_507_300_000.))
306                .unwrap()
307                .checked_sub(Integer::from_str("1672507300000")?)
308                .unwrap()
309                .checked_abs()
310                .unwrap()
311                < Integer::from(1_000_000)
312        );
313        Ok(())
314    }
315
316    #[test]
317    fn from_double() -> Result<(), ParseIntError> {
318        assert_eq!(
319            Integer::try_from(Double::from(0.0)).ok(),
320            Some(Integer::from_str("0")?)
321        );
322        assert_eq!(
323            Integer::try_from(Double::from(-0.0)).ok(),
324            Some(Integer::from_str("0")?)
325        );
326        assert_eq!(
327            Integer::try_from(Double::from(-123.1)).ok(),
328            Some(Integer::from_str("-123")?)
329        );
330        assert!(
331            Integer::try_from(Double::from(1_672_507_300_000.))
332                .unwrap()
333                .checked_sub(Integer::from_str("1672507300000").unwrap())
334                .unwrap()
335                .checked_abs()
336                .unwrap()
337                < Integer::from(10)
338        );
339        Integer::try_from(Double::from(f64::NAN)).unwrap_err();
340        Integer::try_from(Double::from(f64::INFINITY)).unwrap_err();
341        Integer::try_from(Double::from(f64::NEG_INFINITY)).unwrap_err();
342        Integer::try_from(Double::from(f64::MIN)).unwrap_err();
343        Integer::try_from(Double::from(f64::MAX)).unwrap_err();
344        Ok(())
345    }
346
347    #[test]
348    fn from_decimal() -> Result<(), ParseIntError> {
349        assert_eq!(
350            Integer::try_from(Decimal::from(0)).ok(),
351            Some(Integer::from_str("0")?)
352        );
353        assert_eq!(
354            Integer::try_from(Decimal::from_str("-123.1").unwrap()).ok(),
355            Some(Integer::from_str("-123")?)
356        );
357        Integer::try_from(Decimal::MIN).unwrap_err();
358        Integer::try_from(Decimal::MAX).unwrap_err();
359        Ok(())
360    }
361
362    #[test]
363    fn add() {
364        assert_eq!(
365            Integer::MIN.checked_add(1),
366            Some(Integer::from(i64::MIN + 1))
367        );
368        assert_eq!(Integer::MAX.checked_add(1), None);
369    }
370
371    #[test]
372    fn sub() {
373        assert_eq!(Integer::MIN.checked_sub(1), None);
374        assert_eq!(
375            Integer::MAX.checked_sub(1),
376            Some(Integer::from(i64::MAX - 1))
377        );
378    }
379
380    #[test]
381    fn mul() {
382        assert_eq!(Integer::MIN.checked_mul(2), None);
383        assert_eq!(Integer::MAX.checked_mul(2), None);
384    }
385
386    #[test]
387    fn div() {
388        assert_eq!(Integer::from(1).checked_div(0), None);
389    }
390
391    #[test]
392    fn rem() {
393        assert_eq!(Integer::from(10).checked_rem(3), Some(Integer::from(1)));
394        assert_eq!(Integer::from(6).checked_rem(-2), Some(Integer::from(0)));
395        assert_eq!(Integer::from(1).checked_rem(0), None);
396    }
397}