plist/
integer.rs

1use std::{fmt, num::ParseIntError};
2
3/// An integer that can be represented by either an `i64` or a `u64`.
4#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
5pub struct Integer {
6    value: i128,
7}
8
9impl Integer {
10    /// Returns the value as an `i64` if it can be represented by that type.
11    pub fn as_signed(self) -> Option<i64> {
12        if self.value >= i128::from(i64::min_value()) && self.value <= i128::from(i64::max_value())
13        {
14            Some(self.value as i64)
15        } else {
16            None
17        }
18    }
19
20    /// Returns the value as a `u64` if it can be represented by that type.
21    pub fn as_unsigned(self) -> Option<u64> {
22        if self.value >= 0 && self.value <= i128::from(u64::max_value()) {
23            Some(self.value as u64)
24        } else {
25            None
26        }
27    }
28
29    pub(crate) fn from_str(s: &str) -> Result<Self, ParseIntError> {
30        if s.starts_with("0x") {
31            // NetBSD dialect adds the `0x` numeric objects,
32            // which are always unsigned.
33            // See the `PROP_NUMBER(3)` man page
34            let s = s.trim_start_matches("0x");
35            u64::from_str_radix(s, 16).map(Into::into)
36        } else {
37            // Match Apple's implementation in CFPropertyList.h - always try to parse as an i64 first.
38            // TODO: Use IntErrorKind once stable and retry parsing on overflow only.
39            Ok(match s.parse::<i64>() {
40                Ok(v) => v.into(),
41                Err(_) => s.parse::<u64>()?.into(),
42            })
43        }
44    }
45}
46
47impl fmt::Debug for Integer {
48    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49        self.value.fmt(f)
50    }
51}
52
53impl fmt::Display for Integer {
54    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55        self.value.fmt(f)
56    }
57}
58
59impl From<i64> for Integer {
60    fn from(value: i64) -> Integer {
61        Integer {
62            value: value.into(),
63        }
64    }
65}
66
67impl From<i32> for Integer {
68    fn from(value: i32) -> Integer {
69        Integer {
70            value: value.into(),
71        }
72    }
73}
74
75impl From<i16> for Integer {
76    fn from(value: i16) -> Integer {
77        Integer {
78            value: value.into(),
79        }
80    }
81}
82
83impl From<i8> for Integer {
84    fn from(value: i8) -> Integer {
85        Integer {
86            value: value.into(),
87        }
88    }
89}
90
91impl From<u64> for Integer {
92    fn from(value: u64) -> Integer {
93        Integer {
94            value: value.into(),
95        }
96    }
97}
98
99impl From<u32> for Integer {
100    fn from(value: u32) -> Integer {
101        Integer {
102            value: value.into(),
103        }
104    }
105}
106
107impl From<u16> for Integer {
108    fn from(value: u16) -> Integer {
109        Integer {
110            value: value.into(),
111        }
112    }
113}
114
115impl From<u8> for Integer {
116    fn from(value: u8) -> Integer {
117        Integer {
118            value: value.into(),
119        }
120    }
121}
122
123#[cfg(feature = "serde")]
124pub mod serde_impls {
125    use serde::{
126        de::{Deserialize, Deserializer, Error, Visitor},
127        ser::{Serialize, Serializer},
128    };
129    use std::fmt;
130
131    use crate::Integer;
132
133    impl Serialize for Integer {
134        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
135        where
136            S: Serializer,
137        {
138            if let Some(v) = self.as_unsigned() {
139                serializer.serialize_u64(v)
140            } else if let Some(v) = self.as_signed() {
141                serializer.serialize_i64(v)
142            } else {
143                unreachable!();
144            }
145        }
146    }
147
148    struct IntegerVisitor;
149
150    impl<'de> Visitor<'de> for IntegerVisitor {
151        type Value = Integer;
152
153        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
154            formatter.write_str("a plist integer")
155        }
156
157        fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
158        where
159            E: Error,
160        {
161            Ok(Integer::from(v))
162        }
163
164        fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
165        where
166            E: Error,
167        {
168            Ok(Integer::from(v))
169        }
170    }
171
172    impl<'de> Deserialize<'de> for Integer {
173        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
174        where
175            D: Deserializer<'de>,
176        {
177            deserializer.deserialize_any(IntegerVisitor)
178        }
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::Integer;
185
186    #[test]
187    fn from_str_limits() {
188        assert_eq!(Integer::from_str("-1"), Ok((-1).into()));
189        assert_eq!(Integer::from_str("0"), Ok(0.into()));
190        assert_eq!(Integer::from_str("1"), Ok(1.into()));
191        assert_eq!(
192            Integer::from_str("-9223372036854775808"),
193            Ok((-9223372036854775808i64).into())
194        );
195        assert!(Integer::from_str("-9223372036854775809").is_err());
196        assert_eq!(
197            Integer::from_str("18446744073709551615"),
198            Ok(18446744073709551615u64.into())
199        );
200        assert!(Integer::from_str("18446744073709551616").is_err());
201    }
202}