use crate::error::Result;
use crate::traits::PdfWriteHandler;
pub const PRIORITY_HIGH: f64 = 0.0;
pub const PRIORITY_NORMAL: f64 = 10.0;
pub const PRIORITY_LOW: f64 = 20.0;
pub struct WriteHandlerRegistration {
handler: Box<dyn PdfWriteHandler>,
priority: f64,
}
impl WriteHandlerRegistration {
#[must_use]
pub fn new(handler: Box<dyn PdfWriteHandler>, priority: f64) -> Self {
Self { handler, priority }
}
#[must_use]
pub const fn priority(&self) -> f64 {
self.priority
}
#[must_use]
pub fn handler(&self) -> &dyn PdfWriteHandler {
self.handler.as_ref()
}
pub fn handler_mut(&mut self) -> &mut dyn PdfWriteHandler {
self.handler.as_mut()
}
}
pub struct WriteHandlerChain {
registrations: Vec<WriteHandlerRegistration>,
sorted: bool,
}
impl WriteHandlerChain {
#[must_use]
pub fn new() -> Self {
Self {
registrations: Vec::new(),
sorted: true,
}
}
pub fn register(&mut self, handler: Box<dyn PdfWriteHandler>, priority: f64) {
self.registrations
.push(WriteHandlerRegistration::new(handler, priority));
self.sorted = false;
}
#[must_use]
pub fn len(&self) -> usize {
self.registrations.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.registrations.is_empty()
}
fn ensure_sorted(&mut self) {
if !self.sorted {
self.registrations.sort_by(|a, b| {
a.priority
.partial_cmp(&b.priority)
.unwrap_or(std::cmp::Ordering::Equal)
});
self.sorted = true;
}
}
pub fn before_document(&mut self) -> Result<()> {
self.ensure_sorted();
for reg in &mut self.registrations {
reg.handler.before_document()?;
}
Ok(())
}
pub fn before_page(&mut self, page_number: usize) -> Result<()> {
self.ensure_sorted();
for reg in &mut self.registrations {
reg.handler.before_page(page_number)?;
}
Ok(())
}
pub fn after_page(&mut self, page_number: usize) -> Result<()> {
self.ensure_sorted();
for reg in &mut self.registrations {
reg.handler.after_page(page_number)?;
}
Ok(())
}
pub fn after_document(&mut self) -> Result<()> {
self.ensure_sorted();
for reg in &mut self.registrations {
reg.handler.after_document()?;
}
Ok(())
}
}
impl Default for WriteHandlerChain {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
#[derive(Clone)]
struct RecordingHandler {
name: &'static str,
log: Arc<Mutex<Vec<&'static str>>>,
}
impl PdfWriteHandler for RecordingHandler {
fn before_document(&mut self) -> Result<()> {
self.log.lock().expect("poisoned").push(self.name);
Ok(())
}
fn before_page(&mut self, _page_number: usize) -> Result<()> {
self.log.lock().expect("poisoned").push(self.name);
Ok(())
}
fn after_page(&mut self, _page_number: usize) -> Result<()> {
self.log.lock().expect("poisoned").push(self.name);
Ok(())
}
fn after_document(&mut self) -> Result<()> {
self.log.lock().expect("poisoned").push(self.name);
Ok(())
}
}
struct FailingHandler;
impl PdfWriteHandler for FailingHandler {
fn before_document(&mut self) -> Result<()> {
Err(crate::error::PdfError::Other("intentional failure".into()))
}
}
#[test]
fn empty_chain_succeeds() {
let mut chain = WriteHandlerChain::new();
assert!(chain.before_document().is_ok());
assert!(chain.before_page(1).is_ok());
assert!(chain.after_page(1).is_ok());
assert!(chain.after_document().is_ok());
assert!(chain.is_empty());
}
#[test]
fn handlers_execute_in_priority_order() {
let log = Arc::new(Mutex::new(Vec::new()));
let mut chain = WriteHandlerChain::new();
chain.register(
Box::new(RecordingHandler {
name: "low",
log: Arc::clone(&log),
}),
PRIORITY_LOW,
);
chain.register(
Box::new(RecordingHandler {
name: "high",
log: Arc::clone(&log),
}),
PRIORITY_HIGH,
);
chain.register(
Box::new(RecordingHandler {
name: "normal",
log: Arc::clone(&log),
}),
PRIORITY_NORMAL,
);
chain.before_document().unwrap();
let entries: Vec<_> = log.lock().expect("poisoned").clone();
assert_eq!(entries, vec!["high", "normal", "low"]);
}
#[test]
fn equal_priority_preserves_registration_order() {
let log = Arc::new(Mutex::new(Vec::new()));
let mut chain = WriteHandlerChain::new();
chain.register(
Box::new(RecordingHandler {
name: "first",
log: Arc::clone(&log),
}),
PRIORITY_NORMAL,
);
chain.register(
Box::new(RecordingHandler {
name: "second",
log: Arc::clone(&log),
}),
PRIORITY_NORMAL,
);
chain.before_page(1).unwrap();
let entries: Vec<_> = log.lock().expect("poisoned").clone();
assert_eq!(entries, vec!["first", "second"]);
}
#[test]
fn error_stops_chain_execution() {
let log = Arc::new(Mutex::new(Vec::new()));
let mut chain = WriteHandlerChain::new();
chain.register(
Box::new(RecordingHandler {
name: "before",
log: Arc::clone(&log),
}),
PRIORITY_HIGH,
);
chain.register(Box::new(FailingHandler), PRIORITY_NORMAL);
chain.register(
Box::new(RecordingHandler {
name: "after",
log: Arc::clone(&log),
}),
PRIORITY_LOW,
);
let result = chain.before_document();
assert!(result.is_err());
let entries: Vec<_> = log.lock().expect("poisoned").clone();
assert_eq!(entries, vec!["before"]);
}
#[test]
fn len_tracks_registrations() {
let mut chain = WriteHandlerChain::new();
assert_eq!(chain.len(), 0);
chain.register(Box::new(FailingHandler), PRIORITY_NORMAL);
assert_eq!(chain.len(), 1);
}
#[test]
fn default_is_empty() {
let chain = WriteHandlerChain::default();
assert!(chain.is_empty());
}
#[test]
fn lifecycle_methods_all_invoked() {
let log = Arc::new(Mutex::new(Vec::new()));
let mut chain = WriteHandlerChain::new();
chain.register(
Box::new(RecordingHandler {
name: "h",
log: Arc::clone(&log),
}),
PRIORITY_NORMAL,
);
chain.before_document().unwrap();
chain.before_page(1).unwrap();
chain.after_page(1).unwrap();
chain.after_document().unwrap();
let entries: Vec<_> = log.lock().expect("poisoned").clone();
assert_eq!(entries, vec!["h", "h", "h", "h"]);
}
}