use slotmap::KeyData;
use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
#[derive(Default)]
pub struct SymbolId(KeyData);
unsafe impl slotmap::Key for SymbolId {
fn data(&self) -> KeyData {
self.0
}
}
impl From<KeyData> for SymbolId {
fn from(k: KeyData) -> Self {
Self(k)
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for SymbolId {
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("SymbolId")
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
generator.subschema_for::<String>()
}
}
impl SymbolId {
pub fn parse(s: &str) -> Option<Self> {
let inner = s
.strip_prefix("SymbolId(")
.and_then(|s| s.strip_suffix(')'))
.unwrap_or(s);
let (idx_str, ver_str) = inner.split_once('v')?;
let idx: u32 = idx_str.parse().ok()?;
let ver: u32 = ver_str.parse().ok()?;
if ver == 0 {
return None;
}
let ffi = ((ver as u64) << 32) | (idx as u64);
Some(KeyData::from_ffi(ffi).into())
}
fn index(&self) -> u32 {
self.0.as_ffi() as u32
}
fn version(&self) -> u32 {
(self.0.as_ffi() >> 32) as u32
}
}
impl fmt::Display for SymbolId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}v{}", self.index(), self.version())
}
}
impl fmt::Debug for SymbolId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SymbolId({}v{})", self.index(), self.version())
}
}
use serde::{Deserialize, Deserializer, Serialize, Serializer};
impl Serialize for SymbolId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for SymbolId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Self::parse(&s).ok_or_else(|| serde::de::Error::custom(format!("Invalid SymbolId: {}", s)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use slotmap::SlotMap;
#[test]
fn test_symbol_id_basic() {
let mut map: SlotMap<SymbolId, &str> = SlotMap::with_key();
let id1 = map.insert("foo");
let id2 = map.insert("bar");
assert_ne!(id1, id2);
assert_eq!(map.get(id1), Some(&"foo"));
assert_eq!(map.get(id2), Some(&"bar"));
}
#[test]
fn test_symbol_id_generation() {
let mut map: SlotMap<SymbolId, &str> = SlotMap::with_key();
let id1 = map.insert("foo");
map.remove(id1);
let id2 = map.insert("bar");
assert_ne!(id1, id2);
assert!(map.get(id1).is_none());
}
#[test]
fn test_parse_compact() {
let id = SymbolId::parse("165v1");
assert!(id.is_some());
}
#[test]
fn test_parse_debug_format() {
let id = SymbolId::parse("SymbolId(165v1)");
assert!(id.is_some());
}
#[test]
fn test_parse_invalid() {
assert!(SymbolId::parse("").is_none());
assert!(SymbolId::parse("invalid").is_none());
assert!(SymbolId::parse("165v0").is_none()); }
#[test]
fn test_display_format() {
let mut map: SlotMap<SymbolId, &str> = SlotMap::with_key();
let id = map.insert("test");
let display = format!("{}", id);
assert!(display.contains('v'), "Display format should contain 'v'");
assert!(
!display.contains("SymbolId"),
"Display should not contain 'SymbolId'"
);
}
#[test]
fn test_debug_format() {
let mut map: SlotMap<SymbolId, &str> = SlotMap::with_key();
let id = map.insert("test");
let debug = format!("{:?}", id);
assert!(
debug.starts_with("SymbolId("),
"Debug format should start with 'SymbolId('"
);
assert!(debug.ends_with(')'), "Debug format should end with ')'");
}
#[test]
fn test_display_debug_roundtrip() {
let mut map: SlotMap<SymbolId, &str> = SlotMap::with_key();
let id = map.insert("test");
let display = format!("{}", id);
let parsed_from_display = SymbolId::parse(&display);
assert_eq!(
Some(id),
parsed_from_display,
"Should parse from display format"
);
let debug = format!("{:?}", id);
let parsed_from_debug = SymbolId::parse(&debug);
assert_eq!(
Some(id),
parsed_from_debug,
"Should parse from debug format"
);
}
#[test]
fn test_serde_roundtrip() {
let mut map: SlotMap<SymbolId, &str> = SlotMap::with_key();
let id = map.insert("test");
let json = serde_json::to_string(&id).unwrap();
let deserialized: SymbolId = serde_json::from_str(&json).unwrap();
assert_eq!(id, deserialized);
}
}