use std::borrow::Borrow;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct AsciiKey<T: ?Sized>(pub T);
impl AsciiKey<str> {
#[inline]
#[must_use]
pub const fn new(key: &str) -> &Self {
unsafe { &*(std::ptr::from_ref(key) as *const Self) }
}
}
impl<T: AsRef<str> + ?Sized> Hash for AsciiKey<T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
let string_ref = self.0.as_ref();
for byte in string_ref.bytes() {
state.write_u8(byte.to_ascii_uppercase());
}
state.write_usize(string_ref.len());
}
}
impl<T: AsRef<str> + ?Sized, U: AsRef<str> + ?Sized> PartialEq<AsciiKey<U>> for AsciiKey<T> {
#[inline]
fn eq(&self, other: &AsciiKey<U>) -> bool {
self.0.as_ref().eq_ignore_ascii_case(other.0.as_ref())
}
}
impl<T: AsRef<str> + ?Sized> Eq for AsciiKey<T> {}
impl Borrow<AsciiKey<str>> for AsciiKey<String> {
#[inline]
fn borrow(&self) -> &AsciiKey<str> {
AsciiKey::new(self.0.as_str())
}
}
#[derive(Debug)]
pub struct AsciiMap<V> {
inner: HashMap<AsciiKey<String>, V>,
}
impl<V> AsciiMap<V> {
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
inner: HashMap::new(),
}
}
#[inline]
pub fn insert(&mut self, key: String, value: V) -> Option<V> {
self.inner.insert(AsciiKey(key), value)
}
#[inline]
#[must_use]
pub fn get(&self, key: &str) -> Option<&V> {
self.inner.get(AsciiKey::new(key))
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (&str, &V)> {
self.inner.iter().map(|(k, v)| (k.0.as_str(), v))
}
#[inline]
pub fn values(&self) -> impl Iterator<Item = &V> {
self.inner.values()
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.inner.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
impl<V> Default for AsciiMap<V> {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ascii_key_case_insensitive_hash() {
use std::collections::hash_map::DefaultHasher;
fn hash(s: &str) -> u64 {
let mut hasher = DefaultHasher::new();
AsciiKey(s).hash(&mut hasher);
hasher.finish()
}
assert_eq!(hash("GET"), hash("get"));
assert_eq!(hash("GET"), hash("Get"));
assert_eq!(hash("GET"), hash("gEt"));
assert_eq!(hash("HELLO"), hash("hello"));
assert_eq!(hash("Hello"), hash("hElLo"));
assert_ne!(hash("GET"), hash("SET"));
assert_ne!(hash("GET"), hash("GETS"));
}
#[test]
fn test_ascii_key_equality() {
assert_eq!(AsciiKey("GET"), AsciiKey("get"));
assert_eq!(AsciiKey("GET"), AsciiKey("Get"));
assert_eq!(AsciiKey("HELLO"), AsciiKey("hello"));
assert_ne!(AsciiKey("GET"), AsciiKey("SET"));
assert_ne!(AsciiKey("GET"), AsciiKey("GETS"));
}
#[test]
fn test_ascii_map_insert_and_get() {
let mut map: AsciiMap<i32> = AsciiMap::new();
map.insert("GET".to_string(), 1);
map.insert("SET".to_string(), 2);
assert_eq!(map.get("GET"), Some(&1));
assert_eq!(map.get("get"), Some(&1));
assert_eq!(map.get("Get"), Some(&1));
assert_eq!(map.get("SET"), Some(&2));
assert_eq!(map.get("set"), Some(&2));
assert_eq!(map.get("UNKNOWN"), None);
}
#[test]
fn test_ascii_map_overwrite() {
let mut map: AsciiMap<i32> = AsciiMap::new();
map.insert("GET".to_string(), 1);
let old = map.insert("get".to_string(), 2);
assert_eq!(old, Some(1));
assert_eq!(map.get("GET"), Some(&2));
}
#[test]
fn test_ascii_map_iter() {
let mut map: AsciiMap<i32> = AsciiMap::new();
map.insert("A".to_string(), 1);
map.insert("B".to_string(), 2);
let mut count = 0u8;
for _ in map.iter() {
count += 1;
}
assert_eq!(count, 2);
}
#[test]
fn test_ascii_map_values() {
let mut map: AsciiMap<i32> = AsciiMap::new();
map.insert("A".to_string(), 1);
map.insert("B".to_string(), 2);
let values: Vec<i32> = map.values().copied().collect();
assert!(values.contains(&1));
assert!(values.contains(&2));
}
#[test]
fn test_ascii_map_len_and_empty() {
let mut map: AsciiMap<i32> = AsciiMap::new();
assert!(map.is_empty());
assert_eq!(map.len(), 0);
map.insert("GET".to_string(), 1);
assert!(!map.is_empty());
assert_eq!(map.len(), 1);
}
#[test]
fn test_zero_allocation_lookup() {
let mut map: AsciiMap<i32> = AsciiMap::new();
map.insert("COMMAND".to_string(), 42);
let key: &str = "command";
assert_eq!(map.get(key), Some(&42));
}
}