Skip to main content

docparser_core/
registry.rs

1//! 注册表:能力 / 后端注册、按策略解析后端链、复合后端委托环检测。
2
3use std::collections::HashMap;
4use std::fmt;
5use std::sync::Arc;
6
7use crate::plugin::{FormatPlugin, ParseCapability, ProcessCapability};
8use crate::policy::BackendPolicy;
9
10/// 复合后端不可委托成环。
11#[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>, // backend id -> delegate id
28}
29
30impl Registry {
31    pub fn new() -> Self {
32        Self::default()
33    }
34
35    /// 注册解析后端;同一声明的多个 format 共享同一 `Arc`。
36    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    /// 按格式找插件。
63    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    /// 解析该格式可尝试的解析后端链(过滤不可用项)。
93    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        // 空链兜底:一个插件可以声明多条后端链(如字幕 srt/vtt/ass 共用一个插件),
115        // 而 `allow_fallback = false` 会把链截断到 1 项——截掉的正好是"属于该格式"的后端时,
116        // 上面就一个都匹配不上(表现为「全部解析后端失败」,且是空链、没有任何尝试记录)。
117        // 此时退回"注册在该格式下的后端"(注册序):空链永远是错误结果,退回只会把
118        // "必然失败"变成"按默认顺序尝试",不会推翻调用方显式指定的可用后端。
119        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        // 自环同样拒绝
258        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}