use std::sync::Arc;
use serde_json::Value;
pub type Decoder = Arc<dyn Fn(&[u8], &str) -> Result<Value, String> + Send + Sync>;
#[derive(Clone, Default)]
pub(crate) struct Decoders {
entries: Vec<(String, Decoder)>,
}
impl Decoders {
pub(crate) fn insert(&mut self, media_type: &str, decoder: Decoder) {
let key = normalize(media_type);
match self.entries.iter_mut().find(|(known, _)| *known == key) {
Some(entry) => entry.1 = decoder,
None => self.entries.push((key, decoder)),
}
}
pub(crate) fn media_types(&self) -> impl Iterator<Item = &str> {
self.entries
.iter()
.map(|(media_type, _)| media_type.as_str())
}
pub(crate) fn find(&self, media_type: &str) -> Option<&Decoder> {
let media_type = normalize(media_type);
let range = media_type
.split_once('/')
.map(|(kind, _)| format!("{kind}/*"));
let mut best: Option<(usize, &Decoder)> = None;
for (key, decoder) in &self.entries {
let rank = if *key == media_type {
0
} else if range.as_ref() == Some(key) {
1
} else if key == "*/*" {
2
} else {
continue;
};
if best.is_none_or(|(known, _)| rank < known) {
best = Some((rank, decoder));
}
}
best.map(|(_, decoder)| decoder)
}
}
fn normalize(media_type: &str) -> String {
media_type
.split(';')
.next()
.unwrap_or(media_type)
.trim()
.to_ascii_lowercase()
}
#[cfg(test)]
mod tests {
use super::*;
fn decoder(tag: &'static str) -> Decoder {
Arc::new(move |_bytes: &[u8], _media_type: &str| Ok(Value::String(tag.to_owned())))
}
fn found(decoders: &Decoders, media_type: &str) -> Option<String> {
let decoder = decoders.find(media_type)?;
match decoder(b"", media_type) {
Ok(Value::String(tag)) => Some(tag),
other => panic!("unexpected {other:?}"),
}
}
#[test]
fn an_exact_media_type_is_found() {
let mut decoders = Decoders::default();
decoders.insert("application/xml", decoder("xml"));
assert_eq!(found(&decoders, "application/xml").as_deref(), Some("xml"));
assert_eq!(found(&decoders, "application/json"), None);
}
#[test]
fn parameters_and_case_do_not_hide_a_decoder() {
let mut decoders = Decoders::default();
decoders.insert("Application/XML; charset=utf-8", decoder("xml"));
assert_eq!(found(&decoders, "application/xml").as_deref(), Some("xml"));
}
#[test]
fn a_range_catches_what_no_exact_key_does() {
let mut decoders = Decoders::default();
decoders.insert("text/*", decoder("range"));
decoders.insert("*/*", decoder("any"));
assert_eq!(found(&decoders, "text/csv").as_deref(), Some("range"));
assert_eq!(found(&decoders, "image/png").as_deref(), Some("any"));
}
#[test]
fn the_most_specific_registration_wins() {
let mut decoders = Decoders::default();
decoders.insert("*/*", decoder("any"));
decoders.insert("text/*", decoder("range"));
decoders.insert("text/csv", decoder("exact"));
assert_eq!(found(&decoders, "text/csv").as_deref(), Some("exact"));
assert_eq!(found(&decoders, "text/plain").as_deref(), Some("range"));
}
#[test]
fn registering_the_same_media_type_twice_replaces_it() {
let mut decoders = Decoders::default();
decoders.insert("text/csv", decoder("first"));
decoders.insert("text/csv", decoder("second"));
assert_eq!(found(&decoders, "text/csv").as_deref(), Some("second"));
assert_eq!(decoders.media_types().count(), 1);
}
#[test]
fn an_empty_registry_finds_nothing() {
assert!(Decoders::default().find("text/csv").is_none());
}
}