case_insensitive_string/
lib.rs

1#[cfg(feature = "serde")]
2pub extern crate serde;
3
4#[cfg(feature = "compact")]
5pub extern crate compact_str;
6
7pub mod features;
8
9#[cfg(feature = "compact")]
10use compact_str::CompactString;
11/// case-insensitive string handling
12#[cfg(not(feature = "compact"))]
13#[derive(Debug, Clone, Default)]
14#[repr(transparent)]
15pub struct CaseInsensitiveString(String);
16
17/// case-insensitive string handling
18#[cfg(feature = "compact")]
19#[derive(Debug, Clone, Default)]
20#[repr(transparent)]
21pub struct CaseInsensitiveString(CompactString);
22
23impl CaseInsensitiveString {
24    /// Creates a `CaseInsensitiveString` slice from any byte slice.
25    ///
26    ///
27    /// This is a cost-free conversion.
28    ///
29    /// # Example
30    ///
31    /// You can create `CaseInsensitiveString`'s from byte arrays, byte slices or string slices:
32    ///
33    /// ```
34    /// use case_insensitive_string::CaseInsensitiveString;
35    ///
36    /// let a = CaseInsensitiveString::new(b"abc");
37    /// let b = CaseInsensitiveString::new("abc");
38    ///
39    /// assert_eq!(a, b);
40    /// ```
41    #[inline]
42    pub fn new<'a, B: ?Sized + AsRef<[u8]>>(bytes: &'a B) -> CaseInsensitiveString {
43        CaseInsensitiveString::from(bytes.as_ref())
44    }
45
46    #[inline]
47    pub fn as_bytes(&self) -> &[u8] {
48        &self.0.as_bytes()
49    }
50
51    #[cfg(not(feature = "compact"))]
52    #[inline]
53    pub fn inner(&self) -> &String {
54        &self.0
55    }
56
57    #[cfg(feature = "compact")]
58    #[inline]
59    pub fn inner(&self) -> &CompactString {
60        &self.0
61    }
62
63    /// Appends the given [`char`] to the end of this [`CaseInsensitiveString`].
64    ///
65    /// # Examples
66    /// ```
67    /// # use case_insensitive_string::CaseInsensitiveString;
68    /// let mut s = CaseInsensitiveString::new("foo");
69    ///
70    /// s.push('b');
71    /// s.push('a');
72    /// s.push('r');
73    ///
74    /// assert_eq!(CaseInsensitiveString::from("foobar"), s);
75    /// ```
76    pub fn push(&mut self, ch: char) {
77        self.push_str(ch.encode_utf8(&mut [0; 4]));
78    }
79
80    /// Appends a given string slice onto the end of this [`CaseInsensitiveString`]
81    ///
82    /// # Examples
83    /// ```
84    /// # use case_insensitive_string::CaseInsensitiveString;
85    /// let mut s = CaseInsensitiveString::new("abc");
86    ///
87    /// s.push_str("123");
88    ///
89    /// assert_eq!(CaseInsensitiveString::new("abc123"), s);
90    /// ```
91    #[inline]
92    pub fn push_str(&mut self, s: &str) {
93        self.0.push_str(s)
94    }
95
96    /// Convert the [`CaseInsensitiveString`] into a [`String`].
97    /// ```
98    pub fn into_string(self) -> String {
99        self.0.into()
100    }
101
102    /// Removes a [`char`] from this [`CaseInsensitiveString`] at a byte position and returns it.
103    ///
104    /// This is an *O*(*n*) operation, as it requires copying every element in the
105    /// buffer.
106    ///
107    /// # Panics
108    ///
109    /// Panics if `idx` is larger than or equal to the [`CaseInsensitiveString`]'s length,
110    /// or if it does not lie on a [`char`] boundary.
111    ///
112    /// # Examples
113    ///
114    /// ### Basic usage:
115    ///
116    /// ```
117    /// # use case_insensitive_string::CaseInsensitiveString;
118    /// let mut c = CaseInsensitiveString::from("hello world");
119    ///
120    /// assert_eq!(c.remove(0), 'h');
121    /// assert_eq!(c, "ello world".into());
122    ///
123    /// assert_eq!(c.remove(5), 'w');
124    /// assert_eq!(c, "ello orld".into());
125    /// ```
126    ///
127    /// ### Past total length:
128    ///
129    /// ```should_panic
130    /// # use case_insensitive_string::CaseInsensitiveString;
131    /// let mut c = CaseInsensitiveString::from("hello there!");
132    /// c.remove(100);
133    /// ```
134    ///
135    /// ### Not on char boundary:
136    ///
137    /// ```should_panic
138    /// # use case_insensitive_string::CaseInsensitiveString;
139    /// let mut c = CaseInsensitiveString::from("🦄");
140    /// c.remove(1);
141    /// ```
142    #[inline]
143    pub fn remove(&mut self, idx: usize) -> char {
144        self.0.remove(idx)
145    }
146
147    /// Returns the length of the [`CaseInsensitiveString`] in `bytes`, not [`char`]s or graphemes.
148    ///
149    /// When using `UTF-8` encoding (which all strings in Rust do) a single character will be 1 to 4
150    /// bytes long, therefore the return value of this method might not be what a human considers
151    /// the length of the string.
152    ///
153    /// # Examples
154    /// ```
155    /// # use case_insensitive_string::CaseInsensitiveString;
156    /// let ascii = CaseInsensitiveString::new("hello world");
157    /// assert_eq!(ascii.len(), 11);
158    ///
159    /// let emoji = CaseInsensitiveString::new("👱");
160    /// assert_eq!(emoji.len(), 4);
161    /// ```
162    #[inline]
163    pub fn len(&self) -> usize {
164        self.0.len()
165    }
166
167    /// Returns `true` if the [`CaseInsensitiveString`] has a length of 0, `false` otherwise
168    ///
169    /// # Examples
170    /// ```
171    /// # use case_insensitive_string::CaseInsensitiveString;
172    /// let mut msg = CaseInsensitiveString::new("");
173    /// assert!(msg.is_empty());
174    ///
175    /// // add some characters
176    /// msg.push_str("hello reader!");
177    /// assert!(!msg.is_empty());
178    /// ```
179    #[inline]
180    pub fn is_empty(&self) -> bool {
181        self.len() == 0
182    }
183}
184
185impl Eq for CaseInsensitiveString {}
186
187impl std::hash::Hash for CaseInsensitiveString {
188    #[inline]
189    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
190        self.0.to_ascii_lowercase().hash(state)
191    }
192}
193
194impl From<&str> for CaseInsensitiveString {
195    #[inline]
196    fn from(s: &str) -> Self {
197        CaseInsensitiveString { 0: s.into() }
198    }
199}
200
201#[cfg(feature = "compact")]
202impl From<CompactString> for CaseInsensitiveString {
203    #[inline]
204    fn from(s: CompactString) -> Self {
205        CaseInsensitiveString { 0: s.into() }
206    }
207}
208
209impl From<String> for CaseInsensitiveString {
210    fn from(s: String) -> Self {
211        CaseInsensitiveString { 0: s.into() }
212    }
213}
214
215impl From<&[u8]> for CaseInsensitiveString {
216    fn from(s: &[u8]) -> Self {
217        CaseInsensitiveString {
218            0: String::from_utf8_lossy(s).into(),
219        }
220    }
221}
222
223impl From<CaseInsensitiveString> for String {
224    #[inline]
225    fn from(s: CaseInsensitiveString) -> Self {
226        s.into_string()
227    }
228}
229
230impl From<&CaseInsensitiveString> for String {
231    #[inline]
232    fn from(s: &CaseInsensitiveString) -> Self {
233        s.0.to_string()
234    }
235}
236
237impl From<&CaseInsensitiveString> for CaseInsensitiveString {
238    #[inline]
239    fn from(s: &CaseInsensitiveString) -> Self {
240        s.clone()
241    }
242}
243
244impl AsRef<str> for CaseInsensitiveString {
245    #[inline]
246    fn as_ref(&self) -> &str {
247        &self.0
248    }
249}
250
251impl core::fmt::Display for CaseInsensitiveString {
252    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
253        write!(f, "{}", self.0)
254    }
255}
256
257impl std::ops::Deref for CaseInsensitiveString {
258    type Target = str;
259
260    #[inline]
261    fn deref(&self) -> &str {
262        &self.0.as_str()
263    }
264}
265
266impl std::ops::DerefMut for CaseInsensitiveString {
267    #[inline]
268    fn deref_mut(&mut self) -> &mut str {
269        self.0.as_mut_str()
270    }
271}
272
273impl std::borrow::Borrow<str> for CaseInsensitiveString {
274    #[inline]
275    fn borrow(&self) -> &str {
276        &self.0.as_str()
277    }
278}
279
280impl PartialEq for CaseInsensitiveString {
281    #[inline]
282    fn eq(&self, other: &Self) -> bool {
283        self.0.eq_ignore_ascii_case(&other.0)
284    }
285}
286
287#[cfg(feature = "compact")]
288impl PartialEq<CaseInsensitiveString> for &CompactString {
289    fn eq(&self, other: &CaseInsensitiveString) -> bool {
290        self.eq_ignore_ascii_case(&other.as_ref())
291    }
292}
293
294impl PartialEq<CaseInsensitiveString> for String {
295    fn eq(&self, other: &CaseInsensitiveString) -> bool {
296        self.eq_ignore_ascii_case(&other.as_ref())
297    }
298}
299
300impl<'a> PartialEq<&'a CaseInsensitiveString> for String {
301    fn eq(&self, other: &&CaseInsensitiveString) -> bool {
302        self.eq_ignore_ascii_case(&other.as_ref())
303    }
304}
305
306impl PartialEq<CaseInsensitiveString> for &String {
307    fn eq(&self, other: &CaseInsensitiveString) -> bool {
308        self.eq_ignore_ascii_case(&other.as_ref())
309    }
310}
311
312impl PartialEq<CaseInsensitiveString> for str {
313    fn eq(&self, other: &CaseInsensitiveString) -> bool {
314        self.eq_ignore_ascii_case(&other.as_ref())
315    }
316}
317
318impl<'a> PartialEq<&'a CaseInsensitiveString> for str {
319    fn eq(&self, other: &&CaseInsensitiveString) -> bool {
320        self.eq_ignore_ascii_case(&other.as_ref())
321    }
322}
323
324impl PartialEq<CaseInsensitiveString> for &str {
325    fn eq(&self, other: &CaseInsensitiveString) -> bool {
326        self.eq_ignore_ascii_case(&other.as_ref())
327    }
328}
329
330impl PartialEq<CaseInsensitiveString> for &&str {
331    fn eq(&self, other: &CaseInsensitiveString) -> bool {
332        self.eq_ignore_ascii_case(&other.as_ref())
333    }
334}
335
336impl<'a> PartialEq<CaseInsensitiveString> for std::borrow::Cow<'a, str> {
337    fn eq(&self, other: &CaseInsensitiveString) -> bool {
338        self.eq_ignore_ascii_case(&other.as_ref())
339    }
340}
341
342impl<'a> PartialEq<CaseInsensitiveString> for &std::borrow::Cow<'a, str> {
343    fn eq(&self, other: &CaseInsensitiveString) -> bool {
344        self.eq_ignore_ascii_case(&other.as_ref())
345    }
346}
347
348impl PartialEq<String> for &CaseInsensitiveString {
349    fn eq(&self, other: &String) -> bool {
350        self.eq_ignore_ascii_case(&other.as_ref())
351    }
352}
353
354impl<'a> PartialEq<std::borrow::Cow<'a, str>> for &CaseInsensitiveString {
355    fn eq(&self, other: &std::borrow::Cow<'a, str>) -> bool {
356        self.eq_ignore_ascii_case(&other.as_ref())
357    }
358}
359
360impl PartialOrd for CaseInsensitiveString {
361    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
362        Some(self.cmp(&other))
363    }
364}
365
366impl Ord for CaseInsensitiveString {
367    /// # Examples
368    ///
369    /// ```
370    /// # use case_insensitive_string::CaseInsensitiveString;
371    /// assert!(CaseInsensitiveString::from("goodbye") < CaseInsensitiveString::from("hello"));
372    /// assert!(CaseInsensitiveString::from("Goodbye") < CaseInsensitiveString::from("hello"));
373    /// assert!(CaseInsensitiveString::from("goodbye") < CaseInsensitiveString::from("Hello"));
374    /// assert!(CaseInsensitiveString::from("Goodbye") < CaseInsensitiveString::from("Hello"));
375    /// ```
376    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
377        self.0.to_ascii_lowercase().cmp(&other.0.to_ascii_lowercase())
378    }
379}