use core::{
fmt::{Debug, Display},
hash::Hash,
marker::PhantomData,
};
use uuid::Uuid;
pub trait Identifier: Copy + Eq + Hash + Debug {
type Underlying: Copy + Eq;
fn underlying(&self) -> Self::Underlying;
}
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(transparent)]
pub struct Id<Tag, U = Uuid> {
value: U,
#[serde(skip)]
_tag: PhantomData<fn() -> Tag>,
}
impl<Tag, U: Copy> Id<Tag, U> {
pub const fn from_raw(value: U) -> Self {
Self { value, _tag: PhantomData }
}
pub const fn raw(self) -> U {
self.value
}
}
impl<Tag> Id<Tag, Uuid> {
#[cfg(not(target_arch = "wasm32"))]
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self::from_raw(Uuid::new_v4())
}
}
impl<Tag, U: Copy> Clone for Id<Tag, U> {
fn clone(&self) -> Self {
*self
}
}
impl<Tag, U: Copy> Copy for Id<Tag, U> {}
impl<Tag, U: PartialEq> PartialEq for Id<Tag, U> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<Tag, U: Eq> Eq for Id<Tag, U> {}
impl<Tag, U: Hash> Hash for Id<Tag, U> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
impl<Tag, U: Debug> Debug for Id<Tag, U> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Debug::fmt(&self.value, f)
}
}
impl<Tag, U: Display> Display for Id<Tag, U> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Display::fmt(&self.value, f)
}
}
impl<Tag, U: Copy + Eq + Hash + Debug> Identifier for Id<Tag, U> {
type Underlying = U;
fn underlying(&self) -> U {
self.value
}
}
impl<Tag, U: Copy> From<U> for Id<Tag, U> {
fn from(value: U) -> Self {
Self::from_raw(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
struct AccountTag;
struct TransferTag;
type AccountId = Id<AccountTag, u128>;
type TransferId = Id<TransferTag, u128>;
#[test]
fn round_trips_through_raw() {
let id = AccountId::from_raw(99);
assert_eq!(id.raw(), 99);
assert_eq!(id.underlying(), 99);
assert_eq!(AccountId::from(99), id);
}
#[test]
fn equality_is_by_value_within_a_tag() {
assert_eq!(AccountId::from_raw(1), AccountId::from_raw(1));
assert_ne!(AccountId::from_raw(1), AccountId::from_raw(2));
}
#[test]
fn distinct_tags_hash_independently() {
use std::collections::HashMap;
let mut by_account: HashMap<AccountId, &str> = HashMap::new();
by_account.insert(AccountId::from_raw(1), "a");
assert_eq!(by_account.get(&AccountId::from_raw(1)), Some(&"a"));
let _t = TransferId::from_raw(1);
}
#[test]
fn serializes_transparently() {
let json = serde_json::to_string(&AccountId::from_raw(7)).unwrap();
assert_eq!(json, "7");
let back: AccountId = serde_json::from_str(&json).unwrap();
assert_eq!(back, AccountId::from_raw(7));
}
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn new_mints_unique_uuids() {
struct DocTag;
type DocId = Id<DocTag>;
assert_ne!(DocId::new(), DocId::new());
}
}