use super::{error::*, media_type::*, name::*, params::*, parse::*, value::*};
use std::{
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
str::FromStr,
};
#[derive(Debug, Clone)]
pub struct MediaTypeBuf {
data: Box<str>,
indices: Indices,
}
impl MediaTypeBuf {
pub fn new(ty: Name, subty: Name) -> Self {
Self::from_string(format!("{}/{}", ty, subty)).unwrap()
}
pub fn from_parts(
ty: Name,
subty: Name,
suffix: Option<Name>,
params: &[(Name, Value)],
) -> Result<Self, MediaTypeError> {
use std::fmt::Write;
let mut s = String::new();
write!(s, "{}/{}", ty, subty).unwrap();
if let Some(suffix) = suffix {
write!(s, "+{}", suffix).unwrap();
}
for (key, value) in params {
write!(s, "; {}={}", key, value).unwrap();
}
Self::from_string(s)
}
pub fn from_string(mut s: String) -> Result<Self, MediaTypeError> {
let (indices, len) = Indices::parse(&s)?;
s.truncate(len);
Ok(Self {
data: s.into(),
indices,
})
}
pub fn ty(&self) -> Name {
Name::new_unchecked(&self.data[self.indices.ty()])
}
pub fn subty(&self) -> Name {
Name::new_unchecked(&self.data[self.indices.subty()])
}
pub fn suffix(&self) -> Option<Name> {
self.indices
.suffix()
.map(|range| Name::new_unchecked(&self.data[range]))
}
pub fn essence(&self) -> &str {
self.data.split(';').next().unwrap()
}
pub fn as_str(&self) -> &str {
&self.data
}
pub fn canonicalize(&self) -> Self {
use std::fmt::Write;
let mut s = String::with_capacity(self.data.len());
write!(
s,
"{}/{}",
self.ty().as_str().to_ascii_lowercase(),
self.subty().as_str().to_ascii_lowercase()
)
.unwrap();
if let Some(suffix) = self.suffix() {
write!(s, "+{}", suffix.as_str().to_ascii_lowercase()).unwrap();
}
for (key, value) in self.params() {
write!(s, "; {}={}", key.as_str().to_ascii_lowercase(), value).unwrap();
}
s.shrink_to_fit();
Self::from_string(s).unwrap()
}
pub fn to_ref(&self) -> MediaType {
MediaType::parse(self.as_str()).unwrap()
}
}
impl ReadParams for MediaTypeBuf {
fn params(&self) -> Params {
Params::from_indices(&self.data, &self.indices)
}
fn get_param(&self, key: Name) -> Option<Value> {
let params = self.indices.params();
params
.binary_search_by_key(&key, |&[start, end, _, _]| {
Name::new_unchecked(&self.data[start as usize..end as usize])
})
.ok()
.map(|index| {
Value::new_unchecked(
&self.data[params[index][2] as usize..params[index][3] as usize],
)
})
}
}
impl FromStr for MediaTypeBuf {
type Err = MediaTypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (indices, len) = Indices::parse(s)?;
Ok(Self {
data: s[..len].into(),
indices,
})
}
}
impl From<MediaType<'_>> for MediaTypeBuf {
fn from(t: MediaType) -> Self {
Self::from_string(t.to_string()).unwrap()
}
}
impl From<&MediaType<'_>> for MediaTypeBuf {
fn from(t: &MediaType) -> Self {
Self::from_string(t.to_string()).unwrap()
}
}
impl AsRef<str> for MediaTypeBuf {
fn as_ref(&self) -> &str {
&self.data
}
}
impl PartialEq for MediaTypeBuf {
fn eq(&self, other: &Self) -> bool {
self.ty() == other.ty()
&& self.subty() == other.subty()
&& self.suffix() == other.suffix()
&& self.params().eq(other.params())
}
}
impl Eq for MediaTypeBuf {}
impl PartialOrd for MediaTypeBuf {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MediaTypeBuf {
fn cmp(&self, other: &Self) -> Ordering {
match self.ty().cmp(&other.ty()) {
Ordering::Equal => (),
ne => return ne,
}
match self.subty().cmp(&other.subty()) {
Ordering::Equal => (),
ne => return ne,
}
match self.suffix().cmp(&other.suffix()) {
Ordering::Equal => (),
ne => return ne,
}
self.params().cmp(other.params())
}
}
impl Hash for MediaTypeBuf {
fn hash<H: Hasher>(&self, state: &mut H) {
self.ty().hash(state);
self.subty().hash(state);
self.suffix().hash(state);
for param in self.params() {
param.hash(state);
}
}
}
impl PartialEq<MediaType<'_>> for MediaTypeBuf {
fn eq(&self, other: &MediaType) -> bool {
self.ty() == other.ty
&& self.subty() == other.subty
&& self.suffix() == other.suffix
&& self.params().eq(other.params())
}
}
impl PartialOrd<MediaType<'_>> for MediaTypeBuf {
fn partial_cmp(&self, other: &MediaType) -> Option<Ordering> {
match self.ty().partial_cmp(&other.ty) {
Some(Ordering::Equal) => (),
ne => return ne,
}
match self.subty().partial_cmp(&other.subty) {
Some(Ordering::Equal) => (),
ne => return ne,
}
match self.suffix().partial_cmp(&other.suffix) {
Some(Ordering::Equal) => (),
ne => return ne,
}
self.params().partial_cmp(other.params())
}
}
impl fmt::Display for MediaTypeBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.ty(), self.subty())?;
if let Some(suffix) = self.suffix() {
write!(f, "+{}", suffix)?;
}
for (key, value) in self.params() {
write!(f, "; {}={}", key, value)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{names::*, values::*};
#[test]
fn from_parts() {
assert_eq!(
MediaTypeBuf::from_parts(IMAGE, SVG, Some(XML), &[(CHARSET, UTF_8)])
.unwrap()
.to_string(),
"image/svg+xml; charset=UTF-8"
);
}
#[test]
fn get_param() {
assert_eq!(
MediaTypeBuf::from_str("image/svg+xml")
.unwrap()
.get_param(CHARSET),
None
);
assert_eq!(
MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8")
.unwrap()
.get_param(CHARSET),
Some(UTF_8)
);
assert_eq!(
MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8; HELLO=WORLD")
.unwrap()
.get_param(Name::new("hello").unwrap()),
Some(Value::new("WORLD").unwrap())
);
}
#[test]
fn essence() {
assert_eq!(
MediaTypeBuf::from_str("image/svg+xml").unwrap().essence(),
"image/svg+xml"
);
assert_eq!(
MediaTypeBuf::from_str("image/svg+xml; ")
.unwrap()
.essence(),
"image/svg+xml"
);
assert_eq!(
MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8")
.unwrap()
.essence(),
"image/svg+xml"
);
}
#[test]
fn canonicalize() {
assert_eq!(
MediaTypeBuf::from_str("IMAGE/SVG+XML; CHARSET=UTF-8; ")
.unwrap()
.canonicalize()
.to_string(),
"image/svg+xml; charset=UTF-8"
);
}
#[test]
fn cmp() {
assert_eq!(
MediaTypeBuf::from_str("text/plain").unwrap(),
MediaTypeBuf::from_str("TEXT/PLAIN").unwrap()
);
assert_eq!(
MediaTypeBuf::from_str("image/svg+xml; charset=UTF-8").unwrap(),
MediaTypeBuf::from_str("IMAGE/SVG+XML; CHARSET=UTF-8").unwrap()
);
assert_eq!(
MediaTypeBuf::from_str("image/svg+xml; hello=WORLD; charset=UTF-8").unwrap(),
MediaTypeBuf::from_str("IMAGE/SVG+XML; CHARSET=UTF-8; HELLO=WORLD").unwrap()
);
}
}