db/prompts/rhai_rs_generation_prompt.md
2025-05-22 03:57:03 +03:00

193 lines
9.2 KiB
Markdown

## 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<OurDB>) { ... }`.
2. **Helper Function for ID Conversion:**
```rust
fn i64_to_u32(val: i64, context_pos: Position, field_name: &str, object_name: &str) -> Result<u32, Box<EvalAltResult>> {
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<EvalAltResult>> {
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<FieldType, Box<EvalAltResult>> { 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<rhai::Array, Box<EvalAltResult>> {
let rhai_array = model.sub_items.iter().cloned().map(Dynamic::from).collect::<rhai::Array>();
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<OurDB>` 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<EvalAltResult::ErrorRuntime(...)`.
* **Example for `set_[your_model_name]`:**
```rust
// In register_[your_model_name]_rhai_module(engine: &mut Engine, db: Arc<OurDB>)
let captured_db_for_set = Arc::clone(&db);
engine.register_fn("set_[your_model_name]",
move |model: [YourModelName]| -> Result<(), Box<EvalAltResult>> {
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<OurDB>)
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<EvalAltResult>> {
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<OurDB>) {
// 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::<rhai::Array>())
// });
// // ... etc.
// }
```