easypdf_core/
handler_chain.rs1use crate::error::Result;
13use crate::traits::PdfWriteHandler;
14
15pub const PRIORITY_HIGH: f64 = 0.0;
17
18pub const PRIORITY_NORMAL: f64 = 10.0;
20
21pub const PRIORITY_LOW: f64 = 20.0;
23
24pub struct WriteHandlerRegistration {
29 handler: Box<dyn PdfWriteHandler>,
31 priority: f64,
33}
34
35impl WriteHandlerRegistration {
36 #[must_use]
38 pub fn new(handler: Box<dyn PdfWriteHandler>, priority: f64) -> Self {
39 Self { handler, priority }
40 }
41
42 #[must_use]
44 pub const fn priority(&self) -> f64 {
45 self.priority
46 }
47
48 #[must_use]
50 pub fn handler(&self) -> &dyn PdfWriteHandler {
51 self.handler.as_ref()
52 }
53
54 pub fn handler_mut(&mut self) -> &mut dyn PdfWriteHandler {
56 self.handler.as_mut()
57 }
58}
59
60pub struct WriteHandlerChain {
83 registrations: Vec<WriteHandlerRegistration>,
84 sorted: bool,
85}
86
87impl WriteHandlerChain {
88 #[must_use]
90 pub fn new() -> Self {
91 Self {
92 registrations: Vec::new(),
93 sorted: true,
94 }
95 }
96
97 pub fn register(&mut self, handler: Box<dyn PdfWriteHandler>, priority: f64) {
101 self.registrations
102 .push(WriteHandlerRegistration::new(handler, priority));
103 self.sorted = false;
104 }
105
106 #[must_use]
108 pub fn len(&self) -> usize {
109 self.registrations.len()
110 }
111
112 #[must_use]
114 pub fn is_empty(&self) -> bool {
115 self.registrations.is_empty()
116 }
117
118 fn ensure_sorted(&mut self) {
120 if !self.sorted {
121 self.registrations.sort_by(|a, b| {
122 a.priority
123 .partial_cmp(&b.priority)
124 .unwrap_or(std::cmp::Ordering::Equal)
125 });
126 self.sorted = true;
127 }
128 }
129
130 pub fn before_document(&mut self) -> Result<()> {
136 self.ensure_sorted();
137 for reg in &mut self.registrations {
138 reg.handler.before_document()?;
139 }
140 Ok(())
141 }
142
143 pub fn before_page(&mut self, page_number: usize) -> Result<()> {
149 self.ensure_sorted();
150 for reg in &mut self.registrations {
151 reg.handler.before_page(page_number)?;
152 }
153 Ok(())
154 }
155
156 pub fn after_page(&mut self, page_number: usize) -> Result<()> {
162 self.ensure_sorted();
163 for reg in &mut self.registrations {
164 reg.handler.after_page(page_number)?;
165 }
166 Ok(())
167 }
168
169 pub fn after_document(&mut self) -> Result<()> {
175 self.ensure_sorted();
176 for reg in &mut self.registrations {
177 reg.handler.after_document()?;
178 }
179 Ok(())
180 }
181}
182
183impl Default for WriteHandlerChain {
184 fn default() -> Self {
185 Self::new()
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use std::sync::{Arc, Mutex};
193
194 #[derive(Clone)]
196 struct RecordingHandler {
197 name: &'static str,
198 log: Arc<Mutex<Vec<&'static str>>>,
199 }
200
201 impl PdfWriteHandler for RecordingHandler {
202 fn before_document(&mut self) -> Result<()> {
203 self.log.lock().expect("poisoned").push(self.name);
204 Ok(())
205 }
206 fn before_page(&mut self, _page_number: usize) -> Result<()> {
207 self.log.lock().expect("poisoned").push(self.name);
208 Ok(())
209 }
210 fn after_page(&mut self, _page_number: usize) -> Result<()> {
211 self.log.lock().expect("poisoned").push(self.name);
212 Ok(())
213 }
214 fn after_document(&mut self) -> Result<()> {
215 self.log.lock().expect("poisoned").push(self.name);
216 Ok(())
217 }
218 }
219
220 struct FailingHandler;
221
222 impl PdfWriteHandler for FailingHandler {
223 fn before_document(&mut self) -> Result<()> {
224 Err(crate::error::PdfError::Other("intentional failure".into()))
225 }
226 }
227
228 #[test]
229 fn empty_chain_succeeds() {
230 let mut chain = WriteHandlerChain::new();
231 assert!(chain.before_document().is_ok());
232 assert!(chain.before_page(1).is_ok());
233 assert!(chain.after_page(1).is_ok());
234 assert!(chain.after_document().is_ok());
235 assert!(chain.is_empty());
236 }
237
238 #[test]
239 fn handlers_execute_in_priority_order() {
240 let log = Arc::new(Mutex::new(Vec::new()));
241 let mut chain = WriteHandlerChain::new();
242 chain.register(
243 Box::new(RecordingHandler {
244 name: "low",
245 log: Arc::clone(&log),
246 }),
247 PRIORITY_LOW,
248 );
249 chain.register(
250 Box::new(RecordingHandler {
251 name: "high",
252 log: Arc::clone(&log),
253 }),
254 PRIORITY_HIGH,
255 );
256 chain.register(
257 Box::new(RecordingHandler {
258 name: "normal",
259 log: Arc::clone(&log),
260 }),
261 PRIORITY_NORMAL,
262 );
263
264 chain.before_document().unwrap();
265 let entries: Vec<_> = log.lock().expect("poisoned").clone();
266 assert_eq!(entries, vec!["high", "normal", "low"]);
267 }
268
269 #[test]
270 fn equal_priority_preserves_registration_order() {
271 let log = Arc::new(Mutex::new(Vec::new()));
272 let mut chain = WriteHandlerChain::new();
273 chain.register(
274 Box::new(RecordingHandler {
275 name: "first",
276 log: Arc::clone(&log),
277 }),
278 PRIORITY_NORMAL,
279 );
280 chain.register(
281 Box::new(RecordingHandler {
282 name: "second",
283 log: Arc::clone(&log),
284 }),
285 PRIORITY_NORMAL,
286 );
287
288 chain.before_page(1).unwrap();
289 let entries: Vec<_> = log.lock().expect("poisoned").clone();
290 assert_eq!(entries, vec!["first", "second"]);
291 }
292
293 #[test]
294 fn error_stops_chain_execution() {
295 let log = Arc::new(Mutex::new(Vec::new()));
296 let mut chain = WriteHandlerChain::new();
297 chain.register(
298 Box::new(RecordingHandler {
299 name: "before",
300 log: Arc::clone(&log),
301 }),
302 PRIORITY_HIGH,
303 );
304 chain.register(Box::new(FailingHandler), PRIORITY_NORMAL);
305 chain.register(
306 Box::new(RecordingHandler {
307 name: "after",
308 log: Arc::clone(&log),
309 }),
310 PRIORITY_LOW,
311 );
312
313 let result = chain.before_document();
314 assert!(result.is_err());
315 let entries: Vec<_> = log.lock().expect("poisoned").clone();
316 assert_eq!(entries, vec!["before"]);
317 }
318
319 #[test]
320 fn len_tracks_registrations() {
321 let mut chain = WriteHandlerChain::new();
322 assert_eq!(chain.len(), 0);
323 chain.register(Box::new(FailingHandler), PRIORITY_NORMAL);
324 assert_eq!(chain.len(), 1);
325 }
326
327 #[test]
328 fn default_is_empty() {
329 let chain = WriteHandlerChain::default();
330 assert!(chain.is_empty());
331 }
332
333 #[test]
334 fn lifecycle_methods_all_invoked() {
335 let log = Arc::new(Mutex::new(Vec::new()));
336 let mut chain = WriteHandlerChain::new();
337 chain.register(
338 Box::new(RecordingHandler {
339 name: "h",
340 log: Arc::clone(&log),
341 }),
342 PRIORITY_NORMAL,
343 );
344
345 chain.before_document().unwrap();
346 chain.before_page(1).unwrap();
347 chain.after_page(1).unwrap();
348 chain.after_document().unwrap();
349
350 let entries: Vec<_> = log.lock().expect("poisoned").clone();
351 assert_eq!(entries, vec!["h", "h", "h", "h"]);
352 }
353}