jacquard_common/
cowstr.rs1use alloc::borrow::Cow;
2use alloc::boxed::Box;
3use alloc::string::String;
4use core::fmt;
5use core::hash::{Hash, Hasher};
6use core::ops::Deref;
7
8use serde::{Deserialize, Serialize};
9use smol_str::SmolStr;
10
11use crate::IntoStatic;
12
13#[derive(Clone)]
21pub enum CowStr<'s> {
22 Borrowed(&'s str),
24 Owned(SmolStr),
26}
27
28impl CowStr<'static> {
29 pub fn copy_from_str(s: &str) -> Self {
32 Self::Owned(SmolStr::from(s))
33 }
34
35 pub fn new_static(s: &'static str) -> Self {
37 Self::Owned(SmolStr::new_static(s))
38 }
39}
40
41impl<'s> CowStr<'s> {
42 #[inline]
43 pub fn from_utf8(s: &'s [u8]) -> Result<Self, core::str::Utf8Error> {
45 Ok(Self::Borrowed(core::str::from_utf8(s)?))
46 }
47
48 #[inline]
49 pub fn from_utf8_owned(s: impl AsRef<[u8]>) -> Result<Self, core::str::Utf8Error> {
51 Ok(Self::Owned(SmolStr::new(core::str::from_utf8(s.as_ref())?)))
52 }
53
54 #[inline]
55 pub fn from_utf8_lossy(s: &'s [u8]) -> Self {
58 Self::Owned(String::from_utf8_lossy(&s).into())
59 }
60
61 #[inline]
65 pub unsafe fn from_utf8_unchecked(s: &'s [u8]) -> Self {
66 unsafe { Self::Owned(SmolStr::new(core::str::from_utf8_unchecked(s))) }
67 }
68
69 #[inline]
71 pub fn as_str(&self) -> &str {
72 match self {
73 CowStr::Borrowed(s) => s,
74 CowStr::Owned(s) => s.as_str(),
75 }
76 }
77}
78
79impl AsRef<str> for CowStr<'_> {
80 #[inline]
81 fn as_ref(&self) -> &str {
82 match self {
83 CowStr::Borrowed(s) => s,
84 CowStr::Owned(s) => s.as_str(),
85 }
86 }
87}
88
89impl Deref for CowStr<'_> {
90 type Target = str;
91
92 #[inline]
93 fn deref(&self) -> &Self::Target {
94 match self {
95 CowStr::Borrowed(s) => s,
96 CowStr::Owned(s) => s.as_str(),
97 }
98 }
99}
100
101impl<'a> From<Cow<'a, str>> for CowStr<'a> {
102 #[inline]
103 fn from(s: Cow<'a, str>) -> Self {
104 match s {
105 Cow::Borrowed(s) => CowStr::Borrowed(s),
106 #[allow(clippy::useless_conversion)]
107 Cow::Owned(s) => CowStr::Owned(s.into()),
108 }
109 }
110}
111
112impl<'s> From<&'s str> for CowStr<'s> {
113 #[inline]
114 fn from(s: &'s str) -> Self {
115 CowStr::Borrowed(s)
116 }
117}
118
119impl Default for CowStr<'_> {
120 #[inline]
121 fn default() -> Self {
122 CowStr::new_static("")
123 }
124}
125
126impl From<String> for CowStr<'_> {
127 #[inline]
128 fn from(s: String) -> Self {
129 #[allow(clippy::useless_conversion)]
130 CowStr::Owned(s.into())
131 }
132}
133
134impl From<Box<str>> for CowStr<'_> {
135 #[inline]
136 fn from(s: Box<str>) -> Self {
137 CowStr::Owned(s.into())
138 }
139}
140
141impl<'s> From<&'s String> for CowStr<'s> {
142 #[inline]
143 fn from(s: &'s String) -> Self {
144 CowStr::Borrowed(s.as_str())
145 }
146}
147
148impl From<CowStr<'_>> for String {
149 #[inline]
150 fn from(s: CowStr<'_>) -> Self {
151 match s {
152 CowStr::Borrowed(s) => s.into(),
153 #[allow(clippy::useless_conversion)]
154 CowStr::Owned(s) => s.into(),
155 }
156 }
157}
158
159impl From<CowStr<'_>> for SmolStr {
160 #[inline]
161 fn from(s: CowStr<'_>) -> Self {
162 match s {
163 CowStr::Borrowed(s) => SmolStr::new(s),
164 CowStr::Owned(s) => SmolStr::new(s),
165 }
166 }
167}
168
169impl From<SmolStr> for CowStr<'_> {
170 #[inline]
171 fn from(s: SmolStr) -> Self {
172 CowStr::Owned(s)
173 }
174}
175
176impl From<CowStr<'_>> for Box<str> {
177 #[inline]
178 fn from(s: CowStr<'_>) -> Self {
179 match s {
180 CowStr::Borrowed(s) => s.into(),
181 CowStr::Owned(s) => String::from(s).into_boxed_str(),
182 }
183 }
184}
185
186impl<'a> PartialEq<CowStr<'a>> for CowStr<'_> {
187 #[inline]
188 fn eq(&self, other: &CowStr<'a>) -> bool {
189 self.deref() == other.deref()
190 }
191}
192
193impl PartialEq<&str> for CowStr<'_> {
194 #[inline]
195 fn eq(&self, other: &&str) -> bool {
196 self.deref() == *other
197 }
198}
199
200impl PartialEq<CowStr<'_>> for &str {
201 #[inline]
202 fn eq(&self, other: &CowStr<'_>) -> bool {
203 *self == other.deref()
204 }
205}
206
207impl PartialEq<String> for CowStr<'_> {
208 #[inline]
209 fn eq(&self, other: &String) -> bool {
210 self.deref() == other.as_str()
211 }
212}
213
214impl PartialEq<CowStr<'_>> for String {
215 #[inline]
216 fn eq(&self, other: &CowStr<'_>) -> bool {
217 self.as_str() == other.deref()
218 }
219}
220
221impl PartialOrd for CowStr<'_> {
222 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
223 Some(match (self, other) {
224 (CowStr::Borrowed(s1), CowStr::Borrowed(s2)) => s1.cmp(s2),
225 (CowStr::Borrowed(s1), CowStr::Owned(s2)) => s1.cmp(&s2.as_ref()),
226 (CowStr::Owned(s1), CowStr::Borrowed(s2)) => s1.as_str().cmp(s2),
227 (CowStr::Owned(s1), CowStr::Owned(s2)) => s1.cmp(s2),
228 })
229 }
230}
231
232impl Ord for CowStr<'_> {
233 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
234 match (self, other) {
235 (CowStr::Borrowed(s1), CowStr::Borrowed(s2)) => s1.cmp(s2),
236 (CowStr::Borrowed(s1), CowStr::Owned(s2)) => s1.cmp(&s2.as_ref()),
237 (CowStr::Owned(s1), CowStr::Borrowed(s2)) => s1.as_str().cmp(s2),
238 (CowStr::Owned(s1), CowStr::Owned(s2)) => s1.cmp(s2),
239 }
240 }
241}
242
243impl Eq for CowStr<'_> {}
244
245impl Hash for CowStr<'_> {
246 #[inline]
247 fn hash<H: Hasher>(&self, state: &mut H) {
248 self.deref().hash(state)
249 }
250}
251
252impl fmt::Debug for CowStr<'_> {
253 #[inline]
254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255 self.deref().fmt(f)
256 }
257}
258
259impl fmt::Display for CowStr<'_> {
260 #[inline]
261 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262 self.deref().fmt(f)
263 }
264}
265
266impl IntoStatic for CowStr<'_> {
267 type Output = CowStr<'static>;
268
269 #[inline]
270 fn into_static(self) -> Self::Output {
271 match self {
272 CowStr::Borrowed(s) => CowStr::Owned((*s).into()),
273 CowStr::Owned(s) => CowStr::Owned(s),
274 }
275 }
276}
277
278impl Serialize for CowStr<'_> {
279 #[inline]
280 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
281 where
282 S: serde::Serializer,
283 {
284 serializer.serialize_str(self)
285 }
286}
287
288pub struct CowStrVisitor;
290
291impl<'de> serde::de::Visitor<'de> for CowStrVisitor {
292 type Value = CowStr<'de>;
293
294 #[inline]
295 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
296 write!(formatter, "a string")
297 }
298
299 #[inline]
300 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
301 where
302 E: serde::de::Error,
303 {
304 Ok(CowStr::copy_from_str(v))
305 }
306
307 #[inline]
308 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
309 where
310 E: serde::de::Error,
311 {
312 Ok(CowStr::Borrowed(v))
313 }
314
315 #[inline]
316 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
317 where
318 E: serde::de::Error,
319 {
320 Ok(v.into())
321 }
322}
323
324impl<'de, 'a> Deserialize<'de> for CowStr<'a>
325where
326 'de: 'a,
327{
328 #[inline]
329 fn deserialize<D>(deserializer: D) -> Result<CowStr<'a>, D::Error>
330 where
331 D: serde::Deserializer<'de>,
332 {
333 deserializer.deserialize_str(CowStrVisitor)
334 }
335}
336
337pub trait ToCowStr {
339 fn to_cowstr(&self) -> CowStr<'_>;
341}
342
343impl<T> ToCowStr for T
344where
345 T: fmt::Display + ?Sized,
346{
347 fn to_cowstr(&self) -> CowStr<'_> {
348 CowStr::Owned(smol_str::format_smolstr!("{}", self))
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
357 fn test_partialeq_with_str() {
358 let cow_str1 = CowStr::Borrowed("hello");
359 let cow_str2 = CowStr::Borrowed("hello");
360 let cow_str3 = CowStr::Borrowed("world");
361
362 assert_eq!(cow_str1, "hello");
363 assert_eq!("hello", cow_str1);
364 assert_eq!(cow_str1, cow_str2);
365 assert_ne!(cow_str1, "world");
366 assert_ne!("world", cow_str1);
367 assert_ne!(cow_str1, cow_str3);
368 }
369}