use std::borrow::{Borrow, Cow};
use std::convert::AsRef;
use std::cmp::Ordering;
use std::str::Utf8Error;
use std::fmt;
use ref_cast::RefCast;
use stable_pattern::{Pattern, Searcher, ReverseSearcher, Split, SplitInternal};
use crate::uri::fmt::{DEFAULT_ENCODE_SET, percent_encode, percent_encode_bytes};
use crate::uncased::UncasedStr;
#[repr(transparent)]
#[derive(RefCast, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RawStr(str);
impl ToOwned for RawStr {
type Owned = RawStrBuf;
fn to_owned(&self) -> Self::Owned {
RawStrBuf(self.to_string())
}
}
#[repr(transparent)]
#[derive(RefCast, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RawStrBuf(String);
impl RawStrBuf {
pub fn into_string(self) -> String {
self.0
}
}
impl RawStr {
pub fn new<S: AsRef<str> + ?Sized>(string: &S) -> &RawStr {
RawStr::ref_cast(string.as_ref())
}
pub fn from_cow_str(cow: Cow<'_, str>) -> Cow<'_, RawStr> {
match cow {
Cow::Borrowed(b) => Cow::Borrowed(b.into()),
Cow::Owned(b) => Cow::Owned(b.into()),
}
}
pub fn into_cow_str(cow: Cow<'_, RawStr>) -> Cow<'_, str> {
match cow {
Cow::Borrowed(b) => Cow::Borrowed(b.as_str()),
Cow::Owned(b) => Cow::Owned(b.into_string()),
}
}
fn _percent_decode(&self) -> percent_encoding::PercentDecode<'_> {
percent_encoding::percent_decode(self.as_bytes())
}
#[inline(always)]
pub fn percent_decode(&self) -> Result<Cow<'_, str>, Utf8Error> {
self._percent_decode().decode_utf8()
}
#[inline(always)]
pub fn percent_decode_lossy(&self) -> Cow<'_, str> {
self._percent_decode().decode_utf8_lossy()
}
fn _replace_plus(&self) -> Cow<'_, str> {
let string = self.as_str();
let mut allocated = String::new(); for i in memchr::memchr_iter(b'+', string.as_bytes()) {
if allocated.is_empty() {
allocated = string.into();
}
unsafe { allocated.as_bytes_mut()[i] = b' '; }
}
match allocated.is_empty() {
true => Cow::Borrowed(string),
false => Cow::Owned(allocated)
}
}
#[inline(always)]
pub fn percent_encode(&self) -> Cow<'_, RawStr> {
Self::from_cow_str(percent_encode::<DEFAULT_ENCODE_SET>(self))
}
#[inline(always)]
pub fn percent_encode_bytes(bytes: &[u8]) -> Cow<'_, RawStr> {
Self::from_cow_str(percent_encode_bytes::<DEFAULT_ENCODE_SET>(bytes))
}
pub fn url_decode(&self) -> Result<Cow<'_, str>, Utf8Error> {
let string = self._replace_plus();
match percent_encoding::percent_decode(string.as_bytes()).decode_utf8()? {
Cow::Owned(s) => Ok(Cow::Owned(s)),
Cow::Borrowed(_) => Ok(string)
}
}
pub fn url_decode_lossy(&self) -> Cow<'_, str> {
let string = self._replace_plus();
match percent_encoding::percent_decode(string.as_bytes()).decode_utf8_lossy() {
Cow::Owned(s) => Cow::Owned(s),
Cow::Borrowed(_) => string
}
}
pub fn html_escape(&self) -> Cow<'_, str> {
let mut escaped = false;
let mut allocated = Vec::new(); for c in self.as_bytes() {
match *c {
b'&' | b'<' | b'>' | b'"' | b'\'' | b'/' | b'`' => {
if !escaped {
let i = (c as *const u8 as usize) - (self.as_ptr() as usize);
allocated = Vec::with_capacity(self.len() * 2);
allocated.extend_from_slice(&self.as_bytes()[..i]);
}
match *c {
b'&' => allocated.extend_from_slice(b"&"),
b'<' => allocated.extend_from_slice(b"<"),
b'>' => allocated.extend_from_slice(b">"),
b'"' => allocated.extend_from_slice(b"""),
b'\'' => allocated.extend_from_slice(b"'"),
b'/' => allocated.extend_from_slice(b"/"),
b'`' => allocated.extend_from_slice(b"`"),
_ => unreachable!()
}
escaped = true;
}
_ if escaped => allocated.push(*c),
_ => { }
}
}
if escaped {
unsafe { Cow::Owned(String::from_utf8_unchecked(allocated)) }
} else {
Cow::Borrowed(self.as_str())
}
}
#[inline]
pub const fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline(always)]
pub const fn as_str(&self) -> &str {
&self.0
}
#[inline(always)]
pub const fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub const fn as_ptr(&self) -> *const u8 {
self.as_str().as_ptr()
}
#[inline(always)]
pub fn as_uncased_str(&self) -> &UncasedStr {
self.as_str().into()
}
#[inline]
pub fn contains<'a, P: Pattern<'a>>(&'a self, pat: P) -> bool {
pat.is_contained_in(self.as_str())
}
pub fn starts_with<'a, P: Pattern<'a>>(&'a self, pat: P) -> bool {
pat.is_prefix_of(self.as_str())
}
pub fn ends_with<'a, P>(&'a self, pat: P) -> bool
where P: Pattern<'a>, <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>
{
pat.is_suffix_of(self.as_str())
}
#[inline]
pub fn find<'a, P: Pattern<'a>>(&'a self, pat: P) -> Option<usize> {
pat.into_searcher(self.as_str()).next_match().map(|(i, _)| i)
}
#[inline]
pub fn split<'a, P>(&'a self, pat: P) -> impl Iterator<Item = &'a RawStr>
where P: Pattern<'a>
{
let split: Split<'_, P> = Split(SplitInternal {
start: 0,
end: self.len(),
matcher: pat.into_searcher(self.as_str()),
allow_trailing_empty: true,
finished: false,
});
split.map(|s| s.into())
}
#[inline]
pub fn split_at_byte(&self, b: u8) -> (&RawStr, &RawStr) {
if !b.is_ascii() {
return (self, &self[0..0]);
}
match memchr::memchr(b, self.as_bytes()) {
Some(i) => unsafe {
let s = self.as_str();
let start = s.get_unchecked(0..i);
let end = s.get_unchecked((i + 1)..self.len());
(start.into(), end.into())
},
None => (self, &self[0..0])
}
}
#[inline]
pub fn strip_prefix<'a, P: Pattern<'a>>(&'a self, prefix: P) -> Option<&'a RawStr> {
prefix.strip_prefix_of(self.as_str()).map(RawStr::new)
}
#[inline]
pub fn strip_suffix<'a, P>(&'a self, suffix: P) -> Option<&'a RawStr>
where P: Pattern<'a>,<P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,
{
suffix.strip_suffix_of(self.as_str()).map(RawStr::new)
}
#[inline]
pub fn parse<F: std::str::FromStr>(&self) -> Result<F, F::Err> {
std::str::FromStr::from_str(self.as_str())
}
}
#[cfg(feature = "serde")]
mod serde {
use serde_::{ser, de, Serialize, Deserialize};
use super::*;
impl Serialize for RawStr {
fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
where S: ser::Serializer
{
self.as_str().serialize(ser)
}
}
impl<'de: 'a, 'a> Deserialize<'de> for &'a RawStr {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where D: de::Deserializer<'de>
{
<&'a str as Deserialize<'de>>::deserialize(de).map(RawStr::new)
}
}
}
impl fmt::Debug for RawStr {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<'a> From<&'a str> for &'a RawStr {
#[inline(always)]
fn from(string: &'a str) -> &'a RawStr {
RawStr::new(string)
}
}
impl<'a> From<&'a RawStr> for Cow<'a, RawStr> {
fn from(raw: &'a RawStr) -> Self {
Cow::Borrowed(raw)
}
}
impl From<RawStrBuf> for Cow<'_, RawStr> {
fn from(raw: RawStrBuf) -> Self {
Cow::Owned(raw)
}
}
macro_rules! impl_partial {
($A:ty : $B:ty as $T:ty) => (
impl PartialEq<$A> for $B {
#[inline(always)]
fn eq(&self, other: &$A) -> bool {
let left: $T = self.as_ref();
let right: $T = other.as_ref();
left == right
}
}
impl PartialOrd<$A> for $B {
#[inline(always)]
fn partial_cmp(&self, other: &$A) -> Option<Ordering> {
let left: $T = self.as_ref();
let right: $T = other.as_ref();
left.partial_cmp(right)
}
}
);
($A:ty : $B:ty) => (impl_partial!($A : $B as &str);)
}
impl_partial!(RawStr : &RawStr);
impl_partial!(&RawStr : RawStr);
impl_partial!(str : RawStr);
impl_partial!(str : &RawStr);
impl_partial!(&str : RawStr);
impl_partial!(&&str : RawStr);
impl_partial!(Cow<'_, str> : RawStr);
impl_partial!(Cow<'_, str> : &RawStr);
impl_partial!(RawStr : Cow<'_, str>);
impl_partial!(&RawStr : Cow<'_, str>);
impl_partial!(Cow<'_, RawStr> : RawStr as &RawStr);
impl_partial!(Cow<'_, RawStr> : &RawStr as &RawStr);
impl_partial!(RawStr : Cow<'_, RawStr> as &RawStr);
impl_partial!(&RawStr : Cow<'_, RawStr> as &RawStr);
impl_partial!(String : RawStr);
impl_partial!(String : &RawStr);
impl_partial!(RawStr : String);
impl_partial!(&RawStr : String);
impl_partial!(RawStr : str);
impl_partial!(RawStr : &str);
impl_partial!(RawStr : &&str);
impl_partial!(&RawStr : str);
impl AsRef<str> for RawStr {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<RawStr> for str {
#[inline(always)]
fn as_ref(&self) -> &RawStr {
RawStr::new(self)
}
}
impl AsRef<RawStr> for RawStr {
#[inline(always)]
fn as_ref(&self) -> &RawStr {
self
}
}
impl AsRef<[u8]> for RawStr {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<I: core::slice::SliceIndex<str, Output=str>> core::ops::Index<I> for RawStr {
type Output = RawStr;
#[inline]
fn index(&self, index: I) -> &Self::Output {
self.as_str()[index].into()
}
}
impl std::borrow::Borrow<str> for RawStr {
#[inline(always)]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl std::borrow::Borrow<RawStr> for &str {
#[inline(always)]
fn borrow(&self) -> &RawStr {
(*self).into()
}
}
impl fmt::Display for RawStr {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl AsRef<RawStr> for RawStrBuf {
#[inline(always)]
fn as_ref(&self) -> &RawStr {
RawStr::new(self.0.as_str())
}
}
impl Borrow<RawStr> for RawStrBuf {
#[inline(always)]
fn borrow(&self) -> &RawStr {
self.as_ref()
}
}
impl std::ops::Deref for RawStrBuf {
type Target = RawStr;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl fmt::Display for RawStrBuf {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::Debug for RawStrBuf {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl From<String> for RawStrBuf {
#[inline(always)]
fn from(string: String) -> Self {
RawStrBuf(string)
}
}
impl From<&str> for RawStrBuf {
#[inline(always)]
fn from(string: &str) -> Self {
string.to_string().into()
}
}
impl From<&RawStr> for RawStrBuf {
#[inline(always)]
fn from(raw: &RawStr) -> Self {
raw.to_string().into()
}
}
#[cfg(test)]
mod tests {
use super::RawStr;
#[test]
fn can_compare() {
let raw_str = RawStr::new("abc");
assert_eq!(raw_str, "abc");
assert_eq!("abc", raw_str.as_str());
assert_eq!(raw_str, RawStr::new("abc"));
assert_eq!(raw_str, "abc".to_string());
assert_eq!("abc".to_string(), raw_str.as_str());
}
}