use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::StateUpdate;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ControlFlow {
Goto(String),
Continue,
Return,
Send(Vec<SendCommand>),
}
impl Default for ControlFlow {
fn default() -> Self {
Self::Continue
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Command {
pub updates: Vec<StateUpdate>,
pub control: ControlFlow,
}
impl Command {
pub fn new() -> Self {
Self::default()
}
pub fn update(mut self, key: impl Into<String>, value: Value) -> Self {
self.updates.push(StateUpdate::new(key, value));
self
}
pub fn updates(mut self, updates: Vec<StateUpdate>) -> Self {
self.updates.extend(updates);
self
}
pub fn continue_(mut self) -> Self {
self.control = ControlFlow::Continue;
self
}
pub fn goto(mut self, node: impl Into<String>) -> Self {
self.control = ControlFlow::Goto(node.into());
self
}
pub fn return_(mut self) -> Self {
self.control = ControlFlow::Return;
self
}
pub fn send(targets: Vec<SendCommand>) -> Self {
Self {
updates: Vec::new(),
control: ControlFlow::Send(targets),
}
}
pub fn just_update(key: impl Into<String>, value: Value) -> Self {
Self::new().update(key, value)
}
pub fn just_goto(node: impl Into<String>) -> Self {
Self::new().goto(node)
}
pub fn just_return() -> Self {
Self::new().return_()
}
pub fn is_return(&self) -> bool {
matches!(self.control, ControlFlow::Return)
}
pub fn is_send(&self) -> bool {
matches!(self.control, ControlFlow::Send(_))
}
pub fn goto_target(&self) -> Option<&str> {
match &self.control {
ControlFlow::Goto(target) => Some(target),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SendCommand {
pub target: String,
pub input: Value,
pub branch_id: Option<String>,
}
impl SendCommand {
pub fn new(target: impl Into<String>, input: Value) -> Self {
Self {
target: target.into(),
input,
branch_id: None,
}
}
pub fn with_branch(
target: impl Into<String>,
input: Value,
branch_id: impl Into<String>,
) -> Self {
Self {
target: target.into(),
input,
branch_id: Some(branch_id.into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_command_builder() {
let cmd = Command::new()
.update("key1", json!("value1"))
.update("key2", json!(42))
.goto("next_node");
assert_eq!(cmd.updates.len(), 2);
assert_eq!(cmd.updates[0].key, "key1");
assert_eq!(cmd.goto_target(), Some("next_node"));
}
#[test]
fn test_command_continue() {
let cmd = Command::new().update("result", json!("done")).continue_();
assert_eq!(cmd.control, ControlFlow::Continue);
assert!(!cmd.is_return());
}
#[test]
fn test_command_return() {
let cmd = Command::new().update("final", json!("result")).return_();
assert!(cmd.is_return());
}
#[test]
fn test_command_send() {
let cmd = Command::send(vec![
SendCommand::new("worker", json!({"task": 1})),
SendCommand::new("worker", json!({"task": 2})),
]);
assert!(cmd.is_send());
if let ControlFlow::Send(targets) = &cmd.control {
assert_eq!(targets.len(), 2);
} else {
panic!("Expected Send control flow");
}
}
#[test]
fn test_send_command() {
let send = SendCommand::new("process", json!({"data": "test"}));
assert_eq!(send.target, "process");
assert!(send.branch_id.is_none());
let send_with_branch =
SendCommand::with_branch("process", json!({"data": "test"}), "branch-1");
assert_eq!(send_with_branch.branch_id, Some("branch-1".to_string()));
}
#[test]
fn test_just_helpers() {
let cmd = Command::just_update("key", json!("value"));
assert_eq!(cmd.updates.len(), 1);
assert_eq!(cmd.control, ControlFlow::Continue);
let cmd = Command::just_goto("target");
assert!(cmd.updates.is_empty());
assert_eq!(cmd.goto_target(), Some("target"));
let cmd = Command::just_return();
assert!(cmd.is_return());
}
}