This repository has been archived on 2025-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
rhaj/rhai_wrapper/examples/user_management_example.rs
2025-05-13 02:00:35 +03:00

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