use std::{fmt, sync::Arc};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct SessionId(Arc<str>);
impl SessionId {
pub fn new(id: impl Into<Arc<str>>) -> Self {
Self(id.into())
}
pub fn generate() -> Self {
let mut buf = [0u8; uuid::fmt::Simple::LENGTH];
let hex = uuid::Uuid::new_v4().simple().encode_lower(&mut buf);
Self(Arc::from(&*hex))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for SessionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl From<&str> for SessionId {
fn from(id: &str) -> Self {
Self::new(id)
}
}
impl From<String> for SessionId {
fn from(id: String) -> Self {
Self::new(id)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn is_lowercase_hex(s: &str) -> bool {
!s.is_empty()
&& s.bytes()
.all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
}
#[test]
fn generate_is_32_lowercase_hex_chars() {
let id = SessionId::generate();
assert_eq!(id.as_str().len(), 32, "expected the 16-byte CID shape");
assert!(
is_lowercase_hex(id.as_str()),
"not lowercase hex: {id}, which would not compare against an mlog filename"
);
}
#[test]
fn generate_has_no_hyphens() {
let id = SessionId::generate();
assert!(!id.as_str().contains('-'), "hyphenated form leaked: {id}");
}
#[test]
fn generate_is_unique_per_call() {
let a = SessionId::generate();
let b = SessionId::generate();
assert_ne!(a, b);
}
#[test]
fn new_preserves_a_connection_id_verbatim() {
let cid = "8f2a1c94d7e3b60518aa4c2f9d013e77";
assert_eq!(SessionId::new(cid).as_str(), cid);
}
#[test]
fn display_matches_as_str() {
let id = SessionId::generate();
assert_eq!(id.to_string(), id.as_str());
}
#[test]
fn clone_shares_the_allocation() {
let id = SessionId::generate();
let cloned = id.clone();
assert!(
std::ptr::eq(id.as_str().as_ptr(), cloned.as_str().as_ptr()),
"clone reallocated instead of sharing"
);
}
#[test]
fn accepts_the_rfc_connection_id_length_bounds() {
let shortest = ""; let longest = "0011223344556677889900aabbccddeeff001122"; assert_eq!(SessionId::new(shortest).as_str().len(), 0);
assert_eq!(SessionId::new(longest).as_str().len(), 40);
assert!(is_lowercase_hex(longest));
}
}