use glob::{Pattern, PatternError};
use indexmap::{IndexMap, indexmap};
use mime::Mime;
use serde::{Deserialize, Serialize};
use slumber_util::yaml::{
self, DeserializeYaml, Expected, LocatedError, SourceMap, SourcedYaml,
};
use std::str::FromStr;
#[derive(Debug, Serialize)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(transparent)]
pub struct MimeMap<V> {
patterns: IndexMap<MimePattern, V>,
}
impl<V> MimeMap<V> {
pub fn get(&self, mime: &Mime) -> Option<&V> {
self.patterns
.iter()
.find(|(pattern, _)| pattern.matches(mime.essence_str()))
.map(|(_, value)| value)
}
}
impl<V> Default for MimeMap<V> {
fn default() -> Self {
Self {
patterns: Default::default(),
}
}
}
impl DeserializeYaml for MimeMap<String> {
fn expected() -> Expected {
Expected::OneOf(&[&Expected::String, &Expected::Mapping])
}
fn deserialize(
yaml: SourcedYaml,
source_map: &SourceMap,
) -> yaml::Result<Self> {
let patterns: IndexMap<MimePattern, String> = if yaml.data.is_mapping()
{
DeserializeYaml::deserialize(yaml, source_map)?
} else {
let s = yaml.try_into_string()?;
indexmap! { MimePattern::default() => s }
};
Ok(Self { patterns })
}
}
#[derive(
Clone,
Debug,
derive_more::Display,
derive_more::Deref,
Serialize,
Deserialize,
Eq,
Hash,
PartialEq,
)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(try_from = "String", into = "String")]
struct MimePattern(Pattern);
impl Default for MimePattern {
fn default() -> Self {
Self(Pattern::from_str("*/*").unwrap())
}
}
impl From<MimePattern> for String {
fn from(value: MimePattern) -> Self {
value.to_string()
}
}
impl FromStr for MimePattern {
type Err = PatternError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let dealiased = match s {
"default" => "*/*",
"image" => "image/*",
"json" => "application/*json",
other => other,
};
Ok(Self(dealiased.parse()?))
}
}
impl TryFrom<String> for MimePattern {
type Error = PatternError;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.parse()
}
}
impl DeserializeYaml for MimePattern {
fn expected() -> Expected {
Expected::String
}
fn deserialize(
yaml: SourcedYaml,
_source_map: &SourceMap,
) -> yaml::Result<Self> {
let location = yaml.location;
let s = yaml.try_into_string()?;
s.parse()
.map_err(|error| LocatedError::other(error, location))
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
use serde_yaml::Mapping;
use slumber_util::yaml::deserialize_yaml;
fn map(entries: &[(&str, &str)]) -> MimeMap<String> {
MimeMap {
patterns: entries
.iter()
.map(|(k, v)| {
(k.parse::<MimePattern>().unwrap(), (*v).to_owned())
})
.collect(),
}
}
#[rstest]
#[case::string("test", map(&[("*/*", "test")]))]
#[case::empty_map(serde_yaml::Value::Mapping(Mapping::default()), map(&[]))]
#[case::aliases(serde_yaml::Value::Mapping([
("json".into(), "json-value".into()),
("image".into(), "image-value".into()),
("default".into(), "default-value".into()),
].into_iter().collect()),
map(&[
("application/*json", "json-value"),
("image/*","image-value"),
("*/*", "default-value"),
]),
)]
fn test_deserialize(
#[case] yaml: impl Into<serde_yaml::Value>,
#[case] expected: MimeMap<String>,
) {
assert_eq!(
deserialize_yaml::<MimeMap<String>>(yaml.into()).unwrap(),
expected
);
}
#[rstest]
#[case::wildcard("text/plain", "text")]
#[case::default("image/png", "default")]
#[case::priority("audio/ogg", "default")]
#[case::json_plain("application/json", "json")]
#[case::json_extension("application/ld+json", "json")]
#[case::essence("text/csv; charset=utf-8", "csv")]
fn test_match_mimes(#[case] mime: Mime, #[case] expected: String) {
let map = map(&[
("text/csv", "csv"),
("text/*", "text"),
("json", "json"),
("*/*", "default"),
("audio/*", "audio"),
]);
let actual = map.get(&mime).unwrap();
assert_eq!(actual, &expected);
}
}