use serde::{Deserialize, Serialize}; use smol_str::SmolStr; use std::{ borrow::Cow, fmt, hash::{Hash, Hasher}, ops::Deref, }; use crate::IntoStatic; /// Shamelessly copied from [](https://github.com/bearcove/merde) /// A copy-on-write immutable string type that uses [`SmolStr`] for /// the "owned" variant. /// /// The standard [`Cow`] type cannot be used, since /// `::Owned` is `String`, and not `SmolStr`. #[derive(Clone)] pub enum CowStr<'s> { /// &str varaiant Borrowed(&'s str), /// Smolstr variant Owned(SmolStr), } impl CowStr<'static> { /// Create a new `CowStr` by copying from a `&str` — this might allocate /// if the string is longer than `MAX_INLINE_SIZE`. pub fn copy_from_str(s: &str) -> Self { Self::Owned(SmolStr::from(s)) } /// Create a new owned `CowStr` from a static &str without allocating pub fn new_static(s: &'static str) -> Self { Self::Owned(SmolStr::new_static(s)) } } impl<'s> CowStr<'s> { #[inline] /// Borrow and decode a byte slice as utf8 into a CowStr pub fn from_utf8(s: &'s [u8]) -> Result { Ok(Self::Borrowed(std::str::from_utf8(s)?)) } #[inline] /// Take bytes and decode them as utf8 into an owned CowStr. Might allocate. pub fn from_utf8_owned(s: impl AsRef<[u8]>) -> Result { Ok(Self::Owned(SmolStr::new(std::str::from_utf8(&s.as_ref())?))) } #[inline] /// Take bytes and decode them as utf8, skipping invalid characters, taking ownership. /// Will allocate, uses String::from_utf8_lossy() internally for now. pub fn from_utf8_lossy(s: &'s [u8]) -> Self { Self::Owned(String::from_utf8_lossy(&s).into()) } /// # Safety /// /// This function is unsafe because it does not check that the bytes are valid UTF-8. #[inline] pub unsafe fn from_utf8_unchecked(s: &'s [u8]) -> Self { unsafe { Self::Owned(SmolStr::new(std::str::from_utf8_unchecked(s))) } } /// Returns a reference to the underlying string slice. #[inline] pub fn as_str(&self) -> &str { match self { CowStr::Borrowed(s) => s, CowStr::Owned(s) => s.as_str(), } } } impl AsRef for CowStr<'_> { #[inline] fn as_ref(&self) -> &str { match self { CowStr::Borrowed(s) => s, CowStr::Owned(s) => s.as_str(), } } } impl Deref for CowStr<'_> { type Target = str; #[inline] fn deref(&self) -> &Self::Target { match self { CowStr::Borrowed(s) => s, CowStr::Owned(s) => s.as_str(), } } } impl<'a> From> for CowStr<'a> { #[inline] fn from(s: Cow<'a, str>) -> Self { match s { Cow::Borrowed(s) => CowStr::Borrowed(s), #[allow(clippy::useless_conversion)] Cow::Owned(s) => CowStr::Owned(s.into()), } } } impl<'s> From<&'s str> for CowStr<'s> { #[inline] fn from(s: &'s str) -> Self { CowStr::Borrowed(s) } } impl Default for CowStr<'_> { #[inline] fn default() -> Self { CowStr::new_static("") } } impl From for CowStr<'_> { #[inline] fn from(s: String) -> Self { #[allow(clippy::useless_conversion)] CowStr::Owned(s.into()) } } impl From> for CowStr<'_> { #[inline] fn from(s: Box) -> Self { CowStr::Owned(s.into()) } } impl<'s> From<&'s String> for CowStr<'s> { #[inline] fn from(s: &'s String) -> Self { CowStr::Borrowed(s.as_str()) } } impl From> for String { #[inline] fn from(s: CowStr<'_>) -> Self { match s { CowStr::Borrowed(s) => s.into(), #[allow(clippy::useless_conversion)] CowStr::Owned(s) => s.into(), } } } impl From> for SmolStr { #[inline] fn from(s: CowStr<'_>) -> Self { match s { CowStr::Borrowed(s) => SmolStr::new(s), CowStr::Owned(s) => SmolStr::new(s), } } } impl From for CowStr<'_> { #[inline] fn from(s: SmolStr) -> Self { CowStr::Owned(s) } } impl From> for Box { #[inline] fn from(s: CowStr<'_>) -> Self { match s { CowStr::Borrowed(s) => s.into(), CowStr::Owned(s) => String::from(s).into_boxed_str(), } } } impl<'a> PartialEq> for CowStr<'_> { #[inline] fn eq(&self, other: &CowStr<'a>) -> bool { self.deref() == other.deref() } } impl PartialEq<&str> for CowStr<'_> { #[inline] fn eq(&self, other: &&str) -> bool { self.deref() == *other } } impl PartialEq> for &str { #[inline] fn eq(&self, other: &CowStr<'_>) -> bool { *self == other.deref() } } impl PartialEq for CowStr<'_> { #[inline] fn eq(&self, other: &String) -> bool { self.deref() == other.as_str() } } impl PartialEq> for String { #[inline] fn eq(&self, other: &CowStr<'_>) -> bool { self.as_str() == other.deref() } } impl PartialOrd for CowStr<'_> { fn partial_cmp(&self, other: &Self) -> Option { Some(match (self, other) { (CowStr::Borrowed(s1), CowStr::Borrowed(s2)) => s1.cmp(s2), (CowStr::Borrowed(s1), CowStr::Owned(s2)) => s1.cmp(&s2.as_ref()), (CowStr::Owned(s1), CowStr::Borrowed(s2)) => s1.as_str().cmp(s2), (CowStr::Owned(s1), CowStr::Owned(s2)) => s1.cmp(s2), }) } } impl Ord for CowStr<'_> { fn cmp(&self, other: &Self) -> std::cmp::Ordering { match (self, other) { (CowStr::Borrowed(s1), CowStr::Borrowed(s2)) => s1.cmp(s2), (CowStr::Borrowed(s1), CowStr::Owned(s2)) => s1.cmp(&s2.as_ref()), (CowStr::Owned(s1), CowStr::Borrowed(s2)) => s1.as_str().cmp(s2), (CowStr::Owned(s1), CowStr::Owned(s2)) => s1.cmp(s2), } } } impl Eq for CowStr<'_> {} impl Hash for CowStr<'_> { #[inline] fn hash(&self, state: &mut H) { self.deref().hash(state) } } impl fmt::Debug for CowStr<'_> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.deref().fmt(f) } } impl fmt::Display for CowStr<'_> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.deref().fmt(f) } } impl IntoStatic for CowStr<'_> { type Output = CowStr<'static>; #[inline] fn into_static(self) -> Self::Output { match self { CowStr::Borrowed(s) => CowStr::Owned((*s).into()), CowStr::Owned(s) => CowStr::Owned(s), } } } impl Serialize for CowStr<'_> { #[inline] fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(self) } } // impl<'de> Deserialize<'de> for CowStr<'_> { // #[inline] // fn deserialize(deserializer: D) -> Result, D::Error> // where // D: serde::Deserializer<'de>, // { // struct CowStrVisitor; // impl<'de> serde::de::Visitor<'de> for CowStrVisitor { // type Value = CowStr<'static>; // #[inline] // fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { // write!(formatter, "a string") // } // #[inline] // fn visit_str(self, v: &str) -> Result // where // E: serde::de::Error, // { // Ok(CowStr::copy_from_str(v)) // } // #[inline] // fn visit_string(self, v: String) -> Result // where // E: serde::de::Error, // { // Ok(v.into()) // } // } // deserializer.deserialize_str(CowStrVisitor) // } // } impl<'de, 'a, 'b> Deserialize<'de> for CowStr<'a> where 'de: 'a, { #[inline] fn deserialize(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { struct CowStrVisitor; impl<'de> serde::de::Visitor<'de> for CowStrVisitor { type Value = CowStr<'de>; #[inline] fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "a string") } #[inline] fn visit_str(self, v: &str) -> Result where E: serde::de::Error, { Ok(CowStr::copy_from_str(v)) } #[inline] fn visit_borrowed_str(self, v: &'de str) -> Result where E: serde::de::Error, { Ok(CowStr::Borrowed(v)) } #[inline] fn visit_string(self, v: String) -> Result where E: serde::de::Error, { Ok(v.into()) } } deserializer.deserialize_str(CowStrVisitor) } } /// Convert to a CowStr. pub trait ToCowStr { /// Convert to a CowStr. fn to_cowstr(&self) -> CowStr<'_>; } impl ToCowStr for T where T: fmt::Display + ?Sized, { fn to_cowstr(&self) -> CowStr<'_> { CowStr::Owned(smol_str::format_smolstr!("{}", self)) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_partialeq_with_str() { let cow_str1 = CowStr::Borrowed("hello"); let cow_str2 = CowStr::Borrowed("hello"); let cow_str3 = CowStr::Borrowed("world"); assert_eq!(cow_str1, "hello"); assert_eq!("hello", cow_str1); assert_eq!(cow_str1, cow_str2); assert_ne!(cow_str1, "world"); assert_ne!("world", cow_str1); assert_ne!(cow_str1, cow_str3); } }