# AI Prompt: Generate Rhai Module Integration for Rust Models ## Context You are tasked with creating a Rhai scripting integration for Rust models in the heromodels crate. The integration should follow the builder pattern and provide a comprehensive set of functions for interacting with the models through Rhai scripts. ## Key Concepts to Understand 1. **Builder Pattern vs. Rhai Mutation**: Rust models use builder pattern (methods consume self and return Self), but Rhai requires functions that mutate in place (&mut self). 2. **Database Access**: Database functions are exposed as global functions in a 'db' module namespace (e.g., `db::save_flow`, not `db.save_flow`). 3. **ID Handling**: New objects should use ID 0 to allow database assignment of real IDs. 4. **Function vs. Method Calls**: Rhai scripts must use function calls (e.g., `status(flow, STATUS_DRAFT)`) not method chaining (e.g., `flow.status(STATUS_DRAFT)`). ## Requirements 1. **Module Structure and Imports**: - Create a `rhai.rs` file that exports a function named `register_[model]_rhai_module` which takes an Engine and an Arc - **IMPORTANT**: Import `heromodels_core::Model` trait for access to methods like `get_id` - Use the `#[export_module]` macro to define the Rhai module - Register the module globally with the engine using the correct module registration syntax - Create separate database functions that capture the database reference 2. **Builder Pattern Integration**: - For each model struct marked with `#[model]`, create constructor functions (e.g., `new_[model]`) that accept ID 0 for new objects - For each builder method in the model, create a corresponding Rhai function that: - Takes a mutable reference to the model as first parameter - Uses `std::mem::take` to get ownership (if the model implements Default) or `std::mem::replace` (if it doesn't) - Calls the builder method on the owned object - Assigns the result back to the mutable reference - Returns a clone of the modified object 3. **Field Access**: - **IMPORTANT**: Do NOT create getter methods for fields that can be accessed directly - Use direct field access in example code (e.g., `flow.name`, not `flow.get_name()`) - Only create getter functions for computed properties or when special conversion is needed - Handle special types appropriately (e.g., convert timestamps, IDs, etc.) 4. **Database Functions**: - For each model, implement the following database functions in the `db` module namespace: - `db::save_[model]`: Save the model to the database and return the saved model with assigned ID - `db::get_[model]_by_id`: Retrieve a model by ID - `db::delete_[model]`: Delete a model from the database - `db::list_[model]s`: List all models of this type - **IMPORTANT**: Use `db::function_name` syntax in scripts, not `db.function_name` - Handle errors properly with detailed error messages using debug format `{:?}` for error objects - Convert between Rhai's i64 and Rust's u32/u64 types for IDs and timestamps - Return saved objects from save functions to ensure proper ID handling 5. **Error Handling**: - Use `Box` for error returns - Provide detailed error messages with proper position information - **IMPORTANT**: Use `context.call_position()` not the deprecated `context.position()` - Handle type conversions safely - Format error messages with debug format for error objects: `format!("Error: {:?}", err)` 6. **Type Conversions**: - Create helper functions for converting between Rhai and Rust types - Handle special cases like enums with string representations ## Implementation Pattern ```rust // Helper functions for type conversion fn i64_to_u32(val: i64, position: Position, param_name: &str, function_name: &str) -> Result> { u32::try_from(val).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("{} must be a positive integer less than 2^32 in {}", param_name, function_name).into(), position )) }) } // Model constructor #[rhai_fn(name = "new_[model]")] pub fn new_model() -> Model { Model::new() } // Builder method integration #[rhai_fn(name = "[method_name]", return_raw, global, pure)] pub fn model_method(model: &mut Model, param: Type) -> Result> { let owned_model = std::mem::take(model); // or replace if Default not implemented *model = owned_model.method_name(param); Ok(model.clone()) } // Getter method #[rhai_fn(name = "[field]", global)] pub fn get_model_field(model: &mut Model) -> Type { model.field.clone() } // Database function db_module.set_native_fn("save_[model]", move |model: Model| -> Result> { db_clone.set(&model) .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {}", e).into(), Position::NONE))) .map(|(_, model)| model) }); ``` ## Important Notes 1. All models must follow the builder pattern where methods take ownership of `self` and return `Self` 2. If a model doesn't implement `Default`, use `std::mem::replace` instead of `std::mem::take` 3. Ensure proper error handling for all operations 4. Register all functions with appropriate Rhai names 5. Follow the naming conventions established in existing modules 6. Ensure all database operations are properly wrapped with error handling 7. **CRITICAL**: When creating example scripts, use function calls not method chaining 8. **CRITICAL**: Always use ID 0 for new objects in scripts to allow database assignment 9. **CRITICAL**: After saving objects, use the returned objects with assigned IDs for further operations 10. **CRITICAL**: In Rust examples, include the Model trait import and use direct field access ## Example Rhai Script ```rhai // Create a new flow with ID 0 (database will assign real ID) let flow = new_flow(0, "12345678-1234-1234-1234-123456789012"); name(flow, "Document Approval Flow"); status(flow, STATUS_DRAFT); // Save flow to database and get saved object with assigned ID let saved_flow = db::save_flow(flow); print(`Flow saved to database with ID: ${get_flow_id(saved_flow)}`); // Retrieve flow from database using assigned ID let retrieved_flow = db::get_flow_by_id(get_flow_id(saved_flow)); ``` ## Example Rust Code ```rust use heromodels_core::Model; // Import Model trait for get_id() // Create flow using Rhai let flow: Flow = engine.eval("new_flow(0, \"12345678-1234-1234-1234-123456789012\")").unwrap(); // Access fields directly println!("Flow name: {}", flow.name); // Save to database using Rhai function let save_script = "fn save_it(f) { return db::save_flow(f); }"; let save_ast = engine.compile(save_script).unwrap(); let saved_flow: Flow = engine.call_fn(&mut scope, &save_ast, "save_it", (flow,)).unwrap(); println!("Saved flow ID: {}", saved_flow.get_id()); ``` ## Reference Implementations See the existing implementations for examples: 1. `heromodels/src/models/calendar/rhai.rs` - Calendar module Rhai bindings 2. `heromodels/src/models/flow/rhai.rs` - Flow module Rhai bindings 3. `heromodels_rhai/examples/flow/flow_script.rhai` - Example Rhai script 4. `heromodels_rhai/examples/flow/example.rs` - Example Rust code