use std::fmt;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Name(ustr::Ustr);
impl Name {
#[inline]
pub fn new(s: &str) -> Self {
const SLOTS: usize = 4096;
thread_local! {
static RECENT: [std::cell::Cell<Option<ustr::Ustr>>; SLOTS] =
const { [const { std::cell::Cell::new(None) }; SLOTS] };
}
let mut h = rustc_hash::FxHasher::default();
std::hash::Hasher::write(&mut h, s.as_bytes());
let slot = (std::hash::Hasher::finish(&h) as usize) & (SLOTS - 1);
RECENT.with(|recent| {
let cell = &recent[slot];
if let Some(u) = cell.get() {
if u.as_str() == s {
return Self(u);
}
}
let u = ustr::ustr(s);
cell.set(Some(u));
Self(u)
})
}
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn ascii_lowercase(self) -> Self {
if self.as_str().bytes().all(|b| !b.is_ascii_uppercase()) {
return self;
}
static CACHE: std::sync::OnceLock<
dashmap::DashMap<ustr::Ustr, ustr::Ustr, rustc_hash::FxBuildHasher>,
> = std::sync::OnceLock::new();
let cache = CACHE.get_or_init(|| dashmap::DashMap::with_hasher(Default::default()));
if let Some(v) = cache.get(&self.0) {
return Name(*v);
}
let lowered = ustr::ustr(&self.as_str().to_ascii_lowercase());
const CACHE_CAP: usize = 1 << 16;
if cache.len() >= CACHE_CAP {
cache.clear();
}
cache.insert(self.0, lowered);
Name(lowered)
}
}
impl From<&str> for Name {
#[inline]
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for Name {
#[inline]
fn from(s: String) -> Self {
Self::new(&s)
}
}
impl From<Arc<str>> for Name {
#[inline]
fn from(s: Arc<str>) -> Self {
Self::new(&s)
}
}
impl From<Name> for String {
#[inline]
fn from(s: Name) -> String {
s.as_str().to_string()
}
}
impl From<Name> for Arc<str> {
#[inline]
fn from(s: Name) -> Arc<str> {
Arc::from(s.as_str())
}
}
impl std::ops::Deref for Name {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> for Name {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl PartialEq<str> for Name {
#[inline]
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<Name> for str {
#[inline]
fn eq(&self, other: &Name) -> bool {
self == other.as_str()
}
}
impl PartialEq<String> for Name {
#[inline]
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<Arc<str>> for Name {
#[inline]
fn eq(&self, other: &Arc<str>) -> bool {
self.as_str() == other.as_ref()
}
}
impl PartialEq<Name> for Arc<str> {
#[inline]
fn eq(&self, other: &Name) -> bool {
self.as_ref() == other.as_str()
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl fmt::Debug for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Name({:?})", self.as_str())
}
}
impl Serialize for Name {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for Name {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = std::borrow::Cow::<str>::deserialize(deserializer)?;
Ok(Self::new(&s))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ascii_lowercase_correct_across_cache_overflow() {
let early = Name::new("App\\EarlyService");
assert_eq!(early.ascii_lowercase().as_str(), "app\\earlyservice");
for i in 0..70_000 {
let n = Name::new(&format!("App\\Storm{i}"));
assert_eq!(
n.ascii_lowercase().as_str(),
format!("app\\storm{i}").as_str()
);
}
assert_eq!(early.ascii_lowercase().as_str(), "app\\earlyservice");
}
}