1use serde::{Deserialize, Deserializer, 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/// Deserialization helper for things that wrap a CowStr
287pub struct CowStrVisitor;
288
289impl<'de> serde::de::Visitor<'de> for CowStrVisitor {
290 type Value = CowStr<'de>;
291
292 #[inline]
293 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
294 write!(formatter, "a string")
295 }
296
297 #[inline]
298 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
299 where
300 E: serde::de::Error,
301 {
302 Ok(CowStr::copy_from_str(v))
303 }
304
305 #[inline]
306 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
307 where
308 E: serde::de::Error,
309 {
310 Ok(CowStr::Borrowed(v))
311 }
312
313 #[inline]
314 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
315 where
316 E: serde::de::Error,
317 {
318 Ok(v.into())
319 }
320}
321
322impl<'de, 'a> Deserialize<'de> for CowStr<'a>
323where
324 'de: 'a,
325{
326 #[inline]
327 fn deserialize<D>(deserializer: D) -> Result<CowStr<'a>, D::Error>
328 where
329 D: serde::Deserializer<'de>,
330 {
331 deserializer.deserialize_str(CowStrVisitor)
332 }
333}
334
335/// Serde helper for deserializing stuff when you want an owned version
336pub fn deserialize_owned<'de, T, D>(deserializer: D) -> Result<<T as IntoStatic>::Output, D::Error>
337where
338 T: Deserialize<'de> + IntoStatic,
339 D: Deserializer<'de>,
340{
341 let value = T::deserialize(deserializer)?;
342 Ok(value.into_static())
343}
344
345/// Convert to a CowStr.
346pub trait ToCowStr {
347 /// Convert to a CowStr.
348 fn to_cowstr(&self) -> CowStr<'_>;
349}
350
351impl<T> ToCowStr for T
352where
353 T: fmt::Display + ?Sized,
354{
355 fn to_cowstr(&self) -> CowStr<'_> {
356 CowStr::Owned(smol_str::format_smolstr!("{}", self))
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn test_partialeq_with_str() {
366 let cow_str1 = CowStr::Borrowed("hello");
367 let cow_str2 = CowStr::Borrowed("hello");
368 let cow_str3 = CowStr::Borrowed("world");
369
370 assert_eq!(cow_str1, "hello");
371 assert_eq!("hello", cow_str1);
372 assert_eq!(cow_str1, cow_str2);
373 assert_ne!(cow_str1, "world");
374 assert_ne!("world", cow_str1);
375 assert_ne!(cow_str1, cow_str3);
376 }
377}