## AI Prompt: Generate `rhai.rs` for a new Rust Model **Objective:** Create a `rhai.rs` file to expose the Rust model `[YourModelName]` (and any related owned sub-models like `[YourSubModelName]`) to the Rhai scripting engine. This file should allow Rhai scripts to create, manipulate, and retrieve instances of `[YourModelName]`. **Input Requirements (Provide this information for your specific model):** 1. **Rust Struct Definition(s):** ```rust // Example for [YourModelName] #[derive(Clone, Debug, Serialize, Deserialize)] // Ensure necessary derives pub struct [YourModelName] { pub base_data: BaseModelData, // Common field for ID pub unique_id_field: String, // Example: like flow_uuid pub name: String, pub status: String, // If it owns a collection of sub-models: pub sub_items: Vec<[YourSubModelName]>, // Other fields... } impl [YourModelName] { // Constructor pub fn new(id: u32, unique_id_field: String /*, other essential params */) -> Self { Self { base_data: BaseModelData::new(id), unique_id_field, name: "".to_string(), // Default or passed in status: "".to_string(), // Default or passed in sub_items: Vec::new(), // ... } } // Builder methods pub fn name(mut self, name: String) -> Self { self.name = name; self } pub fn status(mut self, status: String) -> Self { self.status = status; self } // Method to add sub-items pub fn add_sub_item(mut self, item: [YourSubModelName]) -> Self { self.sub_items.push(item); self } // Other methods to expose... } // Example for [YourSubModelName] (if applicable) #[derive(Clone, Debug, Serialize, Deserialize)] pub struct [YourSubModelName] { pub id: u32, pub description: String, // ... } impl [YourSubModelName] { pub fn new(id: u32, description: String) -> Self { Self { id, description } } // Builder methods for sub-model... } ``` 2. **Key ID fields that need `i64` (Rhai) to `u32` (Rust) conversion:** (e.g., `base_data.id`, `[YourSubModelName].id`, any foreign key IDs). **Implementation Guidelines for `rhai.rs`:** 1. **File Structure:** * Start with necessary imports: `rhai::{Dynamic, Engine, EvalAltResult, NativeCallContext, Position}`, `std::sync::Arc`, your model structs, `BaseModelData`, `OurDB`. * Include the `i64_to_u32` helper function. * Define `pub fn register_[your_model_name]_rhai_module(engine: &mut Engine, db: Arc) { ... }`. 2. **Helper Function for ID Conversion:** ```rust fn i64_to_u32(val: i64, context_pos: Position, field_name: &str, object_name: &str) -> Result> { val.try_into().map_err(|_e| { Box::new(EvalAltResult::ErrorArithmetic( format!("Conversion error for {} in {} from i64 to u32", field_name, object_name), context_pos, )) }) } ``` 3. **Constructors (e.g., `new_[your_model_name]`):** * Use `NativeCallContext` for manual argument parsing and `i64_to_u32` conversion for ID fields. * Example: ```rust engine.register_fn("new_[your_model_name]", move |context: NativeCallContext, id_i64: i64, unique_id_str: String /*, other_args... */| -> Result<[YourModelName], Box> { let id_u32 = i64_to_u32(id_i64, context.position(), "id", "new_[your_model_name]")?; Ok([YourModelName]::new(id_u32, unique_id_str /*, ... */)) }); ``` * Do the same for `new_[your_sub_model_name]` if applicable. * **Note on `adapter_macros`**: `adapt_rhai_i64_input_fn!` is generally NOT suitable for constructors with multiple arguments or mixed types (e.g., `u32` and `String`). Prefer `NativeCallContext`. 4. **Builder Methods:** * Register functions that take ownership of the model, modify it, and return it. * Example: ```rust engine.register_fn("name", |model: [YourModelName], name_val: String| -> [YourModelName] { model.name(name_val) }); engine.register_fn("status", |model: [YourModelName], status_val: String| -> [YourModelName] { model.status(status_val) }); // For adding sub-items (if applicable) engine.register_fn("add_sub_item", |model: [YourModelName], item: [YourSubModelName]| -> [YourModelName] { model.add_sub_item(item) }); ``` 5. **Getters:** * Use `engine.register_get("field_name", |model: &mut [YourModelName]| -> Result> { Ok(model.field.clone()) });` * For `base_data.id` (u32), cast to `i64` for Rhai: `Ok(model.base_data.id as i64)` * **For `Vec<[YourSubModelName]>` fields (e.g., `sub_items`):** Convert to `rhai::Array`. ```rust engine.register_get("sub_items", |model: &mut [YourModelName]| -> Result> { let rhai_array = model.sub_items.iter().cloned().map(Dynamic::from).collect::(); Ok(rhai_array) }); ``` (Ensure `[YourSubModelName]` is `Clone` and works with `Dynamic::from`). 6. **Setters (Less common if using a full builder pattern, but can be useful):** * Use `engine.register_set("field_name", |model: &mut [YourModelName], value: FieldType| { model.field = value; Ok(()) });` * If a setter method in Rust takes an ID (e.g., `set_related_id(id: u32)`), and you want to call it from Rhai with an `i64`: * You *could* use `adapter_macros::adapt_rhai_i64_input_method!(YourModelType::set_related_id, u32)` if `YourModelType::set_related_id` takes `&mut self, u32`. * Or, handle manually with `NativeCallContext` if the method signature is more complex or doesn't fit the macro. 7. **Other Custom Methods:** * Register any other public methods from your Rust struct that should be callable. * Example: `engine.register_fn("custom_method_name", |model: &mut [YourModelName]| model.custom_method_in_rust());` 8. **Database Interaction (Using actual `OurDB` methods like `set` and `get_by_id`):** * The `Arc` instance passed to `register_[your_model_name]_rhai_module` should be cloned and *captured* by the closures for DB functions. * The Rhai script will call these functions without explicitly passing the DB instance (e.g., `set_my_model(my_model_instance);`, `let m = get_my_model_by_id(123);`). * Ensure proper error handling, converting DB errors and `Option::None` (for getters) to `Box) let captured_db_for_set = Arc::clone(&db); engine.register_fn("set_[your_model_name]", move |model: [YourModelName]| -> Result<(), Box> { captured_db_for_set.set(&model).map_err(|e| { Box::new(EvalAltResult::ErrorRuntime( format!("Failed to set [YourModelName] (ID: {}): {}", model.base_data.id, e).into(), Position::NONE, )) }) }); ``` * **Example for `get_[your_model_name]_by_id`:** ```rust // In register_[your_model_name]_rhai_module(engine: &mut Engine, db: Arc) let captured_db_for_get = Arc::clone(&db); engine.register_fn("get_[your_model_name]_by_id", move |context: NativeCallContext, id_i64: i64| -> Result<[YourModelName], Box> { let id_u32 = i64_to_u32(id_i64, context.position(), "id", "get_[your_model_name]_by_id")?; captured_db_for_get.get_by_id(id_u32) // Assumes OurDB directly implements Collection<_, [YourModelName]> .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( format!("Error getting [YourModelName] (ID: {}): {}", id_u32, e).into(), Position::NONE, )))? .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime( format!("[YourModelName] with ID {} not found", id_u32).into(), Position::NONE, ))) }); ``` **Example Output Snippet (Illustrating a few parts):** ```rust // #[...] // pub struct MyItem { /* ... */ } // impl MyItem { /* ... */ } // pub fn register_my_item_rhai_module(engine: &mut Engine, db: Arc) { // fn i64_to_u32(...) { /* ... */ } // // engine.register_fn("new_my_item", |ctx: NativeCallContext, id: i64, name: String| { /* ... */ }); // engine.register_fn("name", |item: MyItem, name: String| -> MyItem { /* ... */ }); // engine.register_get("id", |item: &mut MyItem| Ok(item.base_data.id as i64)); // engine.register_get("sub_elements", |item: &mut MyItem| { // Ok(item.sub_elements.iter().cloned().map(Dynamic::from).collect::()) // }); // // ... etc. // } ```