diff --git a/2 b/2 new file mode 100644 index 0000000..34e912e --- /dev/null +++ b/2 @@ -0,0 +1,335 @@ +extern crate flow; +use flow::eval; +use flow::ExpressionEvaluator; +use serde_json::json; +use std::f64::consts; + +#[test] +fn test_eval_function() { + let context = json!({ + "x": 10, + "y": 5 + }); + + assert_eq!(eval("x + y", &context), json!(15)); + assert_eq!(eval("x * y", &context), json!(50)); + assert_eq!(eval("x - y", &context), json!(5)); + assert_eq!(eval("x / y", &context), json!(2)); +} + +#[test] +fn test_expression_evaluator_error_handling() { + let context = json!({}); + let mut evaluator = ExpressionEvaluator::new(&context); + + let result = evaluator.eval("nonexistent.field"); + assert!( + result.is_null() || result.is_string(), + "Expected null or error string for nonexistent field" + ); + + let result = evaluator.eval("1 +"); + assert!( + result.is_null() || result.is_string(), + "Expected null or error string for invalid syntax" + ); +} + +#[test] +#[should_panic(expected = "Division by zero")] +fn test_expression_evaluator_division_by_zero() { + let context = json!({}); + let mut evaluator = ExpressionEvaluator::new(&context); + // panic + assert_eq!(evaluator.eval("1 / 0"), ""); +} + +#[test] +fn test_expression_evaluator() { + let context = json!({ + "person": { + "name": "Alice", + "age": 30 + }, + "constants": { + "pi": consts::PI + } + }); + + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("person.name"), json!("Alice")); + assert_eq!(evaluator.eval("person.age + 5"), json!(35)); + assert_eq!(evaluator.eval("constants.pi * 2"), json!(consts::TAU)); +} + +#[test] +fn test_number_functions() { + let context = json!({}); + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("abs(-1.23)"), json!(1.23)); + assert!(evaluator.eval("rand(100)").as_u64().unwrap() <= 100); + assert_eq!(evaluator.eval("floor(5.9)"), json!(5)); + assert_eq!(evaluator.eval("round(5.5)"), json!(6)); + assert_eq!(evaluator.eval("ceil(5.1)"), json!(6)); + assert_eq!(evaluator.eval("number('20')"), json!(20)); + assert_eq!(evaluator.eval("isNumeric('20')"), json!(true)); + assert_eq!(evaluator.eval("isNumeric('test')"), json!(false)); +} + +#[test] +fn test_string_functions() { + let context = json!({}); + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("len('string')"), json!(6)); + assert_eq!(evaluator.eval("upper('string')"), json!("STRING")); + assert_eq!(evaluator.eval("lower('StrInG')"), json!("string")); + assert_eq!( + evaluator.eval("startsWith('Saturday night plans', 'Sat')"), + json!(true) + ); + assert_eq!( + evaluator.eval("endsWith('Saturday night plans', 'plans')"), + json!(true) + ); + assert_eq!( + evaluator.eval("contains('Saturday night plans', 'night')"), + json!(true) + ); + assert_eq!(evaluator.eval("matches('12345', '^\\d+$')"), json!(true)); + assert_eq!( + evaluator.eval("extract('2022-02-01', '(\\d{4})-(\\d{2})-(\\d{2})')"), + json!(["2022-02-01", "2022", "02", "01"]) + ); +} + +#[test] +fn test_complex_expressions() { + let context = json!({ + "x": 10, + "y": 5, + "name": "John", + "price": 120 + }); + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("100 + 100"), json!(200)); + assert_eq!(evaluator.eval("10 * 5"), json!(50)); + assert_eq!(evaluator.eval("10 ^ 2"), json!(100)); + assert_eq!(evaluator.eval("1 + 2 + 3"), json!(6)); + + assert_eq!(evaluator.eval("x + y"), json!(15)); + assert_eq!(evaluator.eval("x * y"), json!(50)); + + assert_eq!( + evaluator.eval("\"hello\" + \" \" + \"world\""), + json!("hello world") + ); + assert_eq!(evaluator.eval("len(\"world\")"), json!(5)); + assert_eq!(evaluator.eval("upper(\"john\")"), json!("JOHN")); + assert_eq!(evaluator.eval("lower(\"HELLO\")"), json!("hello")); + + assert_eq!(evaluator.eval("x > y"), json!(true)); + assert_eq!(evaluator.eval("x <= 10"), json!(true)); + + assert_eq!(evaluator.eval("true and false"), json!(false)); + assert_eq!(evaluator.eval("true or false"), json!(true)); + assert_eq!(evaluator.eval("not false"), json!(true)); + + assert_eq!(evaluator.eval("5 > 3"), json!(true)); + assert_eq!(evaluator.eval("10 <= 10"), json!(true)); + assert_eq!(evaluator.eval("\"abc\" == \"abc\""), json!(true)); + + assert_eq!(evaluator.eval("true and false"), json!(false)); + assert_eq!(evaluator.eval("true or false"), json!(true)); + assert_eq!(evaluator.eval("not false"), json!(true)); + + assert_eq!(evaluator.eval("abs(-5)"), json!(5)); + assert_eq!(evaluator.eval("round(3.7)"), json!(4)); + assert_eq!(evaluator.eval("floor(3.7)"), json!(3)); + assert_eq!(evaluator.eval("ceil(3.2)"), json!(4)); + + // Ternary + assert_eq!( + evaluator.eval("price > 100 ? 'premium' : 'value'"), + json!("premium") + ); +} + +#[test] +fn test_unary_expressions() { + let context = json!({ + "$": 5, + "currency": "USD" + }); + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("$ == 5"), json!(true)); + assert_eq!(evaluator.eval("$ >= 1"), json!(true)); + assert_eq!(evaluator.eval("$ < 1"), json!(false)); + assert_eq!(evaluator.eval("$ >= 0 and $ <= 10"), json!(true)); + assert_eq!(evaluator.eval("$ > 0 and $ < 10"), json!(true)); + + let context = json!({ "$": "USD" }); + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("$ == 'USD'"), json!(true)); + assert_eq!(evaluator.eval("$ == 'EUR'"), json!(false)); + assert_eq!(evaluator.eval("startsWith($, \"US\")"), json!(true)); + assert_eq!(evaluator.eval("endsWith($, \"D\")"), json!(true)); + assert_eq!(evaluator.eval("lower($) == \"usd\""), json!(true)); +} + +#[test] +fn test_boolean_operations() { + let context = json!({ + "a": true, + "b": false, + "x": 10, + "y": 5 + }); + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("a and b"), json!(false)); + assert_eq!(evaluator.eval("a or b"), json!(true)); + assert_eq!(evaluator.eval("!a"), json!(false)); + assert_eq!(evaluator.eval("not(b)"), json!(true)); + + assert_eq!(evaluator.eval("a == true"), json!(true)); + assert_eq!(evaluator.eval("b != true"), json!(true)); + + assert_eq!(evaluator.eval("(x > y) and (y < 10)"), json!(true)); + assert_eq!(evaluator.eval("(x < y) or (x == 10)"), json!(true)); + + assert_eq!(evaluator.eval("bool('true')"), json!(true)); + assert_eq!(evaluator.eval("bool('false')"), json!(false)); +} + +#[test] +fn test_datatime_operations() { + let context = json!({}); + + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("date('2024-01-01')"), json!(1704067200)); + + assert_eq!(evaluator.eval("weekOfYear('2024-09-18')"), json!(38)); + + assert_eq!( + evaluator.eval("date('2024-04-04T21:48:30Z')"), + json!(1712267310) + ); + + assert_eq!(evaluator.eval("year('2024-09-18')"), json!(2024)); + // + assert_eq!(evaluator.eval("time('21:49')"), json!(78540)); + + assert_eq!(evaluator.eval("time('21:48:20')"), json!(78500)); + + assert_eq!(evaluator.eval("duration('30m')"), json!(1800)); + assert_eq!(evaluator.eval("duration('72h')"), json!(259200)); + + assert_eq!(evaluator.eval("dayOfMonth(date('2022-11-09'))"), json!(9)); + + assert_eq!(evaluator.eval("dayOfYear(date('2022-11-10'))"), json!(314)); + + assert_eq!( + evaluator.eval("weekdayString(date('2022-11-14'))"), + json!("Mon") + ); + + // https://github.com/gorules/zen/pull/91/files#diff-0351bd90a8ac5af79cbd69797085dae5d37782fb4359797dd72a801485f133a2R207 + assert_eq!( + evaluator.eval("dateString(startOf('2024-01-01 00:00:00', 'day'))"), + json!("2024-01-01 00:00:00") + ); +} + +#[test] +fn test_array_operations() { + let mut context = json!({ + "code": 0, + "data": { + "array": [ + 1, + 8, + 9, + 10, + 30 + ], + "map": { + "subMap": { + "status": 1 + } + } + } + }); + //https://github.com/gorules/zen/blob/17b468f50755f448a298c60ff2af8eb1856fd0bb/core/expression/tests/data/standard.csv + let mut eveluator = ExpressionEvaluator::new(&context); + assert_eq!(eveluator.eval("len([1, 2, 3, 4, 5])"), json!(5)); + assert_eq!(eveluator.eval("sum([1, 2, 3, 4, 5])"), json!(15)); + assert_eq!( + eveluator.eval("map([1, 2, 3, 4, 5], # ^ 2)"), + json!([1, 4, 9, 16, 25]) + ); + + assert_eq!( + eveluator.eval("filter([1, 2, 3, 4, 5], # > 3)"), + json!([4, 5]) + ); + + assert_eq!(eval("filter(data.array,# > 9)", &context), json!([10, 30])); + assert_eq!(eval("data.map.subMap.status > 0", &context), json!(true)); + + let context = json!([ + { + "id": "1", + "price": [ + { + "type": 1, + "val": 2300 + }, + { + "type": 2, + "val": 1300 + }, + { + "type": 3, + "val": 4211 + } + ], + "type": 1 + }, + { + "id": "2", + "price": [ + { + "type": 1, + "val": 2300 + }, + { + "type": 2, + "val": 1300 + }, + { + "type": 3, + "val": 4211 + } + ], + "type": 2 + } + ]); + assert_eq!(eval("len([*])", &context), json!(2)); + // assert_eq!( + // eval("$[0].price[?(@.type == 3)].val", &context), + // json!(4211) + // ); + // assert_eq!( + // eval("$[*].price[?(@.type == 3)].val", &context), + // json!([4211, 4211]) + // ); + // assert_eq!(eval("$[0].id", &context), json!("1")); + // assert_eq!(eval("$[1].type", &context), json!(2)); +} diff --git a/Cargo.lock b/Cargo.lock index c39b284..a207d34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,17 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.11" @@ -35,6 +46,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -155,6 +167,17 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + [[package]] name = "async-trait" version = "0.1.81" @@ -254,6 +277,18 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block" version = "0.1.6" @@ -269,6 +304,30 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "borsh-derive", + "cfg_aliases 0.2.1", +] + +[[package]] +name = "borsh-derive" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +dependencies = [ + "once_cell", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.72", + "syn_derive", +] + [[package]] name = "bstr" version = "1.9.1" @@ -332,7 +391,7 @@ dependencies = [ "getrandom", "indicatif", "rand", - "reqwest", + "reqwest 0.11.27", "serde", "spin", "tokio", @@ -410,8 +469,8 @@ dependencies = [ "serde", "serde_json", "serde_rusqlite", - "strum", - "strum_macros", + "strum 0.25.0", + "strum_macros 0.25.3", "tempfile", "thiserror", ] @@ -519,11 +578,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a0014ee82ef967bd82dda378cfaf340f255c39c729e29ac3bc65d3107e4c7ee" dependencies = [ "burn-core", - "crossterm", + "crossterm 0.27.0", "derive-new", "log", "nvml-wrapper", - "ratatui", + "ratatui 0.25.0", "serde", "sysinfo", "systemstat", @@ -552,6 +611,28 @@ dependencies = [ "wgpu", ] +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "bytemuck" version = "1.16.1" @@ -638,6 +719,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.1.6" @@ -660,6 +750,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.38" @@ -731,8 +827,10 @@ dependencies = [ "burn", "clap", "core", + "crossterm 0.28.1", "plotters", "rand", + "ratatui 0.28.1", ] [[package]] @@ -788,6 +886,29 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "compact_str" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console" version = "0.15.8" @@ -807,6 +928,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core" version = "0.1.0" @@ -939,6 +1069,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.6.0", + "crossterm_winapi", + "mio 1.0.1", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + [[package]] name = "crossterm_winapi" version = "0.9.1" @@ -1219,6 +1365,27 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "exr" version = "1.72.0" @@ -1271,6 +1438,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.30" @@ -1287,6 +1460,21 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" +[[package]] +name = "flow" +version = "0.14.0" +dependencies = [ + "clap", + "csv", + "serde", + "serde_json", + "tempfile", + "thiserror", + "tokio", + "zen-engine", + "zen-expression", +] + [[package]] name = "flume" version = "0.11.0" @@ -1389,6 +1577,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures-channel" version = "0.3.30" @@ -1777,7 +1971,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", "indexmap", "slab", "tokio", @@ -1800,13 +1994,22 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + [[package]] name = "hashbrown" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash", + "ahash 0.8.11", ] [[package]] @@ -1815,7 +2018,7 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash", + "ahash 0.8.11", "allocator-api2", "serde", ] @@ -1888,6 +2091,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -1895,7 +2109,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -1911,6 +2148,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.30" @@ -1922,8 +2165,8 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -1935,6 +2178,43 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1942,12 +2222,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.30", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.4.1", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -2059,6 +2359,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instability" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" +dependencies = [ + "quote", + "syn 2.0.72", +] + [[package]] name = "instant" version = "0.1.13" @@ -2089,6 +2399,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -2128,6 +2447,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json_dotpath" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbdcfef3cf5591f0cef62da413ae795e3d1f5a00936ccec0b2071499a32efd1a" +dependencies = [ + "serde", + "serde_derive", + "serde_json", + "thiserror", +] + [[package]] name = "khronos-egl" version = "6.0.0" @@ -2342,6 +2673,7 @@ checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ "hermit-abi", "libc", + "log", "wasi", "windows-sys 0.52.0", ] @@ -2359,7 +2691,7 @@ dependencies = [ "indexmap", "log", "num-traits", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "termcolor", "thiserror", @@ -2406,6 +2738,12 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -2608,6 +2946,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -2686,19 +3030,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "pin-project-lite" -version = "0.2.14" +name = "petgraph" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pin-project" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] [[package]] -name = "pkg-config" +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" @@ -2819,6 +3193,49 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit 0.22.20", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -2838,6 +3255,26 @@ checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" name = "providers" version = "0.1.0" +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "pulp" version = "0.18.21" @@ -2859,6 +3296,54 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "quinn" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.0.0", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash 2.0.0", + "rustls", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.36" @@ -2890,6 +3375,12 @@ dependencies = [ "uuid", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -2944,18 +3435,39 @@ checksum = "a5659e52e4ba6e07b2dad9f1158f578ef84a73762625ddb51536019f34d180eb" dependencies = [ "bitflags 2.6.0", "cassowary", - "crossterm", + "crossterm 0.27.0", "indoc", - "itertools", + "itertools 0.12.1", "lru", "paste", "stability", - "strum", + "strum 0.25.0", "time", "unicode-segmentation", "unicode-width", ] +[[package]] +name = "ratatui" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" +dependencies = [ + "bitflags 2.6.0", + "cassowary", + "compact_str", + "crossterm 0.28.1", + "instability", + "itertools 0.13.0", + "lru", + "paste", + "strum 0.26.3", + "strum_macros 0.26.4", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + [[package]] name = "raw-cpuid" version = "10.7.0" @@ -3052,6 +3564,21 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -3070,9 +3597,9 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", "hyper-tls", "ipnet", "js-sys", @@ -3082,11 +3609,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-native-tls", @@ -3098,6 +3625,48 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pemfile 2.1.3", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "windows-registry", +] + [[package]] name = "ring" version = "0.17.8" @@ -3113,6 +3682,35 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rmp" version = "0.8.14" @@ -3135,6 +3733,56 @@ dependencies = [ "serde", ] +[[package]] +name = "rquickjs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cbd33e0b668aea0ab238b9164523aca929096f9f40834700d71d91dd4888882" +dependencies = [ + "either", + "rquickjs-core", + "rquickjs-macro", +] + +[[package]] +name = "rquickjs-core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9129d69b7b8f7ee8ad1da5b12c7f4a8a8acd45f2e6dd9cb2ee1bc5a1f2fa3d" +dependencies = [ + "async-lock", + "either", + "relative-path", + "rquickjs-sys", +] + +[[package]] +name = "rquickjs-macro" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d2ecaf7c9eda262e02a91e9541989a9dd18984d17d0d97f99f33b464318057" +dependencies = [ + "convert_case", + "fnv", + "ident_case", + "indexmap", + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "rquickjs-core", + "syn 2.0.72", +] + +[[package]] +name = "rquickjs-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6f2288d8e7fbb5130f62cf720451641e99d55f6fde9db86aa2914ecb553fd2" +dependencies = [ + "cc", +] + [[package]] name = "rusqlite" version = "0.30.0" @@ -3149,6 +3797,32 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rust_decimal" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rust_decimal_macros" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da991f231869f34268415a49724c6578e740ad697ba0999199d6f22b3949332c" +dependencies = [ + "quote", + "rust_decimal", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3161,6 +3835,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc_version" version = "0.4.0" @@ -3207,6 +3887,16 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.7.0" @@ -3299,6 +3989,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "security-framework" version = "2.11.1" @@ -3430,12 +4126,13 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", "mio 0.8.11", + "mio 1.0.1", "signal-hook", ] @@ -3454,6 +4151,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "slab" version = "0.4.9" @@ -3540,7 +4243,16 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" dependencies = [ - "strum_macros", + "strum_macros 0.25.3", +] + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", ] [[package]] @@ -3556,6 +4268,19 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.72", +] + [[package]] name = "subtle" version = "2.6.1" @@ -3584,12 +4309,33 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "sync_wrapper" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -3665,6 +4411,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tch" version = "0.15.0" @@ -3828,7 +4580,9 @@ dependencies = [ "bytes", "libc", "mio 1.0.1", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -3855,6 +4609,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.11" @@ -3868,6 +4633,34 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.6.18", +] + [[package]] name = "torch-sys" version = "0.15.0" @@ -3883,6 +4676,27 @@ dependencies = [ "zip", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.2" @@ -3991,6 +4805,17 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "unicode-width" version = "0.1.13" @@ -4215,7 +5040,7 @@ checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" dependencies = [ "arrayvec", "cfg-if", - "cfg_aliases", + "cfg_aliases 0.1.1", "js-sys", "log", "naga", @@ -4241,7 +5066,7 @@ dependencies = [ "arrayvec", "bit-vec", "bitflags 2.6.0", - "cfg_aliases", + "cfg_aliases 0.1.1", "codespan-reporting", "indexmap", "log", @@ -4250,7 +5075,7 @@ dependencies = [ "parking_lot", "profiling", "raw-window-handle", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror", "web-sys", @@ -4270,7 +5095,7 @@ dependencies = [ "bit-set", "bitflags 2.6.0", "block", - "cfg_aliases", + "cfg_aliases 0.1.1", "core-graphics-types", "d3d12", "glow", @@ -4294,7 +5119,7 @@ dependencies = [ "range-alloc", "raw-window-handle", "renderdoc-sys", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror", "wasm-bindgen", @@ -4370,6 +5195,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4509,6 +5364,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -4540,6 +5413,15 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "xml-rs" version = "0.8.20" @@ -4582,6 +5464,65 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zen-engine" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77398961eaa1c530e48705e47970c317bf7cbff04b7825c788c2882707301567" +dependencies = [ + "anyhow", + "fixedbitset", + "itertools 0.13.0", + "json_dotpath", + "once_cell", + "petgraph", + "reqwest 0.12.7", + "rquickjs", + "serde", + "serde_json", + "thiserror", + "tokio", + "zen-expression", + "zen-tmpl", +] + +[[package]] +name = "zen-expression" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed2b313d6c8099cf795234364255d3c9bd3fcf89e5cfea3cd729f74528cff5e" +dependencies = [ + "ahash 0.8.11", + "bumpalo", + "chrono", + "fastrand", + "humantime", + "nohash-hasher", + "once_cell", + "regex", + "rust_decimal", + "rust_decimal_macros", + "serde", + "serde_json", + "strsim", + "strum 0.26.3", + "strum_macros 0.26.4", + "thiserror", +] + +[[package]] +name = "zen-tmpl" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f79c0c77977c10209b21770f0c7707feda74239cc076445c8f3c336450489d1" +dependencies = [ + "itertools 0.13.0", + "serde", + "serde_json", + "thiserror", + "zen-expression", +] + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index ae70bd2..a9e3b3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [workspace] resolver = "2" -members = ["core", "bench", "cli", "providers"] +members = ["core", "bench", "cli", "providers", "flow"] [workspace.package] edition = "2021" diff --git a/flow/Cargo.toml b/flow/Cargo.toml new file mode 100644 index 0000000..8db4f0c --- /dev/null +++ b/flow/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "flow" +edition.workspace = true +version.workspace = true +readme.workspace = true +license.workspace = true + + +[lib] +name = "flow" +path = "src/lib.rs" + + +[dependencies] +tokio = { version = "1.0", features = ["full"] } +clap = { version = "4.0", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "^1.0" +thiserror = "1.0.63" +zen-engine = "0.26.0" +zen-expression = "0.26.0" +csv = "1.1" + +[dev-dependencies] +tokio = { version = "1.0", features = ["full", "test-util"] } +tempfile = "3.2" diff --git a/flow/src/graph.rs b/flow/src/graph.rs new file mode 100644 index 0000000..e9da453 --- /dev/null +++ b/flow/src/graph.rs @@ -0,0 +1,188 @@ +use zen_engine::model::{ + DecisionContent, DecisionEdge, DecisionNode, DecisionNodeKind, DecisionTableContent, + DecisionTableHitPolicy, DecisionTableInputField, DecisionTableOutputField, Expression, + ExpressionNodeContent, FunctionNodeContent, +}; + +use crate::rule::Decision; +use std::collections::HashMap; + +trait NodeBuilder { + fn build(&self, decision: Decision) -> DecisionNode; +} + +pub struct DecisionTableNodeBuilder; +impl NodeBuilder for DecisionTableNodeBuilder { + fn build(&self, decision: Decision) -> DecisionNode { + let Decision { + id, + rules, + inputs, + outputs, + .. + } = decision; + let input_fields = make_fields(inputs, |f| DecisionTableInputField { + id: f.clone(), + name: f.clone(), + field: Some(f.clone()), + }); + let output_fields = make_fields(outputs, |f| DecisionTableOutputField { + id: f.clone(), + name: f.clone(), + field: f.clone(), + }); + let content = DecisionTableContent { + hit_policy: DecisionTableHitPolicy::First, + rules, + inputs: input_fields, + outputs: output_fields, + }; + DecisionNode { + id: id.clone(), + name: id, + kind: DecisionNodeKind::DecisionTableNode { content }, + } + } +} + +pub struct ExpressionNodeBuilder; +impl NodeBuilder for ExpressionNodeBuilder { + fn build(&self, decision: Decision) -> DecisionNode { + let Decision { + id, + expression, + inputs, + .. + } = decision; + let key = inputs.first().unwrap().to_string(); + let expression = Expression { + id: key.clone(), + key, + value: expression.to_string(), + }; + let content = ExpressionNodeContent { + expressions: vec![expression], + }; + DecisionNode { + id: id.clone(), + name: id, + kind: DecisionNodeKind::ExpressionNode { content }, + } + } +} + +pub struct FunctionNodeBuilder; +impl NodeBuilder for FunctionNodeBuilder { + fn build(&self, decision: Decision) -> DecisionNode { + let Decision { id, function, .. } = decision; + DecisionNode { + id: id.clone(), + name: id, + kind: DecisionNodeKind::FunctionNode { + content: FunctionNodeContent::Version1(function), + }, + } + } +} + +// Node factory +pub struct NodeFactory { + builders: HashMap>, +} + +impl NodeFactory { + fn new() -> Self { + let mut builders = HashMap::new(); + builders.insert( + "table".to_string(), + Box::new(DecisionTableNodeBuilder) as Box, + ); + builders.insert( + "expression".to_string(), + Box::new(ExpressionNodeBuilder) as Box, + ); + builders.insert( + "function".to_string(), + Box::new(FunctionNodeBuilder) as Box, + ); + Self { builders } + } + + fn create_node(&self, decision: Decision) -> DecisionNode { + self.builders + .get(&decision.kind) + .expect(&format!("Unsupported decision kind: {}", decision.kind)) + .build(decision) + } +} + +fn make_fields(fields: Vec, field_creator: F) -> Vec +where + F: Fn(String) -> T, +{ + fields.into_iter().map(field_creator).collect() +} + +pub struct EdgeBuilder; + +impl EdgeBuilder { + fn build_edges(flow: &[Decision]) -> Vec { + flow.iter() + .flat_map(|d| { + d.sources + .iter() + .map(|source| DecisionEdge { + id: "".into(), + source_id: source.clone(), + target_id: d.id.clone(), + source_handle: Some("".into()), + }) + .chain(d.targets.iter().map(|target| DecisionEdge { + id: "".into(), + source_id: d.id.clone(), + target_id: target.clone(), + source_handle: Some("".into()), + })) + }) + .collect() + } +} + +pub struct DecisionGraphBuilder { + node_factory: NodeFactory, +} + +impl DecisionGraphBuilder { + pub fn new() -> Self { + Self { + node_factory: NodeFactory::new(), + } + } + + pub fn build(&self, flow: Vec) -> DecisionContent { + let mut nodes = vec![DecisionNode { + id: "request".to_string(), + name: "request".to_string(), + kind: DecisionNodeKind::InputNode, + }]; + + nodes.extend( + flow.iter() + .map(|d| self.node_factory.create_node(d.clone())), + ); + + nodes.push(DecisionNode { + id: "response".to_string(), + name: "response".to_string(), + kind: DecisionNodeKind::OutputNode, + }); + + let edges = EdgeBuilder::build_edges(&flow); + + DecisionContent { nodes, edges } + } +} + +pub async fn build(flow: Vec) -> DecisionContent { + DecisionGraphBuilder::new().build(flow) +} diff --git a/flow/src/lib.rs b/flow/src/lib.rs new file mode 100644 index 0000000..88ec891 --- /dev/null +++ b/flow/src/lib.rs @@ -0,0 +1,31 @@ +pub mod graph; +pub mod rule; + +use zen_expression::{evaluate_expression, Isolate}; +use serde_json::Value; + +pub fn eval(expr: &str, data: &Value) -> Value { + match evaluate_expression(expr, data) { + Ok(result) => result, + Err(error) => Value::String(error.to_string()), + } +} + +pub struct ExpressionEvaluator<'a> { + isolate: Isolate<'a>, +} + +impl<'a> ExpressionEvaluator<'a> { + pub fn new(context: &'a Value) -> Self { + Self { + isolate: Isolate::with_environment(context), + } + } + + pub fn eval(&mut self, expr: &'a str) -> Value { + match self.isolate.run_standard(expr) { + Ok(result) => result, + Err(error) => Value::String(error.to_string()), + } + } +} diff --git a/flow/src/rule.rs b/flow/src/rule.rs new file mode 100644 index 0000000..7b2d473 --- /dev/null +++ b/flow/src/rule.rs @@ -0,0 +1,149 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use std::fs::File; +use std::io::Read; +use std::path::Path; +use std::path::PathBuf; +use thiserror::Error; + +#[derive(Debug, Serialize, Deserialize)] +pub struct DecisionRef { + pub id: String, + pub kind: String, + pub rules: String, + pub inputs: Option>, + pub outputs: Option>, + pub sources: Vec, + pub targets: Vec, +} + +pub type Rule = HashMap; + +#[derive(Debug, Clone)] +pub struct Decision { + pub id: String, + pub kind: String, + pub rules: Vec, + pub expression: String, + pub function: String, + pub inputs: Vec, + pub outputs: Vec, + pub sources: Vec, + pub targets: Vec, +} + +#[derive(Error, Debug)] +pub enum ReaderError { + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + #[error("JSON error: {0}")] + Json(#[from] serde_json::Error), + #[error("CSV error: {0}")] + Csv(#[from] csv::Error), + #[error("Unknown file extension: {0}")] + UnknownExtension(String), + #[error("Invalid or missing variable: {0}")] + InvalidVariable(String), +} + +impl From for Decision { + fn from(dec_ref: DecisionRef) -> Self { + let DecisionRef { + id, + kind, + rules, + inputs, + outputs, + sources, + targets, + } = dec_ref; + + let rules_table = RulesReader::read_rules(&rules).unwrap_or_default(); + let function_content = if kind == "function" { + std::fs::read_to_string(&rules).unwrap_or_default() + } else { + String::new() + }; + + Decision { + id, + kind, + rules: rules_table, + expression: rules, + function: function_content, + inputs: inputs.unwrap_or_default(), + outputs: outputs.unwrap_or_default(), + sources, + targets, + } + } +} + +pub struct RulesReader; + +impl RulesReader { + pub fn read_rules(path: &str) -> Result, ReaderError> { + let path = PathBuf::from(path); + let extension = path + .extension() + .and_then(std::ffi::OsStr::to_str) + .ok_or_else(|| ReaderError::UnknownExtension(path.to_string_lossy().to_string()))?; + + match extension { + "json" => Self::read_rules_json(&path), + "csv" => Self::read_rules_csv(&path), + _ => Err(ReaderError::UnknownExtension(extension.to_string())), + } + } + + fn read_rules_json(path: &Path) -> Result, ReaderError> { + let mut file = File::open(path)?; + let mut data = String::new(); + file.read_to_string(&mut data)?; + let rules: Vec = serde_json::from_str(&data)?; + Ok(rules) + } + + fn read_rules_csv(path: &Path) -> Result, ReaderError> { + let file = File::open(path)?; + let mut rdr = csv::Reader::from_reader(file); + let rules: Result, csv::Error> = rdr.deserialize().collect(); + Ok(rules?) + } +} + +pub struct DecisionReader; + +impl DecisionReader { + pub async fn read_flow>(path: P) -> Result, ReaderError> { + let mut file = File::open(path)?; + let mut data = String::new(); + file.read_to_string(&mut data)?; + let decision_refs: Vec = serde_json::from_str(&data)?; + Ok(decision_refs.into_iter().map(Decision::from).collect()) + } + + pub async fn read_input>(path: P) -> Result { + let mut file = File::open(path)?; + let mut data = String::new(); + file.read_to_string(&mut data)?; + Ok(serde_json::from_str(&data)?) + } + + pub async fn read_str(data: &str) -> Result { + Ok(serde_json::from_str(data)?) + } +} + +pub async fn read_flow>(path: P) -> Vec { + DecisionReader::read_flow(path).await.unwrap_or_default() +} + +pub async fn read_input>(path: P) -> Value { + DecisionReader::read_input(path).await.unwrap_or_default() +} + +pub async fn read_str(data: &str) -> Value { + DecisionReader::read_str(data).await.unwrap_or_default() +} diff --git a/flow/tests/lib_tests.rs b/flow/tests/lib_tests.rs new file mode 100644 index 0000000..7b68d96 --- /dev/null +++ b/flow/tests/lib_tests.rs @@ -0,0 +1,353 @@ +extern crate flow; +use flow::eval; +use flow::ExpressionEvaluator; +use serde_json::json; +use std::f64::consts; + +#[test] +fn test_eval_function() { + let context = json!({ + "x": 10, + "y": 5 + }); + + assert_eq!(eval("x + y", &context), json!(15)); + assert_eq!(eval("x * y", &context), json!(50)); + assert_eq!(eval("x - y", &context), json!(5)); + assert_eq!(eval("x / y", &context), json!(2)); +} + +#[test] +fn test_expression_evaluator_error_handling() { + let context = json!({}); + let mut evaluator = ExpressionEvaluator::new(&context); + + let result = evaluator.eval("nonexistent.field"); + assert!( + result.is_null() || result.is_string(), + "Expected null or error string for nonexistent field" + ); + + let result = evaluator.eval("1 +"); + assert!( + result.is_null() || result.is_string(), + "Expected null or error string for invalid syntax" + ); +} + +#[test] +#[should_panic(expected = "Division by zero")] +fn test_expression_evaluator_division_by_zero() { + let context = json!({}); + let mut evaluator = ExpressionEvaluator::new(&context); + // panic + assert_eq!(evaluator.eval("1 / 0"), ""); +} + +#[test] +fn test_expression_evaluator() { + let context = json!({ + "person": { + "name": "Alice", + "age": 30 + }, + "constants": { + "pi": consts::PI + } + }); + + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("person.name"), json!("Alice")); + assert_eq!(evaluator.eval("person.age + 5"), json!(35)); + assert_eq!(evaluator.eval("constants.pi * 2"), json!(consts::TAU)); +} + +#[test] +fn test_number_functions() { + let context = json!({}); + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("abs(-1.23)"), json!(1.23)); + assert!(evaluator.eval("rand(100)").as_u64().unwrap() <= 100); + assert_eq!(evaluator.eval("floor(5.9)"), json!(5)); + assert_eq!(evaluator.eval("round(5.5)"), json!(6)); + assert_eq!(evaluator.eval("ceil(5.1)"), json!(6)); + assert_eq!(evaluator.eval("number('20')"), json!(20)); + assert_eq!(evaluator.eval("isNumeric('20')"), json!(true)); + assert_eq!(evaluator.eval("isNumeric('test')"), json!(false)); +} + +#[test] +fn test_string_functions() { + let context = json!({}); + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("len('string')"), json!(6)); + assert_eq!(evaluator.eval("upper('string')"), json!("STRING")); + assert_eq!(evaluator.eval("lower('StrInG')"), json!("string")); + assert_eq!( + evaluator.eval("startsWith('Saturday night plans', 'Sat')"), + json!(true) + ); + assert_eq!( + evaluator.eval("endsWith('Saturday night plans', 'plans')"), + json!(true) + ); + assert_eq!( + evaluator.eval("contains('Saturday night plans', 'night')"), + json!(true) + ); + assert_eq!(evaluator.eval("matches('12345', '^\\d+$')"), json!(true)); + assert_eq!( + evaluator.eval("extract('2022-02-01', '(\\d{4})-(\\d{2})-(\\d{2})')"), + json!(["2022-02-01", "2022", "02", "01"]) + ); +} + +#[test] +fn test_complex_expressions() { + let context = json!({ + "x": 10, + "y": 5, + "name": "John", + "price": 120 + }); + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("100 + 100"), json!(200)); + assert_eq!(evaluator.eval("10 * 5"), json!(50)); + assert_eq!(evaluator.eval("10 ^ 2"), json!(100)); + assert_eq!(evaluator.eval("1 + 2 + 3"), json!(6)); + + assert_eq!(evaluator.eval("x + y"), json!(15)); + assert_eq!(evaluator.eval("x * y"), json!(50)); + + assert_eq!( + evaluator.eval("\"hello\" + \" \" + \"world\""), + json!("hello world") + ); + assert_eq!(evaluator.eval("len(\"world\")"), json!(5)); + assert_eq!(evaluator.eval("upper(\"john\")"), json!("JOHN")); + assert_eq!(evaluator.eval("lower(\"HELLO\")"), json!("hello")); + + assert_eq!(evaluator.eval("x > y"), json!(true)); + assert_eq!(evaluator.eval("x <= 10"), json!(true)); + + assert_eq!(evaluator.eval("true and false"), json!(false)); + assert_eq!(evaluator.eval("true or false"), json!(true)); + assert_eq!(evaluator.eval("not false"), json!(true)); + + assert_eq!(evaluator.eval("5 > 3"), json!(true)); + assert_eq!(evaluator.eval("10 <= 10"), json!(true)); + assert_eq!(evaluator.eval("\"abc\" == \"abc\""), json!(true)); + + assert_eq!(evaluator.eval("true and false"), json!(false)); + assert_eq!(evaluator.eval("true or false"), json!(true)); + assert_eq!(evaluator.eval("not false"), json!(true)); + + assert_eq!(evaluator.eval("abs(-5)"), json!(5)); + assert_eq!(evaluator.eval("round(3.7)"), json!(4)); + assert_eq!(evaluator.eval("floor(3.7)"), json!(3)); + assert_eq!(evaluator.eval("ceil(3.2)"), json!(4)); + + // Ternary + assert_eq!( + evaluator.eval("price > 100 ? 'premium' : 'value'"), + json!("premium") + ); +} + +#[test] +fn test_unary_expressions() { + let context = json!({ + "$": 5, + "currency": "USD" + }); + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("$ == 5"), json!(true)); + assert_eq!(evaluator.eval("$ >= 1"), json!(true)); + assert_eq!(evaluator.eval("$ < 1"), json!(false)); + assert_eq!(evaluator.eval("$ >= 0 and $ <= 10"), json!(true)); + assert_eq!(evaluator.eval("$ > 0 and $ < 10"), json!(true)); + + let context = json!({ "$": "USD" }); + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("$ == 'USD'"), json!(true)); + assert_eq!(evaluator.eval("$ == 'EUR'"), json!(false)); + assert_eq!(evaluator.eval("startsWith($, \"US\")"), json!(true)); + assert_eq!(evaluator.eval("endsWith($, \"D\")"), json!(true)); + assert_eq!(evaluator.eval("lower($) == \"usd\""), json!(true)); +} + +#[test] +fn test_boolean_operations() { + let context = json!({ + "a": true, + "b": false, + "x": 10, + "y": 5 + }); + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("a and b"), json!(false)); + assert_eq!(evaluator.eval("a or b"), json!(true)); + assert_eq!(evaluator.eval("!a"), json!(false)); + assert_eq!(evaluator.eval("not(b)"), json!(true)); + + assert_eq!(evaluator.eval("a == true"), json!(true)); + assert_eq!(evaluator.eval("b != true"), json!(true)); + + assert_eq!(evaluator.eval("(x > y) and (y < 10)"), json!(true)); + assert_eq!(evaluator.eval("(x < y) or (x == 10)"), json!(true)); + + assert_eq!(evaluator.eval("bool('true')"), json!(true)); + assert_eq!(evaluator.eval("bool('false')"), json!(false)); +} + +#[test] +fn test_datatime_operations() { + let context = json!({}); + + let mut evaluator = ExpressionEvaluator::new(&context); + + assert_eq!(evaluator.eval("date('2024-01-01')"), json!(1704067200)); + + assert_eq!(evaluator.eval("weekOfYear('2024-09-18')"), json!(38)); + + assert_eq!( + evaluator.eval("date('2024-04-04T21:48:30Z')"), + json!(1712267310) + ); + + assert_eq!(evaluator.eval("year('2024-09-18')"), json!(2024)); + // + assert_eq!(evaluator.eval("time('21:49')"), json!(78540)); + + assert_eq!(evaluator.eval("time('21:48:20')"), json!(78500)); + + assert_eq!(evaluator.eval("duration('30m')"), json!(1800)); + assert_eq!(evaluator.eval("duration('72h')"), json!(259200)); + + assert_eq!(evaluator.eval("dayOfMonth(date('2022-11-09'))"), json!(9)); + + assert_eq!(evaluator.eval("dayOfYear(date('2022-11-10'))"), json!(314)); + + assert_eq!( + evaluator.eval("weekdayString(date('2022-11-14'))"), + json!("Mon") + ); + + // https://github.com/gorules/zen/pull/91/files#diff-0351bd90a8ac5af79cbd69797085dae5d37782fb4359797dd72a801485f133a2R207 + assert_eq!( + evaluator.eval("dateString(startOf('2024-01-01 00:00:00', 'day'))"), + json!("2024-01-01 00:00:00") + ); +} + +#[test] +fn test_array_operations() { + let context = json!({ + "code": 0, + "data": { + "array": [ + 1, + 8, + 9, + 10, + 30 + ], + "map": { + "subMap": { + "status": 1 + } + } + } + }); + //https://github.com/gorules/zen/blob/17b468f50755f448a298c60ff2af8eb1856fd0bb/core/expression/tests/data/standard.csv + let mut eveluator = ExpressionEvaluator::new(&context); + assert_eq!(eveluator.eval("len([1, 2, 3, 4, 5])"), json!(5)); + assert_eq!(eveluator.eval("sum([1, 2, 3, 4, 5])"), json!(15)); + assert_eq!( + eveluator.eval("map([1, 2, 3, 4, 5], # ^ 2)"), + json!([1, 4, 9, 16, 25]) + ); + + assert_eq!( + eveluator.eval("filter([0.13,1, 2, 3, 4, 5], # > 3)"), + json!([4, 5]) + ); + + assert_eq!(eval("filter(data.array,# > 9)", &context), json!([10, 30])); + assert_eq!(eval("data.map.subMap.status > 0", &context), json!(true)); + + let context = json!([ + { + "id": "1", + "price": [ + { + "type": 0, + "val": 420_222_111 + }, + { + "type": 1, + "val": 2300 + }, + { + "type": 2, + "val": 1300 + }, + { + "type": 3, + "val": 4211 + }, + ], + "type": 1 + }, + { + "id": "2", + "price": [ + { + "type": 1, + "val": 2300 + }, + { + "type": 2, + "val": 1300 + }, + { + "type": 3, + "val": 4211 + } + ], + "type": 2 + } + ]); + let expr = r#" + map( + filter($root, #.type == 1 or #.type == 2), + { + id: #.id, + type1_val: filter(#.price, #.type == 1)[0].val, + type2_val: filter(#.price, #.type == 2)[0].val + } + ) + "#; + + let expected = json!([ + { + "id": "1", + "type1_val": 2300, + "type2_val": 1300 + }, + { + "id": "2", + "type1_val": 2300, + "type2_val": 1300 + } + ]); + + assert_eq!(eval(expr, &context), expected); +} diff --git a/flow/tests/rule_tests.rs b/flow/tests/rule_tests.rs new file mode 100644 index 0000000..662957c --- /dev/null +++ b/flow/tests/rule_tests.rs @@ -0,0 +1,51 @@ +extern crate flow; +use tempfile::NamedTempFile; +use std::io::Write; +use flow::rule::{read_flow, read_input, read_str, RulesReader, DecisionReader}; + +fn create_temp_file(content: &str, extension: &str) -> NamedTempFile { + let mut file = NamedTempFile::new().unwrap(); + writeln!(file, "{}", content).unwrap(); + let path = file.path().to_owned(); + let new_path = path.with_extension(extension); + std::fs::rename(&path, &new_path).unwrap(); + file +} + +#[tokio::test] +async fn test_read_rules_json() { +} + +#[tokio::test] +async fn test_read_rules_csv() { + +} + +#[tokio::test] +async fn test_read_flow() { + +} + +#[tokio::test] +async fn test_read_input() { +} + +#[tokio::test] +async fn test_read_str() { + let content = r#"{"key": "value"}"#; + let value = read_str(content).await; + + assert_eq!(value["key"], "value"); +} + +#[tokio::test] +async fn test_error_handling() { + let result = DecisionReader::read_flow("non_existent_file.json").await; + assert!(result.is_err()); + + let result = DecisionReader::read_input("non_existent_file.json").await; + assert!(result.is_err()); + + let result = DecisionReader::read_str("invalid json").await; + assert!(result.is_err()); +}