Skip to content

Commit 785ae7c

Browse files
author
Andrew Witten
authored
RUST-819 Add methods for converting between Binary and Uuid (#308)
Add methods for converting between Binary and Uuid consistent with specs (https://github.com/mongodb/specifications/blob/master/source/uuid.rst).
1 parent 0f03fb8 commit 785ae7c

File tree

4 files changed

+302
-70
lines changed

4 files changed

+302
-70
lines changed

src/bson.rs

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

39+
#[cfg(feature = "uuid-0_8")]
40+
use serde::de::Error;
41+
3942
/// Possible BSON value types.
4043
#[derive(Clone, PartialEq)]
4144
pub enum Bson {
@@ -1073,6 +1076,137 @@ impl Binary {
10731076
}
10741077
}
10751078

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+
10761210
#[cfg(feature = "uuid-0_8")]
10771211
#[cfg_attr(docsrs, doc(cfg(feature = "uuid-0_8")))]
10781212
impl From<uuid::Uuid> for Binary {

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,9 @@ pub use self::{
320320
ser::{to_bson, to_document, to_vec, Serializer},
321321
};
322322

323+
#[cfg(feature = "uuid-0_8")]
324+
pub use self::bson::UuidRepresentation;
325+
323326
#[macro_use]
324327
mod macros;
325328
mod bson;

src/serde_helpers.rs

Lines changed: 17 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ pub mod hex_string_as_object_id {
355355
#[cfg(feature = "uuid-0_8")]
356356
#[cfg_attr(docsrs, doc(cfg(feature = "uuid-0_8")))]
357357
pub mod uuid_as_binary {
358-
use crate::{spec::BinarySubtype, Binary};
358+
use crate::Binary;
359359
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
360360
use std::result::Result;
361361
use uuid::Uuid;
@@ -374,21 +374,7 @@ pub mod uuid_as_binary {
374374
D: Deserializer<'de>,
375375
{
376376
let binary = Binary::deserialize(deserializer)?;
377-
if binary.subtype == BinarySubtype::Uuid {
378-
if binary.bytes.len() == 16 {
379-
let mut bytes = [0u8; 16];
380-
bytes.copy_from_slice(&binary.bytes);
381-
Ok(Uuid::from_bytes(bytes))
382-
} else {
383-
Err(de::Error::custom(
384-
"cannot convert Binary to Uuid: incorrect bytes length",
385-
))
386-
}
387-
} else {
388-
Err(de::Error::custom(
389-
"cannot convert Binary to Uuid: incorrect binary subtype",
390-
))
391-
}
377+
binary.to_uuid().map_err(de::Error::custom)
392378
}
393379
}
394380

@@ -412,21 +398,15 @@ pub mod uuid_as_binary {
412398
#[cfg(feature = "uuid-0_8")]
413399
#[cfg_attr(docsrs, doc(cfg(feature = "uuid-0_8")))]
414400
pub mod uuid_as_java_legacy_binary {
415-
use crate::{spec::BinarySubtype, Binary};
401+
use crate::{bson::UuidRepresentation, Binary};
416402
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
417403
use std::result::Result;
418404
use uuid::Uuid;
419405

420406
/// Serializes a Uuid as a Binary in a Java Legacy UUID format.
421407
#[cfg_attr(docsrs, doc(cfg(feature = "uuid-0_8")))]
422408
pub fn serialize<S: Serializer>(val: &Uuid, serializer: S) -> Result<S::Ok, S::Error> {
423-
let mut bytes = val.as_bytes().to_vec();
424-
bytes[0..8].reverse();
425-
bytes[8..16].reverse();
426-
let binary = Binary {
427-
subtype: BinarySubtype::UuidOld,
428-
bytes,
429-
};
409+
let binary = Binary::from_uuid_with_representation(*val, UuidRepresentation::JavaLegacy);
430410
binary.serialize(serializer)
431411
}
432412

@@ -437,17 +417,9 @@ pub mod uuid_as_java_legacy_binary {
437417
D: Deserializer<'de>,
438418
{
439419
let binary = Binary::deserialize(deserializer)?;
440-
if binary.subtype != BinarySubtype::UuidOld {
441-
Err(de::Error::custom("expecting BinarySubtype::UuidOld"))
442-
} else if binary.bytes.len() != 16 {
443-
Err(de::Error::custom("expecting 16 bytes"))
444-
} else {
445-
let mut buf = [0u8; 16];
446-
buf.copy_from_slice(&binary.bytes);
447-
buf[0..8].reverse();
448-
buf[8..16].reverse();
449-
Ok(Uuid::from_bytes(buf))
450-
}
420+
binary
421+
.to_uuid_with_representation(UuidRepresentation::JavaLegacy)
422+
.map_err(de::Error::custom)
451423
}
452424
}
453425

@@ -471,18 +443,15 @@ pub mod uuid_as_java_legacy_binary {
471443
#[cfg(feature = "uuid-0_8")]
472444
#[cfg_attr(docsrs, doc(cfg(feature = "uuid-0_8")))]
473445
pub mod uuid_as_python_legacy_binary {
474-
use crate::{spec::BinarySubtype, Binary};
446+
use crate::{bson::UuidRepresentation, Binary};
475447
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
476448
use std::result::Result;
477449
use uuid::Uuid;
478450

479451
/// Serializes a Uuid as a Binary in a Python Legacy UUID format.
480452
#[cfg_attr(docsrs, doc(cfg(feature = "uuid-0_8")))]
481453
pub fn serialize<S: Serializer>(val: &Uuid, serializer: S) -> Result<S::Ok, S::Error> {
482-
let binary = Binary {
483-
subtype: BinarySubtype::UuidOld,
484-
bytes: val.as_bytes().to_vec(),
485-
};
454+
let binary = Binary::from_uuid_with_representation(*val, UuidRepresentation::PythonLegacy);
486455
binary.serialize(serializer)
487456
}
488457

@@ -493,15 +462,9 @@ pub mod uuid_as_python_legacy_binary {
493462
D: Deserializer<'de>,
494463
{
495464
let binary = Binary::deserialize(deserializer)?;
496-
if binary.subtype != BinarySubtype::UuidOld {
497-
Err(de::Error::custom("expecting BinarySubtype::UuidOld"))
498-
} else if binary.bytes.len() != 16 {
499-
Err(de::Error::custom("expecting 16 bytes"))
500-
} else {
501-
let mut buf = [0u8; 16];
502-
buf.copy_from_slice(&binary.bytes);
503-
Ok(Uuid::from_bytes(buf))
504-
}
465+
binary
466+
.to_uuid_with_representation(UuidRepresentation::PythonLegacy)
467+
.map_err(de::Error::custom)
505468
}
506469
}
507470

@@ -525,22 +488,15 @@ pub mod uuid_as_python_legacy_binary {
525488
#[cfg(feature = "uuid-0_8")]
526489
#[cfg_attr(docsrs, doc(cfg(feature = "uuid-0_8")))]
527490
pub mod uuid_as_c_sharp_legacy_binary {
528-
use crate::{spec::BinarySubtype, Binary};
491+
use crate::{bson::UuidRepresentation, Binary};
529492
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
530493
use std::result::Result;
531494
use uuid::Uuid;
532495

533496
/// Serializes a Uuid as a Binary in a C# Legacy UUID format.
534497
#[cfg_attr(docsrs, doc(cfg(feature = "uuid-0_8")))]
535498
pub fn serialize<S: Serializer>(val: &Uuid, serializer: S) -> Result<S::Ok, S::Error> {
536-
let mut bytes = val.as_bytes().to_vec();
537-
bytes[0..4].reverse();
538-
bytes[4..6].reverse();
539-
bytes[6..8].reverse();
540-
let binary = Binary {
541-
subtype: BinarySubtype::UuidOld,
542-
bytes,
543-
};
499+
let binary = Binary::from_uuid_with_representation(*val, UuidRepresentation::CSharpLegacy);
544500
binary.serialize(serializer)
545501
}
546502

@@ -551,18 +507,9 @@ pub mod uuid_as_c_sharp_legacy_binary {
551507
D: Deserializer<'de>,
552508
{
553509
let binary = Binary::deserialize(deserializer)?;
554-
if binary.subtype != BinarySubtype::UuidOld {
555-
Err(de::Error::custom("expecting BinarySubtype::UuidOld"))
556-
} else if binary.bytes.len() != 16 {
557-
Err(de::Error::custom("expecting 16 bytes"))
558-
} else {
559-
let mut buf = [0u8; 16];
560-
buf.copy_from_slice(&binary.bytes);
561-
buf[0..4].reverse();
562-
buf[4..6].reverse();
563-
buf[6..8].reverse();
564-
Ok(Uuid::from_bytes(buf))
565-
}
510+
binary
511+
.to_uuid_with_representation(UuidRepresentation::CSharpLegacy)
512+
.map_err(de::Error::custom)
566513
}
567514
}
568515

0 commit comments

Comments
 (0)