From 6c6be9d26549f6e512220b6e96fcb03bd7e73098 Mon Sep 17 00:00:00 2001 From: CeerDecy <1748788674@qq.com> Date: Sat, 27 Dec 2025 12:15:00 +0800 Subject: [PATCH] feat: provide detailed condition evaluation results and surface skip reasons --- auto-engine-core/src/types/conditions.rs | 48 ++++++++++++++++++++---- auto-engine-core/src/utils/mod.rs | 30 +++++++++++++++ auto-engine-core/src/workflow/runner.rs | 5 ++- 3 files changed, 73 insertions(+), 10 deletions(-) diff --git a/auto-engine-core/src/types/conditions.rs b/auto-engine-core/src/types/conditions.rs index 071a7fc..eadc784 100644 --- a/auto-engine-core/src/types/conditions.rs +++ b/auto-engine-core/src/types/conditions.rs @@ -12,15 +12,25 @@ pub struct Conditions { pub not_exist: Option, } +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct ConditionResult { + pub pass: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, +} + impl Conditions { - pub async fn check(&self, ctx: &Context) -> Result { + pub async fn check(&self, ctx: &Context) -> Result { if let Some(key) = &self.exist && key != "" { let values = ctx.string_value.read().await; if !values.contains_key(key) { log::info!("{} does not exist, {:?}", key, values); - return Ok(false); + return Ok(ConditionResult { + pass: false, + reason: Some(format!("{} does not exist", key)), + }); } } @@ -30,25 +40,47 @@ impl Conditions { let values = ctx.string_value.read().await; if values.contains_key(key) { log::info!("{} does not exist, {:?}", key, values); - return Ok(false); + return Ok(ConditionResult { + pass: false, + reason: Some(format!("{} does not exist", key)), + }); } } if let Some(condition) = &self.condition && condition != "" { - let condition = utils::parse_variables(ctx, condition).await; - if condition == "" { - return Ok(false); + let result = utils::try_parse_variables(ctx, condition).await; + let condition = match result { + Ok(v) => v, + Err(err) => { + log::error!("{}", err); + return Ok(ConditionResult { + pass: false, + reason: Some(err.to_string()), + }); + } + }; + if condition.trim() == "" { + return Ok(ConditionResult { + pass: false, + reason: Some(condition.clone()), + }); } let result = evalexpr::eval_boolean(&condition) .map_err(|err| format!("{} is not boolean", err))?; if !result { log::info!("{} does not pass condition", condition); - return Ok(false); + return Ok(ConditionResult { + pass: false, + reason: Some(condition.clone()), + }); } } - Ok(true) + Ok(ConditionResult { + pass: false, + reason: None, + }) } } diff --git a/auto-engine-core/src/utils/mod.rs b/auto-engine-core/src/utils/mod.rs index 53ed64a..2ff862e 100644 --- a/auto-engine-core/src/utils/mod.rs +++ b/auto-engine-core/src/utils/mod.rs @@ -26,6 +26,36 @@ pub async fn parse_variables(context: &Context, input: &str) -> String { .into_owned() } +pub async fn try_parse_variables(context: &Context, input: &str) -> Result { + let ctx = context.string_value.read().await; + let mut err: Option = None; + + let result = REGEX_PARSE_VARIABLES.replace_all(input, |caps: ®ex::Captures| { + let var_name = &caps[1]; + + let variable = match ctx.get(var_name) { + Some(value) => serde_json::to_string(value) + .unwrap_or_default() + .trim_matches('"') + .to_string(), + None => { + err = Some(format!("variable `{}` not found", var_name)); + String::new() + } + }; + if variable == "" { + err = Some(format!("variable `{}` is empty", var_name)); + } + variable + }); + + if let Some(e) = err { + Err(e) + } else { + Ok(result.into_owned()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/auto-engine-core/src/workflow/runner.rs b/auto-engine-core/src/workflow/runner.rs index 1ae959a..13ae2cd 100644 --- a/auto-engine-core/src/workflow/runner.rs +++ b/auto-engine-core/src/workflow/runner.rs @@ -257,11 +257,12 @@ fn handle_nod( } if let Some(condition) = node_schema.metadata.conditions { - if !condition.check(&ctx).await? { + let result = condition.check(&ctx).await?; + if !result.pass { emitter .emit( NODE_EVENT, - NodeEventPayload::skip::(node_id.clone(), None), + NodeEventPayload::skip(node_id.clone(), Some(result)), ) .unwrap_or_default(); return Ok(None);