use core::fmt;
use core::str::FromStr;
use serde::{Deserialize, Serialize};
macro_rules! impl_typed_id {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct $name(pub u64);
impl $name {
#[must_use]
pub fn from_seed(seed: &str) -> Self {
let h = blake3::hash(seed.as_bytes());
let bytes = h.as_bytes();
let id = u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3],
bytes[4], bytes[5], bytes[6], bytes[7],
]);
Self(id)
}
pub const NULL: Self = Self(0);
}
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, concat!(stringify!($name), "({:016x})"), self.0)
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:016x}", self.0)
}
}
impl FromStr for $name {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let n = u64::from_str_radix(s, 16)
.map_err(|e| anyhow::anyhow!(concat!("invalid ", stringify!($name), ": {}"), e))?;
Ok(Self(n))
}
}
};
}
impl_typed_id!(
SessionId
);
impl_typed_id!(
WindowId
);
impl_typed_id!(
PaneId
);
impl_typed_id!(
DefinitionId
);
impl DefinitionId {
#[must_use]
pub fn from_project(root: &std::path::Path) -> Self {
Self(ishou_tokens::fleet_session_names::stable_seed(
root.to_string_lossy().as_bytes(),
))
}
}
pub type InstanceId = SessionId;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_seed_is_deterministic() {
assert_eq!(PaneId::from_seed("foo"), PaneId::from_seed("foo"));
assert_ne!(PaneId::from_seed("foo"), PaneId::from_seed("bar"));
}
#[test]
fn display_round_trips_through_from_str() {
let id = SessionId::from_seed("session-7");
let s = id.to_string();
assert_eq!(s.len(), 16);
let parsed: SessionId = s.parse().unwrap();
assert_eq!(id, parsed);
}
#[test]
fn null_is_distinct_from_any_seeded_id() {
let seeded = WindowId::from_seed("test");
assert_ne!(WindowId::NULL, seeded);
assert_eq!(WindowId::NULL.0, 0);
}
#[test]
fn definition_id_inner_is_the_project_name_seed() {
let root = std::path::Path::new("/code/pleme-io/mado");
let expected = ishou_tokens::fleet_session_names::stable_seed(
root.to_string_lossy().as_bytes(),
);
assert_eq!(DefinitionId::from_project(root).0, expected);
}
#[test]
fn definition_id_is_restart_and_call_stable() {
let root = std::path::Path::new("/x/y/z");
assert_eq!(DefinitionId::from_project(root), DefinitionId::from_project(root));
}
#[test]
fn instance_id_is_session_id_alias() {
let s: SessionId = SessionId(7);
let i: InstanceId = s;
assert_eq!(i.0, 7);
}
}