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#[derive(Clone)]
18pub enum CowStr<'s> {
19 Borrowed(&'s str),
20 Owned(CompactString),
21}
22
23impl CowStr<'static> {
24 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 #[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 let conn = Connection::open_in_memory()?;
322
323 conn.execute(
325 "CREATE TABLE test_table (id INTEGER PRIMARY KEY, value TEXT)",
326 [],
327 )?;
328
329 let cow_str = CowStr::from("Hello, Rusqlite!");
331 conn.execute("INSERT INTO test_table (value) VALUES (?1)", [&cow_str])?;
332
333 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}