uncased/
borrowed.rs

1use core::cmp::Ordering;
2use core::hash::{Hash, Hasher};
3use core::fmt;
4
5#[cfg(feature = "alloc")]
6use alloc::{string::String, sync::Arc};
7
8/// A cost-free reference to an uncased (case-insensitive, case-preserving)
9/// ASCII string.
10///
11/// This is typically created from an `&str` as follows:
12///
13/// ```rust
14/// use uncased::UncasedStr;
15///
16/// let ascii_ref: &UncasedStr = "Hello, world!".into();
17/// ```
18#[derive(Debug)]
19#[repr(transparent)]
20pub struct UncasedStr(str);
21
22impl UncasedStr {
23    /// Cost-free conversion from an `&str` reference to an `UncasedStr`.
24    ///
25    /// This is a `const fn` on Rust 1.56+.
26    ///
27    /// # Example
28    ///
29    /// ```rust
30    /// use uncased::UncasedStr;
31    ///
32    /// let uncased_str = UncasedStr::new("Hello!");
33    /// assert_eq!(uncased_str, "hello!");
34    /// assert_eq!(uncased_str, "Hello!");
35    /// assert_eq!(uncased_str, "HeLLo!");
36    /// ```
37    #[inline(always)]
38    #[cfg(not(const_fn_transmute))]
39    pub fn new(string: &str) -> &UncasedStr {
40        // This is a `newtype`-like transformation. `repr(transparent)` ensures
41        // that this is safe and correct.
42        unsafe { &*(string as *const str as *const UncasedStr) }
43    }
44
45    /// Cost-free conversion from an `&str` reference to an `UncasedStr`.
46    ///
47    /// This is a `const fn` on Rust 1.56+.
48    ///
49    /// # Example
50    ///
51    /// ```rust
52    /// use uncased::UncasedStr;
53    ///
54    /// let uncased_str = UncasedStr::new("Hello!");
55    /// assert_eq!(uncased_str, "hello!");
56    /// assert_eq!(uncased_str, "Hello!");
57    /// assert_eq!(uncased_str, "HeLLo!");
58    /// ```
59    #[inline(always)]
60    #[cfg(const_fn_transmute)]
61    pub const fn new(string: &str) -> &UncasedStr {
62        // This is a `newtype`-like transformation. `repr(transparent)` ensures
63        // that this is safe and correct.
64        unsafe { core::mem::transmute(string) }
65    }
66
67    /// Returns `self` as an `&str`.
68    ///
69    /// # Example
70    ///
71    /// ```rust
72    /// use uncased::UncasedStr;
73    ///
74    /// let uncased_str = UncasedStr::new("Hello!");
75    /// assert_eq!(uncased_str.as_str(), "Hello!");
76    /// assert_ne!(uncased_str.as_str(), "hELLo!");
77    /// ```
78    #[inline(always)]
79    pub fn as_str(&self) -> &str {
80        &self.0
81    }
82
83    /// Returns the length, in bytes, of `self`.
84    ///
85    /// # Example
86    ///
87    /// ```rust
88    /// use uncased::UncasedStr;
89    ///
90    /// let uncased_str = UncasedStr::new("Hello!");
91    /// assert_eq!(uncased_str.len(), 6);
92    /// ```
93    #[inline(always)]
94    pub fn len(&self) -> usize {
95        self.as_str().len()
96    }
97
98    /// Returns `true` if `self` has a length of zero bytes.
99    ///
100    /// # Examples
101    ///
102    /// ```
103    /// use uncased::UncasedStr;
104    ///
105    /// let s = UncasedStr::new("");
106    /// assert!(s.is_empty());
107    ///
108    /// let s = UncasedStr::new("not empty");
109    /// assert!(!s.is_empty());
110    /// ```
111    #[inline]
112    pub fn is_empty(&self) -> bool {
113        self.as_str().is_empty()
114    }
115
116    /// Returns `true` if `self` starts with any casing of the string `string`;
117    /// otherwise, returns `false`.
118    ///
119    /// # Example
120    ///
121    /// ```rust
122    /// use uncased::UncasedStr;
123    ///
124    /// let uncased_str = UncasedStr::new("MoOO");
125    /// assert!(uncased_str.starts_with("moo"));
126    /// assert!(uncased_str.starts_with("MOO"));
127    /// assert!(uncased_str.starts_with("MOOO"));
128    /// assert!(!uncased_str.starts_with("boo"));
129    ///
130    /// let uncased_str = UncasedStr::new("Bèe");
131    /// assert!(!uncased_str.starts_with("Be"));
132    /// assert!(uncased_str.starts_with("Bè"));
133    /// assert!(uncased_str.starts_with("Bè"));
134    /// assert!(uncased_str.starts_with("bèe"));
135    /// assert!(uncased_str.starts_with("BèE"));
136    /// ```
137    #[inline(always)]
138    pub fn starts_with(&self, string: &str) -> bool {
139        self.as_str()
140            .get(..string.len())
141            .map(|s| Self::new(s) == string)
142            .unwrap_or(false)
143    }
144
145    /// Converts a `Box<UncasedStr>` into an `Uncased` without copying or
146    /// allocating.
147    ///
148    /// # Example
149    ///
150    /// ```rust
151    /// use uncased::Uncased;
152    ///
153    /// let uncased = Uncased::new("Hello!");
154    /// let boxed = uncased.clone().into_boxed_uncased();
155    /// assert_eq!(boxed.into_uncased(), uncased);
156    /// ```
157    #[inline(always)]
158    #[cfg(feature = "alloc")]
159    #[cfg_attr(nightly, doc(cfg(feature = "alloc")))]
160    pub fn into_uncased(self: alloc::boxed::Box<UncasedStr>) -> crate::Uncased<'static> {
161        // This is the inverse of a `newtype`-like transformation. The
162        // `repr(transparent)` ensures that this is safe and correct.
163        unsafe {
164            let raw_str = alloc::boxed::Box::into_raw(self) as *mut str;
165            crate::Uncased::from(alloc::boxed::Box::from_raw(raw_str).into_string())
166        }
167    }
168}
169
170impl<'a> From<&'a str> for &'a UncasedStr {
171    #[inline(always)]
172    fn from(string: &'a str) -> &'a UncasedStr {
173        UncasedStr::new(string)
174    }
175}
176
177impl<I: core::slice::SliceIndex<str, Output = str>> core::ops::Index<I> for UncasedStr {
178    type Output = UncasedStr;
179
180    #[inline]
181    fn index(&self, index: I) -> &Self::Output {
182        self.as_str()[index].into()
183    }
184}
185
186impl AsRef<str> for UncasedStr {
187    fn as_ref(&self) -> &str {
188        self.as_str()
189    }
190}
191
192impl AsRef<[u8]> for UncasedStr {
193    fn as_ref(&self) -> &[u8] {
194        self.as_str().as_bytes()
195    }
196}
197
198impl fmt::Display for UncasedStr {
199    #[inline(always)]
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        self.0.fmt(f)
202    }
203}
204
205macro_rules! impl_partial_eq {
206    ($other:ty $([$o_i:ident])? = $this:ty $([$t_i:ident])?) => (
207        impl PartialEq<$other> for $this {
208            #[inline(always)]
209            fn eq(&self, other: &$other) -> bool {
210                self $(.$t_i())? .eq_ignore_ascii_case(other $(.$o_i())?)
211            }
212        }
213    )
214}
215
216impl_partial_eq!(UncasedStr [as_str] = UncasedStr [as_str]);
217impl_partial_eq!(str = UncasedStr [as_str]);
218impl_partial_eq!(UncasedStr [as_str] = str);
219impl_partial_eq!(str = &UncasedStr [as_str]);
220impl_partial_eq!(&UncasedStr [as_str] = str);
221impl_partial_eq!(&str = UncasedStr [as_str]);
222impl_partial_eq!(UncasedStr [as_str] = &str);
223
224#[cfg(feature = "alloc")] impl_partial_eq!(String [as_str] = UncasedStr [as_str]);
225
226#[cfg(feature = "alloc")] impl_partial_eq!(UncasedStr [as_str] = String [as_str] );
227
228impl Eq for UncasedStr { }
229
230macro_rules! impl_partial_ord {
231    ($other:ty $([$o_i:ident])? >< $this:ty $([$t_i:ident])?) => (
232        impl PartialOrd<$other> for $this {
233            #[inline(always)]
234            fn partial_cmp(&self, other: &$other) -> Option<Ordering> {
235                let this: &UncasedStr = self$(.$t_i())?.into();
236                let other: &UncasedStr = other$(.$o_i())?.into();
237                this.partial_cmp(other)
238            }
239        }
240    )
241}
242
243impl PartialOrd for UncasedStr {
244    #[inline(always)]
245    fn partial_cmp(&self, other: &UncasedStr) -> Option<Ordering> {
246        Some(self.cmp(other))
247    }
248}
249
250impl Ord for UncasedStr {
251    fn cmp(&self, other: &Self) -> Ordering {
252        let self_chars = self.0.chars().map(|c| c.to_ascii_lowercase());
253        let other_chars = other.0.chars().map(|c| c.to_ascii_lowercase());
254        self_chars.cmp(other_chars)
255    }
256}
257
258impl_partial_ord!(str >< UncasedStr);
259impl_partial_ord!(UncasedStr >< str);
260
261#[cfg(feature = "alloc")] impl_partial_ord!(String [as_str] >< UncasedStr);
262#[cfg(feature = "alloc")] impl_partial_ord!(UncasedStr >< String [as_str]);
263
264impl Hash for UncasedStr {
265    #[inline(always)]
266    fn hash<H: Hasher>(&self, hasher: &mut H) {
267        self.0.bytes().for_each(|b| hasher.write_u8(b.to_ascii_lowercase()));
268    }
269}
270
271#[cfg(feature = "alloc")]
272impl From<&UncasedStr> for Arc<UncasedStr> {
273    #[inline]
274    fn from(v: &UncasedStr) -> Arc<UncasedStr> {
275        // SAFETY: `UncasedStr` is repr(transparent)(str). As a result, `str`
276        // and `UncasedStr` have the same size and alignment. Furthermore, the
277        // pointer passed to `from_raw()` is clearly obtained by calling
278        // `into_raw()`. This fulfills the safety requirements of `from_raw()`.
279        let arc: Arc<str> = Arc::from(&v.0);
280        let raw = Arc::into_raw(arc) as *const str as *const UncasedStr;
281        unsafe { Arc::from_raw(raw) }
282    }
283}