1use std::{fmt, str::FromStr};
2
3pub const ID_LEN: usize = 32;
5
6#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
8pub enum FixedIdError {
9 #[error("identifier must be {ID_LEN} bytes, got {0}")]
11 InvalidLength(usize),
12 #[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 pub const fn from_bytes(bytes: [u8; ID_LEN]) -> Self {
37 Self(bytes)
38 }
39
40 pub const fn as_bytes(&self) -> &[u8; ID_LEN] {
42 &self.0
43 }
44
45 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 AuthorId
88);
89fixed_id!(
90 DomainId
92);
93fixed_id!(
94 CollectionId
96);
97fixed_id!(
98 CommitId
100);
101fixed_id!(
102 BlobHash
104);
105fixed_id!(
106 SnapshotId
108);
109fixed_id!(
110 SchemaId
112);
113fixed_id!(
114 CapabilityId
116);
117fixed_id!(
118 TransactionId
120);
121fixed_id!(
122 MigrationId
124);
125
126impl CollectionId {
127 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}