jacquard_common/
cowstr.rs

1use serde::{Deserialize, Serialize};
2use smol_str::SmolStr;
3use std::{
4    borrow::Cow,
5    fmt,
6    hash::{Hash, Hasher},
7    ops::Deref,
8};
9
10use crate::IntoStatic;
11
12/// Shamelessly copied from [](https://github.com/bearcove/merde)
13/// A copy-on-write immutable string type that uses [`SmolStr`] for
14/// the "owned" variant.
15///
16/// The standard [`Cow`] type cannot be used, since
17/// `<str as ToOwned>::Owned` is `String`, and not `SmolStr`.
18#[derive(Clone)]
19pub enum CowStr<'s> {
20    /// &str varaiant
21    Borrowed(&'s str),
22    /// Smolstr variant
23    Owned(SmolStr),
24}
25
26impl CowStr<'static> {
27    /// Create a new `CowStr` by copying from a `&str` — this might allocate
28    /// if the string is longer than `MAX_INLINE_SIZE`.
29    pub fn copy_from_str(s: &str) -> Self {
30        Self::Owned(SmolStr::from(s))
31    }
32
33    /// Create a new owned `CowStr` from a static &str without allocating
34    pub fn new_static(s: &'static str) -> Self {
35        Self::Owned(SmolStr::new_static(s))
36    }
37}
38
39impl<'s> CowStr<'s> {
40    #[inline]
41    /// Borrow and decode a byte slice as utf8 into a CowStr
42    pub fn from_utf8(s: &'s [u8]) -> Result<Self, std::str::Utf8Error> {
43        Ok(Self::Borrowed(std::str::from_utf8(s)?))
44    }
45
46    #[inline]
47    /// Take bytes and decode them as utf8 into an owned CowStr. Might allocate.
48    pub fn from_utf8_owned(s: impl AsRef<[u8]>) -> Result<Self, std::str::Utf8Error> {
49        Ok(Self::Owned(SmolStr::new(std::str::from_utf8(&s.as_ref())?)))
50    }
51
52    #[inline]
53    /// Take bytes and decode them as utf8, skipping invalid characters, taking ownership.
54    /// Will allocate, uses String::from_utf8_lossy() internally for now.
55    pub fn from_utf8_lossy(s: &'s [u8]) -> Self {
56        Self::Owned(String::from_utf8_lossy(&s).into())
57    }
58
59    /// # Safety
60    ///
61    /// This function is unsafe because it does not check that the bytes are valid UTF-8.
62    #[inline]
63    pub unsafe fn from_utf8_unchecked(s: &'s [u8]) -> Self {
64        unsafe { Self::Owned(SmolStr::new(std::str::from_utf8_unchecked(s))) }
65    }
66
67    /// Returns a reference to the underlying string slice.
68    #[inline]
69    pub fn as_str(&self) -> &str {
70        match self {
71            CowStr::Borrowed(s) => s,
72            CowStr::Owned(s) => s.as_str(),
73        }
74    }
75}
76
77impl AsRef<str> for CowStr<'_> {
78    #[inline]
79    fn as_ref(&self) -> &str {
80        match self {
81            CowStr::Borrowed(s) => s,
82            CowStr::Owned(s) => s.as_str(),
83        }
84    }
85}
86
87impl Deref for CowStr<'_> {
88    type Target = str;
89
90    #[inline]
91    fn deref(&self) -> &Self::Target {
92        match self {
93            CowStr::Borrowed(s) => s,
94            CowStr::Owned(s) => s.as_str(),
95        }
96    }
97}
98
99impl<'a> From<Cow<'a, str>> for CowStr<'a> {
100    #[inline]
101    fn from(s: Cow<'a, str>) -> Self {
102        match s {
103            Cow::Borrowed(s) => CowStr::Borrowed(s),
104            #[allow(clippy::useless_conversion)]
105            Cow::Owned(s) => CowStr::Owned(s.into()),
106        }
107    }
108}
109
110impl<'s> From<&'s str> for CowStr<'s> {
111    #[inline]
112    fn from(s: &'s str) -> Self {
113        CowStr::Borrowed(s)
114    }
115}
116
117impl Default for CowStr<'_> {
118    #[inline]
119    fn default() -> Self {
120        CowStr::new_static("")
121    }
122}
123
124impl From<String> for CowStr<'_> {
125    #[inline]
126    fn from(s: String) -> Self {
127        #[allow(clippy::useless_conversion)]
128        CowStr::Owned(s.into())
129    }
130}
131
132impl From<Box<str>> for CowStr<'_> {
133    #[inline]
134    fn from(s: Box<str>) -> Self {
135        CowStr::Owned(s.into())
136    }
137}
138
139impl<'s> From<&'s String> for CowStr<'s> {
140    #[inline]
141    fn from(s: &'s String) -> Self {
142        CowStr::Borrowed(s.as_str())
143    }
144}
145
146impl From<CowStr<'_>> for String {
147    #[inline]
148    fn from(s: CowStr<'_>) -> Self {
149        match s {
150            CowStr::Borrowed(s) => s.into(),
151            #[allow(clippy::useless_conversion)]
152            CowStr::Owned(s) => s.into(),
153        }
154    }
155}
156
157impl From<CowStr<'_>> for SmolStr {
158    #[inline]
159    fn from(s: CowStr<'_>) -> Self {
160        match s {
161            CowStr::Borrowed(s) => SmolStr::new(s),
162            CowStr::Owned(s) => SmolStr::new(s),
163        }
164    }
165}
166
167impl From<SmolStr> for CowStr<'_> {
168    #[inline]
169    fn from(s: SmolStr) -> Self {
170        CowStr::Owned(s)
171    }
172}
173
174impl From<CowStr<'_>> for Box<str> {
175    #[inline]
176    fn from(s: CowStr<'_>) -> Self {
177        match s {
178            CowStr::Borrowed(s) => s.into(),
179            CowStr::Owned(s) => String::from(s).into_boxed_str(),
180        }
181    }
182}
183
184impl<'a> PartialEq<CowStr<'a>> for CowStr<'_> {
185    #[inline]
186    fn eq(&self, other: &CowStr<'a>) -> bool {
187        self.deref() == other.deref()
188    }
189}
190
191impl PartialEq<&str> for CowStr<'_> {
192    #[inline]
193    fn eq(&self, other: &&str) -> bool {
194        self.deref() == *other
195    }
196}
197
198impl PartialEq<CowStr<'_>> for &str {
199    #[inline]
200    fn eq(&self, other: &CowStr<'_>) -> bool {
201        *self == other.deref()
202    }
203}
204
205impl PartialEq<String> for CowStr<'_> {
206    #[inline]
207    fn eq(&self, other: &String) -> bool {
208        self.deref() == other.as_str()
209    }
210}
211
212impl PartialEq<CowStr<'_>> for String {
213    #[inline]
214    fn eq(&self, other: &CowStr<'_>) -> bool {
215        self.as_str() == other.deref()
216    }
217}
218
219impl PartialOrd for CowStr<'_> {
220    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
221        Some(match (self, other) {
222            (CowStr::Borrowed(s1), CowStr::Borrowed(s2)) => s1.cmp(s2),
223            (CowStr::Borrowed(s1), CowStr::Owned(s2)) => s1.cmp(&s2.as_ref()),
224            (CowStr::Owned(s1), CowStr::Borrowed(s2)) => s1.as_str().cmp(s2),
225            (CowStr::Owned(s1), CowStr::Owned(s2)) => s1.cmp(s2),
226        })
227    }
228}
229
230impl Ord for CowStr<'_> {
231    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
232        match (self, other) {
233            (CowStr::Borrowed(s1), CowStr::Borrowed(s2)) => s1.cmp(s2),
234            (CowStr::Borrowed(s1), CowStr::Owned(s2)) => s1.cmp(&s2.as_ref()),
235            (CowStr::Owned(s1), CowStr::Borrowed(s2)) => s1.as_str().cmp(s2),
236            (CowStr::Owned(s1), CowStr::Owned(s2)) => s1.cmp(s2),
237        }
238    }
239}
240
241impl Eq for CowStr<'_> {}
242
243impl Hash for CowStr<'_> {
244    #[inline]
245    fn hash<H: Hasher>(&self, state: &mut H) {
246        self.deref().hash(state)
247    }
248}
249
250impl fmt::Debug for CowStr<'_> {
251    #[inline]
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        self.deref().fmt(f)
254    }
255}
256
257impl fmt::Display for CowStr<'_> {
258    #[inline]
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        self.deref().fmt(f)
261    }
262}
263
264impl IntoStatic for CowStr<'_> {
265    type Output = CowStr<'static>;
266
267    #[inline]
268    fn into_static(self) -> Self::Output {
269        match self {
270            CowStr::Borrowed(s) => CowStr::Owned((*s).into()),
271            CowStr::Owned(s) => CowStr::Owned(s),
272        }
273    }
274}
275
276impl Serialize for CowStr<'_> {
277    #[inline]
278    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
279    where
280        S: serde::Serializer,
281    {
282        serializer.serialize_str(self)
283    }
284}
285
286impl<'de, 'a> Deserialize<'de> for CowStr<'a>
287where
288    'de: 'a,
289{
290    #[inline]
291    fn deserialize<D>(deserializer: D) -> Result<CowStr<'a>, D::Error>
292    where
293        D: serde::Deserializer<'de>,
294    {
295        struct CowStrVisitor;
296
297        impl<'de> serde::de::Visitor<'de> for CowStrVisitor {
298            type Value = CowStr<'de>;
299
300            #[inline]
301            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
302                write!(formatter, "a string")
303            }
304
305            #[inline]
306            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
307            where
308                E: serde::de::Error,
309            {
310                Ok(CowStr::copy_from_str(v))
311            }
312
313            #[inline]
314            fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
315            where
316                E: serde::de::Error,
317            {
318                Ok(CowStr::Borrowed(v))
319            }
320
321            #[inline]
322            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
323            where
324                E: serde::de::Error,
325            {
326                Ok(v.into())
327            }
328        }
329
330        deserializer.deserialize_str(CowStrVisitor)
331    }
332}
333
334/// Convert to a CowStr.
335pub trait ToCowStr {
336    /// Convert to a CowStr.
337    fn to_cowstr(&self) -> CowStr<'_>;
338}
339
340impl<T> ToCowStr for T
341where
342    T: fmt::Display + ?Sized,
343{
344    fn to_cowstr(&self) -> CowStr<'_> {
345        CowStr::Owned(smol_str::format_smolstr!("{}", self))
346    }
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352
353    #[test]
354    fn test_partialeq_with_str() {
355        let cow_str1 = CowStr::Borrowed("hello");
356        let cow_str2 = CowStr::Borrowed("hello");
357        let cow_str3 = CowStr::Borrowed("world");
358
359        assert_eq!(cow_str1, "hello");
360        assert_eq!("hello", cow_str1);
361        assert_eq!(cow_str1, cow_str2);
362        assert_ne!(cow_str1, "world");
363        assert_ne!("world", cow_str1);
364        assert_ne!(cow_str1, cow_str3);
365    }
366}