use core::ops::{Deref, Range, RangeFrom};
use crate::MediaType;
use super::{Error, Uncased};
const fn is_rnc(c: u8) -> bool {
match c {
b'!' | b'#' | b'$' | b'&' | b'-' | b'^' | b'_' | b'.' | b'+' => true,
_ => c.is_ascii_alphanumeric(),
}
}
#[derive(Clone, Debug)]
pub struct Essence<T> {
pub(crate) string: T,
pub(crate) genus: Range<usize>,
pub(crate) species: Range<usize>,
pub(crate) tree: Option<Range<usize>>,
pub(crate) suffix: Option<Range<usize>>,
}
impl<'a> Essence<&'a str> {
const MAX: usize = 127;
#[allow(clippy::if_same_then_else)]
pub(crate) const fn parse(string: &'a str) -> Result<(Self, RangeFrom<usize>), Error> {
let mut genus: Option<Range<usize>> = None;
let mut species: Option<Range<usize>> = None;
let mut tree: Option<Range<usize>> = None;
let mut suffix: Option<Range<usize>> = None;
let mut i = 0;
while i < string.len() {
let c = string.as_bytes()[i];
if let Some(genus) = genus.as_ref() {
if let Some(species) = species.as_ref() {
suffix = match suffix {
Some(x) => Some(x.start..species.end),
None => None,
};
if c == b';' {
break;
} else if !c.is_ascii_whitespace() {
return Err(Error(i));
}
} else if i - genus.end > Self::MAX {
return Err(Error(i));
} else if i == genus.end + 1 && !c.is_ascii_alphanumeric() {
return Err(Error(i));
} else if c.is_ascii_whitespace() {
species = Some(genus.end + 1..i);
} else if c == b';' {
species = Some(genus.end + 1..i);
continue; } else if !is_rnc(c) {
return Err(Error(i));
} else if c == b'.' && tree.is_none() {
tree = Some(genus.end + 1..i);
} else if i == string.len() - 1 {
species = Some(genus.end + 1..i + 1);
if let Some(prev) = suffix {
suffix = Some(prev.start..i + 1);
}
} else if c == b'+' {
suffix = Some(i + 1..i + 1);
}
} else if c == b'/' {
genus = Some(0..i);
} else if i == 0 && !c.is_ascii_alphanumeric() {
return Err(Error(i));
} else if i >= Self::MAX {
return Err(Error(i));
} else if !is_rnc(c) {
return Err(Error(i));
}
i += 1;
}
if let Some(genus) = genus {
if let Some(species) = species {
let essence = Self {
string,
genus,
species,
tree,
suffix,
};
return Ok((essence, i..));
}
return Err(Error(i));
}
Err(Error(i))
}
pub const fn new_const(s: &'a str) -> Result<Self, Error> {
match Self::parse(s) {
Ok((essence, end)) => {
if essence.species.end != end.start {
Err(Error(essence.species.end))
} else if end.start != s.len() {
Err(Error(end.start))
} else {
Ok(essence)
}
}
Err(e) => Err(e),
}
}
}
impl<T: AsRef<str>> Essence<T> {
pub fn new(string: T) -> Result<Self, Error> {
let Essence {
string: _,
genus,
species,
tree,
suffix,
} = Essence::new_const(string.as_ref())?;
Ok(Self {
string,
genus,
species,
tree,
suffix,
})
}
fn name(&self, range: Range<usize>) -> Uncased<&str> {
Uncased(&self.string.as_ref()[range])
}
fn body(&self) -> Uncased<&str> {
self.name(self.genus.start..self.species.end)
}
pub fn genus(&self) -> Uncased<&str> {
self.name(self.genus.clone())
}
pub fn species(&self) -> Uncased<&str> {
self.name(self.species.clone())
}
pub fn tree(&self) -> Option<Uncased<&str>> {
self.tree.clone().map(|r| self.name(r))
}
pub fn suffix(&self) -> Option<Uncased<&str>> {
self.suffix.clone().map(|r| self.name(r))
}
}
impl<T> Deref for Essence<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.string
}
}
impl<T: AsRef<U>, U: ?Sized> AsRef<U> for Essence<T> {
fn as_ref(&self) -> &U {
self.string.as_ref()
}
}
impl<T: AsRef<str>> Eq for Essence<T> {}
impl<T: AsRef<str>, U: AsRef<str>> PartialEq<Essence<U>> for Essence<T> {
fn eq(&self, other: &Essence<U>) -> bool {
self.body() == other.body()
}
}
impl<T: AsRef<str>, U: AsRef<str>> PartialEq<MediaType<U>> for Essence<T> {
fn eq(&self, other: &MediaType<U>) -> bool {
self == other.essence()
}
}
impl<T: AsRef<str>> PartialEq<str> for Essence<T> {
fn eq(&self, other: &str) -> bool {
self.body() == other
}
}
impl<T: AsRef<str>> PartialEq<&str> for Essence<T> {
fn eq(&self, other: &&str) -> bool {
self.body() == other
}
}
impl<T: AsRef<str>> PartialEq<Essence<T>> for str {
fn eq(&self, other: &Essence<T>) -> bool {
self == other.body()
}
}
impl<T: AsRef<str>> PartialEq<Essence<T>> for &str {
fn eq(&self, other: &Essence<T>) -> bool {
*self == *other.body()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<T: AsRef<str>> PartialEq<Essence<T>> for alloc::string::String {
fn eq(&self, other: &Essence<T>) -> bool {
*self == *other.body()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl core::str::FromStr for Essence<alloc::string::String> {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s.into())
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<T: serde::Serialize> serde::Serialize for Essence<T> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.string.serialize(serializer)
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'de, T: AsRef<str> + serde::Deserialize<'de>> serde::Deserialize<'de> for Essence<T> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::new(T::deserialize(deserializer)?)
.map_err(|_| <D::Error as serde::de::Error>::custom("invalid media type"))
}
}
#[cfg(test)]
mod test {
use super::{Essence, Uncased};
#[rstest::rstest]
#[case("text/plainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainpl")]
#[case("texttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttex/plain")]
#[case("APPLICATION/VND.JOSE+JSON")]
#[case("APPLICATION/JOSE+JSON")]
#[case("APPLICATION/VND.JOSE")]
#[case("APPLICATION/JOSE")]
#[case("AUDIO/AMR-WB+")]
fn parse(#[case] mime: &str) {
let lower = mime.to_ascii_lowercase();
let (genus, species) = lower.split_once('/').unwrap();
let tree = species.split_once('.').map(|x| Uncased(x.0));
let mut suffix = species.rsplit_once('+').map(|x| Uncased(x.1));
if let Some(Uncased("")) = suffix {
suffix = None;
}
let e = Essence::new(mime).unwrap();
assert_eq!(e, lower.as_ref());
assert_eq!(e.genus(), genus);
assert_eq!(e.species(), species);
assert_eq!(e.tree(), tree);
assert_eq!(e.suffix(), suffix);
}
#[rstest::rstest]
#[case("", 0)]
#[case("textplain", 9)]
#[case("text//plain", 5)]
#[case(" text/plain", 0)]
#[case("text /plain", 4)]
#[case("text/ plain", 5)]
#[case("t٤xt/plain", 1)]
#[case("text/p١ain", 6)]
#[case("text/plain ", 10)]
#[case("text/plain;", 10)]
#[case("text/plain; charset=UTF-8", 10)]
#[case("texttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttext/plain", 127)]
#[case("text/plainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainplainpla", 132)]
fn parse_fail(#[case] string: &str, #[case] byte: usize) {
assert_eq!(byte, Essence::new(string).unwrap_err().0);
}
}