A better Rust ATProto crate
at main 8.9 kB view raw
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}