use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
pub struct WidgetId(u128);
impl WidgetId {
pub fn app_root() -> Self {
Self::explicit("fission.app.root")
}
pub const fn from_u128(val: u128) -> Self {
Self(val)
}
pub fn as_u128(&self) -> u128 {
self.0
}
pub fn explicit(key: &str) -> Self {
let mut hasher = blake3::Hasher::new();
hasher.update(b"explicit:");
hasher.update(key.as_bytes());
let hash = hasher.finalize();
Self(u128::from_le_bytes(
hash.as_bytes()[0..16].try_into().unwrap(),
))
}
pub fn derived(parent: u128, path: &[u32]) -> Self {
let mut hasher = blake3::Hasher::new();
hasher.update(b"derived:");
hasher.update(&parent.to_le_bytes());
for index in path {
hasher.update(&index.to_le_bytes());
}
let hash = hasher.finalize();
Self(u128::from_le_bytes(
hash.as_bytes()[0..16].try_into().unwrap(),
))
}
pub fn scoped(parent: u128, key: &str) -> Self {
let mut hasher = blake3::Hasher::new();
hasher.update(b"scoped:");
hasher.update(&parent.to_le_bytes());
hasher.update(key.as_bytes());
let hash = hasher.finalize();
Self(u128::from_le_bytes(
hash.as_bytes()[0..16].try_into().unwrap(),
))
}
#[doc(hidden)]
pub fn scoped_location(parent: u128, file: &str, line: u32, column: u32) -> Self {
let mut hasher = blake3::Hasher::new();
hasher.update(b"source-location:");
hasher.update(&parent.to_le_bytes());
hasher.update(file.as_bytes());
hasher.update(&line.to_le_bytes());
hasher.update(&column.to_le_bytes());
let hash = hasher.finalize();
Self(u128::from_le_bytes(
hash.as_bytes()[0..16].try_into().unwrap(),
))
}
}
impl fmt::Debug for WidgetId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "WidgetId({:032x})", self.0)
}
}
impl fmt::Display for WidgetId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:032x}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::WidgetId;
#[test]
fn all_identity_domains_are_deterministic_and_distinct() {
let parent = WidgetId::explicit("parent");
let explicit = WidgetId::explicit("child");
let derived = WidgetId::derived(parent.as_u128(), &[4, 2]);
let scoped = WidgetId::scoped(parent.as_u128(), "child");
assert_eq!(explicit, WidgetId::explicit("child"));
assert_eq!(derived, WidgetId::derived(parent.as_u128(), &[4, 2]));
assert_eq!(scoped, WidgetId::scoped(parent.as_u128(), "child"));
assert_ne!(explicit, derived);
assert_ne!(explicit, scoped);
assert_ne!(derived, scoped);
}
#[test]
fn structural_path_segments_and_order_are_significant() {
let parent = WidgetId::explicit("parent");
assert_ne!(
WidgetId::derived(parent.as_u128(), &[1, 2]),
WidgetId::derived(parent.as_u128(), &[2, 1])
);
assert_ne!(
WidgetId::derived(parent.as_u128(), &[1, 2]),
WidgetId::derived(parent.as_u128(), &[1, 2, 0])
);
}
#[test]
fn parent_identity_namespaces_structural_and_scoped_children() {
let first = WidgetId::explicit("first-parent");
let second = WidgetId::explicit("second-parent");
assert_ne!(
WidgetId::derived(first.as_u128(), &[0]),
WidgetId::derived(second.as_u128(), &[0])
);
assert_ne!(
WidgetId::scoped(first.as_u128(), "item"),
WidgetId::scoped(second.as_u128(), "item")
);
}
#[test]
fn source_location_identity_includes_every_input() {
let parent = WidgetId::explicit("parent");
let base = WidgetId::scoped_location(parent.as_u128(), "src/app.rs", 10, 4);
assert_eq!(
base,
WidgetId::scoped_location(parent.as_u128(), "src/app.rs", 10, 4)
);
assert_ne!(
base,
WidgetId::scoped_location(parent.as_u128(), "src/other.rs", 10, 4)
);
assert_ne!(
base,
WidgetId::scoped_location(parent.as_u128(), "src/app.rs", 11, 4)
);
assert_ne!(
base,
WidgetId::scoped_location(parent.as_u128(), "src/app.rs", 10, 5)
);
}
}