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> { Borrowed(&'s str), Owned(SmolStr), } impl CowStr<'static> { /// Create a new `CowStr` by copying from a `&str` — this might allocate /// if the `compact_str` feature is disabled, or if the string is longer /// than `MAX_INLINE_SIZE`. pub fn copy_from_str(s: &str) -> Self { Self::Owned(SmolStr::from(s)) } pub fn new_static(s: &'static str) -> Self { Self::Owned(SmolStr::new_static(s)) } } impl<'s> CowStr<'s> { #[inline] pub fn from_utf8(s: &'s [u8]) -> Result { Ok(Self::Borrowed(std::str::from_utf8(s)?)) } #[inline] pub fn from_utf8_owned(s: Vec) -> Result { Ok(Self::Owned(SmolStr::new(std::str::from_utf8(&s)?))) } #[inline] 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))) } } } 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 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 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 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: 'a, 'a> Deserialize<'de> for CowStr<'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) } } #[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); } }