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
286// impl<'de> Deserialize<'de> for CowStr<'_> {
287// #[inline]
288// fn deserialize<D>(deserializer: D) -> Result<CowStr<'static>, D::Error>
289// where
290// D: serde::Deserializer<'de>,
291// {
292// struct CowStrVisitor;
293
294// impl<'de> serde::de::Visitor<'de> for CowStrVisitor {
295// type Value = CowStr<'static>;
296
297// #[inline]
298// fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
299// write!(formatter, "a string")
300// }
301
302// #[inline]
303// fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
304// where
305// E: serde::de::Error,
306// {
307// Ok(CowStr::copy_from_str(v))
308// }
309
310// #[inline]
311// fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
312// where
313// E: serde::de::Error,
314// {
315// Ok(v.into())
316// }
317// }
318
319// deserializer.deserialize_str(CowStrVisitor)
320// }
321// }
322
323impl<'de, 'a, 'b> Deserialize<'de> for CowStr<'a>
324where
325 'de: 'a,
326{
327 #[inline]
328 fn deserialize<D>(deserializer: D) -> Result<CowStr<'a>, D::Error>
329 where
330 D: serde::Deserializer<'de>,
331 {
332 struct CowStrVisitor;
333
334 impl<'de> serde::de::Visitor<'de> for CowStrVisitor {
335 type Value = CowStr<'de>;
336
337 #[inline]
338 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
339 write!(formatter, "a string")
340 }
341
342 #[inline]
343 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
344 where
345 E: serde::de::Error,
346 {
347 Ok(CowStr::copy_from_str(v))
348 }
349
350 #[inline]
351 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
352 where
353 E: serde::de::Error,
354 {
355 Ok(CowStr::Borrowed(v))
356 }
357
358 #[inline]
359 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
360 where
361 E: serde::de::Error,
362 {
363 Ok(v.into())
364 }
365 }
366
367 deserializer.deserialize_str(CowStrVisitor)
368 }
369}
370
371/// Convert to a CowStr.
372pub trait ToCowStr {
373 /// Convert to a CowStr.
374 fn to_cowstr(&self) -> CowStr<'_>;
375}
376
377impl<T> ToCowStr for T
378where
379 T: fmt::Display + ?Sized,
380{
381 fn to_cowstr(&self) -> CowStr<'_> {
382 CowStr::Owned(smol_str::format_smolstr!("{}", self))
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
391 fn test_partialeq_with_str() {
392 let cow_str1 = CowStr::Borrowed("hello");
393 let cow_str2 = CowStr::Borrowed("hello");
394 let cow_str3 = CowStr::Borrowed("world");
395
396 assert_eq!(cow_str1, "hello");
397 assert_eq!("hello", cow_str1);
398 assert_eq!(cow_str1, cow_str2);
399 assert_ne!(cow_str1, "world");
400 assert_ne!("world", cow_str1);
401 assert_ne!(cow_str1, cow_str3);
402 }
403}