Skip to content

Commit 961b5c9

Browse files
authored
RUST-1024 Add serde_with integration for Uuid and DateTime (#323)
1 parent e170a44 commit 961b5c9

File tree

7 files changed

+193
-9
lines changed

7 files changed

+193
-9
lines changed

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ default = []
3636
chrono-0_4 = []
3737
# if enabled, include API for interfacing with uuid 0.8
3838
uuid-0_8 = []
39+
# if enabled, include serde_with interop.
40+
# should be used in conjunction with chrono-0_4 or uuid-0_8.
41+
# it's commented out here because Cargo implicitly adds a feature flag for
42+
# all optional dependencies.
43+
# serde_with
3944

4045
[lib]
4146
name = "bson"
@@ -52,6 +57,7 @@ base64 = "0.13.0"
5257
lazy_static = "1.4.0"
5358
uuid = { version = "0.8.1", features = ["serde", "v4"] }
5459
serde_bytes = "0.11.5"
60+
serde_with = { version = "1", optional = true }
5561

5662
[dev-dependencies]
5763
assert_matches = "1.2"

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@ Note that if you are using `bson` through the `mongodb` crate, you do not need t
4444

4545
#### Feature Flags
4646

47-
| Feature | Description | Extra dependencies | Default |
48-
|:-------------|:---------------------------------------------------------------------------------------|:-------------------|:--------|
49-
| `chrono-0_4` | Enable support for v0.4 of the [`chrono`](docs.rs/chrono/0.4) crate in the public API. | n/a | no |
50-
| `uuid-0_8` | Enable support for v0.8 of the [`uuid`](docs.rs/uuid/0.8) crate in the public API. | n/a | no |
47+
| Feature | Description | Extra dependencies | Default |
48+
|:-------------|:----------------------------------------------------------------------------------------------------|:-------------------|:--------|
49+
| `chrono-0_4` | Enable support for v0.4 of the [`chrono`](docs.rs/chrono/0.4) crate in the public API. | n/a | no |
50+
| `uuid-0_8` | Enable support for v0.8 of the [`uuid`](docs.rs/uuid/0.8) crate in the public API. | n/a | no |
51+
| `serde_with` | Enable [`serde_with`](docs.rs/serde_with/latest) integrations for `bson::DateTime` and `bson::Uuid` | serde_with | no |
5152

5253
## Overview of the BSON Format
5354

serde-tests/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ edition = "2018"
88
default = []
99

1010
[dependencies]
11-
bson = { path = "..", default-features = false }
11+
bson = { path = "..", features = ["uuid-0_8", "chrono-0_4", "serde_with"] }
1212
serde = { version = "1.0", features = ["derive"] }
1313
pretty_assertions = "0.6.1"
1414
hex = "0.4.2"
15+
serde_with = "1"
16+
chrono = "0.4"
17+
uuid = "0.8"
1518

1619
[dev-dependencies]
1720
serde_json = "1"

serde-tests/test.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ where
6262
.expect(description);
6363

6464
let expected_bytes_serde = bson::to_vec(&expected_value).expect(description);
65+
6566
assert_eq!(expected_bytes_serde, expected_bytes, "{}", description);
6667

6768
let expected_bytes_from_doc_serde = bson::to_vec(&expected_doc).expect(description);
@@ -1231,6 +1232,54 @@ fn u2i() {
12311232
bson::to_vec(&v).unwrap_err();
12321233
}
12331234

1235+
#[test]
1236+
fn serde_with_chrono() {
1237+
#[serde_with::serde_as]
1238+
#[derive(Deserialize, Serialize, PartialEq, Debug)]
1239+
struct Foo {
1240+
#[serde_as(as = "Option<bson::DateTime>")]
1241+
as_bson: Option<chrono::DateTime<chrono::Utc>>,
1242+
1243+
#[serde_as(as = "Option<bson::DateTime>")]
1244+
none_bson: Option<chrono::DateTime<chrono::Utc>>,
1245+
}
1246+
1247+
let f = Foo {
1248+
as_bson: Some(bson::DateTime::now().into()),
1249+
none_bson: None,
1250+
};
1251+
let expected = doc! {
1252+
"as_bson": Bson::DateTime(f.as_bson.unwrap().into()),
1253+
"none_bson": Bson::Null
1254+
};
1255+
1256+
run_test(&f, &expected, "serde_with - chrono");
1257+
}
1258+
1259+
#[test]
1260+
fn serde_with_uuid() {
1261+
#[serde_with::serde_as]
1262+
#[derive(Deserialize, Serialize, PartialEq, Debug)]
1263+
struct Foo {
1264+
#[serde_as(as = "Option<bson::Uuid>")]
1265+
as_bson: Option<uuid::Uuid>,
1266+
1267+
#[serde_as(as = "Option<bson::Uuid>")]
1268+
none_bson: Option<uuid::Uuid>,
1269+
}
1270+
1271+
let f = Foo {
1272+
as_bson: Some(uuid::Uuid::new_v4()),
1273+
none_bson: None,
1274+
};
1275+
let expected = doc! {
1276+
"as_bson": bson::Uuid::from(f.as_bson.unwrap()),
1277+
"none_bson": Bson::Null
1278+
};
1279+
1280+
run_test(&f, &expected, "serde_with - uuid");
1281+
}
1282+
12341283
#[test]
12351284
fn hint_cleared() {
12361285
#[derive(Debug, Serialize, Deserialize)]

src/datetime.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ use std::{
55
time::{Duration, SystemTime},
66
};
77

8+
#[cfg(all(feature = "serde_with", feature = "chrono-0_4"))]
9+
use serde::{Deserialize, Deserializer, Serialize};
10+
#[cfg(all(feature = "serde_with", feature = "chrono-0_4"))]
11+
use serde_with::{DeserializeAs, SerializeAs};
12+
813
use chrono::{LocalResult, TimeZone, Utc};
914

1015
/// Struct representing a BSON datetime.
@@ -54,6 +59,40 @@ use chrono::{LocalResult, TimeZone, Utc};
5459
/// }
5560
/// # }
5661
/// ```
62+
/// ## The `serde_with` feature flag
63+
///
64+
/// The `serde_with` feature can be enabled to support more ergonomic serde attributes for
65+
/// (de)serializing `chrono::DateTime` from/to BSON via the [`serde_with`](https://docs.rs/serde_with/1.11.0/serde_with/)
66+
/// crate. The main benefit of this compared to the regular `serde_helpers` is that `serde_with` can
67+
/// handle nested `chrono::DateTime` values (e.g. in `Option`), whereas the former only works on
68+
/// fields that are exactly `chrono::DateTime`.
69+
/// ```
70+
/// # #[cfg(all(feature = "chrono-0_4", feature = "serde_with"))]
71+
/// # {
72+
/// use serde::{Deserialize, Serialize};
73+
/// use bson::doc;
74+
///
75+
/// #[serde_with::serde_as]
76+
/// #[derive(Deserialize, Serialize, PartialEq, Debug)]
77+
/// struct Foo {
78+
/// /// Serializes as a BSON datetime rather than using `chrono::DateTime`'s serialization
79+
/// #[serde_as(as = "Option<bson::DateTime>")]
80+
/// as_bson: Option<chrono::DateTime<chrono::Utc>>,
81+
/// }
82+
///
83+
/// let dt = chrono::Utc::now();
84+
/// let foo = Foo {
85+
/// as_bson: Some(dt),
86+
/// };
87+
///
88+
/// let expected = doc! {
89+
/// "as_bson": bson::DateTime::from_chrono(dt),
90+
/// };
91+
///
92+
/// assert_eq!(bson::to_document(&foo)?, expected);
93+
/// # }
94+
/// # Ok::<(), Box<dyn std::error::Error>>(())
95+
/// ```
5796
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
5897
pub struct DateTime(i64);
5998

@@ -234,6 +273,33 @@ impl<T: chrono::TimeZone> From<chrono::DateTime<T>> for crate::DateTime {
234273
}
235274
}
236275

276+
#[cfg(all(feature = "chrono-0_4", feature = "serde_with"))]
277+
#[cfg_attr(docsrs, doc(cfg(all(feature = "chrono-0_4", feature = "serde_with"))))]
278+
impl<'de> DeserializeAs<'de, chrono::DateTime<Utc>> for crate::DateTime {
279+
fn deserialize_as<D>(deserializer: D) -> std::result::Result<chrono::DateTime<Utc>, D::Error>
280+
where
281+
D: Deserializer<'de>,
282+
{
283+
let dt = DateTime::deserialize(deserializer)?;
284+
Ok(dt.to_chrono())
285+
}
286+
}
287+
288+
#[cfg(all(feature = "chrono-0_4", feature = "serde_with"))]
289+
#[cfg_attr(docsrs, doc(cfg(all(feature = "chrono-0_4", feature = "chrono-0_4"))))]
290+
impl SerializeAs<chrono::DateTime<Utc>> for crate::DateTime {
291+
fn serialize_as<S>(
292+
source: &chrono::DateTime<Utc>,
293+
serializer: S,
294+
) -> std::result::Result<S::Ok, S::Error>
295+
where
296+
S: serde::Serializer,
297+
{
298+
let dt = DateTime::from_chrono(*source);
299+
dt.serialize(serializer)
300+
}
301+
}
302+
237303
/// Errors that can occur during [`DateTime`] construction and generation.
238304
#[derive(Clone, Debug)]
239305
#[non_exhaustive]

src/lib.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,11 @@
5959
//!
6060
//! #### Feature Flags
6161
//!
62-
//! | Feature | Description | Extra dependencies | Default |
63-
//! |:-------------|:---------------------------------------------------------------------------------------|:-------------------|:--------|
64-
//! | `chrono-0_4` | Enable support for v0.4 of the [`chrono`](docs.rs/chrono/0.4) crate in the public API. | n/a | no |
65-
//! | `uuid-0_8` | Enable support for v0.8 of the [`uuid`](docs.rs/uuid/0.8) crate in the public API. | n/a | no |
62+
//! | Feature | Description | Extra dependencies | Default |
63+
//! |:-------------|:----------------------------------------------------------------------------------------------------|:-------------------|:--------|
64+
//! | `chrono-0_4` | Enable support for v0.4 of the [`chrono`](docs.rs/chrono/0.4) crate in the public API. | n/a | no |
65+
//! | `uuid-0_8` | Enable support for v0.8 of the [`uuid`](docs.rs/uuid/0.8) crate in the public API. | n/a | no |
66+
//! | `serde_with` | Enable [`serde_with`](docs.rs/serde_with/latest) integrations for `bson::DateTime` and `bson::Uuid` | serde_with | no |
6667
//!
6768
//! ## BSON values
6869
//!

src/uuid/mod.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,40 @@
6969
//! # };
7070
//! ```
7171
//!
72+
//! ## The `serde_with` feature flag
73+
//!
74+
//! The `serde_with` feature can be enabled to support more ergonomic serde attributes for
75+
//! (de)serializing `uuid::Uuid` from/to BSON via the [`serde_with`](https://docs.rs/serde_with/1.11.0/serde_with/)
76+
//! crate. The main benefit of this compared to the regular `serde_helpers` is that `serde_with` can
77+
//! handle nested `uuid::Uuid` values (e.g. in `Option`), whereas the former only works on fields
78+
//! that are exactly `uuid::Uuid`.
79+
//! ```
80+
//! # #[cfg(all(feature = "uuid-0_8", feature = "serde_with"))]
81+
//! # {
82+
//! use serde::{Deserialize, Serialize};
83+
//! use bson::doc;
84+
//!
85+
//! #[serde_with::serde_as]
86+
//! #[derive(Deserialize, Serialize, PartialEq, Debug)]
87+
//! struct Foo {
88+
//! /// Serializes as a BSON binary rather than using `uuid::Uuid`'s serialization
89+
//! #[serde_as(as = "Option<bson::Uuid>")]
90+
//! as_bson: Option<uuid::Uuid>,
91+
//! }
92+
//!
93+
//! let foo = Foo {
94+
//! as_bson: Some(uuid::Uuid::new_v4()),
95+
//! };
96+
//!
97+
//! let expected = doc! {
98+
//! "as_bson": bson::Uuid::from(foo.as_bson.unwrap()),
99+
//! };
100+
//!
101+
//! assert_eq!(bson::to_document(&foo)?, expected);
102+
//! # }
103+
//! # Ok::<(), Box<dyn std::error::Error>>(())
104+
//! ```
105+
//!
72106
//! ## Using `crate::Uuid` with non-BSON formats
73107
//!
74108
//! [`crate::Uuid`]'s `serde` implementation is the same as `uuid::Uuid`'s
@@ -413,6 +447,30 @@ impl From<uuid::Uuid> for Binary {
413447
}
414448
}
415449

450+
#[cfg(all(feature = "uuid-0_8", feature = "serde_with"))]
451+
#[cfg_attr(docsrs, doc(cfg(all(feature = "uuid-0_8", feature = "serde_with"))))]
452+
impl<'de> serde_with::DeserializeAs<'de, uuid::Uuid> for crate::Uuid {
453+
fn deserialize_as<D>(deserializer: D) -> std::result::Result<uuid::Uuid, D::Error>
454+
where
455+
D: serde::Deserializer<'de>,
456+
{
457+
let uuid = Uuid::deserialize(deserializer)?;
458+
Ok(uuid.into())
459+
}
460+
}
461+
462+
#[cfg(all(feature = "uuid-0_8", feature = "serde_with"))]
463+
#[cfg_attr(docsrs, doc(cfg(all(feature = "uuid-0_8", feature = "sere_with"))))]
464+
impl serde_with::SerializeAs<uuid::Uuid> for crate::Uuid {
465+
fn serialize_as<S>(source: &uuid::Uuid, serializer: S) -> std::result::Result<S::Ok, S::Error>
466+
where
467+
S: serde::Serializer,
468+
{
469+
let uuid = Uuid::from_external_uuid(*source);
470+
uuid.serialize(serializer)
471+
}
472+
}
473+
416474
/// Errors that can occur during [`Uuid`] construction and generation.
417475
#[derive(Clone, Debug)]
418476
#[non_exhaustive]

0 commit comments

Comments
 (0)