From 5f7770cef752ad7be34c06fa233afe15761c317d Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 5 Apr 2026 10:40:40 +0200 Subject: [PATCH 1/8] ref: moved flows into root folder --- flows/01_return_object.json | 78 ++++++++++++++++++++++++ flows/02_return_flow_input.json | 89 ++++++++++++++++++++++++++++ flows/03_for_each.json | 101 ++++++++++++++++++++++++++++++++ 3 files changed, 268 insertions(+) create mode 100644 flows/01_return_object.json create mode 100644 flows/02_return_flow_input.json create mode 100644 flows/03_for_each.json diff --git a/flows/01_return_object.json b/flows/01_return_object.json new file mode 100644 index 0000000..cd5b3f3 --- /dev/null +++ b/flows/01_return_object.json @@ -0,0 +1,78 @@ +{ + "name": "01_return_object", + "description": "This flow expects a simple http response object as the return value", + "inputs": [ + { + "input": null, + "expected_result": { + "status_code": 200, + "headers": { + "Header": "X" + }, + "payload": "Hello World" + } + } + ], + "flow": { + "starting_node_id": "1", + "node_functions": [ + { + "definition_source": "taurus", + "databaseId": "2", + "runtimeFunctionId": "rest::control::respond", + "parameters": [ + { + "databaseId": "4", + "runtimeParameterId": "http_response", + "value": { + "referenceValue": { + "nodeId": "1" + } + } + } + ] + }, + { + "databaseId": "1", + "definition_source": "taurus", + "runtimeFunctionId": "http::response::create", + "parameters": [ + { + "databaseId": "1", + "runtimeParameterId": "http_status_code", + "value": { + "literalValue": { + "stringValue": "200" + } + } + }, + { + "databaseId": "2", + "runtimeParameterId": "headers", + "value": { + "literalValue": { + "structValue": { + "fields": { + "Header": { + "stringValue": "X" + } + } + } + } + } + }, + { + "databaseId": "3", + "runtimeParameterId": "payload", + "value": { + "literalValue": { + "stringValue": "Hello World" + } + } + } + ], + "nextNodeId": "2" + } + ] + } +} diff --git a/flows/02_return_flow_input.json b/flows/02_return_flow_input.json new file mode 100644 index 0000000..9a44b8c --- /dev/null +++ b/flows/02_return_flow_input.json @@ -0,0 +1,89 @@ +{ + "name": "02_return_object", + "description": "This flow expects the same output value as the input", + "inputs": [ + { + "input": null, + "expected_result": { + "status_code": 200, + "headers": { + "Authentication": "X" + }, + "payload": null + } + }, + { + "input": { + "name": "Joe Doe", + "age": 51, + "pets": [ + "dog", + "cat", + "bird" + ] + }, + "expected_result": { + "status_code": 200, + "headers": { + "Authentication": "X" + }, + "payload": { + "name": "Joe Doe", + "age": 51, + "pets": [ + "dog", + "cat", + "bird" + ] + } + } + } + ], + "flow": { + "flowId": "2", + "projectId": "1", + "startingNodeId": "3", + "nodeFunctions": [ + { + "definition_source": "taurus", + "databaseId": "3", + "runtimeFunctionId": "http::response::create", + "parameters": [ + { + "databaseId": "5", + "runtimeParameterId": "http_status_code", + "value": { + "literalValue": { + "stringValue": "200" + } + } + }, + { + "databaseId": "6", + "runtimeParameterId": "headers", + "value": { + "literalValue": { + "structValue": { + "fields": { + "Authentication": { + "stringValue": "X" + } + } + } + } + } + }, + { + "databaseId": "7", + "runtimeParameterId": "payload", + "value": { + "referenceValue": { + "flowInput": {} + } + } + } + ] + } + ] + } +} diff --git a/flows/03_for_each.json b/flows/03_for_each.json new file mode 100644 index 0000000..383688f --- /dev/null +++ b/flows/03_for_each.json @@ -0,0 +1,101 @@ +{ + "name": "03_for_each", + "description": "This flow validates the functionality of the refernece type 'input_type'", + "inputs": [ + { + "input": null, + "expected_result": null + } + ], + "flow": { + "startingNodeId": "5", + "nodeFunctions": [ + { + "definition_source": "taurus", + "databaseId": "5", + "runtimeFunctionId": "std::list::for_each", + "parameters": [ + { + "databaseId": "11", + "runtimeParameterId": "list", + "value": { + "literalValue": { + "listValue": { + "values": [ + { + "numberValue": { + "integer": "1" + } + }, + { + "numberValue": { + "integer": "2" + } + }, + { + "numberValue": { + "integer": "3" + } + }, + { + "numberValue": { + "integer": "4" + } + }, + { + "numberValue": { + "integer": "5" + } + }, + { + "numberValue": { + "integer": "6" + } + } + ] + } + } + } + }, + { + "databaseId": "12", + "runtimeParameterId": "consumer", + "value": { + "nodeFunctionId": "6" + } + } + ] + }, + { + "databaseId": "6", + "definition_source": "taurus", + "runtimeFunctionId": "std::number::add", + "parameters": [ + { + "databaseId": "13", + "runtimeParameterId": "first", + "value": { + "referenceValue": { + "inputType": { + "nodeId": "5", + "parameterIndex": "1" + } + } + } + }, + { + "databaseId": "14", + "runtimeParameterId": "second", + "value": { + "literalValue": { + "numberValue": { + "integer": "2" + } + } + } + } + ] + } + ] + } +} From 19ef01fae5f6e169f67ccbd745e566ef21779144 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 5 Apr 2026 10:40:51 +0200 Subject: [PATCH 2/8] feat: added test-core and manual crate --- Cargo.lock | 17 +++++++++++++++++ Cargo.toml | 5 ++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 71b110c..2edea45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -987,6 +987,10 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "manual" +version = "0.1.0" + [[package]] name = "matchit" version = "0.8.4" @@ -1823,6 +1827,19 @@ dependencies = [ [[package]] name = "tests" version = "0.1.0" +dependencies = [ + "env_logger", + "log", + "serde", + "serde_json", + "taurus-core", + "tests-core", + "tucana", +] + +[[package]] +name = "tests-core" +version = "0.1.0" dependencies = [ "env_logger", "log", diff --git a/Cargo.toml b/Cargo.toml index 1104924..2374da2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [ "crates/core", "crates/taurus", "crates/tests" ] +members = [ "crates/core", "crates/manual", "crates/taurus", "crates/tests" , "crates/tests-core"] resolver = "3" [workspace.package] @@ -25,3 +25,6 @@ serde = "1.0.228" [workspace.dependencies.taurus-core] path = "./crates/core" + +[workspace.dependencies.tests-core] +path = "./crates/tests-core" From 0c678fbe137aab10cbeee3ea676a6f1de2d6ff81 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 5 Apr 2026 10:41:06 +0200 Subject: [PATCH 3/8] feat: made empty defintion source take runtime by default --- crates/core/src/context/executor.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/core/src/context/executor.rs b/crates/core/src/context/executor.rs index 478f1c7..48c7ad1 100644 --- a/crates/core/src/context/executor.rs +++ b/crates/core/src/context/executor.rs @@ -32,8 +32,7 @@ use crate::runtime::remote::RemoteRuntime; use futures_lite::future::block_on; use std::collections::HashMap; -use tucana::aquila::execution_result::Result as ExecutionOutcome; -use tucana::aquila::{ActionRuntimeError, ExecutionRequest, ExecutionResult}; +use tucana::aquila::ExecutionRequest; use tucana::shared::reference_value::Target; use tucana::shared::value::Kind; use tucana::shared::{NodeFunction, Struct, Value}; @@ -56,6 +55,14 @@ pub struct Executor<'a> { /// The current policy treats any node whose `definition_source` is not `"taurus"` /// as a remote node. fn is_remote(node: &NodeFunction) -> bool { + if node.definition_source == "" { + log::warn!( + "Found empty definition source, taking runtime as origin for node id: {}", + node.database_id + ); + return false; + } + node.definition_source != "taurus" } From 61356ffa28e071ed47928538aa107e6b7b6fdbf6 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 5 Apr 2026 10:41:18 +0200 Subject: [PATCH 4/8] feat: init manual create --- crates/manual/Cargo.toml | 6 ++++++ crates/manual/src/main.rs | 3 +++ 2 files changed, 9 insertions(+) create mode 100644 crates/manual/Cargo.toml create mode 100644 crates/manual/src/main.rs diff --git a/crates/manual/Cargo.toml b/crates/manual/Cargo.toml new file mode 100644 index 0000000..829e882 --- /dev/null +++ b/crates/manual/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "manual" +version.workspace = true +edition.workspace = true + +[dependencies] diff --git a/crates/manual/src/main.rs b/crates/manual/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/crates/manual/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 78f94cf4ce60cfa774e5ca88f090fd3cbc52f166 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 5 Apr 2026 10:41:46 +0200 Subject: [PATCH 5/8] ref: split test crate into tests-core --- crates/tests-core/Cargo.toml | 13 +++ crates/tests-core/src/lib.rs | 99 ++++++++++++++++++++ crates/tests/Cargo.toml | 1 + crates/tests/flows/01_return_object.json | 76 --------------- crates/tests/flows/02_return_flow_input.json | 88 ----------------- crates/tests/flows/03_for_each.json | 99 -------------------- crates/tests/src/main.rs | 99 +++----------------- 7 files changed, 125 insertions(+), 350 deletions(-) create mode 100644 crates/tests-core/Cargo.toml create mode 100644 crates/tests-core/src/lib.rs delete mode 100644 crates/tests/flows/01_return_object.json delete mode 100644 crates/tests/flows/02_return_flow_input.json delete mode 100644 crates/tests/flows/03_for_each.json diff --git a/crates/tests-core/Cargo.toml b/crates/tests-core/Cargo.toml new file mode 100644 index 0000000..cc6b9ff --- /dev/null +++ b/crates/tests-core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tests-core" +version.workspace = true +edition.workspace = true + +[dependencies] +tucana = { workspace = true } +taurus-core = { workspace = true } +log = { workspace = true } +env_logger = { workspace = true } +serde_json = { workspace = true } +serde = { workspace = true } + diff --git a/crates/tests-core/src/lib.rs b/crates/tests-core/src/lib.rs new file mode 100644 index 0000000..590526c --- /dev/null +++ b/crates/tests-core/src/lib.rs @@ -0,0 +1,99 @@ +use std::path::Path; + +use log::{error, info}; +use serde::Deserialize; +use tucana::shared::ValidationFlow; + +#[derive(Clone, Deserialize)] +pub struct Input { + pub input: Option, + pub expected_result: serde_json::Value, +} + +#[derive(Clone, Deserialize)] +pub struct Case { + pub name: String, + pub description: String, + pub inputs: Vec, + pub flow: ValidationFlow, +} + +pub enum CaseResult { + Success, + Failure(Input, serde_json::Value), +} + +pub trait Testable { + fn run(&self) -> CaseResult; +} + +#[derive(Clone, Deserialize)] +pub struct Cases { + pub cases: Vec, +} + +pub fn print_success(case: &Case) { + info!("test {} ... ok", case.name); +} + +pub fn print_failure(case: &Case, input: &Input, result: serde_json::Value) { + error!("test {} ... FAILED", case.name); + error!(" input: {:?}", input.input); + error!(" expected: {:?}", input.expected_result); + error!(" real_value: {:?}", result); + error!(" message: {}", case.description); +} + +fn get_test_case + std::fmt::Debug>(path: P) -> Option { + let content = match std::fs::read_to_string(&path) { + Ok(it) => it, + Err(err) => { + log::error!("Cannot read file ({:?}): {:?}", path, err); + return None; + } + }; + + match serde_json::from_str(&content) { + Ok(it) => it, + Err(err) => { + log::error!("Cannot read json ({:?}): {:?}", path, err); + return None; + } + } +} + +fn get_test_cases(path: &str) -> Cases { + let mut items = Vec::new(); + let dir = match std::fs::read_dir(path) { + Ok(d) => d, + Err(err) => { + panic!("Cannot open path: {:?}", err) + } + }; + + for entry in dir { + let entry = match entry { + Ok(it) => it, + Err(err) => { + log::error!("Cannot read entry: {:?}", err); + continue; + } + }; + let file_path = entry.path(); + items.push(match get_test_case(&file_path) { + Some(it) => it, + None => { + continue; + } + }); + } + + Cases { cases: items } +} + + +impl Cases { + pub fn from_path(path: &str) -> Self { + get_test_cases(path) + } +} diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml index 0e4f08a..8ac7443 100644 --- a/crates/tests/Cargo.toml +++ b/crates/tests/Cargo.toml @@ -9,4 +9,5 @@ taurus-core = { workspace = true } log = { workspace = true } env_logger = { workspace = true } serde_json = { workspace = true } +tests-core = { workspace = true } serde = { workspace = true } diff --git a/crates/tests/flows/01_return_object.json b/crates/tests/flows/01_return_object.json deleted file mode 100644 index 34c28c8..0000000 --- a/crates/tests/flows/01_return_object.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "name": "01_return_object", - "description": "This flow expects a simple http response object as the return value", - "inputs": [ - { - "input": null, - "expected_result": { - "status_code": 200, - "headers": { - "Header": "X" - }, - "payload": "Hello World" - } - } - ], - "flow": { - "starting_node_id": "1", - "node_functions": [ - { - "databaseId": "2", - "runtimeFunctionId": "rest::control::respond", - "parameters": [ - { - "databaseId": "4", - "runtimeParameterId": "http_response", - "value": { - "referenceValue": { - "nodeId": "1" - } - } - } - ] - }, - { - "databaseId": "1", - "runtimeFunctionId": "http::response::create", - "parameters": [ - { - "databaseId": "1", - "runtimeParameterId": "http_status_code", - "value": { - "literalValue": { - "stringValue": "200" - } - } - }, - { - "databaseId": "2", - "runtimeParameterId": "headers", - "value": { - "literalValue": { - "structValue": { - "fields": { - "Header": { - "stringValue": "X" - } - } - } - } - } - }, - { - "databaseId": "3", - "runtimeParameterId": "payload", - "value": { - "literalValue": { - "stringValue": "Hello World" - } - } - } - ], - "nextNodeId": "2" - } - ] - } -} diff --git a/crates/tests/flows/02_return_flow_input.json b/crates/tests/flows/02_return_flow_input.json deleted file mode 100644 index ef8402d..0000000 --- a/crates/tests/flows/02_return_flow_input.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "name": "02_return_object", - "description": "This flow expects the same output value as the input", - "inputs": [ - { - "input": null, - "expected_result": { - "status_code": 200, - "headers": { - "Authentication": "X" - }, - "payload": null - } - }, - { - "input": { - "name": "Joe Doe", - "age": 51, - "pets": [ - "dog", - "cat", - "bird" - ] - }, - "expected_result": { - "status_code": 200, - "headers": { - "Authentication": "X" - }, - "payload": { - "name": "Joe Doe", - "age": 51, - "pets": [ - "dog", - "cat", - "bird" - ] - } - } - } - ], - "flow": { - "flowId": "2", - "projectId": "1", - "startingNodeId": "3", - "nodeFunctions": [ - { - "databaseId": "3", - "runtimeFunctionId": "http::response::create", - "parameters": [ - { - "databaseId": "5", - "runtimeParameterId": "http_status_code", - "value": { - "literalValue": { - "stringValue": "200" - } - } - }, - { - "databaseId": "6", - "runtimeParameterId": "headers", - "value": { - "literalValue": { - "structValue": { - "fields": { - "Authentication": { - "stringValue": "X" - } - } - } - } - } - }, - { - "databaseId": "7", - "runtimeParameterId": "payload", - "value": { - "referenceValue": { - "flowInput": {} - } - } - } - ] - } - ] - } -} diff --git a/crates/tests/flows/03_for_each.json b/crates/tests/flows/03_for_each.json deleted file mode 100644 index 231d005..0000000 --- a/crates/tests/flows/03_for_each.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "name": "03_for_each", - "description": "This flow validates the functionality of the refernece type 'input_type'", - "inputs": [ - { - "input": null, - "expected_result": null - } - ], - "flow": { - "startingNodeId": "5", - "nodeFunctions": [ - { - "databaseId": "5", - "runtimeFunctionId": "std::list::for_each", - "parameters": [ - { - "databaseId": "11", - "runtimeParameterId": "list", - "value": { - "literalValue": { - "listValue": { - "values": [ - { - "numberValue": { - "integer": "1" - } - }, - { - "numberValue": { - "integer": "2" - } - }, - { - "numberValue": { - "integer": "3" - } - }, - { - "numberValue": { - "integer": "4" - } - }, - { - "numberValue": { - "integer": "5" - } - }, - { - "numberValue": { - "integer": "6" - } - } - ] - } - } - } - }, - { - "databaseId": "12", - "runtimeParameterId": "consumer", - "value": { - "nodeFunctionId": "6" - } - } - ] - }, - { - "databaseId": "6", - "runtimeFunctionId": "std::number::add", - "parameters": [ - { - "databaseId": "13", - "runtimeParameterId": "first", - "value": { - "referenceValue": { - "inputType": { - "nodeId": "5", - "parameterIndex": "1" - } - } - } - }, - { - "databaseId": "14", - "runtimeParameterId": "second", - "value": { - "literalValue": { - "numberValue": { - "integer": "2" - } - } - } - } - ] - } - ] - } -} diff --git a/crates/tests/src/main.rs b/crates/tests/src/main.rs index 5eb1e37..7278561 100644 --- a/crates/tests/src/main.rs +++ b/crates/tests/src/main.rs @@ -3,102 +3,27 @@ use serde::Deserialize; use serde_json::json; use std::collections::HashMap; use taurus_core::context::{context::Context, executor::Executor, registry::FunctionStore}; +use tests_core::{Case, CaseResult, Cases, print_failure, print_success}; use tucana::shared::{ - NodeFunction, ValidationFlow, + NodeFunction, helper::value::{from_json_value, to_json_value}, }; -#[derive(Clone, Deserialize)] -struct Input { - input: Option, - expected_result: serde_json::Value, +pub trait Testable { + fn run(&self) -> CaseResult; } -#[derive(Clone, Deserialize)] -struct Case { - name: String, - description: String, - inputs: Vec, - flow: ValidationFlow, -} - -#[derive(Clone, Deserialize)] -struct TestCases { - cases: Vec, -} - -fn print_success(case: &Case) { - info!("test {} ... ok", case.name); -} - -fn print_failure(case: &Case, input: &Input, result: serde_json::Value) { - error!("test {} ... FAILED", case.name); - error!(" input: {:?}", input.input); - error!(" expected: {:?}", input.expected_result); - error!(" real_value: {:?}", result); - error!(" message: {}", case.description); -} - -fn get_test_cases(path: &str) -> TestCases { - let mut items = Vec::new(); - let dir = match std::fs::read_dir(path) { - Ok(d) => d, - Err(err) => { - panic!("Cannot open path: {:?}", err) +fn run_tests(cases: Cases) { + for case in cases.cases.clone() { + match case.run() { + CaseResult::Success => print_success(&case), + CaseResult::Failure(input, result) => print_failure(&case, &input, result), } - }; - - for entry in dir { - let entry = match entry { - Ok(it) => it, - Err(err) => { - log::error!("Cannot read entry: {:?}", err); - continue; - } - }; - let path = entry.path(); - - let content = match std::fs::read_to_string(&path) { - Ok(it) => it, - Err(err) => { - log::error!("Cannot read file ({:?}): {:?}", path, err); - continue; - } - }; - items.push(match serde_json::from_str(&content) { - Ok(it) => it, - Err(err) => { - log::error!("Cannot read json ({:?}): {:?}", path, err); - continue; - } - }); - } - - TestCases { cases: items } -} - -impl TestCases { - pub fn from_path(path: &str) -> Self { - get_test_cases(path) } - - pub fn run_tests(&self) { - for case in self.cases.clone() { - match case.run() { - CaseResult::Success => print_success(&case), - CaseResult::Failure(input, result) => print_failure(&case, &input, result), - } - } - } -} - -enum CaseResult { - Success, - Failure(Input, serde_json::Value), } -impl Case { +impl Testable for Case { fn run(&self) -> CaseResult { let store = FunctionStore::default(); @@ -167,6 +92,6 @@ fn main() { .filter_level(log::LevelFilter::Info) .init(); - let cases = TestCases::from_path("./crates/tests/flows/"); - cases.run_tests(); + let cases = Cases::from_path("./flows/"); + run_tests(cases); } From 11dc13dbead232dc25e6558752a36b4c9fe4768f Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 5 Apr 2026 13:59:21 +0200 Subject: [PATCH 6/8] feat: added remote execution & output logic --- Cargo.lock | 60 +++++++++++++ crates/manual/Cargo.toml | 12 +++ crates/manual/src/main.rs | 161 ++++++++++++++++++++++++++++++++++- crates/tests-core/src/lib.rs | 8 ++ 4 files changed, 239 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2edea45..283b285 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,6 +278,46 @@ dependencies = [ "num-traits", ] +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + [[package]] name = "code0-flow" version = "0.0.29" @@ -990,6 +1030,20 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "manual" version = "0.1.0" +dependencies = [ + "async-nats 0.46.0", + "clap", + "env_logger", + "log", + "prost", + "serde", + "serde_json", + "taurus-core", + "tests-core", + "tokio", + "tonic", + "tucana", +] [[package]] name = "matchit" @@ -1746,6 +1800,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" diff --git a/crates/manual/Cargo.toml b/crates/manual/Cargo.toml index 829e882..3682f73 100644 --- a/crates/manual/Cargo.toml +++ b/crates/manual/Cargo.toml @@ -4,3 +4,15 @@ version.workspace = true edition.workspace = true [dependencies] +tests-core = { workspace = true } +tucana = { workspace = true } +taurus-core = { workspace = true } +log = { workspace = true } +env_logger = { workspace = true } +serde_json = { workspace = true } +serde = { workspace = true } +prost = { workspace = true } +tonic = { workspace = true } +tokio = { workspace = true } +async-nats = { workspace = true } +clap ={ version = "4.6.0", features= ["derive"] } diff --git a/crates/manual/src/main.rs b/crates/manual/src/main.rs index e7a11a9..2962a1e 100644 --- a/crates/manual/src/main.rs +++ b/crates/manual/src/main.rs @@ -1,3 +1,160 @@ -fn main() { - println!("Hello, world!"); +use std::collections::HashMap; + +use async_nats::Client; +use clap::{Parser, arg, command}; +use prost::Message; +use taurus_core::context::{context::Context, executor::Executor, registry::FunctionStore}; +use taurus_core::runtime::{error::RuntimeError, remote::RemoteRuntime}; +use tests_core::Case; +use tonic::async_trait; +use tucana::shared::helper::value::to_json_value; +use tucana::shared::{NodeFunction, helper::value::from_json_value}; +use tucana::{ + aquila::{ExecutionRequest, ExecutionResult}, + shared::Value, +}; + +pub struct RemoteNatsClient { + client: Client, +} + +impl RemoteNatsClient { + pub fn new(client: Client) -> Self { + RemoteNatsClient { client } + } +} + +#[async_trait] +impl RemoteRuntime for RemoteNatsClient { + async fn execute_remote( + &self, + remote_name: String, + request: ExecutionRequest, + ) -> Result { + let topic = format!("action.{}.{}", remote_name, request.execution_identifier); + let payload = request.encode_to_vec(); + let res = self.client.request(topic, payload.into()).await; + let message = match res { + Ok(r) => r, + Err(_) => { + return Err(RuntimeError::simple_str( + "RemoteRuntimeExeption", + "Failed to handle NATS message", + )); + } + }; + + let decode_result = ExecutionResult::decode(message.payload); + let execution_result = match decode_result { + Ok(r) => r, + Err(_) => { + return Err(RuntimeError::simple_str( + "RemoteRuntimeExeption", + "Failed to decode NATS message", + )); + } + }; + + match execution_result.result { + Some(result) => match result { + tucana::aquila::execution_result::Result::Success(value) => Ok(value), + tucana::aquila::execution_result::Result::Error(err) => { + let name = err.code.to_string(); + let description = match err.description { + Some(string) => string, + None => "Unknown Error".to_string(), + }; + let error = RuntimeError::new(name, description, None); + Err(error) + } + }, + None => Err(RuntimeError::simple_str( + "RemoteRuntimeExeption", + "Result of Remote Response was empty.", + )), + } + } +} + +#[derive(clap::Parser, Debug)] +#[command(author, version, about)] +struct Args { + /// Index value + #[arg(short, long, default_value_t = 0)] + index: i32, + + /// NATS server URL + #[arg(short, long, default_value_t = String::from("nats://127.0.0.1:4222"))] + nats_url: String, + + /// Path value + #[arg(short, long)] + path: String, +} + +#[tokio::main] +async fn main() { + env_logger::Builder::from_default_env() + .filter_level(log::LevelFilter::Info) + .init(); + + let args = Args::parse(); + let index = args.index; + let nats_url = args.nats_url; + let path = args.path; + let case = Case::from_path(&path); + + let store = FunctionStore::default(); + + let node_functions: HashMap = case + .clone() + .flow + .node_functions + .into_iter() + .map(|node| (node.database_id, node)) + .collect(); + + let mut context = match case.inputs.get(index as usize) { + Some(inp) => match inp.input.clone() { + Some(json_input) => Context::new(from_json_value(json_input)), + None => Context::default(), + }, + None => Context::default(), + }; + + let client = match async_nats::connect(nats_url).await { + Ok(client) => { + log::info!("Connected to nats server"); + client + } + Err(err) => { + panic!("Failed to connect to NATS server: {}", err); + } + }; + let remote = RemoteNatsClient::new(client); + let result = Executor::new(&store, node_functions.clone()) + .with_remote_runtime(&remote) + .execute(case.flow.starting_node_id, &mut context, true); + + match result { + taurus_core::context::signal::Signal::Success(value) => { + let json = to_json_value(value); + let pretty = serde_json::to_string_pretty(&json).unwrap(); + println!("{}", pretty); + } + taurus_core::context::signal::Signal::Return(value) => { + let json = to_json_value(value); + let pretty = serde_json::to_string_pretty(&json).unwrap(); + println!("{}", pretty); + } + taurus_core::context::signal::Signal::Respond(value) => { + let json = to_json_value(value); + let pretty = serde_json::to_string_pretty(&json).unwrap(); + println!("{}", pretty); + } + taurus_core::context::signal::Signal::Stop => println!("Revieved Stop signal"), + taurus_core::context::signal::Signal::Failure(runtime_error) => { + println!("RuntimeError: {:?}", runtime_error); + } + } } diff --git a/crates/tests-core/src/lib.rs b/crates/tests-core/src/lib.rs index 590526c..b50afb7 100644 --- a/crates/tests-core/src/lib.rs +++ b/crates/tests-core/src/lib.rs @@ -91,6 +91,14 @@ fn get_test_cases(path: &str) -> Cases { Cases { cases: items } } +impl Case { + pub fn from_path(path: &str) -> Self { + match get_test_case(path) { + Some(s) => s, + None => panic!("flow was not found"), + } + } +} impl Cases { pub fn from_path(path: &str) -> Self { From bf66f71eb62702c7c15339455d119972d84e2435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20G=C3=B6tz?= <52959657+raphael-goetz@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:12:05 +0200 Subject: [PATCH 7/8] Update crates/tests/src/main.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Raphael Götz <52959657+raphael-goetz@users.noreply.github.com> --- crates/tests/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/tests/src/main.rs b/crates/tests/src/main.rs index 7278561..c46c474 100644 --- a/crates/tests/src/main.rs +++ b/crates/tests/src/main.rs @@ -15,10 +15,10 @@ pub trait Testable { } fn run_tests(cases: Cases) { - for case in cases.cases.clone() { + for case in &cases.cases { match case.run() { - CaseResult::Success => print_success(&case), - CaseResult::Failure(input, result) => print_failure(&case, &input, result), + CaseResult::Success => print_success(case), + CaseResult::Failure(input, result) => print_failure(case, &input, result), } } } From 8210773d5153d9c21a1a63999cc7607183b760ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20G=C3=B6tz?= <52959657+raphael-goetz@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:12:30 +0200 Subject: [PATCH 8/8] Update crates/manual/src/main.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Raphael Götz <52959657+raphael-goetz@users.noreply.github.com> --- crates/manual/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/manual/src/main.rs b/crates/manual/src/main.rs index 2962a1e..ba0559e 100644 --- a/crates/manual/src/main.rs +++ b/crates/manual/src/main.rs @@ -152,7 +152,7 @@ async fn main() { let pretty = serde_json::to_string_pretty(&json).unwrap(); println!("{}", pretty); } - taurus_core::context::signal::Signal::Stop => println!("Revieved Stop signal"), + taurus_core::context::signal::Signal::Stop => println!("Received Stop signal"), taurus_core::context::signal::Signal::Failure(runtime_error) => { println!("RuntimeError: {:?}", runtime_error); }