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, } 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 { 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 { 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, comments: HashMap, } type OurDB = Arc>; 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 { let db = db_arc.lock().unwrap(); db.users.get(&id).cloned() } fn get_all_users(db_arc: OurDB) -> Vec { 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 { 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> { let db = db_arc.lock().unwrap(); let users: Vec = 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> { 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") // 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") .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(()) }