tarantool 10.0.0

Tarantool rust bindings
Documentation
use crate::ffi::uuid as ffi;
use std::convert::{TryFrom, TryInto};
use std::io::Write;

use crate::msgpack;
use crate::msgpack::{Context, Decode, DecodeError, Encode, EncodeError};
pub use ::uuid::{adapter, Error};
use serde::{Deserialize, Serialize};

type Inner = ::uuid::Uuid;

#[derive(Debug, Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd, Default)]
pub struct Uuid {
    inner: Inner,
}

impl Uuid {
    #[inline(always)]
    pub fn random() -> Self {
        unsafe {
            let mut tt = std::mem::MaybeUninit::uninit();
            ffi::tt_uuid_create(tt.as_mut_ptr());
            Self::from_tt_uuid(tt.assume_init())
        }
    }

    #[inline(always)]
    pub fn from_inner(inner: Inner) -> Self {
        inner.into()
    }

    #[inline(always)]
    pub fn into_inner(self) -> Inner {
        self.into()
    }

    /// Convert an array of bytes in the big endian order into a `Uuid`.
    #[inline(always)]
    pub fn from_bytes(bytes: [u8; 16]) -> Self {
        Inner::from_bytes(bytes).into()
    }

    /// Convert a slice of bytes in the big endian order into a `Uuid`. Return
    /// `None` if there's not enough bytes in the slice.
    #[inline(always)]
    pub fn try_from_slice(bytes: &[u8]) -> Option<Self> {
        std::convert::TryInto::try_into(bytes)
            .ok()
            .map(Self::from_bytes)
    }

    /// Convert the tarantool native (little endian) uuid representation into a
    /// `Uuid`.
    #[inline(always)]
    pub fn from_tt_uuid(mut tt: ffi::tt_uuid) -> Self {
        unsafe {
            tt.tl = tt.tl.swap_bytes();
            tt.tm = tt.tm.swap_bytes();
            tt.th = tt.th.swap_bytes();
            let bytes: [u8; 16] = std::mem::transmute(tt);
            Self::from_bytes(bytes)
        }
    }

    /// Return an array of bytes in tarantool native (little endian) format
    #[inline(always)]
    pub fn to_tt_uuid(&self) -> ffi::tt_uuid {
        unsafe {
            let mut tt: ffi::tt_uuid = std::mem::transmute(*self.inner.as_bytes());
            tt.tl = tt.tl.swap_bytes();
            tt.tm = tt.tm.swap_bytes();
            tt.th = tt.th.swap_bytes();
            tt
        }
    }

    /// Return an array of bytes in the big endian order
    #[inline(always)]
    pub fn as_bytes(&self) -> &[u8; 16] {
        self.inner.as_bytes()
    }

    /// The 'nil UUID'.
    ///
    /// The nil UUID is special form of UUID that is specified to have all
    /// 128 bits set to zero, as defined in [IETF RFC 4122 Section 4.1.7][RFC].
    ///
    /// [RFC]: https://tools.ietf.org/html/rfc4122.html#section-4.1.7
    ///
    /// # Examples
    ///
    /// Basic usage:
    ///
    /// ```
    /// use uuid::Uuid;
    ///
    /// let uuid = Uuid::nil();
    ///
    /// assert_eq!(
    ///     uuid.to_hyphenated().to_string(),
    ///     "00000000-0000-0000-0000-000000000000"
    /// );
    /// ```
    #[inline(always)]
    pub fn nil() -> Self {
        Inner::nil().into()
    }

    /// Tests if the UUID is nil.
    #[inline(always)]
    pub fn is_nil(&self) -> bool {
        self.inner.is_nil()
    }

    /// Parses a `Uuid` from a string of hexadecimal digits with optional
    /// hyphens.
    ///
    /// Any of the formats generated by this module (simple, hyphenated, urn)
    /// are supported by this parsing function.
    #[inline(always)]
    pub fn parse_str(input: &str) -> Result<Self, Error> {
        Inner::parse_str(input).map(Self::from)
    }

    /// Get a [`Hyphenated`] formatter.
    ///
    /// [`Hyphenated`]: adapter/struct.Hyphenated.html
    #[inline(always)]
    pub const fn to_hyphenated(self) -> adapter::Hyphenated {
        self.inner.to_hyphenated()
    }

    /// Get a borrowed [`HyphenatedRef`] formatter.
    ///
    /// [`HyphenatedRef`]: adapter/struct.HyphenatedRef.html
    #[inline(always)]
    pub const fn to_hyphenated_ref(&self) -> adapter::HyphenatedRef<'_> {
        self.inner.to_hyphenated_ref()
    }

    /// Get a [`Simple`] formatter.
    ///
    /// [`Simple`]: adapter/struct.Simple.html
    #[inline(always)]
    pub const fn to_simple(self) -> adapter::Simple {
        self.inner.to_simple()
    }

    /// Get a borrowed [`SimpleRef`] formatter.
    ///
    /// [`SimpleRef`]: adapter/struct.SimpleRef.html
    #[inline(always)]
    pub const fn to_simple_ref(&self) -> adapter::SimpleRef<'_> {
        self.inner.to_simple_ref()
    }

    /// Get a [`Urn`] formatter.
    ///
    /// [`Urn`]: adapter/struct.Urn.html
    #[inline(always)]
    pub const fn to_urn(self) -> adapter::Urn {
        self.inner.to_urn()
    }

    /// Get a borrowed [`UrnRef`] formatter.
    ///
    /// [`UrnRef`]: adapter/struct.UrnRef.html
    #[inline(always)]
    pub const fn to_urn_ref(&self) -> adapter::UrnRef<'_> {
        self.inner.to_urn_ref()
    }

    fn from_ext_structure(tag: i8, bytes: &[u8]) -> Result<Self, String> {
        if tag != ffi::MP_UUID {
            return Err(format!("Expected UUID, found msgpack ext #{}", tag));
        }

        Self::try_from_slice(bytes).ok_or_else(|| {
            format!(
                "Not enough bytes for UUID: expected 16, got {}",
                bytes.len()
            )
        })
    }
}

impl<'a> TryFrom<msgpack::ExtStruct<'a>> for Uuid {
    type Error = String;

    #[inline(always)]
    fn try_from(value: msgpack::ExtStruct<'a>) -> Result<Self, Self::Error> {
        Self::from_ext_structure(value.tag, value.data)
    }
}

impl From<Inner> for Uuid {
    #[inline(always)]
    fn from(inner: Inner) -> Self {
        Self { inner }
    }
}

impl From<Uuid> for Inner {
    #[inline(always)]
    fn from(uuid: Uuid) -> Self {
        uuid.inner
    }
}

impl std::fmt::Display for Uuid {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.inner.fmt(f)
    }
}

impl std::fmt::LowerHex for Uuid {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        std::fmt::LowerHex::fmt(&self.inner, f)
    }
}

impl std::fmt::UpperHex for Uuid {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        std::fmt::UpperHex::fmt(&self.inner, f)
    }
}

impl std::str::FromStr for Uuid {
    type Err = Error;

    fn from_str(uuid_str: &str) -> Result<Self, Self::Err> {
        Self::parse_str(uuid_str)
    }
}

////////////////////////////////////////////////////////////////////////////////
// Tuple
////////////////////////////////////////////////////////////////////////////////

impl Encode for Uuid {
    fn encode(&self, w: &mut impl Write, context: &Context) -> Result<(), EncodeError> {
        msgpack::ExtStruct::new(ffi::MP_UUID, self.as_bytes()).encode(w, context)
    }
}

impl<'de> Decode<'de> for Uuid {
    fn decode(r: &mut &'de [u8], context: &Context) -> Result<Self, DecodeError> {
        msgpack::ExtStruct::decode(r, context)?
            .try_into()
            .map_err(DecodeError::new::<Self>)
    }
}

impl serde::Serialize for Uuid {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        #[derive(Serialize)]
        struct _ExtStruct<'a>((i8, &'a serde_bytes::Bytes));

        let data = self.as_bytes();
        _ExtStruct((ffi::MP_UUID, serde_bytes::Bytes::new(data))).serialize(serializer)
    }
}

impl<'de> serde::Deserialize<'de> for Uuid {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct _ExtStruct((i8, serde_bytes::ByteBuf));

        let _ExtStruct((tag, bytes)) = Deserialize::deserialize(deserializer)?;

        Self::from_ext_structure(tag, bytes.as_slice()).map_err(serde::de::Error::custom)
    }
}

////////////////////////////////////////////////////////////////////////////////
// Lua
////////////////////////////////////////////////////////////////////////////////

static mut CTID_UUID: Option<u32> = None;

fn ctid_uuid() -> u32 {
    // SAFETY: only safe to call this from tx thread
    unsafe {
        if (*std::ptr::addr_of!(CTID_UUID)).is_none() {
            let lua = crate::global_lua();
            let ctid_uuid =
                tlua::ffi::luaL_ctypeid(tlua::AsLua::as_lua(&lua), crate::c_ptr!("struct tt_uuid"));
            assert!(ctid_uuid != 0);
            CTID_UUID = Some(ctid_uuid)
        }
        CTID_UUID.unwrap()
    }
}

unsafe impl tlua::AsCData for ffi::tt_uuid {
    fn ctypeid() -> tlua::ffi::CTypeID {
        ctid_uuid()
    }
}

impl<L> tlua::LuaRead<L> for Uuid
where
    L: tlua::AsLua,
{
    fn lua_read_at_position(lua: L, index: std::num::NonZeroI32) -> tlua::ReadResult<Self, L> {
        let tlua::CData(uuid) = lua.read_at_nz(index)?;
        Ok(Self::from_tt_uuid(uuid))
    }
}

impl<L: tlua::AsLua> tlua::Push<L> for Uuid {
    type Err = tlua::Void;

    #[inline(always)]
    fn push_to_lua(&self, lua: L) -> Result<tlua::PushGuard<L>, (Self::Err, L)> {
        Ok(lua.push_one(tlua::CData(self.to_tt_uuid())))
    }
}

impl<L: tlua::AsLua> tlua::PushOne<L> for Uuid {}

impl<L: tlua::AsLua> tlua::PushInto<L> for Uuid {
    type Err = tlua::Void;

    fn push_into_lua(self, lua: L) -> Result<tlua::PushGuard<L>, (Self::Err, L)> {
        Ok(lua.push_one(tlua::CData(self.to_tt_uuid())))
    }
}

impl<L: tlua::AsLua> tlua::PushOneInto<L> for Uuid {}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::msgpack;

    #[test]
    fn serialize() {
        let result = [
            216, 2, 227, 6, 158, 228, 241, 69, 78, 73, 160, 56, 166, 128, 14, 42, 161, 217,
        ];
        let uuid = Uuid::parse_str("e3069ee4-f145-4e49-a038-a6800e2aa1d9").unwrap();
        let serde_data = rmp_serde::to_vec(&uuid).unwrap();
        assert_eq!(serde_data, result);

        let msgpack_data = msgpack::encode(&uuid);
        assert_eq!(msgpack_data, result);
    }

    #[test]
    fn deserialize() {
        let uuid = Uuid {
            inner: uuid::Uuid::NAMESPACE_DNS,
        };
        let serde_data = rmp_serde::to_vec(&uuid).unwrap();

        let msgpack_uuid = msgpack::decode(serde_data.as_slice()).unwrap();
        assert_eq!(uuid, msgpack_uuid);

        let serde_uuid = rmp_serde::from_slice(serde_data.as_slice()).unwrap();
        assert_eq!(uuid, serde_uuid);
    }
}