308 lines
8.3 KiB
Rust
308 lines
8.3 KiB
Rust
use std::{io, str::FromStr};
|
|
|
|
#[derive(Debug)]
|
|
pub enum ContentType {
|
|
Text(TextType),
|
|
Aplication(ApplicationType),
|
|
Image(Image),
|
|
Any,
|
|
}
|
|
|
|
impl ContentType {
|
|
fn to_str(&self) -> Box<str> {
|
|
match self {
|
|
ContentType::Text(text) => format!("text/{}", text.to_str()).into_boxed_str(),
|
|
ContentType::Aplication(app) => {
|
|
format!("application/{}", app.to_str()).into_boxed_str()
|
|
}
|
|
ContentType::Image(img) => format!("image/{}", img.to_str()).into_boxed_str(),
|
|
ContentType::Any => "*/*".to_string().into_boxed_str(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Parameter {
|
|
Preference(f32),
|
|
Charset(Charset),
|
|
Other(Box<str>, Box<str>),
|
|
}
|
|
|
|
impl Parameter {
|
|
fn to_str(&self) -> Box<str> {
|
|
match &self {
|
|
Parameter::Preference(val) => format!("q={val}").into_boxed_str(),
|
|
Parameter::Charset(ch) => format!("charset={}", ch.to_str()).into_boxed_str(),
|
|
Parameter::Other(p, v) => format!("{p}={v}").into_boxed_str(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Charset {
|
|
UTF8,
|
|
}
|
|
|
|
impl Charset {
|
|
fn to_str(&self) -> &'static str {
|
|
match self {
|
|
Charset::UTF8 => "utf-8",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Content {
|
|
pub content_type: ContentType,
|
|
pub parameter: Option<Vec<Parameter>>,
|
|
}
|
|
|
|
impl Content {
|
|
pub fn to_str(&self) -> Box<str> {
|
|
match &self.parameter {
|
|
Some(p) => format!(
|
|
"{}; {}",
|
|
self.content_type.to_str(),
|
|
p.iter()
|
|
.map(|par| par.to_str())
|
|
.collect::<Vec<_>>()
|
|
.join("; ")
|
|
)
|
|
.into_boxed_str(),
|
|
None => self.content_type.to_str(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Content {
|
|
pub fn new(content_type: ContentType) -> Self {
|
|
Self {
|
|
content_type,
|
|
parameter: None,
|
|
}
|
|
}
|
|
|
|
pub fn with_params(content_type: ContentType, params: Vec<Parameter>) -> Self {
|
|
Self {
|
|
content_type,
|
|
parameter: Some(params),
|
|
}
|
|
}
|
|
|
|
pub fn add_parameter(&mut self, param: Parameter) {
|
|
match &mut self.parameter {
|
|
Some(params) => params.push(param),
|
|
None => self.parameter = Some(vec![param]),
|
|
}
|
|
}
|
|
|
|
pub fn quality(&self) -> Option<f32> {
|
|
self.parameter.as_ref()?.iter().find_map(|p| {
|
|
if let Parameter::Preference(q) = p {
|
|
Some(*q)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn charset(&self) -> Option<&Charset> {
|
|
self.parameter.as_ref()?.iter().find_map(|p| {
|
|
if let Parameter::Charset(cs) = p {
|
|
Some(cs)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn matches(&self, other: &ContentType) -> bool {
|
|
type C = ContentType;
|
|
match (&self.content_type, other) {
|
|
(C::Any, _) | (_, C::Any) => true,
|
|
(C::Text(TextType::Any), C::Text(_)) => true,
|
|
(C::Text(_), C::Text(TextType::Any)) => true,
|
|
(C::Aplication(ApplicationType::Any), C::Aplication(_)) => true,
|
|
(C::Aplication(_), C::Aplication(ApplicationType::Any)) => true,
|
|
(C::Image(Image::Any), C::Image(_)) => true,
|
|
(C::Image(_), C::Image(Image::Any)) => true,
|
|
(a, b) => std::mem::discriminant(a) == std::mem::discriminant(b),
|
|
}
|
|
}
|
|
|
|
pub fn is_text(&self) -> bool {
|
|
matches!(self.content_type, ContentType::Text(_))
|
|
}
|
|
|
|
pub fn is_application(&self) -> bool {
|
|
matches!(self.content_type, ContentType::Aplication(_))
|
|
}
|
|
|
|
pub fn is_image(&self) -> bool {
|
|
matches!(self.content_type, ContentType::Image(_))
|
|
}
|
|
|
|
pub fn html_utf8() -> Self {
|
|
Self::with_params(
|
|
ContentType::Text(TextType::Html),
|
|
vec![Parameter::Charset(Charset::UTF8)],
|
|
)
|
|
}
|
|
|
|
pub fn json_utf8() -> Self {
|
|
Self::with_params(
|
|
ContentType::Aplication(ApplicationType::Json),
|
|
vec![Parameter::Charset(Charset::UTF8)],
|
|
)
|
|
}
|
|
}
|
|
|
|
pub fn invalid_data_error(error: &str) -> io::Error {
|
|
io::Error::new(io::ErrorKind::InvalidData, error)
|
|
}
|
|
|
|
impl FromStr for Content {
|
|
type Err = io::Error;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s.split(';').collect::<Vec<&str>>().as_slice() {
|
|
[val] => Ok(Self {
|
|
content_type: ContentType::from_str(val)?,
|
|
parameter: None,
|
|
}),
|
|
|
|
[val, par @ ..] => Ok(Self {
|
|
content_type: ContentType::from_str(val)?,
|
|
parameter: Some(
|
|
par.iter()
|
|
.map(|p| Parameter::from_str(p))
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
),
|
|
}),
|
|
_ => Err(invalid_data_error("Invalid content-type")),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromStr for Parameter {
|
|
type Err = io::Error;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s.split('=').collect::<Vec<&str>>().as_slice() {
|
|
["q", value] => {
|
|
let pref_val = match value.parse::<f32>() {
|
|
Ok(v) => Ok(v),
|
|
Err(_) => Err(invalid_data_error("Invalid preference")),
|
|
}?;
|
|
|
|
Ok(Parameter::Preference(pref_val))
|
|
}
|
|
|
|
["charset", "utf-8"] => Ok(Parameter::Charset(Charset::UTF8)),
|
|
|
|
[t, v] => Ok(Parameter::Other(
|
|
t.to_string().into_boxed_str(),
|
|
v.to_string().into_boxed_str(),
|
|
)),
|
|
|
|
_ => Err(io::Error::new(
|
|
io::ErrorKind::InvalidData,
|
|
"Invalid parameter",
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromStr for ContentType {
|
|
type Err = io::Error;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let parts: Vec<&str> = s.split("/").collect();
|
|
|
|
match parts.as_slice() {
|
|
["*", "*"] => Ok(ContentType::Any),
|
|
["text", "*"] => Ok(ContentType::Text(TextType::Any)),
|
|
["text", "html"] => Ok(ContentType::Text(TextType::Html)),
|
|
["text", "css"] => Ok(ContentType::Text(TextType::Css)),
|
|
["text", "javascript"] => Ok(ContentType::Text(TextType::Javascript)),
|
|
|
|
["application", "json"] => Ok(ContentType::Aplication(ApplicationType::Json)),
|
|
["application", "xhtml+xml"] => Ok(ContentType::Aplication(ApplicationType::XhtmlXml)),
|
|
["application", "xml"] => Ok(ContentType::Aplication(ApplicationType::Xml)),
|
|
["application", "*"] => Ok(ContentType::Aplication(ApplicationType::Any)),
|
|
|
|
["image", "png"] => Ok(ContentType::Image(Image::Png)),
|
|
["image", "jpeg"] | ["image", "jpg"] => Ok(ContentType::Image(Image::Jpeg)),
|
|
["image", "avif"] => Ok(ContentType::Image(Image::Avif)),
|
|
["image", "webp"] => Ok(ContentType::Image(Image::Webp)),
|
|
["image", "svg"] | ["image", "svg+xml"] => Ok(ContentType::Image(Image::Svg)),
|
|
["image", "*"] => Ok(ContentType::Image(Image::Any)),
|
|
|
|
_ => {
|
|
println!("{parts:?}");
|
|
Err(invalid_data_error("Invalid content-type-type"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Image {
|
|
Png,
|
|
Avif,
|
|
Jpeg,
|
|
Webp,
|
|
Svg,
|
|
Any,
|
|
}
|
|
|
|
impl Image {
|
|
fn to_str(&self) -> &'static str {
|
|
match self {
|
|
Image::Png => "png",
|
|
Image::Avif => "avif",
|
|
Image::Jpeg => "jpeg",
|
|
Image::Webp => "webp",
|
|
Image::Svg => "svg",
|
|
Image::Any => "*",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum TextType {
|
|
Html,
|
|
Css,
|
|
Javascript,
|
|
Any,
|
|
}
|
|
|
|
impl TextType {
|
|
fn to_str(&self) -> &'static str {
|
|
match self {
|
|
TextType::Html => "html",
|
|
TextType::Css => "css",
|
|
TextType::Javascript => "javascript",
|
|
TextType::Any => "*",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum ApplicationType {
|
|
Json,
|
|
Any,
|
|
XhtmlXml,
|
|
Xml,
|
|
}
|
|
|
|
impl ApplicationType {
|
|
fn to_str(&self) -> &'static str {
|
|
match self {
|
|
ApplicationType::Json => "json",
|
|
ApplicationType::Any => "*",
|
|
ApplicationType::XhtmlXml => "xhtml+xml",
|
|
ApplicationType::Xml => "xml",
|
|
}
|
|
}
|
|
}
|