use sha2::{Digest, Sha256};
use uuid::Uuid;
pub const DEFAULT_PREFIX: &str = "bd";
pub fn generate_id(prefix: &str) -> String {
let uuid = Uuid::new_v4();
let mut hasher = Sha256::new();
hasher.update(uuid.as_bytes());
let hash = hasher.finalize();
format!("{}-{}", prefix, hex::encode(&hash[..2]))
}
pub fn generate_default_id() -> String {
generate_id(DEFAULT_PREFIX)
}
pub fn generate_child_id(parent_id: &str, counter: u32) -> String {
format!("{}.{}", parent_id, counter)
}
pub fn parse_id(id: &str) -> Option<IdComponents<'_>> {
let parts: Vec<&str> = id.splitn(2, '-').collect();
if parts.len() != 2 {
return None;
}
let prefix = parts[0];
let rest = parts[1];
let child_parts: Vec<&str> = rest.split('.').collect();
let hash = child_parts[0];
let children: Vec<u32> = child_parts[1..]
.iter()
.filter_map(|s| s.parse().ok())
.collect();
Some(IdComponents {
prefix,
hash,
children,
})
}
#[derive(Debug, Clone, PartialEq)]
pub struct IdComponents<'a> {
pub prefix: &'a str,
pub hash: &'a str,
pub children: Vec<u32>,
}
impl<'a> IdComponents<'a> {
pub fn root_id(&self) -> String {
format!("{}-{}", self.prefix, self.hash)
}
pub fn parent_id(&self) -> Option<String> {
if self.children.is_empty() {
None
} else if self.children.len() == 1 {
Some(self.root_id())
} else {
let parent_children: Vec<_> = self.children[..self.children.len() - 1]
.iter()
.map(|n| n.to_string())
.collect();
Some(format!("{}-{}.{}", self.prefix, self.hash, parent_children.join(".")))
}
}
pub fn depth(&self) -> usize {
self.children.len()
}
}
pub fn is_valid_id(id: &str) -> bool {
parse_id(id).is_some()
}
pub fn is_child_of(child_id: &str, parent_id: &str) -> bool {
child_id.starts_with(parent_id) && child_id.len() > parent_id.len()
&& child_id.chars().nth(parent_id.len()) == Some('.')
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_id() {
let id1 = generate_id("bd");
let id2 = generate_id("bd");
assert!(id1.starts_with("bd-"));
assert_eq!(id1.len(), 7); assert_ne!(id1, id2); }
#[test]
fn test_generate_child_id() {
let child = generate_child_id("bd-a1b2", 1);
assert_eq!(child, "bd-a1b2.1");
let grandchild = generate_child_id("bd-a1b2.1", 2);
assert_eq!(grandchild, "bd-a1b2.1.2");
}
#[test]
fn test_parse_id() {
let components = parse_id("bd-a1b2").unwrap();
assert_eq!(components.prefix, "bd");
assert_eq!(components.hash, "a1b2");
assert!(components.children.is_empty());
assert_eq!(components.depth(), 0);
let child = parse_id("bd-a1b2.1").unwrap();
assert_eq!(child.children, vec![1]);
assert_eq!(child.depth(), 1);
assert_eq!(child.parent_id(), Some("bd-a1b2".to_string()));
let grandchild = parse_id("bd-a1b2.1.2").unwrap();
assert_eq!(grandchild.children, vec![1, 2]);
assert_eq!(grandchild.depth(), 2);
assert_eq!(grandchild.parent_id(), Some("bd-a1b2.1".to_string()));
}
#[test]
fn test_is_child_of() {
assert!(is_child_of("bd-a1b2.1", "bd-a1b2"));
assert!(is_child_of("bd-a1b2.1.2", "bd-a1b2"));
assert!(is_child_of("bd-a1b2.1.2", "bd-a1b2.1"));
assert!(!is_child_of("bd-a1b2", "bd-a1b2"));
assert!(!is_child_of("bd-a1b3", "bd-a1b2"));
}
}