Skip to content

Commit

Permalink
RUST-756 Replace compat::u2f with serde helpers (crossterm-rs#254)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickfreed authored May 5, 2021
1 parent 0d31401 commit 982b15e
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 136 deletions.
27 changes: 0 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ Encoding and decoding support for BSON in Rust
- [BSON Values](#bson-values)
- [BSON Documents](#bson-documents)
- [Modeling BSON with strongly typed data structures](#modeling-bson-with-strongly-typed-data-structures)
- [Breaking Changes](#breaking-changes)
- [Contributing](#contributing)
- [Running the Tests](#running-the-tests)
- [Continuous Integration](#continuous-integration)
Expand Down Expand Up @@ -185,32 +184,6 @@ separate the "business logic" that operates over the data from the (de)serializa
translates the data to/from its serialized form. This can lead to more clear and concise code
that is also less error prone.

## Breaking Changes

In the BSON specification, _unsigned integer types_ are unsupported; for example, `u32`. In the older version of this crate (< `v0.8.0`), if you uses `serde` to serialize _unsigned integer types_ into BSON, it will store them with `Bson::Double` type. From `v0.8.0`, we removed this behavior and simply returned an error when you want to serialize _unsigned integer types_ to BSON. [#72](https://github.com/zonyitoo/bson-rs/pull/72)

For backward compatibility, we've provided a mod `bson::compat::u2f` to explicitly serialize _unsigned integer types_ into BSON's floating point value as follows:

```rust
#[test]
fn test_compat_u2f() {
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
struct Foo {
#[serde(with = "bson::compat::u2f")]
x: u32
}

let foo = Foo { x: 20 };
let b = bson::to_bson(&foo).unwrap();
assert_eq!(b, Bson::Document(doc! { "x": Bson::Double(20.0) }));

let de_foo = bson::from_bson::<Foo>(b).unwrap();
assert_eq!(de_foo, foo);
}
```

In this example, we added an attribute `#[serde(with = "bson::compat::u2f")]` on field `x`, which will tell `serde` to use the `bson::compat::u2f::serialize` and `bson::compat::u2f::deserialize` methods to process this field.

## Contributing

We encourage and would happily accept contributions in the form of GitHub pull requests. Before opening one, be sure to run the tests locally; check out the [testing section](#running-the-tests) for information on how to do that. Once you open a pull request, your branch will be run against the same testing matrix that we use for our [continuous integration](#continuous-integration) system, so it is usually sufficient to only run the integration tests locally against a standalone. Remember to always run the linter tests before opening a pull request.
Expand Down
3 changes: 0 additions & 3 deletions src/compat/mod.rs

This file was deleted.

81 changes: 0 additions & 81 deletions src/compat/u2f.rs

This file was deleted.

1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,6 @@ pub use self::{
#[macro_use]
mod macros;
mod bson;
pub mod compat;
pub mod de;
pub mod decimal128;
pub mod document;
Expand Down
92 changes: 89 additions & 3 deletions src/serde_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@ pub use iso_string_as_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,
deserialize as deserialize_timestamp_from_u32,
serialize as serialize_timestamp_as_u32,
};
pub use u32_as_f64::{deserialize as deserialize_u32_from_f64, serialize as serialize_u32_as_f64};
pub use u32_as_timestamp::{
deserialize as deserialize_u32_from_timestamp, serialize as serialize_u32_as_timestamp,
deserialize as deserialize_u32_from_timestamp,
serialize as serialize_u32_as_timestamp,
};
pub use u64_as_f64::{deserialize as deserialize_u64_from_f64, serialize as serialize_u64_as_f64};
pub use uuid_as_binary::{
deserialize as deserialize_uuid_from_binary, serialize as serialize_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.
Expand Down Expand Up @@ -59,6 +64,87 @@ pub fn serialize_u64_as_i64<S: Serializer>(val: &u64, serializer: S) -> Result<S
}
}

/// 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<u32, D::Error>
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<S: Serializer>(val: &u32, serializer: S) -> Result<S::Ok, S::Error> {
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<u64, D::Error>
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<S: Serializer>(val: &u64, serializer: S) -> Result<S::Ok, S::Error> {
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
)))
}
}
}

/// Contains functions to serialize a chrono::DateTime as a bson::DateTime and deserialize a
/// chrono::DateTime from a bson::DateTime.
///
Expand Down
60 changes: 39 additions & 21 deletions src/tests/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@

use crate::{
bson,
compat::u2f,
doc,
from_bson,
from_document,
oid::ObjectId,
serde_helpers,
serde_helpers::{
hex_string_as_object_id,
bson_datetime_as_iso_string,
chrono_datetime_as_bson_datetime,
hex_string_as_object_id,
iso_string_as_bson_datetime,
timestamp_as_u32,
u32_as_timestamp,
Expand Down Expand Up @@ -274,23 +273,6 @@ fn test_ser_datetime() {
assert_eq!(xfoo, foo);
}

#[test]
fn test_compat_u2f() {
let _guard = LOCK.run_concurrently();
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
struct Foo {
#[serde(with = "u2f")]
x: u32,
}

let foo = Foo { x: 20 };
let b = to_bson(&foo).unwrap();
assert_eq!(b, Bson::Document(doc! { "x": (Bson::Double(20.0)) }));

let de_foo = from_bson::<Foo>(b).unwrap();
assert_eq!(de_foo, foo);
}

#[test]
fn test_binary_generic_roundtrip() {
let _guard = LOCK.run_concurrently();
Expand Down Expand Up @@ -664,6 +646,40 @@ fn test_unsigned_helpers() {
};
let doc_result = 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 = 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 = 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 = to_document(&f);
assert!(doc_result.is_err());

let f = F {
num_1: 1,
num_2: u64::MAX - 255,
};
let doc_result = to_document(&f);
assert!(doc_result.is_err());
}

#[test]
Expand Down Expand Up @@ -735,9 +751,11 @@ fn test_oid_helpers() {
}

let oid = ObjectId::new();
let a = A { oid: oid.to_string() };
let a = A {
oid: oid.to_string(),
};
let doc = to_document(&a).unwrap();
assert_eq!(doc.get_object_id("oid").unwrap(), oid);
assert_eq!(doc.get_object_id("oid").unwrap(), oid);
let a: A = from_document(doc).unwrap();
assert_eq!(a.oid, oid.to_string());
}
Expand Down

0 comments on commit 982b15e

Please sign in to comment.