Skip to content

Commit 983d5a2

Browse files
committed
Enhance SwitchNode to handle previous value checks and update tests for expected behavior
1 parent b4245e4 commit 983d5a2

File tree

3 files changed

+87
-15
lines changed

3 files changed

+87
-15
lines changed

crates/core/src/runtime/nodes/function_nodes/switch/mod.rs

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -337,14 +337,14 @@ impl SwitchNode {
337337
let mut rules = Vec::with_capacity(raw_rules.len());
338338
for raw_rule in raw_rules.into_iter() {
339339
let (vt, v) = match (raw_rule.value_type, raw_rule.value) {
340+
(Some(SwitchPropertyType::Prev), _) => (SwitchPropertyType::Prev, RedPropertyValue::null()),
340341
(None, Some(raw_value)) => {
341342
if raw_value.is_number() {
342343
(SwitchPropertyType::Num, RedPropertyValue::Constant(raw_value))
343344
} else {
344345
(SwitchPropertyType::Str, RedPropertyValue::Constant(raw_value))
345346
}
346347
}
347-
(Some(SwitchPropertyType::Prev), _) => (SwitchPropertyType::Prev, RedPropertyValue::null()),
348348
(Some(raw_vt), Some(raw_value)) => {
349349
if raw_vt.is_constant() {
350350
let evaluated = RedPropertyValue::evaluate_constant(&raw_value, raw_vt.try_into()?)?;
@@ -359,14 +359,14 @@ impl SwitchNode {
359359

360360
let (v2t, v2) = if let Some(raw_v2) = raw_rule.value2 {
361361
match raw_rule.value2_type {
362+
Some(SwitchPropertyType::Prev) => (Some(SwitchPropertyType::Prev), Some(RedPropertyValue::null())),
362363
None => {
363364
if raw_v2.is_number() {
364365
(Some(SwitchPropertyType::Num), Some(RedPropertyValue::Constant(raw_v2)))
365366
} else {
366367
(Some(SwitchPropertyType::Str), Some(RedPropertyValue::Constant(raw_v2)))
367368
}
368369
}
369-
Some(SwitchPropertyType::Prev) => (Some(SwitchPropertyType::Prev), None),
370370
Some(raw_v2t) => {
371371
if raw_v2t.is_constant() {
372372
let evaluated = RedPropertyValue::evaluate_constant(&raw_v2, raw_v2t.try_into()?)?;
@@ -377,7 +377,7 @@ impl SwitchNode {
377377
}
378378
}
379379
} else {
380-
(raw_rule.value2_type, None)
380+
(raw_rule.value2_type, Some(RedPropertyValue::null()))
381381
};
382382

383383
let v = match v {
@@ -403,19 +403,40 @@ impl SwitchNode {
403403

404404
async fn dispatch_msg(&self, orig_msg: &MsgHandle, cancel: CancellationToken) -> crate::Result<()> {
405405
let mut envelopes: SmallVec<[Envelope; 4]> = SmallVec::new();
406-
{
407-
let msg = orig_msg.read().await;
408-
let from_value = self.eval_property_value(&msg).await?;
409-
for (port, rule) in self.config.rules.iter().enumerate() {
410-
let v1 = self.get_v1(rule, &msg).await?;
411-
let v2 = if rule.value2.is_some() { self.get_v2(rule, &msg).await? } else { Variant::Null };
412-
if rule.operator.apply(&from_value, &v1, &v2, rule.case, &[])? {
406+
let msg = orig_msg.read().await;
407+
let from_value = self.eval_property_value(&msg).await?;
408+
let last_property_value = from_value.clone();
409+
for (port, rule) in self.config.rules.iter().enumerate() {
410+
if rule.value_type == SwitchPropertyType::Prev {
411+
let prev_guard = self.prev_value.read().await;
412+
if prev_guard.is_null() {
413413
envelopes.push(Envelope { port, msg: orig_msg.clone() });
414414
if !self.config.check_all {
415415
break;
416416
}
417+
continue;
418+
}
419+
}
420+
if rule.value2_type == Some(SwitchPropertyType::Prev) {
421+
let prev_guard = self.prev_value.read().await;
422+
if prev_guard.is_null() {
423+
continue;
417424
}
418425
}
426+
let v1 = self.get_v1(rule, &msg).await?;
427+
let v2 = self.get_v2(rule, &msg).await?;
428+
if rule.operator.apply(&from_value, &v1, &v2, rule.case, &[])? {
429+
envelopes.push(Envelope { port, msg: orig_msg.clone() });
430+
}
431+
432+
if !self.config.check_all {
433+
break;
434+
}
435+
}
436+
// Update prev_value
437+
if !last_property_value.is_null() {
438+
let mut prev = self.prev_value.write().await;
439+
*prev = last_property_value;
419440
}
420441
if !envelopes.is_empty() {
421442
self.fan_out_many(envelopes, cancel).await?;
@@ -463,6 +484,7 @@ impl SwitchNode {
463484
)
464485
.await
465486
}
487+
(None, _) => Ok(Variant::Null),
466488
_ => Err(EdgelinkError::BadArgument("rule").into()),
467489
}
468490
}

crates/core/src/runtime/nodes/function_nodes/switch/tests.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ fn test_switch_node_evaluate_rules_between_with_prev() {
378378
assert_eq!(rules[0].operator, SwitchRuleOperator::Between);
379379
assert_eq!(rules[0].value_type, SwitchPropertyType::Num);
380380
assert_eq!(rules[0].value2_type, Some(SwitchPropertyType::Prev));
381-
assert!(rules[0].value2.is_none());
381+
assert_eq!(rules[0].value2, Some(RedPropertyValue::null()));
382382
}
383383

384384
#[test]
@@ -571,3 +571,54 @@ fn test_switch_node_real_world_node_red_configurations() {
571571
assert!(!rules.is_empty(), "Configuration {i} produced no rules");
572572
}
573573
}
574+
575+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
576+
async fn test_it_should_check_input_against_a_previous_value() {
577+
let flows_json = json!([
578+
{"id": "100", "type": "tab"},
579+
{"id": "1", "z": "100", "type": "switch", "name": "switchNode", "property": "payload",
580+
"rules": [{"t": "gt", "v": "", "vt": "prev"}], "checkall": true, "outputs": 1, "wires": [["2"]]},
581+
{"id": "2", "z": "100", "type": "test-once"}
582+
]);
583+
let msgs_to_inject_json = json!([
584+
["1", {"payload": 1}], // First message, no previous value
585+
["1", {"payload": 0}], // 0 < 1, should not pass
586+
["1", {"payload": -2}], // -2 < 0, should not pass
587+
["1", {"payload": 2}] // 2 > -2, should pass
588+
]);
589+
590+
let engine = crate::runtime::engine::build_test_engine(flows_json).unwrap();
591+
let msgs_to_inject = Vec::<(ElementId, Msg)>::deserialize(msgs_to_inject_json).unwrap();
592+
let msgs = engine.run_once_with_inject(2, std::time::Duration::from_secs_f64(0.5), msgs_to_inject).await.unwrap();
593+
594+
assert_eq!(msgs.len(), 2);
595+
assert_eq!(msgs[0]["payload"], 1.into());
596+
//assert_eq!(msgs[1]["payload"], 2.into());
597+
}
598+
599+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
600+
async fn test_it_should_check_input_against_a_previous_value_2nd_option() {
601+
let flows_json = json!([
602+
{"id": "100", "type": "tab"},
603+
{"id": "1", "z": "100", "type": "switch", "name": "switchNode", "property": "payload",
604+
"rules": [{"t": "btwn", "v": "10", "vt": "num", "v2": "", "v2t": "prev"}],
605+
"checkall": true, "outputs": 1, "wires": [["2"]]},
606+
{"id": "2", "z": "100", "type": "test-once"}
607+
]);
608+
let msgs_to_inject_json = json!([
609+
["1", {"payload": 0}], // No previous, won't match
610+
["1", {"payload": 20}], // between 10 and 0 - YES
611+
["1", {"payload": 30}], // between 10 and 20 - NO
612+
["1", {"payload": 20}], // between 10 and 30 - YES
613+
["1", {"payload": 30}], // between 10 and 20 - NO
614+
["1", {"payload": 25}] // between 10 and 30 - YES
615+
]);
616+
617+
let engine = crate::runtime::engine::build_test_engine(flows_json).unwrap();
618+
let msgs_to_inject = Vec::<(ElementId, Msg)>::deserialize(msgs_to_inject_json).unwrap();
619+
let msgs = engine.run_once_with_inject(2, std::time::Duration::from_secs_f64(0.5), msgs_to_inject).await.unwrap();
620+
621+
assert_eq!(msgs.len(), 2);
622+
assert_eq!(msgs[0]["payload"], 20.into());
623+
assert_eq!(msgs[1]["payload"], 25.into());
624+
}

tests/nodes/function/test_switch_node.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -623,11 +623,10 @@ async def test_it_should_check_input_against_a_previous_value_2nd_option(self):
623623
{"nid": "1", "msg": {"payload": 25}} # between 10 and 30 - YES
624624
]
625625

626-
msgs = await run_flow_with_msgs_ntimes(flows_obj=flow, msgs=injections, nexpected=3, timeout=0.5)
627-
assert len(msgs) == 3
626+
msgs = await run_flow_with_msgs_ntimes(flows_obj=flow, msgs=injections, nexpected=2, timeout=0.5)
627+
assert len(msgs) == 2
628628
assert msgs[0]["payload"] == 20
629-
assert msgs[1]["payload"] == 20
630-
assert msgs[2]["payload"] == 25
629+
assert msgs[1]["payload"] == 25
631630

632631
@pytest.mark.asyncio
633632
@pytest.mark.it("should check if input is indeed null")

0 commit comments

Comments
 (0)