diff options
Diffstat (limited to 'third_party/rust/plist/src/integer.rs')
-rw-r--r-- | third_party/rust/plist/src/integer.rs | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/third_party/rust/plist/src/integer.rs b/third_party/rust/plist/src/integer.rs new file mode 100644 index 0000000000..4e69ec1b2b --- /dev/null +++ b/third_party/rust/plist/src/integer.rs @@ -0,0 +1,202 @@ +use std::{fmt, num::ParseIntError}; + +/// An integer that can be represented by either an `i64` or a `u64`. +#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Integer { + value: i128, +} + +impl Integer { + /// Returns the value as an `i64` if it can be represented by that type. + pub fn as_signed(self) -> Option<i64> { + if self.value >= i128::from(i64::min_value()) && self.value <= i128::from(i64::max_value()) + { + Some(self.value as i64) + } else { + None + } + } + + /// Returns the value as a `u64` if it can be represented by that type. + pub fn as_unsigned(self) -> Option<u64> { + if self.value >= 0 && self.value <= i128::from(u64::max_value()) { + Some(self.value as u64) + } else { + None + } + } + + pub(crate) fn from_str(s: &str) -> Result<Self, ParseIntError> { + if s.starts_with("0x") { + // NetBSD dialect adds the `0x` numeric objects, + // which are always unsigned. + // See the `PROP_NUMBER(3)` man page + let s = s.trim_start_matches("0x"); + u64::from_str_radix(s, 16).map(Into::into) + } else { + // Match Apple's implementation in CFPropertyList.h - always try to parse as an i64 first. + // TODO: Use IntErrorKind once stable and retry parsing on overflow only. + Ok(match s.parse::<i64>() { + Ok(v) => v.into(), + Err(_) => s.parse::<u64>()?.into(), + }) + } + } +} + +impl fmt::Debug for Integer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.value.fmt(f) + } +} + +impl fmt::Display for Integer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.value.fmt(f) + } +} + +impl From<i64> for Integer { + fn from(value: i64) -> Integer { + Integer { + value: value.into(), + } + } +} + +impl From<i32> for Integer { + fn from(value: i32) -> Integer { + Integer { + value: value.into(), + } + } +} + +impl From<i16> for Integer { + fn from(value: i16) -> Integer { + Integer { + value: value.into(), + } + } +} + +impl From<i8> for Integer { + fn from(value: i8) -> Integer { + Integer { + value: value.into(), + } + } +} + +impl From<u64> for Integer { + fn from(value: u64) -> Integer { + Integer { + value: value.into(), + } + } +} + +impl From<u32> for Integer { + fn from(value: u32) -> Integer { + Integer { + value: value.into(), + } + } +} + +impl From<u16> for Integer { + fn from(value: u16) -> Integer { + Integer { + value: value.into(), + } + } +} + +impl From<u8> for Integer { + fn from(value: u8) -> Integer { + Integer { + value: value.into(), + } + } +} + +#[cfg(feature = "serde")] +pub mod serde_impls { + use serde::{ + de::{Deserialize, Deserializer, Error, Visitor}, + ser::{Serialize, Serializer}, + }; + use std::fmt; + + use crate::Integer; + + impl Serialize for Integer { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + if let Some(v) = self.as_unsigned() { + serializer.serialize_u64(v) + } else if let Some(v) = self.as_signed() { + serializer.serialize_i64(v) + } else { + unreachable!(); + } + } + } + + struct IntegerVisitor; + + impl<'de> Visitor<'de> for IntegerVisitor { + type Value = Integer; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a plist integer") + } + + fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> + where + E: Error, + { + Ok(Integer::from(v)) + } + + fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> + where + E: Error, + { + Ok(Integer::from(v)) + } + } + + impl<'de> Deserialize<'de> for Integer { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(IntegerVisitor) + } + } +} + +#[cfg(test)] +mod tests { + use super::Integer; + + #[test] + fn from_str_limits() { + assert_eq!(Integer::from_str("-1"), Ok((-1).into())); + assert_eq!(Integer::from_str("0"), Ok(0.into())); + assert_eq!(Integer::from_str("1"), Ok(1.into())); + assert_eq!( + Integer::from_str("-9223372036854775808"), + Ok((-9223372036854775808i64).into()) + ); + assert!(Integer::from_str("-9223372036854775809").is_err()); + assert_eq!( + Integer::from_str("18446744073709551615"), + Ok(18446744073709551615u64.into()) + ); + assert!(Integer::from_str("18446744073709551616").is_err()); + } +} |