diff --git a/src/extjson/models.rs b/src/extjson/models.rs index d7107d4e..57c3c978 100644 --- a/src/extjson/models.rs +++ b/src/extjson/models.rs @@ -6,8 +6,9 @@ use serde::{ de::{Error as _, Unexpected}, Deserialize, Serialize, + Serializer, }; -use std::borrow::Cow; +use std::{borrow::Cow, result::Result as StdResult}; use crate::{ base64, @@ -217,13 +218,18 @@ pub(crate) struct Timestamp { body: TimestampBody, } +/// Serializes a u32 as an i64. +fn serialize_u32_as_i64(val: &u32, serializer: S) -> StdResult { + serializer.serialize_i64(*val as i64) +} + #[derive(Serialize, Deserialize, Debug)] #[serde(deny_unknown_fields)] pub(crate) struct TimestampBody { - #[serde(serialize_with = "crate::serde_helpers::serialize_u32_as_i64")] + #[serde(serialize_with = "serialize_u32_as_i64")] pub(crate) t: u32, - #[serde(serialize_with = "crate::serde_helpers::serialize_u32_as_i64")] + #[serde(serialize_with = "serialize_u32_as_i64")] pub(crate) i: u32, } diff --git a/src/serde_helpers.rs b/src/serde_helpers.rs index ded62da6..017c18d5 100644 --- a/src/serde_helpers.rs +++ b/src/serde_helpers.rs @@ -1,27 +1,11 @@ //! Collection of helper functions for serializing to and deserializing from BSON using Serde -use serde::{de::Visitor, ser, Deserialize, Serialize, Serializer}; +use serde::{de::Visitor, Deserialize, Serialize, Serializer}; use std::{ - convert::TryFrom, marker::PhantomData, ops::{Deref, DerefMut}, result::Result, }; -#[doc(inline)] -pub use timestamp_as_u32::{ - deserialize as deserialize_timestamp_from_u32, - serialize as serialize_timestamp_as_u32, -}; -#[doc(inline)] -pub use u32_as_f64::{deserialize as deserialize_u32_from_f64, serialize as serialize_u32_as_f64}; -#[doc(inline)] -pub use u32_as_timestamp::{ - deserialize as deserialize_u32_from_timestamp, - serialize as serialize_u32_as_timestamp, -}; -#[doc(inline)] -pub use u64_as_f64::{deserialize as deserialize_u64_from_f64, serialize as serialize_u64_as_f64}; - #[cfg(feature = "uuid-1")] #[doc(inline)] pub use uuid_1_as_binary::{ @@ -47,35 +31,6 @@ pub use uuid_1_as_python_legacy_binary::{ serialize as serialize_uuid_1_as_python_legacy_binary, }; -/// Attempts to serialize a u32 as an i32. Errors if an exact conversion is not possible. -pub fn serialize_u32_as_i32(val: &u32, serializer: S) -> Result { - match i32::try_from(*val) { - Ok(val) => serializer.serialize_i32(val), - Err(_) => Err(ser::Error::custom(format!("cannot convert {} to i32", val))), - } -} - -/// Serializes a u32 as an i64. -pub fn serialize_u32_as_i64(val: &u32, serializer: S) -> Result { - serializer.serialize_i64(*val as i64) -} - -/// Attempts to serialize a u64 as an i32. Errors if an exact conversion is not possible. -pub fn serialize_u64_as_i32(val: &u64, serializer: S) -> Result { - match i32::try_from(*val) { - Ok(val) => serializer.serialize_i32(val), - Err(_) => Err(ser::Error::custom(format!("cannot convert {} to i32", val))), - } -} - -/// Attempts to serialize a u64 as an i64. Errors if an exact conversion is not possible. -pub fn serialize_u64_as_i64(val: &u64, serializer: S) -> Result { - match i64::try_from(*val) { - Ok(val) => serializer.serialize_i64(val), - Err(_) => Err(ser::Error::custom(format!("cannot convert {} to i64", val))), - } -} - #[cfg(feature = "serde_with-3")] pub mod object_id { use crate::{macros::serde_conv_doc, oid::ObjectId}; @@ -138,87 +93,6 @@ pub mod object_id { ); } -/// Contains functions to serialize a u32 as an f64 (BSON double) and deserialize a -/// u32 from an f64 (BSON double). -/// -/// ```rust -/// # use serde::{Serialize, Deserialize}; -/// # use bson::serde_helpers::u32_as_f64; -/// #[derive(Serialize, Deserialize)] -/// struct FileInfo { -/// #[serde(with = "u32_as_f64")] -/// pub size_bytes: u32, -/// } -/// ``` -pub mod u32_as_f64 { - use serde::{de, Deserialize, Deserializer, Serializer}; - - /// Deserializes a u32 from an f64 (BSON double). Errors if an exact conversion is not possible. - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let f = f64::deserialize(deserializer)?; - if (f - f as u32 as f64).abs() <= f64::EPSILON { - Ok(f as u32) - } else { - Err(de::Error::custom(format!( - "cannot convert f64 (BSON double) {} to u32", - f - ))) - } - } - - /// Serializes a u32 as an f64 (BSON double). - pub fn serialize(val: &u32, serializer: S) -> Result { - serializer.serialize_f64(*val as f64) - } -} - -/// Contains functions to serialize a u64 as an f64 (BSON double) and deserialize a -/// u64 from an f64 (BSON double). -/// -/// ```rust -/// # use serde::{Serialize, Deserialize}; -/// # use bson::serde_helpers::u64_as_f64; -/// #[derive(Serialize, Deserialize)] -/// struct FileInfo { -/// #[serde(with = "u64_as_f64")] -/// pub size_bytes: u64, -/// } -/// ``` -pub mod u64_as_f64 { - use serde::{de, ser, Deserialize, Deserializer, Serializer}; - - /// Deserializes a u64 from an f64 (BSON double). Errors if an exact conversion is not possible. - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let f = f64::deserialize(deserializer)?; - if (f - f as u64 as f64).abs() <= f64::EPSILON { - Ok(f as u64) - } else { - Err(de::Error::custom(format!( - "cannot convert f64 (BSON double) {} to u64", - f - ))) - } - } - - /// Serializes a u64 as an f64 (BSON double). Errors if an exact conversion is not possible. - pub fn serialize(val: &u64, serializer: S) -> Result { - if val < &u64::MAX && *val == *val as f64 as u64 { - serializer.serialize_f64(*val as f64) - } else { - Err(ser::Error::custom(format!( - "cannot convert u64 {} to f64 (BSON double)", - val - ))) - } - } -} - /// Type converters for serializing and deserializing [`crate::DateTime`] using /// [`serde_with::serde_as`]. /// @@ -380,6 +254,298 @@ pub mod datetime { ); } +/// Type converters for serializing and deserializing `crate::Timestamp` using +/// [`serde_with::serde_as`]. +/// +/// ## Available converters +/// - [`timestamp::AsU32`] — converts a [`crate::Timestamp`] to and from a `u32`. +/// - [`timestamp::FromU32`] — converts a `u32` to and from a [`crate::Timestamp`]. +#[cfg(feature = "serde_with-3")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde_with-3")))] +pub mod timestamp { + use crate::{macros::serde_conv_doc, Timestamp}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use serde_with::{DeserializeAs, SerializeAs}; + use std::result::Result; + + serde_conv_doc!( + /// Converts a [`Timestamp`] to and from a `u32`. + /// + /// The `u32` should represent seconds since the Unix epoch. + /// + /// Serialization errors if the Timestamp has a non-zero increment. + /// ```rust + /// # #[cfg(feature = "serde_with-3")] + /// # { + /// use bson::{serde_helpers::timestamp, Timestamp}; + /// use serde::{Serialize, Deserialize}; + /// use serde_with::serde_as; + /// #[serde_as] + /// #[derive(Serialize, Deserialize)] + /// struct Item { + /// #[serde_as(as = "timestamp::AsU32")] + /// pub timestamp: Timestamp, + /// } + /// # } + /// ``` + pub AsU32, + Timestamp, + |timestamp: &Timestamp| -> Result { + if timestamp.increment != 0 { + return Err(format!("Cannot convert Timestamp with a non-zero increment to u32: {:?}", timestamp)); + } + Ok(timestamp.time) + }, + |value: u32| -> Result { + Ok(Timestamp { time: value, increment: 0 }) + } + ); + + serde_conv_doc!( + /// Converts a `u32` to and from a [`Timestamp`]. + /// + /// The `u32` should represent seconds since the Unix epoch. + /// + /// Deserialization errors if the Timestamp has a non-zero increment. + /// ```rust + /// # #[cfg(feature = "serde_with-3")] + /// # { + /// use bson::serde_helpers::timestamp; + /// use serde::{Serialize, Deserialize}; + /// use serde_with::serde_as; + /// #[serde_as] + /// #[derive(Serialize, Deserialize)] + /// struct Event { + /// #[serde_as(as = "timestamp::FromU32")] + /// pub time: u32, + /// } + /// # } + /// ``` + pub FromU32, + u32, + |value: &u32| -> Result { + Ok(Timestamp { time: *value, increment: 0 }) + }, + |timestamp: Timestamp| -> Result { + if timestamp.increment != 0 { + return Err(format!("Cannot convert Timestamp with a non-zero increment to u32: {:?}", timestamp)); + } + Ok(timestamp.time) + } + ); +} + +/// Type converters for serializing and deserializing `u32` using [`serde_with::serde_as`]. +/// +/// ## Available converters +/// - [`u32::AsF64`] — converts a `u32` to and from an `f64`. +/// - [`u32::AsI32`] — converts a `u32` to and from an `i32`. +/// - [`u32::AsI64`] — converts a `u32` to and from an `i64`. +#[cfg(feature = "serde_with-3")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde_with-3")))] +pub mod u32 { + use crate::macros::serde_conv_doc; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use serde_with::{DeserializeAs, SerializeAs}; + use std::result::Result; + + serde_conv_doc!( + /// Converts a `u32` to and from an `f64`. + /// + /// Deserialization errors if an exact conversion is not possible. + /// ```rust + /// # #[cfg(feature = "serde_with-3")] + /// # { + /// use bson::serde_helpers::u32; + /// use serde::{Serialize, Deserialize}; + /// use serde_with::serde_as; + /// #[serde_as] + /// #[derive(Serialize, Deserialize)] + /// struct FileInfo { + /// #[serde_as(as = "u32::AsF64")] + /// pub size_bytes: u32, + /// } + /// # } + /// ``` + pub AsF64, + u32, + |value: &u32| -> Result { + Ok(f64::from(*value)) + }, + |value: f64| -> Result { + if (value - value as u32 as f64).abs() <= f64::EPSILON { + Ok(value as u32) + } else { + Err(format!("Cannot convert f64 {} to u32", value)) + } + } + ); + + serde_conv_doc!( + /// Converts a `u32` to and from an `i32`. + /// + /// Errors if an exact conversion is not possible. + /// ```rust + /// # #[cfg(feature = "serde_with-3")] + /// # { + /// use bson::{serde_helpers::u32}; + /// use serde::{Serialize, Deserialize}; + /// use serde_with::serde_as; + /// #[serde_as] + /// #[derive(Serialize, Deserialize)] + /// struct Item { + /// #[serde_as(as = "u32::AsI32")] + /// pub value: u32, + /// } + /// # } + /// ``` + pub AsI32, + u32, + |value: &u32| -> Result { + i32::try_from(*value).map_err(|e| format!("Cannot convert u32 {} to i32: {}", value, e)) + }, + |value: i32| -> Result { + u32::try_from(value).map_err(|e| format!("Cannot convert i32 {} to u32: {}", value, e)) + } + ); + + serde_conv_doc!( + /// Converts a `u32` to and from an `i64`. + /// + /// Deserialization errors if an exact conversion is not possible. + /// ```rust + /// # #[cfg(feature = "serde_with-3")] + /// # { + /// use bson::{serde_helpers::u32}; + /// use serde::{Serialize, Deserialize}; + /// use serde_with::serde_as; + /// #[serde_as] + /// #[derive(Serialize, Deserialize)] + /// struct Item { + /// #[serde_as(as = "u32::AsI64")] + /// pub value: u32, + /// } + /// # } + /// ``` + pub AsI64, + u32, + |value: &u32| -> Result { + Ok(i64::from(*value)) + }, + |value: i64| -> Result { + u32::try_from(value).map_err(|e| format!("Cannot convert i64 {} to u32: {}", value, e)) + } + ); +} + +/// Type converters for serializing and deserializing `u64` using [`serde_with::serde_as`]. +/// +/// ## Available converters +/// - [`u64::AsF64`] — converts a `u64` to and from an `f64`. +/// - [`u64::AsI32`] — converts a `u64` to and from an `i32`. +/// - [`u64::AsI64`] — converts a `u64` to and from an `i64`. +#[cfg(feature = "serde_with-3")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde_with-3")))] +pub mod u64 { + use crate::macros::serde_conv_doc; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use serde_with::{DeserializeAs, SerializeAs}; + use std::result::Result; + + serde_conv_doc!( + /// Converts a `u64` to and from an `f64`. + /// + /// Errors if an exact conversion is not possible. + /// + /// ```rust + /// # #[cfg(feature = "serde_with-3")] + /// # { + /// use bson::serde_helpers::u64; + /// use serde::{Serialize, Deserialize}; + /// use serde_with::serde_as; + /// #[serde_as] + /// #[derive(Serialize, Deserialize)] + /// struct FileInfo { + /// #[serde_as(as = "u64::AsF64")] + /// pub size_bytes: u64, + /// } + /// # } + /// ``` + pub AsF64, + u64, + |value: &u64| -> Result { + if value < &u64::MAX && *value == *value as f64 as u64 { + Ok(*value as f64) + } else { + Err(format!("Cannot convert u64 {} to f64", value)) + } + }, + |value: f64| -> Result { + if (value - value as u64 as f64).abs() <= f64::EPSILON { + Ok(value as u64) + } else { + Err(format!("Cannot convert f64 {} to u64", value)) + } + } + ); + + serde_conv_doc!( + /// Converts a `u64` to and from an `i32`. + /// + /// Errors if an exact conversion is not possible. + /// ```rust + /// # #[cfg(feature = "serde_with-3")] + /// # { + /// use bson::{serde_helpers::u64}; + /// use serde::{Serialize, Deserialize}; + /// use serde_with::serde_as; + /// #[serde_as] + /// #[derive(Serialize, Deserialize)] + /// struct Item { + /// #[serde_as(as = "u64::AsI32")] + /// pub value: u64, + /// } + /// # } + /// ``` + pub AsI32, + u64, + |value: &u64| -> Result { + i32::try_from(*value).map_err(|e| format!("Cannot convert u64 {} to i32: {}", value, e)) + }, + |value: i32| -> Result { + u64::try_from(value).map_err(|e| format!("Cannot convert i32 {} to u64: {}", value, e)) + } + ); + + serde_conv_doc!( + /// Converts a `u64` to and from an `i64`. + /// + /// Errors if an exact conversion is not possible. + /// ```rust + /// # #[cfg(feature = "serde_with-3")] + /// # { + /// use bson::{serde_helpers::u64}; + /// use serde::{Serialize, Deserialize}; + /// use serde_with::serde_as; + /// #[serde_as] + /// #[derive(Serialize, Deserialize)] + /// struct Item { + /// #[serde_as(as = "u64::AsI64")] + /// pub value: u64, + /// } + /// # } + /// ``` + pub AsI64, + u64, + |value: &u64| -> Result { + i64::try_from(*value).map_err(|e| format!("Cannot convert u64 {} to i64: {}", value, e)) + }, + |value: i64| -> Result { + u64::try_from(value).map_err(|e| format!("Cannot convert i64 {} to u64: {}", value, e)) + } + ); +} + #[allow(unused_macros)] macro_rules! as_binary_mod { ($feat:meta, $uu:path) => { @@ -542,81 +708,6 @@ pub mod uuid_1_as_c_sharp_legacy_binary { ); } -/// Contains functions to serialize a u32 as a bson::Timestamp and deserialize a u32 from a -/// bson::Timestamp. The u32 should represent seconds since the Unix epoch. -/// -/// ```rust -/// # use serde::{Serialize, Deserialize}; -/// # use bson::serde_helpers::u32_as_timestamp; -/// #[derive(Serialize, Deserialize)] -/// struct Event { -/// #[serde(with = "u32_as_timestamp")] -/// pub time: u32, -/// } -/// ``` -pub mod u32_as_timestamp { - use crate::{Bson, Timestamp}; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - use std::result::Result; - - /// Serializes a u32 as a bson::Timestamp. - pub fn serialize(val: &u32, serializer: S) -> Result { - let timestamp = Bson::Timestamp(Timestamp { - time: *val, - increment: 0, - }); - timestamp.serialize(serializer) - } - - /// Deserializes a u32 from a bson::Timestamp. - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let timestamp = Timestamp::deserialize(deserializer)?; - Ok(timestamp.time) - } -} - -/// Contains functions to serialize a bson::Timestamp as a u32 and deserialize a bson::Timestamp -/// from a u32. The u32 should represent seconds since the Unix epoch. Serialization will return an -/// error if the Timestamp has a non-zero increment. -/// -/// ```rust -/// # use serde::{Serialize, Deserialize}; -/// # use bson::{serde_helpers::timestamp_as_u32, Timestamp}; -/// #[derive(Serialize, Deserialize)] -/// struct Item { -/// #[serde(with = "timestamp_as_u32")] -/// pub timestamp: Timestamp, -/// } -/// ``` -pub mod timestamp_as_u32 { - use crate::Timestamp; - use serde::{ser, Deserialize, Deserializer, Serializer}; - use std::result::Result; - - /// Serializes a bson::Timestamp as a u32. Returns an error if the conversion is lossy (i.e. the - /// Timestamp has a non-zero increment). - pub fn serialize(val: &Timestamp, serializer: S) -> Result { - if val.increment != 0 { - return Err(ser::Error::custom( - "Cannot convert Timestamp with a non-zero increment to u32", - )); - } - serializer.serialize_u32(val.time) - } - - /// Deserializes a bson::Timestamp from a u32. - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let time = u32::deserialize(deserializer)?; - Ok(Timestamp { time, increment: 0 }) - } -} - /// Wrapping a type in `HumanReadable` signals to the BSON serde integration that it and all /// recursively contained types should be serialized to and deserialized from their human-readable /// formats. diff --git a/src/tests/serde.rs b/src/tests/serde.rs index 1bbde432..b395ef6b 100644 --- a/src/tests/serde.rs +++ b/src/tests/serde.rs @@ -7,7 +7,7 @@ use crate::{ deserialize_from_document, doc, oid::ObjectId, - serde_helpers::{self, datetime, object_id, timestamp_as_u32, u32_as_timestamp}, + serde_helpers::{self, datetime, object_id, timestamp, u32, u64}, serialize_to_bson, serialize_to_document, spec::BinarySubtype, @@ -651,95 +651,6 @@ fn test_serialize_deserialize_unsigned_numbers() { assert!(doc_result.is_err()); } -#[test] -fn test_unsigned_helpers() { - let _guard = LOCK.run_concurrently(); - - #[derive(Serialize)] - struct A { - #[serde(serialize_with = "serde_helpers::serialize_u32_as_i32")] - num_1: u32, - #[serde(serialize_with = "serde_helpers::serialize_u64_as_i32")] - num_2: u64, - } - - let a = A { num_1: 1, num_2: 2 }; - let doc = serialize_to_document(&a).unwrap(); - assert!(doc.get_i32("num_1").unwrap() == 1); - assert!(doc.get_i32("num_2").unwrap() == 2); - - let a = A { - num_1: u32::MAX, - num_2: 1, - }; - let doc_result = serialize_to_document(&a); - assert!(doc_result.is_err()); - - let a = A { - num_1: 1, - num_2: u64::MAX, - }; - let doc_result = serialize_to_document(&a); - assert!(doc_result.is_err()); - - #[derive(Serialize)] - struct B { - #[serde(serialize_with = "serde_helpers::serialize_u32_as_i64")] - num_1: u32, - #[serde(serialize_with = "serde_helpers::serialize_u64_as_i64")] - num_2: u64, - } - - let b = B { - num_1: u32::MAX, - num_2: i64::MAX as u64, - }; - let doc = serialize_to_document(&b).unwrap(); - assert!(doc.get_i64("num_1").unwrap() == u32::MAX as i64); - assert!(doc.get_i64("num_2").unwrap() == i64::MAX); - - let b = B { - num_1: 1, - num_2: i64::MAX as u64 + 1, - }; - let doc_result = serialize_to_document(&b); - assert!(doc_result.is_err()); - - #[derive(Deserialize, Serialize, Debug, PartialEq)] - struct F { - #[serde(with = "serde_helpers::u32_as_f64")] - num_1: u32, - #[serde(with = "serde_helpers::u64_as_f64")] - num_2: u64, - } - - let f = F { - num_1: 101, - num_2: 12345, - }; - let doc = serialize_to_document(&f).unwrap(); - assert!((doc.get_f64("num_1").unwrap() - 101.0).abs() < f64::EPSILON); - assert!((doc.get_f64("num_2").unwrap() - 12345.0).abs() < f64::EPSILON); - - let back: F = deserialize_from_document(doc).unwrap(); - assert_eq!(back, f); - - let f = F { - num_1: 1, - // f64 cannot represent many large integers exactly, u64::MAX included - num_2: u64::MAX, - }; - let doc_result = serialize_to_document(&f); - assert!(doc_result.is_err()); - - let f = F { - num_1: 1, - num_2: u64::MAX - 255, - }; - let doc_result = serialize_to_document(&f); - assert!(doc_result.is_err()); -} - #[test] #[cfg(feature = "serde_with-3")] fn test_datetime_rfc3339_string_helpers() { @@ -1120,6 +1031,755 @@ fn test_datetime_time03_offset_datetime_helper() { ); } +#[test] +#[cfg(feature = "serde_with-3")] +fn test_timestamp_u32_helpers() { + let _guard = LOCK.run_concurrently(); + + #[serde_as] + #[derive(Deserialize, Serialize, Debug, PartialEq)] + struct A { + #[serde_as(as = "timestamp::AsU32")] + pub timestamp: Timestamp, + + #[serde_as(as = "Option")] + pub timestamp_optional_none: Option, + + #[serde_as(as = "Option")] + pub timestamp_optional_some: Option, + + #[serde_as(as = "Vec")] + pub timestamp_vector: Vec, + } + + let time = 12345; + let timestamp = Timestamp { time, increment: 0 }; + let a = A { + timestamp, + timestamp_optional_none: None, + timestamp_optional_some: Some(timestamp), + timestamp_vector: vec![timestamp], + }; + + // Serialize the struct to BSON + let doc = serialize_to_document(&a).unwrap(); + + // Validate serialized data + assert_eq!( + doc.get("timestamp").unwrap(), + &Bson::Int64(time as i64), + "Expected serialized time to match the original." + ); + + assert_eq!( + doc.get("timestamp_optional_none"), + Some(&Bson::Null), + "Expected serialized timestamp_optional_none to be None." + ); + + assert_eq!( + doc.get("timestamp_optional_some"), + Some(&Bson::Int64(time as i64)), + "Expected serialized timestamp_optional_some to match original time." + ); + + let timestamp_vector = doc + .get_array("timestamp_vector") + .expect("Expected serialized timestamp_vector to be a BSON array."); + let expected_timestamp_vector: Vec = vec![Bson::Int64(time as i64)]; + assert_eq!( + timestamp_vector, &expected_timestamp_vector, + "Expected each serialized element in timestamp_vector to match the original." + ); + + // Validate deserialized data + let a_deserialized: A = deserialize_from_document(doc).unwrap(); + assert_eq!( + a_deserialized, a, + "Deserialized struct does not match original." + ); + + // Validate serializing error case with an invalid Timestamp + let invalid_timestamp_for_serializing = Timestamp { + time: 0, + increment: 2, + }; + let bad_a: A = A { + timestamp: invalid_timestamp_for_serializing, + timestamp_optional_none: None, + timestamp_optional_some: Some(invalid_timestamp_for_serializing), + timestamp_vector: vec![invalid_timestamp_for_serializing], + }; + let result = serialize_to_document(&bad_a); + assert!( + result.is_err(), + "Serialization should fail for Timestamp with increment != 0" + ); + let err_string = format!("{:?}", result.unwrap_err()); + assert!( + err_string.contains("Cannot convert Timestamp with a non-zero increment to u32"), + "Expected error message to mention non-zero increment: {}", + err_string + ); + + #[serde_as] + #[derive(Deserialize, Serialize, Debug, PartialEq)] + struct B { + #[serde_as(as = "timestamp::FromU32")] + pub time: u32, + + #[serde_as(as = "Option")] + pub time_optional_none: Option, + + #[serde_as(as = "Option")] + pub time_optional_some: Option, + + #[serde_as(as = "Vec")] + pub time_vector: Vec, + } + + let time = 12345; + let b = B { + time, + time_optional_none: None, + time_optional_some: Some(time), + time_vector: vec![time], + }; + + // Serialize the struct to BSON + let doc = serialize_to_document(&b).unwrap(); + + // Validate serialized data + assert_eq!( + doc.get_timestamp("time").unwrap(), + Timestamp { time, increment: 0 }, + "Expected serialized time to match the original." + ); + + assert_eq!( + doc.get("time_optional_none"), + Some(&Bson::Null), + "Expected serialized time_optional_none to be None." + ); + + assert_eq!( + doc.get("time_optional_some"), + Some(&Bson::Timestamp(Timestamp { time, increment: 0 })), + "Expected serialized time_optional_some to match original." + ); + + let time_vector = doc + .get_array("time_vector") + .expect("Expected serialized time_vector to be a BSON array."); + let expected_time_vector: Vec = vec![Bson::Timestamp(Timestamp { time, increment: 0 })]; + assert_eq!( + time_vector, &expected_time_vector, + "Expected each serialized element in time_vector to match the original." + ); + + // Validate deserialized data + let b_deserialized: B = deserialize_from_document(doc).unwrap(); + assert_eq!( + b_deserialized, b, + "Deserialized struct does not match original." + ); + + // Validate deserializing error case with an invalid Timestamp + let invalid_timestamp_for_deserializing = Timestamp { + time: 0, + increment: 2, + }; + let invalid_doc = doc! { + "time": invalid_timestamp_for_deserializing, + "time_optional_none": Bson::Null, + "time_optional_some": Some(invalid_timestamp_for_deserializing), + "time_vector": [invalid_timestamp_for_deserializing] + }; + let result: Result = deserialize_from_document(invalid_doc); + assert!( + result.is_err(), + "Deserialization should fail for Timestamp with increment != 0" + ); + let err_string = format!("{:?}", result.unwrap_err()); + assert!( + err_string.contains("Cannot convert Timestamp with a non-zero increment to u32"), + "Expected error message to mention non-zero increment: {}", + err_string + ); +} + +#[test] +#[cfg(feature = "serde_with-3")] +fn test_u32_f64_helper() { + let _guard = LOCK.run_concurrently(); + + #[serde_as] + #[derive(Deserialize, Serialize, Debug, PartialEq)] + struct A { + #[serde_as(as = "u32::AsF64")] + pub value: u32, + + #[serde_as(as = "Option")] + pub value_optional_none: Option, + + #[serde_as(as = "Option")] + pub value_optional_some: Option, + + #[serde_as(as = "Vec")] + pub value_vector: Vec, + } + + let value = 12345; + let a = A { + value, + value_optional_none: None, + value_optional_some: Some(value), + value_vector: vec![value], + }; + + // Serialize the struct to BSON + let doc = serialize_to_document(&a).unwrap(); + + // Validate serialized data + assert_eq!( + doc.get("value"), + Some(&Bson::Double(value as f64)), + "Expected serialized value to match the original." + ); + + assert_eq!( + doc.get("value_optional_none"), + Some(&Bson::Null), + "Expected serialized value_optional_none to be None." + ); + + assert_eq!( + doc.get("value_optional_some"), + Some(&Bson::Double(value as f64)), + "Expected serialized value_optional_some to match original." + ); + + let value_vector = doc + .get_array("value_vector") + .expect("Expected serialized value_vector to be a BSON array."); + let expected_value_vector: Vec = vec![Bson::Double(value as f64)]; + assert_eq!( + value_vector, &expected_value_vector, + "Expected each serialized element in value_vector to match the original." + ); + + // Validate deserialized data + let a_deserialized: A = deserialize_from_document(doc).unwrap(); + assert_eq!( + a_deserialized, a, + "Deserialized struct does not match original." + ); +} + +#[test] +#[cfg(feature = "serde_with-3")] +fn test_u32_i32_helper() { + let _guard = LOCK.run_concurrently(); + + #[serde_as] + #[derive(Deserialize, Serialize, PartialEq, Debug)] + struct A { + #[serde_as(as = "u32::AsI32")] + value: u32, + + #[serde_as(as = "Option")] + value_optional_none: Option, + + #[serde_as(as = "Option")] + value_optional_some: Option, + + #[serde_as(as = "Vec")] + value_vector: Vec, + } + + let value = 1; + let a = A { + value, + value_optional_none: None, + value_optional_some: Some(value), + value_vector: vec![value], + }; + + // Serialize the struct to BSON + let doc = serialize_to_document(&a).unwrap(); + + // Validate serialized data + assert_eq!( + doc.get_i32("value").unwrap(), + value as i32, + "Expected serialized value to match original." + ); + + assert_eq!( + doc.get("value_optional_none"), + Some(&Bson::Null), + "Expected serialized value_optional_none to be None." + ); + + assert_eq!( + doc.get("value_optional_some"), + Some(&Bson::Int32(value as i32)), + "Expected serialized value_optional_some to match original." + ); + + let value_vector = doc + .get_array("value_vector") + .expect("Expected serialized value_vector to be a BSON array."); + let expected_value_vector: Vec = vec![Bson::Int32(value as i32)]; + assert_eq!( + value_vector, &expected_value_vector, + "Expected each serialized element in value_vector to match the original." + ); + + // Validate deserialized data + let a_deserialized: A = deserialize_from_document(doc).unwrap(); + assert_eq!( + a_deserialized, a, + "Deserialized struct does not match original." + ); + + // Validate serialization fails because u32::MAX is too large to fit in i32 + let invalid_value_for_serializing = u32::MAX; + let bad_a: A = A { + value: invalid_value_for_serializing, + value_optional_none: None, + value_optional_some: Some(invalid_value_for_serializing), + value_vector: vec![invalid_value_for_serializing], + }; + let result = serialize_to_document(&bad_a); + assert!( + result.is_err(), + "Serialization should fail for u32::MAX since it can't be exactly represented as i32" + ); + let err_string = format!("{:?}", result.unwrap_err()); + assert!( + err_string.contains("Cannot convert u32"), + "Expected error message to mention failed u32 to i32 conversion, got: {}", + err_string + ); + + // Validate deserialization fails for i32::MIN because negative values can't be converted to + // u32 + let invalid_value_for_deserializing = i32::MIN; + let bad_a = doc! { + "value": invalid_value_for_deserializing, + "value_optional_none": Bson::Null, + "value_optional_some": Some(invalid_value_for_deserializing), + "value_vector": [invalid_value_for_deserializing], + }; + let result: Result = deserialize_from_document(bad_a); + assert!( + result.is_err(), + "Deserialization should fail for i32::MIN since it can't be exactly represented as u32" + ); + let err_string = format!("{:?}", result.unwrap_err()); + assert!( + err_string.contains("Cannot convert i32"), + "Expected error message to mention failed i32 to u32 conversion, got: {}", + err_string + ); +} + +#[test] +#[cfg(feature = "serde_with-3")] +fn test_u32_i64_helper() { + let _guard = LOCK.run_concurrently(); + + #[serde_as] + #[derive(Deserialize, Serialize, PartialEq, Debug)] + struct A { + #[serde_as(as = "u32::AsI64")] + value: u32, + + #[serde_as(as = "Option")] + value_optional_none: Option, + + #[serde_as(as = "Option")] + value_optional_some: Option, + + #[serde_as(as = "Vec")] + value_vector: Vec, + } + + let value = u32::MAX; + let a = A { + value, + value_optional_none: None, + value_optional_some: Some(value), + value_vector: vec![value], + }; + + // Serialize the struct to BSON + let doc = serialize_to_document(&a).unwrap(); + + // Validate serialized data + assert_eq!( + doc.get_i64("value").unwrap(), + value as i64, + "Expected serialized value to match original." + ); + + assert_eq!( + doc.get("value_optional_none"), + Some(&Bson::Null), + "Expected serialized value_optional_none to be None." + ); + + assert_eq!( + doc.get("value_optional_some"), + Some(&Bson::Int64(value as i64)), + "Expected serialized value_optional_some to match original." + ); + + let value_vector = doc + .get_array("value_vector") + .expect("Expected serialized value_vector to be a BSON array."); + let expected_value_vector: Vec = vec![Bson::Int64(value as i64)]; + assert_eq!( + value_vector, &expected_value_vector, + "Expected each serialized element in value_vector to match the original." + ); + + // Validate deserialized data + let a_deserialized: A = deserialize_from_document(doc).unwrap(); + assert_eq!( + a_deserialized, a, + "Round-trip failed: deserialized struct did not match original." + ); + + // Validate deserialization fails for i64::MIN because negative values can't be converted to + // u32 + let invalid_value_for_deserializing = i64::MIN; + let bad_a = doc! { + "value": invalid_value_for_deserializing, + "value_optional_none": Bson::Null, + "value_optional_some": Some(invalid_value_for_deserializing), + "value_vector": [invalid_value_for_deserializing], + }; + let result: Result = deserialize_from_document(bad_a); + assert!( + result.is_err(), + "Deserialization should fail for i64::MIN since it can't be exactly represented as u32" + ); + let err_string = format!("{:?}", result.unwrap_err()); + assert!( + err_string.contains("Cannot convert i64"), + "Expected error message to mention failed i64 to u32 conversion, got: {}", + err_string + ); +} + +#[test] +#[cfg(feature = "serde_with-3")] +fn test_u64_f64_helper() { + let _guard = LOCK.run_concurrently(); + + #[serde_as] + #[derive(Deserialize, Serialize, Debug, PartialEq)] + struct A { + #[serde_as(as = "u64::AsF64")] + pub value: u64, + + #[serde_as(as = "Option")] + pub value_optional_none: Option, + + #[serde_as(as = "Option")] + pub value_optional_some: Option, + + #[serde_as(as = "Vec")] + pub value_vector: Vec, + } + + let value = 12345; + let a = A { + value, + value_optional_none: None, + value_optional_some: Some(value), + value_vector: vec![value], + }; + + // Serialize the struct to BSON + let doc = serialize_to_document(&a).unwrap(); + + // Validate serialized data + assert_eq!( + doc.get("value"), + Some(&Bson::Double(value as f64)), + "Expected serialized value to match the original." + ); + + assert_eq!( + doc.get("value_optional_none"), + Some(&Bson::Null), + "Expected serialized value_optional_none to be None." + ); + + assert_eq!( + doc.get("value_optional_some"), + Some(&Bson::Double(value as f64)), + "Expected serialized value_optional_some to match original." + ); + + let value_vector = doc + .get_array("value_vector") + .expect("Expected serialized value_vector to be a BSON array."); + let expected_value_vector: Vec = vec![Bson::Double(value as f64)]; + assert_eq!( + value_vector, &expected_value_vector, + "Expected each serialized element in value_vector to match the original." + ); + + // Validate deserialized data + let a_deserialized: A = deserialize_from_document(doc).unwrap(); + assert_eq!( + a_deserialized, a, + "Deserialized struct does not match original." + ); + + // Validate serializing error case with u64 over size limit + let invalid_value_for_serializing = u64::MAX; + let bad_a: A = A { + value: invalid_value_for_serializing, + value_optional_none: None, + value_optional_some: Some(invalid_value_for_serializing), + value_vector: vec![invalid_value_for_serializing], + }; + let result = serialize_to_document(&bad_a); + assert!( + result.is_err(), + "Serialization should fail for u64::MAX since it can't be exactly represented as f64" + ); + let err_string = format!("{:?}", result.unwrap_err()); + assert!( + err_string.contains("Cannot convert u64"), + "Expected error message to mention failed u64 to f64 conversion, got: {}", + err_string + ); +} + +#[test] +#[cfg(feature = "serde_with-3")] +fn test_u64_i32_helper() { + let _guard = LOCK.run_concurrently(); + + #[serde_as] + #[derive(Deserialize, Serialize, PartialEq, Debug)] + struct A { + #[serde_as(as = "u64::AsI32")] + value: u64, + + #[serde_as(as = "Option")] + value_optional_none: Option, + + #[serde_as(as = "Option")] + value_optional_some: Option, + + #[serde_as(as = "Vec")] + value_vector: Vec, + } + + let value = 1; + let a = A { + value, + value_optional_none: None, + value_optional_some: Some(value), + value_vector: vec![value], + }; + + // Serialize the struct to BSON + let doc = serialize_to_document(&a).unwrap(); + + // Validate serialized data + assert_eq!( + doc.get_i32("value").unwrap(), + value as i32, + "Expected serialized value to match original." + ); + + assert_eq!( + doc.get("value_optional_none"), + Some(&Bson::Null), + "Expected serialized value_optional_none to be None." + ); + + assert_eq!( + doc.get("value_optional_some"), + Some(&Bson::Int32(value as i32)), + "Expected serialized value_optional_some to match original." + ); + + let value_vector = doc + .get_array("value_vector") + .expect("Expected serialized value_vector to be a BSON array."); + let expected_value_vector: Vec = vec![Bson::Int32(value as i32)]; + assert_eq!( + value_vector, &expected_value_vector, + "Expected each serialized element in value_vector to match the original." + ); + + // Validate deserialized data + let a_deserialized: A = deserialize_from_document(doc).unwrap(); + assert_eq!( + a_deserialized, a, + "Round-trip failed: deserialized struct did not match original." + ); + + // Validate serialization fails because i32::MAX + 1 is too large to fit in i32 + let invalid_value_for_serializing = i32::MAX as u64 + 1; + let bad_a: A = A { + value: invalid_value_for_serializing, + value_optional_none: None, + value_optional_some: Some(invalid_value_for_serializing), + value_vector: vec![invalid_value_for_serializing], + }; + let result = serialize_to_document(&bad_a); + assert!( + result.is_err(), + "Serialization should fail for u64::MAX since it can't be exactly represented as i32" + ); + let err_string = format!("{:?}", result.unwrap_err()); + assert!( + err_string.contains("Cannot convert u64"), + "Expected error message to mention failed u64 to i32 conversion, got: {}", + err_string + ); + + // Validate deserialization fails for i32::MIN because negative values can't be converted to + // u64 + let invalid_value_for_deserializing = i32::MIN; + let bad_a = doc! { + "value": invalid_value_for_deserializing, + "value_optional_none": Bson::Null, + "value_optional_some": Some(invalid_value_for_deserializing), + "value_vector": [invalid_value_for_deserializing], + }; + let result: Result = deserialize_from_document(bad_a); + assert!( + result.is_err(), + "Deserialization should fail for i32::MIN since it can't be exactly represented as u64" + ); + let err_string = format!("{:?}", result.unwrap_err()); + assert!( + err_string.contains("Cannot convert i32"), + "Expected error message to mention failed i32 to u64 conversion, got: {}", + err_string + ); +} + +#[test] +#[cfg(feature = "serde_with-3")] +fn test_u64_i64_helper() { + let _guard = LOCK.run_concurrently(); + #[serde_as] + #[derive(Deserialize, Serialize, PartialEq, Debug)] + struct A { + #[serde_as(as = "u64::AsI64")] + value: u64, + + #[serde_as(as = "Option")] + value_optional_none: Option, + + #[serde_as(as = "Option")] + value_optional_some: Option, + + #[serde_as(as = "Vec")] + value_vector: Vec, + } + + let value = i64::MAX as u64; + let a = A { + value, + value_optional_none: None, + value_optional_some: Some(value), + value_vector: vec![value], + }; + + // Serialize the struct to BSON + let doc = serialize_to_document(&a).unwrap(); + + // Validate serialized data + assert_eq!( + doc.get_i64("value").unwrap(), + value as i64, + "Expected serialized value to match original." + ); + + assert_eq!( + doc.get("value_optional_none"), + Some(&Bson::Null), + "Expected serialized value_optional_none to be None." + ); + + assert_eq!( + doc.get("value_optional_some"), + Some(&Bson::Int64(value as i64)), + "Expected serialized value_optional_some to match original." + ); + + let value_vector = doc + .get_array("value_vector") + .expect("Expected serialized value_vector to be a BSON array."); + let expected_value_vector: Vec = vec![Bson::Int64(value as i64)]; + assert_eq!( + value_vector, &expected_value_vector, + "Expected each serialized element in value_vector to match the original." + ); + + // Validate deserialized data + let a_deserialized: A = deserialize_from_document(doc).unwrap(); + assert_eq!( + a_deserialized, a, + "Round-trip failed: deserialized struct did not match original." + ); + + // Validate serialization fails because i64::MAX + 1 is too large to fit in i64 + let invalid_value_for_serializing = i64::MAX as u64 + 1; + let bad_a: A = A { + value: invalid_value_for_serializing, + value_optional_none: None, + value_optional_some: Some(invalid_value_for_serializing), + value_vector: vec![invalid_value_for_serializing], + }; + let result = serialize_to_document(&bad_a); + assert!( + result.is_err(), + "Serialization should fail for (i64::MAX as u64) + 1 since it can't be exactly \ + represented as i64" + ); + let err_string = format!("{:?}", result.unwrap_err()); + assert!( + err_string.contains("Cannot convert u64"), + "Expected error message to mention failed u64 to i64 conversion, got: {}", + err_string + ); + + // Validate deserialization fails for i64::MIN because negative values can't be converted to + // u64 + let invalid_value_for_deserializing = i64::MIN; + let bad_a = doc! { + "value": invalid_value_for_deserializing, + "value_optional_none": Bson::Null, + "value_optional_some": Some(invalid_value_for_deserializing), + "value_vector": [invalid_value_for_deserializing], + }; + let result: Result = deserialize_from_document(bad_a); + assert!( + result.is_err(), + "Deserialization should fail for i64::MIN since it can't be exactly represented as u64" + ); + let err_string = format!("{:?}", result.unwrap_err()); + assert!( + err_string.contains("Cannot convert i64"), + "Expected error message to mention failed i64 to u64 conversion, got: {}", + err_string + ); +} + #[test] fn test_oid_helpers() { let _guard = LOCK.run_concurrently(); @@ -1374,47 +2034,6 @@ fn test_uuid_1_helpers() { assert_eq!(a.uuid, uuid); } -#[test] -fn test_timestamp_helpers() { - let _guard = LOCK.run_concurrently(); - - #[derive(Deserialize, Serialize)] - struct A { - #[serde(with = "u32_as_timestamp")] - pub time: u32, - } - - let time = 12345; - let a = A { time }; - let doc = serialize_to_document(&a).unwrap(); - let timestamp = doc.get_timestamp("time").unwrap(); - assert_eq!(timestamp.time, time); - assert_eq!(timestamp.increment, 0); - let a: A = deserialize_from_document(doc).unwrap(); - assert_eq!(a.time, time); - - #[derive(Deserialize, Serialize)] - struct B { - #[serde(with = "timestamp_as_u32")] - pub timestamp: Timestamp, - } - - let time = 12345; - let timestamp = Timestamp { time, increment: 0 }; - let b = B { timestamp }; - let val = serde_json::to_value(b).unwrap(); - assert_eq!(val["timestamp"], time); - let b: B = serde_json::from_value(val).unwrap(); - assert_eq!(b.timestamp, timestamp); - - let timestamp = Timestamp { - time: 12334, - increment: 1, - }; - let b = B { timestamp }; - assert!(serde_json::to_value(b).is_err()); -} - #[test] fn large_dates() { let _guard = LOCK.run_concurrently();