Skip to main content

kcode_kweb_db/
ids.rs

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}