iroh_db_core/
id.rs

1use std::{fmt, str::FromStr};
2
3/// Number of bytes in every protocol identifier.
4pub const ID_LEN: usize = 32;
5
6/// An error returned when parsing a fixed-size protocol identifier.
7#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
8pub enum FixedIdError {
9    /// The decoded identifier did not contain exactly 32 bytes.
10    #[error("identifier must be {ID_LEN} bytes, got {0}")]
11    InvalidLength(usize),
12    /// The identifier was not valid hexadecimal.
13    #[error("identifier is not valid hexadecimal: {0}")]
14    InvalidHex(String),
15}
16
17macro_rules! fixed_id {
18    ($(#[$meta:meta])* $name:ident) => {
19        $(#[$meta])*
20        #[derive(
21            Clone,
22            Copy,
23            PartialEq,
24            Eq,
25            PartialOrd,
26            Ord,
27            Hash,
28            minicbor::Encode,
29            minicbor::Decode,
30        )]
31        #[cbor(transparent)]
32        pub struct $name(#[n(0)] [u8; ID_LEN]);
33
34        impl $name {
35            /// Constructs an identifier from its exact protocol bytes.
36            pub const fn from_bytes(bytes: [u8; ID_LEN]) -> Self {
37                Self(bytes)
38            }
39
40            /// Returns the exact protocol bytes.
41            pub const fn as_bytes(&self) -> &[u8; ID_LEN] {
42                &self.0
43            }
44
45            /// Consumes this identifier and returns its exact protocol bytes.
46            pub const fn to_bytes(self) -> [u8; ID_LEN] {
47                self.0
48            }
49        }
50
51        impl fmt::Display for $name {
52            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
53                formatter.write_str(&hex::encode(self.0))
54            }
55        }
56
57        impl fmt::Debug for $name {
58            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59                write!(formatter, "{}({self})", stringify!($name))
60            }
61        }
62
63        impl FromStr for $name {
64            type Err = FixedIdError;
65
66            fn from_str(value: &str) -> Result<Self, Self::Err> {
67                let bytes = hex::decode(value)
68                    .map_err(|error| FixedIdError::InvalidHex(error.to_string()))?;
69                let actual = bytes.len();
70                let bytes = bytes
71                    .try_into()
72                    .map_err(|_: Vec<u8>| FixedIdError::InvalidLength(actual))?;
73                Ok(Self(bytes))
74            }
75        }
76
77        impl AsRef<[u8]> for $name {
78            fn as_ref(&self) -> &[u8] {
79                &self.0
80            }
81        }
82    };
83}
84
85fixed_id!(
86    /// A cryptographic device identity, represented independently from the iroh adapter.
87    AuthorId
88);
89fixed_id!(
90    /// An isolated authorization, encryption, replication, and transaction boundary.
91    DomainId
92);
93fixed_id!(
94    /// The stable identifier of a typed collection.
95    CollectionId
96);
97fixed_id!(
98    /// The content hash of an immutable commit blob.
99    CommitId
100);
101fixed_id!(
102    /// The BLAKE3 content hash of any immutable iroh blob.
103    BlobHash
104);
105fixed_id!(
106    /// The content hash of an immutable snapshot blob.
107    SnapshotId
108);
109fixed_id!(
110    /// The stable hash of a canonical record schema.
111    SchemaId
112);
113fixed_id!(
114    /// The stable hash of a canonical capability certificate.
115    CapabilityId
116);
117fixed_id!(
118    /// A unique identifier for one domain-local transaction.
119    TransactionId
120);
121fixed_id!(
122    /// A stable identifier for one domain-local schema migration attempt.
123    MigrationId
124);
125
126impl CollectionId {
127    /// Derives the stable collection identifier for a declared collection name.
128    pub fn for_name(name: &str) -> Self {
129        let mut hasher = blake3::Hasher::new();
130        hasher.update(b"iroh-db/collection/v1");
131        hasher.update(name.as_bytes());
132        Self::from_bytes(*hasher.finalize().as_bytes())
133    }
134}