use crate::Error;
use sha2::{Digest, Sha256};
use std::fmt;
use std::fmt::Write;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct Id(String);
impl Id {
pub fn from_hashed_bytes(val: impl AsRef<[u8]>) -> Self {
Self(Self::hash_bytes_to_hex(val.as_ref()))
}
pub fn from_hashed_string(val: &str) -> Self {
Self::from_hashed_bytes(val.as_bytes())
}
pub fn from_hashed_u64(val: u64) -> Self {
Self::from_hashed_bytes(val.to_le_bytes())
}
pub fn generate_uuid_v4() -> Self {
Self(Uuid::new_v4().to_string())
}
pub fn generate_uuid_v7() -> Self {
Self(Uuid::now_v7().to_string())
}
pub fn generate_uuid_v5(namespace: &Uuid, name: &str) -> Self {
Self(Uuid::new_v5(namespace, name.as_bytes()).to_string())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Id {
fn hash_bytes_to_hex(val: &[u8]) -> String {
let mut sha256 = Sha256::new();
sha256.update(val);
let result = sha256.finalize();
let mut hash = String::with_capacity(64);
for byte in result {
write!(&mut hash, "{:02X}", byte).unwrap();
}
hash
}
fn validate(s: &str) -> Result<(), Error> {
if s.is_empty() {
return Err(Error::EmptyId);
}
Ok(())
}
}
impl From<Id> for String {
fn from(item: Id) -> Self {
item.0
}
}
impl TryFrom<&str> for Id {
type Error = Error;
fn try_from(item: &str) -> Result<Self, Self::Error> {
Self::validate(item)?;
Ok(Self(item.to_string()))
}
}
impl TryFrom<String> for Id {
type Error = Error;
fn try_from(item: String) -> Result<Self, Self::Error> {
Self::validate(&item)?;
Ok(Self(item))
}
}
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn try_from_empty_string_returns_empty_id_error() {
assert_eq!(Id::try_from(""), Err(Error::EmptyId));
assert_eq!(Id::try_from("".to_string()), Err(Error::EmptyId));
}
#[test]
fn from_hashed_string_is_deterministic() {
let xml = "<gml:Point><gml:pos>1 2 3</gml:pos></gml:Point>";
assert_eq!(Id::from_hashed_string(xml), Id::from_hashed_string(xml));
}
}