use crate::goto::Goto;
use crate::node::NodeOut;
use crate::state::GraphState;
#[derive(Debug, Clone)]
pub struct Command<S: GraphState> {
update: S::Update,
goto: Goto,
payload: Option<serde_json::Value>,
}
impl<S: GraphState> Command<S> {
pub fn new(update: S::Update) -> Self {
Self {
update,
goto: Goto::End,
payload: None,
}
}
pub fn goto_only(goto: Goto) -> Self
where
S::Update: Default,
{
Self {
update: S::Update::default(),
goto,
payload: None,
}
}
pub fn goto(mut self, name: impl Into<String>) -> Self {
self.goto = Goto::Node(name.into());
self
}
pub fn goto_multiple<I, N>(mut self, names: I) -> Self
where
I: IntoIterator<Item = N>,
N: Into<String>,
{
self.goto = Goto::Multiple(names.into_iter().map(Into::into).collect());
self
}
pub fn send<I, N>(mut self, targets: I) -> Self
where
I: IntoIterator<Item = (N, serde_json::Value)>,
N: Into<String>,
{
self.goto = Goto::Send(targets.into_iter().map(|(n, p)| (n.into(), p)).collect());
self
}
pub fn end(mut self) -> Self {
self.goto = Goto::End;
self
}
pub fn with_payload(mut self, payload: serde_json::Value) -> Self {
match std::mem::replace(&mut self.goto, Goto::End) {
Goto::Node(name) => {
self.goto = Goto::Send(vec![(name, payload)]);
}
other @ Goto::End => {
self.goto = other;
}
other => {
self.goto = other;
self.payload = Some(payload);
}
}
self
}
}
impl<S: GraphState> From<Command<S>> for NodeOut<S> {
fn from(c: Command<S>) -> Self {
if c.payload.is_some() && !matches!(c.goto, Goto::End) {
tracing::debug!(
"Command::with_payload set on a non-Node goto; payload ignored \
(use Command::send for per-target payloads)"
);
}
NodeOut {
update: c.update,
goto: c.goto,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Default, Clone, PartialEq)]
struct S {
n: u32,
}
#[derive(Debug, Default, Clone)]
struct SU {
n: u32,
}
impl GraphState for S {
type Update = SU;
fn apply(&mut self, u: Self::Update) {
self.n += u.n;
}
}
#[test]
fn defaults_to_end() {
let cmd: Command<S> = Command::new(SU { n: 1 });
let out: NodeOut<S> = cmd.into();
assert!(matches!(out.goto, Goto::End));
assert_eq!(out.update.n, 1);
}
#[test]
fn goto_routes_single() {
let cmd: Command<S> = Command::new(SU { n: 0 }).goto("next");
let out: NodeOut<S> = cmd.into();
assert_eq!(out.goto, Goto::Node("next".into()));
}
#[test]
fn with_payload_promotes_node_to_send() {
let cmd: Command<S> = Command::new(SU { n: 0 })
.goto("worker")
.with_payload(serde_json::json!({"x": 1}));
let out: NodeOut<S> = cmd.into();
if let Goto::Send(t) = out.goto {
assert_eq!(t.len(), 1);
assert_eq!(t[0].0, "worker");
assert_eq!(t[0].1["x"], 1);
} else {
panic!("expected Send");
}
}
#[test]
fn send_with_multiple_targets() {
let cmd: Command<S> = Command::new(SU { n: 0 }).send([
("a", serde_json::json!({"i": 0})),
("b", serde_json::json!({"i": 1})),
]);
let out: NodeOut<S> = cmd.into();
if let Goto::Send(t) = out.goto {
assert_eq!(t.len(), 2);
} else {
panic!("expected Send");
}
}
#[test]
fn goto_only_skips_update() {
let cmd: Command<S> = Command::goto_only(Goto::node("x"));
let out: NodeOut<S> = cmd.into();
assert_eq!(out.goto, Goto::Node("x".into()));
assert_eq!(out.update.n, 0);
}
}