├── img.png ├── example ├── wasm_example │ ├── wasm_example_one.wasm │ ├── wasm_example_two.wasm │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── src │ ├── main.rs │ ├── wasm_test.rs │ ├── custom_rule_exec.rs │ ├── array_env_test.rs │ ├── lua_test.rs │ ├── function_test.rs │ ├── expr_test.rs │ └── many_async_test.rs ├── lua_script │ ├── handle.lua │ └── module.lua ├── benches │ ├── expression_parse.rs │ ├── assign_simple_parse.rs │ ├── wasm_bm.rs │ ├── lua_script_bm.rs │ ├── function_benchmark.rs │ └── async_rush.rs └── Cargo.toml ├── rush_lua_engine ├── lua_test_script │ ├── module.lua │ └── simple.lua ├── src │ ├── lib.rs │ ├── loader.rs │ ├── lua_runtime_builder.rs │ └── lua_runtime.rs └── Cargo.toml ├── rush_core ├── src │ ├── lib.rs │ ├── define.rs │ ├── task_pool.rs │ ├── std_tool.rs │ ├── function.rs │ └── rush.rs └── Cargo.toml ├── rush_wasm_engine ├── src │ ├── lib.rs │ ├── loader.rs │ ├── export_env.rs │ ├── wasm_runtime_build.rs │ └── wasm_runtime.rs └── Cargo.toml ├── rush_expr_engine ├── src │ ├── lib.rs │ ├── error.rs │ ├── calc_builder.rs │ ├── assign.rs │ ├── rule_builder.rs │ ├── calc.rs │ └── calc_parse.rs └── Cargo.toml ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── cmd.sh ├── Cargo.toml ├── LICENSE └── README.md /img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woshihaoren4/rush/HEAD/img.png -------------------------------------------------------------------------------- /example/wasm_example/wasm_example_one.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woshihaoren4/rush/HEAD/example/wasm_example/wasm_example_one.wasm -------------------------------------------------------------------------------- /example/wasm_example/wasm_example_two.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woshihaoren4/rush/HEAD/example/wasm_example/wasm_example_two.wasm -------------------------------------------------------------------------------- /rush_lua_engine/lua_test_script/module.lua: -------------------------------------------------------------------------------- 1 | module = {name="test_module"} 2 | 3 | module.show = function(...) 4 | print("show:",...) 5 | end 6 | 7 | return module -------------------------------------------------------------------------------- /rush_lua_engine/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod loader; 2 | mod lua_runtime; 3 | mod lua_runtime_builder; 4 | 5 | pub use loader::*; 6 | pub use lua_runtime::*; 7 | pub use lua_runtime_builder::*; 8 | -------------------------------------------------------------------------------- /example/src/main.rs: -------------------------------------------------------------------------------- 1 | mod array_env_test; 2 | mod custom_rule_exec; 3 | mod expr_test; 4 | mod function_test; 5 | mod lua_test; 6 | mod many_async_test; 7 | mod wasm_test; 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /example/lua_script/handle.lua: -------------------------------------------------------------------------------- 1 | local md = require("lua_script/module") 2 | 3 | function handle(req) 4 | md.show_req(req) 5 | return {message='success'} 6 | end 7 | 8 | return {handle_function="handle"} -------------------------------------------------------------------------------- /example/lua_script/module.lua: -------------------------------------------------------------------------------- 1 | module = {name="show_module"} 2 | 3 | module.show_req = function(req) 4 | for i, v in pairs(req) do 5 | print("show--->",i,v) 6 | end 7 | end 8 | 9 | return module -------------------------------------------------------------------------------- /rush_core/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod define; 2 | mod function; 3 | mod rush; 4 | mod std_tool; 5 | mod task_pool; 6 | 7 | pub use define::*; 8 | pub use function::*; 9 | pub use rush::*; 10 | pub use task_pool::*; 11 | -------------------------------------------------------------------------------- /rush_wasm_engine/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod export_env; 2 | mod loader; 3 | mod wasm_runtime; 4 | mod wasm_runtime_build; 5 | 6 | pub use export_env::*; 7 | pub use loader::*; 8 | pub use wasm_runtime::*; 9 | pub use wasm_runtime_build::*; 10 | -------------------------------------------------------------------------------- /rush_lua_engine/lua_test_script/simple.lua: -------------------------------------------------------------------------------- 1 | 2 | local md = require("../lua_test_script/module") 3 | 4 | function handle(req) 5 | for k, v in pairs(req) do 6 | md.show(k,v) 7 | end 8 | local resp = {code=0,message="success"} 9 | return resp 10 | end -------------------------------------------------------------------------------- /example/wasm_example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm_example_one" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | 10 | 11 | [lib] 12 | crate-type = ["cdylib"] -------------------------------------------------------------------------------- /rush_expr_engine/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate core; 2 | 3 | mod assign; 4 | mod calc; 5 | mod calc_builder; 6 | mod calc_parse; 7 | mod error; 8 | mod rule_builder; 9 | 10 | pub use assign::*; 11 | pub use calc::*; 12 | pub use calc_builder::*; 13 | pub use calc_parse::*; 14 | pub use error::*; 15 | pub use rule_builder::*; 16 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | test: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Run tests 20 | run: cargo test --bin example --verbose 21 | -------------------------------------------------------------------------------- /rush_expr_engine/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt; 3 | 4 | #[derive(Default, Debug)] 5 | pub struct NotFoundFieldError(pub String); 6 | 7 | impl fmt::Display for NotFoundFieldError { 8 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 9 | write!(f, "not found field[{}]", self.0.as_str()) 10 | } 11 | } 12 | 13 | impl Error for NotFoundFieldError {} 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | .idea -------------------------------------------------------------------------------- /cmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cmd='./cmd.sh [cmd] 4 | bench run example bench' 5 | 6 | case $1 in 7 | bench) 8 | cd ./example || echo "\n not found path:./example" && exit 1 9 | cargo bench -- --verbose 10 | ;; 11 | test) 12 | cargo test --bin example --verbose 13 | ;; 14 | build_wasm) 15 | cd ./example/wasm_example 16 | cargo build --target wasm32-unknown-unknown --release 17 | ls ../../target/wasm32-unknown-unknown/release 18 | ;; 19 | *) 20 | echo "please input a cmd (bench)" 21 | ;; 22 | esac -------------------------------------------------------------------------------- /rush_core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush_core" 3 | version = "0.1.2" 4 | edition.workspace = true 5 | authors.workspace = true 6 | repository.workspace = true 7 | keywords.workspace = true 8 | description.workspace = true 9 | license.workspace = true 10 | readme = "../README.md" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | wd_tools.workspace = true 16 | anyhow.workspace = true 17 | serde.workspace = true 18 | serde_json.workspace = true 19 | async-trait.workspace = true 20 | tokio.workspace = true 21 | -------------------------------------------------------------------------------- /rush_expr_engine/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush_expr_engine" 3 | version = "0.1.2" 4 | edition.workspace = true 5 | authors.workspace = true 6 | repository.workspace = true 7 | keywords.workspace = true 8 | description.workspace = true 9 | license.workspace = true 10 | readme.workspace = true 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | wd_tools.workspace = true 16 | anyhow.workspace = true 17 | serde.workspace = true 18 | serde_json.workspace = true 19 | rush_core = {version = "0.1", path = "../rush_core"} -------------------------------------------------------------------------------- /rush_wasm_engine/src/loader.rs: -------------------------------------------------------------------------------- 1 | use crate::WasmLoader; 2 | use anyhow::anyhow; 3 | use wd_tools::{PFErr, PFOk}; 4 | 5 | pub const WASM_LOADER_FILE: &'static str = "wasm_file:"; 6 | pub struct WasmLoaderFile; 7 | 8 | impl WasmLoader for WasmLoaderFile { 9 | fn load(&self, _rule_name: String, mut file: String) -> anyhow::Result> { 10 | if !file.starts_with(WASM_LOADER_FILE) { 11 | return anyhow!("expect loader tag:{} not found", WASM_LOADER_FILE).err(); 12 | } 13 | let path = file.split_off(WASM_LOADER_FILE.len()); 14 | let path = path.trim_matches(|x| " \t\r\n".contains(x)); 15 | let bytes = std::fs::read(path)?; 16 | bytes.ok() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["rush_core","rush_expr_engine","rush_lua_engine","example", "rush_wasm_engine","example/wasm_example"] 3 | 4 | [workspace.package] 5 | edition = "2021" 6 | authors = ["Teshin <1443965173@qq.com>"] 7 | repository = "https://github.com/woshihaoren4/rush" 8 | keywords = ["rush"] 9 | description = "The rules engine is based on the rete algorithm" 10 | license = "MIT" 11 | readme = "README.md" 12 | 13 | [workspace.dependencies] 14 | wd_tools = {version = "0.8.12",features = ["point-free","sync"]} 15 | anyhow = "1.0.75" 16 | serde = {version = "1.0.188",features = ["derive"]} 17 | serde_json = "1.0.105" 18 | async-trait = "0.1.73" 19 | tokio = {version = "1.32",features = ["full"]} 20 | async-channel = {version = "1.9.0"} -------------------------------------------------------------------------------- /rush_wasm_engine/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush_wasm_engine" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | authors.workspace = true 6 | repository.workspace = true 7 | keywords.workspace = true 8 | description.workspace = true 9 | license.workspace = true 10 | readme.workspace = true 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | wasmer = {version = "4.1.1"} 16 | anyhow.workspace = true 17 | wd_tools.workspace = true 18 | async-channel.workspace = true 19 | serde.workspace = true 20 | serde_json.workspace = true 21 | rush_core = {version = "0.1",path = "../rush_core",optional = true} 22 | async-trait.workspace = true 23 | #tokio = {version = "1.32.0",features = ["full"]} 24 | tokio = {version = "1.32.0",features = ["sync"]} 25 | 26 | 27 | [features] 28 | #default = ["rule-flow"] 29 | rule-flow = ["rush_core"] -------------------------------------------------------------------------------- /rush_lua_engine/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rush_lua_engine" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | authors.workspace = true 6 | repository.workspace = true 7 | keywords.workspace = true 8 | description.workspace = true 9 | license.workspace = true 10 | readme.workspace = true 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | mlua = { version = "0.9.1", features = ["lua54", "vendored","serialize","async"] } 16 | serde.workspace = true 17 | serde_json.workspace = true 18 | wd_tools.workspace = true 19 | anyhow.workspace = true 20 | async-channel.workspace = true 21 | rush_core = {version = "0.1",path = "../rush_core",optional = true} 22 | async-trait.workspace = true 23 | #tokio = {version = "1.32.0",features = ["full"]} 24 | tokio = {version = "1.32.0",features = ["sync"]} 25 | 26 | 27 | [features] 28 | #default = ["rule-flow"] 29 | rule-flow = ["rush_core"] -------------------------------------------------------------------------------- /example/benches/expression_parse.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use rush_expr_engine::Calc; 3 | 4 | fn single_parse() { 5 | let _calc: Calc = "1".parse().unwrap(); 6 | } 7 | 8 | fn simple_parse() { 9 | let _calc: Calc = "(requests_made * requests_succeeded / 100) >= 90" 10 | .parse() 11 | .unwrap(); 12 | } 13 | 14 | fn full_parse() { 15 | let _calc: Calc = r#"(args1 != 'hello world' 16 | || utc('2023-01-02') > utc(args2)) 17 | && (in(args3,[1,2,3,4,'helle','world']) 18 | || 3.14 > args4 >> 2 || !args5 || false )"# 19 | .parse() 20 | .unwrap(); 21 | } 22 | 23 | fn criterion_benchmark(c: &mut Criterion) { 24 | c.bench_function("single_parse", |b| b.iter(|| single_parse())); 25 | c.bench_function("simple_parse", |b| b.iter(|| simple_parse())); 26 | c.bench_function("full_parse", |b| b.iter(|| full_parse())); 27 | } 28 | 29 | criterion_group!(benches, criterion_benchmark); 30 | criterion_main!(benches); 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 woshihaoren4 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/benches/assign_simple_parse.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use rush_expr_engine::Assign; 3 | 4 | fn assign_simple_parse() { 5 | let _a = "msg='success'" 6 | .parse::() 7 | .expect("assign_simple_parse panic:"); 8 | } 9 | 10 | fn assign_full_parse() { 11 | let exec_expression = r#" 12 | data.message = 'success'; 13 | data.code = 0; 14 | data.value1 = [1,2,3]; 15 | data.value2 = args1 + args2; 16 | data.value3 = !args3; 17 | data.value4 = str_len('hello world'); 18 | data.value5 = 1>>2; 19 | "#; 20 | let _a = exec_expression 21 | .parse::() 22 | .expect("new Assign failed"); 23 | } 24 | 25 | fn criterion_benchmark(c: &mut Criterion) { 26 | let mut group = c.benchmark_group("assign_parse_benchmark"); 27 | group.significance_level(0.1).sample_size(100); 28 | 29 | group.bench_function("assign_simple_parse", |b| b.iter(|| assign_simple_parse())); 30 | group.bench_function("rule_full_parse", |b| b.iter(|| assign_full_parse())); 31 | group.finish(); 32 | } 33 | 34 | criterion_group!(benches, criterion_benchmark); 35 | criterion_main!(benches); 36 | -------------------------------------------------------------------------------- /example/benches/wasm_bm.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; 2 | use rush_core::AsyncRuleFlow; 3 | use rush_wasm_engine::{WasmRuntime, WasmRuntimeFactory}; 4 | use serde::Deserialize; 5 | use serde_json::Value; 6 | 7 | const WASM_RULE: &'static str = " 8 | rule WASM_RULE _ wasm 9 | wasm_file: ../target/wasm32-unknown-unknown/release/wasm_example_one.wasm 10 | "; 11 | 12 | #[derive(Deserialize)] 13 | struct Resp { 14 | #[serde(default = "Default::default")] 15 | input: String, 16 | } 17 | 18 | async fn wasm_async_flow(rt: &WasmRuntime) { 19 | let res: Resp = rt 20 | .async_flow(Value::String("hello world".into())) 21 | .await 22 | .unwrap(); 23 | assert_eq!(res.input.as_str(), "hello world"); 24 | } 25 | 26 | fn async_wasm_benchmark(c: &mut Criterion) { 27 | let rt = WasmRuntimeFactory::new().build(WASM_RULE).unwrap(); 28 | 29 | c.bench_with_input( 30 | BenchmarkId::new("wasm_async_flow", "wasm_async_flow"), 31 | &rt, 32 | |b, s| { 33 | b.to_async(tokio::runtime::Runtime::new().unwrap()) 34 | .iter(|| wasm_async_flow(s)); 35 | }, 36 | ); 37 | } 38 | 39 | criterion_group!(benches, async_wasm_benchmark); 40 | criterion_main!(benches); 41 | -------------------------------------------------------------------------------- /example/wasm_example/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, CStr, CString}; 2 | 3 | extern "C" { 4 | fn success(ptr: *const u8, len: usize) -> u32; 5 | fn error(ptr: *const u8, len: usize) -> u32; 6 | } 7 | 8 | #[no_mangle] 9 | pub extern "C" fn handle(ptr: *mut c_char, len: u32) -> u32 { 10 | unsafe { 11 | let slice = std::slice::from_raw_parts(ptr, len as usize); 12 | let cs = CStr::from_ptr(slice.as_ptr()); 13 | let cs = CString::from(cs); 14 | let s = cs.to_str().unwrap(); 15 | let s = format!(r#"{{"input":{}}}"#, s); 16 | 17 | success(s.as_ptr(), s.len()); 18 | } 19 | 1u32 20 | } 21 | 22 | // #[no_mangle] 23 | // pub extern "C" fn handle(ptr: *mut c_char, len: u32) -> u32 { 24 | // unsafe { 25 | // let slice = std::slice::from_raw_parts(ptr, len as usize); 26 | // let cs = CStr::from_ptr(slice.as_ptr()); 27 | // let cs = cs.to_str().unwrap(); 28 | // if cs.trim_matches('"') == "true" { 29 | // let s = format!(r#"{{"result":"success"}}"#); 30 | // success(s.as_ptr(), s.len()); 31 | // }else{ 32 | // let s = format!(r#"input[{}] make a error"#,cs); 33 | // error(s.as_ptr(), s.len()); 34 | // } 35 | // } 36 | // 1u32 37 | // } 38 | -------------------------------------------------------------------------------- /rush_core/src/define.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_json::Value; 3 | use std::sync::Arc; 4 | 5 | // 核心抽象,所有规则算法都可以看作是一个过滤器,给如一个输入,给出一个输出 6 | pub trait RuleFlow { 7 | fn version(&self) -> i32 { 8 | 1 9 | } 10 | fn flow Deserialize<'a>>(&self, obj: Obj) -> anyhow::Result; 11 | } 12 | 13 | #[async_trait::async_trait] 14 | pub trait AsyncRuleFlow: RuleFlow + Sync + Send { 15 | async fn async_flow Deserialize<'a>>( 16 | &self, 17 | obj: Obj, 18 | ) -> anyhow::Result { 19 | self.flow(obj) 20 | } 21 | } 22 | 23 | // 计算节点 24 | pub trait CalcNode: Send + Sync { 25 | fn when(&self, fs: Arc, input: &Value) -> anyhow::Result; 26 | } 27 | // 运算规则 28 | pub trait Exec: Send + Sync { 29 | fn execute( 30 | &self, 31 | fs: Arc, 32 | input: &Value, 33 | output: &mut Value, 34 | ) -> anyhow::Result<()>; 35 | } 36 | // 函数 37 | pub trait Function: Send + Sync { 38 | fn call(&self, fs: Arc, args: Vec) -> anyhow::Result; 39 | } 40 | // 函数集 41 | pub trait FunctionSet: Send + Sync { 42 | fn get(&self, name: &str) -> Option>; 43 | } 44 | 45 | #[async_trait::async_trait] 46 | pub trait RuleEngineDiscovery { 47 | fn version(&self) -> i32 { 48 | 1 49 | } 50 | async fn upgrade(&self) -> F; 51 | } 52 | -------------------------------------------------------------------------------- /example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | authors.workspace = true 6 | repository.workspace = true 7 | keywords.workspace = true 8 | description.workspace = true 9 | license.workspace = true 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | rush_expr_engine = {path = "../rush_expr_engine" } 15 | rush_core = {path = "../rush_core"} 16 | rush_lua_engine = {path = "../rush_lua_engine",features = ["rule-flow"]} 17 | rush_wasm_engine = {path = "../rush_wasm_engine",features = ["rule-flow"]} 18 | serde_json.workspace = true 19 | serde.workspace = true 20 | tokio.workspace = true 21 | anyhow.workspace = true 22 | 23 | [dev-dependencies] 24 | criterion = { version = "0.5", features = ["html_reports","async_tokio"] } 25 | 26 | [[bench]] 27 | name = "expression_parse" 28 | harness = false 29 | 30 | [[bench]] 31 | name = "assign_simple_parse" 32 | harness = false 33 | 34 | [[bench]] 35 | name = "async_rush" 36 | harness = false 37 | 38 | [[bench]] 39 | name = "function_benchmark" 40 | harness = false 41 | 42 | [[bench]] 43 | name = "lua_script_bm" 44 | harness = false 45 | 46 | [[bench]] 47 | name = "wasm_bm" 48 | harness = false 49 | 50 | ## https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options 51 | [[bin]] 52 | name = "example" 53 | path = "src/main.rs" 54 | bench = false 55 | -------------------------------------------------------------------------------- /example/src/wasm_test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test { 3 | use rush_core::AsyncRuleFlow; 4 | use rush_wasm_engine::WasmRuntimeFactory; 5 | use serde_json::Value; 6 | use std::collections::HashMap; 7 | 8 | const WASM_RULE: &'static str = " 9 | rule WASM_RULE _ wasm 10 | wasm_file: wasm_example/wasm_example_one.wasm 11 | "; 12 | 13 | #[tokio::test] 14 | async fn test_wasm_build() { 15 | let rt = WasmRuntimeFactory::new() 16 | .async_build(WASM_RULE) 17 | .await 18 | .unwrap(); 19 | 20 | let result: HashMap = 21 | rt.async_flow(Value::String("hello".into())).await.unwrap(); 22 | assert_eq!(result.get("input").unwrap().as_str(), "hello"); 23 | } 24 | 25 | const WASM_RULE_ERROR: &'static str = " 26 | rule WASM_RULE_ERROR _ wasm 27 | wasm_file: wasm_example/wasm_example_two.wasm 28 | "; 29 | #[tokio::test] 30 | async fn test_wasm_error_build() { 31 | let rt = WasmRuntimeFactory::new() 32 | .async_build(WASM_RULE_ERROR) 33 | .await 34 | .unwrap(); 35 | 36 | let result: HashMap = 37 | rt.async_flow(Value::String("true".into())).await.unwrap(); 38 | assert_eq!(result.get("result").unwrap().as_str(), "success"); 39 | 40 | let result: anyhow::Result = rt.async_flow(Value::String("false".into())).await; 41 | assert_eq!( 42 | result.unwrap_err().to_string().as_str(), 43 | "input[\"false\"] make a error" 44 | ) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/benches/lua_script_bm.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; 2 | use rush_core::AsyncRuleFlow; 3 | use rush_lua_engine::{LuaRuntime, LuaRuntimeFactory}; 4 | use serde::Deserialize; 5 | use serde_json::Value; 6 | use std::collections::HashMap; 7 | 8 | const LUA_RULE_SCRIPT: &'static str = r#" 9 | rule LUA_RULE_SCRIPT _ lua 10 | lua_script: 11 | function handle(req) 12 | local resp = {} 13 | 14 | if req.source == ONLINE_CHANNEL then 15 | resp.message = "线上渠道" 16 | elseif req.source == OFFLINE_CHANNEL then 17 | resp.message = "线下渠道" 18 | else 19 | resp.message = "未知渠道:"..req.source 20 | end 21 | 22 | return resp 23 | end 24 | 25 | return {handle_function="handle"} 26 | "#; 27 | #[derive(Deserialize)] 28 | struct Resp { 29 | #[serde(default = "Default::default")] 30 | message: String, 31 | } 32 | 33 | async fn lua_async_flow(rt: &LuaRuntime) { 34 | let res: Resp = rt 35 | .async_flow(r#"{"source":"online"}"#.parse::().unwrap()) 36 | .await 37 | .unwrap(); 38 | assert_eq!(res.message.as_str(), "线上渠道"); 39 | } 40 | 41 | fn async_lua_benchmark(c: &mut Criterion) { 42 | let mut envs = HashMap::new(); 43 | envs.insert("ONLINE_CHANNEL".into(), "online".into()); 44 | envs.insert("OFFLINE_CHANNEL".into(), "offline".into()); 45 | 46 | let rt = LuaRuntimeFactory::new() 47 | .load(LUA_RULE_SCRIPT, envs) 48 | .unwrap(); 49 | 50 | c.bench_with_input( 51 | BenchmarkId::new("lua_async_flow", "lua_async_flow"), 52 | &rt, 53 | |b, s| { 54 | b.to_async(tokio::runtime::Runtime::new().unwrap()) 55 | .iter(|| lua_async_flow(s)); 56 | }, 57 | ); 58 | } 59 | 60 | criterion_group!(benches, async_lua_benchmark); 61 | criterion_main!(benches); 62 | -------------------------------------------------------------------------------- /rush_wasm_engine/src/export_env.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Error}; 2 | use std::mem::MaybeUninit; 3 | use wasmer::{imports, Function, FunctionEnv, FunctionEnvMut, Imports, Memory, Store, WasmPtr}; 4 | use wd_tools::PFErr; 5 | 6 | pub struct ExportEnv { 7 | memory: MaybeUninit, 8 | result: anyhow::Result, 9 | } 10 | 11 | impl ExportEnv { 12 | pub fn new() -> Self { 13 | Self { 14 | memory: MaybeUninit::uninit(), 15 | result: anyhow!("not init").err(), 16 | } 17 | } 18 | pub fn init(&mut self, mem: Memory) { 19 | self.memory.write(mem); 20 | } 21 | 22 | //成功并且返回 23 | pub fn ret_success(mut env: FunctionEnvMut, ptr: WasmPtr, len: u32) -> u32 { 24 | let (env, store) = env.data_and_store_mut(); 25 | unsafe { 26 | let view = env.memory.assume_init_ref().view(&store); 27 | let result = ptr.read_utf8_string(&view, len); 28 | env.result = match result { 29 | Ok(o) => Ok(o), 30 | Err(e) => Err(Error::from(e)), 31 | }; 32 | } 33 | 0u32 34 | } 35 | //失败返回 36 | pub fn ret_error(mut env: FunctionEnvMut, ptr: WasmPtr, len: u32) -> u32 { 37 | let (env, store) = env.data_and_store_mut(); 38 | unsafe { 39 | let view = env.memory.assume_init_ref().view(&store); 40 | let result = ptr.read_utf8_string(&view, len); 41 | env.result = match result { 42 | Ok(o) => anyhow!("{}", o).err(), 43 | Err(e) => Err(Error::from(e)), 44 | }; 45 | } 46 | 0u32 47 | } 48 | pub fn generate_import(env: &FunctionEnv, store: &mut Store) -> Imports { 49 | imports! { 50 | "env"=>{ 51 | "success" => Function::new_typed_with_env(store,env,ExportEnv::ret_success), 52 | "error" => Function::new_typed_with_env(store,env,ExportEnv::ret_error), 53 | } 54 | } 55 | } 56 | pub fn get_result(&mut self) -> anyhow::Result { 57 | unsafe { std::ptr::replace(&mut self.result, anyhow!("not init").err()) } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /example/src/custom_rule_exec.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test { 3 | use rush_core::{CalcNode, Exec, FunctionSet, RuleFlow, Rush}; 4 | use rush_expr_engine::{Assign, CalcBuilder}; 5 | use serde_json::Value; 6 | use std::collections::HashMap; 7 | use std::sync::Arc; 8 | 9 | struct CustomCalc; 10 | impl CalcNode for CustomCalc { 11 | fn when(&self, _fs: Arc, input: &Value) -> anyhow::Result { 12 | if let Value::String(s) = input { 13 | return Ok(s == "true"); 14 | } 15 | return Ok(false); 16 | } 17 | } 18 | struct CustomExec; 19 | impl Exec for CustomExec { 20 | fn execute( 21 | &self, 22 | _fs: Arc, 23 | _input: &Value, 24 | output: &mut Value, 25 | ) -> anyhow::Result<()> { 26 | if let Value::Object(obj) = output { 27 | obj.insert("result".to_string(), Value::from("success")); 28 | } 29 | Ok(()) 30 | } 31 | } 32 | 33 | #[test] 34 | fn test_custom_calc_exec() { 35 | let rh = Rush::new().register_rule("custom_rule", vec![CustomCalc], CustomExec); 36 | let res: HashMap = rh.flow("true".parse::().unwrap()).unwrap(); 37 | assert_eq!(res.get("result").unwrap().as_str(), "success"); 38 | 39 | let res: HashMap = rh.flow("false".parse::().unwrap()).unwrap(); 40 | assert_eq!(res.get("result"), None); 41 | } 42 | 43 | #[test] 44 | fn test_custom_expr() { 45 | let calc = CalcBuilder::new("status == 2").build().unwrap(); 46 | let assign: Assign = "message = 'success'".parse().unwrap(); 47 | 48 | let rh = Rush::new().register_rule("custom_rule", vec![calc], assign); 49 | let res: HashMap = rh 50 | .flow(r#"{"status":2}"#.parse::().unwrap()) 51 | .unwrap(); 52 | assert_eq!(res.get("message").unwrap().as_str(), "success"); 53 | 54 | let res: HashMap = rh 55 | .flow(r#"{"status":1}"#.parse::().unwrap()) 56 | .unwrap(); 57 | assert_eq!(res.get("result"), None); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /example/src/array_env_test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test { 3 | use rush_core::{RuleFlow, Rush}; 4 | use rush_expr_engine::ExprEngine; 5 | use serde_json::Value; 6 | use std::collections::HashMap; 7 | 8 | const ARRAY_RULE:&'static str = " 9 | rule ARRAY_RULE 10 | when 11 | contain([1,2,3,4],status) && !contain([5],status); 12 | contain([2,3.1,'hello',true,2>>1],1) && !contain([2,3.1,'hello',true,2>>1],3.1) /*浮点数无法判断包含*/; 13 | sub([1,2,3,4],[1]) && !sub([1,2,3,4],[5]); 14 | sub([2,3.1,'hello',true,2>>1],[1,'world']) && !contain([2,3.1,'hello',true,2>>1],[3.1]) /*浮点数无法判断子集*/; 15 | then 16 | message = 'success' 17 | "; 18 | 19 | #[test] 20 | fn test_array_rule() { 21 | let rh = Rush::from(Into::::into([ARRAY_RULE])); 22 | 23 | let res: HashMap = rh 24 | .flow(r#"{"status":2}"#.parse::().unwrap()) 25 | .unwrap(); 26 | assert_eq!(res.get("message").unwrap().as_str(), "success"); 27 | } 28 | const ENV_RULE_DISCOUNT: &'static str = " 29 | rule ENV_RULE_DISCOUNT 30 | when 31 | env('ACTIVITY_DISCOUNT_TYPE') == type 32 | then 33 | type = '打折活动' 34 | "; 35 | const ENV_RULE_COUPON: &'static str = " 36 | rule ENV_RULE_COUPON 37 | when 38 | env('ACTIVITY_COUPON_TYPE') == type 39 | then 40 | type = '卡券活动' 41 | "; 42 | #[test] 43 | fn test_env_rule() { 44 | let envs = HashMap::from([ 45 | ("ACTIVITY_DISCOUNT_TYPE".to_string(), "discount".to_string()), 46 | ("ACTIVITY_COUPON_TYPE".to_string(), "coupon".to_string()), 47 | ]); 48 | 49 | let rh = Rush::from(Into::::into([ 50 | ENV_RULE_DISCOUNT, 51 | ENV_RULE_COUPON, 52 | ])) 53 | .set_env(envs); 54 | 55 | let res: HashMap = rh 56 | .flow(r#"{"type":"discount"}"#.parse::().unwrap()) 57 | .unwrap(); 58 | assert_eq!(res.get("type").unwrap().as_str(), "打折活动"); 59 | let res: HashMap = rh 60 | .flow(r#"{"type":"coupon"}"#.parse::().unwrap()) 61 | .unwrap(); 62 | assert_eq!(res.get("type").unwrap().as_str(), "卡券活动"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /rush_lua_engine/src/loader.rs: -------------------------------------------------------------------------------- 1 | use crate::AsyncCustomScriptLoad; 2 | use anyhow::anyhow; 3 | use wd_tools::{PFErr, PFOk, PFSome}; 4 | 5 | pub const LUA_SCRIPT_TAG: &'static str = "lua_script:"; 6 | pub const LUA_FILE_TAG: &'static str = "lua_file:"; 7 | 8 | pub struct AsyncCustomScriptLoadDefaultImpl; 9 | #[async_trait::async_trait] 10 | impl AsyncCustomScriptLoad for AsyncCustomScriptLoadDefaultImpl { 11 | fn try_load(&self, _rule_name: String, mut script: String) -> Option { 12 | if !script.starts_with(LUA_SCRIPT_TAG) { 13 | return None; 14 | } 15 | script.split_off(LUA_SCRIPT_TAG.len()).some() 16 | } 17 | 18 | async fn load(&self, rule_name: String, script: String) -> anyhow::Result { 19 | return match self.try_load(rule_name, script) { 20 | None => anyhow!( 21 | "AsyncCustomScriptLoadDefaultImpl: script start tag must is '{LUA_SCRIPT_TAG}'" 22 | ) 23 | .err(), 24 | Some(s) => Ok(s), 25 | }; 26 | } 27 | } 28 | 29 | pub struct AsyncCustomScriptLoadFile; 30 | 31 | #[async_trait::async_trait] 32 | impl AsyncCustomScriptLoad for AsyncCustomScriptLoadFile { 33 | fn try_load(&self, _rule_name: String, mut script: String) -> Option { 34 | if !script.starts_with(LUA_FILE_TAG) { 35 | return None; 36 | } 37 | let path = script.split_off(LUA_FILE_TAG.len()); 38 | let path = path.trim_matches(|x| " \t\r\n".contains(x)); 39 | let data = match std::fs::read_to_string(path) { 40 | Ok(s) => s, 41 | Err(e) => { 42 | println!( 43 | "AsyncCustomScriptLoadFile open file failed; {}", 44 | e.to_string() 45 | ); 46 | return None; 47 | } 48 | }; 49 | data.some() 50 | } 51 | 52 | async fn load(&self, _rule_name: String, mut script: String) -> anyhow::Result { 53 | if !script.starts_with(LUA_FILE_TAG) { 54 | return anyhow!("AsyncCustomScriptLoadFile: script start tag must is '{LUA_FILE_TAG}'") 55 | .err(); 56 | } 57 | let path = script.split_off(LUA_FILE_TAG.len()); 58 | let path = path.trim_start_matches(|x| " \t\r\n".contains(x)); 59 | let data = std::fs::read_to_string(path)?; 60 | data.ok() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /rush_expr_engine/src/calc_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::{Calc, Element}; 2 | use std::collections::VecDeque; 3 | 4 | pub trait CalcBuilderEvent { 5 | fn remove_annotation_before(&self, _expr: &mut String) -> anyhow::Result<()> { 6 | Ok(()) 7 | } 8 | //disable_fast_parse_annotation == ture 才会触发这个动作 9 | fn remove_annotation_after(&self, _expr: &mut String) -> anyhow::Result<()> { 10 | Ok(()) 11 | } 12 | fn expression_split_check(&self, _deq: &mut VecDeque) -> anyhow::Result<()> { 13 | Ok(()) 14 | } 15 | fn calc_check(&self, _calc: &mut Calc) -> anyhow::Result<()> { 16 | Ok(()) 17 | } 18 | } 19 | impl CalcBuilderEvent for () {} 20 | 21 | /// CalcBuilder 构建过程 22 | /// 1. 去掉注释 23 | /// 2. 表达式拆分到队列 24 | /// 3. 队列重组为运算树 25 | #[derive(Debug, Default)] 26 | pub struct CalcBuilder { 27 | //true: 关闭快速注解解析模式 28 | // 在快速解析中 例如:"a > b &/*注释*/& d < c" 这样的注解会解析失败 29 | disable_fast_parse_annotation: bool, 30 | 31 | //需要解析的表达式 32 | expr: String, 33 | } 34 | 35 | impl CalcBuilder { 36 | pub fn new>(expr: S) -> Self { 37 | Self { 38 | expr: expr.into(), 39 | ..Default::default() 40 | } 41 | } 42 | 43 | pub fn disable_fast_parse_annotation(mut self) -> Self { 44 | self.disable_fast_parse_annotation = true; 45 | self 46 | } 47 | } 48 | 49 | impl CalcBuilder { 50 | pub fn build(self) -> anyhow::Result { 51 | self.build_event::<()>(None) 52 | } 53 | pub fn build_event(self, event: Option) -> anyhow::Result { 54 | let Self { 55 | disable_fast_parse_annotation, 56 | mut expr, 57 | } = self; 58 | 59 | if let Some(ref e) = event { 60 | //<<-------- 开始前检查 61 | e.remove_annotation_before(&mut expr)?; 62 | } 63 | 64 | if disable_fast_parse_annotation { 65 | expr = Calc::parse_remove_annotation(expr)?; 66 | 67 | if let Some(ref e) = event { 68 | //<<---------- 去注释后检查 69 | e.remove_annotation_after(&mut expr)?; 70 | } 71 | } 72 | 73 | let mut deq = Calc::expression_split(expr)?; 74 | 75 | if let Some(ref e) = event { 76 | //<<---------- 拆解检查 77 | e.expression_split_check(&mut deq)?; 78 | } 79 | 80 | let mut calc = Calc::convert_one_group_calc(None, &mut deq)?; 81 | 82 | if let Some(ref e) = event { 83 | //<<---------- 算子检查 84 | e.calc_check(&mut calc)?; 85 | } 86 | 87 | Ok(calc) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /example/benches/function_benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; 2 | use rush_core::{Function, FunctionSet, RuleFlow, Rush}; 3 | use rush_expr_engine::ExprEngine; 4 | use serde::{Deserialize, Serialize}; 5 | use serde_json::Value; 6 | use std::sync::Arc; 7 | 8 | const FUNCTION_RULE: &'static str = " 9 | rule FUNCTION_RULE 10 | when 11 | str_rev(country)=='加拿大'; 12 | str_splice(str_rev(country),city)=='加拿大 多伦多'; 13 | abs(revenue.low) < 100 && abs(revenue.high) > 1000; 14 | then 15 | message = str_splice(str_rev(country),city,'贫富差距大'); 16 | revenue.avg = abs(revenue.low) + abs(revenue.high) / 2; 17 | "; 18 | pub fn str_rev(s: String) -> anyhow::Result { 19 | let mut result = String::new(); 20 | let si = s.chars(); 21 | for s in si.rev() { 22 | result.push(s); 23 | } 24 | Ok(result) 25 | } 26 | pub struct StrSplice(char); 27 | impl Function for StrSplice { 28 | fn call(&self, _fs: Arc, args: Vec) -> anyhow::Result { 29 | let mut res = String::new(); 30 | for i in args { 31 | if !res.is_empty() { 32 | res.push(self.0); 33 | } 34 | if let Value::String(s) = i { 35 | res += s.as_str(); 36 | } 37 | } 38 | Ok(Value::String(res)) 39 | } 40 | } 41 | 42 | #[derive(Deserialize, Serialize)] 43 | pub struct Resp { 44 | #[serde(default = "Default::default")] 45 | message: String, 46 | revenue: Revenue, 47 | } 48 | #[derive(Deserialize, Serialize)] 49 | pub struct Revenue { 50 | avg: i64, 51 | } 52 | fn have_function_rush(rh: &Rush) { 53 | let resp: Resp = rh 54 | .flow( 55 | r#"{"country":"大拿加","city":"多伦多","revenue":{ "low":-10,"high":1002 } }"# 56 | .parse::() 57 | .unwrap(), 58 | ) 59 | .unwrap(); 60 | 61 | assert_eq!(resp.message.as_str(), "加拿大 多伦多 贫富差距大"); 62 | assert_eq!(resp.revenue.avg, 506); 63 | } 64 | 65 | fn criterion_benchmark(c: &mut Criterion) { 66 | let ee = ExprEngine::from([FUNCTION_RULE]); 67 | let rh = Rush::from(ee); 68 | 69 | let rh = rh 70 | .raw_register_function("str_splice", StrSplice(' ')) 71 | .register_function("str_rev", str_rev) 72 | .register_function("abs", |i: i64| Ok(i.abs())); 73 | 74 | c.bench_with_input( 75 | BenchmarkId::new("have_function_rush", "have_function_rush"), 76 | &rh, 77 | |b, s| { 78 | b.iter(|| have_function_rush(s)); 79 | }, 80 | ); 81 | } 82 | 83 | criterion_group!(benches, criterion_benchmark); 84 | criterion_main!(benches); 85 | -------------------------------------------------------------------------------- /example/benches/async_rush.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; 2 | use rush_core::{MultiRush, RuleFlow, Rush}; 3 | use rush_expr_engine::ExprEngine; 4 | use serde::Deserialize; 5 | use serde_json::Value; 6 | 7 | pub const MANY_RULE_ONE: &str = " 8 | rule MANY_RULE_ONE 9 | when 10 | country == '美国'; 11 | age <= 18; 12 | then 13 | tag = '美国的年轻人' 14 | "; 15 | 16 | pub const MANY_RULE_TWO: &str = " 17 | rule MANY_RULE_TWO 18 | when 19 | country == '美国'; 20 | age > 18 && age < 30; 21 | then 22 | tag = '美国的青年人' 23 | "; 24 | 25 | pub const MANY_RULE_THREE: &str = " 26 | rule MANY_RULE_THREE 27 | when 28 | country == '中国'; 29 | age <= 18; 30 | then 31 | tag = '中国的年轻人' 32 | "; 33 | pub const MANY_RULE_FOUR: &str = " 34 | rule MANY_RULE_FOUR 35 | when 36 | country == '中国'; 37 | age > 18 && age < 30; 38 | then 39 | tag = '中国的青年人' 40 | "; 41 | #[derive(Deserialize)] 42 | struct Tag { 43 | #[serde(default = "Default::default")] 44 | tag: String, 45 | } 46 | async fn multi_flow(rh: &MultiRush) { 47 | let res: Tag = rh 48 | .multi_flow(r#"{"country":"美国","age":17}"#.parse::().unwrap()) 49 | .await 50 | .unwrap(); 51 | assert_eq!( 52 | res.tag.as_str(), 53 | "美国的年轻人", 54 | r#"case : {{"country":"美国","age":17}} failed"# 55 | ); 56 | } 57 | 58 | fn sync_flow(rh: &Rush) { 59 | let res: Tag = rh 60 | .flow(r#"{"country":"美国","age":17}"#.parse::().unwrap()) 61 | .unwrap(); 62 | assert_eq!( 63 | res.tag.as_str(), 64 | "美国的年轻人", 65 | r#"case : {{"country":"美国","age":17}} failed"# 66 | ); 67 | } 68 | 69 | fn criterion_benchmark(c: &mut Criterion) { 70 | let rh = Rush::from(Into::::into([ 71 | MANY_RULE_ONE, 72 | MANY_RULE_TWO, 73 | MANY_RULE_THREE, 74 | MANY_RULE_FOUR, 75 | ])); 76 | let mrh: MultiRush = rh.into(); 77 | 78 | c.bench_with_input( 79 | BenchmarkId::new("multi_flow", "multi_flow"), 80 | &mrh, 81 | |b, s| { 82 | b.to_async(tokio::runtime::Runtime::new().unwrap()) 83 | .iter(|| multi_flow(s)); 84 | }, 85 | ); 86 | 87 | let rh = Rush::from(Into::::into([ 88 | MANY_RULE_ONE, 89 | MANY_RULE_TWO, 90 | MANY_RULE_THREE, 91 | MANY_RULE_FOUR, 92 | ])); 93 | 94 | c.bench_with_input(BenchmarkId::new("sync_flow", "sync_flow"), &rh, |b, s| { 95 | b.iter(|| sync_flow(s)); 96 | }); 97 | } 98 | 99 | criterion_group!(benches, criterion_benchmark); 100 | criterion_main!(benches); 101 | -------------------------------------------------------------------------------- /rush_core/src/task_pool.rs: -------------------------------------------------------------------------------- 1 | use crate::Rush; 2 | use serde::{Deserialize, Serialize}; 3 | use serde_json::Value; 4 | use std::sync::Arc; 5 | use tokio::sync::mpsc; 6 | #[derive(Debug)] 7 | pub struct MulMsg { 8 | pub result: anyhow::Result, 9 | pub rule_name: String, 10 | } 11 | impl MulMsg { 12 | pub fn new(result: anyhow::Result, rule_name: String) -> Self { 13 | Self { result, rule_name } 14 | } 15 | } 16 | 17 | #[derive(Debug)] 18 | pub struct MultiRush { 19 | rush: Arc, 20 | } 21 | impl MultiRush { 22 | pub async fn multi_flow Deserialize<'de>>( 23 | &self, 24 | obj: Obj, 25 | ) -> anyhow::Result { 26 | let obj: Value = serde_json::to_value(obj)?; 27 | let (send, mut recv) = mpsc::channel(self.rush.nodes.len()); 28 | let obj = Arc::new(obj); 29 | for (k, _) in self.rush.nodes.iter() { 30 | let rh = self.rush.clone(); 31 | let rule_name = k.to_string(); 32 | let obj = obj.clone(); 33 | let send = send.clone(); 34 | tokio::spawn(async move { 35 | let cs = if let Some(i) = rh.nodes.get(rule_name.as_str()) { 36 | i 37 | } else { 38 | return; //not to here 39 | }; 40 | for i in cs.iter() { 41 | match i.when(rh.functions.share(), &obj) { 42 | Ok(b) => { 43 | if !b { 44 | if let Err(e) = 45 | send.send(MulMsg::new(Ok(b), rule_name.clone())).await 46 | { 47 | println!("rush.multi_flow false recv is close:{}", e); 48 | } 49 | return; 50 | } 51 | } 52 | Err(e) => { 53 | if let Err(e) = send.send(MulMsg::new(Err(e), rule_name.clone())).await 54 | { 55 | println!("rush.multi_flow error recv is close:{}", e); 56 | } 57 | return; 58 | } 59 | } 60 | } 61 | if let Err(e) = send.send(MulMsg::new(Ok(true), rule_name)).await { 62 | println!("rush.multi_flow over recv is close:{}", e); 63 | } 64 | }); 65 | } 66 | let mut rules = vec![]; 67 | for _ in 0..self.rush.nodes.len() { 68 | if let Some(i) = recv.recv().await { 69 | if i.result? { 70 | rules.push(i.rule_name); 71 | } 72 | } else { 73 | println!("rush.multi_flow should is not null"); 74 | break; 75 | } 76 | } 77 | drop(send); 78 | let val = self.rush.execute(&obj, rules)?; 79 | let val = serde_json::from_value(val)?; 80 | Ok(val) 81 | } 82 | } 83 | 84 | impl From for MultiRush { 85 | fn from(value: Rush) -> Self { 86 | Self { 87 | rush: Arc::new(value), 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /example/src/lua_test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test { 3 | use rush_core::{AsyncRuleFlow, RuleFlow}; 4 | use rush_lua_engine::{LuaRuntime, LuaRuntimeFactory}; 5 | use serde::Deserialize; 6 | use serde_json::Value; 7 | use std::collections::HashMap; 8 | 9 | const LUA_SCRIPT: &'static str = r#" 10 | function handle(req) 11 | local resp = {} 12 | 13 | if req.source == "online" then 14 | resp.message = "线上渠道" 15 | elseif req.source == "offline" then 16 | resp.message = "线下渠道" 17 | else 18 | resp.message = "未知渠道:"..req.source 19 | end 20 | 21 | return resp 22 | end 23 | 24 | return {handle_function="handle"} 25 | "#; 26 | 27 | #[test] 28 | fn test_lua_time() { 29 | let rt = LUA_SCRIPT.parse::().unwrap(); 30 | 31 | let res: HashMap = rt 32 | .flow(r#"{"source":"online"}"#.parse::().unwrap()) 33 | .unwrap(); 34 | assert_eq!(res.get("message").unwrap().as_str(), "线上渠道"); 35 | 36 | let res: HashMap = rt 37 | .flow(r#"{"source":"offline"}"#.parse::().unwrap()) 38 | .unwrap(); 39 | assert_eq!(res.get("message").unwrap().as_str(), "线下渠道"); 40 | 41 | let res: HashMap = rt 42 | .flow(r#"{"source":"unknown"}"#.parse::().unwrap()) 43 | .unwrap(); 44 | assert_eq!(res.get("message").unwrap().as_str(), "未知渠道:unknown"); 45 | } 46 | 47 | const LUA_RULE_SCRIPT: &'static str = r#" 48 | rule LUA_RULE_SCRIPT _ lua 49 | lua_script: 50 | function handle(req) 51 | local resp = {} 52 | 53 | if req.source == ONLINE_CHANNEL then 54 | resp.message = "线上渠道" 55 | elseif req.source == OFFLINE_CHANNEL then 56 | resp.message = "线下渠道" 57 | else 58 | resp.message = "未知渠道:"..req.source 59 | end 60 | 61 | return resp 62 | end 63 | 64 | return {handle_function="handle"} 65 | "#; 66 | 67 | #[tokio::test] 68 | async fn test_lua_rule_build() { 69 | let mut envs = HashMap::new(); 70 | envs.insert("ONLINE_CHANNEL".into(), "online".into()); 71 | envs.insert("OFFLINE_CHANNEL".into(), "offline".into()); 72 | 73 | let rt = LuaRuntimeFactory::new() 74 | // .load(LUA_RULE_SCRIPT, envs)..unwrap(); //同步try_load 75 | .build(LUA_RULE_SCRIPT, envs) 76 | .await 77 | .unwrap(); 78 | 79 | let res: HashMap = rt 80 | .async_flow(r#"{"source":"online"}"#.parse::().unwrap()) 81 | .await 82 | .unwrap(); 83 | assert_eq!(res.get("message").unwrap().as_str(), "线上渠道"); 84 | } 85 | 86 | const LUA_RULE_FILE: &'static str = r#" 87 | rule LUA_RULE_FILE _ lua 88 | lua_file: lua_script/handle.lua 89 | "#; 90 | 91 | #[derive(Deserialize)] 92 | struct Resp { 93 | message: String, 94 | } 95 | 96 | #[test] 97 | fn test_lua_from_file() { 98 | let rt = LuaRuntimeFactory::new() 99 | .load(LUA_RULE_FILE, HashMap::new()) 100 | .unwrap(); 101 | let resp: Resp = rt 102 | .flow(r#"{"hello":"world"}"#.parse::().unwrap()) 103 | .unwrap(); 104 | assert_eq!(resp.message.as_str(), "success") 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /rush_expr_engine/src/assign.rs: -------------------------------------------------------------------------------- 1 | use crate::Calc; 2 | use anyhow::anyhow; 3 | use rush_core::{Exec, FunctionSet}; 4 | use serde_json::{Map, Value}; 5 | use std::collections::HashMap; 6 | use std::str::FromStr; 7 | use std::sync::Arc; 8 | use wd_tools::PFErr; 9 | 10 | #[derive(Debug, Default)] 11 | pub struct Assign { 12 | execs: HashMap, 13 | } 14 | impl Assign { 15 | pub fn new() -> Self { 16 | Assign { 17 | execs: HashMap::new(), 18 | } 19 | } 20 | pub fn add_exec, C: Into>(mut self, key: K, expr: C) -> Self { 21 | self.execs.insert(key.into(), expr.into()); 22 | self 23 | } 24 | #[allow(unused_assignments)] 25 | fn insert_value(k: &str, input: Value, mut out: &mut Value) -> anyhow::Result<()> { 26 | let ks: Vec<_> = k.split(".").collect(); 27 | let last = ks.len() - 1; 28 | 29 | for (i, e) in ks.into_iter().enumerate() { 30 | out = if let Value::Object(map) = out { 31 | if i == last { 32 | map.insert(e.to_string(), input); 33 | return Ok(()); 34 | } 35 | if map.get(e).is_none() { 36 | map.insert(e.to_string(), Value::Object(Map::new())); 37 | } 38 | if let Some(s) = map.get_mut(e) { 39 | s 40 | } else { 41 | panic!("Out of the question to here"); 42 | } 43 | } else { 44 | return anyhow!("want insert at,but the path is not obj").err(); 45 | } 46 | } 47 | return Ok(()); 48 | } 49 | } 50 | impl Exec for Assign { 51 | fn execute( 52 | &self, 53 | fs: Arc, 54 | input: &Value, 55 | output: &mut Value, 56 | ) -> anyhow::Result<()> { 57 | for (k, c) in self.execs.iter() { 58 | let val = c.value(&fs, input)?; 59 | Self::insert_value(k, val, output)?; 60 | } 61 | Ok(()) 62 | } 63 | } 64 | 65 | impl FromStr for Assign { 66 | type Err = anyhow::Error; 67 | 68 | fn from_str(s: &str) -> Result { 69 | let s = s.trim_start_matches(" \r\n\t"); 70 | let ss: Vec<_> = s.split(";").collect(); 71 | let mut assign = Assign::new(); 72 | for i in ss { 73 | let expr = i.trim_matches(|x| " \r\n\t".contains(x)); 74 | if expr.is_empty() { 75 | continue; 76 | } 77 | if let Some((k, e)) = expr.split_once("=") { 78 | assign = assign.add_exec(k.trim_matches(|x| " \r\n\t".contains(x)), e); 79 | } else { 80 | return anyhow!( 81 | "parse[{}] failed, expr must format:[argument = expression]", 82 | i 83 | ) 84 | .err(); 85 | } 86 | } 87 | Ok(assign) 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod test { 93 | use crate::Assign; 94 | 95 | #[test] 96 | fn test_assign_new() { 97 | let exec_expression = r#" 98 | data.message = 'success'; 99 | data.code = 0; 100 | data.value1 = [1,2,3]; 101 | data.value2 = args1 + args2; 102 | data.value3 = !args3; 103 | data.value4 = str_len('hello world'); 104 | data.value5 = 1>>2; 105 | "#; 106 | let a = exec_expression 107 | .parse::() 108 | .expect("new Assign failed"); 109 | println!("--->{:?}", a); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /rush_wasm_engine/src/wasm_runtime_build.rs: -------------------------------------------------------------------------------- 1 | use crate::{WasmLoaderFile, WasmRuntime, WASM_LOADER_FILE}; 2 | use anyhow::anyhow; 3 | use std::collections::HashMap; 4 | use wd_tools::PFErr; 5 | 6 | #[async_trait::async_trait] 7 | pub trait WasmLoader: Send + Sync { 8 | fn load(&self, _rule_name: String, file: String) -> anyhow::Result>; 9 | async fn async_load(&self, rule_name: String, file: String) -> anyhow::Result> { 10 | self.load(rule_name, file) 11 | } 12 | } 13 | 14 | #[derive(Default)] 15 | pub struct WasmRuntimeFactory { 16 | loader: HashMap<&'static str, Box>, 17 | } 18 | 19 | impl WasmRuntimeFactory { 20 | pub fn new() -> Self { 21 | let loader: HashMap<&'static str, Box> = HashMap::new(); 22 | let mut lrf = Self { loader }; 23 | lrf.add_loader(WASM_LOADER_FILE, WasmLoaderFile); 24 | lrf 25 | } 26 | pub fn add_loader(&mut self, tag: &'static str, loader: Load) { 27 | self.loader.insert(tag, Box::new(loader)); 28 | } 29 | pub fn remove_loader>(&mut self, tag: S) { 30 | self.loader.remove(tag.as_ref()); 31 | } 32 | fn check_engine(buf: &str) -> anyhow::Result<(String, String)> { 33 | let buf = buf.trim_start_matches(|c| " \n\r\t".contains(c)); 34 | let (head, body) = if let Some(s) = buf.split_once('\n') { 35 | s 36 | } else { 37 | return anyhow!("first input must is : rule [name] [description] wasm [other]").err(); 38 | }; 39 | let list = head.split(' ').collect::>(); 40 | if list.len() < 4 { 41 | return anyhow!("rule header format: rule [name] [description] wasm [other]").err(); 42 | } 43 | if list[0].to_lowercase() != "rule" { 44 | return anyhow!("rule header must have start 'rule'").err(); 45 | } 46 | if list[3].to_lowercase() != "wasm" { 47 | return anyhow!("WasmRuntime no support rule[{}]", list[3]).err(); 48 | } 49 | let body = body.trim_start_matches(|c| " \n\r\t".contains(c)); 50 | Ok((list[2].to_string(), body.into())) 51 | } 52 | pub fn build>(&self, rule: S) -> anyhow::Result { 53 | let (rule, buf) = Self::check_engine(rule.as_ref())?; 54 | for (k, v) in self.loader.iter() { 55 | if buf.starts_with(*k) { 56 | let bytes = v.load(rule, buf)?; 57 | return WasmRuntime::new(bytes); 58 | } 59 | } 60 | anyhow!("not found eligible loader").err() 61 | } 62 | pub async fn async_build>(&self, rule: S) -> anyhow::Result { 63 | let (rule, buf) = Self::check_engine(rule.as_ref())?; 64 | for (k, v) in self.loader.iter() { 65 | if buf.starts_with(*k) { 66 | let bytes = v.async_load(rule, buf).await?; 67 | return WasmRuntime::new(bytes); 68 | } 69 | } 70 | anyhow!("not found eligible loader").err() 71 | } 72 | } 73 | 74 | #[cfg(test)] 75 | mod test { 76 | use crate::WasmRuntimeFactory; 77 | use serde_json::Value; 78 | use std::collections::HashMap; 79 | 80 | const WASM_RULE: &'static str = " 81 | rule WASM_RULE _ wasm 82 | wasm_file: ../target/wasm32-unknown-unknown/release/wasm_example_one.wasm 83 | "; 84 | 85 | #[test] 86 | fn test_wasm_build() { 87 | let rt = WasmRuntimeFactory::new().build(WASM_RULE).unwrap(); 88 | 89 | let result: HashMap = rt.call(Value::String("hello".into())).unwrap(); 90 | assert_eq!(result.get("input").unwrap().as_str(), "hello"); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /example/src/function_test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test { 3 | use rush_core::{Function, FunctionSet, RuleFlow, Rush}; 4 | use rush_expr_engine::ExprEngine; 5 | use serde::{Deserialize, Serialize}; 6 | use serde_json::Value; 7 | use std::sync::Arc; 8 | 9 | const FUNCTION_RULE: &'static str = " 10 | rule FUNCTION_RULE 11 | when 12 | str_rev(country)=='加拿大'; 13 | str_splice(str_rev(country),city)=='加拿大 多伦多'; 14 | abs(revenue.low) < 100 && abs(revenue.high) > 1000; 15 | then 16 | message = str_splice(str_rev(country),city,'贫富差距大'); 17 | revenue.avg = abs(revenue.low) + abs(revenue.high) / 2; 18 | "; 19 | 20 | //可以是函数 21 | pub fn str_rev(s: String) -> anyhow::Result { 22 | let mut result = String::new(); 23 | let si = s.chars(); 24 | for s in si.rev() { 25 | result.push(s); 26 | } 27 | Ok(result) 28 | } 29 | 30 | //也可以实现了trait的struct 31 | pub struct StrSplice(char); 32 | impl Function for StrSplice { 33 | fn call(&self, _fs: Arc, args: Vec) -> anyhow::Result { 34 | let mut res = String::new(); 35 | for i in args { 36 | if !res.is_empty() { 37 | res.push(self.0); 38 | } 39 | if let Value::String(s) = i { 40 | res += s.as_str(); 41 | } 42 | } 43 | Ok(Value::String(res)) 44 | } 45 | } 46 | 47 | #[derive(Deserialize, Serialize)] 48 | pub struct Resp { 49 | #[serde(default = "Default::default")] 50 | message: String, 51 | #[serde(default = "Default::default")] 52 | revenue: Revenue, 53 | } 54 | #[derive(Deserialize, Serialize, Default)] 55 | pub struct Revenue { 56 | avg: i64, 57 | } 58 | 59 | #[test] 60 | fn test_function() { 61 | let ee = ExprEngine::from([FUNCTION_RULE]); 62 | let rh = Rush::from(ee); 63 | 64 | let rh = rh 65 | .raw_register_function("str_splice", StrSplice(' ')) 66 | .register_function("str_rev", str_rev) 67 | //可以用闭包的方式添加 68 | .register_function("abs", |i: i64| Ok(i.abs())); 69 | 70 | let resp: Resp = rh 71 | .flow( 72 | r#"{"country":"大拿加","city":"多伦多","revenue":{"low":-10,"high":1002}}"# 73 | .parse::() 74 | .unwrap(), 75 | ) 76 | .unwrap(); 77 | assert_eq!(resp.message.as_str(), "加拿大 多伦多 贫富差距大"); 78 | assert_eq!(resp.revenue.avg, 506); 79 | 80 | let resp: Resp = rh 81 | .flow( 82 | r#"{"country":"加拿大","city":"多伦多","revenue":{"low":-10,"high":1002}}"# 83 | .parse::() 84 | .unwrap(), 85 | ) 86 | .unwrap(); 87 | assert_eq!(resp.message.as_str(), ""); 88 | assert_eq!(resp.revenue.avg, 0); 89 | 90 | let resp: Resp = rh 91 | .flow( 92 | r#"{"country":"大拿加","city":"温哥华","revenue":{"low":-10,"high":1002}}"# 93 | .parse::() 94 | .unwrap(), 95 | ) 96 | .unwrap(); 97 | assert_eq!(resp.message.as_str(), ""); 98 | assert_eq!(resp.revenue.avg, 0); 99 | 100 | let resp: Resp = rh 101 | .flow( 102 | r#"{"country":"加拿大","city":"多伦多","revenue":{"low":-100,"high":1002}}"# 103 | .parse::() 104 | .unwrap(), 105 | ) 106 | .unwrap(); 107 | assert_eq!(resp.message.as_str(), ""); 108 | assert_eq!(resp.revenue.avg, 0); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /example/src/expr_test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test { 3 | use rush_core::{RuleFlow, Rush}; 4 | use rush_expr_engine::ExprEngine; 5 | use serde::{Deserialize, Serialize}; 6 | use serde_json::Value; 7 | use std::collections::HashMap; 8 | 9 | const SIMPLE_RULE: &'static str = " 10 | rule COMPLEX_RULE 11 | when 12 | age > 18 13 | then 14 | stage = '成人' 15 | "; 16 | 17 | #[test] 18 | fn test_simple_rule() { 19 | let rh = Rush::from(Into::::into([SIMPLE_RULE])); 20 | 21 | let res: HashMap = 22 | rh.flow(r#"{"age":19}"#.parse::().unwrap()).unwrap(); 23 | assert_eq!(res.get("stage").unwrap().as_str(), "成人"); 24 | 25 | let res: HashMap = 26 | rh.flow(r#"{"age":18}"#.parse::().unwrap()).unwrap(); 27 | assert_eq!(res.get("stage"), None); 28 | } 29 | 30 | const ORDER: &'static str = r#"{"order_id":"78135346576251344","shop_channel":"eleme","status":2,"logistic_type":"堂食","store_id":"78135346875149325","mobile":"187****1234","remark":"不放糖","payment":{"total_amount":1000,"real_pay_amount":800,"discount_amount":200},"products":[{"name":"酱香拿铁","spu_id":"78135346576987856","sku_id":"78135346587513154","quantity":2,"price":250},{"name":"黑凤梨","spu_id":"78135346658764184","sku_id":"78135346784318321","quantity":1,"price":500}],"coupons":[{"name":"会员专属八折券","code":"DE34-DFAS-13KD-XX34","quantity":1,"price":200}]}"#; 31 | 32 | #[derive(Serialize, Deserialize)] 33 | struct Product { 34 | name: String, 35 | quantity: i64, 36 | price: i64, 37 | } 38 | #[derive(Serialize, Deserialize)] 39 | struct Coupon { 40 | name: String, 41 | quantity: i64, 42 | price: i64, 43 | } 44 | 45 | fn product_total_amount(ps: Vec) -> anyhow::Result { 46 | let mut total_amount = 0; 47 | for i in ps.iter() { 48 | total_amount += i.quantity * i.price 49 | } 50 | Ok(total_amount) 51 | } 52 | fn coupon_discount_amount(cs: Vec) -> anyhow::Result { 53 | let mut total_amount = 0; 54 | for i in cs.iter() { 55 | total_amount += i.quantity * i.price 56 | } 57 | Ok(total_amount) 58 | } 59 | 60 | const COMPLEX_RULE: &'static str = " 61 | rule COMPLEX_RULE 62 | when 63 | shop_channel == 'eleme'; 64 | status == 2; /*支付完成*/ 65 | logistic_type == '堂食'; 66 | payment.total_amount == payment.real_pay_amount + payment.discount_amount; 67 | payment.real_pay_amount == payment.total_amount - payment.discount_amount; 68 | - payment.total_amount == 1000 - 2000; 69 | payment.discount_amount * 2 == 400; 70 | payment.discount_amount / 2 == 100; 71 | payment.total_amount == product_total_amount(products); 72 | payment.discount_amount == coupon_discount_amount(coupons); 73 | 2>>1 == 1; 74 | 1<<2 == 4; 75 | 1 | 2 == 3; 76 | 3 & 2 == 2; 77 | contain([1,2,3,4],status) && !contain([5],status); 78 | sub([1,2,3,4],[1]) && !sub([1,2,3,4],[5]); 79 | 1 ^ 2 == 3; 80 | ~ 1 == -2; 81 | true; 82 | true || false; 83 | then 84 | message = 'success' 85 | "; 86 | #[test] 87 | fn test_complex_rule() { 88 | let rh = Rush::from(Into::::into([COMPLEX_RULE])) 89 | .register_function("product_total_amount", product_total_amount) 90 | .register_function("coupon_discount_amount", coupon_discount_amount); 91 | 92 | let res: HashMap = rh.flow(ORDER.parse::().unwrap()).unwrap(); 93 | assert_eq!(res.get("message").unwrap().as_str(), "success"); 94 | } 95 | 96 | #[test] 97 | fn test_rush() { 98 | let rule_01: &str = r" 99 | rule r1 100 | when 101 | value != ''; 102 | then 103 | value = '1'; 104 | "; 105 | 106 | let rule_02: &str = r" 107 | rule r2 108 | when 109 | value != ''; 110 | then 111 | value = '2'; 112 | "; 113 | 114 | let rule_03: &str = r" 115 | rule r3 116 | when 117 | value != ''; 118 | then 119 | value = '3'; 120 | "; 121 | 122 | let rh = Rush::from(Into::::into(vec![rule_01, rule_02, rule_03])); 123 | let res: HashMap = rh 124 | .flow(r#"{"value":"abc"}"#.parse::().unwrap()) 125 | .unwrap(); 126 | assert_eq!(res.get("value").unwrap(),"3") 127 | 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /example/src/many_async_test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test { 3 | use rush_core::{MultiRush, RuleFlow, Rush}; 4 | use rush_expr_engine::ExprEngine; 5 | use serde::Deserialize; 6 | use serde_json::{Map, Value}; 7 | 8 | pub const NULL_EXPR_RULE: &str = " 9 | rule NULL_EXPR_RULE 10 | when 11 | then 12 | "; 13 | #[test] 14 | fn test_null_success() { 15 | let ee = ExprEngine::from([NULL_EXPR_RULE]); 16 | let rh = Rush::from(ee); 17 | let val: Value = rh.flow(()).unwrap(); 18 | assert_eq!( 19 | val, 20 | Value::Object(Map::new()), 21 | "if when is null,then all always success" 22 | ); 23 | } 24 | 25 | pub const NULL_WHEN_ONE_EXEC: &str = " 26 | rule NULL_WHEN_ONE_EXEC 27 | when 28 | then 29 | message = 'success' 30 | "; 31 | #[test] 32 | #[should_panic] 33 | fn test_null_failed() { 34 | let ee = ExprEngine::from([NULL_WHEN_ONE_EXEC]); 35 | let rh = Rush::from(ee); 36 | let val: Value = rh.flow(()).unwrap(); 37 | assert_eq!( 38 | val, 39 | Value::Object(Map::new()), 40 | "if when is null,then all always success" 41 | ); 42 | } 43 | 44 | pub const MANY_RULE_ONE: &str = " 45 | rule MANY_RULE_ONE 46 | when 47 | country == '美国'; 48 | age <= 18; 49 | then 50 | tag = '美国的年轻人' 51 | "; 52 | 53 | pub const MANY_RULE_TWO: &str = " 54 | rule MANY_RULE_TWO 55 | when 56 | country == '美国'; 57 | age > 18 && age < 30; 58 | then 59 | tag = '美国的青年人' 60 | "; 61 | 62 | pub const MANY_RULE_THREE: &str = " 63 | rule MANY_RULE_THREE 64 | when 65 | country == '中国'; 66 | age <= 18; 67 | then 68 | tag = '中国的年轻人' 69 | "; 70 | pub const MANY_RULE_FOUR: &str = " 71 | rule MANY_RULE_FOUR 72 | when 73 | country == '中国'; 74 | age > 18 && age < 30; 75 | then 76 | tag = '中国的青年人' 77 | "; 78 | 79 | #[derive(Deserialize)] 80 | struct Tag { 81 | #[serde(default = "Default::default")] 82 | tag: String, 83 | } 84 | #[test] 85 | fn test_many_test() { 86 | let rh = Rush::from(Into::::into([ 87 | MANY_RULE_ONE, 88 | MANY_RULE_TWO, 89 | MANY_RULE_THREE, 90 | MANY_RULE_FOUR, 91 | ])); 92 | let res: Tag = rh 93 | .flow(r#"{"country":"美国","age":17}"#.parse::().unwrap()) 94 | .unwrap(); 95 | assert_eq!( 96 | res.tag.as_str(), 97 | "美国的年轻人", 98 | r#"case : {{"country":"美国","age":17}} failed"# 99 | ); 100 | let res: Tag = rh 101 | .flow(r#"{"country":"美国","age":19}"#.parse::().unwrap()) 102 | .unwrap(); 103 | assert_eq!( 104 | res.tag.as_str(), 105 | "美国的青年人", 106 | r#"case : {{"country":"美国","age":19}} failed"# 107 | ); 108 | let res: Tag = rh 109 | .flow(r#"{"country":"中国","age":17}"#.parse::().unwrap()) 110 | .unwrap(); 111 | assert_eq!( 112 | res.tag.as_str(), 113 | "中国的年轻人", 114 | r#"case : {{"country":"中国","age":17}} failed"# 115 | ); 116 | let res: Tag = rh 117 | .flow(r#"{"country":"中国","age":19}"#.parse::().unwrap()) 118 | .unwrap(); 119 | assert_eq!( 120 | res.tag.as_str(), 121 | "中国的青年人", 122 | r#"case : {{"country":"中国","age":19}} failed"# 123 | ); 124 | 125 | let res: Tag = rh.flow(r#"{"age":17}"#.parse::().unwrap()).unwrap(); 126 | assert_eq!(res.tag.as_str(), "", r#"case: country is null failed"#); 127 | let res: Tag = rh 128 | .flow(r#"{"country":"美国"}"#.parse::().unwrap()) 129 | .unwrap(); 130 | assert_eq!(res.tag.as_str(), "", r#"case: age is null failed"#); 131 | } 132 | 133 | #[tokio::test(flavor = "multi_thread", worker_threads = 4)] 134 | async fn test_multi_many_test() { 135 | let rh = Rush::from(Into::::into([ 136 | MANY_RULE_ONE, 137 | MANY_RULE_TWO, 138 | MANY_RULE_THREE, 139 | MANY_RULE_FOUR, 140 | ])); 141 | let res: Tag = Into::::into(rh) 142 | .multi_flow(r#"{"country":"美国","age":17}"#.parse::().unwrap()) 143 | .await 144 | .unwrap(); 145 | 146 | assert_eq!( 147 | res.tag.as_str(), 148 | "美国的年轻人", 149 | r#"case : {{"country":"美国","age":17}} failed"# 150 | ); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /rush_expr_engine/src/rule_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::{Assign, Calc}; 2 | use anyhow::anyhow; 3 | use wd_tools::PFErr; 4 | 5 | const RULE_FORMAT: &str = "\n\ 6 | The keyword cannot be repeated: when,then 7 | rule [name] [description] [engine/default:expr] [...] 8 | when 9 | [condition 1]; 10 | [condition 2]; 11 | ... 12 | [condition n]; 13 | then 14 | [key1 = execute 1]; 15 | [key2 = execute 2]; 16 | ... 17 | [keyn = execute n]; 18 | "; 19 | const EXPR_ENGINE: &str = "expr"; 20 | const RULE_TAG: &str = "rule"; 21 | 22 | #[derive(Debug, Default)] 23 | pub struct ExprEngine { 24 | rules: Vec<(String, Vec, Assign)>, 25 | } 26 | 27 | impl ExprEngine { 28 | pub fn insert_rule>(&mut self, name: S, calc: Vec, assign: Assign) { 29 | self.rules.push((name.into(), calc, assign)); 30 | } 31 | pub fn register_rule_by_expr, E: AsRef>( 32 | &mut self, 33 | name: S, 34 | calc: E, 35 | exec: E, 36 | ) -> anyhow::Result<()> { 37 | //先解析calc 38 | let ss: Vec<_> = calc.as_ref().split(";").collect(); 39 | let mut calc = vec![]; 40 | for s in ss { 41 | let s = s.trim_matches(|c| " \n\t\r".contains(c)); 42 | if s.is_empty() { 43 | continue; 44 | } 45 | calc.push(s.parse()?); 46 | } 47 | let assign = exec.as_ref().parse()?; 48 | self.insert_rule(name.into(), calc, assign); 49 | Ok(()) 50 | } 51 | pub fn register_rule>(&mut self, rule: R) -> anyhow::Result<()> { 52 | let rule = rule.as_ref(); 53 | // 先解头 54 | let ce: Vec<_> = rule.split("when").collect(); 55 | if ce.len() != 2 { 56 | return anyhow!( 57 | "rule[{}] format error,format that can be parsed:{}", 58 | rule, 59 | RULE_FORMAT 60 | ) 61 | .err(); 62 | } 63 | let hs: Vec<_> = ce[0] 64 | .trim_matches(|c| " \n\r\t".contains(c)) 65 | .split(" ") 66 | .collect(); 67 | if hs.len() <= 1 { 68 | return anyhow!("not found rule name").err(); 69 | } 70 | if hs[0].trim_matches(|c| " \n\r\t".contains(c)).to_lowercase() != RULE_TAG { 71 | return anyhow!("rule must start with 'rule'").err(); 72 | } 73 | if hs.len() >= 4 { 74 | if hs[3].to_lowercase() != EXPR_ENGINE { 75 | return anyhow!("ExprEngine only support expression parse").err(); 76 | } 77 | } 78 | let name = hs[1].to_string(); 79 | //再解析条件 80 | let ce: Vec<_> = ce[1].split("then").collect(); 81 | if ce.len() != 2 { 82 | return anyhow!( 83 | "rule[{}] format error,format that can be parsed:{}", 84 | rule, 85 | RULE_FORMAT 86 | ) 87 | .err(); 88 | } 89 | // let cs:Vec<_> = ce[0].split(";").collect(); 90 | self.register_rule_by_expr(name, ce[0], ce[1])?; 91 | Ok(()) 92 | } 93 | } 94 | 95 | impl IntoIterator for ExprEngine { 96 | type Item = (String, Vec, Assign); 97 | type IntoIter = std::vec::IntoIter; 98 | 99 | fn into_iter(self) -> Self::IntoIter { 100 | self.rules 101 | .into_iter() 102 | .map(|(n, c, a)| (n, c, a)) 103 | .collect::>() 104 | .into_iter() 105 | } 106 | } 107 | 108 | impl<'a, S: IntoIterator> From for ExprEngine { 109 | fn from(value: S) -> Self { 110 | let mut ee = ExprEngine::default(); 111 | for i in value { 112 | ee.register_rule(i).expect("from item parse rule failed"); 113 | } 114 | ee 115 | } 116 | } 117 | 118 | #[cfg(test)] 119 | mod test { 120 | use crate::ExprEngine; 121 | 122 | #[test] 123 | fn test_expr_engine_from() { 124 | let rule1 = "rule rule1 第一条规则\ 125 | when 126 | a > b || d < c; 127 | tag == '便签1'; 128 | then 129 | data.code = 0 130 | "; 131 | 132 | let rule2 = "rule rule2 第二条规则\ 133 | when 134 | tag == '便签2' 135 | then 136 | data.message = 'success'; 137 | data.total = len(request.list); 138 | "; 139 | 140 | let ee = ExprEngine::from([rule1, rule2]); 141 | for ((name, cs, a)) in ee.rules { 142 | println!("---> rule {}", name); 143 | println!("when"); 144 | for i in cs { 145 | println!(" {}", i.to_string()); 146 | } 147 | println!("then"); 148 | println!(" {:?}", a); 149 | println!("<--- over"); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /rush_lua_engine/src/lua_runtime_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | AsyncCustomScriptLoadDefaultImpl, AsyncCustomScriptLoadFile, LuaRuntime, LUA_FILE_TAG, 3 | LUA_SCRIPT_TAG, 4 | }; 5 | use anyhow::anyhow; 6 | use std::collections::HashMap; 7 | use wd_tools::PFErr; 8 | 9 | #[async_trait::async_trait] 10 | pub trait AsyncCustomScriptLoad: Send + Sync { 11 | //在同步中尝试解析 12 | fn try_load(&self, _rule_name: String, _script: String) -> Option { 13 | None 14 | } 15 | async fn load(&self, rule_name: String, script: String) -> anyhow::Result; 16 | } 17 | 18 | #[derive(Default)] 19 | pub struct LuaRuntimeFactory { 20 | loader: HashMap<&'static str, Box>, 21 | } 22 | 23 | impl LuaRuntimeFactory { 24 | pub fn new() -> Self { 25 | let loader: HashMap<&'static str, Box> = HashMap::new(); 26 | let mut lrf = Self { loader }; 27 | lrf.add_loader(LUA_SCRIPT_TAG, AsyncCustomScriptLoadDefaultImpl); 28 | lrf.add_loader(LUA_FILE_TAG, AsyncCustomScriptLoadFile); 29 | lrf 30 | } 31 | pub fn add_loader( 32 | &mut self, 33 | tag: &'static str, 34 | loader: Load, 35 | ) { 36 | self.loader.insert(tag, Box::new(loader)); 37 | } 38 | pub fn remove_loader>(&mut self, tag: S) { 39 | self.loader.remove(tag.as_ref()); 40 | } 41 | fn check_engine(buf: &str) -> anyhow::Result<(String, String)> { 42 | let buf = buf.trim_start_matches(|c| " \n\r\t".contains(c)); 43 | let (head, body) = if let Some(s) = buf.split_once('\n') { 44 | s 45 | } else { 46 | return anyhow!("first input must is : rule [name] [description] lua [other]").err(); 47 | }; 48 | let list = head.split(' ').collect::>(); 49 | if list.len() < 4 { 50 | return anyhow!("rule header format: rule [name] [description] lua [other]").err(); 51 | } 52 | if list[0].to_lowercase() != "rule" { 53 | return anyhow!("rule header must have start 'rule'").err(); 54 | } 55 | if list[3].to_lowercase() != "lua" { 56 | return anyhow!("LuaRuntime no support rule[{}]", list[3]).err(); 57 | } 58 | let body = body.trim_start_matches(|c| " \n\r\t".contains(c)); 59 | Ok((list[2].to_string(), body.into())) 60 | } 61 | pub fn load>( 62 | &self, 63 | script: S, 64 | envs: HashMap, 65 | ) -> anyhow::Result { 66 | let (rule, buf) = Self::check_engine(script.as_ref())?; 67 | for (k, v) in self.loader.iter() { 68 | if buf.starts_with(*k) { 69 | let script = if let Some(s) = v.try_load(rule, buf) { 70 | s 71 | } else { 72 | return anyhow!("can load script, please use async build").err(); 73 | }; 74 | return LuaRuntime::new(script, envs); 75 | } 76 | } 77 | anyhow!("not found eligible loader").err() 78 | } 79 | pub async fn build>( 80 | &self, 81 | script: S, 82 | envs: HashMap, 83 | ) -> anyhow::Result { 84 | let (rule, buf) = Self::check_engine(script.as_ref())?; 85 | for (k, v) in self.loader.iter() { 86 | if buf.starts_with(*k) { 87 | let script = v.load(rule, buf).await?; 88 | return LuaRuntime::new(script, envs); 89 | } 90 | } 91 | anyhow!("not found eligible loader").err() 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod test { 97 | use crate::LuaRuntimeFactory; 98 | use serde_json::Value; 99 | use std::collections::HashMap; 100 | 101 | const LUA_RULE: &'static str = r#" 102 | rule LUA_RULE _ lua 103 | lua_script: 104 | function handle(req) 105 | for k, v in pairs(req) do 106 | print(prefix,"--->",k,v) 107 | end 108 | local resp = {message="success"} 109 | return resp 110 | end 111 | 112 | return {handle_function="handle"} 113 | "#; 114 | 115 | #[tokio::test(flavor = "multi_thread", worker_threads = 2)] 116 | async fn test_factory() { 117 | let rt = LuaRuntimeFactory::new() 118 | .build( 119 | LUA_RULE, 120 | HashMap::from([("prefix".to_string(), "req".to_string())]), 121 | ) 122 | .await 123 | .unwrap(); 124 | let res: HashMap = rt 125 | .async_call::(r#"{"like":"eat","age":18}"#.parse().unwrap()) 126 | .await 127 | .unwrap(); 128 | assert_eq!(res.get("message").unwrap().as_str(), "success") 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /rush_core/src/std_tool.rs: -------------------------------------------------------------------------------- 1 | use crate::{Function, FunctionSet, Rush}; 2 | use anyhow::anyhow; 3 | use serde_json::Value; 4 | use std::collections::HashMap; 5 | use std::sync::Arc; 6 | use wd_tools::sync::Acl; 7 | use wd_tools::{PFErr, PFOk}; 8 | 9 | #[derive(Default, Debug)] 10 | pub struct Env { 11 | envs: Acl>, 12 | } 13 | impl Env { 14 | #[allow(dead_code)] 15 | pub fn add_env_variable>(&mut self, key: S, value: S) { 16 | self.envs.update(|arc| { 17 | let mut map = HashMap::new(); 18 | for (k, v) in arc.iter() { 19 | map.insert(k.to_string(), v.to_string()); 20 | } 21 | map.insert(key.into(), value.into()); 22 | map 23 | }); 24 | } 25 | #[allow(dead_code)] 26 | pub fn add_env_variables>>(&mut self, mp: Map) { 27 | self.envs.update(|arc| { 28 | let mut map = HashMap::new(); 29 | for (k, v) in arc.iter() { 30 | map.insert(k.to_string(), v.to_string()); 31 | } 32 | for (k, v) in mp.into() { 33 | map.insert(k, v); 34 | } 35 | map 36 | }); 37 | } 38 | } 39 | impl From> for Env { 40 | fn from(envs: HashMap) -> Self { 41 | let envs = Acl::new(envs); 42 | Env { envs } 43 | } 44 | } 45 | impl From>> for Env { 46 | fn from(envs: Acl>) -> Self { 47 | Env { envs } 48 | } 49 | } 50 | impl Rush { 51 | pub fn set_env>(self, env: E) -> Self { 52 | self.raw_register_function("env", env.into()) 53 | } 54 | } 55 | 56 | impl Function for Env { 57 | fn call(&self, _fs: Arc, args: Vec) -> anyhow::Result { 58 | if args.len() < 1 { 59 | return anyhow!("must have a args").err(); 60 | } 61 | if let Value::String(key) = args.get(0).unwrap() { 62 | if let Some(s) = self.envs.share().get(key) { 63 | return Value::String(s.to_string()).ok(); 64 | } 65 | } 66 | return Value::String(String::new()).ok(); 67 | } 68 | } 69 | 70 | pub struct ArrayContain; 71 | 72 | impl ArrayContain { 73 | fn contain(array: &Vec, des: &Value) -> bool { 74 | match des { 75 | Value::Null => { 76 | for i in array { 77 | if i.is_null() { 78 | return true; 79 | } 80 | } 81 | } 82 | Value::Bool(d) => { 83 | for i in array { 84 | if let Some(s) = i.as_bool() { 85 | if &s == d { 86 | return true; 87 | } 88 | } 89 | } 90 | } 91 | Value::Number(n) => { 92 | if let Some(n) = n.as_i64() { 93 | for i in array { 94 | if let Some(i) = i.as_i64() { 95 | if n == i { 96 | return true; 97 | } 98 | } 99 | } 100 | } 101 | } 102 | Value::String(s) => { 103 | for i in array { 104 | if let Some(i) = i.as_str() { 105 | if s == i { 106 | return true; 107 | } 108 | } 109 | } 110 | } 111 | Value::Array(ref list) => { 112 | for i in list { 113 | if !Self::contain(array, i) { 114 | return false; 115 | } 116 | } 117 | return true; 118 | } 119 | Value::Object(_) => {} 120 | }; 121 | return false; 122 | } 123 | } 124 | 125 | impl Function for ArrayContain { 126 | fn call(&self, _fs: Arc, mut args: Vec) -> anyhow::Result { 127 | if args.len() < 2 { 128 | return anyhow!("ArrayContain: must have two args").err(); 129 | } 130 | let array = if let Value::Array(array) = args.remove(0) { 131 | array 132 | } else { 133 | return anyhow!("ArrayContain: first input must is array").err(); 134 | }; 135 | let des = args.remove(0); 136 | Ok(Value::Bool(ArrayContain::contain(&array, &des))) 137 | } 138 | } 139 | 140 | pub struct ArraySub; 141 | impl Function for ArraySub { 142 | fn call(&self, _fs: Arc, mut args: Vec) -> anyhow::Result { 143 | if args.len() < 2 { 144 | return anyhow!("ArrayContain: must have two args").err(); 145 | } 146 | let la = if let Value::Array(array) = args.remove(0) { 147 | array 148 | } else { 149 | return anyhow!("ArrayContain: first input must is array").err(); 150 | }; 151 | let ra = if let Value::Array(array) = args.remove(0) { 152 | array 153 | } else { 154 | return anyhow!("ArrayContain: second input must is array").err(); 155 | }; 156 | for i in la { 157 | for j in &ra { 158 | if &i == j { 159 | return Ok(Value::Bool(true)); 160 | } 161 | } 162 | } 163 | return Ok(Value::Bool(false)); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rush 2 | [中文文档](https://juejin.cn/column/7281080737491025955) 3 | 4 | Rush is a universal rules engine. 5 | 6 | Rush provides a general computational abstraction. There can be multiple implementations and any combination. 7 | 8 | Conditional parallel computing 9 | 10 | ## Quick start 11 | 12 | ```rust 13 | const SIMPLE_RULE: &'static str = " 14 | rule COMPLEX_RULE 15 | when 16 | age > 18 17 | then 18 | stage = 'adult' 19 | "; 20 | fn main(){ 21 | 22 | let rh = Rush::from(Into::::into([SIMPLE_RULE])); 23 | 24 | let res:HashMap = rh.flow(r#"{"age":19}"#.parse::().unwrap()).unwrap(); 25 | 26 | assert_eq!(res.get("stage").unwrap().as_str(),"adult"); 27 | 28 | } 29 | ``` 30 | 31 | Here, regular expressions are generated directly. You can actually implement some of them yourself to integrate with your own services. 32 | 33 | [More examples](https://github.com/woshihaoren4/rush/tree/main/example/src) 34 | 35 | ## Keywords 36 | 37 | The direct parsing rules are as follows: 38 | ```rust 39 | The keyword cannot be repeated: when,then 40 | rule [name] [description] [engine/default:expr] [...] 41 | when 42 | [condition 1]; 43 | [condition 2]; 44 | ... 45 | [condition n]; 46 | then 47 | [key1 = execute 1]; 48 | [key2 = execute 2]; 49 | ... 50 | [keyn = execute n]; 51 | ``` 52 | 53 | ## Operators 54 | - Modifiers: + - / * & | ^ % >> << 55 | - Comparators: > >= < <= == != 56 | - Logical ops: || && 57 | - Numeric constants, as i64, if have '.' as f64 58 | - String constants (single quotes: 'foobar') 59 | - Boolean constants: true false 60 | - Parenthesis to control order of evaluation ( ) 61 | - Arrays [anything separated by , within parenthesis: [1, 2, 'foo']] 62 | - contain function example: `contain([1,2.3,'hello'],1)` 63 | - sub function: Find whether there are subsets of the two arrays 64 | - Prefixes: ! - ~ 65 | - Null coalescence: null 66 | - Function: function_name(args)result 67 | - Input field by digits, letters and underscores,if field not found then condition is failed 68 | 69 | ## Function 70 | 71 | You can add functions just like normal rust functions 72 | 73 | ```rust 74 | let rh = rh 75 | .register_function("abs", |i: i64| Ok(i.abs())); 76 | ``` 77 | 78 | [More function impl](https://github.com/woshihaoren4/rush/blob/main/example/src/function_test.rs) 79 | 80 | ## Lua 81 | 82 | [More lua example](https://github.com/woshihaoren4/rush/blob/lua_engine/example/src/lua_test.rs) 83 | 84 | ```rust 85 | const LUA_RULE_SCRIPT: &'static str = r#" 86 | rule LUA_RULE_SCRIPT _ lua 87 | lua_script: 88 | function handle(req) 89 | local resp = {} 90 | 91 | if req.source == ONLINE_CHANNEL then 92 | resp.message = "线上渠道" 93 | elseif req.source == OFFLINE_CHANNEL then 94 | resp.message = "线下渠道" 95 | else 96 | resp.message = "未知渠道:"..req.source 97 | end 98 | 99 | return resp 100 | end 101 | 102 | return {handle_function="handle"} 103 | "#; 104 | ``` 105 | 106 | ## WASM 107 | 108 | [More wasm example](https://github.com/woshihaoren4/rush/blob/main/example/src/wasm_test.rs) 109 | 110 | Compile a wasm file with the following code, use command `cargo build --target wasm32-unknown-unknown --release` 111 | ```rust 112 | extern "C" { 113 | fn success(ptr: *const u8, len: usize) -> u32; 114 | } 115 | 116 | #[no_mangle] 117 | pub extern "C" fn handle(ptr: *mut c_char, len: u32) -> u32 { 118 | unsafe { 119 | let slice = std::slice::from_raw_parts(ptr, len as usize); 120 | let cs = CStr::from_ptr(slice.as_ptr()); 121 | let cs = CString::from(cs); 122 | let s = cs.to_str().unwrap(); 123 | let s = format!(r#"{{"input":{}}}"#, s); 124 | 125 | success(s.as_ptr(), s.len()); 126 | } 127 | 1u32 128 | } 129 | ``` 130 | 131 | Refer to this file in the rule and call: 132 | ```rust 133 | const WASM_RULE: &'static str = " 134 | rule WASM_RULE _ wasm 135 | wasm_file: wasm_example/wasm_example_one.wasm 136 | "; 137 | 138 | #[tokio::test] 139 | async fn test_wasm_build() { 140 | let rt = WasmRuntimeFactory::new().async_build(WASM_RULE).await.unwrap(); 141 | 142 | let result: HashMap = 143 | rt.async_flow(Value::String("hello".into())).await.unwrap(); 144 | 145 | assert_eq!(result.get("input").unwrap().as_str(), "hello"); 146 | } 147 | ``` 148 | 149 | 150 | ## Abstraction and Structure 151 | 152 | ![img.png](img.png) 153 | 154 | ## Benchmark 155 | 156 | If you're concerned about the overhead of this library, a good range of benchmarks are built into this repo. You can run them with `cargo bench -- --verbose` The library is built with an eye towards being quick, but has not been aggressively profiled and optimized. For most applications, though, it is completely fine. 157 | 158 | [benchmark detail](https://github.com/woshihaoren4/rush/tree/main/example/benches) 159 | 160 | Here are my test results,at MacBook Pro,CPU 2.6 GHz Intel Core i7, [lowest, average, highest] 161 | 162 | ```bash 163 | assign_simple_parse time: [620.70 ns 625.08 ns 630.18 ns] 164 | rule_full_parse time: [7.5513 µs 7.5794 µs 7.6094 µs] 165 | multi_flow time: [15.363 µs 15.721 µs 16.184 µs] 166 | sync_flow time: [2.9953 µs 3.0295 µs 3.0700 µs] 167 | single_parse time: [165.08 ns 174.83 ns 186.49 ns] 168 | simple_parse time: [2.6358 µs 2.6470 µs 2.6591 µs] 169 | full_parse time: [19.868 µs 20.089 µs 20.356 µs] 170 | have_function_rush time: [6.9074 µs 6.9507 µs 7.0011 µs] 171 | lua_async_flow time: [10.731 µs 10.847 µs 10.970 µs] 172 | wasm_async_flow time: [8.7260 µs 8.8046 µs 8.8806 µs] 173 | ``` 174 | 175 | ## Plan 176 | 177 | The next step is to provide a run pool. Ability to execute more rules in parallel. 178 | 179 | ## License 180 | This project is licensed under the MIT general use license. You're free to integrate, fork, and play with this code as you feel fit without consulting the author, as long as you provide proper credit to the author in your works. -------------------------------------------------------------------------------- /rush_core/src/function.rs: -------------------------------------------------------------------------------- 1 | use crate::{Function, FunctionSet}; 2 | use anyhow::anyhow; 3 | use serde::{Deserialize, Serialize}; 4 | use serde_json::Value; 5 | use std::sync::Arc; 6 | use wd_tools::{PFErr, PFOk}; 7 | 8 | pub trait FromValue: Sized { 9 | fn from(val: Value) -> anyhow::Result; 10 | } 11 | // impl FromValue for String{ 12 | // fn from(value: Value)->anyhow::Result{ 13 | // if let Value::String(s) = value{ 14 | // return s.ok() 15 | // } 16 | // return anyhow!("unknown type[{}] FromValue for String",value).err() 17 | // } 18 | // } 19 | // macro_rules! from_value_i64 { 20 | // ($($t:ty),*) => { 21 | // $( 22 | // impl FromValue for $t{ 23 | // fn from(val: Value) -> anyhow::Result { 24 | // if let Value::Number(ref n) = val{ 25 | // if let Some(i) = n.as_i64(){ 26 | // return (i as $t).ok() 27 | // } 28 | // } 29 | // return anyhow!("unknown type[{}] FromValue for number",val).err() 30 | // } 31 | // } 32 | // )* 33 | // }; 34 | // } 35 | // from_value_i64!(i8,i16,i32,i64,isize,u8,u16,u32,u64,usize); 36 | // 37 | // macro_rules! from_value_f64 { 38 | // ($($t:ty),*) => { 39 | // $( 40 | // impl FromValue for $t{ 41 | // fn from(val: Value) -> anyhow::Result { 42 | // if let Value::Number(ref n) = val{ 43 | // if let Some(f) = n.as_f64(){ 44 | // return (f as $t).ok() 45 | // } 46 | // } 47 | // return anyhow!("unknown type[{}] FromValue for float",val).err() 48 | // } 49 | // } 50 | // )* 51 | // }; 52 | // } 53 | // from_value_f64!(f32,f64); 54 | // 55 | // impl FromValue for bool{ 56 | // fn from(val: Value) -> anyhow::Result { 57 | // if let Value::Bool(b) = val{ 58 | // return b.ok() 59 | // } 60 | // return anyhow!("unknown type[{}] FromValue for bool",val).err() 61 | // } 62 | // } 63 | // impl FromValue for Option<()>{ 64 | // fn from(val: Value) -> anyhow::Result { 65 | // if let Value::Null = val{ 66 | // return None.ok() 67 | // } 68 | // return anyhow!("unknown type[{}] FromValue for null",val).err() 69 | // } 70 | // } 71 | // 72 | // impl FromValue for Vec{ 73 | // fn from(val: Value) -> anyhow::Result { 74 | // if let Value::Array(array) = val{ 75 | // let mut list = vec![]; 76 | // for i in array{ 77 | // list.push(T::from(i)?); 78 | // } 79 | // return list.ok() 80 | // } 81 | // return anyhow!("unknown type[{}] FromValue for array",val).err() 82 | // } 83 | // } 84 | impl FromValue for T 85 | where 86 | T: for<'a> Deserialize<'a>, 87 | { 88 | fn from(val: Value) -> anyhow::Result { 89 | let t: T = serde_json::from_value(val)?; 90 | t.ok() 91 | } 92 | } 93 | 94 | pub trait HostFunction: Send + Sync { 95 | fn call(&self, args: Vec) -> anyhow::Result; 96 | } 97 | pub struct FunctionImpl { 98 | inner: Box>, 99 | } 100 | 101 | impl FunctionImpl { 102 | pub fn new + Send + Sync + 'static>(f: F) -> Self { 103 | let inner = Box::new(f); 104 | Self { inner } 105 | } 106 | } 107 | 108 | impl Function for FunctionImpl 109 | where 110 | O: Serialize, 111 | { 112 | fn call(&self, _fs: Arc, args: Vec) -> anyhow::Result { 113 | self.inner.call(args) 114 | } 115 | } 116 | impl HostFunction<(), O> for F 117 | where 118 | O: Serialize, 119 | F: Fn() -> anyhow::Result + Send + Sync + 'static, 120 | { 121 | fn call(&self, _args: Vec) -> anyhow::Result { 122 | let out = self()?; 123 | let val = serde_json::to_value(out)?; 124 | Ok(val) 125 | } 126 | } 127 | macro_rules! function_impl_template { 128 | ($n:tt,$($t:tt),*) => { 129 | impl<$($t,)* O,F> HostFunction<($($t,)*),O> for F 130 | where $($t:FromValue,)* 131 | O:Serialize,F:Fn($($t,)*)->anyhow::Result + Send + Sync + 'static 132 | { 133 | fn call(&self, mut args: Vec) -> anyhow::Result { 134 | if args.len() < $n { 135 | return anyhow!("expecting {} parameters actually finds {} parameters",$n,args.len()).err() 136 | } 137 | let out = self($($t::from(args.remove(0))?,)*)?; 138 | let val = serde_json::to_value(out)?; 139 | Ok(val) 140 | } 141 | } 142 | }; 143 | } 144 | function_impl_template!(1, A1); 145 | function_impl_template!(2, A1, A2); 146 | function_impl_template!(3, A1, A2, A3); 147 | function_impl_template!(4, A1, A2, A3, A4); 148 | function_impl_template!(5, A1, A2, A3, A4, A5); 149 | function_impl_template!(6, A1, A2, A3, A4, A5, A6); 150 | function_impl_template!(7, A1, A2, A3, A4, A5, A6, A7); 151 | function_impl_template!(8, A1, A2, A3, A4, A5, A6, A7, A8); 152 | function_impl_template!(9, A1, A2, A3, A4, A5, A6, A7, A8, A9); 153 | function_impl_template!(10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10); 154 | function_impl_template!(11, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11); 155 | function_impl_template!(12, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12); 156 | function_impl_template!(13, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13); 157 | function_impl_template!(14, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14); 158 | function_impl_template!(15, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15); 159 | function_impl_template!(16, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16); 160 | 161 | #[cfg(test)] 162 | mod test { 163 | use crate::{Function, FunctionImpl, FunctionSet, HostFunction}; 164 | use serde::Serialize; 165 | use serde_json::Value; 166 | use std::sync::Arc; 167 | #[derive(Debug)] 168 | struct FSet; 169 | impl FunctionSet for FSet { 170 | fn get(&self, _name: &str) -> Option> { 171 | None 172 | } 173 | } 174 | 175 | fn call + 'static>(f: F) { 176 | let f = FunctionImpl { inner: Box::new(f) }; 177 | let b: Box = Box::new(f); 178 | let _ = b 179 | .call( 180 | Arc::new(FSet {}), 181 | vec![Value::String("hello".into()), Value::String("world".into())], 182 | ) 183 | .unwrap(); 184 | // let _ = f.call(Arc::new(FSet{}),vec![Value::String("hello".into()),Value::String("world".into())]); 185 | } 186 | 187 | //cargo test --color=always --lib function::test::test_fn --no-fail-fast -- --exact unstable-options --show-output --nocapture 188 | #[test] 189 | fn test_fn() { 190 | call(|a: String| { 191 | println!("--->{}", a); 192 | Ok("hello") 193 | }); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /rush_wasm_engine/src/wasm_runtime.rs: -------------------------------------------------------------------------------- 1 | use crate::ExportEnv; 2 | use anyhow::{anyhow, Error}; 3 | use serde::{Deserialize, Serialize}; 4 | use std::time::Duration; 5 | use tokio::sync::oneshot::{channel, error, Sender}; 6 | use wasmer::{FunctionEnv, Instance, Module, Store, TypedFunction, WasmPtr}; 7 | use wd_tools::PFErr; 8 | 9 | #[derive(Debug)] 10 | struct Task { 11 | input: Vec, 12 | sender: Sender>, 13 | } 14 | 15 | pub struct WasmRuntime { 16 | sender: async_channel::Sender, 17 | } 18 | 19 | impl WasmRuntime { 20 | pub fn new(wasm_bytes: Vec) -> anyhow::Result { 21 | let (sender, receiver) = async_channel::bounded(32); 22 | let (sender_init, mut receiver_init) = channel::>(); 23 | 24 | std::thread::spawn(move || { 25 | let mut store = Store::default(); 26 | let module = match Module::new(&store, wasm_bytes) { 27 | Ok(m) => m, 28 | Err(e) => { 29 | let _ = sender_init.send(Err(Error::from(e))); 30 | return; 31 | } 32 | }; 33 | 34 | let env = ExportEnv::new(); 35 | let env = FunctionEnv::new(&mut store, env); 36 | let import_obj = ExportEnv::generate_import(&env, &mut store); 37 | 38 | let instance = match Instance::new(&mut store, &module, &import_obj) { 39 | Ok(o) => o, 40 | Err(e) => { 41 | let _ = sender_init.send(Err(Error::from(e))); 42 | return; 43 | } 44 | }; 45 | 46 | let memory = match instance.exports.get_memory("memory") { 47 | Ok(o) => o, 48 | Err(e) => { 49 | let _ = sender_init.send(Err(Error::from(e))); 50 | return; 51 | } 52 | }; 53 | env.as_mut(&mut store).init(memory.clone()); 54 | 55 | let handle: TypedFunction<(WasmPtr, u32), u32> = 56 | match instance.exports.get_typed_function(&store, "handle") { 57 | Ok(o) => o, 58 | Err(e) => { 59 | let _ = sender_init.send(Err(Error::from(e))); 60 | return; 61 | } 62 | }; 63 | 64 | if sender_init.send(Ok(())).is_err() { 65 | return; 66 | } 67 | 68 | loop { 69 | let Task { input, sender } = match receiver.recv_blocking() { 70 | Ok(o) => o, 71 | Err(_) => return, 72 | }; 73 | let view = memory.view(&store); 74 | let ptr: WasmPtr = WasmPtr::new(0); 75 | let value = match ptr.slice(&view, input.len() as u32) { 76 | Ok(o) => o, 77 | Err(e) => { 78 | let _ = sender.send(Err(Error::from(e))); 79 | continue; 80 | } 81 | }; 82 | if let Err(e) = value.write_slice(input.as_slice()) { 83 | let _ = sender.send(Err(Error::from(e))); 84 | continue; 85 | }; 86 | if let Err(e) = handle.call(&mut store, ptr, input.len() as u32) { 87 | let _ = sender.send(Err(Error::from(e))); 88 | continue; 89 | } 90 | let result = env.as_mut(&mut store).get_result(); 91 | let _ = sender.send(result); 92 | } 93 | }); 94 | 95 | loop { 96 | match receiver_init.try_recv() { 97 | Ok(Ok(_)) => { 98 | break; 99 | } 100 | Ok(Err(e)) => { 101 | return Err(e); 102 | } 103 | Err(error::TryRecvError::Closed) => { 104 | return anyhow!("init lua runtime unknown error").err(); 105 | } 106 | Err(error::TryRecvError::Empty) => { 107 | std::thread::sleep(Duration::from_millis(1)); 108 | } 109 | } 110 | } 111 | Ok(Self { sender }) 112 | } 113 | 114 | pub fn call Deserialize<'a>>(&self, req: S) -> anyhow::Result { 115 | let input = serde_json::to_string(&req)?.into_bytes(); 116 | let (sender, receiver) = channel(); 117 | let task = Task { input, sender }; 118 | if let Err(e) = self.sender.send_blocking(task) { 119 | let err = e.to_string(); 120 | return anyhow!("wasm runtime call failed: {}", err).err(); 121 | } 122 | return match receiver.blocking_recv() { 123 | Ok(o) => { 124 | let s = o?; 125 | let out = serde_json::from_str::(s.as_str())?; 126 | Ok(out) 127 | } 128 | Err(e) => anyhow!("wasm runtime error:{}", e).err(), 129 | }; 130 | } 131 | 132 | pub async fn async_call Deserialize<'a>>( 133 | &self, 134 | req: S, 135 | ) -> anyhow::Result { 136 | let input = serde_json::to_string(&req)?.into_bytes(); 137 | let (sender, receiver) = channel(); 138 | let task = Task { input, sender }; 139 | if let Err(e) = self.sender.send(task).await { 140 | let err = e.to_string(); 141 | return anyhow!("lua runtime call failed: {}", err).err(); 142 | } 143 | return match receiver.await { 144 | Ok(o) => { 145 | let s = o?; 146 | let out = serde_json::from_str::(s.as_str())?; 147 | Ok(out) 148 | } 149 | Err(e) => anyhow!("wasm runtime error:{}", e).err(), 150 | }; 151 | } 152 | } 153 | impl Drop for WasmRuntime { 154 | fn drop(&mut self) { 155 | self.sender.close(); 156 | } 157 | } 158 | 159 | impl>> From for WasmRuntime { 160 | fn from(value: T) -> Self { 161 | WasmRuntime::new(value.into()).unwrap() 162 | } 163 | } 164 | 165 | #[cfg(feature = "rule-flow")] 166 | impl rush_core::RuleFlow for WasmRuntime { 167 | fn flow Deserialize<'a>>(&self, obj: Obj) -> anyhow::Result { 168 | self.call(obj) 169 | } 170 | } 171 | #[cfg(feature = "rule-flow")] 172 | #[async_trait::async_trait] 173 | impl rush_core::AsyncRuleFlow for WasmRuntime { 174 | async fn async_flow Deserialize<'a>>( 175 | &self, 176 | obj: Obj, 177 | ) -> anyhow::Result { 178 | self.async_call(obj).await 179 | } 180 | } 181 | #[cfg(test)] 182 | mod test { 183 | use crate::WasmRuntime; 184 | use serde_json::Value; 185 | use std::collections::HashMap; 186 | 187 | #[test] 188 | pub fn test_wasm() { 189 | let wasm_bytes = 190 | include_bytes!("../../target/wasm32-unknown-unknown/release/wasm_example_one.wasm"); 191 | 192 | let wr = WasmRuntime::new(wasm_bytes.to_vec()).unwrap(); 193 | 194 | let result: HashMap = wr.call(Value::String("hello".into())).unwrap(); 195 | assert_eq!(result.get("input").unwrap().as_str(), "hello"); 196 | 197 | let result: HashMap = wr.call(Value::String("world".into())).unwrap(); 198 | assert_eq!(result.get("input").unwrap().as_str(), "world"); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /rush_core/src/rush.rs: -------------------------------------------------------------------------------- 1 | use crate::std_tool::{ArrayContain, ArraySub, Env}; 2 | use crate::{ 3 | AsyncRuleFlow, CalcNode, Exec, Function, FunctionImpl, FunctionSet, HostFunction, RuleFlow, 4 | }; 5 | use serde::{Deserialize, Serialize}; 6 | use serde_json::{Map, Value}; 7 | use std::collections::HashMap; 8 | use std::fmt::{Debug, Display, Formatter}; 9 | use std::sync::Arc; 10 | use wd_tools::sync::Acl; 11 | 12 | pub struct Rush { 13 | pub(crate) functions: Acl>>, 14 | pub(crate) nodes: HashMap>>, 15 | pub(crate) nodes_seq: Vec, 16 | pub(crate) exec: HashMap>, 17 | } 18 | 19 | impl Debug for Rush { 20 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 21 | let mut fs = vec![]; 22 | for (i, _) in self.functions.share().iter() { 23 | fs.push(i.to_string()); 24 | } 25 | let mut nodes = vec![]; 26 | for (i, _) in self.nodes.iter() { 27 | nodes.push(i.to_string()); 28 | } 29 | let mut rules = vec![]; 30 | for (i, _) in self.exec.iter() { 31 | rules.push(i.to_string()); 32 | } 33 | write!( 34 | f, 35 | "{{ functions:{:?},nodes:{:?},rules:{:?} }}", 36 | fs, nodes, rules 37 | ) 38 | } 39 | } 40 | impl Display for Rush { 41 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 42 | Debug::fmt(self, f) 43 | } 44 | } 45 | 46 | impl Rush { 47 | pub fn new() -> Self { 48 | let functions = Acl::new(HashMap::new()); 49 | let nodes = HashMap::new(); 50 | let nodes_seq = Vec::new(); 51 | let rules = HashMap::new(); 52 | let rh = Self { 53 | functions, 54 | nodes, 55 | nodes_seq, 56 | exec: rules, 57 | }; 58 | rh.raw_register_function("contain", ArrayContain {}) 59 | .raw_register_function("sub", ArraySub {}) 60 | .raw_register_function("env", Env::default()) 61 | } 62 | pub fn register_rule< 63 | C: CalcNode + Send + Sync + 'static, 64 | E: Exec + Send + Sync + 'static, 65 | T: Into, 66 | >( 67 | mut self, 68 | name: T, 69 | nodes: Vec, 70 | exec: E, 71 | ) -> Self { 72 | let name = name.into(); 73 | let mut ns: Vec> = vec![]; 74 | for i in nodes { 75 | ns.push(Box::new(i)); 76 | } 77 | let mut index = usize::MAX; 78 | if self.nodes.contains_key(&name) { 79 | for (i,n) in self.nodes_seq.iter().enumerate(){ 80 | if n == &name{ 81 | index = i; 82 | break 83 | } 84 | } 85 | } 86 | if index != usize::MAX{ 87 | self.nodes_seq.remove(index); 88 | } 89 | self.nodes_seq.push(name.clone()); 90 | self.nodes.insert(name.clone(), ns); 91 | self.exec.insert(name, Box::new(exec)); 92 | self 93 | } 94 | pub fn delete_rule>(&mut self, name: T) { 95 | let mut index = usize::MAX; 96 | if self.exec.contains_key(name.as_ref()) { 97 | for (i,n) in self.nodes_seq.iter().enumerate(){ 98 | if n == name.as_ref() { 99 | index = i; 100 | break 101 | } 102 | } 103 | } 104 | if index != usize::MAX{ 105 | self.nodes_seq.remove(index); 106 | } 107 | self.nodes.remove(name.as_ref()); 108 | self.exec.remove(name.as_ref()); 109 | } 110 | pub fn raw_register_function, F: Function>(self, name: S, function: F) -> Self { 111 | self.functions.update(|x| { 112 | let mut map = (*x).clone(); 113 | map.insert(name.into(), Arc::new(function)); 114 | map 115 | }); 116 | self 117 | } 118 | pub fn register_function, Args, Out, F>(self, name: S, function: F) -> Self 119 | where 120 | F: HostFunction + 'static, 121 | Out: Serialize, 122 | { 123 | self.raw_register_function(name, FunctionImpl::new(function)) 124 | } 125 | 126 | pub fn delete_function>(self, name: S) -> Self { 127 | self.functions.update(|x| { 128 | let mut map = (*x).clone(); 129 | map.remove(name.as_ref()); 130 | map 131 | }); 132 | self 133 | } 134 | 135 | pub fn execute(&self, obj: &Value, list: Vec) -> anyhow::Result { 136 | let mut output = Value::Object(Map::new()); 137 | for name in list.iter() { 138 | if let Some(r) = self.exec.get(name) { 139 | r.execute(self.functions.share(), obj, &mut output)?; 140 | } 141 | } 142 | Ok(output) 143 | } 144 | /// input_value 145 | /// 1. 计算匹配到的规则 146 | /// 2. 找出规则进行结果生成 147 | fn flow_value(&self, obj: Value) -> anyhow::Result { 148 | let mut rules = vec![]; 149 | 'lp: for k in self.nodes_seq.iter() { 150 | let v = self.nodes.get(k).unwrap(); 151 | for i in v.iter() { 152 | if !i.when(self.functions.share(), &obj)? { 153 | continue 'lp; 154 | } 155 | } 156 | rules.push(k.to_string()); 157 | } 158 | self.execute(&obj, rules) 159 | } 160 | } 161 | 162 | impl, E)>> From for Rush 163 | where 164 | C: CalcNode + 'static, 165 | E: Exec + 'static, 166 | { 167 | fn from(value: I) -> Self { 168 | let mut rush = Rush::new(); 169 | for (name, calc, exec) in value { 170 | rush = rush.register_rule(name, calc, exec); 171 | } 172 | rush 173 | } 174 | } 175 | 176 | impl FunctionSet for HashMap> { 177 | fn get(&self, name: &str) -> Option> { 178 | self.get(name).map(|a| a.clone()) 179 | } 180 | } 181 | 182 | impl RuleFlow for Rush { 183 | fn flow>(&self, obj: Obj) -> anyhow::Result { 184 | let value = serde_json::to_value(obj)?; 185 | let result = self.flow_value(value)?; 186 | let out = Out::deserialize(result)?; 187 | Ok(out) 188 | } 189 | } 190 | impl AsyncRuleFlow for Rush {} 191 | 192 | #[cfg(test)] 193 | mod test { 194 | use crate::rush::Rush; 195 | use crate::{CalcNode, Exec, FunctionSet, RuleFlow}; 196 | use serde::{Deserialize, Serialize}; 197 | use serde_json::Value; 198 | use std::sync::Arc; 199 | 200 | struct CalcNodeImpl; 201 | impl CalcNode for CalcNodeImpl { 202 | fn when(&self, _fs: Arc, _input: &Value) -> anyhow::Result { 203 | return Ok(true); 204 | } 205 | } 206 | struct RuleImpl; 207 | impl Exec for RuleImpl { 208 | fn execute( 209 | &self, 210 | _fs: Arc, 211 | _input: &Value, 212 | _output: &mut Value, 213 | ) -> anyhow::Result<()> { 214 | Ok(()) 215 | } 216 | } 217 | #[derive(Debug, Default, Serialize, Deserialize)] 218 | struct ObjTest { 219 | #[serde(default = "String::default")] 220 | pub name: String, 221 | } 222 | 223 | //cargo test --color=always --lib rush::test::test_simple --no-fail-fast -- --exact unstable-options --show-output 224 | #[test] 225 | fn test_simple() { 226 | let mr = Rush::new(); 227 | let result: ObjTest = mr 228 | .flow(ObjTest { 229 | name: "hello world".into(), 230 | }) 231 | .expect("input failed"); 232 | println!("result ---> {result:?}"); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /rush_lua_engine/src/lua_runtime.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Error}; 2 | use mlua::{Function, Lua, LuaSerdeExt, Value}; 3 | use serde::{Deserialize, Serialize}; 4 | use std::collections::HashMap; 5 | use std::str::FromStr; 6 | use std::time::Duration; 7 | use tokio::sync::oneshot::*; 8 | use wd_tools::{PFErr, PFOk}; 9 | 10 | #[derive(Debug)] 11 | struct Task { 12 | input: serde_json::Value, 13 | sender: Sender>, 14 | } 15 | 16 | #[derive(Serialize, Deserialize, Default)] 17 | pub struct InitResult { 18 | #[serde(default = "Default::default")] 19 | code: isize, 20 | #[serde(default = "String::default")] 21 | message: String, 22 | #[serde(default = "String::default")] 23 | handle_function: String, 24 | } 25 | 26 | #[derive(Debug)] 27 | pub struct LuaRuntime { 28 | sender: async_channel::Sender, 29 | } 30 | 31 | impl Clone for LuaRuntime { 32 | fn clone(&self) -> Self { 33 | Self { 34 | sender: self.sender.clone(), 35 | } 36 | } 37 | } 38 | 39 | impl LuaRuntime { 40 | pub fn new(script: String, envs: HashMap) -> anyhow::Result { 41 | let (sender, receiver) = async_channel::bounded(32); 42 | let (sender_init, mut receiver_init) = channel::>(); 43 | 44 | std::thread::spawn(move || { 45 | let lua = Lua::new(); 46 | let global = lua.globals(); 47 | 48 | for (k, v) in envs { 49 | if let Err(e) = global.set(k, v) { 50 | let _ = sender_init.send(Err(Error::from(e))); 51 | return; 52 | } 53 | } 54 | 55 | let result = lua.load(script).eval::>(); 56 | let val = match result { 57 | Ok(o) => { 58 | if o.is_none() { 59 | let _ = sender_init.send( 60 | anyhow!("load script init failed, not return check result").err(), 61 | ); 62 | return; 63 | } 64 | o.unwrap() 65 | } 66 | Err(e) => { 67 | let _ = sender_init.send(Err(Error::from(e))); 68 | return; 69 | } 70 | }; 71 | let result = match lua.from_value::(val) { 72 | Ok(o) => o, 73 | Err(e) => { 74 | let _ = sender_init.send(Err(Error::from(e))); 75 | return; 76 | } 77 | }; 78 | if result.code != 0 { 79 | let _ = sender_init.send(Ok(result)); 80 | return; 81 | } 82 | if result.handle_function.is_empty() { 83 | let _ = sender_init.send(anyhow!("entry function must manifest").err()); 84 | return; 85 | } 86 | let func = match global.get::<_, Function>(result.handle_function.as_str()) { 87 | Ok(f) => f, 88 | Err(e) => { 89 | let _ = sender_init.send(Err(Error::from(e))); 90 | return; 91 | } 92 | }; 93 | 94 | if sender_init.send(InitResult::default().ok()).is_err() { 95 | return; 96 | } 97 | loop { 98 | let Task { input, sender } = match receiver.recv_blocking() { 99 | Ok(o) => o, 100 | Err(_) => return, 101 | }; 102 | let val = match lua.to_value(&input) { 103 | Ok(o) => o, 104 | Err(e) => { 105 | let _ = sender.send(Err(Error::from(e))); 106 | continue; 107 | } 108 | }; 109 | let val = match func.call::<_, Value>(val) { 110 | Ok(o) => o, 111 | Err(e) => { 112 | let _ = sender.send(Err(Error::from(e))); 113 | continue; 114 | } 115 | }; 116 | 117 | match lua.from_value::(val) { 118 | Ok(o) => { 119 | let _ = sender.send(Ok(o)); 120 | } 121 | Err(e) => { 122 | let _ = sender.send(Err(Error::from(e))); 123 | } 124 | }; 125 | } 126 | }); 127 | 128 | loop { 129 | match receiver_init.try_recv() { 130 | Ok(Ok(o)) => { 131 | if o.code != 0 { 132 | return anyhow!("init lua runtime failed:{}", o.message).err(); 133 | } 134 | break; 135 | } 136 | Ok(Err(e)) => { 137 | return Err(e); 138 | } 139 | Err(error::TryRecvError::Closed) => { 140 | return anyhow!("init lua runtime unknown error").err(); 141 | } 142 | Err(error::TryRecvError::Empty) => { 143 | std::thread::sleep(Duration::from_millis(1)); 144 | } 145 | } 146 | } 147 | Ok(LuaRuntime { sender }) 148 | } 149 | 150 | pub fn call Deserialize<'a>>(&self, req: S) -> anyhow::Result { 151 | let req = serde_json::to_value(req)?; 152 | let (sender, receiver) = channel(); 153 | let task = Task { input: req, sender }; 154 | if let Err(e) = self.sender.send_blocking(task) { 155 | let err = e.to_string(); 156 | return anyhow!("lua runtime call failed: {}", err).err(); 157 | } 158 | return match receiver.blocking_recv() { 159 | Ok(o) => { 160 | let out = serde_json::from_value::(o?)?; 161 | Ok(out) 162 | } 163 | Err(e) => anyhow!("lua runtime error:{}", e).err(), 164 | }; 165 | } 166 | pub async fn async_call Deserialize<'a>>( 167 | &self, 168 | req: S, 169 | ) -> anyhow::Result { 170 | let req = serde_json::to_value(req)?; 171 | let (sender, receiver) = channel(); 172 | let task = Task { input: req, sender }; 173 | if let Err(e) = self.sender.send(task).await { 174 | let err = e.to_string(); 175 | return anyhow!("lua runtime call failed: {}", err).err(); 176 | } 177 | return match receiver.await { 178 | Ok(o) => { 179 | let out: Out = serde_json::from_value(o?)?; 180 | Ok(out) 181 | } 182 | Err(e) => anyhow!("lua runtime error:{}", e).err(), 183 | }; 184 | } 185 | pub fn close(&self) { 186 | self.sender.close(); 187 | } 188 | } 189 | 190 | impl Drop for LuaRuntime { 191 | fn drop(&mut self) { 192 | self.close(); 193 | } 194 | } 195 | 196 | #[cfg(feature = "rule-flow")] 197 | impl rush_core::RuleFlow for LuaRuntime { 198 | fn flow Deserialize<'a>>(&self, obj: Obj) -> anyhow::Result { 199 | self.call(obj) 200 | } 201 | } 202 | #[cfg(feature = "rule-flow")] 203 | #[async_trait::async_trait] 204 | impl rush_core::AsyncRuleFlow for LuaRuntime { 205 | async fn async_flow Deserialize<'a>>( 206 | &self, 207 | obj: Obj, 208 | ) -> anyhow::Result { 209 | self.async_call(obj).await 210 | } 211 | } 212 | 213 | impl FromStr for LuaRuntime { 214 | type Err = anyhow::Error; 215 | 216 | fn from_str(s: &str) -> Result { 217 | LuaRuntime::new(s.to_string(), HashMap::new()) 218 | } 219 | } 220 | 221 | #[cfg(test)] 222 | mod test { 223 | use crate::LuaRuntime; 224 | use serde_json::Value; 225 | use std::collections::HashMap; 226 | 227 | const TEST_LUA_SCRIPT: &'static str = r#" 228 | function handle(req) 229 | for k, v in pairs(req) do 230 | print("--->",k,v) 231 | end 232 | local resp = "success" 233 | return resp 234 | end 235 | 236 | return {code=0,message="success",handle_function="handle"} 237 | "#; 238 | 239 | #[test] 240 | fn test_function_lua_runtime() { 241 | let rt = LuaRuntime::new(TEST_LUA_SCRIPT.to_string(), HashMap::new()).unwrap(); 242 | let result: Value = rt 243 | .call(r#"{"order_id":"78632839429034208","status":1}"#.parse::().unwrap()) 244 | .unwrap(); 245 | assert_eq!(result, Value::String("success".into())) 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /rush_expr_engine/src/calc.rs: -------------------------------------------------------------------------------- 1 | use crate::{CalcBuilder, NotFoundFieldError}; 2 | use anyhow::anyhow; 3 | use rush_core::FunctionSet; 4 | use serde_json::{Number, Value}; 5 | use std::fmt::Debug; 6 | use std::str::FromStr; 7 | use std::sync::Arc; 8 | use wd_tools::{PFErr, PFOk}; 9 | 10 | #[derive(Debug, Eq, PartialEq, Clone)] 11 | pub enum Opt { 12 | //最低 13 | ADD, // + 14 | SUB, // - 15 | MUL, // * 16 | DIV, // / 17 | REM, // % 18 | AND, // & 19 | OR, // | 20 | XOR, // ^ 21 | NOT, // ! 22 | REV, // ~ 23 | SHL, // << 24 | SHR, // >> 25 | //次高 26 | GT, // > 27 | GE, // >= 28 | LT, // < 29 | LE, // <= 30 | EQ, // == 31 | NQ, // != 32 | // 优先级最高 33 | AT, // && 34 | OT, // || 35 | } 36 | 37 | impl AsRef for Opt { 38 | fn as_ref(&self) -> &str { 39 | match self { 40 | Opt::ADD => "+", 41 | Opt::SUB => "-", 42 | Opt::MUL => "*", 43 | Opt::DIV => "/", 44 | Opt::REM => "%", 45 | Opt::AND => "&", 46 | Opt::OR => "|", 47 | Opt::XOR => "^", 48 | Opt::NOT => "!", 49 | Opt::REV => "~", 50 | Opt::SHL => "<<", 51 | Opt::SHR => ">>", 52 | Opt::GT => ">", 53 | Opt::GE => ">=", 54 | Opt::LT => "<", 55 | Opt::LE => "<=", 56 | Opt::EQ => "==", 57 | Opt::NQ => "!=", 58 | Opt::AT => "&&", 59 | Opt::OT => "||", 60 | } 61 | } 62 | } 63 | 64 | #[derive(Debug, PartialEq, Clone)] 65 | pub enum Calc { 66 | NULL, 67 | Field(String), 68 | String(String), 69 | Number(i64), 70 | Float(f64), 71 | Bool(bool), 72 | Array(Vec), 73 | Function(String, Vec), 74 | 75 | Operator(Opt, Vec), 76 | } 77 | 78 | macro_rules! operator_number_float { 79 | ($operator:tt,$args:tt,$fs:tt,$input:tt,$($enum_ty:path=>$opt:tt),*) => { 80 | $( 81 | if let $enum_ty = $operator{ 82 | let nb1 = $args[0].number($fs, $input)?; 83 | let nb2 = $args[1].number($fs,$input)?; 84 | if let Some(i1) = nb1.as_i64() { 85 | if let Some(i2) = nb2.as_i64(){ 86 | return Value::Number(Number::from(i1 $opt i2)).ok() 87 | }else if let Some(i2) = nb2.as_f64(){ 88 | if let Some(s) = Number::from_f64(i1 as f64 $opt i2){ 89 | return Value::Number(s).ok() 90 | } 91 | } 92 | }else if let Some(f1) = nb1.as_f64(){ 93 | if let Some(f2) = nb2.as_i64(){ 94 | if let Some(s) = Number::from_f64(f1 $opt f2 as f64){ 95 | return Value::Number(s).ok() 96 | } 97 | }else if let Some(f2) = nb2.as_f64(){ 98 | if let Some(s) = Number::from_f64(f1 $opt f2){ 99 | return Value::Number(s).ok() 100 | } 101 | } 102 | } 103 | return anyhow!("operator[{:?}] can not support args:[{:?}]",$operator,$args).err() 104 | } 105 | )* 106 | }; 107 | } 108 | macro_rules! operator_number_bool { 109 | ($operator:tt,$args:tt,$fs:tt,$input:tt,$($enum_ty:path=>$opt:tt),*) => { 110 | $( 111 | if let $enum_ty = $operator{ 112 | let nb1 = $args[0].number($fs, $input)?; 113 | let nb2 = $args[1].number($fs,$input)?; 114 | if let Some(i1) = nb1.as_i64() { 115 | if let Some(i2) = nb2.as_i64(){ 116 | return Value::Bool(i1 $opt i2).ok() 117 | }else if let Some(i2) = nb2.as_f64(){ 118 | return Value::Bool((i1 as f64) $opt i2).ok() 119 | } 120 | }else if let Some(f1) = nb1.as_f64(){ 121 | if let Some(f2) = nb2.as_i64(){ 122 | return Value::Bool(f1 $opt (f2 as f64)).ok() 123 | }else if let Some(f2) = nb2.as_f64(){ 124 | return Value::Bool(f1 $opt f2).ok() 125 | } 126 | } 127 | return anyhow!("operator[{:?}] can not support args:[{:?}]",$operator,$args).err() 128 | } 129 | )* 130 | }; 131 | } 132 | macro_rules! operator_number_bit_option { 133 | ($operator:tt,$args:tt,$fs:tt,$input:tt,$($enum_ty:path=>$opt:tt),*) => { 134 | $( 135 | if let $enum_ty = $operator{ 136 | let nb1 = $args[0].number($fs, $input)?; 137 | let nb2 = $args[1].number($fs,$input)?; 138 | if let Some(i1) = nb1.as_i64() { 139 | if let Some(i2) = nb2.as_i64(){ 140 | return Value::Number(Number::from(i1 $opt i2)).ok() 141 | } 142 | } 143 | return anyhow!("operator[{:?}] can not support args:[{:?}]",$operator,$args).err() 144 | } 145 | )* 146 | }; 147 | } 148 | 149 | impl Calc { 150 | pub fn field(&self, mut input: &Value) -> anyhow::Result { 151 | match self { 152 | Calc::Field(field) => { 153 | let ks: Vec<&str> = field.split('.').collect(); 154 | for i in ks { 155 | match input { 156 | Value::Object(obj) => { 157 | match obj.get(i) { 158 | None => { 159 | // return >::into(NotFoundFieldError(i.to_string())).err() 160 | return Err(NotFoundFieldError(i.to_string()).into()); 161 | } 162 | Some(s) => { 163 | input = s; 164 | } 165 | } 166 | } 167 | _ => return anyhow!("not found object at field[{field}]").err(), 168 | } 169 | } 170 | input.clone().ok() 171 | } 172 | _ => anyhow!("calc[{:?}],is not field", self).err(), 173 | } 174 | } 175 | pub fn function(&self, fs: &Arc, input: &Value) -> anyhow::Result { 176 | return match self { 177 | Calc::Function(name, args) => { 178 | let mut val_args = vec![]; 179 | for i in args { 180 | val_args.push(i.value(fs, input)?); 181 | } 182 | if let Some(function) = fs.get(name) { 183 | function.call(fs.clone(), val_args) 184 | } else { 185 | anyhow!("function[{}] not found", name).err() 186 | } 187 | } 188 | _ => anyhow!("type[{:?}] is not function", self).err(), 189 | }; 190 | } 191 | pub fn number(&self, fs: &Arc, input: &Value) -> anyhow::Result { 192 | let n = match self { 193 | Calc::NULL => Number::from(0i64), 194 | Calc::Field(_) => { 195 | let val = self.field(input)?; 196 | match val { 197 | Value::Null => Number::from(0i64), 198 | Value::Number(n) => n, 199 | _ => return anyhow!("type[{val}] can not to number").err(), 200 | } 201 | } 202 | Calc::Number(n) => Number::from(*n), 203 | Calc::Float(f) => { 204 | if let Some(s) = Number::from_f64(*f) { 205 | s 206 | } else { 207 | return anyhow!("want get f64,found NAN").err(); 208 | } 209 | } 210 | Calc::Function(_, _) => { 211 | let val = self.function(fs, input)?; 212 | match val { 213 | Value::Null => Number::from(0i64), 214 | Value::Number(n) => n, 215 | _ => return anyhow!("type[{val}] can not to number").err(), 216 | } 217 | } 218 | Calc::Operator(opt, args) => { 219 | let val = Self::operator(opt, args, fs, input)?; 220 | match val { 221 | Value::Null => Number::from(0i64), 222 | Value::Number(n) => n, 223 | _ => return anyhow!("type[{val}] can not to number").err(), 224 | } 225 | } 226 | _ => return anyhow!("type[{:?}] can not to number", self).err(), 227 | }; 228 | return n.ok(); 229 | } 230 | pub fn value(&self, fs: &Arc, input: &Value) -> anyhow::Result { 231 | let b = match self { 232 | Calc::NULL => Value::Null, 233 | Calc::Field(_) => self.field(input)?, 234 | Calc::String(s) => Value::String(s.clone()), 235 | Calc::Number(n) => Value::Number(Number::from(*n)), 236 | Calc::Float(f) => match Number::from_f64(*f) { 237 | None => return anyhow!("need f64, found a NAN").err(), 238 | Some(n) => Value::Number(n), 239 | }, 240 | Calc::Bool(b) => Value::Bool(*b), 241 | Calc::Array(a) => { 242 | let mut array = vec![]; 243 | for i in a { 244 | array.push(i.value(fs, input)?); 245 | } 246 | Value::Array(array) 247 | } 248 | Calc::Function(_, _) => self.function(fs, input)?, 249 | Calc::Operator(opt, args) => Self::operator(opt, args, fs, input)?, 250 | }; 251 | Ok(b) 252 | } 253 | pub fn operator( 254 | opt: &Opt, 255 | args: &Vec, 256 | fs: &Arc, 257 | input: &Value, 258 | ) -> anyhow::Result { 259 | if args.len() == 0 { 260 | return anyhow!("operator[{:?}] args count must have one", opt).err(); 261 | } 262 | if args.len() == 1 { 263 | match opt { 264 | Opt::SUB => { 265 | let n = args[0].number(fs, input)?; 266 | if let Some(i) = n.as_i64() { 267 | return Value::Number(Number::from(-i)).ok(); 268 | } else if let Some(i) = n.as_u64() { 269 | return Value::Number(Number::from(-(i as i64))).ok(); 270 | } 271 | if let Some(i) = n.as_f64() { 272 | if let Some(s) = Number::from_f64(-i) { 273 | return Value::Number(s).ok(); 274 | } 275 | } 276 | return anyhow!("operator '-' parse number[{n}] failed").err(); 277 | } 278 | Opt::NOT => { 279 | let b = args[0].bool(fs, input)?; 280 | return Value::Bool(!b).ok(); 281 | } 282 | Opt::REV => { 283 | let n = args[0].number(fs, input)?; 284 | if let Some(i) = n.as_i64() { 285 | return Value::Number(Number::from(!i)).ok(); 286 | } 287 | return anyhow!("operator '~' only support i64 type").err(); 288 | } 289 | _ => {} 290 | } 291 | } 292 | if args.len() != 2 { 293 | return anyhow!("operator[{:?}] args count must hava two", opt).err(); 294 | } 295 | // let arg1 = args[0].value(fs,input)?; 296 | // let arg2 = args[1].value(fs,input)?; 297 | 298 | match opt { 299 | Opt::NOT | Opt::REV => { 300 | return anyhow!("operator[{:?}] args count must is one", opt).err() 301 | } 302 | _ => {} 303 | } 304 | 305 | operator_number_float!(opt,args,fs,input, 306 | Opt::ADD=>+, 307 | Opt::SUB=>-, 308 | Opt::MUL=>*, 309 | Opt::DIV=>/, 310 | Opt::REM=>%); 311 | 312 | operator_number_bit_option!(opt,args,fs,input, 313 | Opt::AND=> &, 314 | Opt::OR=> |, 315 | Opt::XOR=> ^, 316 | Opt::SHL=> <<, 317 | Opt::SHR=> >>); 318 | 319 | operator_number_bool!(opt,args,fs,input, 320 | Opt::GT=> >, 321 | Opt::GE=> >=, 322 | Opt::LT=> <, 323 | Opt::LE=> <=); 324 | 325 | if let Opt::AT = opt { 326 | let b1 = args[0].bool(fs, input)?; 327 | let b2 = args[1].bool(fs, input)?; 328 | return Value::Bool(b1 && b2).ok(); 329 | } 330 | if let Opt::OT = opt { 331 | if args[0].bool(fs, input)? { 332 | return Value::Bool(true).ok(); 333 | } 334 | if args[1].bool(fs, input)? { 335 | return Value::Bool(true).ok(); 336 | } 337 | return Value::Bool(false).ok(); 338 | } 339 | 340 | let v1 = args[0].value(fs, input)?; 341 | let v2 = args[1].value(fs, input)?; 342 | if let Opt::EQ = opt { 343 | return Value::Bool(v1 == v2).ok(); 344 | } else if let Opt::NQ = opt { 345 | return Value::Bool(v1 != v2).ok(); 346 | } 347 | 348 | return anyhow!("unknown operator[{:?}]", opt).err(); 349 | } 350 | 351 | pub fn bool(&self, fs: &Arc, input: &Value) -> anyhow::Result { 352 | let b = match self { 353 | Calc::NULL => false, 354 | Calc::Field(_) => { 355 | let val = self.field(input)?; 356 | match val { 357 | Value::Null => false, 358 | Value::Bool(b) => b, 359 | _ => true, 360 | } 361 | } 362 | Calc::String(s) => !s.is_empty(), 363 | Calc::Number(n) => *n != 0, 364 | Calc::Float(_) => true, 365 | Calc::Bool(b) => *b, 366 | Calc::Array(_) => true, 367 | Calc::Function(_, _) => { 368 | let val = self.function(fs, input)?; 369 | match val { 370 | Value::Null => false, 371 | Value::Bool(b) => b, 372 | _ => true, 373 | } 374 | } 375 | Calc::Operator(opt, args) => { 376 | let val = Self::operator(opt, args, fs, input)?; 377 | match val { 378 | Value::Null => false, 379 | Value::Bool(b) => b, 380 | _ => true, 381 | } 382 | } 383 | }; 384 | Ok(b) 385 | } 386 | } 387 | 388 | impl ToString for Calc { 389 | fn to_string(&self) -> String { 390 | match self { 391 | Calc::NULL => "null".into(), 392 | Calc::Field(s) => s.clone(), 393 | Calc::String(s) => format!("\"{}\"", s), 394 | Calc::Number(n) => n.to_string(), 395 | Calc::Float(f) => format!("{:.2}", f), 396 | Calc::Bool(b) => b.to_string(), 397 | Calc::Array(list) => { 398 | let mut array: String = "[".into(); 399 | for (i, e) in list.iter().enumerate() { 400 | if i != 0 { 401 | array.push_str(","); 402 | } 403 | array.push_str(e.to_string().as_str()); 404 | } 405 | array.push_str("]"); 406 | array 407 | } 408 | Calc::Function(func, args) => { 409 | let mut func = func.clone(); 410 | func.push_str("("); 411 | for (i, arg) in args.iter().enumerate() { 412 | if i != 0 { 413 | func.push_str(",") 414 | } 415 | func.push_str(arg.to_string().as_str()); 416 | } 417 | func.push_str(")"); 418 | func 419 | } 420 | Calc::Operator(opt, arg) => { 421 | if arg.len() == 1 { 422 | format!("({} {})", opt.as_ref(), arg[0].to_string()) 423 | } else if arg.len() == 2 { 424 | format!( 425 | "({} {} {})", 426 | arg[0].to_string(), 427 | opt.as_ref(), 428 | arg[1].to_string() 429 | ) 430 | } else { 431 | panic!("Calc.to_string length[{}]", arg.len()); 432 | } 433 | } 434 | } 435 | } 436 | } 437 | 438 | impl FromStr for Calc { 439 | type Err = anyhow::Error; 440 | 441 | fn from_str(s: &str) -> Result { 442 | CalcBuilder::new(s).build() 443 | } 444 | } 445 | impl> From for Calc { 446 | fn from(value: S) -> Self { 447 | Calc::from_str(value.as_ref()).unwrap() 448 | } 449 | } 450 | 451 | impl rush_core::CalcNode for Calc { 452 | fn when(&self, fs: Arc, input: &Value) -> anyhow::Result { 453 | match self.bool(&fs, input) { 454 | Ok(o) => o.ok(), 455 | Err(e) => { 456 | // not found field = false 457 | // no return error 458 | if let Some(_) = e.downcast_ref::() { 459 | Ok(false) 460 | } else { 461 | e.err() 462 | } 463 | } 464 | } 465 | } 466 | } 467 | 468 | #[cfg(test)] 469 | mod test { 470 | use super::Calc; 471 | use crate::Opt; 472 | use rush_core::{CalcNode, Function, FunctionSet}; 473 | use serde::Serialize; 474 | use serde_json::Value; 475 | use std::fmt::Debug; 476 | use std::sync::Arc; 477 | 478 | //cargo test --color=always --lib calc_impl::test::test_calc_show -- --exact unstable-options --nocapture 479 | #[test] 480 | fn test_calc_show() { 481 | let calc = Calc::Operator( 482 | Opt::GE, 483 | vec![ 484 | Calc::Number(32), 485 | Calc::Function("str_len".into(), vec![Calc::String("hello world".into())]), 486 | ], 487 | ); 488 | println!("result ===> {}", calc.to_string()); 489 | } 490 | 491 | //cargo test --color=always --lib calc::test::test_calc_parse -- --exact unstable-options --nocapture 492 | #[test] 493 | fn test_calc_parse() { 494 | let calc: Calc = "a > b || c < d".parse().unwrap(); 495 | println!("---> {}", calc.to_string()); 496 | } 497 | 498 | #[derive(Serialize)] 499 | struct People { 500 | pub age: isize, 501 | pub native_place: String, 502 | } 503 | #[derive(Debug)] 504 | struct FunctionSetImpl; 505 | 506 | impl FunctionSet for FunctionSetImpl { 507 | fn get(&self, _name: &str) -> Option> { 508 | None 509 | } 510 | } 511 | //cargo test --color=always --lib calc::test::test_calc_simple --no-fail-fast -- --exact unstable-options --nocapture 512 | #[test] 513 | fn test_calc_simple() { 514 | let expr = "age > 18 || native_place == \"中国\""; 515 | let calc: Calc = expr.parse().unwrap(); 516 | println!("calc--->{}", calc.to_string()); 517 | let people = People { 518 | age: 19, 519 | native_place: "japan".into(), 520 | }; 521 | let input = serde_json::to_value(people).unwrap(); 522 | let fs = Arc::new(FunctionSetImpl {}); 523 | let result = calc.when(fs, &input).expect("when error===>"); 524 | println!("result ---> {result}"); 525 | } 526 | 527 | #[test] 528 | fn test_calc_bit_operation() { 529 | let expr = "1 ^ 2"; 530 | let calc = Calc::from(expr); 531 | let fs: Arc = Arc::new(FunctionSetImpl {}); 532 | let result = calc.value(&fs, &Value::Null).unwrap(); 533 | println!("---> {:?}", result); 534 | } 535 | } 536 | -------------------------------------------------------------------------------- /rush_expr_engine/src/calc_parse.rs: -------------------------------------------------------------------------------- 1 | use crate::{Calc, Opt}; 2 | use anyhow::anyhow; 3 | use std::cmp::Ordering; 4 | use std::collections::VecDeque; 5 | use std::str::FromStr; 6 | use wd_tools::PFErr; 7 | 8 | #[derive(Debug, Clone, PartialEq)] 9 | pub enum Element { 10 | OPT(Opt), 11 | CALC(Calc), 12 | LeftSmall, // ( 13 | RightSmall, // ) 14 | LeftMed, // [ 15 | RightMed, // ] 16 | LeftBig, // { 17 | RightBig, // } 18 | Comma, // , 19 | } 20 | 21 | impl ToString for Element { 22 | fn to_string(&self) -> String { 23 | match self { 24 | Element::OPT(opt) => opt.as_ref().to_string(), 25 | Element::CALC(calc) => calc.to_string(), 26 | Element::LeftSmall => "(".into(), 27 | Element::RightSmall => ")".into(), 28 | Element::LeftMed => "[".into(), 29 | Element::RightMed => "]".into(), 30 | Element::LeftBig => "{".into(), 31 | Element::RightBig => "}".into(), 32 | Element::Comma => ",".into(), 33 | } 34 | } 35 | } 36 | 37 | macro_rules! match_one { 38 | ($o:expr,$e:expr,$deq:tt,$($key:tt=>$value:expr),*) => { 39 | $( 40 | if $e.starts_with($key) { 41 | $deq.push_back($value); 42 | $e = $e.split_off($key.len()); 43 | $o 44 | } 45 | )* 46 | 47 | }; 48 | } 49 | macro_rules! match_one_return { 50 | ($e:expr,$deq:tt,$($key:tt=>$value:expr),*) =>{ 51 | match_one!(return true,$e,$deq,$($key => $value),*); 52 | } 53 | } 54 | #[allow(unused_macros)] 55 | macro_rules! match_one_break { 56 | ($e:expr,$deq:tt,$($key:tt=>$value:expr),*) =>{ 57 | match_one!(break,$e,$deq,$($key => $value),*); 58 | } 59 | } 60 | 61 | impl Opt { 62 | fn parse_one(expr: &mut String, deq: &mut VecDeque) -> bool { 63 | match_one_return!(*expr,deq, 64 | //先解双字符 65 | "&&"=>Element::OPT(Opt::AT), 66 | "||"=>Element::OPT(Opt::OT), 67 | ">="=>Element::OPT(Opt::GE), 68 | "<="=>Element::OPT(Opt::LE), 69 | "=="=>Element::OPT(Opt::EQ), 70 | "!="=>Element::OPT(Opt::NQ), 71 | "<<"=>Element::OPT(Opt::SHL), 72 | ">>"=>Element::OPT(Opt::SHR), 73 | 74 | "<"=>Element::OPT(Opt::LT), 75 | ">"=>Element::OPT(Opt::GT), 76 | //低 77 | "+"=>Element::OPT(Opt::ADD), 78 | "-"=>Element::OPT(Opt::SUB), 79 | "*"=>Element::OPT(Opt::MUL), 80 | "/"=>Element::OPT(Opt::DIV), 81 | "%"=>Element::OPT(Opt::REM), 82 | "&"=>Element::OPT(Opt::AND), 83 | "|"=>Element::OPT(Opt::OR), 84 | "^"=>Element::OPT(Opt::XOR), 85 | "!"=>Element::OPT(Opt::NOT), 86 | "~"=>Element::OPT(Opt::REV)); 87 | return false; 88 | } 89 | } 90 | 91 | impl PartialOrd for Opt { 92 | fn partial_cmp(&self, other: &Self) -> Option { 93 | return match self { 94 | Opt::GT | Opt::GE | Opt::LT | Opt::LE | Opt::EQ | Opt::NQ => match other { 95 | Opt::GT | Opt::GE | Opt::LT | Opt::LE | Opt::EQ | Opt::NQ => Some(Ordering::Equal), 96 | Opt::OT | Opt::AT => Some(Ordering::Less), 97 | _ => Some(Ordering::Greater), 98 | }, 99 | Opt::OT | Opt::AT => match other { 100 | Opt::OT | Opt::AT => Some(Ordering::Equal), 101 | Opt::GT | Opt::GE | Opt::LT | Opt::LE | Opt::EQ | Opt::NQ => { 102 | Some(Ordering::Greater) 103 | } 104 | _ => Some(Ordering::Greater), 105 | }, 106 | _ => match other { 107 | Opt::GT | Opt::GE | Opt::LT | Opt::LE | Opt::EQ | Opt::NQ => Some(Ordering::Less), 108 | Opt::OT | Opt::AT => Some(Ordering::Less), 109 | _ => Some(Ordering::Equal), 110 | }, 111 | }; 112 | } 113 | } 114 | 115 | impl Calc { 116 | //去掉注释 117 | pub(crate) fn parse_remove_annotation(mut s: String) -> anyhow::Result { 118 | let mut postion = vec![]; 119 | let ss = s.as_bytes(); 120 | let mut i = 0; 121 | while i < s.len() - 1 { 122 | if ss[i] == '/' as u8 && ss[i + 1] == '*' as u8 { 123 | //左边界 124 | if postion.len() % 2 != 0 { 125 | return anyhow::anyhow!("'/*''*/'mismatch").err(); 126 | } 127 | postion.push(i); 128 | i += 1; 129 | } else if ss[i] == '*' as u8 && ss[i + 1] == '/' as u8 { 130 | //右边界 131 | if postion.len() % 2 != 1 { 132 | return anyhow::anyhow!("'/*''*/'mismatch").err(); 133 | } 134 | postion.push(i + 1); 135 | i += 1; 136 | } 137 | i += 1; 138 | } 139 | if postion.len() <= 0 { 140 | return Ok(s); 141 | } 142 | if postion.len() % 2 != 0 { 143 | return anyhow::anyhow!("'/*''*/'mismatch").err(); 144 | } 145 | let mut i = postion.len() as i32 - 1; 146 | while i > 0 { 147 | s.replace_range(postion[i as usize - 1]..postion[i as usize] + 1, ""); 148 | i -= 2 149 | } 150 | return Ok(s); 151 | } 152 | //拆分 153 | pub(crate) fn expression_split(mut expr: String) -> anyhow::Result> { 154 | let mut deq = VecDeque::new(); 155 | 'lp: while !expr.is_empty() { 156 | if expr.starts_with("\n") { 157 | expr = expr.split_off(1); 158 | } else if expr.starts_with("\r") { 159 | expr = expr.split_off(1); 160 | } else if expr.starts_with("\t") { 161 | expr = expr.split_off(1); 162 | } else if expr.starts_with(",") { 163 | deq.push_back(Element::Comma); 164 | expr = expr.split_off(1); 165 | } else if expr.starts_with(" ") { 166 | expr = expr.split_off(1); 167 | } else if expr.starts_with("(") { 168 | deq.push_back(Element::LeftSmall); 169 | expr = expr.split_off(1); 170 | } else if expr.starts_with(")") { 171 | deq.push_back(Element::RightSmall); 172 | expr = expr.split_off(1); 173 | } else if expr.starts_with("[") { 174 | deq.push_back(Element::LeftMed); 175 | expr = expr.split_off(1); 176 | } else if expr.starts_with("]") { 177 | deq.push_back(Element::RightMed); 178 | expr = expr.split_off(1); 179 | } else if expr.starts_with("{") { 180 | deq.push_back(Element::LeftBig); 181 | expr = expr.split_off(1); 182 | } else if expr.starts_with("}") { 183 | deq.push_back(Element::RightBig); 184 | expr = expr.split_off(1); 185 | } else if expr.starts_with("'") { 186 | //字符串 187 | let mut index = 0; 188 | for (i, e) in expr.as_bytes().into_iter().enumerate() { 189 | if i != 0 && *e == '\'' as u8 { 190 | index = i; 191 | break; 192 | } 193 | } 194 | let mut e = expr.split_off(index + 1); 195 | unsafe { 196 | std::ptr::swap(&mut e, &mut expr); 197 | } 198 | let e = e.trim_matches(|c| c == '\''); 199 | deq.push_back(Element::CALC(Calc::String(e.to_string()))); 200 | } else if expr.starts_with("/*") { 201 | //注释 202 | let mut index = 0; 203 | let bs = expr.as_bytes(); 204 | while index < bs.len() - 1 { 205 | if bs[index] == b'*' && bs[index + 1] == b'/' { 206 | expr = expr.split_off(index + 2); 207 | continue 'lp; 208 | } 209 | index += 1; 210 | } 211 | return anyhow!("/* and */ mismatch").err(); 212 | } else if Opt::parse_one(&mut expr, &mut deq) { 213 | //运算符 214 | } else { 215 | let mut chars = expr.chars(); 216 | let char = match chars.next() { 217 | None => return anyhow!("parse error over").err(), 218 | Some(s) => s, 219 | }; 220 | if char.is_ascii_digit() { 221 | //数字 222 | let mut i = 1; 223 | let mut not_is_float = true; 224 | for e in chars.into_iter() { 225 | if e == '.' { 226 | not_is_float = false; 227 | } else if !e.is_ascii_digit() { 228 | break; 229 | } 230 | i += 1; 231 | } 232 | let mut e = expr.split_off(i); 233 | unsafe { 234 | std::ptr::swap(&mut e, &mut expr); 235 | } 236 | if not_is_float { 237 | let i = i64::from_str(e.as_str())?; 238 | deq.push_back(Element::CALC(Calc::Number(i))); 239 | } else { 240 | let i = f64::from_str(e.as_str())?; 241 | deq.push_back(Element::CALC(Calc::Float(i))); 242 | } 243 | } else if char == '_' { 244 | //保留的部分 245 | return anyhow!("Content beginning with the character _ is reserved").err(); 246 | } else if char.is_alphabetic() { 247 | //变量 或者函数 或者数组 248 | let mut i = 1; 249 | let mut ty = 1; //1:变量 2:函数 250 | for e in chars.into_iter() { 251 | if e == '(' { 252 | //函数 253 | ty = 2; 254 | break; 255 | } else if e.is_ascii_digit() 256 | || e.is_alphabetic() 257 | || e == '_' 258 | || e == '_' 259 | || e == '.' 260 | { 261 | } else { 262 | break; 263 | } 264 | i += 1 265 | } 266 | let mut e = expr.split_off(i); 267 | unsafe { 268 | std::ptr::swap(&mut e, &mut expr); 269 | } 270 | if e == "true" || e == "false" { 271 | deq.push_back(Element::CALC(Calc::Bool( 272 | bool::from_str(e.as_str()).unwrap(), 273 | ))); 274 | } else if e.to_lowercase() == "null" || e.to_lowercase() == "nil" { 275 | deq.push_back(Element::CALC(Calc::NULL)); 276 | } else { 277 | match ty { 278 | 1 => deq.push_back(Element::CALC(Calc::Field(e))), 279 | 2 => deq.push_back(Element::CALC(Calc::Function(e, vec![]))), 280 | _ => return anyhow!("unknown string({e})").err(), 281 | } 282 | } 283 | } else { 284 | return anyhow!("unknown char({char})").err(); 285 | } 286 | } 287 | } 288 | return Ok(deq); 289 | } 290 | //组合 291 | 292 | // 取一个单元算子 293 | pub(crate) fn convert_one_group_calc( 294 | calc: Option, 295 | deq: &mut VecDeque, 296 | ) -> anyhow::Result { 297 | if deq.is_empty() { 298 | return if let Some(s) = calc { 299 | Ok(s) 300 | } else { 301 | anyhow!("expression is null").err() 302 | // Calc::NULL.ok() 303 | }; 304 | }; 305 | let ele = deq.pop_front().unwrap(); 306 | match ele { 307 | Element::OPT(opt) => { 308 | match opt { 309 | Opt::ADD 310 | | Opt::MUL 311 | | Opt::DIV 312 | | Opt::REM 313 | | Opt::AND 314 | | Opt::OR 315 | | Opt::XOR 316 | | Opt::SHL 317 | | Opt::SHR => { 318 | //向右边取一个算子单元 319 | let lc = if let Some(s) = calc { 320 | s 321 | } else { 322 | return anyhow!( 323 | "parsing the left side of the operation[{opt:?}] failed" 324 | ) 325 | .err(); 326 | }; 327 | let mut r_deq = Self::split_deque_by_opt(deq, &opt)?; 328 | let rc = Self::convert_one_group_calc(None, &mut r_deq)?; 329 | return Self::convert_one_group_calc( 330 | Some(Calc::Operator(opt, vec![lc, rc])), 331 | deq, 332 | ); 333 | } 334 | Opt::SUB | Opt::NOT => { 335 | // - 336 | //向右边取一个算子单元 337 | let mut r_deq = Self::split_deque_by_opt(deq, &opt)?; 338 | let rc = Self::convert_one_group_calc(None, &mut r_deq)?; 339 | return match calc { 340 | None => Self::convert_one_group_calc( 341 | Some(Calc::Operator(opt, vec![rc])), 342 | deq, 343 | ), 344 | Some(lc) => Self::convert_one_group_calc( 345 | Some(Calc::Operator(opt, vec![lc, rc])), 346 | deq, 347 | ), 348 | }; 349 | } 350 | Opt::REV => { 351 | // ~ 352 | if let Some(_) = calc { 353 | return anyhow!( 354 | "operation[{opt:?}] cannot perform multi-value calculations" 355 | ) 356 | .err(); 357 | }; 358 | let mut r_deq = Self::split_deque_by_opt(deq, &opt)?; 359 | let rc = Self::convert_one_group_calc(None, &mut r_deq)?; 360 | return Self::convert_one_group_calc( 361 | Some(Calc::Operator(opt, vec![rc])), 362 | deq, 363 | ); 364 | } 365 | Opt::GT 366 | | Opt::GE 367 | | Opt::LT 368 | | Opt::LE 369 | | Opt::EQ 370 | | Opt::NQ 371 | | Opt::AT 372 | | Opt::OT => { 373 | let lc = if let Some(s) = calc { 374 | s 375 | } else { 376 | return anyhow!( 377 | "parsing the left side of the operation[{opt:?}] failed" 378 | ) 379 | .err(); 380 | }; 381 | let mut r_deq = Self::split_deque_by_opt(deq, &opt)?; 382 | let rc = Self::convert_one_group_calc(None, &mut r_deq)?; 383 | return Self::convert_one_group_calc( 384 | Some(Calc::Operator(opt, vec![lc, rc])), 385 | deq, 386 | ); 387 | } 388 | } 389 | } 390 | Element::CALC(ec) => { 391 | return match ec { 392 | Calc::NULL 393 | | Calc::Field(_) 394 | | Calc::String(_) 395 | | Calc::Number(_) 396 | | Calc::Float(_) 397 | | Calc::Bool(_) 398 | | Calc::Operator(_, _) 399 | | Calc::Array(_) => Self::convert_one_group_calc(Some(ec), deq), 400 | Calc::Function(name, mut args) => { 401 | if calc.is_some() { 402 | return anyhow!("function[{name}] parse failed").err(); 403 | } 404 | if let Some(Element::LeftSmall) = deq.pop_front() { 405 | } else { 406 | return anyhow!("function[{name}] right must is '('").err(); 407 | } 408 | let list = Self::split_deque_by_comma(deq, Element::RightSmall)?; 409 | for mut i in list { 410 | let c = Self::convert_one_group_calc(None, &mut i)?; 411 | args.push(c); 412 | } 413 | Self::convert_one_group_calc(Some(Calc::Function(name, args)), deq) 414 | } 415 | } 416 | } 417 | Element::LeftSmall => { 418 | if calc.is_some() { 419 | return anyhow!("'(' and ')' must bilateral symmetry").err(); 420 | } 421 | // 拆分出一个单元节点 422 | let mut l_deq = 423 | Self::split_deque_by_ele(deq, Element::LeftSmall, Element::RightSmall)?; 424 | let lc = Self::convert_one_group_calc(None, &mut l_deq)?; 425 | return Self::convert_one_group_calc(Some(lc), deq); 426 | } 427 | Element::RightSmall => return anyhow!("')' must match a '('").err(), 428 | Element::LeftMed => { 429 | //数组 430 | if calc.is_some() { 431 | return anyhow!("'[' and ']' mut bilateral symmetry").err(); 432 | } 433 | let list = Self::split_deque_by_comma(deq, Element::RightMed)?; 434 | let mut array = vec![]; 435 | for mut i in list { 436 | let c = Self::convert_one_group_calc(None, &mut i)?; 437 | array.push(c); 438 | } 439 | return Self::convert_one_group_calc(Some(Calc::Array(array)), deq); 440 | } 441 | Element::RightMed => return anyhow!("']' must match a '['").err(), 442 | Element::LeftBig => return anyhow!("reserved character '{{'").err(), 443 | Element::RightBig => return anyhow!("reserved character '}}'").err(), 444 | Element::Comma => { 445 | return anyhow!("You should use multiple operators instead of multiple expressions") 446 | .err() 447 | } 448 | } 449 | // return Ok(Calc::NULL) 450 | } 451 | pub(crate) fn split_deque_by_comma( 452 | deq: &mut VecDeque, 453 | le: Element, 454 | ) -> anyhow::Result>> { 455 | let mut sub_deq = VecDeque::new(); 456 | let mut deq_list = vec![]; 457 | let (mut count_small, mut count_med) = match le { 458 | Element::RightSmall => (1, 0), 459 | Element::RightMed => (0, 1), 460 | _ => return anyhow!("Calc.split_deque_by_comma nonsupport {:?}", le).err(), 461 | }; 462 | while let Some(e) = deq.pop_front() { 463 | let comma = match le { 464 | Element::RightSmall => e == Element::Comma && count_med == 0 && count_small == 1, 465 | Element::RightMed => e == Element::Comma && count_small == 0 && count_med == 1, 466 | _ => panic!(""), 467 | }; 468 | if comma { 469 | deq_list.push(sub_deq.clone()); 470 | sub_deq.clear(); 471 | continue; 472 | } 473 | if Element::LeftSmall == e { 474 | count_small += 1; 475 | } else if Element::RightSmall == e { 476 | count_small -= 1; 477 | } else if Element::LeftMed == e { 478 | count_med += 1; 479 | } else if Element::RightMed == e { 480 | count_med -= 1; 481 | } 482 | if le == e && count_small == 0 && count_med == 0 { 483 | break; 484 | } 485 | sub_deq.push_back(e); 486 | } 487 | if !sub_deq.is_empty() { 488 | deq_list.push(sub_deq.clone()); 489 | } 490 | return Ok(deq_list); 491 | } 492 | pub(crate) fn split_deque_by_opt( 493 | deq: &mut VecDeque, 494 | opt: &Opt, 495 | ) -> anyhow::Result> { 496 | let mut sub_deq = VecDeque::new(); 497 | let mut count = 0i32; 498 | while let Some(e) = deq.pop_front() { 499 | if let Element::OPT(o) = e.clone() { 500 | if !(&o < opt) && count == 0 { 501 | deq.push_front(Element::OPT(o)); 502 | break; 503 | } 504 | } else if Element::LeftSmall == e { 505 | count += 1; 506 | } else if Element::RightSmall == e { 507 | count -= 1; 508 | } 509 | sub_deq.push_back(e); 510 | } 511 | return Ok(sub_deq); 512 | } 513 | pub(crate) fn split_deque_by_ele( 514 | deq: &mut VecDeque, 515 | le: Element, 516 | re: Element, 517 | ) -> anyhow::Result> { 518 | let mut sub_deq = VecDeque::new(); 519 | let mut count = 1i32; 520 | while let Some(e) = deq.pop_front() { 521 | if e == le { 522 | count += 1; 523 | } else if e == re { 524 | count -= 1; 525 | } 526 | if count == 0 { 527 | return Ok(sub_deq); 528 | } 529 | sub_deq.push_back(e); 530 | } 531 | return anyhow!("element[{le:?} {re:?}] mismatch").err(); 532 | } 533 | 534 | #[allow(dead_code)] 535 | pub(crate) fn expression_parse(expr: String) -> anyhow::Result { 536 | let mut deq = Self::expression_split(expr)?; 537 | 538 | print!("expression_split -->"); 539 | for i in deq.iter() { 540 | print!("_{}_", i.to_string()); 541 | } 542 | println!(" <--- over"); 543 | 544 | Self::convert_one_group_calc(None, &mut deq) 545 | } 546 | } 547 | 548 | #[cfg(test)] 549 | mod test { 550 | use crate::Calc; 551 | 552 | //cargo test --color=always --lib calc_parse::test::test_parse_remove_annotation --no-fail-fast -- --exact unstable-options --nocapture 553 | #[test] 554 | fn test_parse_remove_annotation() { 555 | let data = "/*start*/he/*\n*/llo /**/wo/**/rld/* *//*end*/".into(); 556 | let s = Calc::parse_remove_annotation(data).unwrap(); 557 | assert_eq!(s, "hello world"); 558 | } 559 | //cargo test --color=always --lib calc_parse::test::test_parse_remove_annotation_error --no-fail-fast -- --exact unstable-options --nocapture 560 | #[test] 561 | #[should_panic] 562 | fn test_parse_remove_annotation_error() { 563 | let data = "/*/".into(); 564 | Calc::parse_remove_annotation(data).unwrap(); 565 | } 566 | 567 | //cargo test --color=always --lib calc_parse::test::test_parse_split --no-fail-fast -- --exact unstable-options --nocapture 568 | #[test] 569 | fn test_parse_split() { 570 | let data = "/*注释1*/ a+b/*注释2*/> c ||\ 571 | b &/*注释3*/& c > ab * ( strlen( a ,b , c) ) /*注释4*/ \n\ 572 | || (\"asdd\" > 10 < a * 0.12) || s < [1,2,3]"; 573 | let deq = Calc::expression_split(data.to_string()).unwrap(); 574 | println!("test expression --> {data}"); 575 | print!("split result -->"); 576 | for i in deq.iter() { 577 | print!("_{}_", i.to_string()); 578 | } 579 | println!(" <--- over") 580 | } 581 | 582 | //cargo test --color=always --lib calc_parse::test::test_expression_parse --no-fail-fast -- --exact unstable-options --nocapture 583 | #[test] 584 | fn test_expression_parse() { 585 | let expr = "a > b || c <= d || eft > strlen(\"hello\",\"world\",2) || true || ~x >> y > -z || (n && m < n*m) || in(a,[1>2,3,\"123\"])"; 586 | let calc = Calc::expression_parse(expr.into()).unwrap(); 587 | println!("--->{}", calc.to_string()); 588 | // println!(" '~' < '||' {}",Opt::REV > Opt::OT); 589 | } 590 | 591 | #[test] 592 | fn test_full_parse() { 593 | let expr = r#"(args1 != "hello world" 594 | || time("2023-01-02") > time(args2)) 595 | && (in(args3,[1,2,3,4,"helle","world"]) 596 | || 3.14 > args4 >> 2 || !args5 || false )"#; 597 | let calc = Calc::expression_parse(expr.into()).unwrap(); 598 | println!("--->{}", calc.to_string()); 599 | } 600 | } 601 | --------------------------------------------------------------------------------