Skip to content

Commit

Permalink
RUST-506 Add serde helper functions (crossterm-rs#222)
Browse files Browse the repository at this point in the history
  • Loading branch information
isabelatkinson authored Jan 6, 2021
1 parent 55abdff commit e03e95e
Show file tree
Hide file tree
Showing 4 changed files with 524 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ hex = "0.4.2"
decimal = { version = "2.0.4", default_features = false, optional = true }
base64 = "0.12.1"
lazy_static = "1.4.0"
uuid = "0.8.1"

[dev-dependencies]
assert_matches = "1.2"
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ pub mod document;
pub mod extjson;
pub mod oid;
pub mod ser;
pub mod serde_helpers;
pub mod spec;

#[cfg(test)]
Expand Down
309 changes: 309 additions & 0 deletions src/serde_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
//! Collection of helper functions for serializing to and deserializing from BSON using Serde

use std::{convert::TryFrom, result::Result};

use serde::{ser, Serialize, Serializer};

use crate::oid::ObjectId;

pub use bson_datetime_as_iso_string::{
deserialize as deserialize_bson_datetime_from_iso_string,
serialize as serialize_bson_datetime_as_iso_string,
};
pub use chrono_datetime_as_bson_datetime::{
deserialize as deserialize_chrono_datetime_from_bson_datetime,
serialize as serialize_chrono_datetime_as_bson_datetime,
};
pub use iso_string_as_bson_datetime::{
deserialize as deserialize_iso_string_from_bson_datetime,
serialize as serialize_iso_string_as_bson_datetime,
};
pub use timestamp_as_u32::{
deserialize as deserialize_timestamp_from_u32,
serialize as serialize_timestamp_as_u32,
};
pub use u32_as_timestamp::{
deserialize as deserialize_u32_from_timestamp,
serialize as serialize_u32_as_timestamp,
};
pub use uuid_as_binary::{
deserialize as deserialize_uuid_from_binary,
serialize as serialize_uuid_as_binary,
};

/// Attempts to serialize a u32 as an i32. Errors if an exact conversion is not possible.
pub fn serialize_u32_as_i32<S: Serializer>(val: &u32, serializer: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(val: &u32, serializer: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(val: &u64, serializer: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(val: &u64, serializer: S) -> Result<S::Ok, S::Error> {
match i64::try_from(*val) {
Ok(val) => serializer.serialize_i64(val),
Err(_) => Err(ser::Error::custom(format!("cannot convert {} to i64", val))),
}
}

/// Contains functions to serialize a chrono::DateTime as a bson::DateTime and deserialize a
/// chrono::DateTime from a bson::DateTime.
///
/// ```rust
/// # use serde::{Serialize, Deserialize};
/// # use bson::serde_helpers::chrono_datetime_as_bson_datetime;
/// #[derive(Serialize, Deserialize)]
/// struct Event {
/// #[serde(with = "chrono_datetime_as_bson_datetime")]
/// pub date: chrono::DateTime<chrono::Utc>,
/// }
/// ```
pub mod chrono_datetime_as_bson_datetime {
use crate::DateTime;
use chrono::Utc;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::result::Result;

/// Deserializes a chrono::DateTime from a bson::DateTime.
pub fn deserialize<'de, D>(deserializer: D) -> Result<chrono::DateTime<Utc>, D::Error>
where
D: Deserializer<'de>,
{
let datetime = DateTime::deserialize(deserializer)?;
Ok(datetime.into())
}

/// Serializes a chrono::DateTime as a bson::DateTime.
pub fn serialize<S: Serializer>(
val: &chrono::DateTime<Utc>,
serializer: S,
) -> Result<S::Ok, S::Error> {
let datetime = DateTime::from(val.to_owned());
datetime.serialize(serializer)
}
}

/// Contains functions to serialize an ISO string as a bson::DateTime and deserialize an ISO string
/// from a bson::DateTime.
///
/// ```rust
/// # use serde::{Serialize, Deserialize};
/// # use bson::serde_helpers::iso_string_as_bson_datetime;
/// #[derive(Serialize, Deserialize)]
/// struct Event {
/// #[serde(with = "iso_string_as_bson_datetime")]
/// pub date: String,
/// }
/// ```
pub mod iso_string_as_bson_datetime {
use crate::{Bson, DateTime};
use serde::{ser, Deserialize, Deserializer, Serialize, Serializer};
use std::{result::Result, str::FromStr};

/// Deserializes an ISO string from a DateTime.
pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
let date = DateTime::deserialize(deserializer)?;
Ok(date.to_string())
}

/// Serializes an ISO string as a DateTime.
pub fn serialize<S: Serializer>(val: &str, serializer: S) -> Result<S::Ok, S::Error> {
let date = chrono::DateTime::from_str(val).map_err(|_| {
ser::Error::custom(format!("cannot convert {} to chrono::DateTime", val))
})?;
Bson::DateTime(date).serialize(serializer)
}
}

/// Contains functions to serialize a bson::DateTime as an ISO string and deserialize a
/// bson::DateTime from an ISO string.
///
/// ```rust
/// # use serde::{Serialize, Deserialize};
/// # use bson::serde_helpers::bson_datetime_as_iso_string;
/// #[derive(Serialize, Deserialize)]
/// struct Event {
/// #[serde(with = "bson_datetime_as_iso_string")]
/// pub date: bson::DateTime,
/// }
/// ```
pub mod bson_datetime_as_iso_string {
use crate::DateTime;
use serde::{de, Deserialize, Deserializer, Serializer};
use std::{result::Result, str::FromStr};

/// Deserializes a bson::DateTime from an ISO string.
pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime, D::Error>
where
D: Deserializer<'de>,
{
let iso = String::deserialize(deserializer)?;
let date = chrono::DateTime::from_str(&iso).map_err(|_| {
de::Error::custom(format!("cannot convert {} to chrono::DateTime", iso))
})?;
Ok(DateTime::from(date))
}

/// Serializes a bson::DateTime as an ISO string.
pub fn serialize<S: Serializer>(val: &DateTime, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&val.to_string())
}
}

/// Serializes a hex string as an ObjectId.
pub fn serialize_hex_string_as_object_id<S: Serializer>(
val: &str,
serializer: S,
) -> Result<S::Ok, S::Error> {
match ObjectId::with_string(val) {
Ok(oid) => oid.serialize(serializer),
Err(_) => Err(ser::Error::custom(format!(
"cannot convert {} to ObjectId",
val
))),
}
}

/// Contains functions to serialize a Uuid as a bson::Binary and deserialize a Uuid from a
/// bson::Binary.
///
/// ```rust
/// # use serde::{Serialize, Deserialize};
/// # use uuid::Uuid;
/// # use bson::serde_helpers::uuid_as_binary;
/// #[derive(Serialize, Deserialize)]
/// struct Item {
/// #[serde(with = "uuid_as_binary")]
/// pub id: Uuid,
/// }
/// ```
pub mod uuid_as_binary {
use crate::{spec::BinarySubtype, Binary};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::result::Result;
use uuid::Uuid;

/// Serializes a Uuid as a Binary.
pub fn serialize<S: Serializer>(val: &Uuid, serializer: S) -> Result<S::Ok, S::Error> {
let binary = Binary {
subtype: BinarySubtype::Uuid,
bytes: val.as_bytes().to_vec(),
};
binary.serialize(serializer)
}

/// Deserializes a Uuid from a Binary.
pub fn deserialize<'de, D>(deserializer: D) -> Result<Uuid, D::Error>
where
D: Deserializer<'de>,
{
let binary = Binary::deserialize(deserializer)?;
if binary.subtype == BinarySubtype::Uuid {
if binary.bytes.len() == 16 {
let mut bytes = [0u8; 16];
bytes.copy_from_slice(&binary.bytes);
Ok(Uuid::from_bytes(bytes))
} else {
Err(de::Error::custom(
"cannot convert Binary to Uuid: incorrect bytes length",
))
}
} else {
Err(de::Error::custom(
"cannot convert Binary to Uuid: incorrect binary subtype",
))
}
}
}

/// 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<S: Serializer>(val: &u32, serializer: S) -> Result<S::Ok, S::Error> {
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<u32, D::Error>
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<S: Serializer>(val: &Timestamp, serializer: S) -> Result<S::Ok, S::Error> {
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<Timestamp, D::Error>
where
D: Deserializer<'de>,
{
let time = u32::deserialize(deserializer)?;
Ok(Timestamp { time, increment: 0 })
}
}
Loading

0 comments on commit e03e95e

Please sign in to comment.