use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MimeType(pub mime::Mime);
impl std::hash::Hash for MimeType {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.to_string().hash(state);
}
}
impl MimeType {
pub fn type_(&self) -> &str {
self.0.type_().as_str()
}
pub fn subtype(&self) -> &str {
self.0.subtype().as_str()
}
pub fn matches(&self, pattern: &str) -> bool {
let pattern = pattern.trim().to_lowercase();
if pattern == "*/*" || pattern == "*" {
return true;
}
if self.type_() == "*" {
return true;
}
if let Some(slash_pos) = pattern.find('/') {
let pattern_type = &pattern[..slash_pos];
let pattern_subtype = &pattern[slash_pos + 1..];
if pattern_subtype == "*" {
return self.type_().to_lowercase() == pattern_type;
}
return self.type_().to_lowercase() == pattern_type
&& self.subtype().to_lowercase() == pattern_subtype;
}
self.type_().to_lowercase() == pattern
}
pub fn matches_self(&self, other: &MimeType) -> bool {
self.0.type_() == other.0.type_() && self.0.subtype() == other.0.subtype()
}
}
impl std::fmt::Display for MimeType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}/{}", self.type_(), self.subtype())
}
}
impl FromStr for MimeType {
type Err = crate::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
if s == "*" || s == "*/*" {
return Ok(MimeType(mime::Mime::from_str("*/*").unwrap()));
}
if !s.contains('/') {
let type_only = format!("{}/*", s);
return mime::Mime::from_str(&type_only)
.map(MimeType)
.map_err(|_| crate::Error::InvalidMimeType(s.to_string()));
}
mime::Mime::from_str(s)
.map(MimeType)
.map_err(|_| crate::Error::InvalidMimeType(s.to_string()))
}
}
impl serde::Serialize for MimeType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.to_string().serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for MimeType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
String::deserialize(deserializer).and_then(|s| s.parse().map_err(serde::de::Error::custom))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mime_type_parsing() {
let mt: MimeType = "image/jpeg".parse().unwrap();
assert_eq!(mt.type_(), "image");
assert_eq!(mt.subtype(), "jpeg");
}
#[test]
fn mime_type_type_only() {
let mt: MimeType = "image".parse().unwrap();
assert_eq!(mt.type_(), "image");
}
#[test]
fn mime_type_wildcard_global() {
let mt: MimeType = "*/*".parse().unwrap();
assert!(mt.matches("image/jpeg"));
assert!(mt.matches("*/*"));
}
#[test]
fn mime_type_matches_exact() {
let mt: MimeType = "image/jpeg".parse().unwrap();
assert!(mt.matches("image/jpeg"));
assert!(!mt.matches("image/png"));
assert!(!mt.matches("video/mp4"));
}
#[test]
fn mime_type_matches_type_wildcard() {
let jpeg: MimeType = "image/jpeg".parse().unwrap();
let png: MimeType = "image/png".parse().unwrap();
assert!(jpeg.matches("image"));
assert!(png.matches("image"));
assert!(!jpeg.matches("video"));
assert!(jpeg.matches("image/*"));
assert!(png.matches("image/*"));
}
#[test]
fn mime_type_matches_global_wildcard() {
let mt: MimeType = "image/jpeg".parse().unwrap();
assert!(mt.matches("*/*"));
assert!(mt.matches("*"));
}
#[test]
fn mime_type_invalid() {
assert!("".parse::<MimeType>().is_err());
}
#[test]
fn mime_type_serialization() {
let mt: MimeType = "image/jpeg".parse().unwrap();
assert_eq!(serde_json::to_string(&mt).unwrap(), "\"image/jpeg\"");
}
#[test]
fn mime_type_deserialization() {
let mt: MimeType = serde_json::from_str("\"image/jpeg\"").unwrap();
assert_eq!(mt.type_(), "image");
assert_eq!(mt.subtype(), "jpeg");
}
}