use std::any::Any;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct Token<T> {
value: Arc<T>,
created_at: u64,
}
impl<T> Token<T> {
pub fn new(value: T) -> Self {
Self {
value: Arc::new(value),
created_at: now_millis(),
}
}
pub fn at(value: T, created_at: u64) -> Self {
Self {
value: Arc::new(value),
created_at,
}
}
pub fn from_arc(value: Arc<T>, created_at: u64) -> Self {
Self { value, created_at }
}
pub fn value(&self) -> &T {
&self.value
}
pub fn value_arc(&self) -> Arc<T> {
Arc::clone(&self.value)
}
pub fn created_at(&self) -> u64 {
self.created_at
}
}
pub fn unit_token() -> Token<()> {
Token {
value: Arc::new(()),
created_at: 0,
}
}
#[derive(Debug, Clone)]
pub struct ErasedToken {
pub value: Arc<dyn Any + Send + Sync>,
pub created_at: u64,
}
impl ErasedToken {
pub fn from_typed<T: Send + Sync + 'static>(token: &Token<T>) -> Self {
Self {
value: Arc::clone(&token.value) as Arc<dyn Any + Send + Sync>,
created_at: token.created_at,
}
}
pub fn downcast<T: Send + Sync + 'static>(&self) -> Option<Token<T>> {
self.value.downcast_ref::<T>().map(|_| {
let value = Arc::clone(&self.value);
let typed: Arc<T> = unsafe {
let raw = Arc::into_raw(value);
Arc::from_raw(raw.cast::<T>())
};
Token::from_arc(typed, self.created_at)
})
}
}
pub fn now_millis() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn token_new_has_current_timestamp() {
let t = Token::new(42);
assert_eq!(*t.value(), 42);
assert!(t.created_at() > 0);
}
#[test]
fn token_at_preserves_timestamp() {
let t = Token::at("hello", 1000);
assert_eq!(*t.value(), "hello");
assert_eq!(t.created_at(), 1000);
}
#[test]
fn unit_token_has_zero_timestamp() {
let t = unit_token();
assert_eq!(*t.value(), ());
assert_eq!(t.created_at(), 0);
}
#[test]
fn token_clone_is_cheap() {
let t = Token::new(vec![1, 2, 3]);
let t2 = t.clone();
assert!(Arc::ptr_eq(&t.value, &t2.value));
}
#[test]
fn erased_token_roundtrip() {
let t = Token::new(42i32);
let erased = ErasedToken::from_typed(&t);
let recovered = erased.downcast::<i32>().unwrap();
assert_eq!(*recovered.value(), 42);
assert_eq!(recovered.created_at(), t.created_at());
}
#[test]
fn erased_token_wrong_type_returns_none() {
let t = Token::new(42i32);
let erased = ErasedToken::from_typed(&t);
assert!(erased.downcast::<String>().is_none());
}
}