use crate::Prefix;
use std::fmt;
use std::marker::PhantomData;
use std::str::FromStr;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Puuid<T: Prefix> {
inner: Uuid,
_marker: PhantomData<T>,
}
impl<T: Prefix> Puuid<T> {
pub const fn from_uuid(uuid: Uuid) -> Self {
Self {
inner: uuid,
_marker: PhantomData,
}
}
pub const fn into_inner(self) -> Uuid {
self.inner
}
pub const fn prefix() -> &'static str {
T::VALUE
}
}
impl<T: Prefix> Puuid<T> {
#[cfg(feature = "v4")]
pub fn new_v4() -> Self {
Self::from_uuid(Uuid::new_v4())
}
#[cfg(feature = "v7")]
pub fn new_v7() -> Self {
Self::from_uuid(Uuid::now_v7())
}
#[cfg(feature = "v3")]
pub fn new_v3(namespace: &Uuid, name: &[u8]) -> Self {
Self::from_uuid(Uuid::new_v3(namespace, name))
}
#[cfg(feature = "v5")]
pub fn new_v5(namespace: &Uuid, name: &[u8]) -> Self {
Self::from_uuid(Uuid::new_v5(namespace, name))
}
}
impl<T: Prefix> std::ops::Deref for Puuid<T> {
type Target = Uuid;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T: Prefix> fmt::Display for Puuid<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}_{}", T::VALUE, self.inner)
}
}
impl<T: Prefix> FromStr for Puuid<T> {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let prefix = T::VALUE;
let (found_prefix, uuid_str) = s
.split_once('_')
.ok_or_else(|| "Missing '_' separator".to_string())?;
if found_prefix != prefix {
return Err(format!(
"Wrong prefix. Expected '{}', found '{}'",
prefix, found_prefix
));
}
let uuid = Uuid::from_str(uuid_str).map_err(|e| format!("Invalid UUID format: {}", e))?;
Ok(Self::from_uuid(uuid))
}
}
impl<T: Prefix> Default for Puuid<T> {
fn default() -> Self {
#[cfg(feature = "v7")]
return Self::new_v7();
#[cfg(all(not(feature = "v7"), feature = "v4"))]
return Self::new_v4();
#[cfg(all(not(feature = "v7"), not(feature = "v4")))]
return Self::from_uuid(Uuid::nil());
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prefix;
prefix!(TestPre, "test");
type TestId = Puuid<TestPre>;
#[test]
fn test_display_format() {
let raw = Uuid::nil();
let id = TestId::from_uuid(raw);
assert_eq!(id.to_string(), "test_00000000-0000-0000-0000-000000000000");
}
#[test]
fn test_parsing_success() {
let input = "test_018c6427-4f30-7f89-a1b2-c3d4e5f67890";
let id = TestId::from_str(input).expect("Should parse");
assert_eq!(id.to_string(), input);
}
#[test]
fn test_parsing_wrong_prefix() {
let input = "wrong_018c6427-4f30-7f89-a1b2-c3d4e5f67890";
let err = TestId::from_str(input).unwrap_err();
assert!(err.contains("Wrong prefix"));
}
#[test]
fn test_parsing_bad_format() {
let input = "test_notauuid";
let err = TestId::from_str(input).unwrap_err();
assert!(err.contains("Invalid UUID"));
}
}
#[cfg(feature = "sqlx")]
mod sqlx_impl {
use super::*;
use sqlx::{
Database, Decode, Encode, Type, ValueRef,
encode::{Encode, IsNull},
error::BoxDynError,
};
impl<T: Prefix, DB: Database> Type<DB> for Puuid<T>
where
str: Type<DB>,
{
fn type_info() -> <DB as Database>::TypeInfo {
<str as Type<DB>>::type_info()
}
fn compatible(ty: &<DB as Database>::TypeInfo) -> bool {
<str as Type<DB>>::compatible(ty)
}
}
impl<'r, T: Prefix, DB: Database> Decode<'r, DB> for Puuid<T>
where
&'r str: Decode<'r, DB>,
{
fn decode(value: <DB as Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
let s = <&str as Decode<DB>>::decode(value)?;
let puuid = Self::from_str(s).map_err(|e| BoxDynError::from(e))?;
Ok(puuid)
}
}
impl<'q, T: Prefix, DB: Database> Encode<'q, DB> for Puuid<T>
where
String: Encode<'q, DB>,
{
fn encode_by_ref(
&self,
buf: &mut <DB as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
<String as Encode<DB>>::encode(self.to_string(), buf)
}
}
}