1use std::{any::Any, collections::HashMap};
2
3use rustrails_support::callbacks::{
4 AroundContinuation, Callback, CallbackConditions, CallbackFilter, CallbackKind, CallbackResult,
5};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum ModelEvent {
10 BeforeValidation,
12 AfterValidation,
14 BeforeSave,
16 AfterSave,
18 BeforeCreate,
20 AfterCreate,
22 BeforeUpdate,
24 AfterUpdate,
26 BeforeDestroy,
28 AfterDestroy,
30}
31
32impl ModelEvent {
33 #[must_use]
35 pub fn all() -> &'static [ModelEvent] {
36 static EVENTS: [ModelEvent; 10] = [
37 ModelEvent::BeforeValidation,
38 ModelEvent::AfterValidation,
39 ModelEvent::BeforeSave,
40 ModelEvent::AfterSave,
41 ModelEvent::BeforeCreate,
42 ModelEvent::AfterCreate,
43 ModelEvent::BeforeUpdate,
44 ModelEvent::AfterUpdate,
45 ModelEvent::BeforeDestroy,
46 ModelEvent::AfterDestroy,
47 ];
48
49 &EVENTS
50 }
51
52 #[must_use]
54 pub const fn as_str(&self) -> &'static str {
55 match self {
56 Self::BeforeValidation => "before_validation",
57 Self::AfterValidation => "after_validation",
58 Self::BeforeSave => "before_save",
59 Self::AfterSave => "after_save",
60 Self::BeforeCreate => "before_create",
61 Self::AfterCreate => "after_create",
62 Self::BeforeUpdate => "before_update",
63 Self::AfterUpdate => "after_update",
64 Self::BeforeDestroy => "before_destroy",
65 Self::AfterDestroy => "after_destroy",
66 }
67 }
68}
69
70#[derive(Debug)]
72struct EventCallbacks<T: Send + Sync + 'static> {
73 name: &'static str,
74 callbacks: Vec<Callback<T>>,
75}
76
77impl<T: Send + Sync + 'static> EventCallbacks<T> {
78 fn new(event: ModelEvent) -> Self {
79 Self {
80 name: event.as_str(),
81 callbacks: Vec::new(),
82 }
83 }
84
85 fn add(&mut self, callback: Callback<T>) {
86 self.skip(&callback.name);
87 self.callbacks.push(callback);
88 }
89
90 fn run(&self, target: &mut T) -> CallbackResult {
91 if self.callbacks.is_empty() {
92 return CallbackResult::Continue;
93 }
94
95 let target_any = &*target as &dyn Any;
96 let applicable = self
97 .callbacks
98 .iter()
99 .filter(|callback| conditions_match(&callback.conditions, target_any, self.name))
100 .collect::<Vec<_>>();
101
102 for callback in applicable
103 .iter()
104 .copied()
105 .filter(|callback| callback.kind == CallbackKind::Before)
106 {
107 let result = match &callback.filter {
108 CallbackFilter::Standard(filter) => filter(target),
109 CallbackFilter::Around(_) => CallbackResult::Continue,
110 };
111
112 if result == CallbackResult::Halt {
113 return result;
114 }
115 }
116
117 let around = applicable
118 .iter()
119 .copied()
120 .filter(|callback| callback.kind == CallbackKind::Around)
121 .collect::<Vec<_>>();
122 let result = invoke_around(&around, target, Box::new(|_| CallbackResult::Continue));
123 if result == CallbackResult::Halt {
124 return result;
125 }
126
127 for callback in applicable
128 .iter()
129 .copied()
130 .filter(|callback| callback.kind == CallbackKind::After)
131 {
132 if let CallbackFilter::Standard(filter) = &callback.filter {
133 let _ = filter(target);
134 }
135 }
136
137 result
138 }
139
140 fn skip(&mut self, name: &str) {
141 self.callbacks.retain(|callback| callback.name != name);
142 }
143
144 fn reset(&mut self, kind: Option<CallbackKind>) {
145 match kind {
146 Some(kind) => self.callbacks.retain(|callback| callback.kind != kind),
147 None => self.callbacks.clear(),
148 }
149 }
150}
151
152#[derive(Debug)]
153pub struct ModelCallbacks<T: Send + Sync + 'static> {
154 chains: HashMap<ModelEvent, EventCallbacks<T>>,
155}
156
157impl<T: Send + Sync + 'static> Default for ModelCallbacks<T> {
158 fn default() -> Self {
159 Self::new()
160 }
161}
162
163impl<T: Send + Sync + 'static> ModelCallbacks<T> {
164 #[must_use]
166 pub fn new() -> Self {
167 let chains = ModelEvent::all()
168 .iter()
169 .copied()
170 .map(|event| (event, EventCallbacks::new(event)))
171 .collect();
172
173 Self { chains }
174 }
175
176 pub fn add(&mut self, event: ModelEvent, callback: Callback<T>) {
178 self.chains
179 .entry(event)
180 .or_insert_with(|| EventCallbacks::new(event))
181 .add(callback);
182 }
183
184 pub fn run(&self, event: ModelEvent, target: &mut T) -> CallbackResult {
186 self.chains
187 .get(&event)
188 .map_or(CallbackResult::Continue, |callbacks| callbacks.run(target))
189 }
190
191 pub fn skip(&mut self, event: ModelEvent, name: &str) {
193 if let Some(callbacks) = self.chains.get_mut(&event) {
194 callbacks.skip(name);
195 }
196 }
197
198 pub fn reset(&mut self, event: ModelEvent) {
200 self.reset_callbacks(event, None);
201 }
202
203 pub fn reset_callbacks(&mut self, event: ModelEvent, kind: Option<CallbackKind>) {
205 if let Some(callbacks) = self.chains.get_mut(&event) {
206 callbacks.reset(kind);
207 }
208 }
209}
210
211fn invoke_around<'a, T: Send + Sync + 'static>(
212 callbacks: &'a [&'a Callback<T>],
213 target: &mut T,
214 action: AroundContinuation<'a, T>,
215) -> CallbackResult {
216 match callbacks.split_first() {
217 Some((callback, rest)) => match &callback.filter {
218 CallbackFilter::Around(filter) => filter(
219 target,
220 Box::new(move |target| invoke_around(rest, target, action)),
221 ),
222 CallbackFilter::Standard(_) => invoke_around(rest, target, action),
223 },
224 None => action(target),
225 }
226}
227
228fn conditions_match(conditions: &CallbackConditions, target: &dyn Any, action_name: &str) -> bool {
229 if let Some(only) = &conditions.only
230 && !only.iter().any(|candidate| candidate == action_name)
231 {
232 return false;
233 }
234
235 if let Some(except) = &conditions.except
236 && except.iter().any(|candidate| candidate == action_name)
237 {
238 return false;
239 }
240
241 if let Some(predicate) = &conditions.if_cond
242 && !predicate(target)
243 {
244 return false;
245 }
246
247 if let Some(predicate) = &conditions.unless_cond
248 && predicate(target)
249 {
250 return false;
251 }
252
253 true
254}
255
256pub trait HasModelCallbacks: Send + Sync + Sized + 'static {
258 fn model_callbacks() -> &'static ModelCallbacks<Self>;
260
261 fn run_callbacks(&mut self, event: ModelEvent) -> CallbackResult {
263 Self::model_callbacks().run(event, self)
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use std::collections::HashSet;
270
271 use once_cell::sync::Lazy;
272 use rustrails_support::callbacks::{
273 Callback, CallbackChain, CallbackConditions, CallbackKind, CallbackResult,
274 };
275
276 use super::{HasModelCallbacks, ModelCallbacks, ModelEvent};
277
278 #[derive(Debug, Default)]
279 struct TestModel {
280 log: Vec<&'static str>,
281 allow: bool,
282 blocked: bool,
283 }
284
285 impl TestModel {
286 fn push(&mut self, entry: &'static str) {
287 self.log.push(entry);
288 }
289 }
290
291 static TEST_MODEL_CALLBACKS: Lazy<ModelCallbacks<TestModel>> = Lazy::new(|| {
292 let mut callbacks = ModelCallbacks::new();
293 callbacks.add(
294 ModelEvent::BeforeValidation,
295 Callback::before("record_before_validation", |model: &mut TestModel| {
296 model.push("before_validation");
297 CallbackResult::Continue
298 }),
299 );
300 callbacks.add(
301 ModelEvent::AfterValidation,
302 Callback::after("record_after_validation", |model: &mut TestModel| {
303 model.push("after_validation");
304 CallbackResult::Continue
305 }),
306 );
307 callbacks.add(
308 ModelEvent::AfterCreate,
309 Callback::after("record_after_create", |model: &mut TestModel| {
310 model.push("after_create");
311 CallbackResult::Continue
312 }),
313 );
314 callbacks
315 });
316
317 impl HasModelCallbacks for TestModel {
318 fn model_callbacks() -> &'static ModelCallbacks<Self> {
319 &TEST_MODEL_CALLBACKS
320 }
321 }
322
323 #[test]
324 fn before_and_after_validation_callbacks_fire() {
325 let mut model = TestModel::default();
326
327 assert_eq!(
328 model.run_callbacks(ModelEvent::BeforeValidation),
329 CallbackResult::Continue
330 );
331 assert_eq!(
332 model.run_callbacks(ModelEvent::AfterValidation),
333 CallbackResult::Continue
334 );
335
336 assert_eq!(model.log, vec!["before_validation", "after_validation"]);
337 }
338
339 #[test]
340 fn before_save_can_halt() {
341 let mut callbacks = ModelCallbacks::new();
342 callbacks.add(
343 ModelEvent::BeforeSave,
344 Callback::before("halt_save", |model: &mut TestModel| {
345 model.push("before_save");
346 CallbackResult::Halt
347 }),
348 );
349 callbacks.add(
350 ModelEvent::AfterSave,
351 Callback::after("after_save", |model: &mut TestModel| {
352 model.push("after_save");
353 CallbackResult::Continue
354 }),
355 );
356
357 let mut model = TestModel::default();
358 let result = callbacks.run(ModelEvent::BeforeSave, &mut model);
359
360 assert_eq!(result, CallbackResult::Halt);
361 assert_eq!(model.log, vec!["before_save"]);
362 }
363
364 #[test]
365 fn after_create_fires() {
366 let mut model = TestModel::default();
367
368 let result = model.run_callbacks(ModelEvent::AfterCreate);
369
370 assert_eq!(result, CallbackResult::Continue);
371 assert_eq!(model.log, vec!["after_create"]);
372 }
373
374 #[test]
375 fn before_callbacks_run_in_declaration_order() {
376 let mut callbacks = ModelCallbacks::new();
377 callbacks.add(
378 ModelEvent::BeforeValidation,
379 Callback::before("first", |model: &mut TestModel| {
380 model.push("first");
381 CallbackResult::Continue
382 }),
383 );
384 callbacks.add(
385 ModelEvent::BeforeValidation,
386 Callback::before("second", |model: &mut TestModel| {
387 model.push("second");
388 CallbackResult::Continue
389 }),
390 );
391 callbacks.add(
392 ModelEvent::BeforeValidation,
393 Callback::before("third", |model: &mut TestModel| {
394 model.push("third");
395 CallbackResult::Continue
396 }),
397 );
398
399 let mut model = TestModel::default();
400 let result = callbacks.run(ModelEvent::BeforeValidation, &mut model);
401
402 assert_eq!(result, CallbackResult::Continue);
403 assert_eq!(model.log, vec!["first", "second", "third"]);
404 }
405
406 #[test]
407 fn skip_callback_by_name() {
408 let mut callbacks = ModelCallbacks::new();
409 callbacks.add(
410 ModelEvent::BeforeValidation,
411 Callback::before("keep", |model: &mut TestModel| {
412 model.push("keep");
413 CallbackResult::Continue
414 }),
415 );
416 callbacks.add(
417 ModelEvent::BeforeValidation,
418 Callback::before("skip_me", |model: &mut TestModel| {
419 model.push("skip_me");
420 CallbackResult::Continue
421 }),
422 );
423 callbacks.skip(ModelEvent::BeforeValidation, "skip_me");
424
425 let mut model = TestModel::default();
426 let result = callbacks.run(ModelEvent::BeforeValidation, &mut model);
427
428 assert_eq!(result, CallbackResult::Continue);
429 assert_eq!(model.log, vec!["keep"]);
430 }
431
432 #[test]
433 fn reset_clears_callbacks_for_an_event() {
434 let mut callbacks = ModelCallbacks::new();
435 callbacks.add(
436 ModelEvent::BeforeDestroy,
437 Callback::before("cleanup", |model: &mut TestModel| {
438 model.push("cleanup");
439 CallbackResult::Continue
440 }),
441 );
442 callbacks.reset(ModelEvent::BeforeDestroy);
443
444 let mut model = TestModel::default();
445 let result = callbacks.run(ModelEvent::BeforeDestroy, &mut model);
446
447 assert_eq!(result, CallbackResult::Continue);
448 assert!(model.log.is_empty());
449 }
450
451 #[test]
452 fn reset_callbacks_can_clear_one_kind_without_disturbing_other_kinds() {
453 let mut callbacks = ModelCallbacks::new();
454 callbacks.add(
455 ModelEvent::BeforeSave,
456 Callback::before("before", |model: &mut TestModel| {
457 model.push("before");
458 CallbackResult::Continue
459 }),
460 );
461 callbacks.add(
462 ModelEvent::BeforeSave,
463 Callback::after("after", |model: &mut TestModel| {
464 model.push("after");
465 CallbackResult::Continue
466 }),
467 );
468
469 callbacks.reset_callbacks(ModelEvent::BeforeSave, Some(CallbackKind::Before));
470
471 let mut model = TestModel::default();
472 let result = callbacks.run(ModelEvent::BeforeSave, &mut model);
473
474 assert_eq!(result, CallbackResult::Continue);
475 assert_eq!(model.log, vec!["after"]);
476 }
477
478 #[test]
479 fn all_model_events_are_covered_once() {
480 let expected = [
481 (ModelEvent::BeforeValidation, "before_validation"),
482 (ModelEvent::AfterValidation, "after_validation"),
483 (ModelEvent::BeforeSave, "before_save"),
484 (ModelEvent::AfterSave, "after_save"),
485 (ModelEvent::BeforeCreate, "before_create"),
486 (ModelEvent::AfterCreate, "after_create"),
487 (ModelEvent::BeforeUpdate, "before_update"),
488 (ModelEvent::AfterUpdate, "after_update"),
489 (ModelEvent::BeforeDestroy, "before_destroy"),
490 (ModelEvent::AfterDestroy, "after_destroy"),
491 ];
492
493 assert_eq!(ModelEvent::all().len(), expected.len());
494 assert_eq!(
495 ModelEvent::all()
496 .iter()
497 .map(|event| (*event, event.as_str()))
498 .collect::<Vec<_>>(),
499 expected.to_vec()
500 );
501
502 let names = ModelEvent::all()
503 .iter()
504 .map(ModelEvent::as_str)
505 .collect::<HashSet<_>>();
506 assert_eq!(names.len(), ModelEvent::all().len());
507 }
508
509 #[test]
510 fn before_save_callbacks_fire() {
511 let mut callbacks = ModelCallbacks::new();
512 callbacks.add(
513 ModelEvent::BeforeSave,
514 Callback::before("audit", |model: &mut TestModel| {
515 model.push("before_save");
516 CallbackResult::Continue
517 }),
518 );
519
520 let mut model = TestModel::default();
521 let result = callbacks.run(ModelEvent::BeforeSave, &mut model);
522
523 assert_eq!(result, CallbackResult::Continue);
524 assert_eq!(model.log, vec!["before_save"]);
525 }
526
527 #[test]
528 fn after_save_callbacks_fire() {
529 let mut callbacks = ModelCallbacks::new();
530 callbacks.add(
531 ModelEvent::AfterSave,
532 Callback::after("publish", |model: &mut TestModel| {
533 model.push("after_save");
534 CallbackResult::Continue
535 }),
536 );
537
538 let mut model = TestModel::default();
539 let result = callbacks.run(ModelEvent::AfterSave, &mut model);
540
541 assert_eq!(result, CallbackResult::Continue);
542 assert_eq!(model.log, vec!["after_save"]);
543 }
544
545 #[test]
546 fn after_callbacks_run_in_declaration_order() {
547 let mut callbacks = ModelCallbacks::new();
548 callbacks.add(
549 ModelEvent::AfterSave,
550 Callback::after("first", |model: &mut TestModel| {
551 model.push("first");
552 CallbackResult::Continue
553 }),
554 );
555 callbacks.add(
556 ModelEvent::AfterSave,
557 Callback::after("second", |model: &mut TestModel| {
558 model.push("second");
559 CallbackResult::Continue
560 }),
561 );
562
563 let mut model = TestModel::default();
564 let result = callbacks.run(ModelEvent::AfterSave, &mut model);
565
566 assert_eq!(result, CallbackResult::Continue);
567 assert_eq!(model.log, vec!["first", "second"]);
568 }
569
570 #[test]
571 fn before_save_halt_prevents_later_before_callbacks() {
572 let mut callbacks = ModelCallbacks::new();
573 callbacks.add(
574 ModelEvent::BeforeSave,
575 Callback::before("halt", |model: &mut TestModel| {
576 model.push("halt");
577 CallbackResult::Halt
578 }),
579 );
580 callbacks.add(
581 ModelEvent::BeforeSave,
582 Callback::before("later", |model: &mut TestModel| {
583 model.push("later");
584 CallbackResult::Continue
585 }),
586 );
587
588 let mut model = TestModel::default();
589 let result = callbacks.run(ModelEvent::BeforeSave, &mut model);
590
591 assert_eq!(result, CallbackResult::Halt);
592 assert_eq!(model.log, vec!["halt"]);
593 }
594
595 #[test]
596 fn around_callbacks_wrap_default_action() {
597 let mut callbacks = ModelCallbacks::new();
598 callbacks.add(
599 ModelEvent::BeforeValidation,
600 Callback::around("wrap", |model: &mut TestModel, next| {
601 model.push("around-before");
602 let result = next(model);
603 model.push("around-after");
604 result
605 }),
606 );
607
608 let mut model = TestModel::default();
609 let result = callbacks.run(ModelEvent::BeforeValidation, &mut model);
610
611 assert_eq!(result, CallbackResult::Continue);
612 assert_eq!(model.log, vec!["around-before", "around-after"]);
613 }
614
615 #[test]
616 fn around_callback_can_halt_before_inner_action() {
617 let mut callbacks = ModelCallbacks::new();
618 callbacks.add(
619 ModelEvent::BeforeValidation,
620 Callback::around("halt", |model: &mut TestModel, _next| {
621 model.push("around-only");
622 CallbackResult::Halt
623 }),
624 );
625
626 let mut model = TestModel::default();
627 let result = callbacks.run(ModelEvent::BeforeValidation, &mut model);
628
629 assert_eq!(result, CallbackResult::Halt);
630 assert_eq!(model.log, vec!["around-only"]);
631 }
632
633 #[test]
634 fn conditional_only_matches_event_name() {
635 let mut callbacks = ModelCallbacks::new();
636 callbacks.add(
637 ModelEvent::BeforeSave,
638 Callback::before("only_save", |model: &mut TestModel| {
639 model.push("only_save");
640 CallbackResult::Continue
641 })
642 .with_conditions(
643 rustrails_support::callbacks::CallbackConditions::new().only(["before_save"]),
644 ),
645 );
646
647 let mut model = TestModel::default();
648 let result = callbacks.run(ModelEvent::BeforeSave, &mut model);
649
650 assert_eq!(result, CallbackResult::Continue);
651 assert_eq!(model.log, vec!["only_save"]);
652 }
653
654 #[test]
655 fn conditional_except_skips_matching_event() {
656 let mut callbacks = ModelCallbacks::new();
657 callbacks.add(
658 ModelEvent::BeforeSave,
659 Callback::before("skip_save", |model: &mut TestModel| {
660 model.push("skip_save");
661 CallbackResult::Continue
662 })
663 .with_conditions(
664 rustrails_support::callbacks::CallbackConditions::new().except(["before_save"]),
665 ),
666 );
667
668 let mut model = TestModel::default();
669 let result = callbacks.run(ModelEvent::BeforeSave, &mut model);
670
671 assert_eq!(result, CallbackResult::Continue);
672 assert!(model.log.is_empty());
673 }
674
675 #[test]
676 fn conditional_if_runs_for_allowed_model() {
677 let mut callbacks = ModelCallbacks::new();
678 callbacks.add(
679 ModelEvent::BeforeSave,
680 Callback::before("if_allowed", |model: &mut TestModel| {
681 model.push("if_allowed");
682 CallbackResult::Continue
683 })
684 .with_conditions(
685 rustrails_support::callbacks::CallbackConditions::new().if_cond(|target| {
686 target
687 .downcast_ref::<TestModel>()
688 .map(|model| model.allow)
689 .unwrap_or(false)
690 }),
691 ),
692 );
693
694 let mut model = TestModel {
695 allow: true,
696 ..TestModel::default()
697 };
698 let result = callbacks.run(ModelEvent::BeforeSave, &mut model);
699
700 assert_eq!(result, CallbackResult::Continue);
701 assert_eq!(model.log, vec!["if_allowed"]);
702 }
703
704 #[test]
705 fn conditional_if_skips_for_disallowed_model() {
706 let mut callbacks = ModelCallbacks::new();
707 callbacks.add(
708 ModelEvent::BeforeSave,
709 Callback::before("if_allowed", |model: &mut TestModel| {
710 model.push("if_allowed");
711 CallbackResult::Continue
712 })
713 .with_conditions(
714 rustrails_support::callbacks::CallbackConditions::new().if_cond(|target| {
715 target
716 .downcast_ref::<TestModel>()
717 .map(|model| model.allow)
718 .unwrap_or(false)
719 }),
720 ),
721 );
722
723 let mut model = TestModel::default();
724 let result = callbacks.run(ModelEvent::BeforeSave, &mut model);
725
726 assert_eq!(result, CallbackResult::Continue);
727 assert!(model.log.is_empty());
728 }
729
730 #[test]
731 fn conditional_unless_skips_blocked_model() {
732 let mut callbacks = ModelCallbacks::new();
733 callbacks.add(
734 ModelEvent::BeforeSave,
735 Callback::before("unless_blocked", |model: &mut TestModel| {
736 model.push("unless_blocked");
737 CallbackResult::Continue
738 })
739 .with_conditions(
740 rustrails_support::callbacks::CallbackConditions::new().unless_cond(|target| {
741 target
742 .downcast_ref::<TestModel>()
743 .map(|model| model.blocked)
744 .unwrap_or(false)
745 }),
746 ),
747 );
748
749 let mut model = TestModel {
750 blocked: true,
751 ..TestModel::default()
752 };
753 let result = callbacks.run(ModelEvent::BeforeSave, &mut model);
754
755 assert_eq!(result, CallbackResult::Continue);
756 assert!(model.log.is_empty());
757 }
758
759 #[test]
760 fn conditional_unless_runs_when_not_blocked() {
761 let mut callbacks = ModelCallbacks::new();
762 callbacks.add(
763 ModelEvent::BeforeSave,
764 Callback::before("unless_blocked", |model: &mut TestModel| {
765 model.push("unless_blocked");
766 CallbackResult::Continue
767 })
768 .with_conditions(
769 rustrails_support::callbacks::CallbackConditions::new().unless_cond(|target| {
770 target
771 .downcast_ref::<TestModel>()
772 .map(|model| model.blocked)
773 .unwrap_or(false)
774 }),
775 ),
776 );
777
778 let mut model = TestModel::default();
779 let result = callbacks.run(ModelEvent::BeforeSave, &mut model);
780
781 assert_eq!(result, CallbackResult::Continue);
782 assert_eq!(model.log, vec!["unless_blocked"]);
783 }
784
785 #[test]
786 fn reset_clears_one_event_without_touching_other_events() {
787 let mut callbacks = ModelCallbacks::new();
788 callbacks.add(
789 ModelEvent::BeforeSave,
790 Callback::before("before_save", |model: &mut TestModel| {
791 model.push("before_save");
792 CallbackResult::Continue
793 }),
794 );
795 callbacks.add(
796 ModelEvent::AfterSave,
797 Callback::after("after_save", |model: &mut TestModel| {
798 model.push("after_save");
799 CallbackResult::Continue
800 }),
801 );
802 callbacks.reset(ModelEvent::BeforeSave);
803
804 let mut model = TestModel::default();
805 assert_eq!(
806 callbacks.run(ModelEvent::BeforeSave, &mut model),
807 CallbackResult::Continue
808 );
809 assert!(model.log.is_empty());
810
811 assert_eq!(
812 callbacks.run(ModelEvent::AfterSave, &mut model),
813 CallbackResult::Continue
814 );
815 assert_eq!(model.log, vec!["after_save"]);
816 }
817
818 #[test]
819 fn skip_missing_callback_is_noop() {
820 let mut callbacks = ModelCallbacks::new();
821 callbacks.add(
822 ModelEvent::BeforeSave,
823 Callback::before("keep", |model: &mut TestModel| {
824 model.push("keep");
825 CallbackResult::Continue
826 }),
827 );
828 callbacks.skip(ModelEvent::BeforeSave, "missing");
829
830 let mut model = TestModel::default();
831 let result = callbacks.run(ModelEvent::BeforeSave, &mut model);
832
833 assert_eq!(result, CallbackResult::Continue);
834 assert_eq!(model.log, vec!["keep"]);
835 }
836
837 #[test]
838 fn add_replaces_existing_callback_with_same_name() {
839 let mut callbacks = ModelCallbacks::new();
840 callbacks.add(
841 ModelEvent::BeforeSave,
842 Callback::before("audit", |model: &mut TestModel| {
843 model.push("first");
844 CallbackResult::Continue
845 }),
846 );
847 callbacks.add(
848 ModelEvent::BeforeSave,
849 Callback::before("audit", |model: &mut TestModel| {
850 model.push("second");
851 CallbackResult::Continue
852 }),
853 );
854
855 let mut model = TestModel::default();
856 let result = callbacks.run(ModelEvent::BeforeSave, &mut model);
857
858 assert_eq!(result, CallbackResult::Continue);
859 assert_eq!(model.log, vec!["second"]);
860 }
861
862 #[test]
863 fn test_complete_callback_chain() {
864 let mut chain = CallbackChain::new("create");
865 chain.set_run_after_on_halt(false);
866 chain.add(Callback::before(
867 "before_create",
868 |model: &mut TestModel| {
869 model.push("before_create");
870 CallbackResult::Continue
871 },
872 ));
873 chain.add(Callback::around(
874 "around_create",
875 |model: &mut TestModel, next| {
876 model.push("before_around_create");
877 let result = next(model);
878 model.push("after_around_create");
879 result
880 },
881 ));
882 chain.add(Callback::after(
883 "final_callback",
884 |model: &mut TestModel| {
885 model.push("final_callback");
886 CallbackResult::Continue
887 },
888 ));
889 chain.add(Callback::after("after_create", |model: &mut TestModel| {
890 model.push("after_create");
891 CallbackResult::Continue
892 }));
893
894 let mut model = TestModel::default();
895 let result = chain.run_with(&mut model, "create", |model| {
896 model.push("create");
897 CallbackResult::Continue
898 });
899
900 assert_eq!(result, CallbackResult::Continue);
901 assert_eq!(
902 model.log,
903 vec![
904 "before_create",
905 "before_around_create",
906 "create",
907 "after_around_create",
908 "final_callback",
909 "after_create",
910 ]
911 );
912 }
913
914 #[test]
915 fn test_the_callback_chain_is_not_halted_when_around_or_after_callbacks_return_false() {
916 let mut chain = CallbackChain::new("create");
917 chain.set_run_after_on_halt(false);
918 chain.add(Callback::before(
919 "before_create",
920 |model: &mut TestModel| {
921 model.push("before_create");
922 CallbackResult::Continue
923 },
924 ));
925 chain.add(Callback::around(
926 "around_create",
927 |model: &mut TestModel, next| {
928 model.push("before_around_create");
929 let _ = next(model);
930 model.push("after_around_create");
931 CallbackResult::Continue
932 },
933 ));
934 chain.add(Callback::after(
935 "final_callback",
936 |model: &mut TestModel| {
937 model.push("final_callback");
938 CallbackResult::Continue
939 },
940 ));
941 chain.add(Callback::after("after_create", |model: &mut TestModel| {
942 model.push("after_create");
943 CallbackResult::Halt
944 }));
945
946 let mut model = TestModel::default();
947 let result = chain.run_with(&mut model, "create", |model| {
948 model.push("create");
949 CallbackResult::Continue
950 });
951
952 assert_eq!(result, CallbackResult::Continue);
953 assert_eq!(model.log.last(), Some(&"after_create"));
954 }
955
956 #[test]
957 fn test_the_callback_chain_is_not_halted_when_a_before_callback_returns_false() {
958 let mut chain = CallbackChain::new("create");
959 chain.set_run_after_on_halt(false);
960 chain.add(Callback::before(
961 "before_create",
962 |model: &mut TestModel| {
963 model.push("before_create");
964 CallbackResult::Continue
965 },
966 ));
967 chain.add(Callback::after(
968 "final_callback",
969 |model: &mut TestModel| {
970 model.push("final_callback");
971 CallbackResult::Continue
972 },
973 ));
974
975 let mut model = TestModel::default();
976 let result = chain.run_with(&mut model, "create", |model| {
977 model.push("create");
978 CallbackResult::Continue
979 });
980
981 assert_eq!(result, CallbackResult::Continue);
982 assert_eq!(model.log, vec!["before_create", "create", "final_callback"]);
983 }
984
985 #[test]
986 fn test_the_callback_chain_is_halted_when_a_callback_throws_abort() {
987 let mut chain = CallbackChain::new("create");
988 chain.set_run_after_on_halt(false);
989 chain.add(Callback::before(
990 "before_create",
991 |model: &mut TestModel| {
992 model.push("before_create");
993 CallbackResult::Halt
994 },
995 ));
996 chain.add(Callback::around(
997 "around_create",
998 |model: &mut TestModel, next| {
999 model.push("before_around_create");
1000 next(model)
1001 },
1002 ));
1003
1004 let mut model = TestModel::default();
1005 let result = chain.run_with(&mut model, "create", |model| {
1006 model.push("create");
1007 CallbackResult::Continue
1008 });
1009
1010 assert_eq!(result, CallbackResult::Halt);
1011 assert_eq!(model.log, vec!["before_create"]);
1012 }
1013
1014 #[test]
1015 fn test_after_callbacks_are_not_executed_if_the_block_returns_false() {
1016 let mut chain = CallbackChain::new("create");
1017 chain.set_run_after_on_halt(false);
1018 chain.add(Callback::before(
1019 "before_create",
1020 |model: &mut TestModel| {
1021 model.push("before_create");
1022 CallbackResult::Continue
1023 },
1024 ));
1025 chain.add(Callback::around(
1026 "around_create",
1027 |model: &mut TestModel, next| {
1028 model.push("before_around_create");
1029 let result = next(model);
1030 model.push("after_around_create");
1031 result
1032 },
1033 ));
1034 chain.add(Callback::after("after_create", |model: &mut TestModel| {
1035 model.push("after_create");
1036 CallbackResult::Continue
1037 }));
1038
1039 let mut model = TestModel::default();
1040 let result = chain.run_with(&mut model, "create", |model| {
1041 model.push("create");
1042 CallbackResult::Halt
1043 });
1044
1045 assert_eq!(result, CallbackResult::Halt);
1046 assert_eq!(
1047 model.log,
1048 vec![
1049 "before_create",
1050 "before_around_create",
1051 "create",
1052 "after_around_create",
1053 ]
1054 );
1055 }
1056
1057 #[test]
1058 #[ignore = "Rails-specific: define_model_callbacks generates class-level before/around/after helpers that ModelCallbacks does not expose"]
1059 fn test_only_selects_which_types_of_callbacks_should_be_created() {}
1060
1061 #[test]
1062 #[ignore = "Rails-specific: define_model_callbacks array filtering is part of the callback DSL, not the runtime registry"]
1063 fn test_only_selects_which_types_of_callbacks_should_be_created_from_an_array_list() {}
1064
1065 #[test]
1066 #[ignore = "Rails-specific: define_model_callbacks with only: [] produces no helper methods, which ModelCallbacks does not generate"]
1067 fn test_no_callbacks_should_be_created() {}
1068
1069 #[test]
1070 fn test_the_if_option_array_should_not_be_mutated_by_an_after_callback() {
1071 let only = vec!["create".to_string()];
1072 let conditions = CallbackConditions::new().only(only.iter().map(String::as_str));
1073 let mut chain = CallbackChain::new("create");
1074 chain.set_run_after_on_halt(false);
1075 chain.add(
1076 Callback::after("after_create", |model: &mut TestModel| {
1077 model.push("after_create");
1078 CallbackResult::Continue
1079 })
1080 .with_conditions(conditions),
1081 );
1082
1083 let mut model = TestModel::default();
1084 let result = chain.run_with(&mut model, "create", |model| {
1085 model.push("create");
1086 CallbackResult::Continue
1087 });
1088
1089 assert_eq!(result, CallbackResult::Continue);
1090 assert_eq!(only, vec!["create".to_string()]);
1091 assert_eq!(model.log, vec!["create", "after_create"]);
1092 }
1093
1094 #[test]
1095 #[ignore = "Rails-specific: the Rust callback registry registers one callback per add call instead of supporting varargs DSL declarations"]
1096 fn test_after_create_callbacks_with_both_callbacks_declared_in_one_line() {}
1097
1098 #[test]
1099 fn test_after_create_callbacks_with_both_callbacks_declared_in_different_lines() {
1100 let mut callbacks = ModelCallbacks::new();
1101 callbacks.add(
1102 ModelEvent::AfterCreate,
1103 Callback::after("callback1", |model: &mut TestModel| {
1104 model.push("callback1");
1105 CallbackResult::Continue
1106 }),
1107 );
1108 callbacks.add(
1109 ModelEvent::AfterCreate,
1110 Callback::after("callback2", |model: &mut TestModel| {
1111 model.push("callback2");
1112 CallbackResult::Continue
1113 }),
1114 );
1115
1116 let mut model = TestModel::default();
1117 let result = callbacks.run(ModelEvent::AfterCreate, &mut model);
1118
1119 assert_eq!(result, CallbackResult::Continue);
1120 assert_eq!(model.log, vec!["callback1", "callback2"]);
1121 }
1122}