db/heromodels/docs/prompts/rhai_rs_generation_prompt.md
2025-06-27 12:11:04 +03:00

9.2 KiB

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):

    // 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:

    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:
      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:
      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.
      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]:

      // 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:

      // 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):

// #[...]
// 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.
// }