use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::{self, Display};
use std::hash::Hash;
use std::mem::transmute;
use std::str::FromStr;
use std::time::{Duration, UNIX_EPOCH};
use serde::{Deserialize, Serialize};
use ulid::Ulid;
use uuid::{Uuid, Version};
use crate::error::IdentifierError;
pub trait Identifier:
Sized + Clone + PartialEq + Eq + Hash + Display + Serialize + for<'de> Deserialize<'de>
{
fn parse<S: AsRef<str>>(s: S) -> Result<Self, IdentifierError>;
fn generate() -> Self;
fn as_str(&self) -> &str;
fn timestamp_ms(&self) -> Option<u64>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct UuidIdentifier(Uuid);
thread_local! {
static UUID_CACHE: RefCell<HashMap<Uuid, String>> =
RefCell::new(HashMap::new());
}
impl UuidIdentifier {
pub fn new_v4() -> Self {
Self(Uuid::new_v4())
}
pub fn new_v5(namespace: &Uuid, name: &str) -> Self {
Self(Uuid::new_v5(namespace, name.as_bytes()))
}
pub fn uuid(&self) -> Uuid {
self.0
}
pub fn version(&self) -> Option<Version> {
self.0.get_version()
}
}
impl Identifier for UuidIdentifier {
fn parse<S: AsRef<str>>(s: S) -> Result<Self, IdentifierError> {
Ok(Self(
Uuid::parse_str(s.as_ref()).map_err(IdentifierError::from)?,
))
}
fn generate() -> Self {
Self::new_v4()
}
fn as_str(&self) -> &str {
UUID_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
cache.entry(self.0).or_insert_with(|| self.0.to_string());
unsafe { transmute(cache.get(&self.0).unwrap().as_str()) }
})
}
fn timestamp_ms(&self) -> Option<u64> {
None
}
}
impl Display for UuidIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl From<Uuid> for UuidIdentifier {
fn from(uuid: Uuid) -> Self {
Self(uuid)
}
}
impl FromStr for UuidIdentifier {
type Err = IdentifierError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct UlidIdentifier(Ulid);
thread_local! {
static ULID_CACHE: RefCell<HashMap<Ulid, String>> =
RefCell::new(HashMap::new());
}
impl Default for UlidIdentifier {
fn default() -> Self {
Self::new()
}
}
impl UlidIdentifier {
pub fn new() -> Self {
Self(Ulid::new())
}
pub fn with_timestamp(timestamp_ms: u64) -> Self {
Self(Ulid::from_datetime(
UNIX_EPOCH + Duration::from_millis(timestamp_ms),
))
}
pub fn ulid(&self) -> Ulid {
self.0
}
pub fn get_timestamp_ms(&self) -> u64 {
let datetime = self.0.datetime();
let since_epoch = datetime
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::from_secs(0));
since_epoch.as_millis() as u64
}
pub fn monotonic_from(previous: Option<&Self>) -> Self {
match previous {
Some(prev) => {
let new_ulid = Ulid::new();
let prev_ms = prev.get_timestamp_ms();
let new_ms = {
let datetime = new_ulid.datetime();
let since_epoch = datetime
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::from_secs(0));
since_epoch.as_millis() as u64
};
if new_ms <= prev_ms {
Self(Ulid::from_datetime(
UNIX_EPOCH + Duration::from_millis(prev_ms + 1),
))
} else {
Self(new_ulid)
}
}
None => Self::new(),
}
}
}
impl Identifier for UlidIdentifier {
fn parse<S: AsRef<str>>(s: S) -> Result<Self, IdentifierError> {
Ok(Self(
Ulid::from_string(s.as_ref()).map_err(IdentifierError::from)?,
))
}
fn generate() -> Self {
Self::new()
}
fn as_str(&self) -> &str {
ULID_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
cache.entry(self.0).or_insert_with(|| self.0.to_string());
unsafe { transmute(cache.get(&self.0).unwrap().as_str()) }
})
}
fn timestamp_ms(&self) -> Option<u64> {
Some(self.get_timestamp_ms())
}
}
impl Display for UlidIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<Ulid> for UlidIdentifier {
fn from(ulid: Ulid) -> Self {
Self(ulid)
}
}
impl FromStr for UlidIdentifier {
type Err = IdentifierError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_uuid_identifier() {
let id = UuidIdentifier::generate();
let id_str = id.as_str();
let parsed = UuidIdentifier::parse(id_str).unwrap();
assert_eq!(id, parsed);
}
#[test]
fn test_ulid_identifier() {
let id = UlidIdentifier::generate();
let id_str = id.as_str();
let parsed = UlidIdentifier::parse(id_str).unwrap();
assert_eq!(id, parsed);
assert!(id.timestamp_ms().is_some());
}
}