msg_tool\utils/
case_insensitive_string.rs

1use serde::Deserialize;
2use std::borrow::Borrow;
3use std::cmp::Ordering;
4use std::hash::{Hash, Hasher};
5use std::ops::{Deref, DerefMut};
6
7#[derive(Debug, Deserialize)]
8#[serde(transparent)]
9/// A case-insensitive string wrapper. Can be used as a key in BTreeMap with a case-insensitive ordering.
10///
11/// WARN: Borrowing a &str/&String from this struct will not be case-insensitive. So getting a value from a BTreeMap with a &str/&String key is not ignore case. It just use it's original case.
12pub struct CaseInsensitiveString(String);
13
14impl PartialEq for CaseInsensitiveString {
15    fn eq(&self, other: &Self) -> bool {
16        self.0.eq_ignore_ascii_case(&other.0)
17    }
18}
19
20impl PartialEq<String> for CaseInsensitiveString {
21    fn eq(&self, other: &String) -> bool {
22        self.0.eq_ignore_ascii_case(other)
23    }
24}
25
26impl PartialEq<&str> for CaseInsensitiveString {
27    fn eq(&self, other: &&str) -> bool {
28        self.0.eq_ignore_ascii_case(other)
29    }
30}
31
32impl Eq for CaseInsensitiveString {}
33
34impl PartialOrd for CaseInsensitiveString {
35    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
36        Some(self.cmp(other))
37    }
38}
39
40impl Ord for CaseInsensitiveString {
41    fn cmp(&self, other: &Self) -> Ordering {
42        self.0
43            .to_ascii_lowercase()
44            .cmp(&other.0.to_ascii_lowercase())
45    }
46}
47
48impl Deref for CaseInsensitiveString {
49    type Target = String;
50    fn deref(&self) -> &Self::Target {
51        &self.0
52    }
53}
54
55impl DerefMut for CaseInsensitiveString {
56    fn deref_mut(&mut self) -> &mut Self::Target {
57        &mut self.0
58    }
59}
60
61impl std::fmt::Display for CaseInsensitiveString {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        self.0.fmt(f)
64    }
65}
66
67impl Hash for CaseInsensitiveString {
68    fn hash<H: Hasher>(&self, state: &mut H) {
69        self.0.to_ascii_lowercase().hash(state);
70    }
71}
72
73impl Borrow<CaseInsensitiveStr> for CaseInsensitiveString {
74    fn borrow(&self) -> &CaseInsensitiveStr {
75        CaseInsensitiveStr::from_str(&self.0)
76    }
77}
78
79#[repr(transparent)]
80pub struct CaseInsensitiveStr(str);
81
82impl CaseInsensitiveStr {
83    pub fn from_str(s: &str) -> &Self {
84        // SAFETY: CaseInsensitiveStr has the same memory layout as str, so this transmute is safe.
85        unsafe { &*(s as *const str as *const Self) }
86    }
87
88    pub fn as_str(&self) -> &str {
89        &self.0
90    }
91}
92
93impl PartialEq for CaseInsensitiveStr {
94    fn eq(&self, other: &Self) -> bool {
95        self.eq_ignore_ascii_case(&other.0)
96    }
97}
98
99impl Eq for CaseInsensitiveStr {}
100
101impl PartialOrd for CaseInsensitiveStr {
102    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
103        Some(self.cmp(other))
104    }
105}
106
107impl Ord for CaseInsensitiveStr {
108    fn cmp(&self, other: &Self) -> Ordering {
109        self.0
110            .to_ascii_lowercase()
111            .cmp(&other.0.to_ascii_lowercase())
112    }
113}
114
115impl Deref for CaseInsensitiveStr {
116    type Target = str;
117    fn deref(&self) -> &Self::Target {
118        &self.0
119    }
120}
121
122impl std::fmt::Display for CaseInsensitiveStr {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        self.0.fmt(f)
125    }
126}
127
128impl Hash for CaseInsensitiveStr {
129    fn hash<H: Hasher>(&self, state: &mut H) {
130        self.0.to_ascii_lowercase().hash(state);
131    }
132}
133
134#[test]
135fn test_btree_map() {
136    let mut map = std::collections::BTreeMap::new();
137    map.insert(CaseInsensitiveString("hella".to_string()), 0);
138    map.insert(CaseInsensitiveString("Hello".to_string()), 1);
139    map.insert(CaseInsensitiveString("world".to_string()), 2);
140    assert_eq!(map.get(CaseInsensitiveStr::from_str("hello")), Some(&1));
141    assert_eq!(map.get(CaseInsensitiveStr::from_str("WORLD")), Some(&2));
142    assert_eq!(map.get(CaseInsensitiveStr::from_str("hella")), Some(&0));
143}
144
145#[test]
146fn test_hash_map() {
147    let mut map = std::collections::HashMap::new();
148    map.insert(CaseInsensitiveString("hells".to_string()), 0);
149    map.insert(CaseInsensitiveString("Hello".to_string()), 1);
150    map.insert(CaseInsensitiveString("world".to_string()), 2);
151    assert_eq!(map.get(CaseInsensitiveStr::from_str("hello")), Some(&1));
152    assert_eq!(map.get(CaseInsensitiveStr::from_str("WORLD")), Some(&2));
153    assert_eq!(map.get(CaseInsensitiveStr::from_str("hells")), Some(&0));
154}