use oxicode::Decode;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum InlineType {
Dictionary = 0x00,
SmallInt = 0x01,
Boolean = 0x02,
ShortString = 0x03,
SmallUInt = 0x04,
Decimal = 0x05,
DateTime = 0x06,
}
impl InlineType {
const fn from_node_id(id: u64) -> u8 {
((id >> 56) & 0xFF) as u8
}
const fn encode(self, value: u64) -> u64 {
((self as u64) << 56) | (value & 0x00FF_FFFF_FFFF_FFFF)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct NodeId(u64);
impl NodeId {
const VALUE_MASK: u64 = 0x00FF_FFFF_FFFF_FFFF;
const TYPE_MASK: u64 = 0xFF00_0000_0000_0000;
const MAX_DICT_ID: u64 = 0x00FF_FFFF_FFFF_FFFF;
pub const fn new(id: u64) -> Self {
NodeId(id)
}
pub const fn as_u64(&self) -> u64 {
self.0
}
pub const FIRST: NodeId = NodeId(1);
pub const NULL: NodeId = NodeId(0);
pub const fn is_null(&self) -> bool {
self.0 == 0
}
pub const fn next(&self) -> NodeId {
NodeId(self.0 + 1)
}
pub const fn dict_ref(id: u64) -> Self {
assert!(id <= Self::MAX_DICT_ID);
NodeId(id)
}
pub const fn inline_int(value: i32) -> Self {
let extended = (value as i64) as u64 & Self::VALUE_MASK;
NodeId(InlineType::SmallInt.encode(extended))
}
pub const fn inline_bool(value: bool) -> Self {
NodeId(InlineType::Boolean.encode(if value { 1 } else { 0 }))
}
pub fn inline_short_string(s: &str) -> Option<Self> {
let bytes = s.as_bytes();
if bytes.len() > 7 || !s.is_ascii() {
return None;
}
let mut encoded: u64 = 0;
for (i, &byte) in bytes.iter().enumerate() {
encoded |= (byte as u64) << (i * 8);
}
Some(NodeId(InlineType::ShortString.encode(encoded)))
}
pub const fn inline_uint(value: u32) -> Self {
NodeId(InlineType::SmallUInt.encode(value as u64))
}
pub const fn inline_type(&self) -> u8 {
InlineType::from_node_id(self.0)
}
pub const fn is_dict_ref(&self) -> bool {
!self.is_null() && self.inline_type() == InlineType::Dictionary as u8
}
pub const fn is_inline(&self) -> bool {
!self.is_null() && !self.is_dict_ref()
}
pub const fn dict_id(&self) -> u64 {
self.0 & Self::VALUE_MASK
}
pub const fn inline_int_value(&self) -> Option<i32> {
if self.inline_type() == InlineType::SmallInt as u8 {
let value = (self.0 & Self::VALUE_MASK) as i64;
let extended = if value & 0x0080_0000_0000_0000 != 0 {
value | 0xFF00_0000_0000_0000u64 as i64
} else {
value
};
Some(extended as i32)
} else {
None
}
}
pub const fn inline_bool_value(&self) -> Option<bool> {
if self.inline_type() == InlineType::Boolean as u8 {
Some((self.0 & Self::VALUE_MASK) != 0)
} else {
None
}
}
pub const fn inline_uint_value(&self) -> Option<u32> {
if self.inline_type() == InlineType::SmallUInt as u8 {
Some((self.0 & Self::VALUE_MASK) as u32)
} else {
None
}
}
pub fn inline_string_value(&self) -> Option<String> {
if self.inline_type() == InlineType::ShortString as u8 {
let encoded = self.0 & Self::VALUE_MASK;
let mut bytes = Vec::with_capacity(7);
for i in 0..7 {
let byte = ((encoded >> (i * 8)) & 0xFF) as u8;
if byte == 0 {
break;
}
bytes.push(byte);
}
String::from_utf8(bytes).ok()
} else {
None
}
}
}
impl Default for NodeId {
fn default() -> Self {
NodeId::NULL
}
}
impl fmt::Display for NodeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NodeId({})", self.0)
}
}
impl From<u64> for NodeId {
fn from(id: u64) -> Self {
NodeId(id)
}
}
impl From<NodeId> for u64 {
fn from(id: NodeId) -> Self {
id.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_node_id_creation() {
let id = NodeId::new(42);
assert_eq!(id.as_u64(), 42);
}
#[test]
fn test_node_id_null() {
let null_id = NodeId::NULL;
assert!(null_id.is_null());
assert_eq!(null_id.as_u64(), 0);
let valid_id = NodeId::FIRST;
assert!(!valid_id.is_null());
assert_eq!(valid_id.as_u64(), 1);
}
#[test]
fn test_node_id_next() {
let id = NodeId::new(10);
let next = id.next();
assert_eq!(next.as_u64(), 11);
}
#[test]
fn test_node_id_ordering() {
let id1 = NodeId::new(10);
let id2 = NodeId::new(20);
assert!(id1 < id2);
assert!(id2 > id1);
}
#[test]
fn test_node_id_conversions() {
let raw: u64 = 42;
let id: NodeId = raw.into();
assert_eq!(id.as_u64(), 42);
let back: u64 = id.into();
assert_eq!(back, 42);
}
#[test]
fn test_node_id_serialization() {
let id = NodeId::new(123);
let serialized = oxicode::serde::encode_to_vec(&id, oxicode::config::standard()).unwrap();
let deserialized: NodeId =
oxicode::serde::decode_from_slice(&serialized, oxicode::config::standard())
.unwrap()
.0;
assert_eq!(id, deserialized);
}
#[test]
fn test_dict_ref() {
let id = NodeId::dict_ref(12345);
assert!(id.is_dict_ref());
assert!(!id.is_inline());
assert_eq!(id.dict_id(), 12345);
assert_eq!(id.inline_type(), InlineType::Dictionary as u8);
}
#[test]
fn test_dict_ref_large() {
let large_id = 0x00FF_FFFF_FFFF_FFFE; let id = NodeId::dict_ref(large_id);
assert!(id.is_dict_ref());
assert_eq!(id.dict_id(), large_id);
}
#[test]
fn test_inline_int_positive() {
let id = NodeId::inline_int(42);
assert!(!id.is_dict_ref());
assert!(id.is_inline());
assert_eq!(id.inline_type(), InlineType::SmallInt as u8);
assert_eq!(id.inline_int_value(), Some(42));
assert_eq!(id.inline_bool_value(), None);
assert_eq!(id.inline_uint_value(), None);
}
#[test]
fn test_inline_int_negative() {
let id = NodeId::inline_int(-100);
assert!(id.is_inline());
assert_eq!(id.inline_int_value(), Some(-100));
}
#[test]
fn test_inline_int_zero() {
let id = NodeId::inline_int(0);
assert!(id.is_inline());
assert_eq!(id.inline_int_value(), Some(0));
}
#[test]
fn test_inline_int_bounds() {
let max = NodeId::inline_int(i32::MAX);
assert_eq!(max.inline_int_value(), Some(i32::MAX));
let min = NodeId::inline_int(i32::MIN);
assert_eq!(min.inline_int_value(), Some(i32::MIN));
}
#[test]
fn test_inline_bool_true() {
let id = NodeId::inline_bool(true);
assert!(id.is_inline());
assert_eq!(id.inline_type(), InlineType::Boolean as u8);
assert_eq!(id.inline_bool_value(), Some(true));
assert_eq!(id.inline_int_value(), None);
}
#[test]
fn test_inline_bool_false() {
let id = NodeId::inline_bool(false);
assert!(id.is_inline());
assert_eq!(id.inline_bool_value(), Some(false));
}
#[test]
fn test_inline_short_string() {
let id = NodeId::inline_short_string("hello").unwrap();
assert!(id.is_inline());
assert_eq!(id.inline_type(), InlineType::ShortString as u8);
assert_eq!(id.inline_string_value(), Some("hello".to_string()));
}
#[test]
fn test_inline_short_string_single_char() {
let id = NodeId::inline_short_string("x").unwrap();
assert_eq!(id.inline_string_value(), Some("x".to_string()));
}
#[test]
fn test_inline_short_string_seven_chars() {
let id = NodeId::inline_short_string("abcdefg").unwrap();
assert_eq!(id.inline_string_value(), Some("abcdefg".to_string()));
}
#[test]
fn test_inline_short_string_too_long() {
let result = NodeId::inline_short_string("abcdefgh"); assert!(result.is_none());
}
#[test]
fn test_inline_short_string_non_ascii() {
let result = NodeId::inline_short_string("hello🌍");
assert!(result.is_none());
}
#[test]
fn test_inline_short_string_empty() {
let id = NodeId::inline_short_string("").unwrap();
assert_eq!(id.inline_string_value(), Some("".to_string()));
}
#[test]
fn test_inline_uint() {
let id = NodeId::inline_uint(12345);
assert!(id.is_inline());
assert_eq!(id.inline_type(), InlineType::SmallUInt as u8);
assert_eq!(id.inline_uint_value(), Some(12345));
assert_eq!(id.inline_int_value(), None);
}
#[test]
fn test_inline_uint_zero() {
let id = NodeId::inline_uint(0);
assert_eq!(id.inline_uint_value(), Some(0));
}
#[test]
fn test_inline_uint_max() {
let id = NodeId::inline_uint(u32::MAX);
assert_eq!(id.inline_uint_value(), Some(u32::MAX));
}
#[test]
fn test_inline_types_are_distinct() {
let dict = NodeId::dict_ref(42);
let int = NodeId::inline_int(42);
let uint = NodeId::inline_uint(42);
let bool_true = NodeId::inline_bool(true);
let string = NodeId::inline_short_string("42").unwrap();
assert_ne!(dict.as_u64(), int.as_u64());
assert_ne!(dict.as_u64(), uint.as_u64());
assert_ne!(dict.as_u64(), bool_true.as_u64());
assert_ne!(dict.as_u64(), string.as_u64());
assert_ne!(int.as_u64(), uint.as_u64());
}
#[test]
fn test_inline_int_serialization() {
let id = NodeId::inline_int(-42);
let serialized = oxicode::serde::encode_to_vec(&id, oxicode::config::standard()).unwrap();
let deserialized: NodeId =
oxicode::serde::decode_from_slice(&serialized, oxicode::config::standard())
.unwrap()
.0;
assert_eq!(id, deserialized);
assert_eq!(deserialized.inline_int_value(), Some(-42));
}
#[test]
fn test_inline_bool_serialization() {
let id = NodeId::inline_bool(true);
let serialized = oxicode::serde::encode_to_vec(&id, oxicode::config::standard()).unwrap();
let deserialized: NodeId =
oxicode::serde::decode_from_slice(&serialized, oxicode::config::standard())
.unwrap()
.0;
assert_eq!(id, deserialized);
assert_eq!(deserialized.inline_bool_value(), Some(true));
}
#[test]
fn test_inline_string_serialization() {
let id = NodeId::inline_short_string("test").unwrap();
let serialized = oxicode::serde::encode_to_vec(&id, oxicode::config::standard()).unwrap();
let deserialized: NodeId =
oxicode::serde::decode_from_slice(&serialized, oxicode::config::standard())
.unwrap()
.0;
assert_eq!(id, deserialized);
assert_eq!(deserialized.inline_string_value(), Some("test".to_string()));
}
#[test]
fn test_common_rdf_values() {
assert!(NodeId::inline_int(0).is_inline()); assert!(NodeId::inline_int(1).is_inline()); assert!(NodeId::inline_bool(true).is_inline()); assert!(NodeId::inline_bool(false).is_inline()); assert!(NodeId::inline_short_string("en").unwrap().is_inline()); assert!(NodeId::inline_short_string("").unwrap().is_inline()); }
#[test]
fn test_null_is_not_inline() {
assert!(!NodeId::NULL.is_inline());
assert!(!NodeId::NULL.is_dict_ref());
assert!(NodeId::NULL.is_null());
}
}