use std::collections::VecDeque;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use specta::Type;
use super::blackboard::BoundOutputs;
use super::messages::LifecycleCorrelation;
use super::types::NodeExecutionId;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Type, JsonSchema)]
pub(super) enum TerminalStatus {
Success,
Failure,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Type, JsonSchema)]
pub(super) struct TerminalResult {
pub(super) correlation: LifecycleCorrelation,
pub(super) status: TerminalStatus,
pub(super) outputs: BoundOutputs,
pub(super) state: Value,
pub(super) error: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Type, JsonSchema)]
pub(super) struct TokenExpired {
pub(super) correlation: LifecycleCorrelation,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Type, JsonSchema)]
pub(super) enum MailboxMessage {
Terminal(TerminalResult),
TokenExpired(TokenExpired),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(super) enum MailboxResolution {
Pending,
Terminal(Box<TerminalResult>),
Expired,
}
#[derive(Clone, Debug, Default)]
pub(super) struct NodeMailbox {
messages: VecDeque<MailboxMessage>,
}
impl NodeMailbox {
pub(super) fn snapshot(&self) -> Vec<MailboxMessage> {
self.messages.iter().cloned().collect()
}
pub(super) fn push(&mut self, message: MailboxMessage) {
self.messages.push_back(message);
}
pub(super) fn resolve(&mut self, correlation: LifecycleCorrelation) -> MailboxResolution {
let position = self.messages.iter().position(|message| match message {
MailboxMessage::Terminal(result) => result.correlation == correlation,
MailboxMessage::TokenExpired(expired) => expired.correlation == correlation,
});
let Some(position) = position else {
return MailboxResolution::Pending;
};
let message = self
.messages
.remove(position)
.expect("mailbox position exists");
match message {
MailboxMessage::Terminal(result) => MailboxResolution::Terminal(Box::new(result)),
MailboxMessage::TokenExpired(_) => MailboxResolution::Expired,
}
}
pub(super) fn discard(&mut self, node_execution_id: NodeExecutionId) {
self.messages.retain(|message| match message {
MailboxMessage::Terminal(result) => result.correlation.node_execution_id != node_execution_id,
MailboxMessage::TokenExpired(expired) => expired.correlation.node_execution_id != node_execution_id,
});
}
}
#[cfg(test)]
mod tests {
use super::super::{FlowExecutionId, FlowId, FlowNodeId, NodeExecutionId, NodeKind};
use super::*;
use uuid::Uuid;
fn correlation() -> LifecycleCorrelation {
LifecycleCorrelation {
node_kind: NodeKind::parse("ScriptNode").expect("static node kind is valid"),
flow_id: FlowId(Uuid::new_v4()),
flow_node_id: FlowNodeId(Uuid::new_v4()),
flow_execution_id: FlowExecutionId(Uuid::new_v4()),
node_execution_id: NodeExecutionId(Uuid::new_v4()),
}
}
#[test]
fn resolves_in_enqueue_order_and_discards_late_signals() {
let correlation = correlation();
let mut mailbox = NodeMailbox::default();
assert_eq!(mailbox.resolve(correlation.clone()), MailboxResolution::Pending);
mailbox.push(MailboxMessage::TokenExpired(TokenExpired {
correlation: correlation.clone(),
}));
mailbox.push(MailboxMessage::Terminal(TerminalResult {
correlation: correlation.clone(),
status: TerminalStatus::Success,
outputs: BoundOutputs::default(),
state: serde_json::Value::Null,
error: None,
}));
assert_eq!(mailbox.resolve(correlation.clone()), MailboxResolution::Expired);
assert!(matches!(
mailbox.resolve(correlation.clone()),
MailboxResolution::Terminal(_)
));
mailbox.push(MailboxMessage::TokenExpired(TokenExpired {
correlation: correlation.clone(),
}));
mailbox.discard(correlation.node_execution_id);
assert_eq!(mailbox.resolve(correlation), MailboxResolution::Pending);
}
}