quorum_set/tree/canonical_id.rs
1use std::fmt;
2
3#[cfg(doc)]
4use crate::Node;
5#[cfg(doc)]
6use crate::QuorumTree;
7
8pub(crate) const MAX_CANONICAL_ID_LEN: usize = 64;
9
10pub(crate) fn fmt_escaped<W>(s: &str, f: &mut W) -> fmt::Result
11where W: fmt::Write + ?Sized {
12 for b in s.bytes() {
13 if b.is_ascii_alphanumeric() || b == b'_' {
14 write!(f, "{}", char::from(b))?;
15 } else {
16 write!(f, "%{b:02X}")?;
17 }
18 }
19 Ok(())
20}
21
22/// Generates a deterministic canonical ID.
23///
24/// [`QuorumTree`] uses canonical IDs for equality and ordering. Implementations
25/// for application node IDs should be stable across process restarts and
26/// software versions whenever the logical node identity is unchanged.
27///
28/// Implementations must be consistent with [`Eq`]: two IDs must emit the same
29/// canonical ID if and only if they are equal. If two distinct IDs shared one
30/// canonical ID, two structurally different trees could compare as equal.
31///
32/// User-provided node IDs may emit any string. When a user ID is embedded in a
33/// [`Node`], this crate escapes short IDs and hashes long IDs to keep tree IDs
34/// unambiguous and bounded.
35pub trait CanonicalId {
36 /// Writes this value's canonical ID into `f`.
37 ///
38 /// Implement this method directly when the ID can be written without an
39 /// intermediate allocation.
40 fn fmt_canonical_id<W>(&self, f: &mut W) -> fmt::Result
41 where W: fmt::Write + ?Sized;
42
43 /// Returns this value's canonical ID as a [`String`].
44 fn canonical_id(&self) -> String {
45 let mut s = String::new();
46 self.fmt_canonical_id(&mut s).expect("writing to String should not fail");
47 s
48 }
49}
50
51macro_rules! impl_canonical_id {
52 ($($t:ty),* $(,)?) => {
53 $(impl CanonicalId for $t {
54 fn fmt_canonical_id<W>(&self, f: &mut W) -> fmt::Result
55 where W: fmt::Write + ?Sized {
56 write!(f, "{}", self)
57 }
58 })*
59 };
60}
61
62impl_canonical_id!(
63 u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, String, &str
64);