1use crate::{Error, Result};
2use ed25519_dalek::SigningKey;
3use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
4use sha2::{Digest, Sha256};
5use std::{fmt, str::FromStr};
6
7macro_rules! id_type {
8 ($name:ident, $length:expr) => {
9 #[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
10 pub struct $name(pub [u8; $length]);
11
12 impl fmt::Debug for $name {
13 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
14 fmt::Display::fmt(self, formatter)
15 }
16 }
17
18 impl fmt::Display for $name {
19 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
20 formatter.write_str(&hex::encode(self.0))
21 }
22 }
23
24 impl FromStr for $name {
25 type Err = Error;
26
27 fn from_str(value: &str) -> Result<Self> {
28 if value.len() != $length * 2
29 || !value
30 .bytes()
31 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
32 {
33 return Err(Error::invalid_input(concat!(
34 stringify!($name),
35 " must be exact lowercase hex"
36 )));
37 }
38 let bytes = hex::decode(value)
39 .map_err(|_| Error::invalid_input("identifier contains invalid hex"))?;
40 let array = bytes
41 .try_into()
42 .map_err(|_| Error::invalid_input("identifier has the wrong length"))?;
43 Ok(Self(array))
44 }
45 }
46
47 impl Serialize for $name {
48 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
49 where
50 S: Serializer,
51 {
52 serializer.serialize_str(&self.to_string())
53 }
54 }
55
56 impl<'de> Deserialize<'de> for $name {
57 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
58 where
59 D: Deserializer<'de>,
60 {
61 let value = String::deserialize(deserializer)?;
62 value.parse().map_err(de::Error::custom)
63 }
64 }
65 };
66}
67
68id_type!(NodeId, 6);
69id_type!(ObjectId, 6);
70id_type!(TransactionId, 32);
71id_type!(WriterId, 32);
72
73impl NodeId {
74 pub(crate) fn random() -> Self {
75 Self(rand::random())
76 }
77}
78
79impl ObjectId {
80 pub(crate) fn random() -> Self {
81 Self(rand::random())
82 }
83}
84
85impl TransactionId {
86 pub(crate) fn for_signed_bytes(bytes: &[u8]) -> Self {
87 let mut hash = Sha256::new();
88 hash.update(b"kcode-kweb-db transaction v1\0");
89 hash.update(bytes);
90 Self(hash.finalize().into())
91 }
92}
93
94impl WriterId {
95 pub fn from_signing_key(signing_key: &[u8; 32]) -> Self {
96 let signing_key = SigningKey::from_bytes(signing_key);
97 Self(signing_key.verifying_key().to_bytes())
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn text_is_exact_lowercase_hex() {
107 let id = NodeId([0, 1, 2, 10, 254, 255]);
108 assert_eq!(id.to_string(), "0001020afeff");
109 assert_eq!(id.to_string().parse::<NodeId>().unwrap(), id);
110 assert!("0001020AFEFF".parse::<NodeId>().is_err());
111 assert!("0012".parse::<NodeId>().is_err());
112 }
113}