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