more efforts to automate rhai bindings
This commit is contained in:
242
rhai_wrapper/Cargo.lock
generated
242
rhai_wrapper/Cargo.lock
generated
@@ -16,6 +16,21 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
@@ -28,12 +43,42 @@ version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
@@ -54,6 +99,12 @@ dependencies = [
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
@@ -83,6 +134,30 @@ dependencies = [
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
@@ -92,12 +167,28 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -187,10 +278,44 @@ dependencies = [
|
||||
name = "rhai_wrapper"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"rhai",
|
||||
"rhai_macros_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
@@ -267,6 +392,123 @@ dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
|
@@ -6,7 +6,17 @@ edition = "2021"
|
||||
description = "A wrapper to make generic Rust functions Rhai-compatible."
|
||||
|
||||
[dependencies]
|
||||
rhai = "1.21.0"
|
||||
rhai = "1.18.0"
|
||||
rhai_macros_derive = { path = "../rhai_macros_derive" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
[[example]]
|
||||
name = "user_management_example"
|
||||
path = "examples/user_management_example.rs"
|
||||
|
||||
[[example]]
|
||||
name = "rust_rhai_wrapper_example"
|
||||
path = "examples/rust_rhai_wrapper_example.rs"
|
||||
|
176
rhai_wrapper/examples/rust_rhai_wrapper_example.rs
Normal file
176
rhai_wrapper/examples/rust_rhai_wrapper_example.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
use rhai::Engine;
|
||||
use rhai_wrapper::rust_rhai_wrapper;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
// Define a custom error type for Result examples
|
||||
#[derive(Debug, Clone)]
|
||||
struct MyError(String);
|
||||
|
||||
impl fmt::Display for MyError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for MyError {}
|
||||
|
||||
impl From<String> for MyError {
|
||||
fn from(s: String) -> Self {
|
||||
MyError(s)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Create a Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// 1. Basic example: Add two numbers
|
||||
// Define the original Rust function
|
||||
fn add(a: i32, b: i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
|
||||
// Register it with Rhai
|
||||
engine.register_fn("add_rhai", add);
|
||||
|
||||
// Create a wrapper function that calls Rhai which calls the Rust function
|
||||
rust_rhai_wrapper!(add_via_rhai, "add_rhai", (i32, i32) -> i32);
|
||||
|
||||
// Test the full circle
|
||||
let result = add_via_rhai(&mut engine, 5, 3);
|
||||
println!("add_via_rhai(5, 3) = {}", result);
|
||||
|
||||
// 2. String manipulation example
|
||||
fn concat(s1: String, s2: String) -> String {
|
||||
format!("{} {}", s1, s2)
|
||||
}
|
||||
|
||||
engine.register_fn("concat_rhai", concat);
|
||||
rust_rhai_wrapper!(concat_via_rhai, "concat_rhai", (String, String) -> String);
|
||||
|
||||
let result = concat_via_rhai(&mut engine, "Hello".to_string(), "World".to_string());
|
||||
println!("concat_via_rhai(\"Hello\", \"World\") = {}", result);
|
||||
|
||||
// 3. Function with no arguments
|
||||
fn get_random() -> i32 {
|
||||
42 // Not so random, but it's just an example
|
||||
}
|
||||
|
||||
engine.register_fn("get_random_rhai", get_random);
|
||||
rust_rhai_wrapper!(get_random_via_rhai, "get_random_rhai", () -> i32);
|
||||
|
||||
let result = get_random_via_rhai(&mut engine);
|
||||
println!("get_random_via_rhai() = {}", result);
|
||||
|
||||
// 4. Function with more arguments
|
||||
fn calculate(a: i32, b: i32, c: i32, d: i32) -> i32 {
|
||||
a + b * c - d
|
||||
}
|
||||
|
||||
engine.register_fn("calculate_rhai", calculate);
|
||||
rust_rhai_wrapper!(calculate_via_rhai, "calculate_rhai", (i32, i32, i32, i32) -> i32);
|
||||
|
||||
let result = calculate_via_rhai(&mut engine, 5, 3, 2, 1);
|
||||
println!("calculate_via_rhai(5, 3, 2, 1) = {}", result);
|
||||
|
||||
// 5. Function that handles errors with a custom return type
|
||||
fn divide(a: i32, b: i32) -> Result<i32, String> {
|
||||
if b == 0 {
|
||||
Err("Division by zero".to_string())
|
||||
} else {
|
||||
Ok(a / b)
|
||||
}
|
||||
}
|
||||
|
||||
// Register a safe division function that returns an array with success flag and result/error
|
||||
engine.register_fn("safe_divide", |a: i32, b: i32| -> rhai::Array {
|
||||
if b == 0 {
|
||||
// Return [false, error message]
|
||||
let mut arr = rhai::Array::new();
|
||||
arr.push(rhai::Dynamic::from(false));
|
||||
arr.push(rhai::Dynamic::from("Division by zero"));
|
||||
arr
|
||||
} else {
|
||||
// Return [true, result]
|
||||
let mut arr = rhai::Array::new();
|
||||
arr.push(rhai::Dynamic::from(true));
|
||||
arr.push(rhai::Dynamic::from(a / b));
|
||||
arr
|
||||
}
|
||||
});
|
||||
|
||||
// Create a wrapper for the safe_divide function
|
||||
rust_rhai_wrapper!(safe_divide_via_rhai, "safe_divide", (i32, i32) -> rhai::Array);
|
||||
|
||||
// Test success case
|
||||
let result = safe_divide_via_rhai(&mut engine, 10, 2);
|
||||
println!("safe_divide_via_rhai(10, 2) = {:?}", result);
|
||||
|
||||
// Test error case
|
||||
let result = safe_divide_via_rhai(&mut engine, 10, 0);
|
||||
println!("safe_divide_via_rhai(10, 0) = {:?}", result);
|
||||
|
||||
// Process the result
|
||||
let success = result[0].as_bool().unwrap();
|
||||
if success {
|
||||
println!("Division result: {}", result[1].as_int().unwrap());
|
||||
} else {
|
||||
println!("Division error: {}", result[1].clone().into_string().unwrap());
|
||||
}
|
||||
|
||||
// 6. Complex example: Using a custom type with Rhai
|
||||
#[derive(Debug, Clone)]
|
||||
struct Person {
|
||||
name: String,
|
||||
age: i32,
|
||||
}
|
||||
|
||||
// Register type and methods with Rhai
|
||||
engine.register_type::<Person>();
|
||||
engine.register_fn("new_person", |name: String, age: i32| -> Person {
|
||||
Person { name, age }
|
||||
});
|
||||
engine.register_fn("get_name", |p: &mut Person| -> String {
|
||||
p.name.clone()
|
||||
});
|
||||
engine.register_fn("get_age", |p: &mut Person| -> i32 {
|
||||
p.age
|
||||
});
|
||||
engine.register_fn("is_adult", |p: &mut Person| -> bool {
|
||||
p.age >= 18
|
||||
});
|
||||
|
||||
// Register a function that creates a person and checks if they're an adult
|
||||
engine.register_fn("create_and_check_person", |name: String, age: i32| -> rhai::Array {
|
||||
let person = Person { name, age };
|
||||
let is_adult = person.age >= 18;
|
||||
|
||||
// Create an array with the person and the is_adult flag
|
||||
let mut arr = rhai::Array::new();
|
||||
|
||||
// Convert the person to a map for Rhai
|
||||
let mut map = rhai::Map::new();
|
||||
map.insert("name".into(), rhai::Dynamic::from(person.name));
|
||||
map.insert("age".into(), rhai::Dynamic::from(person.age));
|
||||
|
||||
arr.push(rhai::Dynamic::from(map));
|
||||
arr.push(rhai::Dynamic::from(is_adult));
|
||||
arr
|
||||
});
|
||||
|
||||
// Create a wrapper for the Rhai function
|
||||
rust_rhai_wrapper!(create_person_via_rhai, "create_and_check_person", (String, i32) -> rhai::Array);
|
||||
|
||||
// Test the wrapper
|
||||
let result = create_person_via_rhai(&mut engine, "Alice".to_string(), 25);
|
||||
println!("create_person_via_rhai(\"Alice\", 25) = {:?}", result);
|
||||
|
||||
// Extract data from the Rhai array
|
||||
let person_map = result[0].clone().try_cast::<rhai::Map>().expect("Expected a Map");
|
||||
let is_adult = result[1].clone().as_bool().expect("Expected a boolean");
|
||||
|
||||
println!("Person: {:?}, Is Adult: {}", person_map, is_adult);
|
||||
|
||||
println!("All examples completed successfully!");
|
||||
}
|
312
rhai_wrapper/examples/user_management_example.rs
Normal file
312
rhai_wrapper/examples/user_management_example.rs
Normal file
@@ -0,0 +1,312 @@
|
||||
use rhai::{Engine, INT, CustomType, TypeBuilder};
|
||||
use rhai_wrapper::{wrap_option_return, wrap_vec_return};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::path::Path;
|
||||
use chrono;
|
||||
|
||||
// --- Mock heromodels_core ---
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
|
||||
pub struct BaseModelData {
|
||||
pub id: u32,
|
||||
pub created_at: i64, // Using i64 for timestamp simplicity
|
||||
pub updated_at: i64,
|
||||
pub comment_ids: Vec<u32>,
|
||||
}
|
||||
|
||||
impl BaseModelData {
|
||||
pub fn new(id: u32) -> Self {
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
Self {
|
||||
id,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
comment_ids: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// No &mut self for getters if struct is Clone and passed by value in Rhai, or if Rhai handles it.
|
||||
// For CustomType, Rhai typically passes &mut T to getters/setters.
|
||||
pub fn get_id(&mut self) -> u32 { self.id }
|
||||
pub fn get_created_at(&mut self) -> i64 { self.created_at }
|
||||
pub fn get_updated_at(&mut self) -> i64 { self.updated_at }
|
||||
pub fn get_comment_ids(&mut self) -> Vec<u32> { self.comment_ids.clone() }
|
||||
|
||||
pub fn add_comment_internal(&mut self, comment_id: u32) { // Renamed to avoid clash if also exposed
|
||||
self.comment_ids.push(comment_id);
|
||||
self.updated_at = chrono::Utc::now().timestamp();
|
||||
}
|
||||
}
|
||||
|
||||
// --- User Struct and Methods (Adapted for Rhai) ---
|
||||
|
||||
/// Represents a user in the system
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
|
||||
pub struct User {
|
||||
/// Base model data
|
||||
pub base_data: BaseModelData,
|
||||
|
||||
/// User's username
|
||||
pub username: String,
|
||||
|
||||
/// User's email address
|
||||
pub email: String,
|
||||
|
||||
/// User's full name
|
||||
pub full_name: String,
|
||||
|
||||
/// Whether the user is active
|
||||
pub is_active: bool,
|
||||
}
|
||||
|
||||
impl User {
|
||||
// This is the "builder" entry point
|
||||
pub fn user_builder(id: INT) -> Self {
|
||||
Self {
|
||||
base_data: BaseModelData::new(id as u32),
|
||||
username: String::new(),
|
||||
email: String::new(),
|
||||
full_name: String::new(),
|
||||
is_active: true, // Default, can be changed by .is_active(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Fluent setters returning Self
|
||||
pub fn username(mut self, username: String) -> Self {
|
||||
self.username = username;
|
||||
self.base_data.updated_at = chrono::Utc::now().timestamp();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn email(mut self, email: String) -> Self {
|
||||
self.email = email;
|
||||
self.base_data.updated_at = chrono::Utc::now().timestamp();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn full_name(mut self, full_name: String) -> Self {
|
||||
self.full_name = full_name;
|
||||
self.base_data.updated_at = chrono::Utc::now().timestamp();
|
||||
self
|
||||
}
|
||||
|
||||
// Naming this 'set_is_active' to distinguish from potential getter 'is_active'
|
||||
// or the script can use direct field access if setter is also registered for 'is_active'
|
||||
// For fluent chain .is_active(bool_val)
|
||||
pub fn is_active(mut self, active_status: bool) -> Self {
|
||||
self.is_active = active_status;
|
||||
self.base_data.updated_at = chrono::Utc::now().timestamp();
|
||||
self
|
||||
}
|
||||
|
||||
// Method to add a comment, distinct from direct field manipulation
|
||||
pub fn add_comment(mut self, comment_id: INT) -> Self {
|
||||
self.base_data.add_comment_internal(comment_id as u32);
|
||||
self
|
||||
}
|
||||
|
||||
// Explicit activate/deactivate methods returning Self for chaining if needed
|
||||
pub fn activate(mut self) -> Self {
|
||||
self.is_active = true;
|
||||
self.base_data.updated_at = chrono::Utc::now().timestamp();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn deactivate(mut self) -> Self {
|
||||
self.is_active = false;
|
||||
self.base_data.updated_at = chrono::Utc::now().timestamp();
|
||||
self
|
||||
}
|
||||
|
||||
// Getters for direct field access from Rhai: register with .register_get()
|
||||
// Rhai passes &mut User to these
|
||||
pub fn get_id_rhai(&mut self) -> INT { self.base_data.id as INT }
|
||||
pub fn get_username_rhai(&mut self) -> String { self.username.clone() }
|
||||
pub fn get_email_rhai(&mut self) -> String { self.email.clone() }
|
||||
pub fn get_full_name_rhai(&mut self) -> String { self.full_name.clone() }
|
||||
pub fn get_is_active_rhai(&mut self) -> bool { self.is_active }
|
||||
pub fn get_comment_ids_rhai(&mut self) -> Vec<u32> { self.base_data.comment_ids.clone() }
|
||||
}
|
||||
|
||||
|
||||
// --- Comment Struct and Methods (Adapted for Rhai) ---
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
|
||||
pub struct Comment {
|
||||
pub id: INT,
|
||||
pub user_id: INT, // Assuming comments are linked to users by ID
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
impl Comment {
|
||||
pub fn comment_builder(id: INT) -> Self {
|
||||
Self {
|
||||
id,
|
||||
user_id: 0, // Default
|
||||
content: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Fluent setters
|
||||
pub fn user_id(mut self, user_id: INT) -> Self {
|
||||
self.user_id = user_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn content(mut self, content: String) -> Self {
|
||||
self.content = content;
|
||||
self
|
||||
}
|
||||
|
||||
// Getters for Rhai
|
||||
pub fn get_id_rhai(&mut self) -> INT { self.id }
|
||||
pub fn get_user_id_rhai(&mut self) -> INT { self.user_id }
|
||||
pub fn get_content_rhai(&mut self) -> String { self.content.clone() }
|
||||
}
|
||||
|
||||
// --- Mock Database ---
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct DbState {
|
||||
users: HashMap<INT, User>,
|
||||
comments: HashMap<INT, Comment>,
|
||||
}
|
||||
|
||||
type OurDB = Arc<Mutex<DbState>>;
|
||||
|
||||
fn set_user(db_arc: OurDB, user: User) {
|
||||
let mut db = db_arc.lock().unwrap();
|
||||
db.users.insert(user.base_data.id as INT, user);
|
||||
}
|
||||
|
||||
fn get_user_by_id(db_arc: OurDB, id: INT) -> Option<User> {
|
||||
let db = db_arc.lock().unwrap();
|
||||
db.users.get(&id).cloned()
|
||||
}
|
||||
|
||||
fn get_all_users(db_arc: OurDB) -> Vec<User> {
|
||||
let db = db_arc.lock().unwrap();
|
||||
db.users.values().cloned().collect()
|
||||
}
|
||||
|
||||
fn delete_user_by_id(db_arc: OurDB, id: INT) {
|
||||
let mut db = db_arc.lock().unwrap();
|
||||
db.users.remove(&id);
|
||||
}
|
||||
|
||||
fn set_comment(db_arc: OurDB, comment: Comment) {
|
||||
let mut db = db_arc.lock().unwrap();
|
||||
db.comments.insert(comment.id, comment);
|
||||
}
|
||||
|
||||
fn get_comment_by_id(db_arc: OurDB, id: INT) -> Option<Comment> {
|
||||
let db = db_arc.lock().unwrap();
|
||||
db.comments.get(&id).cloned()
|
||||
}
|
||||
|
||||
fn get_users_by_activity_status_optional(db_arc: OurDB, is_active_filter: bool) -> Option<Vec<User>> {
|
||||
let db = db_arc.lock().unwrap();
|
||||
let users: Vec<User> = db.users.values()
|
||||
.filter(|u| u.is_active == is_active_filter)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
if users.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(users)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
let db_instance: OurDB = Arc::new(Mutex::new(DbState::default()));
|
||||
|
||||
// Register User type and its methods/getters/setters
|
||||
engine
|
||||
.register_type_with_name::<User>("User")
|
||||
// Fluent methods - these are registered as functions that take User and return User
|
||||
.register_fn("user_builder", User::user_builder)
|
||||
.register_fn("username", User::username)
|
||||
.register_fn("email", User::email)
|
||||
.register_fn("full_name", User::full_name)
|
||||
.register_fn("is_active", User::is_active) // This is the fluent setter
|
||||
.register_fn("add_comment", User::add_comment)
|
||||
.register_fn("activate", User::activate)
|
||||
.register_fn("deactivate", User::deactivate)
|
||||
// Getters for direct field-like access: user.id, user.username etc.
|
||||
.register_get("id", User::get_id_rhai)
|
||||
.register_get("username", User::get_username_rhai)
|
||||
.register_get("email", User::get_email_rhai)
|
||||
.register_get("full_name", User::get_full_name_rhai)
|
||||
.register_get("is_active", User::get_is_active_rhai) // This is the getter for direct field access
|
||||
.register_get("comment_ids", User::get_comment_ids_rhai);
|
||||
|
||||
// Register Comment type and its methods/getters/setters
|
||||
engine
|
||||
.register_type_with_name::<Comment>("Comment")
|
||||
.register_fn("comment_builder", Comment::comment_builder)
|
||||
.register_fn("user_id", Comment::user_id)
|
||||
.register_fn("content", Comment::content)
|
||||
.register_get("id", Comment::get_id_rhai)
|
||||
.register_get("user_id", Comment::get_user_id_rhai)
|
||||
.register_get("content", Comment::get_content_rhai);
|
||||
|
||||
// DB functions - now directly registered
|
||||
let db_clone_for_get_db = db_instance.clone();
|
||||
engine.register_fn("get_db", move || db_clone_for_get_db.clone());
|
||||
|
||||
let db_clone_for_set_user = db_instance.clone();
|
||||
engine.register_fn("set_user", move |user: User| set_user(db_clone_for_set_user.clone(), user));
|
||||
|
||||
let db_clone_for_get_user_by_id = db_instance.clone();
|
||||
engine.register_fn("get_user_by_id", move |id: INT| {
|
||||
wrap_option_return!(get_user_by_id, OurDB, INT => User)(db_clone_for_get_user_by_id.clone(), id)
|
||||
});
|
||||
|
||||
let db_clone_for_get_all_users = db_instance.clone();
|
||||
engine.register_fn(
|
||||
"get_all_users",
|
||||
move || {
|
||||
(wrap_vec_return!(get_all_users, OurDB => User))(db_clone_for_get_all_users.clone())
|
||||
}
|
||||
);
|
||||
|
||||
let db_clone_for_delete_user = db_instance.clone();
|
||||
engine.register_fn("delete_user_by_id", move |id: INT| delete_user_by_id(db_clone_for_delete_user.clone(), id));
|
||||
|
||||
let db_clone_for_set_comment = db_instance.clone();
|
||||
engine.register_fn("set_comment", move |comment: Comment| set_comment(db_clone_for_set_comment.clone(), comment));
|
||||
|
||||
let db_clone_for_get_comment_by_id = db_instance.clone();
|
||||
engine.register_fn("get_comment_by_id", move |id: INT| {
|
||||
wrap_option_return!(get_comment_by_id, OurDB, INT => Comment)(db_clone_for_get_comment_by_id.clone(), id)
|
||||
});
|
||||
|
||||
let db_clone_for_optional_vec = db_instance.clone();
|
||||
engine.register_fn(
|
||||
"get_users_by_activity_status_optional",
|
||||
move |is_active_filter: bool| {
|
||||
(wrap_option_vec_return!(get_users_by_activity_status_optional, OurDB, bool => User))(
|
||||
db_clone_for_optional_vec.clone(),
|
||||
is_active_filter
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
engine.register_fn("println", |s: &str| println!("{}", s));
|
||||
engine.register_fn("print", |s: &str| print!("{}", s));
|
||||
|
||||
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("examples")
|
||||
.join("user_script.rhai");
|
||||
|
||||
println!("Loading Rhai script from: {}", script_path.display());
|
||||
|
||||
match engine.eval_file::<()>(script_path) {
|
||||
Ok(_) => println!("Script executed successfully"),
|
||||
Err(e) => eprintln!("Error executing script: {}\nAt: {:?}", e, e.position()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
175
rhai_wrapper/examples/user_script.rhai
Normal file
175
rhai_wrapper/examples/user_script.rhai
Normal file
@@ -0,0 +1,175 @@
|
||||
// Hero Models - Rhai Example Script
|
||||
println("Hero Models - Rhai Usage Example");
|
||||
println("================================");
|
||||
|
||||
// Get the DB instance
|
||||
let db = get_db();
|
||||
|
||||
// Create a new user using the builder pattern
|
||||
println("Creating users...");
|
||||
|
||||
// Create user 1
|
||||
let user1 = user_builder(1)
|
||||
.username("johndoe")
|
||||
.email("john.doe@example.com")
|
||||
.full_name("John Doe")
|
||||
.is_active(false);
|
||||
set_user(user1);
|
||||
|
||||
// Create user 2
|
||||
let user2 = user_builder(2)
|
||||
.username("janesmith")
|
||||
.email("jane.smith@example.com")
|
||||
.full_name("Jane Smith")
|
||||
.is_active(true);
|
||||
set_user(user2);
|
||||
|
||||
// Create user 3
|
||||
let user3 = user_builder(3)
|
||||
.username("willism")
|
||||
.email("willis.masters@example.com")
|
||||
.full_name("Willis Masters")
|
||||
.is_active(true);
|
||||
set_user(user3);
|
||||
|
||||
// Create user 4
|
||||
let user4 = user_builder(4)
|
||||
.username("carrols")
|
||||
.email("carrol.smith@example.com")
|
||||
.full_name("Carrol Smith")
|
||||
.is_active(false);
|
||||
|
||||
set_user(user4);
|
||||
|
||||
// Get user by ID
|
||||
println("\nRetrieving user by ID...");
|
||||
let retrieved_user = get_user_by_id(1);
|
||||
if retrieved_user != () { // In Rhai, functions returning Option<T> yield () for None
|
||||
println("Found user: " + retrieved_user.full_name + " (ID: " + retrieved_user.id + ")");
|
||||
println("Username: " + retrieved_user.username);
|
||||
println("Email: " + retrieved_user.email);
|
||||
println("Active: " + retrieved_user.is_active);
|
||||
} else {
|
||||
println("User not found");
|
||||
}
|
||||
|
||||
// Get users by active status
|
||||
println("\nRetrieving active users...");
|
||||
let all_users_for_active_check = get_all_users();
|
||||
let active_users = [];
|
||||
|
||||
// Filter active users
|
||||
for user_item in all_users_for_active_check {
|
||||
if user_item.is_active == true {
|
||||
active_users.push(user_item);
|
||||
}
|
||||
}
|
||||
|
||||
println("Found " + active_users.len() + " active users:");
|
||||
for user_item in active_users {
|
||||
println("- " + user_item.full_name + " (ID: " + user_item.id + ")");
|
||||
}
|
||||
|
||||
// Delete a user
|
||||
println("\nDeleting user...");
|
||||
delete_user_by_id(2);
|
||||
|
||||
// Get active users again
|
||||
println("\nRetrieving active users after deletion...");
|
||||
let all_users_after_delete = get_all_users();
|
||||
let active_users_after_delete = [];
|
||||
|
||||
// Filter active users
|
||||
for user_item in all_users_after_delete {
|
||||
if user_item.is_active == true {
|
||||
active_users_after_delete.push(user_item);
|
||||
}
|
||||
}
|
||||
|
||||
println("Found " + active_users_after_delete.len() + " active users:");
|
||||
for user_item in active_users_after_delete {
|
||||
println("- " + user_item.full_name + " (ID: " + user_item.id + ")");
|
||||
}
|
||||
|
||||
// Get inactive users
|
||||
println("\nRetrieving inactive users...");
|
||||
let all_users_for_inactive_check = get_all_users();
|
||||
let inactive_users = [];
|
||||
|
||||
// Filter inactive users
|
||||
for user_item in all_users_for_inactive_check {
|
||||
if user_item.is_active == false {
|
||||
inactive_users.push(user_item);
|
||||
}
|
||||
}
|
||||
|
||||
println("Found " + inactive_users.len() + " inactive users:");
|
||||
for user_item in inactive_users {
|
||||
println("- " + user_item.full_name + " (ID: " + user_item.id + ")");
|
||||
}
|
||||
|
||||
// Create a comment for user 1
|
||||
println("\nCreating a comment...");
|
||||
let comment1 = comment_builder(5)
|
||||
.user_id(1)
|
||||
.content("This is a comment on the user");
|
||||
set_comment(comment1);
|
||||
|
||||
// Get the comment
|
||||
println("\nRetrieving comment...");
|
||||
let retrieved_comment = get_comment_by_id(5);
|
||||
if retrieved_comment != () { // In Rhai, functions returning Option<T> yield () for None
|
||||
println("Found comment: " + retrieved_comment.content);
|
||||
println("Comment ID: " + retrieved_comment.id);
|
||||
println("User ID: " + retrieved_comment.user_id);
|
||||
} else {
|
||||
println("Comment not found");
|
||||
}
|
||||
|
||||
println("\nRetrieving optional active users (should be Some(Array) or Unit)...");
|
||||
let optional_active_users = get_users_by_activity_status_optional(true);
|
||||
if optional_active_users == () { // Check for unit (None)
|
||||
println("No active users found (returned unit).");
|
||||
} else {
|
||||
println("Found optional active users:");
|
||||
for user in optional_active_users {
|
||||
println("- " + user.full_name + " (ID: " + user.id + ")");
|
||||
}
|
||||
}
|
||||
|
||||
println("\nRetrieving optional inactive users (should be Some(Array) or Unit)...");
|
||||
let optional_inactive_users = get_users_by_activity_status_optional(false);
|
||||
if optional_inactive_users == () { // Check for unit (None)
|
||||
println("No inactive users found (returned unit).");
|
||||
} else {
|
||||
println("Found optional inactive users:");
|
||||
for user in optional_inactive_users {
|
||||
println("- " + user.full_name + " (ID: " + user.id + ")");
|
||||
}
|
||||
}
|
||||
|
||||
// To specifically test the None case from our Rust logic (empty vec for a status returns None)
|
||||
println("\nTesting None case for optional vector retrieval...");
|
||||
println("Deleting all users to ensure empty states...");
|
||||
let all_users_before_delete_all = get_all_users();
|
||||
for user_to_delete in all_users_before_delete_all {
|
||||
delete_user_by_id(user_to_delete.id);
|
||||
}
|
||||
|
||||
let optional_active_after_delete_all = get_users_by_activity_status_optional(true);
|
||||
if optional_active_after_delete_all == () {
|
||||
println("Correctly received unit for active users after deleting all.");
|
||||
} else {
|
||||
println("ERROR: Expected unit for active users after deleting all, but got an array.");
|
||||
print("Value received: " + optional_active_after_delete_all);
|
||||
}
|
||||
|
||||
let optional_inactive_after_delete_all = get_users_by_activity_status_optional(false);
|
||||
if optional_inactive_after_delete_all == () {
|
||||
println("Correctly received unit for inactive users after deleting all.");
|
||||
} else {
|
||||
println("ERROR: Expected unit for inactive users after deleting all, but got an array.");
|
||||
print("Value received: " + optional_inactive_after_delete_all);
|
||||
}
|
||||
|
||||
println("\nRhai example completed successfully!");
|
@@ -208,3 +208,601 @@ macro_rules! wrap_for_rhai {
|
||||
$func
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro to wrap a Rust function that returns Option<T> for Rhai.
|
||||
/// It converts Some(T) to Dynamic::from(T) and None to Dynamic::UNIT.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_option_return {
|
||||
// Matches fn_name(arg1_type, arg2_type) -> Option<ReturnType>
|
||||
// Example: get_user_by_id(OurDB, INT) -> Option<User>
|
||||
// Macro call: wrap_option_return!(get_user_by_id, OurDB, INT => User)
|
||||
// Generated closure: |db: OurDB, id: INT| -> rhai::Dynamic { ... }
|
||||
($func:ident, $Arg1Type:ty, $Arg2Type:ty => $ReturnType:ident) => {
|
||||
|arg1: $Arg1Type, arg2: $Arg2Type| -> rhai::Dynamic {
|
||||
match $func(arg1, arg2) {
|
||||
Some(value) => {
|
||||
// Ensure the value is converted into a Dynamic.
|
||||
// If $ReturnType is already a CustomType, this should work directly.
|
||||
rhai::Dynamic::from(value)
|
||||
}
|
||||
None => rhai::Dynamic::UNIT,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add more arms here if functions with different numbers/types of arguments returning Option<T>
|
||||
// are needed.
|
||||
// For example, for a function with one argument:
|
||||
// ($func:ident, $Arg1Type:ty => $ReturnType:ident) => { ... };
|
||||
}
|
||||
|
||||
/// Macro to wrap a Rust function that returns Vec<T> for Rhai.
|
||||
/// It converts the Vec into a rhai::Array.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_vec_return {
|
||||
// For functions like fn(Arg1) -> Vec<InnerType>
|
||||
// Example: get_all_users(db: OurDB) -> Vec<User>
|
||||
// Macro call: wrap_vec_return!(get_all_users, OurDB => User)
|
||||
// Generated closure: |db: OurDB| -> rhai::Array { ... }
|
||||
($func:ident, $Arg1Type:ty => $InnerVecType:ty) => {
|
||||
|arg1_val: $Arg1Type| -> rhai::Array {
|
||||
let result_vec: std::vec::Vec<$InnerVecType> = $func(arg1_val);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
|
||||
// For functions like fn(Arg1, Arg2) -> Vec<InnerType>
|
||||
($func:ident, $Arg1Type:ty, $Arg2Type:ty => $InnerVecType:ty) => {
|
||||
|arg1_val: $Arg1Type, arg2_val: $Arg2Type| -> rhai::Array {
|
||||
let result_vec: std::vec::Vec<$InnerVecType> = $func(arg1_val, arg2_val);
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
|
||||
// For functions like fn() -> Vec<InnerType>
|
||||
($func:ident, () => $InnerVecType:ty) => {
|
||||
|| -> rhai::Array {
|
||||
let result_vec: std::vec::Vec<$InnerVecType> = $func();
|
||||
result_vec.into_iter().map(rhai::Dynamic::from).collect::<rhai::Array>()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! wrap_option_vec_return {
|
||||
// Case: fn_name(Arg1Type, Arg2Type) -> Option<Vec<InnerType>>
|
||||
// Generates a closure: |arg1_val: Arg1Type, arg2_val: Arg2Type| -> rhai::Dynamic
|
||||
($func:ident, $Arg1Type:ty, $Arg2Type:ty => $InnerVecType:ty) => {
|
||||
move |arg1_val: $Arg1Type, arg2_val: $Arg2Type| -> rhai::Dynamic {
|
||||
match $func(arg1_val, arg2_val) { // Call the original Rust function
|
||||
Some(vec_inner) => { // vec_inner is Vec<$InnerVecType>
|
||||
let rhai_array = vec_inner.into_iter()
|
||||
.map(rhai::Dynamic::from) // Each $InnerVecType must be convertible to Dynamic
|
||||
.collect::<rhai::Array>();
|
||||
rhai::Dynamic::from(rhai_array)
|
||||
}
|
||||
None => rhai::Dynamic::UNIT,
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: Add arms for different numbers of arguments if needed, e.g.:
|
||||
// ($func:ident, $Arg1Type:ty => $InnerVecType:ty) => { ... }
|
||||
// ($func:ident => $InnerVecType:ty) => { ... }
|
||||
}
|
||||
|
||||
/// Wraps a Rust function that returns `Result<Option<T>, ErrorType>` for Rhai.
|
||||
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
|
||||
/// Assumes `T` implements a `to_rhai_map(&self) -> rhai::Map` method.
|
||||
/// Assumes `ErrorType` implements `std::fmt::Display`.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_option_return_result {
|
||||
// Case: Function with DB connection and 1 additional argument
|
||||
(
|
||||
$func:path, // Path to the actual Rust function
|
||||
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types for actual func
|
||||
$ErrorType:ty // Error type from actual func
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match $func(db_conn_instance, arg1_val) { // Call the actual function
|
||||
Ok(Some(value)) => {
|
||||
// Assumes ReturnType has a .to_rhai_map() method.
|
||||
Ok(rhai::Dynamic::from(value.to_rhai_map()))
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(), // Requires ErrorType: Display
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Case: Function with DB connection and 0 additional arguments
|
||||
(
|
||||
$func:path,
|
||||
$DbConnType:ty => $ReturnType:ty,
|
||||
$ErrorType:ty
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match $func(db_conn_instance) { // Call the actual function
|
||||
Ok(Some(value)) => {
|
||||
Ok(rhai::Dynamic::from(value.to_rhai_map()))
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(),
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: Add variants for more arguments as needed
|
||||
}
|
||||
|
||||
/// Wraps a Rust function that returns `Result<Vec<T>, ErrorType>` for Rhai.
|
||||
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
|
||||
/// Assumes `T` implements a `to_rhai_map(&self) -> rhai::Map` method.
|
||||
/// Assumes `ErrorType` implements `std::fmt::Display`.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_vec_return_result {
|
||||
// Case: Function with DB connection and 1 additional argument
|
||||
(
|
||||
$func:path, // Path to the actual Rust function
|
||||
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types for actual func
|
||||
$ErrorType:ty // Error type from actual func
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match $func(db_conn_instance, arg1_val) { // Call the actual function
|
||||
Ok(vec_of_values) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.to_rhai_map()))
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(), // Requires ErrorType: Display
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Case: Function with DB connection and 0 additional arguments (e.g., get_all)
|
||||
(
|
||||
$func:path,
|
||||
$DbConnType:ty => $ReturnType:ty,
|
||||
$ErrorType:ty
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match $func(db_conn_instance) { // Call the actual function
|
||||
Ok(vec_of_values) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.to_rhai_map()))
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(),
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: Add variants for more arguments as needed
|
||||
}
|
||||
|
||||
/// Wraps a Rust function that returns `Result<Option<Vec<T>>, ErrorType>` for Rhai.
|
||||
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
|
||||
/// Assumes `T` implements a `to_rhai_map(&self) -> rhai::Map` method.
|
||||
/// Assumes `ErrorType` implements `std::fmt::Display`.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_option_vec_return_result {
|
||||
// Case: Function with DB connection and 1 additional argument
|
||||
(
|
||||
$func:path, // Path to the actual Rust function
|
||||
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types for actual func
|
||||
$ErrorType:ty // Error type from actual func
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match $func(db_conn_instance, arg1_val) { // Call the actual function
|
||||
Ok(Some(vec_of_values)) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.to_rhai_map()))
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(), // Requires ErrorType: Display
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Case: Function with DB connection and 0 additional arguments
|
||||
(
|
||||
$func:path,
|
||||
$DbConnType:ty => $ReturnType:ty,
|
||||
$ErrorType:ty
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match $func(db_conn_instance) { // Call the actual function
|
||||
Ok(Some(vec_of_values)) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.to_rhai_map()))
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(),
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: Add variants for more arguments as needed
|
||||
}
|
||||
|
||||
// --- Macros for methods returning Result<_, _> for fallible operations --- //
|
||||
|
||||
/// Wraps a Rust method that returns `Result<Option<T>, ErrorType>` for Rhai.
|
||||
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
|
||||
/// Assumes `T` implements `Clone` and is Rhai `CustomType`.
|
||||
/// Assumes `ErrorType` implements `std::fmt::Display`.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_option_method_result {
|
||||
// Case: Method with DB connection (self) and 1 additional argument
|
||||
(
|
||||
$method_name:ident, // Name of the method on the Collection
|
||||
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type (e.g. &Collection), Arg types
|
||||
$ErrorType:ty // Error type from method
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match db_conn_instance.$method_name(arg1_val) { // Call the method
|
||||
Ok(Some(value)) => {
|
||||
Ok(rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(), // ErrorType: Display
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Case: Method with DB connection (self) and 0 additional arguments
|
||||
(
|
||||
$method_name:ident,
|
||||
$DbConnType:ty => $ReturnType:ty,
|
||||
$ErrorType:ty
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match db_conn_instance.$method_name() { // Call the method
|
||||
Ok(Some(value)) => {
|
||||
Ok(rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(),
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: Add variants for more arguments as needed
|
||||
}
|
||||
|
||||
/// Wraps a Rust method that returns `Result<Vec<T>, ErrorType>` for Rhai.
|
||||
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
|
||||
/// Assumes `T` implements `Clone` and is Rhai `CustomType`.
|
||||
/// Assumes `ErrorType` implements `std::fmt::Display`.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_vec_method_result {
|
||||
// Case: Method with DB connection (self) and 1 additional argument
|
||||
(
|
||||
$method_name:ident, // Name of the method on the Collection
|
||||
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types
|
||||
$ErrorType:ty // Error type from method
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match db_conn_instance.$method_name(arg1_val) { // Call the method
|
||||
Ok(vec_of_values) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(), // ErrorType: Display
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Case: Method with DB connection (self) and 0 additional arguments (e.g., get_all)
|
||||
(
|
||||
$method_name:ident,
|
||||
$DbConnType:ty => $ReturnType:ty,
|
||||
$ErrorType:ty
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match db_conn_instance.$method_name() { // Call the method
|
||||
Ok(vec_of_values) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(),
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: Add variants for more arguments as needed
|
||||
}
|
||||
|
||||
/// Wraps a Rust method that returns `Result<Option<Vec<T>>, ErrorType>` for Rhai.
|
||||
/// The generated closure returns `Result<rhai::Dynamic, Box<rhai::EvalAltResult>>`.
|
||||
/// Assumes `T` implements `Clone` and is Rhai `CustomType`.
|
||||
/// Assumes `ErrorType` implements `std::fmt::Display`.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_option_vec_method_result {
|
||||
// Case: Method with DB connection (self) and 1 additional argument
|
||||
(
|
||||
$method_name:ident, // Name of the method on the Collection
|
||||
$DbConnType:ty, $Arg1Type:ty => $ReturnType:ty, // DB conn type, Arg types
|
||||
$ErrorType:ty // Error type from method
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType, arg1_val: $Arg1Type|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match db_conn_instance.$method_name(arg1_val) { // Call the method
|
||||
Ok(Some(vec_of_values)) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(), // ErrorType: Display
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Case: Method with DB connection (self) and 0 additional arguments
|
||||
(
|
||||
$method_name:ident,
|
||||
$DbConnType:ty => $ReturnType:ty,
|
||||
$ErrorType:ty
|
||||
) => {
|
||||
move |db_conn_instance: $DbConnType|
|
||||
-> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
|
||||
match db_conn_instance.$method_name() { // Call the method
|
||||
Ok(Some(vec_of_values)) => {
|
||||
let rhai_array = vec_of_values
|
||||
.into_iter()
|
||||
.map(|value| rhai::Dynamic::from(value.clone())) // Assumes ReturnType: Clone
|
||||
.collect::<rhai::Array>();
|
||||
Ok(rhai::Dynamic::from(rhai_array))
|
||||
}
|
||||
Ok(None) => Ok(rhai::Dynamic::UNIT),
|
||||
Err(err) => {
|
||||
Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
|
||||
format!("Function Error: {}", err).into(),
|
||||
rhai::Position::NONE,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: Add variants for more arguments as needed
|
||||
}
|
||||
|
||||
// TODO: Consider merging wrap_option_return, wrap_vec_return, and wrap_option_vec_return
|
||||
// into a more general wrap_for_rhai! macro if patterns become too numerous or complex.
|
||||
// For now, separate macros are clear for distinct return type patterns.
|
||||
|
||||
/// A macro that creates a Rust function that calls a Rhai engine to execute a Rhai function which wraps an underlying Rust function.
|
||||
/// This creates a full circle of Rust → Rhai → Rust function calls.
|
||||
///
|
||||
/// # Example Usage
|
||||
/// ```rust,ignore
|
||||
/// // Define a Rust function
|
||||
/// fn add(a: i32, b: i32) -> i32 {
|
||||
/// a + b
|
||||
/// }
|
||||
///
|
||||
/// // Register it with Rhai (assuming engine is already created)
|
||||
/// engine.register_fn("add_rhai", add);
|
||||
///
|
||||
/// // Create a wrapper function that takes an engine reference and calls the Rhai function
|
||||
/// rust_rhai_wrapper!(add_via_rhai, "add_rhai", (i32, i32) -> i32);
|
||||
///
|
||||
/// // Now you can call add_via_rhai which will call the Rhai function add_rhai which calls the Rust function add
|
||||
/// let result = add_via_rhai(&mut engine, 5, 3); // result = 8
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! rust_rhai_wrapper {
|
||||
// Basic case: function with no arguments
|
||||
($func_name:ident, $rhai_func_name:expr, () -> $return_type:ty) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine) -> $return_type {
|
||||
let result = engine.eval::<$return_type>(
|
||||
&format!("{}()", $rhai_func_name)
|
||||
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
|
||||
result
|
||||
}
|
||||
};
|
||||
|
||||
// Function with one argument
|
||||
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty) -> $return_type:ty) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type) -> $return_type {
|
||||
// Create a scope to pass arguments
|
||||
let mut scope = rhai::Scope::new();
|
||||
scope.push("arg1", arg1);
|
||||
|
||||
let result = engine.eval_with_scope::<$return_type>(
|
||||
&mut scope,
|
||||
&format!("{}(arg1)", $rhai_func_name)
|
||||
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
|
||||
result
|
||||
}
|
||||
};
|
||||
|
||||
// Function with two arguments
|
||||
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty) -> $return_type:ty) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type) -> $return_type {
|
||||
// Create a scope to pass arguments
|
||||
let mut scope = rhai::Scope::new();
|
||||
scope.push("arg1", arg1);
|
||||
scope.push("arg2", arg2);
|
||||
|
||||
let result = engine.eval_with_scope::<$return_type>(
|
||||
&mut scope,
|
||||
&format!("{}(arg1, arg2)", $rhai_func_name)
|
||||
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
|
||||
result
|
||||
}
|
||||
};
|
||||
|
||||
// Function with three arguments
|
||||
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty, $arg3_type:ty) -> $return_type:ty) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type, arg3: $arg3_type) -> $return_type {
|
||||
// Create a scope to pass arguments
|
||||
let mut scope = rhai::Scope::new();
|
||||
scope.push("arg1", arg1);
|
||||
scope.push("arg2", arg2);
|
||||
scope.push("arg3", arg3);
|
||||
|
||||
let result = engine.eval_with_scope::<$return_type>(
|
||||
&mut scope,
|
||||
&format!("{}(arg1, arg2, arg3)", $rhai_func_name)
|
||||
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
|
||||
result
|
||||
}
|
||||
};
|
||||
|
||||
// Function with four arguments
|
||||
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty, $arg3_type:ty, $arg4_type:ty) -> $return_type:ty) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type, arg3: $arg3_type, arg4: $arg4_type) -> $return_type {
|
||||
// Create a scope to pass arguments
|
||||
let mut scope = rhai::Scope::new();
|
||||
scope.push("arg1", arg1);
|
||||
scope.push("arg2", arg2);
|
||||
scope.push("arg3", arg3);
|
||||
scope.push("arg4", arg4);
|
||||
|
||||
let result = engine.eval_with_scope::<$return_type>(
|
||||
&mut scope,
|
||||
&format!("{}(arg1, arg2, arg3, arg4)", $rhai_func_name)
|
||||
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
|
||||
result
|
||||
}
|
||||
};
|
||||
|
||||
// Function with five arguments
|
||||
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty, $arg3_type:ty, $arg4_type:ty, $arg5_type:ty) -> $return_type:ty) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type, arg3: $arg3_type, arg4: $arg4_type, arg5: $arg5_type) -> $return_type {
|
||||
// Create a scope to pass arguments
|
||||
let mut scope = rhai::Scope::new();
|
||||
scope.push("arg1", arg1);
|
||||
scope.push("arg2", arg2);
|
||||
scope.push("arg3", arg3);
|
||||
scope.push("arg4", arg4);
|
||||
scope.push("arg5", arg5);
|
||||
|
||||
let result = engine.eval_with_scope::<$return_type>(
|
||||
&mut scope,
|
||||
&format!("{}(arg1, arg2, arg3, arg4, arg5)", $rhai_func_name)
|
||||
).expect(&format!("Failed to call Rhai function {}", $rhai_func_name));
|
||||
result
|
||||
}
|
||||
};
|
||||
|
||||
// Function with a Result return type and no arguments
|
||||
($func_name:ident, $rhai_func_name:expr, () -> Result<$ok_type:ty, $err_type:ty>) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine) -> Result<$ok_type, $err_type> {
|
||||
match engine.eval::<$ok_type>(&format!("{}()", $rhai_func_name)) {
|
||||
Ok(result) => Ok(result),
|
||||
Err(err) => Err($err_type::from(format!("Rhai error: {}", err))),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Function with a Result return type and one argument
|
||||
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty) -> Result<$ok_type:ty, $err_type:ty>) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type) -> Result<$ok_type, $err_type> {
|
||||
// Create a scope to pass arguments
|
||||
let mut scope = rhai::Scope::new();
|
||||
scope.push("arg1", arg1);
|
||||
|
||||
match engine.eval_with_scope::<$ok_type>(&mut scope, &format!("{}(arg1)", $rhai_func_name)) {
|
||||
Ok(result) => Ok(result),
|
||||
Err(err) => Err($err_type::from(format!("Rhai error: {}", err))),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Function with a Result return type and two arguments
|
||||
($func_name:ident, $rhai_func_name:expr, ($arg1_type:ty, $arg2_type:ty) -> Result<$ok_type:ty, $err_type:ty>) => {
|
||||
pub fn $func_name(engine: &mut rhai::Engine, arg1: $arg1_type, arg2: $arg2_type) -> Result<$ok_type, $err_type> {
|
||||
// Create a scope to pass arguments
|
||||
let mut scope = rhai::Scope::new();
|
||||
scope.push("arg1", arg1);
|
||||
scope.push("arg2", arg2);
|
||||
|
||||
match engine.eval_with_scope::<$ok_type>(&mut scope, &format!("{}(arg1, arg2)", $rhai_func_name)) {
|
||||
Ok(result) => Ok(result),
|
||||
Err(err) => Err($err_type::from(format!("Rhai error: {}", err))),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
// into a more general wrap_for_rhai! macro if patterns become too numerous or complex.
|
||||
// For now, separate macros are clear for distinct return type patterns.
|
||||
|
@@ -1,15 +1,23 @@
|
||||
use rhai_wrapper::wrap_for_rhai;
|
||||
use rhai_wrapper::{ToRhaiMap, FromRhaiMap};
|
||||
use rhai::{CustomType, TypeBuilder, Engine, INT, FLOAT, Array};
|
||||
use rhai_macros_derive::{ToRhaiMap as ToRhaiMapDerive, FromRhaiMap as FromRhaiMapDerive};
|
||||
use rhai_macros_derive::{ToRhaiMap as ToRhaiMapDerive, FromRhaiMap as FromRhaiMapDerive, export_fn};
|
||||
|
||||
#[export_fn(rhai_name = "add_rhai")]
|
||||
fn add(a: INT, b: INT) -> INT { a + b }
|
||||
#[export_fn(rhai_name = "mul_rhai")]
|
||||
fn mul(a: INT, b: INT) -> INT { a * b }
|
||||
#[export_fn(rhai_name = "greet_rhai")]
|
||||
fn greet(name: String) -> String { format!("Hello, {name}!") }
|
||||
#[export_fn(rhai_name = "get_forty_two_rhai")]
|
||||
fn get_forty_two() -> INT { 42 }
|
||||
#[export_fn(rhai_name = "shout_rhai")]
|
||||
fn shout() -> String { "HEY!".to_string() }
|
||||
#[export_fn(rhai_name = "add_float_rhai")]
|
||||
fn add_float(a: FLOAT, b: FLOAT) -> FLOAT { a + b }
|
||||
#[export_fn(rhai_name = "is_even_rhai")]
|
||||
fn is_even(n: INT) -> bool { n % 2 == 0 }
|
||||
#[export_fn(rhai_name = "maybe_add_rhai")]
|
||||
fn maybe_add(a: INT, b: INT, do_add: bool) -> Option<INT> { if do_add { Some(a + b) } else { None } }
|
||||
|
||||
// Renamed from sum_vec, takes rhai::Array
|
||||
@@ -110,69 +118,83 @@ fn get_polygon_id_and_num_vertices(poly: Polygon) -> String {
|
||||
#[test]
|
||||
fn test_add() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("add", wrap_for_rhai!(add));
|
||||
let result = engine.eval::<INT>("add(2, 3)").unwrap();
|
||||
engine.register_fn("add_rhai", add_rhai_wrapper);
|
||||
let result = engine.eval::<INT>("add_rhai(2, 3)").unwrap();
|
||||
assert_eq!(result, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mul() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("mul", wrap_for_rhai!(mul));
|
||||
let result = engine.eval::<INT>("mul(4, 5)").unwrap();
|
||||
engine.register_fn("mul_rhai", mul_rhai_wrapper);
|
||||
let result = engine.eval::<INT>("mul_rhai(4, 5)").unwrap();
|
||||
assert_eq!(result, 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_greet() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("greet", wrap_for_rhai!(greet));
|
||||
let result = engine.eval::<String>(r#"greet("Alice")"#).unwrap();
|
||||
engine.register_fn("greet_rhai", greet_rhai_wrapper);
|
||||
let result = engine.eval::<String>(r#"greet_rhai("Alice")"#).unwrap();
|
||||
assert_eq!(result, "Hello, Alice!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_forty_two() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("get_forty_two", wrap_for_rhai!(get_forty_two));
|
||||
let result = engine.eval::<INT>("get_forty_two()").unwrap();
|
||||
engine.register_fn("get_forty_two_rhai", get_forty_two_rhai_wrapper);
|
||||
let result = engine.eval::<INT>("get_forty_two_rhai()").unwrap();
|
||||
assert_eq!(result, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shout() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("shout", wrap_for_rhai!(shout));
|
||||
let result = engine.eval::<String>("shout()").unwrap();
|
||||
engine.register_fn("shout_rhai", shout_rhai_wrapper);
|
||||
let result = engine.eval::<String>("shout_rhai()").unwrap();
|
||||
assert_eq!(result, "HEY!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_float() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("add_float", wrap_for_rhai!(add_float));
|
||||
let result = engine.eval::<FLOAT>("add_float(1.5, 2.25)").unwrap();
|
||||
assert!((result - 3.75).abs() < 1e-8);
|
||||
engine.register_fn("add_float_rhai", add_float_rhai_wrapper);
|
||||
let result = engine.eval::<FLOAT>("add_float_rhai(2.5, 3.5)").unwrap();
|
||||
assert_eq!(result, 6.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_even() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("is_even", wrap_for_rhai!(is_even));
|
||||
let result = engine.eval::<bool>("is_even(4)").unwrap();
|
||||
assert!(result);
|
||||
let result = engine.eval::<bool>("is_even(5)").unwrap();
|
||||
assert!(!result);
|
||||
engine.register_fn("is_even_rhai", is_even_rhai_wrapper);
|
||||
let result_true = engine.eval::<bool>("is_even_rhai(4)").unwrap();
|
||||
assert_eq!(result_true, true);
|
||||
let result_false = engine.eval::<bool>("is_even_rhai(3)").unwrap();
|
||||
assert_eq!(result_false, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_maybe_add() {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("maybe_add", wrap_for_rhai!(maybe_add));
|
||||
let result = engine.eval::<Option<INT>>("maybe_add(2, 3, true)").unwrap();
|
||||
assert_eq!(result, Some(5));
|
||||
let result = engine.eval::<Option<INT>>("maybe_add(2, 3, false)").unwrap();
|
||||
assert_eq!(result, None);
|
||||
engine.register_fn("maybe_add_rhai", maybe_add_rhai_wrapper);
|
||||
|
||||
// Test case where None is returned (expecting an error or specific handling in Rhai)
|
||||
// Rhai treats Option::None as an empty Dynamic, which can lead to type mismatch if not handled.
|
||||
// For now, let's check if the script produces a specific type or if it can be evaluated to Dynamic.
|
||||
// If the function returns None, eval might return an error if trying to cast to INT.
|
||||
// Let's eval to Dynamic and check if it's empty (Rhai's representation of None).
|
||||
let result_none = engine.eval::<rhai::Dynamic>("maybe_add_rhai(2, 3, false)").unwrap();
|
||||
|
||||
// Debug prints
|
||||
println!("Debug [test_maybe_add]: result_none = {:?}", result_none);
|
||||
println!("Debug [test_maybe_add]: result_none.type_name() = {}", result_none.type_name());
|
||||
println!("Debug [test_maybe_add]: result_none.is::<()>() = {}", result_none.is::<()>());
|
||||
|
||||
assert!(result_none.is_unit(), "Expected Rhai None (unit Dynamic)");
|
||||
|
||||
// Test case where Some is returned
|
||||
let result_some = engine.eval::<INT>("maybe_add_rhai(2, 3, true)").unwrap();
|
||||
assert_eq!(result_some, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -502,21 +524,20 @@ mod new_export_fn_tests {
|
||||
assert_eq!(result, 15);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_export_fn_custom_type_arg_return() { // This test was commented out, keeping as is for now
|
||||
// let mut engine = Engine::new();
|
||||
// engine.build_type::<Point>();
|
||||
// // engine.register_fn("offset_simple_point", offset_simple_point_rhai_wrapper);
|
||||
#[test]
|
||||
fn test_export_fn_custom_type_arg_return_new() {
|
||||
let mut engine = Engine::new();
|
||||
engine.build_type::<Point>();
|
||||
engine.register_fn("offset_simple_point", offset_simple_point_rhai_wrapper);
|
||||
|
||||
// // let script = r#"
|
||||
// // let p = #{ x: 10, y: 20 };
|
||||
// // let p_offset = offset_simple_point(p, 5);
|
||||
// // p_offset.x
|
||||
// // "#;
|
||||
// // let result = engine.eval::<INT>(script).unwrap();
|
||||
// // assert_eq!(result, 15);
|
||||
|
||||
// }
|
||||
let script = r#"
|
||||
let p = #{ x: 10, y: 20 };
|
||||
let p_offset = offset_simple_point(p, 5);
|
||||
p_offset.x
|
||||
"#;
|
||||
let result = engine.eval::<INT>(script).unwrap();
|
||||
assert_eq!(result, 15);
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, FromRhaiMapDerive, ToRhaiMapDerive, CustomType)]
|
||||
@@ -556,17 +577,4 @@ mod new_export_fn_tests {
|
||||
assert_eq!(result_struct.optional_nested_vec.as_ref().unwrap().len(), 1);
|
||||
assert_eq!(result_struct.optional_nested_vec.as_ref().unwrap()[0], SampleStruct { value: 3, name: "n3".to_string() });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_export_fn_custom_type_arg_return_new() {
|
||||
let mut engine = Engine::new();
|
||||
engine.build_type::<Point>();
|
||||
engine.register_fn("offset_simple_point", offset_simple_point_rhai_wrapper);
|
||||
|
||||
let script = r#"
|
||||
42
|
||||
"#;
|
||||
let result = engine.eval::<INT>(script).unwrap();
|
||||
assert_eq!(result, 42);
|
||||
}
|
||||
}
|
||||
|
709
rhai_wrapper/tests/wrapper_macros_test.rs
Normal file
709
rhai_wrapper/tests/wrapper_macros_test.rs
Normal file
@@ -0,0 +1,709 @@
|
||||
use rhai::{Engine, INT, FLOAT, Dynamic, Map, Array, EvalAltResult, CustomType, TypeBuilder};
|
||||
use rhai_wrapper::{
|
||||
wrap_option_return, wrap_vec_return, wrap_option_vec_return,
|
||||
wrap_option_return_result, wrap_vec_return_result, wrap_option_vec_return_result,
|
||||
wrap_option_method_result, wrap_vec_method_result, wrap_option_vec_method_result,
|
||||
ToRhaiMap, FromRhaiMap
|
||||
};
|
||||
use rhai_macros_derive::{ToRhaiMap as ToRhaiMapDerive, FromRhaiMap as FromRhaiMapDerive};
|
||||
use std::fmt;
|
||||
|
||||
// Test structs
|
||||
#[derive(Debug, Clone, PartialEq, CustomType, ToRhaiMapDerive, FromRhaiMapDerive)]
|
||||
struct User {
|
||||
id: INT,
|
||||
name: String,
|
||||
age: INT,
|
||||
}
|
||||
|
||||
impl User {
|
||||
fn to_rhai_map(&self) -> Map {
|
||||
let mut map = Map::new();
|
||||
map.insert("id".into(), self.id.into());
|
||||
map.insert("name".into(), self.name.clone().into());
|
||||
map.insert("age".into(), self.age.into());
|
||||
map
|
||||
}
|
||||
|
||||
fn from_rhai_map(map: Map) -> Result<Self, String> {
|
||||
let id = map.get("id")
|
||||
.and_then(|d| d.as_int().ok())
|
||||
.ok_or_else(|| "Field 'id' not found or not an INT".to_string())?;
|
||||
|
||||
let name = map.get("name")
|
||||
.and_then(|d| d.clone().into_string().ok())
|
||||
.ok_or_else(|| "Field 'name' not found or not a String".to_string())?;
|
||||
|
||||
let age = map.get("age")
|
||||
.and_then(|d| d.as_int().ok())
|
||||
.ok_or_else(|| "Field 'age' not found or not an INT".to_string())?;
|
||||
|
||||
Ok(User { id, name, age })
|
||||
}
|
||||
}
|
||||
|
||||
// Mock DB connection type
|
||||
struct OurDB {
|
||||
// Some mock state
|
||||
error_mode: bool,
|
||||
}
|
||||
|
||||
impl OurDB {
|
||||
fn new(error_mode: bool) -> Self {
|
||||
OurDB { error_mode }
|
||||
}
|
||||
}
|
||||
|
||||
// Custom error type for testing
|
||||
#[derive(Debug)]
|
||||
struct DBError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for DBError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "DB Error: {}", self.message)
|
||||
}
|
||||
}
|
||||
|
||||
// Test functions for wrap_option_return
|
||||
fn get_user_by_id(_db: &OurDB, id: INT) -> Option<User> {
|
||||
if id > 0 {
|
||||
Some(User { id, name: format!("User {}", id), age: 30 })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Test functions for wrap_vec_return
|
||||
fn get_all_users(_db: &OurDB) -> Vec<User> {
|
||||
vec![
|
||||
User { id: 1, name: "User 1".to_string(), age: 30 },
|
||||
User { id: 2, name: "User 2".to_string(), age: 25 },
|
||||
]
|
||||
}
|
||||
|
||||
fn get_users_by_age(_db: &OurDB, min_age: INT) -> Vec<User> {
|
||||
vec![
|
||||
User { id: 1, name: "User 1".to_string(), age: 30 },
|
||||
User { id: 2, name: "User 2".to_string(), age: 25 },
|
||||
].into_iter().filter(|u| u.age >= min_age).collect()
|
||||
}
|
||||
|
||||
fn get_all_user_ids() -> Vec<INT> {
|
||||
vec![1, 2, 3]
|
||||
}
|
||||
|
||||
// Test functions for wrap_option_vec_return
|
||||
fn find_users_by_name(_db: &OurDB, name_part: String) -> Option<Vec<User>> {
|
||||
if name_part.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(vec![
|
||||
User { id: 1, name: format!("{} One", name_part), age: 30 },
|
||||
User { id: 2, name: format!("{} Two", name_part), age: 25 },
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// Test functions for result-returning wrappers
|
||||
fn get_user_by_id_result(db: &OurDB, id: INT) -> Result<Option<User>, DBError> {
|
||||
if db.error_mode {
|
||||
Err(DBError { message: "DB connection error".to_string() })
|
||||
} else if id > 0 {
|
||||
Ok(Some(User { id, name: format!("User {}", id), age: 30 }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_all_users_result(db: &OurDB) -> Result<Vec<User>, DBError> {
|
||||
if db.error_mode {
|
||||
Err(DBError { message: "DB connection error".to_string() })
|
||||
} else {
|
||||
Ok(vec![
|
||||
User { id: 1, name: "User 1".to_string(), age: 30 },
|
||||
User { id: 2, name: "User 2".to_string(), age: 25 },
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
fn find_users_by_name_result(db: &OurDB, name_part: String) -> Result<Option<Vec<User>>, DBError> {
|
||||
if db.error_mode {
|
||||
Err(DBError { message: "DB connection error".to_string() })
|
||||
} else if name_part.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(vec![
|
||||
User { id: 1, name: format!("{} One", name_part), age: 30 },
|
||||
User { id: 2, name: format!("{} Two", name_part), age: 25 },
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
// Test methods for method wrappers
|
||||
struct UserCollection {
|
||||
users: Vec<User>,
|
||||
error_mode: bool,
|
||||
}
|
||||
|
||||
impl UserCollection {
|
||||
fn new(error_mode: bool) -> Self {
|
||||
UserCollection {
|
||||
users: vec![
|
||||
User { id: 1, name: "User 1".to_string(), age: 30 },
|
||||
User { id: 2, name: "User 2".to_string(), age: 25 },
|
||||
],
|
||||
error_mode,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_by_id(&self, id: INT) -> Result<Option<User>, DBError> {
|
||||
if self.error_mode {
|
||||
Err(DBError { message: "Collection error".to_string() })
|
||||
} else {
|
||||
Ok(self.users.iter().find(|u| u.id == id).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_all(&self) -> Result<Vec<User>, DBError> {
|
||||
if self.error_mode {
|
||||
Err(DBError { message: "Collection error".to_string() })
|
||||
} else {
|
||||
Ok(self.users.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn find_by_name(&self, name_part: String) -> Result<Option<Vec<User>>, DBError> {
|
||||
if self.error_mode {
|
||||
Err(DBError { message: "Collection error".to_string() })
|
||||
} else if name_part.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(self.users.iter()
|
||||
.filter(|u| u.name.contains(&name_part))
|
||||
.cloned()
|
||||
.collect()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_option_return() {
|
||||
let mut engine = Engine::new();
|
||||
let db = OurDB::new(false);
|
||||
|
||||
// Register the wrapped function
|
||||
engine.register_fn(
|
||||
"get_user_by_id_rhai",
|
||||
wrap_option_return!(get_user_by_id, &OurDB, INT => User)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with existing user
|
||||
let script1 = r#"
|
||||
let user = get_user_by_id_rhai(db, 1);
|
||||
user.id
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"user.id",
|
||||
(db.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 1);
|
||||
|
||||
// Test with non-existing user
|
||||
let script2 = r#"
|
||||
let user = get_user_by_id_rhai(db, 0);
|
||||
if user == () { 42 } else { 0 }
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"if user == () { 42 } else { 0 }",
|
||||
(db, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_vec_return() {
|
||||
let mut engine = Engine::new();
|
||||
let db = OurDB::new(false);
|
||||
|
||||
// Register the wrapped functions
|
||||
engine.register_fn(
|
||||
"get_all_users_rhai",
|
||||
wrap_vec_return!(get_all_users, &OurDB => User)
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"get_users_by_age_rhai",
|
||||
wrap_vec_return!(get_users_by_age, &OurDB, INT => User)
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"get_all_user_ids_rhai",
|
||||
wrap_vec_return!(get_all_user_ids, () => INT)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test get_all_users
|
||||
let script1 = r#"
|
||||
let users = get_all_users_rhai(db);
|
||||
users.len()
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"users.len()",
|
||||
(db.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 2);
|
||||
|
||||
// Test get_users_by_age
|
||||
let script2 = r#"
|
||||
let users = get_users_by_age_rhai(db, 30);
|
||||
users.len()
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"users.len()",
|
||||
(db, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 1);
|
||||
|
||||
// Test get_all_user_ids
|
||||
let script3 = r#"
|
||||
let ids = get_all_user_ids_rhai();
|
||||
ids.len()
|
||||
"#;
|
||||
|
||||
let result3 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script3,
|
||||
"ids.len()",
|
||||
()
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result3, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_option_vec_return() {
|
||||
let mut engine = Engine::new();
|
||||
let db = OurDB::new(false);
|
||||
|
||||
// Register the wrapped function
|
||||
engine.register_fn(
|
||||
"find_users_by_name_rhai",
|
||||
wrap_option_vec_return!(find_users_by_name, &OurDB, String => User)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with found users
|
||||
let script1 = r#"
|
||||
let users = find_users_by_name_rhai(db, "User");
|
||||
users.len()
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"users.len()",
|
||||
(db.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 2);
|
||||
|
||||
// Test with no users found
|
||||
let script2 = r#"
|
||||
let users = find_users_by_name_rhai(db, "");
|
||||
if users == () { 42 } else { 0 }
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"if users == () { 42 } else { 0 }",
|
||||
(db, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_option_return_result() {
|
||||
let mut engine = Engine::new();
|
||||
let db_ok = OurDB::new(false);
|
||||
let db_err = OurDB::new(true);
|
||||
|
||||
// Register the wrapped function
|
||||
engine.register_result_fn(
|
||||
"get_user_by_id_result_rhai",
|
||||
wrap_option_return_result!(get_user_by_id_result, &OurDB, INT => User, DBError)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with existing user
|
||||
let script1 = r#"
|
||||
let user = get_user_by_id_result_rhai(db, 1);
|
||||
user.id
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"user.id",
|
||||
(db_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 1);
|
||||
|
||||
// Test with non-existing user
|
||||
let script2 = r#"
|
||||
let user = get_user_by_id_result_rhai(db, 0);
|
||||
if user == () { 42 } else { 0 }
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"if user == () { 42 } else { 0 }",
|
||||
(db_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 42);
|
||||
|
||||
// Test with error
|
||||
let script3 = r#"
|
||||
try {
|
||||
let user = get_user_by_id_result_rhai(db, 1);
|
||||
0
|
||||
} catch(err) {
|
||||
if err.contains("DB connection error") { 99 } else { 0 }
|
||||
}
|
||||
"#;
|
||||
|
||||
let result3 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script3,
|
||||
"try_catch_block",
|
||||
(db_err, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result3, 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_vec_return_result() {
|
||||
let mut engine = Engine::new();
|
||||
let db_ok = OurDB::new(false);
|
||||
let db_err = OurDB::new(true);
|
||||
|
||||
// Register the wrapped function
|
||||
engine.register_result_fn(
|
||||
"get_all_users_result_rhai",
|
||||
wrap_vec_return_result!(get_all_users_result, &OurDB => User, DBError)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with successful result
|
||||
let script1 = r#"
|
||||
let users = get_all_users_result_rhai(db);
|
||||
users.len()
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"users.len()",
|
||||
(db_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 2);
|
||||
|
||||
// Test with error
|
||||
let script2 = r#"
|
||||
try {
|
||||
let users = get_all_users_result_rhai(db);
|
||||
0
|
||||
} catch(err) {
|
||||
if err.contains("DB connection error") { 99 } else { 0 }
|
||||
}
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"try_catch_block",
|
||||
(db_err, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_option_vec_return_result() {
|
||||
let mut engine = Engine::new();
|
||||
let db_ok = OurDB::new(false);
|
||||
let db_err = OurDB::new(true);
|
||||
|
||||
// Register the wrapped function
|
||||
engine.register_result_fn(
|
||||
"find_users_by_name_result_rhai",
|
||||
wrap_option_vec_return_result!(find_users_by_name_result, &OurDB, String => User, DBError)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with found users
|
||||
let script1 = r#"
|
||||
let users = find_users_by_name_result_rhai(db, "User");
|
||||
users.len()
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"users.len()",
|
||||
(db_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 2);
|
||||
|
||||
// Test with no users found
|
||||
let script2 = r#"
|
||||
let users = find_users_by_name_result_rhai(db, "");
|
||||
if users == () { 42 } else { 0 }
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"if users == () { 42 } else { 0 }",
|
||||
(db_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 42);
|
||||
|
||||
// Test with error
|
||||
let script3 = r#"
|
||||
try {
|
||||
let users = find_users_by_name_result_rhai(db, "User");
|
||||
0
|
||||
} catch(err) {
|
||||
if err.contains("DB connection error") { 99 } else { 0 }
|
||||
}
|
||||
"#;
|
||||
|
||||
let result3 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script3,
|
||||
"try_catch_block",
|
||||
(db_err, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result3, 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_option_method_result() {
|
||||
let mut engine = Engine::new();
|
||||
let collection_ok = UserCollection::new(false);
|
||||
let collection_err = UserCollection::new(true);
|
||||
|
||||
// Register the wrapped method
|
||||
engine.register_result_fn(
|
||||
"get_by_id_rhai",
|
||||
wrap_option_method_result!(get_by_id, &UserCollection, INT => User, DBError)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with existing user
|
||||
let script1 = r#"
|
||||
let user = get_by_id_rhai(collection, 1);
|
||||
user.id
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"user.id",
|
||||
(collection_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 1);
|
||||
|
||||
// Test with non-existing user
|
||||
let script2 = r#"
|
||||
let user = get_by_id_rhai(collection, 999);
|
||||
if user == () { 42 } else { 0 }
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"if user == () { 42 } else { 0 }",
|
||||
(collection_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 42);
|
||||
|
||||
// Test with error
|
||||
let script3 = r#"
|
||||
try {
|
||||
let user = get_by_id_rhai(collection, 1);
|
||||
0
|
||||
} catch(err) {
|
||||
if err.contains("Collection error") { 99 } else { 0 }
|
||||
}
|
||||
"#;
|
||||
|
||||
let result3 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script3,
|
||||
"try_catch_block",
|
||||
(collection_err, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result3, 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_vec_method_result() {
|
||||
let mut engine = Engine::new();
|
||||
let collection_ok = UserCollection::new(false);
|
||||
let collection_err = UserCollection::new(true);
|
||||
|
||||
// Register the wrapped method
|
||||
engine.register_result_fn(
|
||||
"get_all_rhai",
|
||||
wrap_vec_method_result!(get_all, &UserCollection => User, DBError)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with successful result
|
||||
let script1 = r#"
|
||||
let users = get_all_rhai(collection);
|
||||
users.len()
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"users.len()",
|
||||
(collection_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 2);
|
||||
|
||||
// Test with error
|
||||
let script2 = r#"
|
||||
try {
|
||||
let users = get_all_rhai(collection);
|
||||
0
|
||||
} catch(err) {
|
||||
if err.contains("Collection error") { 99 } else { 0 }
|
||||
}
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"try_catch_block",
|
||||
(collection_err, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_option_vec_method_result() {
|
||||
let mut engine = Engine::new();
|
||||
let collection_ok = UserCollection::new(false);
|
||||
let collection_err = UserCollection::new(true);
|
||||
|
||||
// Register the wrapped method
|
||||
engine.register_result_fn(
|
||||
"find_by_name_rhai",
|
||||
wrap_option_vec_method_result!(find_by_name, &UserCollection, String => User, DBError)
|
||||
);
|
||||
|
||||
// Register the User type
|
||||
engine.build_type::<User>();
|
||||
|
||||
// Test with found users
|
||||
let script1 = r#"
|
||||
let users = find_by_name_rhai(collection, "User");
|
||||
users.len()
|
||||
"#;
|
||||
|
||||
let result1 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script1,
|
||||
"users.len()",
|
||||
(collection_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result1, 2);
|
||||
|
||||
// Test with no users found
|
||||
let script2 = r#"
|
||||
let users = find_by_name_rhai(collection, "");
|
||||
if users == () { 42 } else { 0 }
|
||||
"#;
|
||||
|
||||
let result2 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script2,
|
||||
"if users == () { 42 } else { 0 }",
|
||||
(collection_ok.clone(), )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result2, 42);
|
||||
|
||||
// Test with error
|
||||
let script3 = r#"
|
||||
try {
|
||||
let users = find_by_name_rhai(collection, "User");
|
||||
0
|
||||
} catch(err) {
|
||||
if err.contains("Collection error") { 99 } else { 0 }
|
||||
}
|
||||
"#;
|
||||
|
||||
let result3 = engine.call_fn::<INT>(
|
||||
&mut rhai::Scope::new(),
|
||||
script3,
|
||||
"try_catch_block",
|
||||
(collection_err, )
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(result3, 99);
|
||||
}
|
Reference in New Issue
Block a user