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