docparser_core/
registry.rs1use std::collections::HashMap;
4use std::fmt;
5use std::sync::Arc;
6
7use crate::plugin::{FormatPlugin, ParseCapability, ProcessCapability};
8use crate::policy::BackendPolicy;
9
10#[derive(Debug)]
12pub struct RegistryError(pub String);
13
14impl fmt::Display for RegistryError {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 write!(f, "registry: {}", self.0)
17 }
18}
19
20impl std::error::Error for RegistryError {}
21
22#[derive(Default)]
23pub struct Registry {
24 parse_caps: HashMap<String, Vec<Arc<dyn ParseCapability>>>,
25 process_caps: HashMap<String, Arc<dyn ProcessCapability>>,
26 plugins: Vec<Arc<dyn FormatPlugin>>,
27 delegates: HashMap<String, String>, }
29
30impl Registry {
31 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn register_parse(
37 &mut self,
38 cap: Arc<dyn ParseCapability>,
39 delegate_to: Option<&str>,
40 ) -> Result<(), RegistryError> {
41 if let Some(dst) = delegate_to {
42 self.check_delegate_cycle(cap.id(), dst)?;
43 self.delegates.insert(cap.id().to_string(), dst.to_string());
44 }
45 for f in cap.formats() {
46 self.parse_caps
47 .entry((*f).to_string())
48 .or_default()
49 .push(cap.clone());
50 }
51 Ok(())
52 }
53
54 pub fn register_process(&mut self, cap: Arc<dyn ProcessCapability>) {
55 self.process_caps.insert(cap.id().to_string(), cap);
56 }
57
58 pub fn register_plugin(&mut self, plugin: Arc<dyn FormatPlugin>) {
59 self.plugins.push(plugin);
60 }
61
62 pub fn plugin_for(&self, fmt: &str) -> Option<Arc<dyn FormatPlugin>> {
64 self.plugins
65 .iter()
66 .find(|p| p.formats().contains(&fmt))
67 .cloned()
68 }
69
70 fn check_delegate_cycle(&self, src: &str, dst: &str) -> Result<(), RegistryError> {
71 if src == dst {
72 return Err(RegistryError(format!(
73 "backend cannot delegate to itself: {src}"
74 )));
75 }
76 let mut seen = std::collections::HashSet::new();
77 let mut cur = Some(dst.to_string());
78 while let Some(c) = cur {
79 if c == src {
80 return Err(RegistryError(format!(
81 "delegate cycle: {src} -> ... -> {dst}"
82 )));
83 }
84 if !seen.insert(c.clone()) {
85 break;
86 }
87 cur = self.delegates.get(&c).cloned();
88 }
89 Ok(())
90 }
91
92 pub fn resolve_parse_chain(
94 &self,
95 fmt: &str,
96 policy: &BackendPolicy,
97 default: &[&str],
98 ) -> Vec<Arc<dyn ParseCapability>> {
99 let chain =
100 policy.ordered_chain(&default.iter().map(|s| s.to_string()).collect::<Vec<_>>());
101 let caps = self.parse_caps.get(fmt).cloned().unwrap_or_default();
102 let picked: Vec<Arc<dyn ParseCapability>> = chain
103 .iter()
104 .filter_map(|bid| {
105 caps.iter()
106 .find(|c| c.id() == bid)
107 .filter(|c| c.is_available())
108 .cloned()
109 })
110 .collect();
111 if !picked.is_empty() {
112 return picked;
113 }
114 caps.into_iter().filter(|c| c.is_available()).collect()
120 }
121
122 pub fn resolve_process_chain(
123 &self,
124 policy: &BackendPolicy,
125 default: &[&str],
126 ) -> Vec<Arc<dyn ProcessCapability>> {
127 let chain =
128 policy.ordered_chain(&default.iter().map(|s| s.to_string()).collect::<Vec<_>>());
129 chain
130 .iter()
131 .filter_map(|bid| self.process_caps.get(bid.as_str()).cloned())
132 .filter(|c| c.is_available())
133 .filter(|c| c.id() != "ocr" || policy.enable_ocr)
134 .collect()
135 }
136
137 pub fn list_backends(&self) -> Vec<String> {
138 let mut ids: Vec<String> = self
139 .parse_caps
140 .values()
141 .flatten()
142 .map(|c| format!("{} ({})", c.id(), c.kind()))
143 .chain(self.process_caps.keys().cloned())
144 .collect();
145 ids.sort();
146 ids.dedup();
147 ids
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use crate::error::Result;
155 use crate::plugin::RawDoc;
156
157 struct MockParse {
158 id: &'static str,
159 formats: Vec<&'static str>,
160 available: bool,
161 }
162 impl ParseCapability for MockParse {
163 fn id(&self) -> &str {
164 self.id
165 }
166 fn formats(&self) -> &[&str] {
167 &self.formats
168 }
169 fn is_available(&self) -> bool {
170 self.available
171 }
172 fn parse(&self, _path: &str) -> Result<RawDoc> {
173 Ok(RawDoc::default())
174 }
175 }
176
177 struct MockProc {
178 id: &'static str,
179 }
180 impl ProcessCapability for MockProc {
181 fn id(&self) -> &str {
182 self.id
183 }
184 fn process(&self, _raw: &mut RawDoc) -> Result<()> {
185 Ok(())
186 }
187 }
188
189 #[test]
190 fn register_and_resolve_multi_format() {
191 let mut reg = Registry::new();
192 let cap: Arc<dyn ParseCapability> = Arc::new(MockParse {
193 id: "fmt",
194 formats: vec!["a", "b"],
195 available: true,
196 });
197 reg.register_parse(cap, None).unwrap();
198 let pol = BackendPolicy::default();
199 assert_eq!(reg.resolve_parse_chain("a", &pol, &["fmt"]).len(), 1);
200 assert_eq!(reg.resolve_parse_chain("b", &pol, &["fmt"]).len(), 1);
201 assert_eq!(reg.resolve_parse_chain("c", &pol, &["fmt"]).len(), 0);
202 }
203
204 #[test]
205 fn unavailable_is_skipped() {
206 let mut reg = Registry::new();
207 reg.register_parse(
208 Arc::new(MockParse {
209 id: "x",
210 formats: vec!["a"],
211 available: false,
212 }),
213 None,
214 )
215 .unwrap();
216 let pol = BackendPolicy::default();
217 assert_eq!(reg.resolve_parse_chain("a", &pol, &["x"]).len(), 0);
218 }
219
220 #[test]
221 fn process_chain_respects_enable_ocr() {
222 let mut reg = Registry::new();
223 reg.register_process(Arc::new(MockProc { id: "ocr" }));
224 let on = BackendPolicy {
225 enable_ocr: true,
226 ..Default::default()
227 };
228 let off = BackendPolicy {
229 enable_ocr: false,
230 ..Default::default()
231 };
232 assert_eq!(reg.resolve_process_chain(&on, &["ocr"]).len(), 1);
233 assert_eq!(reg.resolve_process_chain(&off, &["ocr"]).len(), 0);
234 }
235
236 #[test]
237 fn delegate_cycle_detected() {
238 let mut reg = Registry::new();
239 reg.register_parse(
240 Arc::new(MockParse {
241 id: "a",
242 formats: vec!["x"],
243 available: true,
244 }),
245 Some("b"),
246 )
247 .unwrap();
248 let err = reg.register_parse(
249 Arc::new(MockParse {
250 id: "b",
251 formats: vec!["x"],
252 available: true,
253 }),
254 Some("a"),
255 );
256 assert!(err.is_err());
257 let err2 = reg.register_parse(
259 Arc::new(MockParse {
260 id: "c",
261 formats: vec!["x"],
262 available: true,
263 }),
264 Some("c"),
265 );
266 assert!(err2.is_err());
267 }
268}