use std::{io, str::FromStr}; #[derive(Debug)] pub enum ContentType { Text(TextType), Aplication(ApplicationType), Image(Image), Any, } impl ContentType { fn to_str(&self) -> Box { 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, Box), } impl Parameter { fn to_str(&self) -> Box { 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>, } impl Content { pub fn to_str(&self) -> Box { match &self.parameter { Some(p) => format!( "{}; {}", self.content_type.to_str(), p.iter() .map(|par| par.to_str()) .collect::>() .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) -> 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 { 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 { match s.split(';').collect::>().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::, _>>()?, ), }), _ => Err(invalid_data_error("Invalid content-type")), } } } impl FromStr for Parameter { type Err = io::Error; fn from_str(s: &str) -> Result { match s.split('=').collect::>().as_slice() { ["q", value] => { let pref_val = match value.parse::() { 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 { 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", } } }