merde_core/
cowstr.rs

1use std::{
2    borrow::Cow,
3    fmt,
4    hash::{Hash, Hasher},
5    ops::Deref,
6};
7
8use compact_str::CompactString;
9
10use crate::IntoStatic;
11
12/// A copy-on-write string type that uses [`CompactString`] for
13/// the "owned" variant.
14///
15/// The standard [`Cow`] type cannot be used, since
16/// `<str as ToOwned>::Owned` is `String`, and not `CompactString`.
17#[derive(Clone)]
18pub enum CowStr<'s> {
19    Borrowed(&'s str),
20    Owned(CompactString),
21}
22
23impl CowStr<'static> {
24    /// Create a new `CowStr` by copying from a `&str` — this might allocate
25    /// if the `compact_str` feature is disabled, or if the string is longer
26    /// than `MAX_INLINE_SIZE`.
27    pub fn copy_from_str(s: &str) -> Self {
28        Self::Owned(CompactString::from(s))
29    }
30}
31
32impl<'s> CowStr<'s> {
33    #[inline]
34    pub fn from_utf8(s: &'s [u8]) -> Result<Self, std::str::Utf8Error> {
35        Ok(Self::Borrowed(std::str::from_utf8(s)?))
36    }
37
38    #[inline]
39    pub fn from_utf8_owned(s: Vec<u8>) -> Result<Self, std::str::Utf8Error> {
40        Ok(Self::Owned(CompactString::from_utf8(s)?))
41    }
42
43    #[inline]
44    pub fn from_utf8_lossy(s: &'s [u8]) -> Self {
45        Self::Owned(CompactString::from_utf8_lossy(s))
46    }
47
48    /// # Safety
49    ///
50    /// This function is unsafe because it does not check that the bytes are valid UTF-8.
51    #[inline]
52    pub unsafe fn from_utf8_unchecked(s: &'s [u8]) -> Self {
53        Self::Owned(CompactString::from_utf8_unchecked(s))
54    }
55}
56
57impl AsRef<str> for CowStr<'_> {
58    #[inline]
59    fn as_ref(&self) -> &str {
60        match self {
61            CowStr::Borrowed(s) => s,
62            CowStr::Owned(s) => s.as_str(),
63        }
64    }
65}
66
67impl Deref for CowStr<'_> {
68    type Target = str;
69
70    #[inline]
71    fn deref(&self) -> &Self::Target {
72        match self {
73            CowStr::Borrowed(s) => s,
74            CowStr::Owned(s) => s.as_str(),
75        }
76    }
77}
78
79impl<'a> From<Cow<'a, str>> for CowStr<'a> {
80    #[inline]
81    fn from(s: Cow<'a, str>) -> Self {
82        match s {
83            Cow::Borrowed(s) => CowStr::Borrowed(s),
84            #[allow(clippy::useless_conversion)]
85            Cow::Owned(s) => CowStr::Owned(s.into()),
86        }
87    }
88}
89
90impl<'s> From<&'s str> for CowStr<'s> {
91    #[inline]
92    fn from(s: &'s str) -> Self {
93        CowStr::Borrowed(s)
94    }
95}
96
97impl From<String> for CowStr<'_> {
98    #[inline]
99    fn from(s: String) -> Self {
100        #[allow(clippy::useless_conversion)]
101        CowStr::Owned(s.into())
102    }
103}
104
105impl From<Box<str>> for CowStr<'_> {
106    #[inline]
107    fn from(s: Box<str>) -> Self {
108        CowStr::Owned(s.into())
109    }
110}
111
112impl<'s> From<&'s String> for CowStr<'s> {
113    #[inline]
114    fn from(s: &'s String) -> Self {
115        CowStr::Borrowed(s.as_str())
116    }
117}
118
119impl From<CowStr<'_>> for String {
120    #[inline]
121    fn from(s: CowStr<'_>) -> Self {
122        match s {
123            CowStr::Borrowed(s) => s.into(),
124            #[allow(clippy::useless_conversion)]
125            CowStr::Owned(s) => s.into(),
126        }
127    }
128}
129
130impl From<CowStr<'_>> for Box<str> {
131    #[inline]
132    fn from(s: CowStr<'_>) -> Self {
133        match s {
134            CowStr::Borrowed(s) => s.into(),
135            CowStr::Owned(s) => s.into(),
136        }
137    }
138}
139
140impl<'a> PartialEq<CowStr<'a>> for CowStr<'_> {
141    #[inline]
142    fn eq(&self, other: &CowStr<'a>) -> bool {
143        self.deref() == other.deref()
144    }
145}
146
147impl PartialEq<&str> for CowStr<'_> {
148    #[inline]
149    fn eq(&self, other: &&str) -> bool {
150        self.deref() == *other
151    }
152}
153
154impl PartialEq<CowStr<'_>> for &str {
155    #[inline]
156    fn eq(&self, other: &CowStr<'_>) -> bool {
157        *self == other.deref()
158    }
159}
160
161impl PartialEq<String> for CowStr<'_> {
162    #[inline]
163    fn eq(&self, other: &String) -> bool {
164        self.deref() == other.as_str()
165    }
166}
167
168impl PartialEq<CowStr<'_>> for String {
169    #[inline]
170    fn eq(&self, other: &CowStr<'_>) -> bool {
171        self.as_str() == other.deref()
172    }
173}
174
175impl Eq for CowStr<'_> {}
176
177impl Hash for CowStr<'_> {
178    #[inline]
179    fn hash<H: Hasher>(&self, state: &mut H) {
180        self.deref().hash(state)
181    }
182}
183
184impl fmt::Debug for CowStr<'_> {
185    #[inline]
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        self.deref().fmt(f)
188    }
189}
190
191impl fmt::Display for CowStr<'_> {
192    #[inline]
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        self.deref().fmt(f)
195    }
196}
197
198impl IntoStatic for CowStr<'_> {
199    type Output = CowStr<'static>;
200
201    #[inline]
202    fn into_static(self) -> Self::Output {
203        match self {
204            CowStr::Borrowed(s) => CowStr::Owned((*s).into()),
205            CowStr::Owned(s) => CowStr::Owned(s),
206        }
207    }
208}
209
210#[cfg(feature = "serde")]
211mod serde_impls {
212    use super::*;
213
214    use serde::{Deserialize, Serialize};
215
216    impl Serialize for CowStr<'_> {
217        #[inline]
218        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
219        where
220            S: serde::Serializer,
221        {
222            serializer.serialize_str(self)
223        }
224    }
225
226    impl<'de: 'a, 'a> Deserialize<'de> for CowStr<'a> {
227        #[inline]
228        fn deserialize<D>(deserializer: D) -> Result<CowStr<'a>, D::Error>
229        where
230            D: serde::Deserializer<'de>,
231        {
232            struct CowStrVisitor;
233
234            impl<'de> serde::de::Visitor<'de> for CowStrVisitor {
235                type Value = CowStr<'de>;
236
237                #[inline]
238                fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
239                    write!(formatter, "a string")
240                }
241
242                #[inline]
243                fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
244                where
245                    E: serde::de::Error,
246                {
247                    Ok(CowStr::copy_from_str(v))
248                }
249
250                #[inline]
251                fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
252                where
253                    E: serde::de::Error,
254                {
255                    Ok(CowStr::Borrowed(v))
256                }
257
258                #[inline]
259                fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
260                where
261                    E: serde::de::Error,
262                {
263                    Ok(v.into())
264                }
265            }
266
267            deserializer.deserialize_str(CowStrVisitor)
268        }
269    }
270}
271
272#[cfg(feature = "rusqlite")]
273mod rusqlite_impls {
274    use super::*;
275    use rusqlite::{types::FromSql, types::FromSqlError, types::ToSql, Result as RusqliteResult};
276
277    impl ToSql for CowStr<'_> {
278        #[inline]
279        fn to_sql(&self) -> RusqliteResult<rusqlite::types::ToSqlOutput<'_>> {
280            Ok(rusqlite::types::ToSqlOutput::Borrowed(self.as_ref().into()))
281        }
282    }
283
284    impl FromSql for CowStr<'_> {
285        #[inline]
286        fn column_result(value: rusqlite::types::ValueRef<'_>) -> Result<Self, FromSqlError> {
287            match value {
288                rusqlite::types::ValueRef::Text(s) => Ok(CowStr::from_utf8(s)
289                    .map_err(|e| FromSqlError::Other(Box::new(e)))?
290                    .into_static()),
291                _ => Err(FromSqlError::InvalidType),
292            }
293        }
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    #[test]
302    fn test_partialeq_with_str() {
303        let cow_str1 = CowStr::Borrowed("hello");
304        let cow_str2 = CowStr::Borrowed("hello");
305        let cow_str3 = CowStr::Borrowed("world");
306
307        assert_eq!(cow_str1, "hello");
308        assert_eq!("hello", cow_str1);
309        assert_eq!(cow_str1, cow_str2);
310        assert_ne!(cow_str1, "world");
311        assert_ne!("world", cow_str1);
312        assert_ne!(cow_str1, cow_str3);
313    }
314
315    #[cfg(feature = "rusqlite")]
316    #[test]
317    fn test_rusqlite_integration() -> Result<(), Box<dyn std::error::Error>> {
318        use rusqlite::Connection;
319
320        // Create an in-memory database
321        let conn = Connection::open_in_memory()?;
322
323        // Create a table
324        conn.execute(
325            "CREATE TABLE test_table (id INTEGER PRIMARY KEY, value TEXT)",
326            [],
327        )?;
328
329        // Insert a CowStr value
330        let cow_str = CowStr::from("Hello, Rusqlite!");
331        conn.execute("INSERT INTO test_table (value) VALUES (?1)", [&cow_str])?;
332
333        // Retrieve the value
334        let mut stmt = conn.prepare("SELECT value FROM test_table")?;
335        let mut rows = stmt.query([])?;
336
337        if let Some(row) = rows.next()? {
338            let retrieved: CowStr = row.get(0)?;
339            assert_eq!(retrieved, "Hello, Rusqlite!");
340        } else {
341            panic!("No rows returned");
342        }
343
344        Ok(())
345    }
346}