roas_http_validator/
decoder.rs1use std::sync::Arc;
35
36use serde_json::Value;
37
38pub type Decoder = Arc<dyn Fn(&[u8], &str) -> Result<Value, String> + Send + Sync>;
45
46#[derive(Clone, Default)]
49pub(crate) struct Decoders {
50 entries: Vec<(String, Decoder)>,
51}
52
53impl Decoders {
54 pub(crate) fn insert(&mut self, media_type: &str, decoder: Decoder) {
56 let key = normalize(media_type);
57 match self.entries.iter_mut().find(|(known, _)| *known == key) {
58 Some(entry) => entry.1 = decoder,
59 None => self.entries.push((key, decoder)),
60 }
61 }
62
63 pub(crate) fn media_types(&self) -> impl Iterator<Item = &str> {
65 self.entries
66 .iter()
67 .map(|(media_type, _)| media_type.as_str())
68 }
69
70 pub(crate) fn find(&self, media_type: &str) -> Option<&Decoder> {
72 let media_type = normalize(media_type);
73 let range = media_type
74 .split_once('/')
75 .map(|(kind, _)| format!("{kind}/*"));
76
77 let mut best: Option<(usize, &Decoder)> = None;
78 for (key, decoder) in &self.entries {
79 let rank = if *key == media_type {
81 0
82 } else if range.as_ref() == Some(key) {
83 1
84 } else if key == "*/*" {
85 2
86 } else {
87 continue;
88 };
89 if best.is_none_or(|(known, _)| rank < known) {
90 best = Some((rank, decoder));
91 }
92 }
93 best.map(|(_, decoder)| decoder)
94 }
95}
96
97fn normalize(media_type: &str) -> String {
100 media_type
101 .split(';')
102 .next()
103 .unwrap_or(media_type)
104 .trim()
105 .to_ascii_lowercase()
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 fn decoder(tag: &'static str) -> Decoder {
113 Arc::new(move |_bytes: &[u8], _media_type: &str| Ok(Value::String(tag.to_owned())))
114 }
115
116 fn found(decoders: &Decoders, media_type: &str) -> Option<String> {
117 let decoder = decoders.find(media_type)?;
118 match decoder(b"", media_type) {
119 Ok(Value::String(tag)) => Some(tag),
120 other => panic!("unexpected {other:?}"),
121 }
122 }
123
124 #[test]
125 fn an_exact_media_type_is_found() {
126 let mut decoders = Decoders::default();
127 decoders.insert("application/xml", decoder("xml"));
128 assert_eq!(found(&decoders, "application/xml").as_deref(), Some("xml"));
129 assert_eq!(found(&decoders, "application/json"), None);
130 }
131
132 #[test]
133 fn parameters_and_case_do_not_hide_a_decoder() {
134 let mut decoders = Decoders::default();
135 decoders.insert("Application/XML; charset=utf-8", decoder("xml"));
136 assert_eq!(found(&decoders, "application/xml").as_deref(), Some("xml"));
137 }
138
139 #[test]
140 fn a_range_catches_what_no_exact_key_does() {
141 let mut decoders = Decoders::default();
142 decoders.insert("text/*", decoder("range"));
143 decoders.insert("*/*", decoder("any"));
144 assert_eq!(found(&decoders, "text/csv").as_deref(), Some("range"));
145 assert_eq!(found(&decoders, "image/png").as_deref(), Some("any"));
146 }
147
148 #[test]
149 fn the_most_specific_registration_wins() {
150 let mut decoders = Decoders::default();
151 decoders.insert("*/*", decoder("any"));
152 decoders.insert("text/*", decoder("range"));
153 decoders.insert("text/csv", decoder("exact"));
154 assert_eq!(found(&decoders, "text/csv").as_deref(), Some("exact"));
155 assert_eq!(found(&decoders, "text/plain").as_deref(), Some("range"));
156 }
157
158 #[test]
159 fn registering_the_same_media_type_twice_replaces_it() {
160 let mut decoders = Decoders::default();
161 decoders.insert("text/csv", decoder("first"));
162 decoders.insert("text/csv", decoder("second"));
163 assert_eq!(found(&decoders, "text/csv").as_deref(), Some("second"));
164 assert_eq!(decoders.media_types().count(), 1);
165 }
166
167 #[test]
168 fn an_empty_registry_finds_nothing() {
169 assert!(Decoders::default().find("text/csv").is_none());
170 }
171}