A better Rust ATProto crate
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}