Skip to main content

actionqueue_engine/derive/
once.rs

1//! Once derivation - creates exactly one run for tasks with Once policy.
2//!
3//! The Once derivation ensures that each task with a Once run policy has
4//! exactly one run created. It tracks derived runs to prevent duplicate
5//! creation.
6
7use actionqueue_core::ids::TaskId;
8use actionqueue_core::run::run_instance::RunInstance;
9
10use crate::derive::{map_run_construction_error, DerivationError, DerivationSuccess};
11use crate::time::clock::Clock;
12
13/// Derives exactly one run for a Once policy task.
14///
15/// If at least one run has already been derived, no new run is created.
16pub fn derive_once(
17    clock: &impl Clock,
18    task_id: TaskId,
19    already_derived: u32,
20) -> Result<DerivationSuccess, DerivationError> {
21    let now = clock.now();
22
23    // If we already have one run derived, nothing to do
24    if already_derived >= 1 {
25        return Ok(DerivationSuccess::new(Vec::new(), 1));
26    }
27
28    // Create one scheduled run (ready immediately for Once policy)
29    let run = RunInstance::new_scheduled(
30        task_id, now, // scheduled_at: ready immediately for Once
31        now, // created_at
32    )
33    .map_err(|source| map_run_construction_error(task_id, source))?;
34
35    Ok(DerivationSuccess::new(vec![run], 1))
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn derive_once_creates_one_run() {
44        let clock = crate::time::clock::MockClock::new(1000);
45        let task_id = TaskId::new();
46
47        let result = derive_once(&clock, task_id, 0).unwrap();
48
49        assert_eq!(result.derived().len(), 1);
50        assert_eq!(result.already_derived(), 1);
51        assert_eq!(result.derived()[0].task_id(), task_id);
52        assert_eq!(result.derived()[0].state(), actionqueue_core::run::state::RunState::Scheduled);
53        assert_eq!(result.derived()[0].scheduled_at(), 1000);
54        assert_eq!(result.derived()[0].created_at(), 1000);
55    }
56
57    #[test]
58    fn derive_once_does_not_duplicate() {
59        let clock = crate::time::clock::MockClock::new(1000);
60        let task_id = TaskId::new();
61
62        // First derivation
63        let result1 = derive_once(&clock, task_id, 0).unwrap();
64        assert_eq!(result1.derived().len(), 1);
65
66        // Second derivation should not create another run
67        let result2 = derive_once(&clock, task_id, 1).unwrap();
68
69        assert_eq!(result2.derived().len(), 0);
70        assert_eq!(result2.already_derived(), 1);
71    }
72
73    #[test]
74    fn derive_once_returns_typed_error_for_nil_task_id_instead_of_panicking() {
75        let clock = crate::time::clock::MockClock::new(1000);
76        let task_id = "00000000-0000-0000-0000-000000000000"
77            .parse::<TaskId>()
78            .expect("nil task id literal must parse");
79
80        let result = derive_once(&clock, task_id, 0);
81
82        assert_eq!(result, Err(DerivationError::InvalidTaskIdForRunConstruction { task_id }));
83    }
84}