Skip to content

Commit 50621ff

Browse files
authored
RUST-871 Support serializing directly to BSON bytes (#279)
1 parent 88ae843 commit 50621ff

15 files changed

+1773
-100
lines changed

.evergreen/config.yml

-15
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,6 @@ functions:
9292
${PREPARE_SHELL}
9393
.evergreen/run-tests-u2i.sh
9494
95-
"run serde tests":
96-
- command: shell.exec
97-
type: test
98-
params:
99-
shell: bash
100-
working_dir: "src"
101-
script: |
102-
${PREPARE_SHELL}
103-
.evergreen/run-tests-serde.sh
104-
10595
"run decimal128 tests":
10696
- command: shell.exec
10797
type: test
@@ -170,10 +160,6 @@ tasks:
170160
commands:
171161
- func: "run u2i tests"
172162

173-
- name: "test-serde"
174-
commands:
175-
- func: "run serde tests"
176-
177163
- name: "test-decimal128"
178164
commands:
179165
- func: "run decimal128 tests"
@@ -211,7 +197,6 @@ buildvariants:
211197
tasks:
212198
- name: "test"
213199
- name: "test-u2i"
214-
- name: "test-serde"
215200
- name: "test-decimal128"
216201

217202
- matrix_name: "compile only"

.evergreen/run-tests-decimal128.sh

+3
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ set -o errexit
44

55
. ~/.cargo/env
66
RUST_BACKTRACE=1 cargo test --features decimal128
7+
8+
cd serde-tests
9+
RUST_BACKTRACE=1 cargo test --features decimal128

.evergreen/run-tests-u2i.sh

+3
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ set -o errexit
44

55
. ~/.cargo/env
66
RUST_BACKTRACE=1 cargo test --features u2i
7+
8+
cd serde-tests
9+
RUST_BACKTRACE=1 cargo test --features u2i

.evergreen/run-tests.sh

+3
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ set -o errexit
55
. ~/.cargo/env
66
RUST_BACKTRACE=1 cargo test
77
RUST_BACKTRACE=1 cargo test --features chrono-0_4,uuid-0_8
8+
9+
cd serde-tests
10+
RUST_BACKTRACE=1 cargo test

serde-tests/Cargo.toml

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ version = "0.1.0"
44
authors = ["Kevin Yeh <kevinyeah@utexas.edu>"]
55
edition = "2018"
66

7+
[features]
8+
u2i = ["bson/u2i"]
9+
decimal128 = ["bson/decimal128"]
10+
711
[dependencies]
8-
bson = { path = "..", features = ["decimal128"] }
12+
bson = { path = ".." }
913
serde = { version = "1.0", features = ["derive"] }
1014
pretty_assertions = "0.6.1"
15+
hex = "0.4.2"
1116

1217
[lib]
1318
name = "serde_tests"

serde-tests/test.rs

+106-4
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ use std::{
1414
collections::{BTreeMap, HashSet},
1515
};
1616

17+
#[cfg(feature = "decimal128")]
18+
use bson::Decimal128;
1719
use bson::{
1820
doc,
1921
oid::ObjectId,
2022
spec::BinarySubtype,
2123
Binary,
2224
Bson,
2325
DateTime,
24-
Decimal128,
2526
Deserializer,
2627
Document,
2728
JavaScriptCodeWithScope,
@@ -37,6 +38,7 @@ use bson::{
3738
/// - deserializing a `T` from the raw BSON version of `expected_doc` produces `expected_value`
3839
/// - deserializing a `Document` from the raw BSON version of `expected_doc` produces
3940
/// `expected_doc`
41+
/// - `bson::to_writer` and `Document::to_writer` produce the same result given the same input
4042
fn run_test<T>(expected_value: &T, expected_doc: &Document, description: &str)
4143
where
4244
T: Serialize + DeserializeOwned + PartialEq + std::fmt::Debug,
@@ -46,6 +48,16 @@ where
4648
.to_writer(&mut expected_bytes)
4749
.expect(description);
4850

51+
let expected_bytes_serde = bson::to_vec(&expected_value).expect(description);
52+
assert_eq!(expected_bytes_serde, expected_bytes, "{}", description);
53+
54+
let expected_bytes_from_doc_serde = bson::to_vec(&expected_doc).expect(description);
55+
assert_eq!(
56+
expected_bytes_from_doc_serde, expected_bytes,
57+
"{}",
58+
description
59+
);
60+
4961
let serialized_doc = bson::to_document(&expected_value).expect(description);
5062
assert_eq!(&serialized_doc, expected_doc, "{}", description);
5163
assert_eq!(
@@ -702,7 +714,7 @@ fn all_types() {
702714
undefined: Bson,
703715
code: Bson,
704716
code_w_scope: JavaScriptCodeWithScope,
705-
decimal: Decimal128,
717+
decimal: Bson,
706718
symbol: Bson,
707719
min_key: Bson,
708720
max_key: Bson,
@@ -737,6 +749,16 @@ fn all_types() {
737749
let oid = ObjectId::new();
738750
let subdoc = doc! { "k": true, "b": { "hello": "world" } };
739751

752+
#[cfg(not(feature = "decimal128"))]
753+
let decimal = {
754+
let bytes = hex::decode("18000000136400D0070000000000000000000000003A3000").unwrap();
755+
let d = Document::from_reader(bytes.as_slice()).unwrap();
756+
d.get("d").unwrap().clone()
757+
};
758+
759+
#[cfg(feature = "decimal128")]
760+
let decimal = Bson::Decimal128(Decimal128::from_str("2.000"));
761+
740762
let doc = doc! {
741763
"x": 1,
742764
"y": 2_i64,
@@ -758,7 +780,7 @@ fn all_types() {
758780
"undefined": Bson::Undefined,
759781
"code": code.clone(),
760782
"code_w_scope": code_w_scope.clone(),
761-
"decimal": Bson::Decimal128(Decimal128::from_i32(5)),
783+
"decimal": decimal.clone(),
762784
"symbol": Bson::Symbol("ok".to_string()),
763785
"min_key": Bson::MinKey,
764786
"max_key": Bson::MaxKey,
@@ -789,7 +811,7 @@ fn all_types() {
789811
undefined: Bson::Undefined,
790812
code,
791813
code_w_scope,
792-
decimal: Decimal128::from_i32(5),
814+
decimal,
793815
symbol: Bson::Symbol("ok".to_string()),
794816
min_key: Bson::MinKey,
795817
max_key: Bson::MaxKey,
@@ -851,3 +873,83 @@ fn borrowed() {
851873
bson::from_slice(bson.as_slice()).expect("deserialization should succeed");
852874
assert_eq!(deserialized, v);
853875
}
876+
877+
#[cfg(feature = "u2i")]
878+
#[test]
879+
fn u2i() {
880+
#[derive(Serialize, Deserialize, Debug, PartialEq)]
881+
struct Foo {
882+
u_8: u8,
883+
u_16: u16,
884+
u_32: u32,
885+
u_32_max: u32,
886+
u_64: u64,
887+
i_64_max: u64,
888+
}
889+
890+
let v = Foo {
891+
u_8: 15,
892+
u_16: 123,
893+
u_32: 1234,
894+
u_32_max: u32::MAX,
895+
u_64: 12345,
896+
i_64_max: i64::MAX as u64,
897+
};
898+
899+
let expected = doc! {
900+
"u_8": 15_i32,
901+
"u_16": 123_i32,
902+
"u_32": 1234_i64,
903+
"u_32_max": u32::MAX as i64,
904+
"u_64": 12345_i64,
905+
"i_64_max": i64::MAX as u64,
906+
};
907+
908+
run_test(&v, &expected, "u2i - valid");
909+
910+
#[derive(Serialize, Debug)]
911+
struct TooBig {
912+
u_64: u64,
913+
}
914+
let v = TooBig {
915+
u_64: i64::MAX as u64 + 1,
916+
};
917+
bson::to_document(&v).unwrap_err();
918+
bson::to_vec(&v).unwrap_err();
919+
}
920+
921+
#[cfg(not(feature = "u2i"))]
922+
#[test]
923+
fn unsigned() {
924+
#[derive(Serialize, Debug)]
925+
struct U8 {
926+
v: u8,
927+
}
928+
let v = U8 { v: 1 };
929+
bson::to_document(&v).unwrap_err();
930+
bson::to_vec(&v).unwrap_err();
931+
932+
#[derive(Serialize, Debug)]
933+
struct U16 {
934+
v: u16,
935+
}
936+
let v = U16 { v: 1 };
937+
bson::to_document(&v).unwrap_err();
938+
bson::to_vec(&v).unwrap_err();
939+
940+
#[derive(Serialize, Debug)]
941+
struct U32 {
942+
v: u32,
943+
}
944+
let v = U32 { v: 1 };
945+
bson::to_document(&v).unwrap_err();
946+
bson::to_vec(&v).unwrap_err();
947+
948+
#[derive(Serialize, Debug)]
949+
struct U64 {
950+
v: u64,
951+
}
952+
let v = U64 { v: 1 };
953+
bson::to_document(&v).unwrap_err();
954+
bson::to_vec(&v).unwrap_err();
955+
}

src/bson.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
//! BSON definition
2323
2424
use std::{
25-
convert::TryFrom,
25+
convert::{TryFrom, TryInto},
2626
fmt::{self, Debug, Display, Formatter},
2727
};
2828

@@ -692,6 +692,20 @@ impl Bson {
692692
}
693693
}
694694

695+
["$numberDecimalBytes"] => {
696+
if let Ok(bytes) = doc.get_binary_generic("$numberDecimalBytes") {
697+
if let Ok(b) = bytes.clone().try_into() {
698+
#[cfg(not(feature = "decimal128"))]
699+
return Bson::Decimal128(Decimal128 { bytes: b });
700+
701+
#[cfg(feature = "decimal128")]
702+
unsafe {
703+
return Bson::Decimal128(Decimal128::from_raw_bytes_le(b));
704+
}
705+
}
706+
}
707+
}
708+
695709
["$binary"] => {
696710
if let Some(binary) = Binary::from_extended_doc(&doc) {
697711
return Bson::Binary(binary);

src/extjson/models.rs

+26-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use chrono::Utc;
44
use serde::{
55
de::{Error, Unexpected},
66
Deserialize,
7+
Serialize,
78
};
89

910
use crate::{extjson, oid, spec::BinarySubtype, Bson};
@@ -27,7 +28,7 @@ impl Int32 {
2728
}
2829
}
2930

30-
#[derive(Deserialize)]
31+
#[derive(Deserialize, Serialize)]
3132
#[serde(deny_unknown_fields)]
3233
pub(crate) struct Int64 {
3334
#[serde(rename = "$numberLong")]
@@ -72,7 +73,7 @@ impl Double {
7273
}
7374
}
7475

75-
#[derive(Deserialize)]
76+
#[derive(Serialize, Deserialize)]
7677
#[serde(deny_unknown_fields)]
7778
pub(crate) struct ObjectId {
7879
#[serde(rename = "$oid")]
@@ -86,6 +87,12 @@ impl ObjectId {
8687
}
8788
}
8889

90+
impl From<crate::oid::ObjectId> for ObjectId {
91+
fn from(id: crate::oid::ObjectId) -> Self {
92+
Self { oid: id.to_hex() }
93+
}
94+
}
95+
8996
#[derive(Deserialize)]
9097
#[serde(deny_unknown_fields)]
9198
pub(crate) struct Symbol {
@@ -100,7 +107,7 @@ pub(crate) struct Regex {
100107
body: RegexBody,
101108
}
102109

103-
#[derive(Deserialize)]
110+
#[derive(Serialize, Deserialize)]
104111
#[serde(deny_unknown_fields)]
105112
pub(crate) struct RegexBody {
106113
pub(crate) pattern: String,
@@ -127,7 +134,7 @@ pub(crate) struct Binary {
127134
pub(crate) body: BinaryBody,
128135
}
129136

130-
#[derive(Deserialize)]
137+
#[derive(Deserialize, Serialize)]
131138
#[serde(deny_unknown_fields)]
132139
pub(crate) struct BinaryBody {
133140
pub(crate) base64: String,
@@ -207,10 +214,13 @@ pub(crate) struct Timestamp {
207214
body: TimestampBody,
208215
}
209216

210-
#[derive(Deserialize)]
217+
#[derive(Serialize, Deserialize)]
211218
#[serde(deny_unknown_fields)]
212219
pub(crate) struct TimestampBody {
220+
#[serde(serialize_with = "crate::serde_helpers::serialize_u32_as_i64")]
213221
pub(crate) t: u32,
222+
223+
#[serde(serialize_with = "crate::serde_helpers::serialize_u32_as_i64")]
214224
pub(crate) i: u32,
215225
}
216226

@@ -223,20 +233,28 @@ impl Timestamp {
223233
}
224234
}
225235

226-
#[derive(Deserialize)]
236+
#[derive(Serialize, Deserialize)]
227237
#[serde(deny_unknown_fields)]
228238
pub(crate) struct DateTime {
229239
#[serde(rename = "$date")]
230240
pub(crate) body: DateTimeBody,
231241
}
232242

233-
#[derive(Deserialize)]
243+
#[derive(Deserialize, Serialize)]
234244
#[serde(untagged)]
235245
pub(crate) enum DateTimeBody {
236246
Canonical(Int64),
237247
Relaxed(String),
238248
}
239249

250+
impl DateTimeBody {
251+
pub(crate) fn from_millis(m: i64) -> Self {
252+
DateTimeBody::Canonical(Int64 {
253+
value: m.to_string(),
254+
})
255+
}
256+
}
257+
240258
impl DateTime {
241259
pub(crate) fn parse(self) -> extjson::de::Result<crate::DateTime> {
242260
match self.body {
@@ -307,7 +325,7 @@ pub(crate) struct DbPointer {
307325
body: DbPointerBody,
308326
}
309327

310-
#[derive(Deserialize)]
328+
#[derive(Serialize, Deserialize)]
311329
#[serde(deny_unknown_fields)]
312330
pub(crate) struct DbPointerBody {
313331
#[serde(rename = "$ref")]

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ pub use self::{
198198
Deserializer,
199199
},
200200
decimal128::Decimal128,
201-
ser::{to_bson, to_document, Serializer},
201+
ser::{to_bson, to_document, to_vec, Serializer},
202202
};
203203

204204
#[macro_use]

0 commit comments

Comments
 (0)