#[cfg(feature = "classification")]
pub mod classification;
#[cfg(feature = "classification")]
pub mod chunk_classification;
#[cfg(feature = "summarization")]
pub mod summarization;
#[cfg(feature = "translation")]
pub mod translation;
#[cfg(feature = "captioning")]
pub mod captioning;
#[cfg(feature = "qr-codes")]
pub mod qr;
#[cfg(feature = "ner")]
pub mod ner;
#[cfg(feature = "redaction")]
pub mod redaction;
type BuiltinRegistration = (&'static str, fn() -> crate::Result<()>);
fn register_all(entries: &[BuiltinRegistration]) -> crate::Result<()> {
let mut failures: Vec<String> = Vec::new();
for (name, register) in entries {
if let Err(e) = register() {
tracing::error!(
"Failed to register built-in post-processor '{name}': {e}. Configuring this \
processor will produce no output for its stage until registration succeeds."
);
failures.push(format!("{name}: {e}"));
}
}
if failures.is_empty() {
Ok(())
} else {
Err(crate::XbergError::Plugin {
message: format!(
"{} of {} built-in post-processor(s) failed to register: {}",
failures.len(),
entries.len(),
failures.join("; ")
),
plugin_name: "builtin".to_string(),
})
}
}
#[cfg(any(
feature = "classification",
feature = "summarization",
feature = "translation",
feature = "captioning",
feature = "qr-codes",
feature = "ner",
feature = "redaction"
))]
fn register_builtin_processor(processor: std::sync::Arc<dyn crate::plugins::PostProcessor>) -> crate::Result<()> {
crate::plugins::processor::register_post_processor_if_absent(processor).map(|_| ())
}
#[cfg(feature = "classification")]
fn register_classification() -> crate::Result<()> {
register_builtin_processor(std::sync::Arc::new(classification::PageClassificationProcessor))
}
#[cfg(feature = "classification")]
fn register_chunk_classification() -> crate::Result<()> {
register_builtin_processor(std::sync::Arc::new(chunk_classification::ChunkClassificationProcessor))
}
#[cfg(feature = "summarization")]
fn register_summarization() -> crate::Result<()> {
register_builtin_processor(std::sync::Arc::new(summarization::SummarizationProcessor))
}
#[cfg(feature = "translation")]
fn register_translation() -> crate::Result<()> {
register_builtin_processor(std::sync::Arc::new(translation::TranslationProcessor))
}
#[cfg(feature = "captioning")]
fn register_captioning() -> crate::Result<()> {
register_builtin_processor(std::sync::Arc::new(captioning::CaptioningProcessor))
}
#[cfg(feature = "qr-codes")]
fn register_qr() -> crate::Result<()> {
register_builtin_processor(std::sync::Arc::new(qr::QrCodeProcessor))
}
#[cfg(feature = "ner")]
fn register_ner() -> crate::Result<()> {
register_builtin_processor(std::sync::Arc::new(ner::NerProcessor))
}
#[cfg(feature = "redaction")]
fn register_redaction() -> crate::Result<()> {
register_builtin_processor(std::sync::Arc::new(redaction::RedactionProcessor))
}
#[allow(
clippy::vec_init_then_push,
reason = "every push below is #[cfg]-gated, so the entries cannot be written as one vec![] literal"
)]
pub fn register_builtin() -> crate::Result<()> {
#[allow(unused_mut, reason = "mutated conditionally per enabled feature below")]
let mut entries: Vec<BuiltinRegistration> = Vec::new();
#[cfg(feature = "classification")]
entries.push(("classification", register_classification));
#[cfg(feature = "classification")]
entries.push(("chunk-classification", register_chunk_classification));
#[cfg(feature = "summarization")]
entries.push(("summarization", register_summarization));
#[cfg(feature = "translation")]
entries.push(("translation", register_translation));
#[cfg(feature = "captioning")]
entries.push(("captioning", register_captioning));
#[cfg(feature = "qr-codes")]
entries.push(("qr-codes", register_qr));
#[cfg(feature = "ner")]
entries.push(("ner", register_ner));
#[cfg(feature = "redaction")]
entries.push(("redaction", register_redaction));
register_all(&entries)
}
#[cfg(test)]
mod tests {
use super::register_all;
#[test]
fn should_attempt_every_registration_even_when_an_earlier_one_fails() {
static ATTEMPTED: std::sync::Mutex<Vec<&'static str>> = std::sync::Mutex::new(Vec::new());
fn record(name: &'static str) {
ATTEMPTED.lock().unwrap().push(name);
}
fn ok_a() -> crate::Result<()> {
record("a");
Ok(())
}
fn failing_b() -> crate::Result<()> {
record("b");
Err(crate::XbergError::Plugin {
message: "boom".to_string(),
plugin_name: "b".to_string(),
})
}
fn ok_c() -> crate::Result<()> {
record("c");
Ok(())
}
let result = register_all(&[("a", ok_a), ("b", failing_b), ("c", ok_c)]);
assert!(result.is_err(), "a failure among the entries must be reported");
assert_eq!(*ATTEMPTED.lock().unwrap(), vec!["a", "b", "c"]);
}
#[test]
fn should_return_ok_when_every_registration_succeeds() {
fn ok() -> crate::Result<()> {
Ok(())
}
assert!(register_all(&[("a", ok), ("b", ok)]).is_ok());
}
#[test]
fn should_name_every_failing_processor_in_the_aggregated_error() {
fn failing() -> crate::Result<()> {
Err(crate::XbergError::Plugin {
message: "nope".to_string(),
plugin_name: "x".to_string(),
})
}
fn ok() -> crate::Result<()> {
Ok(())
}
let err = register_all(&[("first", failing), ("second", ok), ("third", failing)]).unwrap_err();
let message = err.to_string();
assert!(message.contains("first"), "message was: {message}");
assert!(message.contains("third"), "message was: {message}");
assert!(!message.contains("second"), "message was: {message}");
}
}