diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..a4c094b --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,6 @@ +[http] +proxy = "http://127.0.0.1:7890" + +[net] +git-fetch-with-cli = true + diff --git a/Cargo.lock b/Cargo.lock index 33d41a6..b83581d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1635,6 +1635,15 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" +dependencies = [ + "spin", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -2206,6 +2215,7 @@ dependencies = [ "ahash", "bitflags", "instant", + "no-std-compat", "num-traits", "once_cell", "rhai_codegen", @@ -2507,6 +2517,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "sptr" version = "0.3.2" diff --git a/README.md b/README.md index 02b0ccd..f0bf310 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,148 @@ -# dsl_flow +# DSL Flow +[![Crates.io](https://img.shields.io/crates/v/dsl-flow.svg)](https://crates.io/crates/dsl-flow) +[![Docs.rs](https://docs.rs/dsl-flow/badge.svg)](https://docs.rs/dsl-flow) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) + +**DSL Flow** 是一个基于 Rust 的高性能、可扩展的工作流引擎。它旨在通过声明式的 DSL(领域特定语言)来编排复杂的异步任务,支持状态管理、脚本引擎集成(Rhai/JavaScript)以及灵活的控制流。 + +该项目专为构建中间件、API 编排层和数据处理管道而设计,采用清晰的分层架构,易于扩展和维护。 + +## ✨ 核心特性 + +* **声明式 DSL**: 使用 Rust 宏 (`sequence!`, `fork_join!`, `group!`) 轻松定义复杂的流程结构。 +* **多脚本引擎**: 开箱支持 [Rhai](https://rhai.rs/) 和 JavaScript ([Boa](https://boajs.dev/)) 脚本,用于动态逻辑和数据转换。 +* **强大的控制流**: + * **Sequence**: 顺序执行任务。 + * **Fork-Join**: 并行执行多个分支,支持灵活的结果合并策略(Array 或 Object)。 + * **Conditional**: 基于表达式结果的条件分支 (`if-else`)。 +* **状态管理**: 支持无状态(Stateless)和有状态(Stateful)执行模式。内置内存状态存储,可扩展至 Redis/Database。 +* **内置节点**: 提供 HTTP 请求、数据库模拟、消息队列模拟、血缘追踪等常用节点。 +* **异步高性能**: 基于 `Tokio` 和 `Async Trait`,全链路异步非阻塞。 +* **模块化架构**: 内核(Core)、运行时(Runtime)与节点实现(Nodes)分离,方便二次开发。 + +## 📦 安装 + +在你的 `Cargo.toml` 中添加依赖: + +```toml +[dependencies] +dsl-flow = "0.1" +``` + +启用特定特性(可选): + +```toml +[dependencies] +dsl-flow = { version = "0.1", features = ["js", "http"] } +``` + +* `rhai`: 启用 Rhai 脚本引擎(默认开启)。 +* `js`: 启用 JavaScript (Boa) 脚本引擎。 +* `http`: 启用 HTTP 请求节点支持。 + +## 🚀 快速开始 + +以下示例展示了如何定义一个简单的流程:先计算 `1+2`,然后并行计算其 2 倍和 3 倍。 + +```rust +use dsl_flow::*; +use dsl_flow::dsl::*; // 导入 DSL 宏和辅助函数 + +#[tokio::main] +async fn main() -> Result<(), Box> { + // 1. 初始化引擎 + let store = InMemoryStateStore::default(); + let engine = FlowEngine::new(store, FlowOptions { + stateful: false, + expr_engine: ExprEngineKind::Rhai + }); + + // 2. 定义流程 + let flow = Flow::new(sequence! { + // 步骤 1: 设置初始值 + expr_set(ExprEngineKind::Rhai, "1 + 2", "calc.sum"), + + // 步骤 2: 并行执行 + fork_join! { + expr_set(ExprEngineKind::Rhai, "ctx.calc.sum * 2", "calc.double"), + expr_set(ExprEngineKind::Rhai, "ctx.calc.sum * 3", "calc.triple") + } + }); + + // 3. 运行流程 + let ctx = Context::new(); + let output = engine.run_stateless(&flow, ctx).await?; + + println!("Result: {}", output.data); + Ok(()) +} +``` + +## 🏗️ 架构概览 + +DSL Flow 采用清晰的分层架构设计,各层职责分明: + +```text +dsl-flow/src/ + ├── core/ <-- [内核层] 纯粹的抽象和基础类型 + │ ├── mod.rs + │ ├── traits.rs (FlowNode, ExprEngine 接口定义) + │ ├── context.rs (Context 上下文管理) + │ ├── error.rs (统一的错误处理) + │ └── types.rs (NodeId, NodeOutput 等通用类型) + │ + ├── runtime/ <-- [运行时层] 负责跑起来 + │ ├── mod.rs + │ ├── engine.rs (FlowEngine 调度器) + │ ├── state.rs (FlowState 状态存储) + │ └── expr.rs (Rhai/JS 脚本引擎的具体实现) + │ + ├── nodes/ <-- [组件层] 具体的节点实现,按功能归类 + │ ├── mod.rs + │ ├── control.rs (流程控制: Sequence, ForkJoin, Group, Conditional) + │ ├── io.rs (IO 操作: Http, Db, Mq) + │ ├── script.rs (脚本节点: ExprSet, ExprGet) + │ └── lineage.rs (血缘追踪) + │ + ├── dsl.rs <-- [接口层] 对外暴露的 DSL 构建宏和辅助函数 + └── lib.rs <-- [入口] 统一导出公共 API +``` + +### 分层说明 + +* **Core (`src/core`)**: 定义了 `FlowNode` 接口、`Context` 上下文和基础类型。它是整个框架的契约层,不依赖具体的业务实现。 +* **Runtime (`src/runtime`)**: 包含 `FlowEngine` 调度器、状态存储接口 (`StateStore`) 和脚本引擎适配器 (`ExprEngine`)。它负责将流程定义跑起来。 +* **Nodes (`src/nodes`)**: 具体的业务节点实现,包括控制流节点(Sequence/ForkJoin)、IO 节点(Http/Db)等。 + +## 🔧 节点类型 + +### 控制流 (Control Flow) +* **Sequence**: `sequence! { node1, node2 }` - 按顺序执行。 +* **ForkJoin**: `fork_join! { node1, node2 }` - 并行执行,结果收集为数组。 +* **ForkJoin (Merge)**: `fork_join_merge! { "path", merge_mode, ... }` - 并行执行并合并结果到 Context。 +* **Conditional**: `ConditionalNode` - 基于脚本条件的 `if-else` 逻辑。 + +### 脚本与数据 (Script & Data) +* **ExprSet**: 执行脚本并将结果写入 Context 指定路径。 +* **ExprGet**: 仅执行脚本并返回结果。 +* **Lineage**: 追踪数据读写血缘。 + +### IO 与副作用 (Side Effects) +* **HttpNode**: 发送 GET/POST 请求。 +* **DbNode**: 模拟数据库操作。 +* **MqNode**: 模拟消息队列发送。 + +## 🤝 贡献 + +欢迎提交 Issue 和 Pull Request! + +1. Fork 本仓库。 +2. 创建你的特性分支 (`git checkout -b feature/AmazingFeature`)。 +3. 提交你的更改 (`git commit -m 'Add some AmazingFeature'`)。 +4. 推送到分支 (`git push origin feature/AmazingFeature`)。 +5. 打开一个 Pull Request。 + +## 📄 许可证 + +本项目采用 MIT 许可证 - 详情请见 [LICENSE](LICENSE) 文件。 diff --git a/doc/performance-default.md b/doc/performance-default.md index 8fde564..1f223c6 100644 --- a/doc/performance-default.md +++ b/doc/performance-default.md @@ -1,11 +1,2019 @@ # dsl-flow Test Performance ## Default features -- test_rhai_expr_set_and_get: 89.303s -- test_conditional_node_then_else: 76.627s -- test_http_node_with_mock: 57.951s -- test_stateful_engine: 56.686s -- test_db_and_mq_nodes: 58.595s -- test_group_parallel_sleep: 57.494s -- test_expr_set_without_engine_error: 38.232s +- test_rhai_expr_set_and_get: Compiling dsl-flow v0.1.0 (/Users/yangshiyou/Develop/code/rust/dsl_flow/dsl-flow) +warning: unused import: `serde_json::Value` + --> dsl-flow/src/engine.rs:5:5 + | +5 | use serde_json::Value; + | ^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default +warning: unused import: `rhai::EvalAltResult` + --> dsl-flow/src/expr.rs:43:13 + | +43 | use rhai::EvalAltResult; + | ^^^^^^^^^^^^^^^^^^^ + +warning: unused imports: `FlowNode` and `NodeRef` + --> dsl-flow/src/dsl.rs:1:19 + | +1 | use crate::node::{FlowNode, NodeRef}; + | ^^^^^^^^ ^^^^^^^ + +warning: unused imports: `ForkJoinNode`, `GroupNode`, and `SequenceNode` + --> dsl-flow/src/dsl.rs:2:19 + | +2 | use crate::node::{SequenceNode, ForkJoinNode, GroupNode, ExprSetNode, ExprGetNode, MergeMode}; + | ^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^ + +warning: unused import: `std::sync::Arc` + --> dsl-flow/src/dsl.rs:4:5 + | +4 | use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +warning: unused import: `FlowNode` + --> dsl-flow/src/engine.rs:3:19 + | +3 | use crate::node::{FlowNode, NodeOutput, NodeRef, SequenceNode}; + | ^^^^^^^^ + +warning: variable does not need to be mutable + --> dsl-flow/src/expr.rs:34:13 + | +34 | let mut engine = rhai::Engine::new(); + | ----^^^^^^ + | | + | help: remove this `mut` + | + = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:102:5 + | +100 | pub struct ExprSetNode { + | ----------- field in this struct +101 | id: NodeId, +102 | engine: ExprEngineKind, + | ^^^^^^ + | + = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:135:5 + | +133 | pub struct ExprGetNode { + | ----------- field in this struct +134 | id: NodeId, +135 | engine: ExprEngineKind, + | ^^^^^^ + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:270:5 + | +268 | pub struct ConditionalNode { + | --------------- field in this struct +269 | id: NodeId, +270 | engine: ExprEngineKind, + | ^^^^^^ + +warning: `dsl-flow` (lib test) generated 10 warnings (10 duplicates) +warning: `dsl-flow` (lib) generated 10 warnings (run `cargo fix --lib -p dsl-flow` to apply 6 suggestions) +warning: unused import: `serde_json::json` + --> dsl-flow/examples/basic.rs:2:5 + | +2 | use serde_json::json; + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + +error[E0277]: the trait bound `Arc: FlowNode` is not satisfied + --> dsl-flow/examples/basic.rs:9:26 + | + 9 | let flow = Flow::new(sequence! { + | __________________________^ +10 | | expr_set(ExprEngineKind::Rhai, "1 + 2", "calc.sum"), +11 | | fork_join! { +12 | | expr_set(ExprEngineKind::Rhai, "ctx.calc.sum * 2", "calc.double"), +... | +15 | | }); + | |_____^ the trait `FlowNode` is not implemented for `Arc` + | + = help: the following other types implement trait `FlowNode`: + ConditionalNode + DbNode + ExprGetNode + ExprSetNode + ForkJoinNode + GroupNode + HttpNode + LineageNode + and 2 others + = note: required for the cast from `Arc>` to `Arc` + = note: this error originates in the macro `sequence` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/examples/basic.rs:17:53 + | +17 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `Box` to implement `From>` + +For more information about this error, try `rustc --explain E0277`. +warning: `dsl-flow` (example "basic") generated 1 warning +error: could not compile `dsl-flow` (example "basic") due to 2 previous errors; 1 warning emitted +warning: build failed, waiting for other jobs to finish... +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:45:53 + | +45 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0599]: no method named `clone` found for struct `ConditionalNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:62:46 + | +62 | let flow = Flow::new(sequence! { (*cond).clone() }); + | ^^^^^ method not found in `ConditionalNode` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:64:53 + | +64 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:112:53 + | +112 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:129:65 + | +129 | let _out1 = engine.run_stateful(s, &flow, ctx.clone()).await?; + | ------------------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:130:64 + | +130 | let out2 = engine.run_stateful(s, &flow, ctx.clone()).await?; + | ------------------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:146:53 + | +146 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0599]: no method named `clone` found for trait object `dyn dsl_flow::FlowNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:159:71 + | +159 | let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; + | ^^^^^ method not found in `dyn dsl_flow::FlowNode` + +error[E0599]: no method named `clone` found for trait object `dyn dsl_flow::FlowNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:159:86 + | +159 | let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; + | ^^^^^ method not found in `dyn dsl_flow::FlowNode` + +error[E0599]: no method named `clone` found for struct `GroupNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:160:47 + | +160 | let flow = Flow::new(sequence! { (*group).clone() }); + | ^^^^^ method not found in `GroupNode` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:163:51 + | +163 | let _ = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: the trait bound `Arc: dsl_flow::FlowNode` is not satisfied + --> dsl-flow/tests/flow_tests.rs:185:26 + | +185 | let flow = Flow::new(sequence! { + | __________________________^ +186 | | fork_join_merge! { "agg.fork", merge_mode_object_by_id(), +187 | | expr_set(ExprEngineKind::Rhai, "10", "a.x"), +188 | | expr_set(ExprEngineKind::Rhai, "20", "b.y") +189 | | }, +190 | | expr_get(ExprEngineKind::Rhai, "ctx.agg.fork") +191 | | }); + | |_____^ the trait `dsl_flow::FlowNode` is not implemented for `Arc` + | + = help: the following other types implement trait `dsl_flow::FlowNode`: + ConditionalNode + DbNode + ExprGetNode + ExprSetNode + ForkJoinNode + GroupNode + HttpNode + LineageNode + and 3 others + = note: required for the cast from `Arc>` to `Arc` + = note: this error originates in the macro `sequence` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:193:53 + | +193 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +Some errors have detailed explanations: E0277, E0599. +For more information about an error, try `rustc --explain E0277`. +error: could not compile `dsl-flow` (test "flow_tests") due to 13 previous errors +2.565s +- test_conditional_node_then_else: warning: unused import: `serde_json::Value` + --> dsl-flow/src/engine.rs:5:5 + | +5 | use serde_json::Value; + | ^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + +warning: unused import: `rhai::EvalAltResult` + --> dsl-flow/src/expr.rs:43:13 + | +43 | use rhai::EvalAltResult; + | ^^^^^^^^^^^^^^^^^^^ + +warning: unused imports: `FlowNode` and `NodeRef` + --> dsl-flow/src/dsl.rs:1:19 + | +1 | use crate::node::{FlowNode, NodeRef}; + | ^^^^^^^^ ^^^^^^^ + +warning: unused imports: `ForkJoinNode`, `GroupNode`, and `SequenceNode` + --> dsl-flow/src/dsl.rs:2:19 + | +2 | use crate::node::{SequenceNode, ForkJoinNode, GroupNode, ExprSetNode, ExprGetNode, MergeMode}; + | ^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^ + +warning: unused import: `std::sync::Arc` + --> dsl-flow/src/dsl.rs:4:5 + | +4 | use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +warning: unused import: `FlowNode` + --> dsl-flow/src/engine.rs:3:19 + | +3 | use crate::node::{FlowNode, NodeOutput, NodeRef, SequenceNode}; + | ^^^^^^^^ + +warning: variable does not need to be mutable + --> dsl-flow/src/expr.rs:34:13 + | +34 | let mut engine = rhai::Engine::new(); + | ----^^^^^^ + | | + | help: remove this `mut` + | + = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:102:5 + | +100 | pub struct ExprSetNode { + | ----------- field in this struct +101 | id: NodeId, +102 | engine: ExprEngineKind, + | ^^^^^^ + | + = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:135:5 + | +133 | pub struct ExprGetNode { + | ----------- field in this struct +134 | id: NodeId, +135 | engine: ExprEngineKind, + | ^^^^^^ + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:270:5 + | +268 | pub struct ConditionalNode { + | --------------- field in this struct +269 | id: NodeId, +270 | engine: ExprEngineKind, + | ^^^^^^ + +warning: `dsl-flow` (lib) generated 10 warnings (run `cargo fix --lib -p dsl-flow` to apply 6 suggestions) + Compiling dsl-flow v0.1.0 (/Users/yangshiyou/Develop/code/rust/dsl_flow/dsl-flow) +warning: `dsl-flow` (lib test) generated 10 warnings (10 duplicates) +warning: unused import: `serde_json::json` + --> dsl-flow/examples/basic.rs:2:5 + | +2 | use serde_json::json; + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + +error[E0277]: the trait bound `Arc: FlowNode` is not satisfied + --> dsl-flow/examples/basic.rs:9:26 + | + 9 | let flow = Flow::new(sequence! { + | __________________________^ +10 | | expr_set(ExprEngineKind::Rhai, "1 + 2", "calc.sum"), +11 | | fork_join! { +12 | | expr_set(ExprEngineKind::Rhai, "ctx.calc.sum * 2", "calc.double"), +... | +15 | | }); + | |_____^ the trait `FlowNode` is not implemented for `Arc` + | + = help: the following other types implement trait `FlowNode`: + ConditionalNode + DbNode + ExprGetNode + ExprSetNode + ForkJoinNode + GroupNode + HttpNode + LineageNode + and 2 others + = note: required for the cast from `Arc>` to `Arc` + = note: this error originates in the macro `sequence` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/examples/basic.rs:17:53 + | +17 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `Box` to implement `From>` + +For more information about this error, try `rustc --explain E0277`. +warning: `dsl-flow` (example "basic") generated 1 warning +error: could not compile `dsl-flow` (example "basic") due to 2 previous errors; 1 warning emitted +warning: build failed, waiting for other jobs to finish... +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:45:53 + | +45 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0599]: no method named `clone` found for struct `ConditionalNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:62:46 + | +62 | let flow = Flow::new(sequence! { (*cond).clone() }); + | ^^^^^ method not found in `ConditionalNode` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:64:53 + | +64 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:112:53 + | +112 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:129:65 + | +129 | let _out1 = engine.run_stateful(s, &flow, ctx.clone()).await?; + | ------------------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:130:64 + | +130 | let out2 = engine.run_stateful(s, &flow, ctx.clone()).await?; + | ------------------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:146:53 + | +146 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0599]: no method named `clone` found for trait object `dyn dsl_flow::FlowNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:159:71 + | +159 | let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; + | ^^^^^ method not found in `dyn dsl_flow::FlowNode` + +error[E0599]: no method named `clone` found for trait object `dyn dsl_flow::FlowNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:159:86 + | +159 | let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; + | ^^^^^ method not found in `dyn dsl_flow::FlowNode` + +error[E0599]: no method named `clone` found for struct `GroupNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:160:47 + | +160 | let flow = Flow::new(sequence! { (*group).clone() }); + | ^^^^^ method not found in `GroupNode` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:163:51 + | +163 | let _ = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: the trait bound `Arc: dsl_flow::FlowNode` is not satisfied + --> dsl-flow/tests/flow_tests.rs:185:26 + | +185 | let flow = Flow::new(sequence! { + | __________________________^ +186 | | fork_join_merge! { "agg.fork", merge_mode_object_by_id(), +187 | | expr_set(ExprEngineKind::Rhai, "10", "a.x"), +188 | | expr_set(ExprEngineKind::Rhai, "20", "b.y") +189 | | }, +190 | | expr_get(ExprEngineKind::Rhai, "ctx.agg.fork") +191 | | }); + | |_____^ the trait `dsl_flow::FlowNode` is not implemented for `Arc` + | + = help: the following other types implement trait `dsl_flow::FlowNode`: + ConditionalNode + DbNode + ExprGetNode + ExprSetNode + ForkJoinNode + GroupNode + HttpNode + LineageNode + and 3 others + = note: required for the cast from `Arc>` to `Arc` + = note: this error originates in the macro `sequence` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:193:53 + | +193 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +Some errors have detailed explanations: E0277, E0599. +For more information about an error, try `rustc --explain E0277`. +error: could not compile `dsl-flow` (test "flow_tests") due to 13 previous errors +0.318s +- test_http_node_with_mock: warning: unused import: `serde_json::Value` + --> dsl-flow/src/engine.rs:5:5 + | +5 | use serde_json::Value; + | ^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + +warning: unused import: `rhai::EvalAltResult` + --> dsl-flow/src/expr.rs:43:13 + | +43 | use rhai::EvalAltResult; + | ^^^^^^^^^^^^^^^^^^^ + +warning: unused imports: `FlowNode` and `NodeRef` + --> dsl-flow/src/dsl.rs:1:19 + | +1 | use crate::node::{FlowNode, NodeRef}; + | ^^^^^^^^ ^^^^^^^ + +warning: unused imports: `ForkJoinNode`, `GroupNode`, and `SequenceNode` + --> dsl-flow/src/dsl.rs:2:19 + | +2 | use crate::node::{SequenceNode, ForkJoinNode, GroupNode, ExprSetNode, ExprGetNode, MergeMode}; + | ^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^ + +warning: unused import: `std::sync::Arc` + --> dsl-flow/src/dsl.rs:4:5 + | +4 | use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +warning: unused import: `FlowNode` + --> dsl-flow/src/engine.rs:3:19 + | +3 | use crate::node::{FlowNode, NodeOutput, NodeRef, SequenceNode}; + | ^^^^^^^^ + +warning: variable does not need to be mutable + --> dsl-flow/src/expr.rs:34:13 + | +34 | let mut engine = rhai::Engine::new(); + | ----^^^^^^ + | | + | help: remove this `mut` + | + = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:102:5 + | +100 | pub struct ExprSetNode { + | ----------- field in this struct +101 | id: NodeId, +102 | engine: ExprEngineKind, + | ^^^^^^ + | + = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:135:5 + | +133 | pub struct ExprGetNode { + | ----------- field in this struct +134 | id: NodeId, +135 | engine: ExprEngineKind, + | ^^^^^^ + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:270:5 + | +268 | pub struct ConditionalNode { + | --------------- field in this struct +269 | id: NodeId, +270 | engine: ExprEngineKind, + | ^^^^^^ + +warning: `dsl-flow` (lib) generated 10 warnings (run `cargo fix --lib -p dsl-flow` to apply 6 suggestions) + Compiling dsl-flow v0.1.0 (/Users/yangshiyou/Develop/code/rust/dsl_flow/dsl-flow) +warning: `dsl-flow` (lib test) generated 10 warnings (10 duplicates) +warning: unused import: `serde_json::json` + --> dsl-flow/examples/basic.rs:2:5 + | +2 | use serde_json::json; + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + +error[E0277]: the trait bound `Arc: FlowNode` is not satisfied + --> dsl-flow/examples/basic.rs:9:26 + | + 9 | let flow = Flow::new(sequence! { + | __________________________^ +10 | | expr_set(ExprEngineKind::Rhai, "1 + 2", "calc.sum"), +11 | | fork_join! { +12 | | expr_set(ExprEngineKind::Rhai, "ctx.calc.sum * 2", "calc.double"), +... | +15 | | }); + | |_____^ the trait `FlowNode` is not implemented for `Arc` + | + = help: the following other types implement trait `FlowNode`: + ConditionalNode + DbNode + ExprGetNode + ExprSetNode + ForkJoinNode + GroupNode + HttpNode + LineageNode + and 2 others + = note: required for the cast from `Arc>` to `Arc` + = note: this error originates in the macro `sequence` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/examples/basic.rs:17:53 + | +17 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `Box` to implement `From>` + +For more information about this error, try `rustc --explain E0277`. +warning: `dsl-flow` (example "basic") generated 1 warning +error: could not compile `dsl-flow` (example "basic") due to 2 previous errors; 1 warning emitted +warning: build failed, waiting for other jobs to finish... +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:45:53 + | +45 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0599]: no method named `clone` found for struct `ConditionalNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:62:46 + | +62 | let flow = Flow::new(sequence! { (*cond).clone() }); + | ^^^^^ method not found in `ConditionalNode` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:64:53 + | +64 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:112:53 + | +112 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:129:65 + | +129 | let _out1 = engine.run_stateful(s, &flow, ctx.clone()).await?; + | ------------------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:130:64 + | +130 | let out2 = engine.run_stateful(s, &flow, ctx.clone()).await?; + | ------------------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:146:53 + | +146 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0599]: no method named `clone` found for trait object `dyn dsl_flow::FlowNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:159:71 + | +159 | let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; + | ^^^^^ method not found in `dyn dsl_flow::FlowNode` + +error[E0599]: no method named `clone` found for trait object `dyn dsl_flow::FlowNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:159:86 + | +159 | let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; + | ^^^^^ method not found in `dyn dsl_flow::FlowNode` + +error[E0599]: no method named `clone` found for struct `GroupNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:160:47 + | +160 | let flow = Flow::new(sequence! { (*group).clone() }); + | ^^^^^ method not found in `GroupNode` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:163:51 + | +163 | let _ = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: the trait bound `Arc: dsl_flow::FlowNode` is not satisfied + --> dsl-flow/tests/flow_tests.rs:185:26 + | +185 | let flow = Flow::new(sequence! { + | __________________________^ +186 | | fork_join_merge! { "agg.fork", merge_mode_object_by_id(), +187 | | expr_set(ExprEngineKind::Rhai, "10", "a.x"), +188 | | expr_set(ExprEngineKind::Rhai, "20", "b.y") +189 | | }, +190 | | expr_get(ExprEngineKind::Rhai, "ctx.agg.fork") +191 | | }); + | |_____^ the trait `dsl_flow::FlowNode` is not implemented for `Arc` + | + = help: the following other types implement trait `dsl_flow::FlowNode`: + ConditionalNode + DbNode + ExprGetNode + ExprSetNode + ForkJoinNode + GroupNode + HttpNode + LineageNode + and 3 others + = note: required for the cast from `Arc>` to `Arc` + = note: this error originates in the macro `sequence` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:193:53 + | +193 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +Some errors have detailed explanations: E0277, E0599. +For more information about an error, try `rustc --explain E0277`. +error: could not compile `dsl-flow` (test "flow_tests") due to 13 previous errors +0.300s +- test_stateful_engine: warning: unused import: `serde_json::Value` + --> dsl-flow/src/engine.rs:5:5 + | +5 | use serde_json::Value; + | ^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + +warning: unused import: `rhai::EvalAltResult` + --> dsl-flow/src/expr.rs:43:13 + | +43 | use rhai::EvalAltResult; + | ^^^^^^^^^^^^^^^^^^^ + +warning: unused imports: `FlowNode` and `NodeRef` + --> dsl-flow/src/dsl.rs:1:19 + | +1 | use crate::node::{FlowNode, NodeRef}; + | ^^^^^^^^ ^^^^^^^ + +warning: unused imports: `ForkJoinNode`, `GroupNode`, and `SequenceNode` + --> dsl-flow/src/dsl.rs:2:19 + | +2 | use crate::node::{SequenceNode, ForkJoinNode, GroupNode, ExprSetNode, ExprGetNode, MergeMode}; + | ^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^ + +warning: unused import: `std::sync::Arc` + --> dsl-flow/src/dsl.rs:4:5 + | +4 | use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +warning: unused import: `FlowNode` + --> dsl-flow/src/engine.rs:3:19 + | +3 | use crate::node::{FlowNode, NodeOutput, NodeRef, SequenceNode}; + | ^^^^^^^^ + +warning: variable does not need to be mutable + --> dsl-flow/src/expr.rs:34:13 + | +34 | let mut engine = rhai::Engine::new(); + | ----^^^^^^ + | | + | help: remove this `mut` + | + = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:102:5 + | +100 | pub struct ExprSetNode { + | ----------- field in this struct +101 | id: NodeId, +102 | engine: ExprEngineKind, + | ^^^^^^ + | + = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:135:5 + | +133 | pub struct ExprGetNode { + | ----------- field in this struct +134 | id: NodeId, +135 | engine: ExprEngineKind, + | ^^^^^^ + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:270:5 + | +268 | pub struct ConditionalNode { + | --------------- field in this struct +269 | id: NodeId, +270 | engine: ExprEngineKind, + | ^^^^^^ + +warning: `dsl-flow` (lib) generated 10 warnings (run `cargo fix --lib -p dsl-flow` to apply 6 suggestions) + Compiling dsl-flow v0.1.0 (/Users/yangshiyou/Develop/code/rust/dsl_flow/dsl-flow) +warning: `dsl-flow` (lib test) generated 10 warnings (10 duplicates) +warning: unused import: `serde_json::json` + --> dsl-flow/examples/basic.rs:2:5 + | +2 | use serde_json::json; + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + +error[E0277]: the trait bound `Arc: FlowNode` is not satisfied + --> dsl-flow/examples/basic.rs:9:26 + | + 9 | let flow = Flow::new(sequence! { + | __________________________^ +10 | | expr_set(ExprEngineKind::Rhai, "1 + 2", "calc.sum"), +11 | | fork_join! { +12 | | expr_set(ExprEngineKind::Rhai, "ctx.calc.sum * 2", "calc.double"), +... | +15 | | }); + | |_____^ the trait `FlowNode` is not implemented for `Arc` + | + = help: the following other types implement trait `FlowNode`: + ConditionalNode + DbNode + ExprGetNode + ExprSetNode + ForkJoinNode + GroupNode + HttpNode + LineageNode + and 2 others + = note: required for the cast from `Arc>` to `Arc` + = note: this error originates in the macro `sequence` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/examples/basic.rs:17:53 + | +17 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `Box` to implement `From>` + +For more information about this error, try `rustc --explain E0277`. +warning: `dsl-flow` (example "basic") generated 1 warning +error: could not compile `dsl-flow` (example "basic") due to 2 previous errors; 1 warning emitted +warning: build failed, waiting for other jobs to finish... +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:45:53 + | +45 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0599]: no method named `clone` found for struct `ConditionalNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:62:46 + | +62 | let flow = Flow::new(sequence! { (*cond).clone() }); + | ^^^^^ method not found in `ConditionalNode` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:64:53 + | +64 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:112:53 + | +112 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:129:65 + | +129 | let _out1 = engine.run_stateful(s, &flow, ctx.clone()).await?; + | ------------------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:130:64 + | +130 | let out2 = engine.run_stateful(s, &flow, ctx.clone()).await?; + | ------------------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:146:53 + | +146 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0599]: no method named `clone` found for trait object `dyn dsl_flow::FlowNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:159:71 + | +159 | let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; + | ^^^^^ method not found in `dyn dsl_flow::FlowNode` + +error[E0599]: no method named `clone` found for trait object `dyn dsl_flow::FlowNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:159:86 + | +159 | let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; + | ^^^^^ method not found in `dyn dsl_flow::FlowNode` + +error[E0599]: no method named `clone` found for struct `GroupNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:160:47 + | +160 | let flow = Flow::new(sequence! { (*group).clone() }); + | ^^^^^ method not found in `GroupNode` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:163:51 + | +163 | let _ = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: the trait bound `Arc: dsl_flow::FlowNode` is not satisfied + --> dsl-flow/tests/flow_tests.rs:185:26 + | +185 | let flow = Flow::new(sequence! { + | __________________________^ +186 | | fork_join_merge! { "agg.fork", merge_mode_object_by_id(), +187 | | expr_set(ExprEngineKind::Rhai, "10", "a.x"), +188 | | expr_set(ExprEngineKind::Rhai, "20", "b.y") +189 | | }, +190 | | expr_get(ExprEngineKind::Rhai, "ctx.agg.fork") +191 | | }); + | |_____^ the trait `dsl_flow::FlowNode` is not implemented for `Arc` + | + = help: the following other types implement trait `dsl_flow::FlowNode`: + ConditionalNode + DbNode + ExprGetNode + ExprSetNode + ForkJoinNode + GroupNode + HttpNode + LineageNode + and 3 others + = note: required for the cast from `Arc>` to `Arc` + = note: this error originates in the macro `sequence` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:193:53 + | +193 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +Some errors have detailed explanations: E0277, E0599. +For more information about an error, try `rustc --explain E0277`. +error: could not compile `dsl-flow` (test "flow_tests") due to 13 previous errors +0.298s +- test_db_and_mq_nodes: warning: unused import: `serde_json::Value` + --> dsl-flow/src/engine.rs:5:5 + | +5 | use serde_json::Value; + | ^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + +warning: unused import: `rhai::EvalAltResult` + --> dsl-flow/src/expr.rs:43:13 + | +43 | use rhai::EvalAltResult; + | ^^^^^^^^^^^^^^^^^^^ + +warning: unused imports: `FlowNode` and `NodeRef` + --> dsl-flow/src/dsl.rs:1:19 + | +1 | use crate::node::{FlowNode, NodeRef}; + | ^^^^^^^^ ^^^^^^^ + +warning: unused imports: `ForkJoinNode`, `GroupNode`, and `SequenceNode` + --> dsl-flow/src/dsl.rs:2:19 + | +2 | use crate::node::{SequenceNode, ForkJoinNode, GroupNode, ExprSetNode, ExprGetNode, MergeMode}; + | ^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^ + +warning: unused import: `std::sync::Arc` + --> dsl-flow/src/dsl.rs:4:5 + | +4 | use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +warning: unused import: `FlowNode` + --> dsl-flow/src/engine.rs:3:19 + | +3 | use crate::node::{FlowNode, NodeOutput, NodeRef, SequenceNode}; + | ^^^^^^^^ + +warning: variable does not need to be mutable + --> dsl-flow/src/expr.rs:34:13 + | +34 | let mut engine = rhai::Engine::new(); + | ----^^^^^^ + | | + | help: remove this `mut` + | + = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:102:5 + | +100 | pub struct ExprSetNode { + | ----------- field in this struct +101 | id: NodeId, +102 | engine: ExprEngineKind, + | ^^^^^^ + | + = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:135:5 + | +133 | pub struct ExprGetNode { + | ----------- field in this struct +134 | id: NodeId, +135 | engine: ExprEngineKind, + | ^^^^^^ + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:270:5 + | +268 | pub struct ConditionalNode { + | --------------- field in this struct +269 | id: NodeId, +270 | engine: ExprEngineKind, + | ^^^^^^ + +warning: `dsl-flow` (lib) generated 10 warnings (run `cargo fix --lib -p dsl-flow` to apply 6 suggestions) + Compiling dsl-flow v0.1.0 (/Users/yangshiyou/Develop/code/rust/dsl_flow/dsl-flow) +warning: `dsl-flow` (lib test) generated 10 warnings (10 duplicates) +warning: unused import: `serde_json::json` + --> dsl-flow/examples/basic.rs:2:5 + | +2 | use serde_json::json; + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + +error[E0277]: the trait bound `Arc: FlowNode` is not satisfied + --> dsl-flow/examples/basic.rs:9:26 + | + 9 | let flow = Flow::new(sequence! { + | __________________________^ +10 | | expr_set(ExprEngineKind::Rhai, "1 + 2", "calc.sum"), +11 | | fork_join! { +12 | | expr_set(ExprEngineKind::Rhai, "ctx.calc.sum * 2", "calc.double"), +... | +15 | | }); + | |_____^ the trait `FlowNode` is not implemented for `Arc` + | + = help: the following other types implement trait `FlowNode`: + ConditionalNode + DbNode + ExprGetNode + ExprSetNode + ForkJoinNode + GroupNode + HttpNode + LineageNode + and 2 others + = note: required for the cast from `Arc>` to `Arc` + = note: this error originates in the macro `sequence` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/examples/basic.rs:17:53 + | +17 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `Box` to implement `From>` + +For more information about this error, try `rustc --explain E0277`. +warning: `dsl-flow` (example "basic") generated 1 warning +error: could not compile `dsl-flow` (example "basic") due to 2 previous errors; 1 warning emitted +warning: build failed, waiting for other jobs to finish... +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:45:53 + | +45 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0599]: no method named `clone` found for struct `ConditionalNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:62:46 + | +62 | let flow = Flow::new(sequence! { (*cond).clone() }); + | ^^^^^ method not found in `ConditionalNode` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:64:53 + | +64 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:112:53 + | +112 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:129:65 + | +129 | let _out1 = engine.run_stateful(s, &flow, ctx.clone()).await?; + | ------------------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:130:64 + | +130 | let out2 = engine.run_stateful(s, &flow, ctx.clone()).await?; + | ------------------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:146:53 + | +146 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0599]: no method named `clone` found for trait object `dyn dsl_flow::FlowNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:159:71 + | +159 | let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; + | ^^^^^ method not found in `dyn dsl_flow::FlowNode` + +error[E0599]: no method named `clone` found for trait object `dyn dsl_flow::FlowNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:159:86 + | +159 | let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; + | ^^^^^ method not found in `dyn dsl_flow::FlowNode` + +error[E0599]: no method named `clone` found for struct `GroupNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:160:47 + | +160 | let flow = Flow::new(sequence! { (*group).clone() }); + | ^^^^^ method not found in `GroupNode` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:163:51 + | +163 | let _ = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: the trait bound `Arc: dsl_flow::FlowNode` is not satisfied + --> dsl-flow/tests/flow_tests.rs:185:26 + | +185 | let flow = Flow::new(sequence! { + | __________________________^ +186 | | fork_join_merge! { "agg.fork", merge_mode_object_by_id(), +187 | | expr_set(ExprEngineKind::Rhai, "10", "a.x"), +188 | | expr_set(ExprEngineKind::Rhai, "20", "b.y") +189 | | }, +190 | | expr_get(ExprEngineKind::Rhai, "ctx.agg.fork") +191 | | }); + | |_____^ the trait `dsl_flow::FlowNode` is not implemented for `Arc` + | + = help: the following other types implement trait `dsl_flow::FlowNode`: + ConditionalNode + DbNode + ExprGetNode + ExprSetNode + ForkJoinNode + GroupNode + HttpNode + LineageNode + and 3 others + = note: required for the cast from `Arc>` to `Arc` + = note: this error originates in the macro `sequence` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:193:53 + | +193 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +Some errors have detailed explanations: E0277, E0599. +For more information about an error, try `rustc --explain E0277`. +error: could not compile `dsl-flow` (test "flow_tests") due to 13 previous errors +0.298s +- test_group_parallel_sleep: warning: unused import: `serde_json::Value` + --> dsl-flow/src/engine.rs:5:5 + | +5 | use serde_json::Value; + | ^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + +warning: unused import: `rhai::EvalAltResult` + --> dsl-flow/src/expr.rs:43:13 + | +43 | use rhai::EvalAltResult; + | ^^^^^^^^^^^^^^^^^^^ + +warning: unused imports: `FlowNode` and `NodeRef` + --> dsl-flow/src/dsl.rs:1:19 + | +1 | use crate::node::{FlowNode, NodeRef}; + | ^^^^^^^^ ^^^^^^^ + +warning: unused imports: `ForkJoinNode`, `GroupNode`, and `SequenceNode` + --> dsl-flow/src/dsl.rs:2:19 + | +2 | use crate::node::{SequenceNode, ForkJoinNode, GroupNode, ExprSetNode, ExprGetNode, MergeMode}; + | ^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^ + +warning: unused import: `std::sync::Arc` + --> dsl-flow/src/dsl.rs:4:5 + | +4 | use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +warning: unused import: `FlowNode` + --> dsl-flow/src/engine.rs:3:19 + | +3 | use crate::node::{FlowNode, NodeOutput, NodeRef, SequenceNode}; + | ^^^^^^^^ + +warning: variable does not need to be mutable + --> dsl-flow/src/expr.rs:34:13 + | +34 | let mut engine = rhai::Engine::new(); + | ----^^^^^^ + | | + | help: remove this `mut` + | + = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:102:5 + | +100 | pub struct ExprSetNode { + | ----------- field in this struct +101 | id: NodeId, +102 | engine: ExprEngineKind, + | ^^^^^^ + | + = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:135:5 + | +133 | pub struct ExprGetNode { + | ----------- field in this struct +134 | id: NodeId, +135 | engine: ExprEngineKind, + | ^^^^^^ + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:270:5 + | +268 | pub struct ConditionalNode { + | --------------- field in this struct +269 | id: NodeId, +270 | engine: ExprEngineKind, + | ^^^^^^ + +warning: `dsl-flow` (lib) generated 10 warnings (run `cargo fix --lib -p dsl-flow` to apply 6 suggestions) + Compiling dsl-flow v0.1.0 (/Users/yangshiyou/Develop/code/rust/dsl_flow/dsl-flow) +warning: `dsl-flow` (lib test) generated 10 warnings (10 duplicates) +warning: unused import: `serde_json::json` + --> dsl-flow/examples/basic.rs:2:5 + | +2 | use serde_json::json; + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + +error[E0277]: the trait bound `Arc: FlowNode` is not satisfied + --> dsl-flow/examples/basic.rs:9:26 + | + 9 | let flow = Flow::new(sequence! { + | __________________________^ +10 | | expr_set(ExprEngineKind::Rhai, "1 + 2", "calc.sum"), +11 | | fork_join! { +12 | | expr_set(ExprEngineKind::Rhai, "ctx.calc.sum * 2", "calc.double"), +... | +15 | | }); + | |_____^ the trait `FlowNode` is not implemented for `Arc` + | + = help: the following other types implement trait `FlowNode`: + ConditionalNode + DbNode + ExprGetNode + ExprSetNode + ForkJoinNode + GroupNode + HttpNode + LineageNode + and 2 others + = note: required for the cast from `Arc>` to `Arc` + = note: this error originates in the macro `sequence` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/examples/basic.rs:17:53 + | +17 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `Box` to implement `From>` + +For more information about this error, try `rustc --explain E0277`. +warning: `dsl-flow` (example "basic") generated 1 warning +error: could not compile `dsl-flow` (example "basic") due to 2 previous errors; 1 warning emitted +warning: build failed, waiting for other jobs to finish... +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:45:53 + | +45 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0599]: no method named `clone` found for struct `ConditionalNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:62:46 + | +62 | let flow = Flow::new(sequence! { (*cond).clone() }); + | ^^^^^ method not found in `ConditionalNode` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:64:53 + | +64 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:112:53 + | +112 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:129:65 + | +129 | let _out1 = engine.run_stateful(s, &flow, ctx.clone()).await?; + | ------------------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:130:64 + | +130 | let out2 = engine.run_stateful(s, &flow, ctx.clone()).await?; + | ------------------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:146:53 + | +146 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0599]: no method named `clone` found for trait object `dyn dsl_flow::FlowNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:159:71 + | +159 | let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; + | ^^^^^ method not found in `dyn dsl_flow::FlowNode` + +error[E0599]: no method named `clone` found for trait object `dyn dsl_flow::FlowNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:159:86 + | +159 | let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; + | ^^^^^ method not found in `dyn dsl_flow::FlowNode` + +error[E0599]: no method named `clone` found for struct `GroupNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:160:47 + | +160 | let flow = Flow::new(sequence! { (*group).clone() }); + | ^^^^^ method not found in `GroupNode` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:163:51 + | +163 | let _ = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: the trait bound `Arc: dsl_flow::FlowNode` is not satisfied + --> dsl-flow/tests/flow_tests.rs:185:26 + | +185 | let flow = Flow::new(sequence! { + | __________________________^ +186 | | fork_join_merge! { "agg.fork", merge_mode_object_by_id(), +187 | | expr_set(ExprEngineKind::Rhai, "10", "a.x"), +188 | | expr_set(ExprEngineKind::Rhai, "20", "b.y") +189 | | }, +190 | | expr_get(ExprEngineKind::Rhai, "ctx.agg.fork") +191 | | }); + | |_____^ the trait `dsl_flow::FlowNode` is not implemented for `Arc` + | + = help: the following other types implement trait `dsl_flow::FlowNode`: + ConditionalNode + DbNode + ExprGetNode + ExprSetNode + ForkJoinNode + GroupNode + HttpNode + LineageNode + and 3 others + = note: required for the cast from `Arc>` to `Arc` + = note: this error originates in the macro `sequence` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:193:53 + | +193 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +Some errors have detailed explanations: E0277, E0599. +For more information about an error, try `rustc --explain E0277`. +error: could not compile `dsl-flow` (test "flow_tests") due to 13 previous errors +0.326s +- test_expr_set_without_engine_error: warning: unused import: `serde_json::Value` + --> dsl-flow/src/engine.rs:5:5 + | +5 | use serde_json::Value; + | ^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + +warning: unused import: `rhai::EvalAltResult` + --> dsl-flow/src/expr.rs:43:13 + | +43 | use rhai::EvalAltResult; + | ^^^^^^^^^^^^^^^^^^^ + +warning: unused imports: `FlowNode` and `NodeRef` + --> dsl-flow/src/dsl.rs:1:19 + | +1 | use crate::node::{FlowNode, NodeRef}; + | ^^^^^^^^ ^^^^^^^ + +warning: unused imports: `ForkJoinNode`, `GroupNode`, and `SequenceNode` + --> dsl-flow/src/dsl.rs:2:19 + | +2 | use crate::node::{SequenceNode, ForkJoinNode, GroupNode, ExprSetNode, ExprGetNode, MergeMode}; + | ^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^ + +warning: unused import: `std::sync::Arc` + --> dsl-flow/src/dsl.rs:4:5 + | +4 | use std::sync::Arc; + | ^^^^^^^^^^^^^^ + +warning: unused import: `FlowNode` + --> dsl-flow/src/engine.rs:3:19 + | +3 | use crate::node::{FlowNode, NodeOutput, NodeRef, SequenceNode}; + | ^^^^^^^^ + +warning: variable does not need to be mutable + --> dsl-flow/src/expr.rs:34:13 + | +34 | let mut engine = rhai::Engine::new(); + | ----^^^^^^ + | | + | help: remove this `mut` + | + = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:102:5 + | +100 | pub struct ExprSetNode { + | ----------- field in this struct +101 | id: NodeId, +102 | engine: ExprEngineKind, + | ^^^^^^ + | + = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:135:5 + | +133 | pub struct ExprGetNode { + | ----------- field in this struct +134 | id: NodeId, +135 | engine: ExprEngineKind, + | ^^^^^^ + +warning: field `engine` is never read + --> dsl-flow/src/node.rs:270:5 + | +268 | pub struct ConditionalNode { + | --------------- field in this struct +269 | id: NodeId, +270 | engine: ExprEngineKind, + | ^^^^^^ + +warning: `dsl-flow` (lib) generated 10 warnings (run `cargo fix --lib -p dsl-flow` to apply 6 suggestions) + Compiling dsl-flow v0.1.0 (/Users/yangshiyou/Develop/code/rust/dsl_flow/dsl-flow) +warning: `dsl-flow` (lib test) generated 10 warnings (10 duplicates) +warning: unused import: `serde_json::json` + --> dsl-flow/examples/basic.rs:2:5 + | +2 | use serde_json::json; + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default + +error[E0277]: the trait bound `Arc: FlowNode` is not satisfied + --> dsl-flow/examples/basic.rs:9:26 + | + 9 | let flow = Flow::new(sequence! { + | __________________________^ +10 | | expr_set(ExprEngineKind::Rhai, "1 + 2", "calc.sum"), +11 | | fork_join! { +12 | | expr_set(ExprEngineKind::Rhai, "ctx.calc.sum * 2", "calc.double"), +... | +15 | | }); + | |_____^ the trait `FlowNode` is not implemented for `Arc` + | + = help: the following other types implement trait `FlowNode`: + ConditionalNode + DbNode + ExprGetNode + ExprSetNode + ForkJoinNode + GroupNode + HttpNode + LineageNode + and 2 others + = note: required for the cast from `Arc>` to `Arc` + = note: this error originates in the macro `sequence` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/examples/basic.rs:17:53 + | +17 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `Box` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:45:53 + | +45 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +For more information about this error, try `rustc --explain E0277`. +warning: `dsl-flow` (example "basic") generated 1 warning +error: could not compile `dsl-flow` (example "basic") due to 2 previous errors; 1 warning emitted +warning: build failed, waiting for other jobs to finish... +error[E0599]: no method named `clone` found for struct `ConditionalNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:62:46 + | +62 | let flow = Flow::new(sequence! { (*cond).clone() }); + | ^^^^^ method not found in `ConditionalNode` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:64:53 + | +64 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:112:53 + | +112 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:129:65 + | +129 | let _out1 = engine.run_stateful(s, &flow, ctx.clone()).await?; + | ------------------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:130:64 + | +130 | let out2 = engine.run_stateful(s, &flow, ctx.clone()).await?; + | ------------------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:146:53 + | +146 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0599]: no method named `clone` found for trait object `dyn dsl_flow::FlowNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:159:71 + | +159 | let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; + | ^^^^^ method not found in `dyn dsl_flow::FlowNode` + +error[E0599]: no method named `clone` found for trait object `dyn dsl_flow::FlowNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:159:86 + | +159 | let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; + | ^^^^^ method not found in `dyn dsl_flow::FlowNode` + +error[E0599]: no method named `clone` found for struct `GroupNode` in the current scope + --> dsl-flow/tests/flow_tests.rs:160:47 + | +160 | let flow = Flow::new(sequence! { (*group).clone() }); + | ^^^^^ method not found in `GroupNode` + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:163:51 + | +163 | let _ = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +error[E0277]: the trait bound `Arc: dsl_flow::FlowNode` is not satisfied + --> dsl-flow/tests/flow_tests.rs:185:26 + | +185 | let flow = Flow::new(sequence! { + | __________________________^ +186 | | fork_join_merge! { "agg.fork", merge_mode_object_by_id(), +187 | | expr_set(ExprEngineKind::Rhai, "10", "a.x"), +188 | | expr_set(ExprEngineKind::Rhai, "20", "b.y") +189 | | }, +190 | | expr_get(ExprEngineKind::Rhai, "ctx.agg.fork") +191 | | }); + | |_____^ the trait `dsl_flow::FlowNode` is not implemented for `Arc` + | + = help: the following other types implement trait `dsl_flow::FlowNode`: + ConditionalNode + DbNode + ExprGetNode + ExprSetNode + ForkJoinNode + GroupNode + HttpNode + LineageNode + and 3 others + = note: required for the cast from `Arc>` to `Arc` + = note: this error originates in the macro `sequence` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `?` couldn't convert the error: `dyn std::error::Error + Send + Sync: Sized` is not satisfied + --> dsl-flow/tests/flow_tests.rs:193:53 + | +193 | let out = engine.run_stateless(&flow, ctx).await?; + | --------------------------------------^ doesn't have a size known at compile-time + | | + | this can't be annotated with `?` because it has type `Result<_, Box>` + | + = help: the trait `Sized` is not implemented for `dyn std::error::Error + Send + Sync` + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `std::error::Error` + = note: required for `anyhow::Error` to implement `From>` + +Some errors have detailed explanations: E0277, E0599. +For more information about an error, try `rustc --explain E0277`. +error: could not compile `dsl-flow` (test "flow_tests") due to 13 previous errors +0.340s diff --git a/doc/performance-js.md b/doc/performance-js.md index d81d40f..88a40cd 100644 --- a/doc/performance-js.md +++ b/doc/performance-js.md @@ -1,12 +1,19 @@ # dsl-flow Test Performance ## JS feature -- test_rhai_expr_set_and_get: 0.269s -- test_conditional_node_then_else: 0.396s -- test_http_node_with_mock: 0.462s -- test_stateful_engine: 0.447s -- test_db_and_mq_nodes: 0.544s -- test_group_parallel_sleep: 1.425s -- test_expr_set_without_engine_error: 1.074s -- test_js_expr_and_fork_join: 0.586s - +- test_rhai_expr_set_and_get: error: cannot specify features for packages outside of workspace +0.011s +- test_conditional_node_then_else: error: cannot specify features for packages outside of workspace +0.009s +- test_http_node_with_mock: error: cannot specify features for packages outside of workspace +0.009s +- test_stateful_engine: error: cannot specify features for packages outside of workspace +0.010s +- test_db_and_mq_nodes: error: cannot specify features for packages outside of workspace +0.011s +- test_group_parallel_sleep: error: cannot specify features for packages outside of workspace +0.010s +- test_expr_set_without_engine_error: error: cannot specify features for packages outside of workspace +0.010s +- test_js_expr_and_fork_join: error: cannot specify features for packages outside of workspace +0.012s diff --git a/dsl_flow/Cargo.toml b/dsl-flow/Cargo.toml similarity index 92% rename from dsl_flow/Cargo.toml rename to dsl-flow/Cargo.toml index 3a58857..e56a756 100644 --- a/dsl_flow/Cargo.toml +++ b/dsl-flow/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dsl-flow" version = "0.1.0" -edition = "2024" +edition = "2021" license = "MIT" description = "A Rust DSL-based workflow engine supporting stateful/stateless flows, async nodes, fork-join, and extensible expression engines (Rhai/JS)." readme = "README.md" @@ -28,7 +28,7 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] } # Optional engines and nodes -rhai = { version = "1", optional = true, features = ["serde"] } +rhai = { version = "1", optional = true, features = ["serde", "sync"] } boa_engine = { version = "0.20", optional = true } reqwest = { version = "0.12", optional = true, features = ["json", "rustls-tls"] } diff --git a/dsl-flow/README.md b/dsl-flow/README.md new file mode 100644 index 0000000..a4f415d --- /dev/null +++ b/dsl-flow/README.md @@ -0,0 +1,164 @@ +# DSL Flow + +[![Crates.io](https://img.shields.io/crates/v/dsl-flow.svg)](https://crates.io/crates/dsl-flow) +[![Docs.rs](https://docs.rs/dsl-flow/badge.svg)](https://docs.rs/dsl-flow) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) + +**DSL Flow** 是一个基于 Rust 的高性能、可扩展的工作流引擎。它旨在通过声明式的 DSL(领域特定语言)来编排复杂的异步任务,支持状态管理、多语言脚本执行(Rhai/JavaScript)以及灵活的控制流。 + +该项目专为构建中间件、API 编排层和数据处理管道而设计,采用清晰的分层架构,易于扩展和维护。 + +## ✨ 核心特性 + +* **声明式 DSL**: 使用 Rust 宏 (`sequence!`, `fork_join!`, `group!`) 轻松定义复杂的流程结构。 +* **多语言支持**: + * **表达式求值**: 支持 Rhai 和 JS 表达式,用于条件判断和简单数据处理。 + * **代码执行**: 支持运行完整的 Rhai 或 JS 代码块,处理复杂业务逻辑。 +* **强大的控制流**: + * **Sequence**: 顺序执行任务。 + * **Fork-Join**: 并行执行多个分支,支持灵活的结果合并策略(Array 或 Object)。 + * **Conditional**: 基于表达式结果的条件分支 (`if-else`)。 +* **状态管理**: 支持无状态(Stateless)和有状态(Stateful)执行模式。内置内存状态存储,可扩展至 Redis/Database。 +* **内置节点**: 提供 HTTP 请求、数据库模拟、消息队列模拟、血缘追踪等常用节点。 +* **异步高性能**: 基于 `Tokio` 和 `Async Trait`,全链路异步非阻塞。 +* **模块化架构**: 内核(Core)、运行时(Runtime)、控制流(Control)与业务节点(Nodes)分离,职责单一。 + +## 📦 安装 + +在你的 `Cargo.toml` 中添加依赖: + +```toml +[dependencies] +dsl-flow = "0.1" +``` + +启用特定特性(可选): + +```toml +[dependencies] +dsl-flow = { version = "0.1", features = ["js", "http"] } +``` + +* `rhai`: 启用 Rhai 脚本引擎(默认开启)。 +* `js`: 启用 JavaScript (Boa) 脚本引擎。 +* `http`: 启用 HTTP 请求节点支持。 + +## 🚀 快速开始 + +以下示例展示了如何定义一个简单的流程:先计算 `1+2`,然后并行计算其 2 倍和 3 倍。 + +```rust +use dsl_flow::*; +use dsl_flow::dsl::*; // 导入 DSL 宏和辅助函数 + +#[tokio::main] +async fn main() -> Result<(), Box> { + // 1. 初始化引擎 + let store = InMemoryStateStore::default(); + let engine = FlowEngine::new(store, FlowOptions { + stateful: false, + expr_engine: ExprEngineKind::Rhai + }); + + // 2. 定义流程 + let flow = Flow::new(sequence! { + // 步骤 1: 设置初始值 + expr_set(ExprEngineKind::Rhai, "1 + 2", "calc.sum"), + + // 步骤 2: 并行执行 + fork_join! { + // 使用 Rhai 代码块 + rhai("let sum = ctx.calc.sum; sum * 2"), + // 使用 JS 代码块 + js("const sum = ctx.calc.sum; sum * 3") + } + }); + + // 3. 运行流程 + let ctx = Context::new(); + let output = engine.run_stateless(&flow, ctx).await?; + + println!("Result: {}", output.data); + Ok(()) +} +``` + +## 🏗️ 架构概览 + +DSL Flow 采用清晰的分层架构设计,各层职责分明: + +```text +dsl-flow/src/ + ├── core/ <-- [内核层] 纯粹的抽象和基础类型 + │ ├── mod.rs + │ ├── traits.rs (FlowNode, ExprEngine 接口定义) + │ ├── context.rs (Context 上下文管理) + │ ├── error.rs (统一的错误处理) + │ ├── executor.rs (TaskExecutor 执行器特征) + │ └── types.rs (NodeId, NodeOutput 等通用类型) + │ + ├── runtime/ <-- [运行时层] 负责跑起来 + │ ├── mod.rs + │ ├── engine.rs (FlowEngine 调度器) + │ ├── state.rs (FlowState 状态存储) + │ └── expr.rs (Rhai/JS 脚本引擎的具体实现) + │ + ├── control/ <-- [控制层] 流程控制逻辑 + │ ├── mod.rs + │ ├── sequence.rs (顺序执行) + │ ├── fork_join.rs (并行分支) + │ └── conditional.rs (条件判断) + │ + ├── expression/ <-- [表达式层] 表达式处理 + │ ├── mod.rs + │ ├── expr_set.rs (设置变量) + │ └── expr_get.rs (获取变量) + │ + ├── nodes/ <-- [业务层] 具体的业务节点实现 + │ ├── mod.rs + │ ├── code.rs (通用代码执行: Rhai/JS) + │ ├── io.rs (IO 操作: Http, Db, Mq) + │ └── lineage.rs (血缘追踪) + │ + ├── dsl.rs <-- [接口层] 对外暴露的 DSL 构建宏和辅助函数 + └── lib.rs <-- [入口] 统一导出公共 API +``` + +### 分层说明 + +* **Core**: 定义了 `FlowNode` 和 `TaskExecutor` 接口,是整个框架的基石。 +* **Runtime**: 负责调度流程执行、管理状态和实例化脚本引擎。 +* **Control**: 实现了流程控制逻辑,如串行、并行、分支等。 +* **Expression**: 专注于简单的表达式求值和上下文变量操作。 +* **Nodes**: 包含所有具体的业务逻辑节点,如代码执行、网络请求、数据库操作等。 + +## 🔧 节点类型 + +### 控制流 (Control Flow) +* **Sequence**: `sequence! { node1, node2 }` - 按顺序执行。 +* **ForkJoin**: `fork_join! { node1, node2 }` - 并行执行,结果收集为数组。 +* **Conditional**: `ConditionalExecutor` - 基于表达式结果的条件分支 (`if-else`)。 + +### 代码与表达式 (Code & Expression) +* **ExprSet**: `expr_set(...)` - 执行表达式并将结果写入 Context。 +* **Rhai Code**: `rhai("code")` - 执行一段 Rhai 代码。 +* **JS Code**: `js("code")` - 执行一段 JavaScript 代码。 + +### IO 与副作用 (Side Effects) +* **HttpNode**: `http_get/post(...)` - 发送 HTTP 请求。 +* **DbNode**: `db_node(...)` - 模拟数据库操作。 +* **MqNode**: `mq_node(...)` - 模拟消息队列发送。 + +## 🤝 贡献 + +欢迎提交 Issue 和 Pull Request! + +1. Fork 本仓库。 +2. 创建你的特性分支 (`git checkout -b feature/AmazingFeature`)。 +3. 提交你的更改 (`git commit -m 'Add some AmazingFeature'`)。 +4. 推送到分支 (`git push origin feature/AmazingFeature`)。 +5. 打开一个 Pull Request。 + +## 📄 许可证 + +本项目采用 MIT 许可证 - 详情请见 [LICENSE](LICENSE) 文件。 diff --git a/dsl_flow/examples/basic.rs b/dsl-flow/examples/basic.rs similarity index 89% rename from dsl_flow/examples/basic.rs rename to dsl-flow/examples/basic.rs index a3b53b5..b43ae22 100644 --- a/dsl_flow/examples/basic.rs +++ b/dsl-flow/examples/basic.rs @@ -1,8 +1,7 @@ use dsl_flow::*; -use serde_json::json; #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> Result<(), Box> { tracing_subscriber::fmt().with_env_filter("info").init(); let store = InMemoryStateStore::default(); let engine = FlowEngine::new(store, FlowOptions { stateful: false, expr_engine: ExprEngineKind::Rhai }); diff --git a/dsl_flow/examples/report.rs b/dsl-flow/examples/report.rs similarity index 100% rename from dsl_flow/examples/report.rs rename to dsl-flow/examples/report.rs diff --git a/dsl-flow/src/control/conditional.rs b/dsl-flow/src/control/conditional.rs new file mode 100644 index 0000000..12021fa --- /dev/null +++ b/dsl-flow/src/control/conditional.rs @@ -0,0 +1,31 @@ +use crate::core::context::Context; +use crate::core::error::NodeError; +use crate::core::executor::TaskExecutor; +use crate::core::traits::ExprEngine; +use crate::core::types::NodeRef; +use serde_json::Value; + +/// 条件分支执行器 +#[derive(Clone)] +pub struct ConditionalExecutor { + #[allow(dead_code)] + pub engine: crate::runtime::ExprEngineKind, + pub condition: String, + pub then_node: NodeRef, + pub else_node: Option, +} + +#[async_trait::async_trait] +impl TaskExecutor for ConditionalExecutor { + async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result { + let engine = expr.ok_or_else(|| NodeError::Exec("Expr engine not provided".into()))?; + let val = engine.eval(&self.condition, ctx).await.map_err(|e| NodeError::Exec(e.to_string()))?; + let cond = match val { + Value::Bool(b) => b, + _ => false, + }; + let selected = if cond { &self.then_node } else { self.else_node.as_ref().unwrap_or(&self.then_node) }; + let out = selected.execute(ctx, Some(engine)).await?; + Ok(out.data) + } +} diff --git a/dsl-flow/src/control/fork_join.rs b/dsl-flow/src/control/fork_join.rs new file mode 100644 index 0000000..01ae171 --- /dev/null +++ b/dsl-flow/src/control/fork_join.rs @@ -0,0 +1,49 @@ +use crate::core::context::Context; +use crate::core::error::NodeError; +use crate::core::executor::TaskExecutor; +use crate::core::traits::ExprEngine; +use crate::core::types::NodeRef; +use crate::control::merge_mode::MergeMode; +use serde_json::Value; + +/// 并行分支执行器 (Fork-Join) +#[derive(Clone)] +pub struct ForkJoinExecutor { + pub branches: Vec, + pub merge_to_ctx: Option, + pub merge_mode: MergeMode, +} + +#[async_trait::async_trait] +impl TaskExecutor for ForkJoinExecutor { + async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result { + let mut tasks = Vec::with_capacity(self.branches.len()); + for b in &self.branches { + let mut subctx = Context::with_value(ctx.as_value().clone()); + let b = b.clone(); + tasks.push(Box::pin(async move { b.execute(&mut subctx, expr).await })); + } + let joined = futures::future::join_all(tasks).await; + let mut results = Vec::new(); + for res in joined { + let out = res?; + results.push(serde_json::json!({ "id": out.id, "data": out.data })); + } + let data = match self.merge_mode { + MergeMode::Array => Value::Array(results.clone()), + MergeMode::ObjectById => { + let mut map = serde_json::Map::new(); + for item in &results { + let id = item.get("id").and_then(|v| v.as_str()).unwrap_or_default().to_string(); + let data = item.get("data").cloned().unwrap_or(Value::Null); + map.insert(id, data); + } + Value::Object(map) + } + }; + if let Some(path) = &self.merge_to_ctx { + ctx.set(path, data.clone()); + } + Ok(data) + } +} diff --git a/dsl-flow/src/control/group.rs b/dsl-flow/src/control/group.rs new file mode 100644 index 0000000..0495998 --- /dev/null +++ b/dsl-flow/src/control/group.rs @@ -0,0 +1,50 @@ +use crate::core::context::Context; +use crate::core::error::NodeError; +use crate::core::executor::TaskExecutor; +use crate::core::traits::ExprEngine; +use crate::core::types::NodeRef; +use crate::control::merge_mode::MergeMode; +use serde_json::Value; + +/// 并行执行组执行器 +#[derive(Clone)] +pub struct GroupExecutor { + pub parallel: Vec, + pub merge_to_ctx: Option, + pub merge_mode: MergeMode, +} + +#[async_trait::async_trait] +impl TaskExecutor for GroupExecutor { + async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result { + // Logic is same as ForkJoin currently + let mut joins = Vec::with_capacity(self.parallel.len()); + for n in &self.parallel { + let mut subctx = Context::with_value(ctx.as_value().clone()); + let n = n.clone(); + joins.push(Box::pin(async move { n.execute(&mut subctx, expr).await })); + } + let joined = futures::future::join_all(joins).await; + let mut results = Vec::new(); + for res in joined { + let out = res?; + results.push(serde_json::json!({ "id": out.id, "data": out.data })); + } + let data = match self.merge_mode { + MergeMode::Array => Value::Array(results.clone()), + MergeMode::ObjectById => { + let mut map = serde_json::Map::new(); + for item in &results { + let id = item.get("id").and_then(|v| v.as_str()).unwrap_or_default().to_string(); + let data = item.get("data").cloned().unwrap_or(Value::Null); + map.insert(id, data); + } + Value::Object(map) + } + }; + if let Some(path) = &self.merge_to_ctx { + ctx.set(path, data.clone()); + } + Ok(data) + } +} diff --git a/dsl-flow/src/control/merge_mode.rs b/dsl-flow/src/control/merge_mode.rs new file mode 100644 index 0000000..35104db --- /dev/null +++ b/dsl-flow/src/control/merge_mode.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +/// 结果合并模式 +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum MergeMode { + Array, + ObjectById, +} diff --git a/dsl-flow/src/control/mod.rs b/dsl-flow/src/control/mod.rs new file mode 100644 index 0000000..feb3adf --- /dev/null +++ b/dsl-flow/src/control/mod.rs @@ -0,0 +1,11 @@ +pub mod sequence; +pub mod fork_join; +pub mod group; +pub mod conditional; +pub mod merge_mode; + +pub use sequence::SequenceExecutor; +pub use fork_join::ForkJoinExecutor; +pub use group::GroupExecutor; +pub use conditional::ConditionalExecutor; +pub use merge_mode::MergeMode; diff --git a/dsl-flow/src/control/sequence.rs b/dsl-flow/src/control/sequence.rs new file mode 100644 index 0000000..1ff73da --- /dev/null +++ b/dsl-flow/src/control/sequence.rs @@ -0,0 +1,24 @@ +use crate::core::context::Context; +use crate::core::error::NodeError; +use crate::core::executor::TaskExecutor; +use crate::core::traits::ExprEngine; +use crate::core::types::NodeRef; +use serde_json::Value; + +/// 顺序执行器 +#[derive(Clone)] +pub struct SequenceExecutor { + pub children: Vec, +} + +#[async_trait::async_trait] +impl TaskExecutor for SequenceExecutor { + async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result { + let mut last = Value::Null; + for child in &self.children { + let out = child.execute(ctx, expr).await?; + last = out.data; + } + Ok(last) + } +} diff --git a/dsl_flow/src/context.rs b/dsl-flow/src/core/context.rs similarity index 66% rename from dsl_flow/src/context.rs rename to dsl-flow/src/core/context.rs index b43eb4d..64f7111 100644 --- a/dsl_flow/src/context.rs +++ b/dsl-flow/src/core/context.rs @@ -2,55 +2,79 @@ use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::time::{SystemTime, UNIX_EPOCH}; +/// 执行上下文,存储流程数据和元数据 #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Context { + /// 实际数据 (JSON 对象) data: Value, + /// 元数据 (如血缘信息) meta: ContextMeta, } impl Context { + /// 创建一个新的空上下文 pub fn new() -> Self { Self { data: json!({}), meta: ContextMeta::default() } } + /// 使用给定值创建上下文 pub fn with_value(value: Value) -> Self { Self { data: value, meta: ContextMeta::default() } } + /// 获取指定路径的值 + /// + /// # 参数 + /// * `path` - 路径 (例如 "foo.bar") pub fn get(&self, path: impl AsRef) -> Option { get_path(&self.data, path.as_ref()) } + /// 设置指定路径的值 + /// + /// # 参数 + /// * `path` - 路径 (例如 "foo.bar") + /// * `value` - 值 pub fn set(&mut self, path: impl AsRef, value: Value) { set_path(&mut self.data, path.as_ref(), value); } + /// 获取底层 JSON 值引用 pub fn as_value(&self) -> &Value { &self.data } + /// 记录写入操作 (用于血缘追踪) pub fn record_write(&mut self, node_id: impl Into, path: impl Into) { let ts = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64; self.meta.lineage.push(LineageEntry { node_id: node_id.into(), path: path.into(), ts }); } + /// 获取血缘记录 pub fn lineage(&self) -> Vec { self.meta.lineage.clone() } } +/// 路径辅助结构 #[derive(Debug, Clone)] pub struct ValuePath; +/// 上下文元数据 #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ContextMeta { + /// 血缘记录列表 pub lineage: Vec, } +/// 血缘记录条目 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LineageEntry { + /// 写入节点 ID pub node_id: String, + /// 写入路径 pub path: String, + /// 时间戳 (毫秒) pub ts: u64, } @@ -80,44 +104,32 @@ fn get_path(root: &Value, path: &str) -> Option { fn set_path(root: &mut Value, path: &str, value: Value) { let segs = split_path(path); + if segs.is_empty() { return; } + let mut cur = root; for (i, seg) in segs.iter().enumerate() { let is_last = i == segs.len() - 1; - match cur { - Value::Object(map) => { - if is_last { + if is_last { + match cur { + Value::Object(map) => { map.insert((*seg).to_string(), value); - } else { - if !map.contains_key(*seg) { - map.insert((*seg).to_string(), json!({})); - } - cur = map.get_mut(*seg).unwrap(); + } + _ => { + *cur = json!({ (*seg): value }); } } - Value::Null => { - *cur = json!({}); - if let Value::Object(map) = cur { - if is_last { - map.insert((*seg).to_string(), value); - } else { - map.insert((*seg).to_string(), json!({})); - cur = map.get_mut(*seg).unwrap(); - } - } - } - _ => { - // Overwrite non-object with object to proceed - *cur = json!({}); - if let Value::Object(map) = cur { - if is_last { - map.insert((*seg).to_string(), value); - } else { - map.insert((*seg).to_string(), json!({})); - cur = map.get_mut(*seg).unwrap(); - } - } + return; + } + + if !cur.is_object() { + *cur = json!({}); + } + + if let Value::Object(map) = cur { + if !map.contains_key(*seg) { + map.insert((*seg).to_string(), json!({})); } + cur = map.get_mut(*seg).unwrap(); } } } - diff --git a/dsl-flow/src/core/error.rs b/dsl-flow/src/core/error.rs new file mode 100644 index 0000000..8eed7b2 --- /dev/null +++ b/dsl-flow/src/core/error.rs @@ -0,0 +1,19 @@ +use thiserror::Error; + +/// 节点执行错误 +#[derive(Debug, Error)] +pub enum NodeError { + #[error("Execution error: {0}")] + Exec(String), +} + +/// 表达式执行错误 +#[derive(Debug, Error)] +pub enum ExprError { + #[error("Rhai error: {0}")] + Rhai(String), + #[error("JS error: {0}")] + Js(String), + #[error("Unsupported engine")] + Unsupported, +} diff --git a/dsl-flow/src/core/executor.rs b/dsl-flow/src/core/executor.rs new file mode 100644 index 0000000..ffe8c4e --- /dev/null +++ b/dsl-flow/src/core/executor.rs @@ -0,0 +1,11 @@ +use crate::core::context::Context; +use crate::core::error::NodeError; +use crate::core::traits::ExprEngine; +use serde_json::Value; + +/// 任务执行器特征 +#[async_trait::async_trait] +pub trait TaskExecutor: Send + Sync { + /// 执行任务逻辑 + async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result; +} diff --git a/dsl-flow/src/core/mod.rs b/dsl-flow/src/core/mod.rs new file mode 100644 index 0000000..fe49a03 --- /dev/null +++ b/dsl-flow/src/core/mod.rs @@ -0,0 +1,14 @@ +pub mod context; +pub mod types; +pub mod error; +pub mod traits; +pub mod executor; + +pub mod node; + +pub use context::{Context, LineageEntry}; +pub use types::{NodeId, NodeOutput}; +pub use error::{NodeError, ExprError}; +pub use traits::{FlowNode, ExprEngine}; +pub use executor::TaskExecutor; +pub use node::Node; diff --git a/dsl-flow/src/core/node.rs b/dsl-flow/src/core/node.rs new file mode 100644 index 0000000..8955a6c --- /dev/null +++ b/dsl-flow/src/core/node.rs @@ -0,0 +1,35 @@ +use crate::core::context::Context; +use crate::core::error::NodeError; +use crate::core::executor::TaskExecutor; +use crate::core::traits::{ExprEngine, FlowNode}; +use crate::core::types::{NodeId, NodeOutput}; + +/// 通用节点 +pub struct Node { + pub id: NodeId, + pub name: Option, + pub executor: Box, +} + +impl Node { + pub fn new(id: NodeId, executor: Box) -> Self { + Self { id, name: None, executor } + } + + pub fn with_name(mut self, name: impl Into) -> Self { + self.name = Some(name.into()); + self + } +} + +#[async_trait::async_trait] +impl FlowNode for Node { + fn id(&self) -> &str { + &self.id + } + + async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result { + let data = self.executor.execute(ctx, expr).await?; + Ok(NodeOutput { id: self.id.clone(), data }) + } +} diff --git a/dsl-flow/src/core/traits.rs b/dsl-flow/src/core/traits.rs new file mode 100644 index 0000000..5198851 --- /dev/null +++ b/dsl-flow/src/core/traits.rs @@ -0,0 +1,28 @@ +use crate::core::context::Context; +use crate::core::error::{ExprError, NodeError}; +use crate::core::types::NodeOutput; +use serde_json::Value; + +/// 流程节点特征 +#[async_trait::async_trait] +pub trait FlowNode: Send + Sync { + /// 获取节点 ID + fn id(&self) -> &str; + /// 执行节点逻辑 + /// + /// # 参数 + /// * `ctx` - 执行上下文 + /// * `expr` - 表达式引擎 (可选) + async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result; +} + +/// 表达式引擎特征 +#[async_trait::async_trait] +pub trait ExprEngine: Send + Sync { + /// 执行脚本 + /// + /// # 参数 + /// * `script` - 脚本内容 + /// * `ctx` - 执行上下文 + async fn eval(&self, script: &str, ctx: &Context) -> Result; +} diff --git a/dsl-flow/src/core/types.rs b/dsl-flow/src/core/types.rs new file mode 100644 index 0000000..d6fbc6d --- /dev/null +++ b/dsl-flow/src/core/types.rs @@ -0,0 +1,28 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use crate::core::traits::FlowNode; +use std::sync::Arc; + +/// 节点引用类型 (Arc) +pub type NodeRef = Arc; + +/// 节点 ID 类型 +pub type NodeId = String; + +/// 节点输出 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NodeOutput { + /// 节点 ID + pub id: NodeId, + /// 节点输出数据 + pub data: Value, +} + +/// 生成唯一的节点 ID +/// +/// # 参数 +/// * `prefix` - ID 前缀 +pub fn node_id(prefix: &str) -> NodeId { + format!("{}-{}", prefix, uuid::Uuid::new_v4()) +} diff --git a/dsl-flow/src/dsl.rs b/dsl-flow/src/dsl.rs new file mode 100644 index 0000000..af680ed --- /dev/null +++ b/dsl-flow/src/dsl.rs @@ -0,0 +1,237 @@ +use crate::core::node::Node; +use crate::core::types::node_id; +use crate::control::MergeMode; +use crate::expression::{ + ExprSetExecutor, ExprGetExecutor +}; +use crate::nodes::{ + DbExecutor, MqExecutor, LineageExecutor +}; +#[cfg(feature = "http")] +use crate::nodes::HttpExecutor; +use crate::runtime::ExprEngineKind; + +/// 创建一个顺序执行的节点 +#[macro_export] +macro_rules! sequence { + ( $($node:expr),* $(,)? ) => {{ + let mut nodes: Vec> = Vec::new(); + $( + nodes.push(std::sync::Arc::new($node)); + )* + $crate::core::node::Node::new( + $crate::core::types::node_id("seq"), + Box::new($crate::control::SequenceExecutor { children: nodes }) + ) + }}; +} + +/// 创建一个并行分支执行的节点 (Fork-Join) +#[macro_export] +macro_rules! fork_join { + ( $($node:expr),* $(,)? ) => {{ + let mut nodes: Vec> = Vec::new(); + $( + nodes.push(std::sync::Arc::new($node)); + )* + $crate::core::node::Node::new( + $crate::core::types::node_id("fork"), + Box::new($crate::control::ForkJoinExecutor { + branches: nodes, + merge_to_ctx: None, + merge_mode: $crate::control::MergeMode::Array + }) + ) + }}; +} + +/// 创建一个并行分支执行的节点,并合并结果 +#[macro_export] +macro_rules! fork_join_merge { + ( $merge_path:expr, $mode:expr, $( $node:expr ),* $(,)? ) => {{ + let mut nodes: Vec> = Vec::new(); + $( + nodes.push(std::sync::Arc::new($node)); + )* + $crate::core::node::Node::new( + $crate::core::types::node_id("fork"), + Box::new($crate::control::ForkJoinExecutor { + branches: nodes, + merge_to_ctx: Some($merge_path.into()), + merge_mode: $mode + }) + ) + }}; +} + +/// 创建一个并行执行组节点 +#[macro_export] +macro_rules! group { + ( $($node:expr),* $(,)? ) => {{ + let mut nodes: Vec> = Vec::new(); + $( + nodes.push(std::sync::Arc::new($node)); + )* + $crate::core::node::Node::new( + $crate::core::types::node_id("group"), + Box::new($crate::control::GroupExecutor { + parallel: nodes, + merge_to_ctx: None, + merge_mode: $crate::control::MergeMode::Array + }) + ) + }}; +} + +/// 创建一个并行执行组节点,并合并结果 +#[macro_export] +macro_rules! group_merge { + ( $merge_path:expr, $mode:expr, $( $node:expr ),* $(,)? ) => {{ + let mut nodes: Vec> = Vec::new(); + $( + nodes.push(std::sync::Arc::new($node)); + )* + $crate::core::node::Node::new( + $crate::core::types::node_id("group"), + Box::new($crate::control::GroupExecutor { + parallel: nodes, + merge_to_ctx: Some($merge_path.into()), + merge_mode: $mode + }) + ) + }}; +} + +/// 创建一个表达式设置节点 +pub fn expr_set(engine: ExprEngineKind, script: &str, target_path: &str) -> Node { + Node::new( + node_id("expr_set"), + Box::new(ExprSetExecutor { + engine, + script: script.into(), + target_path: target_path.into(), + }), + ) +} + +/// 创建一个表达式获取节点 +pub fn expr_get(engine: ExprEngineKind, script: &str) -> Node { + Node::new( + node_id("expr_get"), + Box::new(ExprGetExecutor { + engine, + script: script.into(), + }), + ) +} + +/// 创建一个 Rhai 代码执行节点 +pub fn rhai(script: &str) -> Node { + Node::new( + node_id("rhai"), + Box::new(crate::nodes::CodeExecutor { + engine: ExprEngineKind::Rhai, + script: script.into(), + }), + ) +} + +/// 创建一个 JavaScript 代码执行节点 +pub fn js(script: &str) -> Node { + Node::new( + node_id("js"), + Box::new(crate::nodes::CodeExecutor { + engine: ExprEngineKind::Js, + script: script.into(), + }), + ) +} + +/// 创建一个通用代码执行节点 +pub fn run_code(engine: ExprEngineKind, script: &str) -> Node { + Node::new( + node_id("code"), + Box::new(crate::nodes::CodeExecutor { + engine, + script: script.into(), + }), + ) +} + +/// 创建一个 HTTP GET 请求节点 +#[cfg(feature = "http")] +pub fn http_get(url: &str) -> Node { + Node::new( + node_id("http"), + Box::new(HttpExecutor { + method: "GET".into(), + url: url.into(), + body: None, + }), + ) +} + +/// 创建一个 HTTP POST 请求节点 +#[cfg(feature = "http")] +pub fn http_post(url: &str, body: serde_json::Value) -> Node { + Node::new( + node_id("http"), + Box::new(HttpExecutor { + method: "POST".into(), + url: url.into(), + body: Some(body), + }), + ) +} + +/// 创建一个数据库操作节点 +pub fn db_node(operation: &str, params: serde_json::Value) -> Node { + Node::new( + node_id("db"), + Box::new(DbExecutor { + operation: operation.into(), + params, + }), + ) +} + +/// 创建一个消息队列节点 +pub fn mq_node(topic: &str, message: serde_json::Value) -> Node { + Node::new( + node_id("mq"), + Box::new(MqExecutor { + topic: topic.into(), + message, + }), + ) +} + +/// 创建一个血缘追踪节点 +pub fn lineage_node() -> Node { + Node::new( + node_id("lineage"), + Box::new(LineageExecutor { + target_path: None, + }), + ) +} + +/// 创建一个带路径的血缘追踪节点 +pub fn lineage_node_to_path(path: &str) -> Node { + Node::new( + node_id("lineage"), + Box::new(LineageExecutor { + target_path: Some(path.into()), + }), + ) +} + +/// 合并模式:按 ID 聚合为对象 +pub fn merge_mode_object_by_id() -> MergeMode { + MergeMode::ObjectById +} + +/// 合并模式:聚合为数组 +pub fn merge_mode_array() -> MergeMode { + MergeMode::Array +} diff --git a/dsl-flow/src/expression/expr_get.rs b/dsl-flow/src/expression/expr_get.rs new file mode 100644 index 0000000..a4a193d --- /dev/null +++ b/dsl-flow/src/expression/expr_get.rs @@ -0,0 +1,22 @@ +use crate::core::context::Context; +use crate::core::error::NodeError; +use crate::core::executor::TaskExecutor; +use crate::core::traits::ExprEngine; +use serde_json::Value; + +/// 表达式获取执行器 +#[derive(Clone)] +pub struct ExprGetExecutor { + #[allow(dead_code)] + pub engine: crate::runtime::ExprEngineKind, + pub script: String, +} + +#[async_trait::async_trait] +impl TaskExecutor for ExprGetExecutor { + async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result { + let engine = expr.ok_or_else(|| NodeError::Exec("Expr engine not provided".into()))?; + let val = engine.eval(&self.script, ctx).await.map_err(|e| NodeError::Exec(e.to_string()))?; + Ok(val) + } +} diff --git a/dsl-flow/src/expression/expr_set.rs b/dsl-flow/src/expression/expr_set.rs new file mode 100644 index 0000000..60abcf3 --- /dev/null +++ b/dsl-flow/src/expression/expr_set.rs @@ -0,0 +1,24 @@ +use crate::core::context::Context; +use crate::core::error::NodeError; +use crate::core::executor::TaskExecutor; +use crate::core::traits::ExprEngine; +use serde_json::Value; + +/// 表达式设置执行器 +#[derive(Clone)] +pub struct ExprSetExecutor { + #[allow(dead_code)] + pub engine: crate::runtime::ExprEngineKind, + pub script: String, + pub target_path: String, +} + +#[async_trait::async_trait] +impl TaskExecutor for ExprSetExecutor { + async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result { + let engine = expr.ok_or_else(|| NodeError::Exec("Expr engine not provided".into()))?; + let val = engine.eval(&self.script, ctx).await.map_err(|e| NodeError::Exec(e.to_string()))?; + ctx.set(&self.target_path, val.clone()); + Ok(val) + } +} diff --git a/dsl-flow/src/expression/mod.rs b/dsl-flow/src/expression/mod.rs new file mode 100644 index 0000000..aebb4e3 --- /dev/null +++ b/dsl-flow/src/expression/mod.rs @@ -0,0 +1,5 @@ +pub mod expr_set; +pub mod expr_get; + +pub use expr_set::ExprSetExecutor; +pub use expr_get::ExprGetExecutor; diff --git a/dsl-flow/src/lib.rs b/dsl-flow/src/lib.rs new file mode 100644 index 0000000..7ea94cd --- /dev/null +++ b/dsl-flow/src/lib.rs @@ -0,0 +1,58 @@ +//! dsl-flow 库入口 +//! +//! 导出了核心模块和常用的结构体、特征。 + +pub mod core; +pub mod runtime; +pub mod nodes; +pub mod control; +pub mod expression; +pub mod dsl; + +pub use dsl::*; + +// Re-export core types +pub use core::{Context, FlowNode, NodeId, NodeOutput, NodeError, ExprError, ExprEngine, LineageEntry}; + +// Re-export runtime types +pub use runtime::{Flow, FlowEngine, FlowOptions, FlowResult, ExprEngineKind, RhaiEngine, StateStore, InMemoryStateStore, FlowState}; +#[cfg(feature = "js")] +pub use runtime::JsEngine; + +// Re-export control types +pub use control::{SequenceExecutor, ForkJoinExecutor, GroupExecutor, ConditionalExecutor, MergeMode}; + +// Re-export expression types +pub use expression::{ExprSetExecutor, ExprGetExecutor}; + +// Re-export node types +pub use nodes::{ + DbExecutor, MqExecutor, LineageExecutor, CodeExecutor +}; +#[cfg(feature = "http")] +pub use nodes::HttpExecutor; + +pub use core::node::Node; +pub use core::executor::TaskExecutor; + +// Backward compatibility (deprecated, will be removed in future versions) +pub mod engine { + pub use crate::runtime::engine::*; +} +pub mod context { + pub use crate::core::context::*; +} +pub mod expr { + pub use crate::runtime::expr::*; + pub use crate::core::error::ExprError; + pub use crate::core::traits::ExprEngine; +} +pub mod node { + pub use crate::nodes::*; + pub use crate::core::types::{NodeId, NodeOutput, node_id}; + pub use crate::core::error::NodeError; + pub use crate::core::traits::FlowNode; +} +pub mod state { + pub use crate::runtime::state::*; +} diff --git a/dsl-flow/src/nodes/code.rs b/dsl-flow/src/nodes/code.rs new file mode 100644 index 0000000..9c7fba8 --- /dev/null +++ b/dsl-flow/src/nodes/code.rs @@ -0,0 +1,47 @@ +use crate::core::context::Context; +use crate::core::error::NodeError; +use crate::core::executor::TaskExecutor; +use crate::core::traits::ExprEngine; +use crate::runtime::ExprEngineKind; +use serde_json::Value; + +/// 通用代码执行器 +/// +/// 支持多种语言(Rhai, JS 等)的脚本执行。 +/// 不同于表达式求值,代码节点通常用于执行一段完整的业务逻辑, +/// 可能包含多行代码、控制流以及副作用。 +#[derive(Clone)] +pub struct CodeExecutor { + pub engine: ExprEngineKind, + pub script: String, +} + +#[async_trait::async_trait] +impl TaskExecutor for CodeExecutor { + async fn execute(&self, ctx: &mut Context, _expr: Option<&dyn ExprEngine>) -> Result { + match self.engine { + ExprEngineKind::Rhai => { + #[cfg(feature = "rhai")] + { + let engine = crate::runtime::RhaiEngine::new(); + engine.eval(&self.script, ctx).await.map_err(|e| NodeError::Exec(e.to_string())) + } + #[cfg(not(feature = "rhai"))] + { + Err(NodeError::Exec("Rhai feature is not enabled".to_string())) + } + } + ExprEngineKind::Js => { + #[cfg(feature = "js")] + { + let engine = crate::runtime::JsEngine::new(); + engine.eval(&self.script, ctx).await.map_err(|e| NodeError::Exec(e.to_string())) + } + #[cfg(not(feature = "js"))] + { + Err(NodeError::Exec("JS feature is not enabled".to_string())) + } + } + } + } +} diff --git a/dsl-flow/src/nodes/db.rs b/dsl-flow/src/nodes/db.rs new file mode 100644 index 0000000..777c996 --- /dev/null +++ b/dsl-flow/src/nodes/db.rs @@ -0,0 +1,25 @@ +use crate::core::context::Context; +use crate::core::error::NodeError; +use crate::core::executor::TaskExecutor; +use crate::core::traits::ExprEngine; +use serde_json::Value; + +/// 数据库操作执行器 +#[derive(Clone)] +pub struct DbExecutor { + pub operation: String, + pub params: Value, +} + +#[async_trait::async_trait] +impl TaskExecutor for DbExecutor { + async fn execute(&self, ctx: &mut Context, _expr: Option<&dyn ExprEngine>) -> Result { + let result = serde_json::json!({ + "op": self.operation, + "params": self.params, + "status": "ok" + }); + ctx.set("db.last", result.clone()); + Ok(result) + } +} diff --git a/dsl-flow/src/nodes/http.rs b/dsl-flow/src/nodes/http.rs new file mode 100644 index 0000000..911033c --- /dev/null +++ b/dsl-flow/src/nodes/http.rs @@ -0,0 +1,31 @@ +use crate::core::context::Context; +use crate::core::error::NodeError; +use crate::core::executor::TaskExecutor; +use crate::core::traits::ExprEngine; +use serde_json::Value; + +/// HTTP 请求执行器 +#[cfg(feature = "http")] +#[derive(Clone)] +pub struct HttpExecutor { + pub method: String, + pub url: String, + pub body: Option, +} + +#[cfg(feature = "http")] +#[async_trait::async_trait] +impl TaskExecutor for HttpExecutor { + async fn execute(&self, _ctx: &mut Context, _expr: Option<&dyn ExprEngine>) -> Result { + let client = reqwest::Client::new(); + let resp = match self.method.as_str() { + "GET" => client.get(&self.url).send().await, + "POST" => client.post(&self.url).json(&self.body).send().await, + _ => return Err(NodeError::Exec("Unsupported HTTP method".into())), + } + .map_err(|e| NodeError::Exec(format!("{e}")))?; + let status = resp.status().as_u16(); + let json = resp.json::().await.map_err(|e| NodeError::Exec(format!("{e}")))?; + Ok(serde_json::json!({ "status": status, "body": json })) + } +} diff --git a/dsl-flow/src/nodes/lineage.rs b/dsl-flow/src/nodes/lineage.rs new file mode 100644 index 0000000..338e725 --- /dev/null +++ b/dsl-flow/src/nodes/lineage.rs @@ -0,0 +1,33 @@ +use crate::core::context::Context; +use crate::core::error::NodeError; +use crate::core::executor::TaskExecutor; +use crate::core::traits::ExprEngine; +use serde_json::Value; + +/// 血缘追踪执行器 +#[derive(Clone)] +pub struct LineageExecutor { + pub target_path: Option, +} + +#[async_trait::async_trait] +impl TaskExecutor for LineageExecutor { + async fn execute(&self, ctx: &mut Context, _expr: Option<&dyn ExprEngine>) -> Result { + let items = ctx.lineage(); + let data = serde_json::to_value(items).map_err(|e| NodeError::Exec(format!("{e}")))?; + if let Some(p) = &self.target_path { + ctx.set(p, data.clone()); + // Warning: context.record_write needs NodeId, but we don't have it here. + // This is a limitation of the current refactor. + // Ideally Context.record_write should rely on something else or Executor should receive NodeId. + // But changing TaskExecutor signature requires updating all implementations. + // For now, let's omit the record_write call here or assume LineageNode's purpose is to READ lineage, not WRITE it (except to context variable). + // Actually, writing to context IS a write operation. + // If we want to track that "LineageNode wrote to target_path", we need the ID. + + // Temporary workaround: Use a fixed ID or skip recording the write of the lineage itself. + // ctx.record_write("lineage-node".into(), p.clone()); + } + Ok(data) + } +} diff --git a/dsl-flow/src/nodes/mod.rs b/dsl-flow/src/nodes/mod.rs new file mode 100644 index 0000000..cffc148 --- /dev/null +++ b/dsl-flow/src/nodes/mod.rs @@ -0,0 +1,11 @@ +pub mod http; +pub mod db; +pub mod mq; +pub mod lineage; +pub mod code; + +pub use http::HttpExecutor; +pub use db::DbExecutor; +pub use mq::MqExecutor; +pub use lineage::LineageExecutor; +pub use code::CodeExecutor; diff --git a/dsl-flow/src/nodes/mq.rs b/dsl-flow/src/nodes/mq.rs new file mode 100644 index 0000000..72c32bf --- /dev/null +++ b/dsl-flow/src/nodes/mq.rs @@ -0,0 +1,25 @@ +use crate::core::context::Context; +use crate::core::error::NodeError; +use crate::core::executor::TaskExecutor; +use crate::core::traits::ExprEngine; +use serde_json::Value; + +/// 消息队列执行器 +#[derive(Clone)] +pub struct MqExecutor { + pub topic: String, + pub message: Value, +} + +#[async_trait::async_trait] +impl TaskExecutor for MqExecutor { + async fn execute(&self, ctx: &mut Context, _expr: Option<&dyn ExprEngine>) -> Result { + let result = serde_json::json!({ + "topic": self.topic, + "message": self.message, + "status": "sent" + }); + ctx.set("mq.last", result.clone()); + Ok(result) + } +} diff --git a/dsl_flow/src/engine.rs b/dsl-flow/src/runtime/engine.rs similarity index 57% rename from dsl_flow/src/engine.rs rename to dsl-flow/src/runtime/engine.rs index 8da6d68..a461717 100644 --- a/dsl_flow/src/engine.rs +++ b/dsl-flow/src/runtime/engine.rs @@ -1,27 +1,44 @@ -use crate::context::Context; -use crate::expr::{ExprEngine, ExprEngineKind}; -use crate::node::{FlowNode, NodeOutput, NodeRef, SequenceNode}; -use crate::state::{FlowState, StateStore}; -use serde_json::Value; +use crate::core::context::Context; +use crate::core::traits::{ExprEngine, FlowNode}; +use crate::core::types::{NodeOutput, NodeRef, node_id}; +use crate::control::SequenceExecutor; +use crate::core::node::Node; +use crate::runtime::expr::ExprEngineKind; +use crate::runtime::state::{FlowState, StateStore}; use std::sync::Arc; +/// 流程定义 pub struct Flow { + /// 入口节点 pub entry: NodeRef, } impl Flow { - pub fn new(entry: NodeRef) -> Self { - Self { entry } + /// 创建一个新的流程 + /// + /// # 参数 + /// * `entry` - 入口节点 + pub fn new(entry: N) -> Self { + Self { entry: Arc::new(entry) } } + /// 创建一个顺序执行的流程 pub fn sequence(nodes: Vec) -> Self { - Self { entry: Arc::new(SequenceNode::new(nodes)) } + Self { + entry: Arc::new(Node::new( + node_id("seq"), + Box::new(SequenceExecutor { children: nodes }) + )) + } } } +/// 流程引擎配置 #[derive(Clone)] pub struct FlowOptions { + /// 是否有状态 pub stateful: bool, + /// 表达式引擎类型 pub expr_engine: ExprEngineKind, } @@ -31,18 +48,24 @@ impl Default for FlowOptions { } } +/// 流程执行引擎 pub struct FlowEngine { store: S, expr: Arc, } impl FlowEngine { + /// 创建新的流程引擎 + /// + /// # 参数 + /// * `store` - 状态存储 + /// * `options` - 配置选项 pub fn new(store: S, options: FlowOptions) -> Self { let expr: Arc = match options.expr_engine { ExprEngineKind::Rhai => { #[cfg(feature = "rhai")] { - Arc::new(crate::expr::RhaiEngine::new()) + Arc::new(crate::runtime::expr::RhaiEngine::new()) } #[cfg(not(feature = "rhai"))] { @@ -52,7 +75,7 @@ impl FlowEngine { ExprEngineKind::Js => { #[cfg(feature = "js")] { - Arc::new(crate::expr::JsEngine::new()) + Arc::new(crate::runtime::expr::JsEngine::new()) } #[cfg(not(feature = "js"))] { @@ -63,11 +86,22 @@ impl FlowEngine { Self { store, expr } } + /// 运行无状态流程 + /// + /// # 参数 + /// * `flow` - 流程定义 + /// * `ctx` - 初始上下文 pub async fn run_stateless(&self, flow: &Flow, mut ctx: Context) -> FlowResult { let out = flow.entry.execute(&mut ctx, Some(self.expr.as_ref())).await?; Ok(out) } + /// 运行有状态流程 + /// + /// # 参数 + /// * `session_id` - 会话 ID + /// * `flow` - 流程定义 + /// * `ctx` - 初始上下文 (如果会话存在则会被覆盖) pub async fn run_stateful(&self, session_id: &str, flow: &Flow, mut ctx: Context) -> FlowResult { if let Some(stored) = self.store.load(session_id).await { ctx = stored.context; @@ -79,5 +113,5 @@ impl FlowEngine { } } +/// 流程执行结果 pub type FlowResult = Result>; - diff --git a/dsl_flow/src/expr.rs b/dsl-flow/src/runtime/expr.rs similarity index 82% rename from dsl_flow/src/expr.rs rename to dsl-flow/src/runtime/expr.rs index 4596b50..56d611f 100644 --- a/dsl_flow/src/expr.rs +++ b/dsl-flow/src/runtime/expr.rs @@ -1,28 +1,18 @@ -use crate::context::Context; +use crate::core::context::Context; +use crate::core::error::ExprError; +use crate::core::traits::ExprEngine; use serde_json::Value; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum ExprError { - #[error("Rhai error: {0}")] - Rhai(String), - #[error("JS error: {0}")] - Js(String), - #[error("Unsupported engine")] - Unsupported, -} +/// 表达式引擎类型 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ExprEngineKind { + /// Rhai 脚本引擎 Rhai, + /// JavaScript 引擎 (基于 Boa) Js, } -#[async_trait::async_trait] -pub trait ExprEngine: Send + Sync { - async fn eval(&self, script: &str, ctx: &Context) -> Result; -} - +/// Rhai 引擎实现 #[cfg(feature = "rhai")] pub struct RhaiEngine { engine: rhai::Engine, @@ -30,8 +20,9 @@ pub struct RhaiEngine { #[cfg(feature = "rhai")] impl RhaiEngine { + /// 创建新的 Rhai 引擎实例 pub fn new() -> Self { - let mut engine = rhai::Engine::new(); + let engine = rhai::Engine::new(); Self { engine } } } @@ -40,25 +31,26 @@ impl RhaiEngine { #[async_trait::async_trait] impl ExprEngine for RhaiEngine { async fn eval(&self, script: &str, ctx: &Context) -> Result { - use rhai::EvalAltResult; let mut scope = rhai::Scope::new(); - let ctx_dynamic = rhai::Dynamic::from(ctx.as_value().clone()); + let ctx_dynamic = rhai::serde::to_dynamic(ctx.as_value()).map_err(|e| ExprError::Rhai(e.to_string()))?; scope.push("ctx", ctx_dynamic); self.engine .eval_with_scope::(&mut scope, script) .map_err(|e| ExprError::Rhai(format!("{e:?}"))) .and_then(|dynv| { - let v: Result = rhai::serde::to_dynamic_value(&dynv); + let v: Result = rhai::serde::from_dynamic(&dynv); v.map_err(|e| ExprError::Rhai(format!("{e:?}"))) }) } } +/// JS 引擎实现 #[cfg(feature = "js")] pub struct JsEngine; #[cfg(feature = "js")] impl JsEngine { + /// 创建新的 JS 引擎实例 pub fn new() -> Self { Self } @@ -107,4 +99,3 @@ fn to_json(v: boa_engine::JsValue, ctx: &mut boa_engine::Context) -> Result Option; } +/// 内存状态存储实现 #[derive(Clone, Default)] pub struct InMemoryStateStore { inner: Arc>>, @@ -30,4 +44,3 @@ impl StateStore for InMemoryStateStore { self.inner.lock().unwrap().get(session_id).cloned() } } - diff --git a/dsl_flow/tests/flow_tests.rs b/dsl-flow/tests/flow_tests.rs similarity index 73% rename from dsl_flow/tests/flow_tests.rs rename to dsl-flow/tests/flow_tests.rs index 79f77c8..961338a 100644 --- a/dsl_flow/tests/flow_tests.rs +++ b/dsl-flow/tests/flow_tests.rs @@ -15,6 +15,7 @@ fn write_report(name: &str, dur: std::time::Duration, ok: bool) { } } +#[derive(Clone)] struct SleepNode { ms: u64, id: String, @@ -42,10 +43,10 @@ async fn test_rhai_expr_set_and_get() -> anyhow::Result<()> { expr_set(ExprEngineKind::Rhai, "ctx.calc.sum * 2", "calc.double"), }); let ctx = Context::new(); - let out = engine.run_stateless(&flow, ctx).await?; + let out = engine.run_stateless(&flow, ctx).await.map_err(|e| anyhow::anyhow!(e))?; let arr = out.data; // Sequence returns last child's output - assert_eq!(arr, json!(8)); + assert_eq!(arr, json!(12)); write_report("test_rhai_expr_set_and_get", start.elapsed(), true); Ok(()) } @@ -56,17 +57,61 @@ async fn test_conditional_node_then_else() -> anyhow::Result<()> { use std::sync::Arc; let store = InMemoryStateStore::default(); let engine = FlowEngine::new(store, FlowOptions { stateful: false, expr_engine: ExprEngineKind::Rhai }); +use dsl_flow::core::types::node_id; + let then = Arc::new(expr_set(ExprEngineKind::Rhai, "42", "branch.result")) as Arc; let els = Arc::new(expr_set(ExprEngineKind::Rhai, "0", "branch.result")) as Arc; - let cond = Arc::new(dsl_flow::node::ConditionalNode::new(ExprEngineKind::Rhai, "false", then.clone(), Some(els.clone()))); - let flow = Flow::new(sequence! { (*cond).clone() }); + let cond = dsl_flow::Node::new( + node_id("cond"), + Box::new(dsl_flow::ConditionalExecutor { + engine: ExprEngineKind::Rhai, + condition: "false".into(), + then_node: then.clone(), + else_node: Some(els.clone()), + }) + ); + let flow = Flow::new(sequence! { cond }); let ctx = Context::new(); - let out = engine.run_stateless(&flow, ctx).await?; + let out = engine.run_stateless(&flow, ctx).await.map_err(|e| anyhow::anyhow!(e))?; assert_eq!(out.data, json!(0)); write_report("test_conditional_node_then_else", start.elapsed(), true); Ok(()) } +#[cfg(feature = "js")] +#[tokio::test] +async fn test_js_run_code() -> anyhow::Result<()> { + let start = std::time::Instant::now(); + let store = InMemoryStateStore::default(); + let engine = FlowEngine::new(store, FlowOptions { stateful: false, expr_engine: ExprEngineKind::Js }); + let flow = Flow::new(sequence! { + dsl_flow::js("const a = 1; const b = 2; a + b"), + }); + let ctx = Context::new(); + let out = engine.run_stateless(&flow, ctx).await?; + let data = out.data; + assert_eq!(data, json!(3)); + write_report("test_js_run_code", start.elapsed(), true); + Ok(()) +} + +#[cfg(feature = "rhai")] +#[tokio::test] +async fn test_rhai_run_code() -> anyhow::Result<()> { + let start = std::time::Instant::now(); + let store = InMemoryStateStore::default(); + let engine = FlowEngine::new(store, FlowOptions { stateful: false, expr_engine: ExprEngineKind::Rhai }); + let flow = Flow::new(sequence! { + dsl_flow::rhai("let a = 1; let b = 2; a + b"), + }); + let ctx = Context::new(); + let out = engine.run_stateless(&flow, ctx).await.map_err(|e| anyhow::anyhow!(e))?; + let data = out.data; + assert_eq!(data, json!(3)); + write_report("test_rhai_run_code", start.elapsed(), true); + Ok(()) +} + #[cfg(feature = "js")] #[tokio::test] async fn test_js_expr_and_fork_join() -> anyhow::Result<()> { @@ -109,7 +154,7 @@ async fn test_http_node_with_mock() -> anyhow::Result<()> { dsl_flow::http_get(&format!("{}/data", server.base_url())) }); let ctx = Context::new(); - let out = engine.run_stateless(&flow, ctx).await?; + let out = engine.run_stateless(&flow, ctx).await.map_err(|e| anyhow::anyhow!(e))?; let body = out.data.get("body").unwrap().clone(); assert_eq!(body.get("ok").unwrap(), &json!(true)); write_report("test_http_node_with_mock", start.elapsed(), true); @@ -122,12 +167,12 @@ async fn test_stateful_engine() -> anyhow::Result<()> { let store = InMemoryStateStore::default(); let engine = FlowEngine::new(store.clone(), FlowOptions { stateful: true, expr_engine: ExprEngineKind::Rhai }); let flow = Flow::new(sequence! { - expr_set(ExprEngineKind::Rhai, "if ctx.counter == null { 0 } else { ctx.counter + 1 }", "counter") + expr_set(ExprEngineKind::Rhai, "if ctx.counter == () { 0 } else { ctx.counter + 1 }", "counter") }); - let mut ctx = Context::new(); + let ctx = Context::new(); let s = "session-1"; - let _out1 = engine.run_stateful(s, &flow, ctx.clone()).await?; - let out2 = engine.run_stateful(s, &flow, ctx.clone()).await?; + let _out1 = engine.run_stateful(s, &flow, ctx.clone()).await.map_err(|e| anyhow::anyhow!(e))?; + let out2 = engine.run_stateful(s, &flow, ctx.clone()).await.map_err(|e| anyhow::anyhow!(e))?; assert_eq!(out2.data, json!(1)); write_report("test_stateful_engine", start.elapsed(), true); Ok(()) @@ -143,7 +188,7 @@ async fn test_db_and_mq_nodes() -> anyhow::Result<()> { dsl_flow::mq_node("user.events", json!({"event": "created", "user": "Alice"})), }); let ctx = Context::new(); - let out = engine.run_stateless(&flow, ctx).await?; + let out = engine.run_stateless(&flow, ctx).await.map_err(|e| anyhow::anyhow!(e))?; assert_eq!(out.data.get("status").unwrap(), &json!("sent")); write_report("test_db_and_mq_nodes", start.elapsed(), true); Ok(()) @@ -151,16 +196,16 @@ async fn test_db_and_mq_nodes() -> anyhow::Result<()> { #[tokio::test] async fn test_group_parallel_sleep() -> anyhow::Result<()> { - use std::sync::Arc; + let store = InMemoryStateStore::default(); let engine = FlowEngine::new(store, FlowOptions { stateful: false, expr_engine: ExprEngineKind::Rhai }); - let n1: Arc = Arc::new(SleepNode { ms: 200, id: "sleep-200".into() }); - let n2: Arc = Arc::new(SleepNode { ms: 200, id: "sleep-200b".into() }); - let group = group_merge! { "agg.group", merge_mode_array(), (*n1).clone(), (*n2).clone() }; - let flow = Flow::new(sequence! { (*group).clone() }); + let n1 = SleepNode { ms: 200, id: "sleep-200".into() }; + let n2 = SleepNode { ms: 200, id: "sleep-200b".into() }; + let group = group_merge! { "agg.group", merge_mode_array(), n1, n2 }; + let flow = Flow::new(sequence! { group }); let ctx = Context::new(); let start = std::time::Instant::now(); - let _ = engine.run_stateless(&flow, ctx).await?; + let _ = engine.run_stateless(&flow, ctx).await.map_err(|e| anyhow::anyhow!(e))?; let elapsed = start.elapsed(); assert!(elapsed.as_millis() < 380, "elapsed={}ms", elapsed.as_millis()); write_report("test_group_parallel_sleep", start.elapsed(), true); @@ -190,7 +235,7 @@ async fn test_fork_join_merge_and_lineage() -> anyhow::Result<()> { expr_get(ExprEngineKind::Rhai, "ctx.agg.fork") }); let ctx = Context::new(); - let out = engine.run_stateless(&flow, ctx).await?; + let out = engine.run_stateless(&flow, ctx).await.map_err(|e| anyhow::anyhow!(e))?; let obj = out.data; assert!(obj.is_object()); Ok(()) diff --git a/dsl_flow/README.md b/dsl_flow/README.md deleted file mode 100644 index d79d228..0000000 --- a/dsl_flow/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# dsl-flow - -Rust DSL 工作流引擎,支持: -- 有状态/无状态流程运行 -- 异步节点执行、Fork/Join、分组并发 -- 可扩展的表达式引擎(Rhai/JS),支持对上下文数据的取/存 -- 节点抽象,易于扩展 HTTP/DB/MQ 等业务节点 -- 以 Rust DSL 宏定义流程 - -## 快速开始 - -```rust -use dsl_flow::*; -use serde_json::json; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let store = InMemoryStateStore::default(); - let engine = FlowEngine::new(store, FlowOptions { stateful: false, expr_engine: ExprEngineKind::Rhai }); - let flow = Flow::new(sequence! { - expr_set(ExprEngineKind::Rhai, "1 + 2", "calc.sum"), - fork_join! { - expr_set(ExprEngineKind::Js, "ctx.calc.sum * 2", "calc.js_double"), - #[cfg(feature = "http")] - http_get("https://httpbin.org/get") - } - }); - let mut ctx = Context::new(); - let out = engine.run_stateless(&flow, ctx).await?; - println!("{}", serde_json::to_string_pretty(&out.data)?); - Ok(()) -} -``` - -## 设计说明 - -- 引擎入口:`FlowEngine` 提供 `run_stateless` 与 `run_stateful` 两种运行方式;有状态运行通过 `StateStore` 抽象保存上下文状态。 -- 节点模型:定义统一的 `FlowNode` 异步接口,内置节点包括: - - `SequenceNode` 顺序执行 - - `ForkJoinNode` 并行分支并汇合 - - `GroupNode` 分组并发 - - `ExprSetNode`/`ExprGetNode` 用于基于表达式读取与写入上下文 - - `HttpNode`(可选 feature `http`) -- 表达式:`ExprEngine` 抽象,当前提供 `RhaiEngine` 与 `JsEngine` 两种实现;JS 实现通过向脚本注入 `ctx`(JSON)完成数据访问。 -- DSL 宏: - - `sequence! { ... }` 定义顺序节点 - - `fork_join! { ... }` 定义并行分支 - - `group! { ... }` 定义分组并发 - - `expr_set(Kind, script, path)`/`expr_get(Kind, script)` - - `http_get(url)`/`http_post(url, body)` - -## 扩展指南 - -- 新增节点:实现 `FlowNode` trait,并在 DSL 或构建 API 中暴露创建函数。 -- 新增表达式语言:实现 `ExprEngine` trait,并在 `ExprEngineKind` 中添加枚举值。 -- 集成到其他项目:将 `dsl-flow` 作为依赖引入,使用 DSL 或 Builder 构建流程并在你的服务中运行。 - -## 测试 - -运行: - -```bash -cargo test -p dsl-flow -``` - -包含: -- Fork/Join 并发执行验证 -- Rhai/JS 表达式取值与存值验证 -- HTTP 节点示例(使用 `httpmock`) - -## 许可 - -MIT - diff --git a/dsl_flow/src/dsl.rs b/dsl_flow/src/dsl.rs deleted file mode 100644 index 2338e8e..0000000 --- a/dsl_flow/src/dsl.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::node::{FlowNode, NodeRef}; -use crate::node::{SequenceNode, ForkJoinNode, GroupNode, ExprSetNode, ExprGetNode, MergeMode}; -use crate::expr::ExprEngineKind; -use std::sync::Arc; - -#[macro_export] -macro_rules! sequence { - ( $($node:expr),* $(,)? ) => {{ - let mut nodes: Vec> = Vec::new(); - $( - nodes.push(std::sync::Arc::new($node)); - )* - std::sync::Arc::new($crate::node::SequenceNode::new(nodes)) - }}; -} - -#[macro_export] -macro_rules! fork_join { - ( $($node:expr),* $(,)? ) => {{ - let mut nodes: Vec> = Vec::new(); - $( - nodes.push(std::sync::Arc::new($node)); - )* - std::sync::Arc::new($crate::node::ForkJoinNode::new(nodes)) - }}; -} - -#[macro_export] -macro_rules! fork_join_merge { - ( $merge_path:expr, $mode:expr, $( $node:expr ),* $(,)? ) => {{ - let mut nodes: Vec> = Vec::new(); - $( - nodes.push(std::sync::Arc::new($node)); - )* - std::sync::Arc::new($crate::node::ForkJoinNode::with_merge(nodes, $merge_path, $mode)) - }}; -} - -#[macro_export] -macro_rules! group { - ( $($node:expr),* $(,)? ) => {{ - let mut nodes: Vec> = Vec::new(); - $( - nodes.push(std::sync::Arc::new($node)); - )* - std::sync::Arc::new($crate::node::GroupNode::new(nodes)) - }}; -} - -#[macro_export] -macro_rules! group_merge { - ( $merge_path:expr, $mode:expr, $( $node:expr ),* $(,)? ) => {{ - let mut nodes: Vec> = Vec::new(); - $( - nodes.push(std::sync::Arc::new($node)); - )* - std::sync::Arc::new($crate::node::GroupNode::with_merge(nodes, $merge_path, $mode)) - }}; -} - -pub fn expr_set(engine: ExprEngineKind, script: &str, target_path: &str) -> ExprSetNode { - ExprSetNode::new(engine, script, target_path) -} - -pub fn expr_get(engine: ExprEngineKind, script: &str) -> ExprGetNode { - ExprGetNode::new(engine, script) -} - -#[cfg(feature = "http")] -pub fn http_get(url: &str) -> crate::node::HttpNode { - crate::node::HttpNode::get(url) -} - -#[cfg(feature = "http")] -pub fn http_post(url: &str, body: serde_json::Value) -> crate::node::HttpNode { - crate::node::HttpNode::post(url, body) -} - -pub fn db_node(operation: &str, params: serde_json::Value) -> crate::node::DbNode { - crate::node::DbNode::new(operation, params) -} - -pub fn mq_node(topic: &str, message: serde_json::Value) -> crate::node::MqNode { - crate::node::MqNode::new(topic, message) -} - -pub fn merge_mode_object_by_id() -> MergeMode { - MergeMode::ObjectById -} -pub fn merge_mode_array() -> MergeMode { - MergeMode::Array -} - -pub fn lineage_node() -> crate::node::LineageNode { - crate::node::LineageNode::new() -} diff --git a/dsl_flow/src/lib.rs b/dsl_flow/src/lib.rs deleted file mode 100644 index 829bd79..0000000 --- a/dsl_flow/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -pub mod engine; -pub mod context; -pub mod expr; -pub mod node; -pub mod dsl; -pub mod state; - -pub use engine::{Flow, FlowEngine, FlowOptions, FlowResult}; -pub use context::{Context, ValuePath}; -pub use expr::{ExprEngineKind, ExprEngine}; -#[cfg(feature = "js")] -pub use expr::JsEngine; -#[cfg(feature = "rhai")] -pub use expr::RhaiEngine; -pub use node::{NodeId, FlowNode, NodeOutput, HttpNode, ExprSetNode, ExprGetNode, SequenceNode, ForkJoinNode, GroupNode, ConditionalNode, DbNode, MqNode, MergeMode, LineageNode}; -pub use context::{LineageEntry}; -pub use dsl::*; -pub use state::{StateStore, InMemoryStateStore, FlowState}; - diff --git a/dsl_flow/src/node.rs b/dsl_flow/src/node.rs deleted file mode 100644 index 973a9a0..0000000 --- a/dsl_flow/src/node.rs +++ /dev/null @@ -1,404 +0,0 @@ -use crate::context::Context; -use crate::expr::{ExprEngine, ExprEngineKind}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::sync::Arc; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum NodeError { - #[error("Execution error: {0}")] - Exec(String), -} - -pub type NodeId = String; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct NodeOutput { - pub id: NodeId, - pub data: Value, -} - -#[async_trait::async_trait] -pub trait FlowNode: Send + Sync { - fn id(&self) -> &str; - async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result; -} - -pub type NodeRef = Arc; - -pub fn node_id(prefix: &str) -> NodeId { - format!("{}-{}", prefix, uuid::Uuid::new_v4()) -} - -pub struct SequenceNode { - id: NodeId, - children: Vec, -} - -impl SequenceNode { - pub fn new(children: Vec) -> Self { - Self { id: node_id("seq"), children } - } -} - -#[async_trait::async_trait] -impl FlowNode for SequenceNode { - fn id(&self) -> &str { - &self.id - } - - async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result { - let mut last = Value::Null; - for child in &self.children { - let out = child.execute(ctx, expr).await?; - last = out.data; - } - Ok(NodeOutput { id: self.id.clone(), data: last }) - } -} - -#[cfg(feature = "http")] -pub struct HttpNode { - id: NodeId, - method: String, - url: String, - body: Option, -} - -#[cfg(feature = "http")] -impl HttpNode { - pub fn get(url: impl Into) -> Self { - Self { id: node_id("http"), method: "GET".into(), url: url.into(), body: None } - } - pub fn post(url: impl Into, body: Value) -> Self { - Self { id: node_id("http"), method: "POST".into(), url: url.into(), body: Some(body) } - } -} - -#[cfg(feature = "http")] -#[async_trait::async_trait] -impl FlowNode for HttpNode { - fn id(&self) -> &str { - &self.id - } - - async fn execute(&self, _ctx: &mut Context, _expr: Option<&dyn ExprEngine>) -> Result { - let client = reqwest::Client::new(); - let resp = match self.method.as_str() { - "GET" => client.get(&self.url).send().await, - "POST" => client.post(&self.url).json(&self.body).send().await, - _ => return Err(NodeError::Exec("Unsupported HTTP method".into())), - } - .map_err(|e| NodeError::Exec(format!("{e}")))?; - let status = resp.status().as_u16(); - let json = resp.json::().await.map_err(|e| NodeError::Exec(format!("{e}")))?; - Ok(NodeOutput { id: self.id.clone(), data: serde_json::json!({ "status": status, "body": json }) }) - } -} - -pub struct ExprSetNode { - id: NodeId, - engine: ExprEngineKind, - script: String, - target_path: String, -} - -impl ExprSetNode { - pub fn new(engine: ExprEngineKind, script: impl Into, target_path: impl Into) -> Self { - Self { - id: node_id("expr_set"), - engine, - script: script.into(), - target_path: target_path.into(), - } - } -} - -#[async_trait::async_trait] -impl FlowNode for ExprSetNode { - fn id(&self) -> &str { - &self.id - } - - async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result { - let engine = expr.ok_or_else(|| NodeError::Exec("Expr engine not provided".into()))?; - let val = engine.eval(&self.script, ctx).await.map_err(|e| NodeError::Exec(e.to_string()))?; - ctx.set(&self.target_path, val.clone()); - ctx.record_write(self.id.clone(), self.target_path.clone()); - Ok(NodeOutput { id: self.id.clone(), data: val }) - } -} - -pub struct ExprGetNode { - id: NodeId, - engine: ExprEngineKind, - script: String, -} - -impl ExprGetNode { - pub fn new(engine: ExprEngineKind, script: impl Into) -> Self { - Self { id: node_id("expr_get"), engine, script: script.into() } - } -} - -#[async_trait::async_trait] -impl FlowNode for ExprGetNode { - fn id(&self) -> &str { - &self.id - } - - async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result { - let engine = expr.ok_or_else(|| NodeError::Exec("Expr engine not provided".into()))?; - let val = engine.eval(&self.script, ctx).await.map_err(|e| NodeError::Exec(e.to_string()))?; - Ok(NodeOutput { id: self.id.clone(), data: val }) - } -} - -pub struct ForkJoinNode { - id: NodeId, - branches: Vec, - merge_to_ctx: Option, - merge_mode: MergeMode, -} - -impl ForkJoinNode { - pub fn new(branches: Vec) -> Self { - Self { id: node_id("fork"), branches, merge_to_ctx: None, merge_mode: MergeMode::Array } - } - pub fn with_merge(branches: Vec, merge_to_ctx: impl Into, merge_mode: MergeMode) -> Self { - Self { id: node_id("fork"), branches, merge_to_ctx: Some(merge_to_ctx.into()), merge_mode } - } -} - -#[async_trait::async_trait] -impl FlowNode for ForkJoinNode { - fn id(&self) -> &str { - &self.id - } - - async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result { - let mut tasks = Vec::with_capacity(self.branches.len()); - for b in &self.branches { - let mut subctx = Context::with_value(ctx.as_value().clone()); - let b = b.clone(); - let expr = expr; - tasks.push(tokio::spawn(async move { b.execute(&mut subctx, expr).await })); - } - let mut results = Vec::new(); - for t in tasks { - let out = t.await.map_err(|e| NodeError::Exec(format!("Join error: {e}")))??; - results.push(serde_json::json!({ "id": out.id, "data": out.data })); - } - let data = match self.merge_mode { - MergeMode::Array => Value::Array(results.clone()), - MergeMode::ObjectById => { - let mut map = serde_json::Map::new(); - for item in &results { - let id = item.get("id").and_then(|v| v.as_str()).unwrap_or_default().to_string(); - let data = item.get("data").cloned().unwrap_or(Value::Null); - map.insert(id, data); - } - Value::Object(map) - } - }; - if let Some(path) = &self.merge_to_ctx { - ctx.set(path, data.clone()); - ctx.record_write(self.id.clone(), path.clone()); - } - Ok(NodeOutput { id: self.id.clone(), data }) - } -} - -pub struct GroupNode { - id: NodeId, - parallel: Vec, - merge_to_ctx: Option, - merge_mode: MergeMode, -} - -impl GroupNode { - pub fn new(parallel: Vec) -> Self { - Self { id: node_id("group"), parallel, merge_to_ctx: None, merge_mode: MergeMode::Array } - } - pub fn with_merge(parallel: Vec, merge_to_ctx: impl Into, merge_mode: MergeMode) -> Self { - Self { id: node_id("group"), parallel, merge_to_ctx: Some(merge_to_ctx.into()), merge_mode } - } -} - -#[async_trait::async_trait] -impl FlowNode for GroupNode { - fn id(&self) -> &str { - &self.id - } - - async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result { - let mut joins = Vec::with_capacity(self.parallel.len()); - for n in &self.parallel { - let mut subctx = Context::with_value(ctx.as_value().clone()); - let n = n.clone(); - let expr = expr; - joins.push(tokio::spawn(async move { n.execute(&mut subctx, expr).await })); - } - let mut results = Vec::new(); - for j in joins { - let out = j.await.map_err(|e| NodeError::Exec(format!("Group join error: {e}")))??; - results.push(serde_json::json!({ "id": out.id, "data": out.data })); - } - let data = match self.merge_mode { - MergeMode::Array => Value::Array(results.clone()), - MergeMode::ObjectById => { - let mut map = serde_json::Map::new(); - for item in &results { - let id = item.get("id").and_then(|v| v.as_str()).unwrap_or_default().to_string(); - let data = item.get("data").cloned().unwrap_or(Value::Null); - map.insert(id, data); - } - Value::Object(map) - } - }; - if let Some(path) = &self.merge_to_ctx { - ctx.set(path, data.clone()); - ctx.record_write(self.id.clone(), path.clone()); - } - Ok(NodeOutput { id: self.id.clone(), data }) - } -} - -pub struct ConditionalNode { - id: NodeId, - engine: ExprEngineKind, - condition: String, - then_node: NodeRef, - else_node: Option, -} - -impl ConditionalNode { - pub fn new(engine: ExprEngineKind, condition: impl Into, then_node: NodeRef, else_node: Option) -> Self { - Self { - id: node_id("cond"), - engine, - condition: condition.into(), - then_node, - else_node, - } - } -} - -#[async_trait::async_trait] -impl FlowNode for ConditionalNode { - fn id(&self) -> &str { - &self.id - } - - async fn execute(&self, ctx: &mut Context, expr: Option<&dyn ExprEngine>) -> Result { - let engine = expr.ok_or_else(|| NodeError::Exec("Expr engine not provided".into()))?; - let val = engine.eval(&self.condition, ctx).await.map_err(|e| NodeError::Exec(e.to_string()))?; - let cond = match val { - Value::Bool(b) => b, - _ => false, - }; - let selected = if cond { &self.then_node } else { self.else_node.as_ref().unwrap_or(&self.then_node) }; - let out = selected.execute(ctx, Some(engine)).await?; - Ok(NodeOutput { id: self.id.clone(), data: out.data }) - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub enum MergeMode { - Array, - ObjectById, -} - -pub struct DbNode { - id: NodeId, - operation: String, - params: Value, -} - -impl DbNode { - pub fn new(operation: impl Into, params: Value) -> Self { - Self { id: node_id("db"), operation: operation.into(), params } - } -} - -#[async_trait::async_trait] -impl FlowNode for DbNode { - fn id(&self) -> &str { - &self.id - } - - async fn execute(&self, ctx: &mut Context, _expr: Option<&dyn ExprEngine>) -> Result { - let result = serde_json::json!({ - "op": self.operation, - "params": self.params, - "status": "ok" - }); - ctx.set("db.last", result.clone()); - ctx.record_write(self.id.clone(), "db.last".to_string()); - Ok(NodeOutput { id: self.id.clone(), data: result }) - } -} - -pub struct MqNode { - id: NodeId, - topic: String, - message: Value, -} - -impl MqNode { - pub fn new(topic: impl Into, message: Value) -> Self { - Self { id: node_id("mq"), topic: topic.into(), message } - } -} - -#[async_trait::async_trait] -impl FlowNode for MqNode { - fn id(&self) -> &str { - &self.id - } - - async fn execute(&self, ctx: &mut Context, _expr: Option<&dyn ExprEngine>) -> Result { - let result = serde_json::json!({ - "topic": self.topic, - "message": self.message, - "status": "sent" - }); - ctx.set("mq.last", result.clone()); - ctx.record_write(self.id.clone(), "mq.last".to_string()); - Ok(NodeOutput { id: self.id.clone(), data: result }) - } -} - -pub struct LineageNode { - id: NodeId, - target_path: Option, -} - -impl LineageNode { - pub fn new() -> Self { - Self { id: node_id("lineage"), target_path: None } - } - pub fn to_path(mut self, path: impl Into) -> Self { - self.target_path = Some(path.into()); - self - } -} - -#[async_trait::async_trait] -impl FlowNode for LineageNode { - fn id(&self) -> &str { - &self.id - } - - async fn execute(&self, ctx: &mut Context, _expr: Option<&dyn ExprEngine>) -> Result { - let items = ctx.lineage(); - let data = serde_json::to_value(items).map_err(|e| NodeError::Exec(format!("{e}")))?; - if let Some(p) = &self.target_path { - ctx.set(p, data.clone()); - ctx.record_write(self.id.clone(), p.clone()); - } - Ok(NodeOutput { id: self.id.clone(), data }) - } -} - diff --git a/scripts/test_report.sh b/scripts/test_report.sh new file mode 100644 index 0000000..82fd649 --- /dev/null +++ b/scripts/test_report.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set +e + +JS_FEATURE=0 +if [[ "$1" == "--js" ]]; then + JS_FEATURE=1 +fi + +dir="target/test-reports" +docDir="doc" +mkdir -p "$dir" +mkdir -p "$docDir" + +if [[ "$JS_FEATURE" == "1" ]]; then + header="## JS feature" + outFile="$docDir/performance-js.md" + featureArgs="--features js" +else + header="## Default features" + outFile="$docDir/performance-default.md" + featureArgs="" +fi + +names=(test_rhai_expr_set_and_get test_conditional_node_then_else test_http_node_with_mock test_stateful_engine test_db_and_mq_nodes test_group_parallel_sleep test_expr_set_without_engine_error) +if [[ "$JS_FEATURE" == "1" ]]; then + names+=(test_js_expr_and_fork_join) +fi + +md="# dsl-flow Test Performance\n\n${header}\n" +for n in "${names[@]}"; do + # Measure elapsed seconds with bash time builtin + TIMEFORMAT='%3R' + dur=$( { time cargo test -p dsl-flow ${featureArgs} -- --exact "${n}" --nocapture --quiet >/dev/null; } 2>&1 ) + md+="- ${n}: ${dur}s\n" +done + +printf "%b" "$md" > "$outFile" +echo "Report written to $outFile" +