#[cfg(test)]
mod test;
use std::{
fmt::{self, Display},
str::FromStr,
};
use serde::{Deserialize, Serialize};
use crate::{de::BsonVisitor, spec::BinarySubtype, Binary, Bson};
pub(crate) const UUID_NEWTYPE_NAME: &str = "$__bson_private_uuid";
#[derive(Clone, Copy, PartialEq, Hash)]
pub struct Uuid {
uuid: uuid::Uuid,
}
impl Uuid {
pub fn new() -> Self {
Self {
uuid: uuid::Uuid::new_v4(),
}
}
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
Self::from_external_uuid(uuid::Uuid::from_bytes(bytes))
}
pub fn parse_str(input: impl AsRef<str>) -> Result<Self> {
let uuid = uuid::Uuid::parse_str(input.as_ref()).map_err(|e| Error::InvalidUuidString {
message: e.to_string(),
})?;
Ok(Self::from_external_uuid(uuid))
}
pub(crate) const fn from_external_uuid(uuid: uuid::Uuid) -> Self {
Self { uuid }
}
pub const fn bytes(self) -> [u8; 16] {
*self.uuid.as_bytes()
}
}
impl Default for Uuid {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "uuid-0_8")]
#[cfg_attr(docsrs, doc(cfg(feature = "uuid-0_8")))]
impl Uuid {
pub fn from_uuid_0_8(uuid: uuid::Uuid) -> Self {
Self::from_external_uuid(uuid)
}
pub fn to_uuid_0_8(self) -> uuid::Uuid {
self.uuid
}
}
impl Serialize for Uuid {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_newtype_struct(UUID_NEWTYPE_NAME, &self.uuid)
}
}
impl<'de> Deserialize<'de> for Uuid {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
match deserializer.deserialize_newtype_struct(UUID_NEWTYPE_NAME, BsonVisitor)? {
Bson::Binary(b)
if matches!(b.subtype, BinarySubtype::Uuid | BinarySubtype::Generic) =>
{
let uuid =
uuid::Uuid::from_slice(b.bytes.as_slice()).map_err(serde::de::Error::custom)?;
Ok(Self::from_external_uuid(uuid))
}
Bson::Binary(b) if b.subtype == BinarySubtype::UuidOld => {
Err(serde::de::Error::custom(
"received legacy UUID (subtype 3) but expected regular UUID (subtype 4)",
))
}
Bson::String(s) => {
let uuid = uuid::Uuid::from_str(s.as_str()).map_err(serde::de::Error::custom)?;
Ok(Self::from_external_uuid(uuid))
}
b => Err(serde::de::Error::invalid_type(b.as_unexpected(), &"a UUID")),
}
}
}
impl Display for Uuid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.uuid.fmt(f)
}
}
impl std::fmt::Debug for Uuid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
std::fmt::Debug::fmt(&self.uuid, f)
}
}
impl From<Uuid> for Binary {
fn from(uuid: Uuid) -> Self {
Binary {
subtype: BinarySubtype::Uuid,
bytes: uuid.bytes().to_vec(),
}
}
}
impl From<Uuid> for Bson {
fn from(u: Uuid) -> Self {
Bson::Binary(u.into())
}
}
#[cfg(feature = "uuid-0_8")]
impl From<uuid::Uuid> for Uuid {
fn from(u: uuid::Uuid) -> Self {
Self::from_external_uuid(u)
}
}
#[cfg(feature = "uuid-0_8")]
impl From<Uuid> for uuid::Uuid {
fn from(s: Uuid) -> Self {
s.to_uuid_0_8()
}
}
#[non_exhaustive]
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum UuidRepresentation {
Standard,
CSharpLegacy,
JavaLegacy,
PythonLegacy,
}
impl Binary {
pub fn from_uuid(uuid: Uuid) -> Self {
Binary::from(uuid)
}
pub fn from_uuid_with_representation(uuid: Uuid, rep: UuidRepresentation) -> Self {
match rep {
UuidRepresentation::Standard => Binary::from_uuid(uuid),
UuidRepresentation::CSharpLegacy => {
let mut bytes = uuid.bytes().to_vec();
bytes[0..4].reverse();
bytes[4..6].reverse();
bytes[6..8].reverse();
Binary {
subtype: BinarySubtype::UuidOld,
bytes,
}
}
UuidRepresentation::PythonLegacy => Binary {
subtype: BinarySubtype::UuidOld,
bytes: uuid.bytes().to_vec(),
},
UuidRepresentation::JavaLegacy => {
let mut bytes = uuid.bytes().to_vec();
bytes[0..8].reverse();
bytes[8..16].reverse();
Binary {
subtype: BinarySubtype::UuidOld,
bytes,
}
}
}
}
pub fn to_uuid_with_representation(&self, rep: UuidRepresentation) -> Result<Uuid> {
if rep != UuidRepresentation::Standard && self.subtype != BinarySubtype::UuidOld {
return Err(Error::RepresentationMismatch {
requested_representation: rep,
actual_binary_subtype: self.subtype,
expected_binary_subtype: BinarySubtype::UuidOld,
});
}
if rep == UuidRepresentation::Standard && self.subtype != BinarySubtype::Uuid {
return Err(Error::RepresentationMismatch {
requested_representation: rep,
actual_binary_subtype: self.subtype,
expected_binary_subtype: BinarySubtype::Uuid,
});
}
if self.bytes.len() != 16 {
return Err(Error::InvalidLength {
length: self.bytes.len(),
});
}
let mut buf = [0u8; 16];
buf.copy_from_slice(&self.bytes);
Ok(match rep {
UuidRepresentation::Standard => Uuid::from_bytes(buf),
UuidRepresentation::CSharpLegacy => {
buf[0..4].reverse();
buf[4..6].reverse();
buf[6..8].reverse();
Uuid::from_bytes(buf)
}
UuidRepresentation::PythonLegacy => Uuid::from_bytes(buf),
UuidRepresentation::JavaLegacy => {
buf[0..8].reverse();
buf[8..16].reverse();
Uuid::from_bytes(buf)
}
})
}
pub fn to_uuid(&self) -> Result<Uuid> {
self.to_uuid_with_representation(UuidRepresentation::Standard)
}
}
#[cfg(feature = "uuid-0_8")]
#[cfg_attr(docsrs, doc(cfg(feature = "uuid-0_8")))]
impl From<uuid::Uuid> for Binary {
fn from(uuid: uuid::Uuid) -> Self {
Binary {
subtype: BinarySubtype::Uuid,
bytes: uuid.as_bytes().to_vec(),
}
}
}
#[cfg(all(feature = "uuid-0_8", feature = "serde_with"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "uuid-0_8", feature = "serde_with"))))]
impl<'de> serde_with::DeserializeAs<'de, uuid::Uuid> for crate::Uuid {
fn deserialize_as<D>(deserializer: D) -> std::result::Result<uuid::Uuid, D::Error>
where
D: serde::Deserializer<'de>,
{
let uuid = Uuid::deserialize(deserializer)?;
Ok(uuid.into())
}
}
#[cfg(all(feature = "uuid-0_8", feature = "serde_with"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "uuid-0_8", feature = "sere_with"))))]
impl serde_with::SerializeAs<uuid::Uuid> for crate::Uuid {
fn serialize_as<S>(source: &uuid::Uuid, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let uuid = Uuid::from_external_uuid(*source);
uuid.serialize(serializer)
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Error {
#[non_exhaustive]
InvalidUuidString { message: String },
#[non_exhaustive]
RepresentationMismatch {
expected_binary_subtype: BinarySubtype,
actual_binary_subtype: BinarySubtype,
requested_representation: UuidRepresentation,
},
#[non_exhaustive]
InvalidLength {
length: usize,
},
}
pub type Result<T> = std::result::Result<T, Error>;
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::InvalidUuidString { message } => {
write!(fmt, "{}", message)
}
Error::RepresentationMismatch {
expected_binary_subtype,
actual_binary_subtype,
requested_representation,
} => {
write!(
fmt,
"expected {:?} when converting to UUID with {:?}, isntead got {:?}",
expected_binary_subtype, requested_representation, actual_binary_subtype
)
}
Error::InvalidLength { length } => {
write!(
fmt,
"expected UUID to contain 16 bytes, instead got {}",
length
)
}
}
}
}
impl std::error::Error for Error {}