Skip to content

Commit 822db72

Browse files
authored
RUST-465 Introduce a bson::Uuid wrapper type (#314)
1 parent 785ae7c commit 822db72

File tree

14 files changed

+888
-404
lines changed

14 files changed

+888
-404
lines changed

Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,14 @@ indexmap = "1.6.2"
5050
hex = "0.4.2"
5151
base64 = "0.13.0"
5252
lazy_static = "1.4.0"
53-
uuid = "0.8.1"
53+
uuid = { version = "0.8.1", features = ["serde", "v4"] }
5454
serde_bytes = "0.11.5"
5555

5656
[dev-dependencies]
5757
assert_matches = "1.2"
5858
serde_bytes = "0.11"
5959
pretty_assertions = "0.6.1"
6060
chrono = { version = "0.4", features = ["serde"] }
61-
uuid = { version = "0.8.1", features = ["serde", "v4"] }
6261

6362
[package.metadata.docs.rs]
6463
all-features = true

README.md

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -246,40 +246,7 @@ let query = doc! {
246246

247247
### Working with UUIDs
248248

249-
The BSON format does not contain a dedicated UUID type, though it does have a "binary" type which
250-
has UUID subtypes. Accordingly, this crate provides a
251-
[`Binary`](https://docs.rs/bson/latest/bson/struct.Binary.html) struct which models this binary type
252-
and serializes to and deserializes from it. The popular [`uuid`](https://docs.rs/uuid) crate does
253-
provide a UUID type (`Uuid`), though its `Serialize` and `Deserialize` implementations operate on
254-
strings, so when using it with BSON, the BSON binary type will not be used. To facilitate the
255-
conversion between `Uuid` values and BSON binary values, the `uuid-0_8` feature flag can be
256-
enabled. This flag exposes a number of convenient conversions from `Uuid`, including the
257-
[`uuid_as_binary`](https://docs.rs/bson/latest/bson/serde_helpers/uuid_as_binary/index.html)
258-
serde helper, which can be used to (de)serialize `Uuid`s to/from BSON binaries with the UUID
259-
subtype, and the `From<Uuid>` implementation for `Bson`, which allows `Uuid` values to be used in
260-
the `doc!` and `bson!` macros.
261-
262-
e.g.
263-
264-
``` rust
265-
use serde::{Serialize, Deserialize};
266-
267-
#[derive(Serialize, Deserialize)]
268-
struct Foo {
269-
// serializes as a String.
270-
uuid: Uuid,
271-
272-
// serializes as a BSON binary with subtype 4.
273-
// this requires the "uuid-0_8" feature flag
274-
#[serde(with = "bson::serde_helpers::uuid_as_binary")]
275-
uuid_as_bson: uuid::Uuid,
276-
}
277-
278-
// this automatic conversion also requires the "uuid-0_8" feature flag
279-
let query = doc! {
280-
"uuid": uuid::Uuid::new_v4(),
281-
};
282-
```
249+
See the module-level documentation for the [`bson::uuid` module](https://docs.rs/bson/latest/bson/uuid).
283250

284251
## Minimum supported Rust version (MSRV)
285252

src/bson.rs

Lines changed: 1 addition & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ use crate::{
3636
Decimal128,
3737
};
3838

39-
#[cfg(feature = "uuid-0_8")]
40-
use serde::de::Error;
41-
4239
/// Possible BSON value types.
4340
#[derive(Clone, PartialEq)]
4441
pub enum Bson {
@@ -1058,7 +1055,7 @@ impl Display for Binary {
10581055
}
10591056

10601057
impl Binary {
1061-
fn from_extended_doc(doc: &Document) -> Option<Self> {
1058+
pub(crate) fn from_extended_doc(doc: &Document) -> Option<Self> {
10621059
let binary = doc.get_document("$binary").ok()?;
10631060
let bytes = binary.get_str("base64").ok()?;
10641061
let bytes = base64::decode(bytes).ok()?;
@@ -1076,148 +1073,6 @@ impl Binary {
10761073
}
10771074
}
10781075

1079-
/// Enum of the possible representations to use when converting between [Uuid](https://docs.rs/uuid/0.8.2/uuid/) and [`Binary`].
1080-
/// This enum is necessary because the different drivers used to have different ways of encoding
1081-
/// UUIDs, with the BSON subtype: 0x03 (UUID old).
1082-
/// If a UUID has been serialized with a particular representation, it MUST
1083-
/// be deserialized with the same representation.
1084-
///
1085-
/// Example:
1086-
/// ```
1087-
/// use crate::{bson::UuidRepresentation, bson::Binary};
1088-
/// use uuid::Uuid;
1089-
/// let uuid = Uuid::parse_str("00112233445566778899AABBCCDDEEFF").unwrap();
1090-
/// let bin = Binary::from_uuid_with_representation(uuid, UuidRepresentation::PythonLegacy);
1091-
/// let new_uuid = bin.to_uuid();
1092-
/// assert!(new_uuid.is_err());
1093-
/// let new_uuid = bin.to_uuid_with_representation(UuidRepresentation::PythonLegacy);
1094-
/// assert!(new_uuid.is_ok());
1095-
/// assert_eq!(new_uuid.unwrap(), uuid);
1096-
/// ```
1097-
#[cfg(feature = "uuid-0_8")]
1098-
#[cfg_attr(docsrs, doc(cfg(feature = "uuid-0_8")))]
1099-
#[non_exhaustive]
1100-
#[derive(PartialEq, Clone, Copy, Debug)]
1101-
pub enum UuidRepresentation {
1102-
/// The canonical representation of UUIDs in BSON (binary with subtype 0x04)
1103-
Standard,
1104-
/// The legacy representation of UUIDs in BSON used by the C# driver (binary subtype 0x03)
1105-
CSharpLegacy,
1106-
/// The legacy representation of UUIDs in BSON used by the Java driver (binary subtype 0x03)
1107-
JavaLegacy,
1108-
/// The legacy representation of UUIDs in BSON used by the Python driver, which is the same
1109-
/// format as STANDARD, but has binary subtype 0x03
1110-
PythonLegacy,
1111-
}
1112-
1113-
#[cfg(feature = "uuid-0_8")]
1114-
#[cfg_attr(docsrs, doc(cfg(feature = "uuid-0_8")))]
1115-
impl Binary {
1116-
/// Serializes a [Uuid](https://docs.rs/uuid/0.8.2/uuid/) into BSON [`Binary`] type
1117-
pub fn from_uuid(uuid: uuid::Uuid) -> Self {
1118-
Binary::from(uuid)
1119-
}
1120-
/// Serializes a UUID into BSON binary type and takes the desired representation as a parameter.
1121-
/// `Binary::from_uuid_with_representation(uuid, UuidRepresentation::Standard)` is equivalent
1122-
/// to `Binary::from_uuid(uuid)`.
1123-
///
1124-
/// See the documentation for [`UuidRepresentation`] for more information on the possible
1125-
/// representations.
1126-
pub fn from_uuid_with_representation(uuid: uuid::Uuid, rep: UuidRepresentation) -> Self {
1127-
match rep {
1128-
UuidRepresentation::Standard => Binary::from_uuid(uuid),
1129-
UuidRepresentation::CSharpLegacy => {
1130-
let mut bytes = uuid.as_bytes().to_vec();
1131-
bytes[0..4].reverse();
1132-
bytes[4..6].reverse();
1133-
bytes[6..8].reverse();
1134-
Binary {
1135-
subtype: BinarySubtype::UuidOld,
1136-
bytes,
1137-
}
1138-
}
1139-
UuidRepresentation::PythonLegacy => Binary {
1140-
subtype: BinarySubtype::UuidOld,
1141-
bytes: uuid.as_bytes().to_vec(),
1142-
},
1143-
UuidRepresentation::JavaLegacy => {
1144-
let mut bytes = uuid.as_bytes().to_vec();
1145-
bytes[0..8].reverse();
1146-
bytes[8..16].reverse();
1147-
Binary {
1148-
subtype: BinarySubtype::UuidOld,
1149-
bytes,
1150-
}
1151-
}
1152-
}
1153-
}
1154-
/// Deserializes a BSON [`Binary`] type into a [Uuid](https://docs.rs/uuid/0.8.2/uuid/), takes the representation with which the [`Binary`]
1155-
/// was serialized.
1156-
///
1157-
/// See the documentation for [`UuidRepresentation`] for more information on the possible
1158-
/// representations.
1159-
pub fn to_uuid_with_representation(
1160-
&self,
1161-
rep: UuidRepresentation,
1162-
) -> Result<uuid::Uuid, crate::de::Error> {
1163-
// If representation is non-standard, then its subtype must be UuidOld
1164-
if rep != UuidRepresentation::Standard && self.subtype != BinarySubtype::UuidOld {
1165-
return Err(Error::custom(format!(
1166-
"expected binary subtype 3 when converting to UUID with a non-standard \
1167-
representation, instead got {:#04x}",
1168-
u8::from(self.subtype)
1169-
)));
1170-
}
1171-
// If representation is standard, then its subtype must be Uuid
1172-
if rep == UuidRepresentation::Standard && self.subtype != BinarySubtype::Uuid {
1173-
return Err(Error::custom(format!(
1174-
"expected binary subtype 4 when converting to UUID with the standard \
1175-
representation, instead got {:#04x}",
1176-
u8::from(self.subtype)
1177-
)));
1178-
}
1179-
// Must be 16 bytes long
1180-
if self.bytes.len() != 16 {
1181-
return Err(Error::custom(format!(
1182-
"expected UUID to contain 16 bytes, instead got {}",
1183-
self.bytes.len()
1184-
)));
1185-
}
1186-
let mut buf = [0u8; 16];
1187-
buf.copy_from_slice(&self.bytes);
1188-
match rep {
1189-
UuidRepresentation::Standard => Ok(uuid::Uuid::from_bytes(buf)),
1190-
UuidRepresentation::CSharpLegacy => {
1191-
buf[0..4].reverse();
1192-
buf[4..6].reverse();
1193-
buf[6..8].reverse();
1194-
Ok(uuid::Uuid::from_bytes(buf))
1195-
}
1196-
UuidRepresentation::PythonLegacy => Ok(uuid::Uuid::from_bytes(buf)),
1197-
UuidRepresentation::JavaLegacy => {
1198-
buf[0..8].reverse();
1199-
buf[8..16].reverse();
1200-
Ok(uuid::Uuid::from_bytes(buf))
1201-
}
1202-
}
1203-
}
1204-
/// Deserializes a BSON [`Binary`] type into a [Uuid](https://docs.rs/uuid/0.8.2/uuid/) using the standard representation.
1205-
pub fn to_uuid(&self) -> Result<uuid::Uuid, crate::de::Error> {
1206-
self.to_uuid_with_representation(UuidRepresentation::Standard)
1207-
}
1208-
}
1209-
1210-
#[cfg(feature = "uuid-0_8")]
1211-
#[cfg_attr(docsrs, doc(cfg(feature = "uuid-0_8")))]
1212-
impl From<uuid::Uuid> for Binary {
1213-
fn from(uuid: uuid::Uuid) -> Self {
1214-
Binary {
1215-
subtype: BinarySubtype::Uuid,
1216-
bytes: uuid.as_bytes().to_vec(),
1217-
}
1218-
}
1219-
}
1220-
12211076
/// Represents a DBPointer. (Deprecated)
12221077
#[derive(Debug, Clone, PartialEq)]
12231078
pub struct DbPointer {

src/de/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ use ::serde::{
4545
Deserialize,
4646
};
4747

48+
pub(crate) use self::serde::BsonVisitor;
49+
4850
pub(crate) const MAX_BSON_SIZE: i32 = 16 * 1024 * 1024;
4951
pub(crate) const MIN_BSON_DOCUMENT_SIZE: i32 = 4 + 1; // 4 bytes for length, one byte for null terminator
5052
pub(crate) const MIN_BSON_STRING_SIZE: i32 = 4 + 1; // 4 bytes for length, one byte for null terminator

src/de/raw.rs

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use serde::{
1212
use crate::{
1313
oid::ObjectId,
1414
spec::{BinarySubtype, ElementType},
15+
uuid::UUID_NEWTYPE_NAME,
1516
Binary,
1617
Bson,
1718
DateTime,
@@ -130,15 +131,24 @@ impl<'de> Deserializer<'de> {
130131
self.current_type = element_type;
131132
Ok(Some(element_type))
132133
}
133-
}
134134

135-
impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut Deserializer<'de> {
136-
type Error = Error;
137-
138-
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
135+
fn deserialize_next<V>(
136+
&mut self,
137+
visitor: V,
138+
binary_subtype_hint: Option<BinarySubtype>,
139+
) -> Result<V::Value>
139140
where
140141
V: serde::de::Visitor<'de>,
141142
{
143+
if let Some(expected_st) = binary_subtype_hint {
144+
if self.current_type != ElementType::Binary {
145+
return Err(Error::custom(format!(
146+
"expected Binary with subtype {:?}, instead got {:?}",
147+
expected_st, self.current_type
148+
)));
149+
}
150+
}
151+
142152
match self.current_type {
143153
ElementType::Int32 => visitor.visit_i32(read_i32(&mut self.bytes)?),
144154
ElementType::Int64 => visitor.visit_i64(read_i64(&mut self.bytes)?),
@@ -166,6 +176,16 @@ impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut Deserializer<'de> {
166176
));
167177
}
168178
let subtype = BinarySubtype::from(read_u8(&mut self.bytes)?);
179+
180+
if let Some(expected_subtype) = binary_subtype_hint {
181+
if subtype != expected_subtype {
182+
return Err(Error::custom(format!(
183+
"expected binary subtype {:?} instead got {:?}",
184+
expected_subtype, subtype
185+
)));
186+
}
187+
}
188+
169189
match subtype {
170190
BinarySubtype::Generic => {
171191
visitor.visit_borrowed_bytes(self.bytes.read_slice(len as usize)?)
@@ -245,6 +265,18 @@ impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut Deserializer<'de> {
245265
}
246266
}
247267
}
268+
}
269+
270+
impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut Deserializer<'de> {
271+
type Error = Error;
272+
273+
#[inline]
274+
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
275+
where
276+
V: serde::de::Visitor<'de>,
277+
{
278+
self.deserialize_next(visitor, None)
279+
}
248280

249281
#[inline]
250282
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
@@ -285,13 +317,24 @@ impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut Deserializer<'de> {
285317
}
286318
}
287319

320+
fn deserialize_newtype_struct<V>(self, name: &'static str, visitor: V) -> Result<V::Value>
321+
where
322+
V: serde::de::Visitor<'de>,
323+
{
324+
if name == UUID_NEWTYPE_NAME {
325+
self.deserialize_next(visitor, Some(BinarySubtype::Uuid))
326+
} else {
327+
visitor.visit_newtype_struct(self)
328+
}
329+
}
330+
288331
fn is_human_readable(&self) -> bool {
289332
false
290333
}
291334

292335
forward_to_deserialize_any! {
293336
bool char str byte_buf unit unit_struct string
294-
identifier newtype_struct seq tuple tuple_struct struct
337+
identifier seq tuple tuple_struct struct
295338
map ignored_any i8 i16 i32 i64 u8 u16 u32 u64 f32 f64
296339
}
297340
}

src/de/serde.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::{
2525
document::{Document, IntoIter},
2626
oid::ObjectId,
2727
spec::BinarySubtype,
28+
uuid::UUID_NEWTYPE_NAME,
2829
Decimal128,
2930
};
3031

@@ -127,7 +128,7 @@ impl<'de> Visitor<'de> for BsonVisitor {
127128
type Value = Bson;
128129

129130
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
130-
f.write_str("expecting a Bson")
131+
f.write_str("a Bson")
131132
}
132133

133134
#[inline]
@@ -460,6 +461,14 @@ impl<'de> Visitor<'de> for BsonVisitor {
460461
bytes: v,
461462
}))
462463
}
464+
465+
#[inline]
466+
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
467+
where
468+
D: serde::Deserializer<'de>,
469+
{
470+
deserializer.deserialize_any(self)
471+
}
463472
}
464473

465474
fn convert_unsigned_to_signed<E>(value: u64) -> Result<Bson, E>
@@ -652,13 +661,26 @@ impl<'de> de::Deserializer<'de> for Deserializer {
652661
#[inline]
653662
fn deserialize_newtype_struct<V>(
654663
self,
655-
_name: &'static str,
664+
name: &'static str,
656665
visitor: V,
657666
) -> crate::de::Result<V::Value>
658667
where
659668
V: Visitor<'de>,
660669
{
661-
visitor.visit_newtype_struct(self)
670+
// if this is a UUID, ensure that value is a subtype 4 binary
671+
if name == UUID_NEWTYPE_NAME {
672+
match self.value {
673+
Some(Bson::Binary(ref b)) if b.subtype == BinarySubtype::Uuid => {
674+
self.deserialize_any(visitor)
675+
}
676+
b => Err(Error::custom(format!(
677+
"expected Binary with subtype 4, instead got {:?}",
678+
b
679+
))),
680+
}
681+
} else {
682+
visitor.visit_newtype_struct(self)
683+
}
662684
}
663685

664686
forward_to_deserialize! {

0 commit comments

Comments
 (0)