use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
pub enum NodeId {
Named(String),
Hash([u8; 32]),
Blank(u64),
}
impl NodeId {
pub fn named(name: impl Into<String>) -> Self {
Self::Named(name.into())
}
pub fn hash(hash: [u8; 32]) -> Self {
Self::Hash(hash)
}
pub fn from_bytes(bytes: &[u8]) -> Self {
let mut arr = [0u8; 32];
let len = bytes.len().min(32);
arr[..len].copy_from_slice(&bytes[..len]);
Self::Hash(arr)
}
pub fn blank() -> Self {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
Self::Blank(COUNTER.fetch_add(1, Ordering::SeqCst))
}
pub fn blank_with_id(id: u64) -> Self {
Self::Blank(id)
}
pub fn is_named(&self) -> bool {
matches!(self, Self::Named(_))
}
pub fn is_hash(&self) -> bool {
matches!(self, Self::Hash(_))
}
pub fn is_blank(&self) -> bool {
matches!(self, Self::Blank(_))
}
pub fn as_name(&self) -> Option<&str> {
match self {
Self::Named(name) => Some(name),
_ => None,
}
}
pub fn as_hash(&self) -> Option<&[u8; 32]> {
match self {
Self::Hash(hash) => Some(hash),
_ => None,
}
}
pub fn namespace(&self) -> Option<&str> {
match self {
Self::Named(name) => name.split(':').next(),
_ => None,
}
}
pub fn local_name(&self) -> Option<&str> {
match self {
Self::Named(name) => name.rsplit(':').next(),
_ => None,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
bincode::serde::encode_to_vec(self, bincode::config::standard()).unwrap_or_default()
}
pub fn from_storage_bytes(bytes: &[u8]) -> Option<Self> {
bincode::serde::decode_from_slice(bytes, bincode::config::standard())
.map(|(v, _)| v)
.ok()
}
}
impl fmt::Display for NodeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Named(name) => write!(f, "<{}>", name),
Self::Hash(hash) => write!(f, "_:hash:{}", hex::encode(&hash[..8])),
Self::Blank(id) => write!(f, "_:b{}", id),
}
}
}
impl From<String> for NodeId {
fn from(s: String) -> Self {
Self::Named(s)
}
}
impl From<&str> for NodeId {
fn from(s: &str) -> Self {
Self::Named(s.to_string())
}
}
impl From<[u8; 32]> for NodeId {
fn from(hash: [u8; 32]) -> Self {
Self::Hash(hash)
}
}
mod hex {
const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
pub fn encode(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for &b in bytes {
s.push(HEX_CHARS[(b >> 4) as usize] as char);
s.push(HEX_CHARS[(b & 0x0f) as usize] as char);
}
s
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_named_node() {
let node = NodeId::named("user:alice");
assert!(node.is_named());
assert_eq!(node.as_name(), Some("user:alice"));
assert_eq!(node.namespace(), Some("user"));
assert_eq!(node.local_name(), Some("alice"));
}
#[test]
fn test_hash_node() {
let hash = [1u8; 32];
let node = NodeId::hash(hash);
assert!(node.is_hash());
assert_eq!(node.as_hash(), Some(&hash));
}
#[test]
fn test_blank_node() {
let node1 = NodeId::blank();
let node2 = NodeId::blank();
assert!(node1.is_blank());
assert_ne!(node1, node2); }
#[test]
fn test_display() {
let named = NodeId::named("user:alice");
assert_eq!(format!("{}", named), "<user:alice>");
let blank = NodeId::blank_with_id(42);
assert_eq!(format!("{}", blank), "_:b42");
}
#[test]
fn test_serialization() {
let node = NodeId::named("test:node");
let bytes = node.to_bytes();
let restored = NodeId::from_storage_bytes(&bytes).unwrap();
assert_eq!(node, restored);
}
}