more efforts to automate rhai bindings

This commit is contained in:
timurgordon
2025-05-13 02:00:35 +03:00
parent 16ad4f5743
commit ec4769a6b0
14 changed files with 3174 additions and 52 deletions

242
rhai_wrapper/Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

View 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!");
}

View 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(())
}

View 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!");

View File

@@ -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.

View File

@@ -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);
}
}

View 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);
}