A better Rust ATProto crate
at oauth 10 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/// 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// impl<'de> Deserialize<'de> for CowStr<'_> { 287// #[inline] 288// fn deserialize<D>(deserializer: D) -> Result<CowStr<'static>, D::Error> 289// where 290// D: serde::Deserializer<'de>, 291// { 292// struct CowStrVisitor; 293 294// impl<'de> serde::de::Visitor<'de> for CowStrVisitor { 295// type Value = CowStr<'static>; 296 297// #[inline] 298// fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 299// write!(formatter, "a string") 300// } 301 302// #[inline] 303// fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 304// where 305// E: serde::de::Error, 306// { 307// Ok(CowStr::copy_from_str(v)) 308// } 309 310// #[inline] 311// fn visit_string<E>(self, v: String) -> Result<Self::Value, E> 312// where 313// E: serde::de::Error, 314// { 315// Ok(v.into()) 316// } 317// } 318 319// deserializer.deserialize_str(CowStrVisitor) 320// } 321// } 322 323impl<'de, 'a, 'b> 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 struct CowStrVisitor; 333 334 impl<'de> serde::de::Visitor<'de> for CowStrVisitor { 335 type Value = CowStr<'de>; 336 337 #[inline] 338 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 339 write!(formatter, "a string") 340 } 341 342 #[inline] 343 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 344 where 345 E: serde::de::Error, 346 { 347 Ok(CowStr::copy_from_str(v)) 348 } 349 350 #[inline] 351 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E> 352 where 353 E: serde::de::Error, 354 { 355 Ok(CowStr::Borrowed(v)) 356 } 357 358 #[inline] 359 fn visit_string<E>(self, v: String) -> Result<Self::Value, E> 360 where 361 E: serde::de::Error, 362 { 363 Ok(v.into()) 364 } 365 } 366 367 deserializer.deserialize_str(CowStrVisitor) 368 } 369} 370 371/// Convert to a CowStr. 372pub trait ToCowStr { 373 /// Convert to a CowStr. 374 fn to_cowstr(&self) -> CowStr<'_>; 375} 376 377impl<T> ToCowStr for T 378where 379 T: fmt::Display + ?Sized, 380{ 381 fn to_cowstr(&self) -> CowStr<'_> { 382 CowStr::Owned(smol_str::format_smolstr!("{}", self)) 383 } 384} 385 386#[cfg(test)] 387mod tests { 388 use super::*; 389 390 #[test] 391 fn test_partialeq_with_str() { 392 let cow_str1 = CowStr::Borrowed("hello"); 393 let cow_str2 = CowStr::Borrowed("hello"); 394 let cow_str3 = CowStr::Borrowed("world"); 395 396 assert_eq!(cow_str1, "hello"); 397 assert_eq!("hello", cow_str1); 398 assert_eq!(cow_str1, cow_str2); 399 assert_ne!(cow_str1, "world"); 400 assert_ne!("world", cow_str1); 401 assert_ne!(cow_str1, cow_str3); 402 } 403}