#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, string::ToString};
#[cfg(feature = "std")]
use std::boxed::Box;
#[cfg(all(debug_assertions, feature = "std"))]
use core::sync::atomic::{AtomicUsize, Ordering};
#[cfg(all(debug_assertions, feature = "std"))]
static INTERNED_KEY_COUNT: AtomicUsize = AtomicUsize::new(0);
#[cfg(all(debug_assertions, feature = "std"))]
const MAX_EXPECTED_INTERNED_KEYS: usize = 1000;
#[cfg(feature = "derive")]
pub use aimdb_derive::RecordKey;
pub trait RecordKey:
Clone + Eq + core::hash::Hash + core::borrow::Borrow<str> + Send + Sync + 'static
{
fn as_str(&self) -> &'static str;
#[inline]
fn link_address(&self) -> Option<&str> {
None
}
}
impl RecordKey for &'static str {
#[inline]
fn as_str(&self) -> &'static str {
self
}
}
#[derive(Clone, Copy)]
pub struct StringKey(StringKeyInner);
#[derive(Clone, Copy)]
enum StringKeyInner {
Static(&'static str),
Interned(&'static str),
}
impl StringKey {
#[inline]
#[must_use]
pub const fn new(s: &'static str) -> Self {
Self(StringKeyInner::Static(s))
}
#[inline]
#[must_use]
pub fn intern(s: impl AsRef<str>) -> Self {
#[cfg(all(debug_assertions, feature = "std"))]
{
let count = INTERNED_KEY_COUNT.fetch_add(1, Ordering::Relaxed);
debug_assert!(
count < MAX_EXPECTED_INTERNED_KEYS,
"StringKey::intern() called {} times. This exceeds the expected limit of {}. \
Interned keys leak memory and should only be created at startup. \
Use static string literals or enum keys for better performance.",
count + 1,
MAX_EXPECTED_INTERNED_KEYS
);
}
let leaked: &'static str = Box::leak(s.as_ref().to_string().into_boxed_str());
Self(StringKeyInner::Interned(leaked))
}
#[inline]
#[must_use]
pub fn from_dynamic(s: &str) -> Self {
Self::intern(s)
}
#[inline]
pub fn is_static(&self) -> bool {
matches!(self.0, StringKeyInner::Static(_))
}
#[inline]
pub fn is_interned(&self) -> bool {
matches!(self.0, StringKeyInner::Interned(_))
}
}
impl RecordKey for StringKey {
#[inline]
fn as_str(&self) -> &'static str {
match self.0 {
StringKeyInner::Static(s) => s,
StringKeyInner::Interned(s) => s,
}
}
}
impl core::fmt::Debug for StringKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self.0 {
StringKeyInner::Static(s) => f.debug_tuple("StringKey::Static").field(&s).finish(),
StringKeyInner::Interned(s) => f.debug_tuple("StringKey::Interned").field(&s).finish(),
}
}
}
impl core::hash::Hash for StringKey {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl PartialEq for StringKey {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for StringKey {}
impl PartialEq<str> for StringKey {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<&str> for StringKey {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialOrd for StringKey {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for StringKey {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl From<&'static str> for StringKey {
#[inline]
fn from(s: &'static str) -> Self {
Self::new(s)
}
}
#[cfg(all(feature = "alloc", not(feature = "std")))]
impl From<alloc::string::String> for StringKey {
#[inline]
fn from(s: alloc::string::String) -> Self {
Self::intern(s)
}
}
#[cfg(feature = "std")]
impl From<String> for StringKey {
#[inline]
fn from(s: String) -> Self {
Self::intern(s)
}
}
impl core::fmt::Display for StringKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
impl AsRef<str> for StringKey {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl core::borrow::Borrow<str> for StringKey {
fn borrow(&self) -> &str {
self.as_str()
}
}
#[cfg(feature = "std")]
impl serde::Serialize for StringKey {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
#[cfg(feature = "std")]
impl<'de> serde::Deserialize<'de> for StringKey {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(Self::intern(s))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct RecordId(pub(crate) u32);
impl RecordId {
#[inline]
pub const fn new(index: u32) -> Self {
Self(index)
}
#[inline]
pub const fn index(self) -> usize {
self.0 as usize
}
#[inline]
pub const fn raw(self) -> u32 {
self.0
}
}
impl core::fmt::Display for RecordId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "RecordId({})", self.0)
}
}
#[cfg(feature = "std")]
impl serde::Serialize for RecordId {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_u32(self.0)
}
}
#[cfg(feature = "std")]
impl<'de> serde::Deserialize<'de> for RecordId {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let index = u32::deserialize(deserializer)?;
Ok(Self(index))
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
#[test]
fn test_string_key_static() {
let key: StringKey = "sensors.temperature".into();
assert!(key.is_static());
assert_eq!(key.as_str(), "sensors.temperature");
}
#[test]
fn test_string_key_interned() {
let key = StringKey::intern(["sensors", ".", "temperature"].concat());
assert!(!key.is_static());
assert!(key.is_interned());
assert_eq!(key.as_str(), "sensors.temperature");
}
#[test]
fn test_string_key_equality() {
let static_key: StringKey = "sensors.temp".into();
let interned_key = StringKey::intern(["sensors", ".", "temp"].concat());
assert_eq!(static_key, interned_key);
}
#[test]
fn test_string_key_hash_consistency() {
use core::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
fn hash_key(key: &StringKey) -> u64 {
let mut hasher = DefaultHasher::new();
key.hash(&mut hasher);
hasher.finish()
}
let static_key: StringKey = "sensors.temp".into();
let interned_key = StringKey::intern(["sensors", ".", "temp"].concat());
assert_eq!(hash_key(&static_key), hash_key(&interned_key));
}
#[test]
fn test_string_key_borrow() {
use std::collections::HashMap;
let mut map: HashMap<StringKey, i32> = HashMap::new();
map.insert("sensors.temp".into(), 42);
assert_eq!(map.get("sensors.temp"), Some(&42));
}
#[test]
fn test_record_id_basic() {
let id = RecordId::new(42);
assert_eq!(id.index(), 42);
assert_eq!(id.raw(), 42);
}
#[test]
fn test_record_id_copy() {
let id1 = RecordId::new(10);
let id2 = id1; assert_eq!(id1, id2);
}
#[test]
fn test_string_key_display() {
let key: StringKey = "sensors.temperature".into();
assert_eq!(format!("{}", key), "sensors.temperature");
}
#[test]
fn test_string_key_debug() {
let static_key: StringKey = "sensors.temp".into();
let interned_key = StringKey::intern(["sensors", ".", "temp"].concat());
let static_debug = format!("{:?}", static_key);
let interned_debug = format!("{:?}", interned_key);
assert!(static_debug.contains("Static"));
assert!(interned_debug.contains("Interned"));
}
#[test]
fn test_string_key_partial_eq_str() {
let key: StringKey = "sensors.temperature".into();
assert!(key == "sensors.temperature");
assert!(key != "other.key");
let s: &str = "sensors.temperature";
assert!(key == s);
}
#[test]
fn test_string_key_from_string() {
let owned = "sensors.temperature".to_string();
let key: StringKey = owned.into();
assert!(!key.is_static());
assert_eq!(key.as_str(), "sensors.temperature");
}
#[test]
fn test_record_id_display() {
let id = RecordId::new(42);
assert_eq!(format!("{}", id), "RecordId(42)");
}
#[test]
fn test_static_str_record_key() {
let key: &'static str = "sensors.temp";
assert_eq!(<&str as RecordKey>::as_str(&key), "sensors.temp");
}
#[test]
fn test_string_key_record_key_trait() {
use crate::RecordKey;
let key: StringKey = "sensors.temp".into();
assert_eq!(RecordKey::as_str(&key), "sensors.temp");
}
#[test]
fn test_string_key_hash_borrow_contract() {
use core::borrow::Borrow;
use core::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
fn hash_value<T: Hash>(t: &T) -> u64 {
let mut h = DefaultHasher::new();
t.hash(&mut h);
h.finish()
}
let static_key: StringKey = "sensors.temp".into();
let borrowed: &str = static_key.borrow();
assert_eq!(
hash_value(&static_key),
hash_value(&borrowed),
"Static StringKey hash must match its borrowed string hash"
);
let interned_key = StringKey::intern(["sensors", ".", "temp"].concat());
let borrowed: &str = interned_key.borrow();
assert_eq!(
hash_value(&interned_key),
hash_value(&borrowed),
"Interned StringKey hash must match its borrowed string hash"
);
use std::collections::HashMap;
let mut map: HashMap<StringKey, i32> = HashMap::new();
map.insert("key.one".into(), 1);
map.insert(StringKey::intern("key.two"), 2);
assert_eq!(map.get("key.one"), Some(&1));
assert_eq!(map.get("key.two"), Some(&2));
assert_eq!(map.get("key.nonexistent"), None);
}
}