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
68impl AsRef<str> for CowStr<'_> {
69 #[inline]
70 fn as_ref(&self) -> &str {
71 match self {
72 CowStr::Borrowed(s) => s,
73 CowStr::Owned(s) => s.as_str(),
74 }
75 }
76}
77
78impl Deref for CowStr<'_> {
79 type Target = str;
80
81 #[inline]
82 fn deref(&self) -> &Self::Target {
83 match self {
84 CowStr::Borrowed(s) => s,
85 CowStr::Owned(s) => s.as_str(),
86 }
87 }
88}
89
90impl<'a> From<Cow<'a, str>> for CowStr<'a> {
91 #[inline]
92 fn from(s: Cow<'a, str>) -> Self {
93 match s {
94 Cow::Borrowed(s) => CowStr::Borrowed(s),
95 #[allow(clippy::useless_conversion)]
96 Cow::Owned(s) => CowStr::Owned(s.into()),
97 }
98 }
99}
100
101impl<'s> From<&'s str> for CowStr<'s> {
102 #[inline]
103 fn from(s: &'s str) -> Self {
104 CowStr::Borrowed(s)
105 }
106}
107
108impl From<String> for CowStr<'_> {
109 #[inline]
110 fn from(s: String) -> Self {
111 #[allow(clippy::useless_conversion)]
112 CowStr::Owned(s.into())
113 }
114}
115
116impl From<Box<str>> for CowStr<'_> {
117 #[inline]
118 fn from(s: Box<str>) -> Self {
119 CowStr::Owned(s.into())
120 }
121}
122
123impl<'s> From<&'s String> for CowStr<'s> {
124 #[inline]
125 fn from(s: &'s String) -> Self {
126 CowStr::Borrowed(s.as_str())
127 }
128}
129
130impl From<CowStr<'_>> for String {
131 #[inline]
132 fn from(s: CowStr<'_>) -> Self {
133 match s {
134 CowStr::Borrowed(s) => s.into(),
135 #[allow(clippy::useless_conversion)]
136 CowStr::Owned(s) => s.into(),
137 }
138 }
139}
140
141impl From<CowStr<'_>> for Box<str> {
142 #[inline]
143 fn from(s: CowStr<'_>) -> Self {
144 match s {
145 CowStr::Borrowed(s) => s.into(),
146 CowStr::Owned(s) => String::from(s).into_boxed_str(),
147 }
148 }
149}
150
151impl<'a> PartialEq<CowStr<'a>> for CowStr<'_> {
152 #[inline]
153 fn eq(&self, other: &CowStr<'a>) -> bool {
154 self.deref() == other.deref()
155 }
156}
157
158impl PartialEq<&str> for CowStr<'_> {
159 #[inline]
160 fn eq(&self, other: &&str) -> bool {
161 self.deref() == *other
162 }
163}
164
165impl PartialEq<CowStr<'_>> for &str {
166 #[inline]
167 fn eq(&self, other: &CowStr<'_>) -> bool {
168 *self == other.deref()
169 }
170}
171
172impl PartialEq<String> for CowStr<'_> {
173 #[inline]
174 fn eq(&self, other: &String) -> bool {
175 self.deref() == other.as_str()
176 }
177}
178
179impl PartialEq<CowStr<'_>> for String {
180 #[inline]
181 fn eq(&self, other: &CowStr<'_>) -> bool {
182 self.as_str() == other.deref()
183 }
184}
185
186impl PartialOrd for CowStr<'_> {
187 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
188 Some(match (self, other) {
189 (CowStr::Borrowed(s1), CowStr::Borrowed(s2)) => s1.cmp(s2),
190 (CowStr::Borrowed(s1), CowStr::Owned(s2)) => s1.cmp(&s2.as_ref()),
191 (CowStr::Owned(s1), CowStr::Borrowed(s2)) => s1.as_str().cmp(s2),
192 (CowStr::Owned(s1), CowStr::Owned(s2)) => s1.cmp(s2),
193 })
194 }
195}
196
197impl Ord for CowStr<'_> {
198 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
199 match (self, other) {
200 (CowStr::Borrowed(s1), CowStr::Borrowed(s2)) => s1.cmp(s2),
201 (CowStr::Borrowed(s1), CowStr::Owned(s2)) => s1.cmp(&s2.as_ref()),
202 (CowStr::Owned(s1), CowStr::Borrowed(s2)) => s1.as_str().cmp(s2),
203 (CowStr::Owned(s1), CowStr::Owned(s2)) => s1.cmp(s2),
204 }
205 }
206}
207
208impl Eq for CowStr<'_> {}
209
210impl Hash for CowStr<'_> {
211 #[inline]
212 fn hash<H: Hasher>(&self, state: &mut H) {
213 self.deref().hash(state)
214 }
215}
216
217impl fmt::Debug for CowStr<'_> {
218 #[inline]
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 self.deref().fmt(f)
221 }
222}
223
224impl fmt::Display for CowStr<'_> {
225 #[inline]
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 self.deref().fmt(f)
228 }
229}
230
231impl IntoStatic for CowStr<'_> {
232 type Output = CowStr<'static>;
233
234 #[inline]
235 fn into_static(self) -> Self::Output {
236 match self {
237 CowStr::Borrowed(s) => CowStr::Owned((*s).into()),
238 CowStr::Owned(s) => CowStr::Owned(s),
239 }
240 }
241}
242
243impl Serialize for CowStr<'_> {
244 #[inline]
245 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
246 where
247 S: serde::Serializer,
248 {
249 serializer.serialize_str(self)
250 }
251}
252
253impl<'de: 'a, 'a> Deserialize<'de> for CowStr<'a> {
254 #[inline]
255 fn deserialize<D>(deserializer: D) -> Result<CowStr<'a>, D::Error>
256 where
257 D: serde::Deserializer<'de>,
258 {
259 struct CowStrVisitor;
260
261 impl<'de> serde::de::Visitor<'de> for CowStrVisitor {
262 type Value = CowStr<'de>;
263
264 #[inline]
265 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
266 write!(formatter, "a string")
267 }
268
269 #[inline]
270 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
271 where
272 E: serde::de::Error,
273 {
274 Ok(CowStr::copy_from_str(v))
275 }
276
277 #[inline]
278 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
279 where
280 E: serde::de::Error,
281 {
282 Ok(CowStr::Borrowed(v))
283 }
284
285 #[inline]
286 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
287 where
288 E: serde::de::Error,
289 {
290 Ok(v.into())
291 }
292 }
293
294 deserializer.deserialize_str(CowStrVisitor)
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn test_partialeq_with_str() {
304 let cow_str1 = CowStr::Borrowed("hello");
305 let cow_str2 = CowStr::Borrowed("hello");
306 let cow_str3 = CowStr::Borrowed("world");
307
308 assert_eq!(cow_str1, "hello");
309 assert_eq!("hello", cow_str1);
310 assert_eq!(cow_str1, cow_str2);
311 assert_ne!(cow_str1, "world");
312 assert_ne!("world", cow_str1);
313 assert_ne!(cow_str1, cow_str3);
314 }
315}