use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::string::String;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::ops::Deref;
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
use crate::IntoStatic;
#[derive(Clone)]
pub enum CowStr<'s> {
Borrowed(&'s str),
Owned(SmolStr),
}
impl CowStr<'static> {
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<Self, core::str::Utf8Error> {
Ok(Self::Borrowed(core::str::from_utf8(s)?))
}
#[inline]
pub fn from_utf8_owned(s: impl AsRef<[u8]>) -> Result<Self, core::str::Utf8Error> {
Ok(Self::Owned(SmolStr::new(core::str::from_utf8(s.as_ref())?)))
}
#[inline]
pub fn from_utf8_lossy(s: &'s [u8]) -> Self {
Self::Owned(String::from_utf8_lossy(&s).into())
}
#[inline]
pub unsafe fn from_utf8_unchecked(s: &'s [u8]) -> Self {
unsafe { Self::Owned(SmolStr::new(core::str::from_utf8_unchecked(s))) }
}
#[inline]
pub fn as_str(&self) -> &str {
match self {
CowStr::Borrowed(s) => s,
CowStr::Owned(s) => s.as_str(),
}
}
}
impl AsRef<str> 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<Cow<'a, str>> 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<String> for CowStr<'_> {
#[inline]
fn from(s: String) -> Self {
#[allow(clippy::useless_conversion)]
CowStr::Owned(s.into())
}
}
impl From<Box<str>> for CowStr<'_> {
#[inline]
fn from(s: Box<str>) -> 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<CowStr<'_>> 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<CowStr<'_>> for SmolStr {
#[inline]
fn from(s: CowStr<'_>) -> Self {
match s {
CowStr::Borrowed(s) => SmolStr::new(s),
CowStr::Owned(s) => SmolStr::new(s),
}
}
}
impl From<SmolStr> for CowStr<'_> {
#[inline]
fn from(s: SmolStr) -> Self {
CowStr::Owned(s)
}
}
impl From<CowStr<'_>> for Box<str> {
#[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<CowStr<'a>> 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<CowStr<'_>> for &str {
#[inline]
fn eq(&self, other: &CowStr<'_>) -> bool {
*self == other.deref()
}
}
impl PartialEq<String> for CowStr<'_> {
#[inline]
fn eq(&self, other: &String) -> bool {
self.deref() == other.as_str()
}
}
impl PartialEq<CowStr<'_>> 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<core::cmp::Ordering> {
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) -> core::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<H: Hasher>(&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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self)
}
}
pub 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<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(CowStr::copy_from_str(v))
}
#[inline]
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(CowStr::Borrowed(v))
}
#[inline]
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v.into())
}
}
impl<'de, 'a> Deserialize<'de> for CowStr<'a>
where
'de: 'a,
{
#[inline]
fn deserialize<D>(deserializer: D) -> Result<CowStr<'a>, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(CowStrVisitor)
}
}
pub trait ToCowStr {
fn to_cowstr(&self) -> CowStr<'_>;
}
impl<T> 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);
}
}