diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c6e79e..cb59ec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,15 @@ * Breaking: Allow values larger than 32 bit for `int!` and `uint!` macros This means that `i32`-suffixed and `u32`-suffixed literals are no longer accepcted - * The old macros can be replaced like follows: `int!(42)` -> `Int::from(42_i32)` etc. + * The old macros can be replaced like follows: `int!(42)` -> `Int::from(42_i32)` etc. * The minimum supported rust version is raised to 1.46. * The `int!` and `uint!` macros now support arbitrary const expressions, not just literals * `Int::new` and `UInt::new` are now const * Breaking: Serialization of `Int` and `UInt` now call the serialization of `i64` and `u64` directly instead of serializing them as newtype structs, emulating `#[serde(transparent)]`. This doesn't make a difference for `serde_json` for example, but it could make a difference for other serializers +* Breaking: Remove the `float_deserialize` feature and replace it with the `(U)Int::deserialize_via_float` methods that + can be used with serde's `deserialize_with` attribute. # 0.2.2 @@ -32,7 +34,7 @@ # 0.1.9 * Add a new Cargo feature: `lax_deserialize` - * See the crate documentation or [README.md](README.md) for what it does. + * See the crate documentation or [README.md](README.md) for what it does. # 0.1.8 diff --git a/Cargo.toml b/Cargo.toml index fb7f81b..c424e21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ optional = true default-features = false [features] -float_deserialize = ["serde"] default = ["std"] "rust_1.81" = [] std = [] diff --git a/README.md b/README.md index efbd713..f2a13fc 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ implementations of `std::error::Error` for `ParseIntError` and without the `std` feature. Deserialization can be routed through `f64` instead of `u64` with the -`float_deserialize` feature. This will still not deserialize numbers with a +`(U)Int::deserialize_via_float` methods. This will still not deserialize numbers with a non-zero fractional component. Please be aware that `serde_json` doesn't losslessly parse large floats with a fractional part by default (even if the fractional part is `.0`). To fix that, enable its diff --git a/src/int.rs b/src/int.rs index 4897393..133f571 100644 --- a/src/int.rs +++ b/src/int.rs @@ -433,6 +433,44 @@ impl Int { Self::new_saturating(self.0.saturating_pow(exp)) } + /// Deserialization function for use with `#[serde(deserialize_with = ...)]` that performs + /// deserialization through `f64` instead of `i64`. + /// This allows deserializing from numbers with a fractional component like `.0`. + /// + /// Note, however, that this will not accept non-zero fractional components like `.1`. + /// + /// # Example + /// ```rs + /// use serde::Deserialize; + /// use js_int::Int; + /// + /// #[derive(Deserialize)] + /// struct Point { + /// #[serde(deserialize_with = "Int::deserialize_via_float")] + /// x: Int; + /// #[serde(deserialize_with = "Int::deserialize_via_float")] + /// y: Int; + /// } + #[cfg(feature = "serde")] + pub fn deserialize_via_float<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + const EXPECTING: &str = + "a number between -2^53 + 1 and 2^53 - 1 without fractional component"; + + let val = f64::deserialize(deserializer)?; + + if val > MAX_SAFE_INT as f64 + || val < MIN_SAFE_INT as f64 + || !super::is_acceptable_float(val) + { + Err(D::Error::invalid_value(Unexpected::Float(val), &EXPECTING)) + } else { + Ok(Self(val as i64)) + } + } + // TODO: wrapping_* methods, overflowing_* methods } @@ -576,34 +614,14 @@ impl<'de> Deserialize<'de> for Int { where D: Deserializer<'de>, { - #[cfg(not(feature = "float_deserialize"))] - { - let val = i64::deserialize(deserializer)?; - - Self::new(val).ok_or_else(|| { - D::Error::invalid_value( - Unexpected::Signed(val), - &"an integer between -2^53 + 1 and 2^53 - 1", - ) - }) - } - - #[cfg(feature = "float_deserialize")] - { - const EXPECTING: &str = - "a number between -2^53 + 1 and 2^53 - 1 without fractional component"; + let val = i64::deserialize(deserializer)?; - let val = f64::deserialize(deserializer)?; - - if val > MAX_SAFE_INT as f64 - || val < MIN_SAFE_INT as f64 - || !super::is_acceptable_float(val) - { - Err(D::Error::invalid_value(Unexpected::Float(val), &EXPECTING)) - } else { - Ok(Self(val as i64)) - } - } + Self::new(val).ok_or_else(|| { + D::Error::invalid_value( + Unexpected::Signed(val), + &"an integer between -2^53 + 1 and 2^53 - 1", + ) + }) } } diff --git a/src/lib.rs b/src/lib.rs index 7d0e475..e6122e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,9 +28,6 @@ //! * `serde`: Serialization and deserialization support via [serde](https://serde.rs). Disabled by //! default. You can use `js_int` + `serde` in `#![no_std]` contexts if you use //! `default-features = false` for both. -//! * `float_deserialize`: Deserialize via `f64`, not via `u64`. If the input has a fraction, -//! deserialization will fail. -//! * `lax_deserialize`: Like `float_deserialize`, but if the input has a fraction, it is //! deserialized with the fractional part discarded. //! Please be aware that `serde_json` doesn't losslessly parse large floats with a fractional part //! by default (even if the fractional part is `.0`). To fix that, enable its `float_roundtrip` @@ -59,7 +56,7 @@ pub use self::{ uint::{UInt, MAX_SAFE_UINT}, }; -#[cfg(feature = "float_deserialize")] +#[cfg(feature = "serde")] #[inline(always)] pub(crate) fn is_acceptable_float(float: f64) -> bool { !float.is_nan() && float.fract() == 0.0 diff --git a/src/uint.rs b/src/uint.rs index 278f72b..44582ec 100644 --- a/src/uint.rs +++ b/src/uint.rs @@ -414,6 +414,41 @@ impl UInt { Self::new_saturating(self.0.saturating_pow(exp)) } + /// Deserialization function for use with `#[serde(deserialize_with = ...)]` that performs + /// deserialization through `f64` instead of `u64`. + /// This allows deserializing from numbers with a fractional component like `.0`. + /// + /// Note, however, that this will not accept non-zero fractional components like `.1`. + /// + /// # Example + /// ```rs + /// use serde::Deserialize; + /// use js_int::UInt; + /// + /// #[derive(Deserialize)] + /// struct Point { + /// #[serde(deserialize_with = "UInt::deserialize_via_float")] + /// x: UInt; + /// #[serde(deserialize_with = "UInt::deserialize_via_float")] + /// y: UInt; + /// } + /// ``` + #[cfg(feature = "serde")] + pub fn deserialize_via_float<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + const EXPECTING: &str = "a number between 0 and 2^53 - 1 without fractional component"; + + let val = f64::deserialize(deserializer)?; + + if val < 0.0 || val > MAX_SAFE_UINT as f64 || !super::is_acceptable_float(val) { + Err(D::Error::invalid_value(Unexpected::Float(val), &EXPECTING)) + } else { + Ok(Self(val as u64)) + } + } + // TODO: wrapping_* methods, overflowing_* methods } @@ -571,30 +606,11 @@ impl<'de> Deserialize<'de> for UInt { where D: Deserializer<'de>, { - #[cfg(not(feature = "float_deserialize"))] - { - let val = u64::deserialize(deserializer)?; - - Self::new(val).ok_or_else(|| { - D::Error::invalid_value( - Unexpected::Unsigned(val), - &"an integer between 0 and 2^53 - 1", - ) - }) - } - - #[cfg(feature = "float_deserialize")] - { - const EXPECTING: &str = "a number between 0 and 2^53 - 1 without fractional component"; + let val = u64::deserialize(deserializer)?; - let val = f64::deserialize(deserializer)?; - - if val < 0.0 || val > MAX_SAFE_UINT as f64 || !super::is_acceptable_float(val) { - Err(D::Error::invalid_value(Unexpected::Float(val), &EXPECTING)) - } else { - Ok(Self(val as u64)) - } - } + Self::new(val).ok_or_else(|| { + D::Error::invalid_value(Unexpected::Unsigned(val), &"an integer between 0 and 2^53 - 1") + }) } } diff --git a/tests/int.rs b/tests/int.rs index 9d1b4bd..6ac2935 100644 --- a/tests/int.rs +++ b/tests/int.rs @@ -32,7 +32,6 @@ fn deserialize() { } #[test] -#[cfg_attr(feature = "float_deserialize", ignore)] fn dont_deserialize_integral_float() { assert!(deserialize_from(-10.0).is_err()); assert!(deserialize_from(-0.0).is_err()); @@ -49,22 +48,27 @@ fn dont_deserialize_fractional_float() { } #[test] -#[cfg_attr(not(feature = "float_deserialize"), ignore)] fn deserialize_integral_float() { - assert_eq!(deserialize_from(-10.0).unwrap(), int!(-10)); - assert_eq!(deserialize_from(-0.0).unwrap(), int!(0)); - assert_eq!(deserialize_from(1.0).unwrap(), int!(1)); - assert_eq!(deserialize_from(9007199254740991.0).unwrap(), Int::MAX); - assert!(deserialize_from(9007199254740992.0).is_err()); + assert_eq!(deserialize_via_float(-10.0).unwrap(), int!(-10)); + assert_eq!(deserialize_via_float(-0.0).unwrap(), int!(0)); + assert_eq!(deserialize_via_float(1.0).unwrap(), int!(1)); + assert_eq!(deserialize_via_float(9007199254740991.0).unwrap(), Int::MAX); + assert!(deserialize_via_float(9007199254740992.0).is_err()); // NOTE: This still ends up as integral because the .49 exceeds the representable range of f64 assert_eq!( - deserialize_from(9007199254740991.49).unwrap(), + deserialize_via_float(9007199254740991.49).unwrap(), Int::try_from(9007199254740991i64).unwrap() ); - assert!(deserialize_from(f64::NAN).is_err()); - assert!(deserialize_from(f64::INFINITY).is_err()); - assert!(deserialize_from(f64::NEG_INFINITY).is_err()); + assert!(deserialize_via_float(f64::NAN).is_err()); + assert!(deserialize_via_float(f64::INFINITY).is_err()); + assert!(deserialize_via_float(f64::NEG_INFINITY).is_err()); + + fn deserialize_via_float<'de, Value: IntoDeserializer<'de>>( + value: Value, + ) -> Result { + Int::deserialize_via_float(value.into_deserializer()) + } } fn deserialize_from<'de, Value: IntoDeserializer<'de>>( diff --git a/tests/uint.rs b/tests/uint.rs index 7432adf..467e8a7 100644 --- a/tests/uint.rs +++ b/tests/uint.rs @@ -29,7 +29,6 @@ fn deserialize() { } #[test] -#[cfg_attr(feature = "float_deserialize", ignore)] fn dont_deserialize_integral_float() { assert!(deserialize_from(1.0).is_err()); assert!(deserialize_from(9007199254740991.0).is_err()); @@ -43,20 +42,25 @@ fn dont_deserialize_fractional_float() { } #[test] -#[cfg_attr(not(feature = "float_deserialize"), ignore)] fn deserialize_integral_float() { - assert_eq!(deserialize_from(1.0).unwrap(), uint!(1)); - assert_eq!(deserialize_from(9007199254740991.0).unwrap(), UInt::MAX); - assert!(deserialize_from(9007199254740992.0).is_err()); + assert_eq!(deserialize_via_float(1.0).unwrap(), uint!(1)); + assert_eq!(deserialize_via_float(9007199254740991.0).unwrap(), UInt::MAX); + assert!(deserialize_via_float(9007199254740992.0).is_err()); // NOTE: This still ends up as integral because the .49 exceeds the representable range of f64 assert_eq!( - deserialize_from(9007199254740991.49).unwrap(), + deserialize_via_float(9007199254740991.49).unwrap(), UInt::try_from(9007199254740991i64).unwrap() ); - assert!(deserialize_from(f64::NAN).is_err()); - assert!(deserialize_from(f64::INFINITY).is_err()); - assert!(deserialize_from(f64::NEG_INFINITY).is_err()); + assert!(deserialize_via_float(f64::NAN).is_err()); + assert!(deserialize_via_float(f64::INFINITY).is_err()); + assert!(deserialize_via_float(f64::NEG_INFINITY).is_err()); + + fn deserialize_via_float<'de, Value: IntoDeserializer<'de>>( + value: Value, + ) -> Result { + UInt::deserialize_via_float(value.into_deserializer()) + } } fn deserialize_from<'de, Value: IntoDeserializer<'de>>(