use crate::{Error, Result};
use ed25519_dalek::SigningKey;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use sha2::{Digest, Sha256};
use std::{fmt, str::FromStr};
macro_rules! id_type {
($name:ident, $length:expr) => {
#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct $name(pub [u8; $length]);
impl fmt::Debug for $name {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, formatter)
}
}
impl fmt::Display for $name {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&hex::encode(self.0))
}
}
impl FromStr for $name {
type Err = Error;
fn from_str(value: &str) -> Result<Self> {
if value.len() != $length * 2
|| !value
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
{
return Err(Error::invalid_input(concat!(
stringify!($name),
" must be exact lowercase hex"
)));
}
let bytes = hex::decode(value)
.map_err(|_| Error::invalid_input("identifier contains invalid hex"))?;
let array = bytes
.try_into()
.map_err(|_| Error::invalid_input("identifier has the wrong length"))?;
Ok(Self(array))
}
}
impl Serialize for $name {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
value.parse().map_err(de::Error::custom)
}
}
};
}
id_type!(NodeId, 6);
id_type!(ObjectId, 6);
id_type!(TransactionId, 32);
id_type!(WriterId, 32);
impl NodeId {
pub(crate) fn random() -> Self {
Self(rand::random())
}
}
impl ObjectId {
pub(crate) fn random() -> Self {
Self(rand::random())
}
}
impl TransactionId {
pub(crate) fn for_signed_bytes(bytes: &[u8]) -> Self {
let mut hash = Sha256::new();
hash.update(b"kcode-kweb-db transaction v1\0");
hash.update(bytes);
Self(hash.finalize().into())
}
}
impl WriterId {
pub fn from_signing_key(signing_key: &[u8; 32]) -> Self {
let signing_key = SigningKey::from_bytes(signing_key);
Self(signing_key.verifying_key().to_bytes())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn text_is_exact_lowercase_hex() {
let id = NodeId([0, 1, 2, 10, 254, 255]);
assert_eq!(id.to_string(), "0001020afeff");
assert_eq!(id.to_string().parse::<NodeId>().unwrap(), id);
assert!("0001020AFEFF".parse::<NodeId>().is_err());
assert!("0012".parse::<NodeId>().is_err());
}
}