313 lines
10 KiB
Rust
313 lines
10 KiB
Rust
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(())
|
|
}
|