Skip to main content

juncture_core/interrupt/
context.rs

1use crate::interrupt::InterruptSignal;
2use std::sync::Arc;
3use std::sync::atomic::{AtomicUsize, Ordering};
4use tokio::sync::mpsc;
5
6/// Interrupt context for managing human-in-the-flow interactions
7///
8/// The context tracks interrupt state across node executions, enabling
9/// resumption with human-provided values.
10///
11/// # Examples
12///
13/// ```ignore
14/// use juncture_core::interrupt::InterruptContext;
15/// use serde_json::json;
16///
17/// let mut context = InterruptContext::new(
18///     vec![Some(json!("human_input"))],
19///     tokio::sync::mpsc::unbounded_channel(),
20/// );
21///
22/// // Get next interrupt index (increments counter)
23/// let index = context.next_index();
24/// ```
25#[derive(Clone, Debug)]
26pub struct InterruptContext {
27    /// Resume values indexed by interrupt position
28    resume_values: Arc<[Option<serde_json::Value>]>,
29
30    /// Current interrupt index counter
31    current_index: Arc<AtomicUsize>,
32
33    /// Channel for sending interrupt signals
34    interrupt_tx: mpsc::UnboundedSender<InterruptSignal>,
35}
36
37impl InterruptContext {
38    /// Create a new interrupt context
39    ///
40    /// # Arguments
41    ///
42    /// * `resume_values` - Values to resume interrupts with (indexed by position)
43    /// * `interrupt_tx` - Channel for sending interrupt signals
44    #[must_use]
45    pub fn new(
46        resume_values: Vec<Option<serde_json::Value>>,
47        interrupt_tx: mpsc::UnboundedSender<InterruptSignal>,
48    ) -> Self {
49        Self {
50            resume_values: resume_values.into_boxed_slice().into(),
51            current_index: Arc::new(AtomicUsize::new(0)),
52            interrupt_tx,
53        }
54    }
55
56    /// Get the next interrupt index (atomically increments counter)
57    #[must_use]
58    pub fn next_index(&self) -> usize {
59        self.current_index.fetch_add(1, Ordering::Relaxed)
60    }
61
62    /// Get resume value for a given index
63    ///
64    /// Returns `None` if no resume value exists for this index.
65    #[must_use]
66    pub fn get_resume_value(&self, index: usize) -> Option<serde_json::Value> {
67        self.resume_values
68            .get(index)
69            .and_then(std::clone::Clone::clone)
70    }
71
72    /// Get the current index without incrementing
73    #[must_use]
74    pub fn current_index(&self) -> usize {
75        self.current_index.load(Ordering::Relaxed)
76    }
77
78    /// Send an interrupt signal
79    ///
80    /// # Errors
81    ///
82    /// Returns an error if the interrupt channel is closed.
83    pub fn send_interrupt(
84        &self,
85        signal: InterruptSignal,
86    ) -> Result<(), mpsc::error::SendError<InterruptSignal>> {
87        self.interrupt_tx.send(signal)
88    }
89}
90
91// Rust guideline compliant 2025-01-18