use std::any::type_name;
use std::fmt::{Debug, Display, Formatter};
use std::hash::{DefaultHasher, Hash, Hasher};
use std::time::SystemTime;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LifetimeId {
bytes: [u8; 10],
}
impl LifetimeId {
pub fn random() -> Self {
let mut bytes = [0u8; 10];
let mut hash = 0u64;
static CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
for i in 0..bytes.len() {
if hash == 0 {
hash = Self::make_hash();
}
let idx = (hash % CHARSET.len() as u64) as usize;
bytes[i] = CHARSET[idx];
hash >>= 5;
}
Self { bytes }
}
pub fn hyphenated(&self) -> Hyphenated<'_> {
Hyphenated(self)
}
pub fn underscored(&self) -> Underscored<'_> {
Underscored(self)
}
pub fn dotted(&self) -> Dotted<'_> {
Dotted(self)
}
pub fn glued(&self) -> Glued<'_> {
Glued(self)
}
}
impl LifetimeId {
fn make_hash() -> u64 {
let seed = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("current time should always be after UNIX epoch")
.as_nanos();
let mut hasher = DefaultHasher::new();
seed.hash(&mut hasher);
hasher.finish()
}
pub fn view_bytes(&self) -> &[u8; 10] {
&self.bytes
}
pub fn view_glued(&self) -> &str {
std::str::from_utf8(&self.bytes).expect(concat!(
"it should be possible to view the internal buffer as a &str because",
" the constructor of this struct always interprets the input string",
" as a sequence of valid UTF-8 characters",
))
}
pub fn view_chunks(&self) -> (&str, &str, &str) {
let chunk_a = std::str::from_utf8(&self.bytes[0..3]).expect(concat!(
"it should be possible to view the internal buffer as a &str because",
" the constructor of this struct always interprets the input string",
" as a sequence of valid UTF-8 characters",
));
let chunk_b = std::str::from_utf8(&self.bytes[3..7]).expect(concat!(
"it should be possible to view the internal buffer as a &str because",
" the constructor of this struct always interprets the input string",
" as a sequence of valid UTF-8 characters",
));
let chunk_c = std::str::from_utf8(&self.bytes[7..10]).expect(concat!(
"it should be possible to view the internal buffer as a &str because",
" the constructor of this struct always interprets the input string",
" as a sequence of valid UTF-8 characters",
));
(chunk_a, chunk_b, chunk_c)
}
}
impl Display for LifetimeId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.hyphenated(), f)
}
}
impl Debug for LifetimeId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct(type_name::<Self>())
.field("bytes", &self.hyphenated())
.finish()
}
}
pub struct Hyphenated<'a>(&'a LifetimeId);
impl<'a> Display for Hyphenated<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let (chunk_a, chunk_b, chunk_c) = self.0.view_chunks();
write!(f, "{}-{}-{}", chunk_a, chunk_b, chunk_c)
}
}
impl<'a> Debug for Hyphenated<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
pub struct Underscored<'a>(&'a LifetimeId);
impl<'a> Display for Underscored<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let (chunk_a, chunk_b, chunk_c) = self.0.view_chunks();
write!(f, "{}_{}_{}", chunk_a, chunk_b, chunk_c)
}
}
impl<'a> Debug for Underscored<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
pub struct Dotted<'a>(&'a LifetimeId);
impl<'a> Display for Dotted<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let (chunk_a, chunk_b, chunk_c) = self.0.view_chunks();
write!(f, "{}.{}.{}", chunk_a, chunk_b, chunk_c)
}
}
impl<'a> Debug for Dotted<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
pub struct Glued<'a>(&'a LifetimeId);
impl<'a> Display for Glued<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.view_glued())
}
}
impl<'a> Debug for Glued<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::{assert_eq, assert_ne};
#[test]
fn generate_lifetime_id() {
let lifetime_id = super::LifetimeId::random().to_string();
assert_eq!(lifetime_id.len(), 12);
assert!(
lifetime_id
.chars()
.all(|c| c.is_ascii_alphabetic() || c == '-')
);
assert!(
lifetime_id
.chars()
.all(|c| c.is_ascii_lowercase() || c == '-')
);
assert!(
matches!(lifetime_id.chars().nth(0), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
);
assert!(
matches!(lifetime_id.chars().nth(1), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
);
assert!(
matches!(lifetime_id.chars().nth(2), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
);
assert!(matches!(lifetime_id.chars().nth(3), Some(c) if c == '-'));
assert!(
matches!(lifetime_id.chars().nth(4), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
);
assert!(
matches!(lifetime_id.chars().nth(5), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
);
assert!(
matches!(lifetime_id.chars().nth(6), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
);
assert!(
matches!(lifetime_id.chars().nth(7), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
);
assert!(matches!(lifetime_id.chars().nth(8), Some(c) if c == '-'));
assert!(
matches!(lifetime_id.chars().nth(9), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
);
assert!(
matches!(lifetime_id.chars().nth(10), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
);
assert!(
matches!(lifetime_id.chars().nth(11), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
);
}
#[test]
fn generate_lifetime_ids() {
let lifetime_id_a = super::LifetimeId::random().to_string();
let lifetime_id_b = super::LifetimeId::random().to_string();
assert_ne!(lifetime_id_a, lifetime_id_b);
}
}