use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use crate::plugin::{FormatPlugin, ParseCapability, ProcessCapability};
use crate::policy::BackendPolicy;
#[derive(Debug)]
pub struct RegistryError(pub String);
impl fmt::Display for RegistryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "registry: {}", self.0)
}
}
impl std::error::Error for RegistryError {}
#[derive(Default)]
pub struct Registry {
parse_caps: HashMap<String, Vec<Arc<dyn ParseCapability>>>,
process_caps: HashMap<String, Arc<dyn ProcessCapability>>,
plugins: Vec<Arc<dyn FormatPlugin>>,
delegates: HashMap<String, String>, }
impl Registry {
pub fn new() -> Self {
Self::default()
}
pub fn register_parse(
&mut self,
cap: Arc<dyn ParseCapability>,
delegate_to: Option<&str>,
) -> Result<(), RegistryError> {
if let Some(dst) = delegate_to {
self.check_delegate_cycle(cap.id(), dst)?;
self.delegates.insert(cap.id().to_string(), dst.to_string());
}
for f in cap.formats() {
self.parse_caps
.entry((*f).to_string())
.or_default()
.push(cap.clone());
}
Ok(())
}
pub fn register_process(&mut self, cap: Arc<dyn ProcessCapability>) {
self.process_caps.insert(cap.id().to_string(), cap);
}
pub fn register_plugin(&mut self, plugin: Arc<dyn FormatPlugin>) {
self.plugins.push(plugin);
}
pub fn plugin_for(&self, fmt: &str) -> Option<Arc<dyn FormatPlugin>> {
self.plugins
.iter()
.find(|p| p.formats().contains(&fmt))
.cloned()
}
fn check_delegate_cycle(&self, src: &str, dst: &str) -> Result<(), RegistryError> {
if src == dst {
return Err(RegistryError(format!(
"backend cannot delegate to itself: {src}"
)));
}
let mut seen = std::collections::HashSet::new();
let mut cur = Some(dst.to_string());
while let Some(c) = cur {
if c == src {
return Err(RegistryError(format!(
"delegate cycle: {src} -> ... -> {dst}"
)));
}
if !seen.insert(c.clone()) {
break;
}
cur = self.delegates.get(&c).cloned();
}
Ok(())
}
pub fn resolve_parse_chain(
&self,
fmt: &str,
policy: &BackendPolicy,
default: &[&str],
) -> Vec<Arc<dyn ParseCapability>> {
let chain =
policy.ordered_chain(&default.iter().map(|s| s.to_string()).collect::<Vec<_>>());
let caps = self.parse_caps.get(fmt).cloned().unwrap_or_default();
chain
.iter()
.filter_map(|bid| {
caps.iter()
.find(|c| c.id() == bid)
.filter(|c| c.is_available())
.cloned()
})
.collect()
}
pub fn resolve_process_chain(
&self,
policy: &BackendPolicy,
default: &[&str],
) -> Vec<Arc<dyn ProcessCapability>> {
let chain =
policy.ordered_chain(&default.iter().map(|s| s.to_string()).collect::<Vec<_>>());
chain
.iter()
.filter_map(|bid| self.process_caps.get(bid.as_str()).cloned())
.filter(|c| c.is_available())
.filter(|c| c.id() != "ocr" || policy.enable_ocr)
.collect()
}
pub fn list_backends(&self) -> Vec<String> {
let mut ids: Vec<String> = self
.parse_caps
.values()
.flatten()
.map(|c| format!("{} ({})", c.id(), c.kind()))
.chain(self.process_caps.keys().cloned())
.collect();
ids.sort();
ids.dedup();
ids
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Result;
use crate::plugin::RawDoc;
struct MockParse {
id: &'static str,
formats: Vec<&'static str>,
available: bool,
}
impl ParseCapability for MockParse {
fn id(&self) -> &str {
self.id
}
fn formats(&self) -> &[&str] {
&self.formats
}
fn is_available(&self) -> bool {
self.available
}
fn parse(&self, _path: &str) -> Result<RawDoc> {
Ok(RawDoc::default())
}
}
struct MockProc {
id: &'static str,
}
impl ProcessCapability for MockProc {
fn id(&self) -> &str {
self.id
}
fn process(&self, _raw: &mut RawDoc) -> Result<()> {
Ok(())
}
}
#[test]
fn register_and_resolve_multi_format() {
let mut reg = Registry::new();
let cap: Arc<dyn ParseCapability> = Arc::new(MockParse {
id: "fmt",
formats: vec!["a", "b"],
available: true,
});
reg.register_parse(cap, None).unwrap();
let pol = BackendPolicy::default();
assert_eq!(reg.resolve_parse_chain("a", &pol, &["fmt"]).len(), 1);
assert_eq!(reg.resolve_parse_chain("b", &pol, &["fmt"]).len(), 1);
assert_eq!(reg.resolve_parse_chain("c", &pol, &["fmt"]).len(), 0);
}
#[test]
fn unavailable_is_skipped() {
let mut reg = Registry::new();
reg.register_parse(
Arc::new(MockParse {
id: "x",
formats: vec!["a"],
available: false,
}),
None,
)
.unwrap();
let pol = BackendPolicy::default();
assert_eq!(reg.resolve_parse_chain("a", &pol, &["x"]).len(), 0);
}
#[test]
fn process_chain_respects_enable_ocr() {
let mut reg = Registry::new();
reg.register_process(Arc::new(MockProc { id: "ocr" }));
let on = BackendPolicy {
enable_ocr: true,
..Default::default()
};
let off = BackendPolicy {
enable_ocr: false,
..Default::default()
};
assert_eq!(reg.resolve_process_chain(&on, &["ocr"]).len(), 1);
assert_eq!(reg.resolve_process_chain(&off, &["ocr"]).len(), 0);
}
#[test]
fn delegate_cycle_detected() {
let mut reg = Registry::new();
reg.register_parse(
Arc::new(MockParse {
id: "a",
formats: vec!["x"],
available: true,
}),
Some("b"),
)
.unwrap();
let err = reg.register_parse(
Arc::new(MockParse {
id: "b",
formats: vec!["x"],
available: true,
}),
Some("a"),
);
assert!(err.is_err());
let err2 = reg.register_parse(
Arc::new(MockParse {
id: "c",
formats: vec!["x"],
available: true,
}),
Some("c"),
);
assert!(err2.is_err());
}
}