From 4ef0ee7647c91fcb67b2af398921d05f1048b9ae Mon Sep 17 00:00:00 2001 From: Stefan Date: Fri, 29 Aug 2025 14:37:20 +0200 Subject: [PATCH] temp --- bindings/c/src/custom_node.rs | 2 +- bindings/c/src/languages/go.rs | 2 +- bindings/c/src/languages/native.rs | 2 +- bindings/nodejs/src/custom_node.rs | 2 +- bindings/python/src/custom_node.rs | 2 +- bindings/python/src/types.rs | 2 +- bindings/uniffi/src/custom_node.rs | 2 +- core/engine/src/decision.rs | 2 +- .../src/{handler => decision_graph}/graph.rs | 21 +- core/engine/src/decision_graph/mod.rs | 2 + .../{handler => decision_graph}/traversal.rs | 11 +- core/engine/src/engine.rs | 2 +- core/engine/src/error.rs | 4 +- core/engine/src/handler/decision.rs | 94 -------- core/engine/src/handler/mod.rs | 6 - core/engine/src/lib.rs | 9 +- core/engine/src/nodes/context.rs | 11 +- .../custom/adapter.rs} | 42 ++-- core/engine/src/nodes/custom/mod.rs | 106 ++------ core/engine/src/nodes/decision_table/mod.rs | 2 +- core/engine/src/nodes/definition.rs | 2 +- core/engine/src/nodes/expression/mod.rs | 2 +- core/engine/src/nodes/extensions.rs | 14 +- core/engine/src/nodes/function/mod.rs | 2 +- core/engine/src/nodes/function/v1/mod.rs | 2 +- core/engine/src/nodes/function/v2/mod.rs | 2 +- .../src/nodes/function/v2/module/zen.rs | 2 +- core/engine/src/nodes/input/mod.rs | 2 +- core/engine/src/nodes/mod.rs | 1 + core/engine/src/nodes/output/mod.rs | 2 +- .../src/{handler/node.rs => nodes/result.rs} | 0 core/engine/src/nodes/transform_attributes.rs | 2 +- core/macros/src/to_variable.rs | 119 ++++++++- core/types/src/decision/mod.rs | 227 ++++++++++++++++++ core/types/src/lib.rs | 1 + 35 files changed, 436 insertions(+), 270 deletions(-) rename core/engine/src/{handler => decision_graph}/graph.rs (96%) create mode 100644 core/engine/src/decision_graph/mod.rs rename core/engine/src/{handler => decision_graph}/traversal.rs (96%) delete mode 100644 core/engine/src/handler/decision.rs delete mode 100644 core/engine/src/handler/mod.rs rename core/engine/src/{handler/custom_node_adapter.rs => nodes/custom/adapter.rs} (68%) rename core/engine/src/{handler/node.rs => nodes/result.rs} (100%) create mode 100644 core/types/src/decision/mod.rs diff --git a/bindings/c/src/custom_node.rs b/bindings/c/src/custom_node.rs index e98e2758..fc774e50 100644 --- a/bindings/c/src/custom_node.rs +++ b/bindings/c/src/custom_node.rs @@ -5,7 +5,7 @@ use anyhow::anyhow; use zen_engine::handler::custom_node_adapter::{ CustomNodeAdapter, CustomNodeRequest, NoopCustomNode, }; -use zen_engine::handler::node::{NodeResponse, NodeResult}; +use zen_engine::nodes::result::{NodeResponse, NodeResult}; use crate::languages::native::NativeCustomNode; diff --git a/bindings/c/src/languages/go.rs b/bindings/c/src/languages/go.rs index 1a0541d3..1cbe8131 100644 --- a/bindings/c/src/languages/go.rs +++ b/bindings/c/src/languages/go.rs @@ -3,8 +3,8 @@ use std::ffi::{c_char, CString}; use std::future::Future; use zen_engine::handler::custom_node_adapter::{CustomNodeAdapter, CustomNodeRequest}; -use zen_engine::handler::node::NodeResult; use zen_engine::loader::{DecisionLoader, LoaderError, LoaderResponse}; +use zen_engine::nodes::result::NodeResult; use crate::custom_node::{DynamicCustomNode, ZenCustomNodeResult}; use crate::engine::{ZenEngine, ZenEngineStruct}; diff --git a/bindings/c/src/languages/native.rs b/bindings/c/src/languages/native.rs index 93de29ec..2a9510f1 100644 --- a/bindings/c/src/languages/native.rs +++ b/bindings/c/src/languages/native.rs @@ -3,8 +3,8 @@ use std::ffi::{c_char, CString}; use std::future::Future; use zen_engine::handler::custom_node_adapter::{CustomNodeAdapter, CustomNodeRequest}; -use zen_engine::handler::node::NodeResult; use zen_engine::loader::{DecisionLoader, LoaderResponse}; +use zen_engine::nodes::result::NodeResult; use crate::custom_node::{DynamicCustomNode, ZenCustomNodeResult}; use crate::engine::{ZenEngine, ZenEngineStruct}; diff --git a/bindings/nodejs/src/custom_node.rs b/bindings/nodejs/src/custom_node.rs index b3d11060..e064550e 100644 --- a/bindings/nodejs/src/custom_node.rs +++ b/bindings/nodejs/src/custom_node.rs @@ -4,7 +4,7 @@ use napi::threadsafe_function::{ErrorStrategy, ThreadsafeFunction}; use crate::types::{ZenEngineHandlerRequest, ZenEngineHandlerResponse}; use zen_engine::handler::custom_node_adapter::{CustomNodeAdapter, CustomNodeRequest}; -use zen_engine::handler::node::{NodeResponse, NodeResult}; +use zen_engine::nodes::result::{NodeResponse, NodeResult}; use zen_engine::Variable; #[derive(Default)] diff --git a/bindings/python/src/custom_node.rs b/bindings/python/src/custom_node.rs index cfe529b2..3f11dd9e 100644 --- a/bindings/python/src/custom_node.rs +++ b/bindings/python/src/custom_node.rs @@ -6,7 +6,7 @@ use pyo3_async_runtimes::TaskLocals; use pythonize::depythonize; use zen_engine::handler::custom_node_adapter::{CustomNodeAdapter, CustomNodeRequest}; -use zen_engine::handler::node::{NodeResponse, NodeResult}; +use zen_engine::nodes::result::{NodeResponse, NodeResult}; use crate::types::PyNodeRequest; diff --git a/bindings/python/src/types.rs b/bindings/python/src/types.rs index ec945278..1723dc71 100644 --- a/bindings/python/src/types.rs +++ b/bindings/python/src/types.rs @@ -10,7 +10,7 @@ use crate::variable::PyVariable; use zen_engine::handler::custom_node_adapter::{ CustomDecisionNode as BaseCustomDecisionNode, CustomNodeRequest, }; -use zen_engine::handler::node::NodeResponse; +use zen_engine::nodes::result::NodeResponse; use zen_expression::Variable; #[derive(Serialize)] diff --git a/bindings/uniffi/src/custom_node.rs b/bindings/uniffi/src/custom_node.rs index 8a287cbc..9567d547 100644 --- a/bindings/uniffi/src/custom_node.rs +++ b/bindings/uniffi/src/custom_node.rs @@ -2,7 +2,7 @@ use crate::error::ZenError; use crate::types::{DecisionNode, ZenEngineHandlerRequest, ZenEngineHandlerResponse}; use uniffi::deps::anyhow::anyhow; use zen_engine::handler::custom_node_adapter::{CustomNodeAdapter, CustomNodeRequest}; -use zen_engine::handler::node::{NodeResponse, NodeResult}; +use zen_engine::nodes::result::{NodeResponse, NodeResult}; use zen_expression::Variable; #[uniffi::export(callback_interface)] diff --git a/core/engine/src/decision.rs b/core/engine/src/decision.rs index 9b8c1180..33ec9e5f 100644 --- a/core/engine/src/decision.rs +++ b/core/engine/src/decision.rs @@ -1,6 +1,6 @@ +use crate::decision_graph::graph::{DecisionGraph, DecisionGraphConfig, DecisionGraphResponse}; use crate::engine::{EvaluationOptions, EvaluationSerializedOptions, EvaluationTraceKind}; use crate::handler::custom_node_adapter::{CustomNodeAdapter, NoopCustomNode}; -use crate::handler::graph::{DecisionGraph, DecisionGraphConfig, DecisionGraphResponse}; use crate::loader::{CachedLoader, DecisionLoader, NoopLoader}; use crate::model::DecisionContent; use crate::nodes::validator_cache::ValidatorCache; diff --git a/core/engine/src/handler/graph.rs b/core/engine/src/decision_graph/graph.rs similarity index 96% rename from core/engine/src/handler/graph.rs rename to core/engine/src/decision_graph/graph.rs index 0ca5307b..c9d646a3 100644 --- a/core/engine/src/handler/graph.rs +++ b/core/engine/src/decision_graph/graph.rs @@ -1,18 +1,7 @@ use crate::engine::EvaluationTraceKind; -use crate::handler::custom_node_adapter::{CustomNodeAdapter, CustomNodeRequest}; -use crate::handler::decision::DecisionHandler; -use crate::handler::expression::ExpressionHandler; -use crate::handler::function::function::{Function, FunctionConfig}; -use crate::handler::function::module::console::ConsoleListener; -use crate::handler::function::module::zen::ZenListener; -use crate::handler::function::FunctionHandler; -use crate::handler::function_v1; -use crate::handler::function_v1::runtime::create_runtime; -use crate::handler::node::NodeRequest; -use crate::handler::table::zen::DecisionTableHandler; -use crate::handler::traversal::{GraphWalker, StableDiDecisionGraph}; use crate::loader::DecisionLoader; use crate::model::{DecisionContent, DecisionNodeKind, FunctionNodeContent}; +use crate::nodes::result::NodeRequest; use crate::nodes::validator_cache::ValidatorCache; use crate::{EvaluationError, NodeError}; use ahash::{HashMap, HashMapExt}; @@ -537,7 +526,7 @@ pub struct DecisionGraphResponse { pub performance: String, pub result: Variable, #[serde(skip_serializing_if = "Option::is_none")] - pub trace: Option>, + pub trace: Option, DecisionGraphTrace>>, } impl DecisionGraphResponse { @@ -565,9 +554,9 @@ impl DecisionGraphResponse { pub struct DecisionGraphTrace { pub input: Variable, pub output: Variable, - pub name: String, - pub id: String, - pub performance: Option, + pub name: Arc, + pub id: Arc, + pub performance: Option>, pub trace_data: Option, pub order: u32, } diff --git a/core/engine/src/decision_graph/mod.rs b/core/engine/src/decision_graph/mod.rs new file mode 100644 index 00000000..aec805f7 --- /dev/null +++ b/core/engine/src/decision_graph/mod.rs @@ -0,0 +1,2 @@ +pub mod graph; +mod traversal; diff --git a/core/engine/src/handler/traversal.rs b/core/engine/src/decision_graph/traversal.rs similarity index 96% rename from core/engine/src/handler/traversal.rs rename to core/engine/src/decision_graph/traversal.rs index 3dc20839..e3af418c 100644 --- a/core/engine/src/handler/traversal.rs +++ b/core/engine/src/decision_graph/traversal.rs @@ -6,6 +6,7 @@ use petgraph::prelude::{EdgeIndex, NodeIndex, StableDiGraph}; use petgraph::visit::{EdgeRef, IntoNodeIdentifiers, VisitMap, Visitable}; use petgraph::{Incoming, Outgoing}; use serde_json::json; +use std::ops::Deref; use std::rc::Rc; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -93,7 +94,7 @@ impl GraphWalker { .iter() .filter_map(|(idx, value)| { let weight = g.node_weight(*idx)?; - Some((Rc::from(weight.name.as_str()), value.clone())) + Some((Rc::from(weight.name.deref()), value.clone())) }) .collect(); @@ -186,7 +187,7 @@ impl GraphWalker { let v = Variable::empty_object(); v.dot_insert( "id", - Variable::String(Rc::from(statement.id.as_str())), + Variable::String(Rc::from(statement.id.deref())), ); v @@ -215,7 +216,9 @@ impl GraphWalker { .edges_directed(nid, Outgoing) .filter(|edge| { edge.weight().source_handle.as_ref().map_or(true, |handle| { - !valid_statements.iter().any(|s| s.id == *handle) + !valid_statements + .iter() + .any(|s| s.id.deref() == handle.as_str()) }) }) .map(|edge| edge.id()) @@ -268,7 +271,7 @@ fn switch_statement_evaluate<'a>( } isolate - .run_standard(switch_statement.condition.as_str()) + .run_standard(switch_statement.condition.deref()) .map_or(false, |v| v.as_bool().unwrap_or(false)) } diff --git a/core/engine/src/engine.rs b/core/engine/src/engine.rs index a3f243ee..a11b0edb 100644 --- a/core/engine/src/engine.rs +++ b/core/engine/src/engine.rs @@ -1,6 +1,6 @@ use crate::decision::Decision; +use crate::decision_graph::graph::DecisionGraphResponse; use crate::handler::custom_node_adapter::{CustomNodeAdapter, NoopCustomNode}; -use crate::handler::graph::DecisionGraphResponse; use crate::loader::{ ClosureLoader, DecisionLoader, DynamicLoader, LoaderResponse, LoaderResult, NoopLoader, }; diff --git a/core/engine/src/error.rs b/core/engine/src/error.rs index f7e8d181..0dfdee79 100644 --- a/core/engine/src/error.rs +++ b/core/engine/src/error.rs @@ -1,7 +1,7 @@ +use crate::decision_graph::graph::DecisionGraphValidationError; use crate::engine::EvaluationTraceKind; -use crate::handler::graph::DecisionGraphValidationError; -pub use crate::handler::node::NodeError; use crate::loader::LoaderError; +pub use crate::nodes::result::NodeError; use jsonschema::{ErrorIterator, ValidationError}; use serde::ser::SerializeMap; use serde::{Serialize, Serializer}; diff --git a/core/engine/src/handler/decision.rs b/core/engine/src/handler/decision.rs deleted file mode 100644 index 7e79740c..00000000 --- a/core/engine/src/handler/decision.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::handler::custom_node_adapter::CustomNodeAdapter; -use crate::handler::graph::{error_trace, DecisionGraph, DecisionGraphConfig}; -use crate::handler::node::{NodeError, NodeRequest, NodeResponse, NodeResult}; -use crate::loader::DecisionLoader; -use crate::model::DecisionNodeKind; -use crate::nodes::validator_cache::ValidatorCache; -use crate::util::transform_attribute::TransformAttributesExecution; -use anyhow::anyhow; -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::sync::Arc; -use tokio::sync::Mutex; - -pub struct DecisionHandler { - trace: bool, - loader: Arc, - adapter: Arc, - max_depth: u8, - js_function: Option>, - validator_cache: ValidatorCache, -} - -impl DecisionHandler { - pub fn new( - trace: bool, - max_depth: u8, - loader: Arc, - adapter: Arc, - js_function: Option>, - validator_cache: ValidatorCache, - ) -> Self { - Self { - trace, - loader, - adapter, - max_depth, - js_function, - validator_cache, - } - } - - pub fn handle<'s, 'arg, 'recursion>( - &'s self, - request: NodeRequest, - ) -> Pin + 'recursion>> - where - 's: 'recursion, - 'arg: 'recursion, - { - Box::pin(async move { - let content = match &request.node.kind { - DecisionNodeKind::DecisionNode { content } => Ok(content), - _ => Err(anyhow!("Unexpected node type")), - }?; - - let sub_decision = self - .loader - .load(&content.key) - .await - .map_err(|err| NodeError::Display(err.to_string()))?; - let sub_tree = DecisionGraph::try_new(DecisionGraphConfig { - content: sub_decision, - max_depth: self.max_depth, - loader: self.loader.clone(), - adapter: self.adapter.clone(), - iteration: request.iteration + 1, - trace: self.trace, - validator_cache: Some(self.validator_cache.clone()), - }) - .map_err(|err| NodeError::Display(err.to_string()))? - .with_function(self.js_function.clone()); - - let sub_tree_mutex = Arc::new(Mutex::new(sub_tree)); - - content - .transform_attributes - .run_with(request.input, |input| { - let sub_tree_mutex = sub_tree_mutex.clone(); - - async move { - let mut sub_tree_ref = sub_tree_mutex.lock().await; - - sub_tree_ref.reset_graph(); - sub_tree_ref.evaluate(input).await.map(|r| NodeResponse { - output: r.result, - trace_data: error_trace(&r.trace), - }) - } - }) - .await - }) - } -} diff --git a/core/engine/src/handler/mod.rs b/core/engine/src/handler/mod.rs deleted file mode 100644 index 35cbfb96..00000000 --- a/core/engine/src/handler/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod decision; - -pub mod custom_node_adapter; -pub mod graph; -pub mod node; -pub(crate) mod traversal; diff --git a/core/engine/src/lib.rs b/core/engine/src/lib.rs index 353f951e..51b219bb 100644 --- a/core/engine/src/lib.rs +++ b/core/engine/src/lib.rs @@ -124,6 +124,7 @@ mod config; mod decision; +mod decision_graph; mod engine; pub mod error; pub mod handler; @@ -133,12 +134,12 @@ mod nodes; pub use config::ZEN_CONFIG; pub use decision::Decision; +pub use decision_graph::graph::DecisionGraphResponse; +pub use decision_graph::graph::DecisionGraphTrace; +pub use decision_graph::graph::DecisionGraphValidationError; pub use engine::{ DecisionEngine, EvaluationOptions, EvaluationSerializedOptions, EvaluationTraceKind, }; pub use error::EvaluationError; -pub use handler::graph::DecisionGraphResponse; -pub use handler::graph::DecisionGraphTrace; -pub use handler::graph::DecisionGraphValidationError; -pub use handler::node::NodeError; +pub use nodes::result::NodeError; pub use zen_expression::Variable; diff --git a/core/engine/src/nodes/context.rs b/core/engine/src/nodes/context.rs index fd3bf09b..656458a3 100644 --- a/core/engine/src/nodes/context.rs +++ b/core/engine/src/nodes/context.rs @@ -1,11 +1,12 @@ -use crate::handler::node::{NodeResponse, NodeResult}; use crate::nodes::definition::{NodeDataType, TraceDataType}; use crate::nodes::extensions::NodeHandlerExtensions; use crate::nodes::function::v2::function::Function; +use crate::nodes::result::{NodeResponse, NodeResult}; use crate::NodeError; use std::cell::RefCell; use std::future::Future; use std::rc::Rc; +use std::sync::Arc; use zen_types::variable::Variable; pub struct NodeContext @@ -13,8 +14,8 @@ where NodeData: NodeDataType, TraceData: TraceDataType, { - pub id: Rc, - pub name: Rc, + pub id: Arc, + pub name: Arc, pub node: NodeData, pub input: Variable, pub trace: Option>, @@ -147,8 +148,8 @@ where } pub struct NodeContextBase { - pub id: Rc, - pub name: Rc, + pub id: Arc, + pub name: Arc, pub input: Variable, } diff --git a/core/engine/src/handler/custom_node_adapter.rs b/core/engine/src/nodes/custom/adapter.rs similarity index 68% rename from core/engine/src/handler/custom_node_adapter.rs rename to core/engine/src/nodes/custom/adapter.rs index baf0eaec..32475ec2 100644 --- a/core/engine/src/handler/custom_node_adapter.rs +++ b/core/engine/src/nodes/custom/adapter.rs @@ -1,5 +1,4 @@ -use crate::handler::node::{NodeError, NodeRequest, NodeResult}; -use crate::model::{DecisionNode, DecisionNodeKind}; +use crate::nodes::result::{NodeError, NodeRequest, NodeResult}; use json_dotpath::DotPaths; use serde::Serialize; use serde_json::Value; @@ -7,6 +6,7 @@ use std::fmt::Debug; use std::future::Future; use std::ops::Deref; use std::pin::Pin; +use std::rc::Rc; use std::sync::Arc; use zen_expression::variable::Variable; use zen_tmpl::TemplateRenderError; @@ -22,10 +22,17 @@ pub trait CustomNodeAdapter: Debug { pub struct NoopCustomNode; impl CustomNodeAdapter for NoopCustomNode { - async fn handle(&self, _: CustomNodeRequest) -> NodeResult { - Err(NodeError::Display( - "Custom node handler not provided".to_string(), - )) + fn handle( + &self, + request: CustomNodeRequest, + ) -> Pin + Send>> { + Box::pin(async move { + Err(NodeError { + trace: None, + node_id: Some(Rc::from(request.node.id.deref())), + source: "Custom node handler not provided".to_string().into(), + }) + }) } } @@ -69,27 +76,10 @@ impl CustomNodeRequest { #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct CustomDecisionNode { - pub id: String, - pub name: String, - pub kind: String, + pub id: Arc, + pub name: Arc, + pub kind: Arc, pub config: Arc, } -impl TryFrom<&DecisionNode> for CustomDecisionNode { - type Error = (); - - fn try_from(value: &DecisionNode) -> Result { - let DecisionNodeKind::CustomNode { content } = &value.kind else { - return Err(()); - }; - - Ok(Self { - id: value.id.clone(), - name: value.name.clone(), - kind: content.kind.clone(), - config: content.config.clone(), - }) - } -} - pub type DynamicCustomNode = Arc; diff --git a/core/engine/src/nodes/custom/mod.rs b/core/engine/src/nodes/custom/mod.rs index 8b895ead..e336a9bb 100644 --- a/core/engine/src/nodes/custom/mod.rs +++ b/core/engine/src/nodes/custom/mod.rs @@ -1,94 +1,30 @@ -use crate::handler::node::{NodeError, NodeRequest, NodeResult}; -use crate::model::{DecisionNode, DecisionNodeKind}; -use json_dotpath::DotPaths; -use serde::Serialize; -use serde_json::Value; -use std::fmt::Debug; -use std::future::Future; -use std::ops::Deref; -use std::pin::Pin; -use std::sync::Arc; -use zen_expression::variable::Variable; -use zen_tmpl::TemplateRenderError; +use crate::nodes::custom::adapter::{CustomDecisionNode, CustomNodeRequest}; +use crate::nodes::result::NodeResult; +use crate::nodes::{NodeContext, NodeHandler}; +use zen_types::decision::CustomNodeContent; +use zen_types::variable::Variable; -pub trait CustomNodeAdapter: Debug { - fn handle( - &self, - request: CustomNodeRequest, - ) -> Pin + Send>>; -} +mod adapter; -#[derive(Default, Debug)] -pub struct NoopCustomNode; +pub struct CustomNodeHandler; -impl CustomNodeAdapter for NoopCustomNode { - fn handle( - &self, - request: CustomNodeRequest, - ) -> Pin + Send>> { - Box::pin(async move { Err(NodeError("Custom node handler not provided".to_string())) }) - } -} +impl NodeHandler for CustomNodeHandler { + type NodeData = CustomNodeContent; + type TraceData = Variable; -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CustomNodeRequest { - pub input: Variable, - pub node: CustomDecisionNode, -} - -impl TryFrom for CustomNodeRequest { - type Error = (); - - fn try_from(value: NodeRequest) -> Result { - Ok(Self { - input: value.input.clone(), - node: value.node.deref().try_into()?, - }) - } -} - -impl CustomNodeRequest { - pub fn get_field(&self, path: &str) -> Result, TemplateRenderError> { - let Some(selected_value) = self.get_field_raw(path) else { - return Ok(None); + fn handle(&self, ctx: NodeContext) -> NodeResult { + let custom_node_request = CustomNodeRequest { + input: ctx.input.clone(), + node: CustomDecisionNode { + id: ctx.id.clone(), + name: ctx.name.clone(), + kind: ctx.node.kind.clone(), + config: ctx.node.config.clone(), + }, }; - let Variable::String(template) = selected_value else { - return Ok(Some(selected_value)); - }; - - let template_value = zen_tmpl::render(template.as_ref(), self.input.clone())?; - Ok(Some(template_value)) - } - - fn get_field_raw(&self, path: &str) -> Option { - self.node.config.dot_get(path).ok().flatten() + ctx.block_on(ctx.extensions.custom_node().handle(custom_node_request))? } } -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CustomDecisionNode { - pub id: String, - pub name: String, - pub kind: String, - pub config: Arc, -} - -impl TryFrom<&DecisionNode> for CustomDecisionNode { - type Error = (); - - fn try_from(value: &DecisionNode) -> Result { - let DecisionNodeKind::CustomNode { content } = &value.kind else { - return Err(()); - }; - - Ok(Self { - id: value.id.clone(), - name: value.name.clone(), - kind: content.kind.clone(), - config: content.config.clone(), - }) - } -} +pub use adapter::DynamicCustomNode; diff --git a/core/engine/src/nodes/decision_table/mod.rs b/core/engine/src/nodes/decision_table/mod.rs index 4b917609..596e4d74 100644 --- a/core/engine/src/nodes/decision_table/mod.rs +++ b/core/engine/src/nodes/decision_table/mod.rs @@ -1,5 +1,5 @@ -use crate::handler::node::NodeResult; use crate::nodes::definition::NodeHandler; +use crate::nodes::result::NodeResult; use crate::nodes::NodeContext; use ahash::HashMap; use serde::Serialize; diff --git a/core/engine/src/nodes/definition.rs b/core/engine/src/nodes/definition.rs index 805b42b2..219e7024 100644 --- a/core/engine/src/nodes/definition.rs +++ b/core/engine/src/nodes/definition.rs @@ -1,8 +1,8 @@ -use crate::handler::node::NodeResult; use crate::nodes::context::NodeContext; use crate::nodes::function::FunctionNodeTrace; use crate::nodes::input::InputNodeTrace; use crate::nodes::output::OutputNodeTrace; +use crate::nodes::result::NodeResult; use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::sync::Arc; diff --git a/core/engine/src/nodes/expression/mod.rs b/core/engine/src/nodes/expression/mod.rs index 1f397945..665aa33c 100644 --- a/core/engine/src/nodes/expression/mod.rs +++ b/core/engine/src/nodes/expression/mod.rs @@ -1,5 +1,5 @@ -use crate::handler::node::NodeResult; use crate::model::ExpressionNodeContent; +use crate::nodes::result::NodeResult; use ahash::HashMap; use std::rc::Rc; diff --git a/core/engine/src/nodes/extensions.rs b/core/engine/src/nodes/extensions.rs index 5acbbbae..d4bb8d96 100644 --- a/core/engine/src/nodes/extensions.rs +++ b/core/engine/src/nodes/extensions.rs @@ -1,5 +1,5 @@ -use crate::handler::custom_node_adapter::DynamicCustomNode; use crate::loader::DynamicLoader; +use crate::nodes::custom::DynamicCustomNode; use crate::nodes::function::v2::function::{Function, FunctionConfig}; use crate::nodes::function::v2::module::console::ConsoleListener; use crate::nodes::function::v2::module::zen::ZenListener; @@ -13,8 +13,8 @@ pub struct NodeHandlerExtensions { tokio_runtime: Arc>, function_runtime: Arc>, - decision_loader: Arc, - custom_node_adapter: Arc, + loader: DynamicLoader, + custom_node_adapter: DynamicCustomNode, } impl NodeHandlerExtensions { @@ -58,4 +58,12 @@ impl NodeHandlerExtensions { .get() .context("Tokio runtime is not initialized") } + + pub fn custom_node(&self) -> &DynamicCustomNode { + &self.custom_node_adapter + } + + pub fn loader(&self) -> &DynamicLoader { + &self.loader + } } diff --git a/core/engine/src/nodes/function/mod.rs b/core/engine/src/nodes/function/mod.rs index 3a24d813..0937549d 100644 --- a/core/engine/src/nodes/function/mod.rs +++ b/core/engine/src/nodes/function/mod.rs @@ -1,10 +1,10 @@ pub(crate) mod v1; pub(crate) mod v2; -use crate::handler::node::NodeResult; use crate::nodes::definition::NodeHandler; use crate::nodes::function::v1::{FunctionV1NodeHandler, FunctionV1Trace}; use crate::nodes::function::v2::{FunctionV2NodeHandler, FunctionV2Trace}; +use crate::nodes::result::NodeResult; use crate::nodes::NodeContext; use std::sync::Arc; use zen_types::decision::{FunctionContent, FunctionNodeContent}; diff --git a/core/engine/src/nodes/function/v1/mod.rs b/core/engine/src/nodes/function/v1/mod.rs index c4efc79c..b68e7b8c 100644 --- a/core/engine/src/nodes/function/v1/mod.rs +++ b/core/engine/src/nodes/function/v1/mod.rs @@ -2,10 +2,10 @@ use std::ops::Deref; use std::sync::Arc; use std::time::{Duration, Instant}; -use crate::handler::node::NodeResult; use crate::nodes::definition::NodeHandler; use crate::nodes::function::v1::runtime::create_runtime; use crate::nodes::function::v1::script::Script; +use crate::nodes::result::NodeResult; use crate::nodes::{NodeContext, NodeContextExt}; use serde_json::Value; use zen_expression::variable::ToVariable; diff --git a/core/engine/src/nodes/function/v2/mod.rs b/core/engine/src/nodes/function/v2/mod.rs index 68374894..d01f39b1 100644 --- a/core/engine/src/nodes/function/v2/mod.rs +++ b/core/engine/src/nodes/function/v2/mod.rs @@ -2,12 +2,12 @@ use std::ops::Deref; use std::rc::Rc; use std::time::Duration; -use crate::handler::node::NodeResult; use crate::nodes::definition::NodeHandler; use crate::nodes::function::v2::error::FunctionResult; use crate::nodes::function::v2::function::{Function, HandlerResponse}; use crate::nodes::function::v2::module::console::Log; use crate::nodes::function::v2::serde::JsValue; +use crate::nodes::result::NodeResult; use crate::nodes::{NodeContext, NodeContextExt}; use ::serde::{Deserialize, Serialize}; use rquickjs::{async_with, CatchResultExt, Object}; diff --git a/core/engine/src/nodes/function/v2/module/zen.rs b/core/engine/src/nodes/function/v2/module/zen.rs index 461b3708..77dd37dd 100644 --- a/core/engine/src/nodes/function/v2/module/zen.rs +++ b/core/engine/src/nodes/function/v2/module/zen.rs @@ -2,8 +2,8 @@ use std::future::Future; use std::pin::Pin; use std::sync::Arc; +use crate::decision_graph::graph::{DecisionGraph, DecisionGraphConfig}; use crate::handler::custom_node_adapter::DynamicCustomNode; -use crate::handler::graph::{DecisionGraph, DecisionGraphConfig}; use crate::loader::{DecisionLoader, DynamicLoader}; use crate::nodes::function::v2::error::{FunctionResult, ResultExt}; use crate::nodes::function::v2::listener::{RuntimeEvent, RuntimeListener}; diff --git a/core/engine/src/nodes/input/mod.rs b/core/engine/src/nodes/input/mod.rs index f75c764c..8ca2ecae 100644 --- a/core/engine/src/nodes/input/mod.rs +++ b/core/engine/src/nodes/input/mod.rs @@ -1,5 +1,5 @@ -use crate::handler::node::NodeResult; use crate::nodes::definition::NodeHandler; +use crate::nodes::result::NodeResult; use crate::nodes::NodeContext; use zen_types::decision::InputNodeContent; use zen_types::variable::Variable; diff --git a/core/engine/src/nodes/mod.rs b/core/engine/src/nodes/mod.rs index bcea43af..8e2b2164 100644 --- a/core/engine/src/nodes/mod.rs +++ b/core/engine/src/nodes/mod.rs @@ -8,6 +8,7 @@ mod extensions; pub(crate) mod function; mod input; mod output; +pub mod result; mod transform_attributes; pub mod validator_cache; diff --git a/core/engine/src/nodes/output/mod.rs b/core/engine/src/nodes/output/mod.rs index 8d4a4e82..1fca47d1 100644 --- a/core/engine/src/nodes/output/mod.rs +++ b/core/engine/src/nodes/output/mod.rs @@ -1,5 +1,5 @@ -use crate::handler::node::NodeResult; use crate::nodes::definition::NodeHandler; +use crate::nodes::result::NodeResult; use crate::nodes::NodeContext; use zen_types::decision::OutputNodeContent; use zen_types::variable::Variable; diff --git a/core/engine/src/handler/node.rs b/core/engine/src/nodes/result.rs similarity index 100% rename from core/engine/src/handler/node.rs rename to core/engine/src/nodes/result.rs diff --git a/core/engine/src/nodes/transform_attributes.rs b/core/engine/src/nodes/transform_attributes.rs index 9226e807..8818cace 100644 --- a/core/engine/src/nodes/transform_attributes.rs +++ b/core/engine/src/nodes/transform_attributes.rs @@ -1,5 +1,5 @@ -use crate::handler::node::{NodeResponse, NodeResult}; use crate::model::{TransformAttributes, TransformExecutionMode}; +use crate::nodes::result::{NodeResponse, NodeResult}; use crate::nodes::{NodeContextBase, NodeContextExt}; use std::future::Future; use std::ops::Deref; diff --git a/core/macros/src/to_variable.rs b/core/macros/src/to_variable.rs index aad1f08f..005c5f66 100644 --- a/core/macros/src/to_variable.rs +++ b/core/macros/src/to_variable.rs @@ -1,5 +1,6 @@ use proc_macro::TokenStream; use quote::quote; +use serde_derive_internals::attr::TagType; use syn::parse_macro_input; pub fn to_variable_impl(input: TokenStream) -> TokenStream { @@ -96,13 +97,119 @@ fn generate_enum_body( .filter(|variant| !variant.attrs.skip_serializing()) .collect(); - let variant_arms = active_variants - .iter() - .map(|variant| generate_variant_arm(enum_ident, variant, container)); + // Check if the enum is untagged + let is_untagged = matches!( + container.attrs.tag(), + serde_derive_internals::attr::TagType::None + ); - quote! { - match self { - #(#variant_arms)* + match container.attrs.tag() { + TagType::None => { + let variant_arms = active_variants + .iter() + .map(|variant| generate_untagged_variant_arm(enum_ident, variant)); + + quote! { + match self { + #(#variant_arms)* + } + } + } + _ => { + let variant_arms = active_variants + .iter() + .map(|variant| generate_variant_arm(enum_ident, variant, container)); + + quote! { + match self { + #(#variant_arms)* + } + } + } + } +} + +fn generate_untagged_variant_arm( + enum_ident: &syn::Ident, + variant: &serde_derive_internals::ast::Variant, +) -> proc_macro2::TokenStream { + let variant_ident = &variant.ident; + + match variant.style { + serde_derive_internals::ast::Style::Unit => { + // Unit variants in untagged enums typically serialize as null + quote! { + #enum_ident::#variant_ident => { + _Variable::Null + } + } + } + + serde_derive_internals::ast::Style::Newtype => { + // Newtype variants serialize directly as their inner value + quote! { + #enum_ident::#variant_ident(value) => { + value.to_variable() + } + } + } + + serde_derive_internals::ast::Style::Tuple => { + let field_count = variant.fields.len(); + let field_patterns: Vec<_> = (0..field_count) + .map(|i| quote::format_ident!("field_{}", i)) + .collect(); + + if field_count == 1 { + quote! { + #enum_ident::#variant_ident(#(#field_patterns),*) => { + (#(#field_patterns)*).to_variable() + } + } + } else { + quote! { + #enum_ident::#variant_ident(#(#field_patterns),*) => { + let mut vec = Vec::with_capacity(#field_count); + #(vec.push((#field_patterns).to_variable());)* + _Variable::from_array(vec) + } + } + } + } + + serde_derive_internals::ast::Style::Struct => { + let active_fields: Vec<_> = variant + .fields + .iter() + .filter(|field| !field.attrs.skip_serializing()) + .collect(); + + let field_mappings = active_fields.iter().map(|field| { + let field_ident = match &field.member { + syn::Member::Named(ident) => ident, + syn::Member::Unnamed(_) => panic!("Unexpected unnamed field in struct variant"), + }; + + let field_name = field.attrs.name().serialize_name(); + + quote! { + map.insert(_Rc::from(#field_name), #field_ident.to_variable()); + } + }); + + let field_patterns = active_fields.iter().map(|field| match &field.member { + syn::Member::Named(ident) => quote! { #ident }, + syn::Member::Unnamed(_) => panic!("Unexpected unnamed field in struct variant"), + }); + + let field_count = active_fields.len(); + quote! { + #enum_ident::#variant_ident { #(#field_patterns),* } => { + let mut map = _VariableMap::with_capacity(#field_count); + #(#field_mappings)* + _Variable::from_object(map) + } + } } } } diff --git a/core/types/src/decision/mod.rs b/core/types/src/decision/mod.rs new file mode 100644 index 00000000..58e23e82 --- /dev/null +++ b/core/types/src/decision/mod.rs @@ -0,0 +1,227 @@ +use ahash::HashMap; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::Value; +use std::sync::Arc; + +/// JDM Decision model +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DecisionContent { + pub nodes: Vec>, + pub edges: Vec>, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DecisionEdge { + pub id: Arc, + pub source_id: Arc, + pub target_id: Arc, + pub source_handle: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DecisionNode { + pub id: Arc, + pub name: Arc, + #[serde(rename = "type")] + #[serde(flatten)] + pub kind: DecisionNodeKind, +} + +impl PartialEq for DecisionNode { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(tag = "type")] +#[serde(rename_all = "camelCase")] +pub enum DecisionNodeKind { + InputNode { + #[serde(default)] + content: InputNodeContent, + }, + OutputNode { + #[serde(default)] + content: OutputNodeContent, + }, + FunctionNode { + content: FunctionNodeContent, + }, + DecisionNode { + content: DecisionNodeContent, + }, + DecisionTableNode { + content: DecisionTableContent, + }, + ExpressionNode { + content: ExpressionNodeContent, + }, + SwitchNode { + content: SwitchNodeContent, + }, + CustomNode { + content: CustomNodeContent, + }, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct InputNodeContent { + #[serde(default, deserialize_with = "empty_string_is_none")] + pub schema: Option>, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct OutputNodeContent { + #[serde(default, deserialize_with = "empty_string_is_none")] + pub schema: Option>, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +pub enum FunctionNodeContent { + Version2(FunctionContent), + Version1(Arc), +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct FunctionContent { + pub source: Arc, + #[serde(default)] + pub omit_nodes: bool, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DecisionNodeContent { + pub key: Arc, + #[serde(flatten)] + pub transform_attributes: TransformAttributes, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DecisionTableContent { + pub rules: Vec, Arc>>, + pub inputs: Vec, + pub outputs: Vec, + pub hit_policy: DecisionTableHitPolicy, + #[serde(flatten)] + pub transform_attributes: TransformAttributes, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum DecisionTableHitPolicy { + First, + Collect, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DecisionTableInputField { + pub id: Arc, + pub name: Arc, + #[serde(default, deserialize_with = "empty_string_is_none")] + pub field: Option>, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DecisionTableOutputField { + pub id: Arc, + pub name: Arc, + pub field: Arc, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ExpressionNodeContent { + pub expressions: Vec, + #[serde(flatten)] + pub transform_attributes: TransformAttributes, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Expression { + pub id: Arc, + pub key: Arc, + pub value: Arc, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SwitchNodeContent { + #[serde(default)] + pub hit_policy: SwitchStatementHitPolicy, + pub statements: Vec, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SwitchStatement { + pub id: Arc, + pub condition: Arc, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub enum SwitchStatementHitPolicy { + #[default] + First, + Collect, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct TransformAttributes { + #[serde(default, deserialize_with = "empty_string_is_none")] + pub input_field: Option>, + #[serde(default, deserialize_with = "empty_string_is_none")] + pub output_path: Option>, + #[serde(default)] + pub execution_mode: TransformExecutionMode, + #[serde(default)] + pub pass_through: bool, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub enum TransformExecutionMode { + #[default] + Single, + Loop, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct CustomNodeContent { + pub kind: Arc, + pub config: Arc, +} + +fn empty_string_is_none<'de, D>(deserializer: D) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum StringOrNull { + String(Arc), + Null, + } + + match StringOrNull::deserialize(deserializer)? { + StringOrNull::String(s) if s.trim().is_empty() => Ok(None), + StringOrNull::String(s) => Ok(Some(s)), + StringOrNull::Null => Ok(None), + } +} diff --git a/core/types/src/lib.rs b/core/types/src/lib.rs index 1772aca9..660e80b0 100644 --- a/core/types/src/lib.rs +++ b/core/types/src/lib.rs @@ -1,4 +1,5 @@ mod constant; +pub mod decision; pub mod rcvalue; pub mod variable; pub mod variable_type;