1use std::sync::Arc;
8
9use crate::{Detection, Detector, DocumentReader, FormatId, Source, confidence};
10
11#[derive(Default)]
12pub struct Registry {
13 readers: Vec<Arc<dyn DocumentReader>>,
14 detectors: Vec<Arc<dyn Detector>>,
15}
16
17impl std::fmt::Debug for Registry {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 f.debug_struct("Registry")
20 .field("formats", &self.formats())
21 .field("detectors", &self.detectors.len())
22 .finish()
23 }
24}
25
26impl Registry {
27 pub fn new() -> Self {
28 Registry::default()
29 }
30
31 pub fn register_reader(&mut self, reader: Arc<dyn DocumentReader>) -> &mut Self {
32 self.readers.push(reader);
33 self
34 }
35
36 pub fn register_detector(&mut self, detector: Arc<dyn Detector>) -> &mut Self {
37 self.detectors.push(detector);
38 self
39 }
40
41 pub fn reader_for(&self, format: FormatId) -> Option<&dyn DocumentReader> {
44 self.readers
45 .iter()
46 .rev()
47 .find(|r| r.id() == format)
48 .map(|r| r.as_ref())
49 }
50
51 pub fn formats(&self) -> Vec<FormatId> {
52 let mut v: Vec<_> = self.readers.iter().map(|r| r.id()).collect();
53 v.dedup();
54 v
55 }
56
57 pub fn detect(&self, src: &Source) -> Option<Detection> {
62 let mut best: Option<Detection> = None;
63 for d in &self.detectors {
64 if let Some(candidate) = d.sniff(src) {
65 let better = match &best {
66 None => true,
67 Some(b) => candidate.confidence >= b.confidence,
68 };
69 if better {
70 best = Some(candidate);
71 }
72 }
73 }
74 best
75 }
76
77 pub fn detect_all(&self, src: &Source) -> Vec<Detection> {
80 let mut all: Vec<_> = self.detectors.iter().filter_map(|d| d.sniff(src)).collect();
81 all.sort_by_key(|d| std::cmp::Reverse(d.confidence));
82 all
83 }
84
85 pub fn detect_or_fallback(&self, src: &Source) -> Detection {
89 if let Some(d) = self.detect(src) {
90 return d;
91 }
92 if src.looks_binary() {
93 Detection::new(
94 FormatId::Binary,
95 confidence::FALLBACK,
96 "no matches; there is a NUL byte in the prefix",
97 )
98 } else {
99 Detection::new(
100 FormatId::PlainText,
101 confidence::FALLBACK,
102 "no matches; the prefix is text",
103 )
104 }
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::{Events, ReadContext, Result};
112
113 struct Fake(FormatId);
114
115 impl DocumentReader for Fake {
116 fn id(&self) -> FormatId {
117 self.0
118 }
119 fn read<'a>(&self, _src: &'a Source, _ctx: &ReadContext) -> Result<Events<'a>> {
120 Ok(Box::new(std::iter::empty()))
121 }
122 }
123
124 struct Sniffer(FormatId, u8);
125
126 impl Detector for Sniffer {
127 fn sniff(&self, _src: &Source) -> Option<Detection> {
128 Some(Detection::new(self.0, self.1, "test"))
129 }
130 }
131
132 #[test]
133 fn the_last_registered_reader_wins() {
134 let mut r = Registry::new();
135 r.register_reader(Arc::new(Fake(FormatId::Markdown)));
136 r.register_reader(Arc::new(Fake(FormatId::Markdown)));
137 assert!(r.reader_for(FormatId::Markdown).is_some());
140 assert!(r.reader_for(FormatId::Pdf).is_none());
141 }
142
143 #[test]
144 fn detect_picks_the_highest_confidence() {
145 let mut r = Registry::new();
146 r.register_detector(Arc::new(Sniffer(FormatId::PlainText, 30)));
147 r.register_detector(Arc::new(Sniffer(FormatId::Markdown, 70)));
148 r.register_detector(Arc::new(Sniffer(FormatId::Csv, 50)));
149 assert_eq!(
150 r.detect(&Source::from_bytes("t", "x")).unwrap().format,
151 FormatId::Markdown
152 );
153 }
154
155 #[test]
156 fn detect_all_sorts_descending() {
157 let mut r = Registry::new();
158 r.register_detector(Arc::new(Sniffer(FormatId::PlainText, 30)));
159 r.register_detector(Arc::new(Sniffer(FormatId::Markdown, 70)));
160 let all = r.detect_all(&Source::from_bytes("t", "x"));
161 assert_eq!(all.len(), 2);
162 assert!(all[0].confidence >= all[1].confidence);
163 }
164
165 #[test]
166 fn fallback_distinguishes_text_from_binary() {
167 let r = Registry::new();
168 assert_eq!(
169 r.detect_or_fallback(&Source::from_bytes("t", "hello"))
170 .format,
171 FormatId::PlainText
172 );
173 assert_eq!(
174 r.detect_or_fallback(&Source::from_bytes("t", vec![0, 1, 2]))
175 .format,
176 FormatId::Binary
177 );
178 }
179}