implement more models and rhai examples

This commit is contained in:
timurgordon 2025-05-22 03:57:03 +03:00
parent aa8ef90f9f
commit 56ec505874
79 changed files with 4546 additions and 182 deletions

44
heromodels/Cargo.lock generated
View File

@ -73,9 +73,9 @@ dependencies = [
[[package]]
name = "bitflags"
version = "2.9.0"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "bumpalo"
@ -85,9 +85,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "cc"
version = "1.2.19"
version = "1.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766"
dependencies = [
"shlex",
]
@ -100,9 +100,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.40"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [
"android-tzdata",
"iana-time-zone",
@ -128,7 +128,7 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
"getrandom 0.2.15",
"getrandom 0.2.16",
"once_cell",
"tiny-keccak",
]
@ -156,9 +156,9 @@ checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
[[package]]
name = "getrandom"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
@ -389,7 +389,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.15",
"getrandom 0.2.16",
]
[[package]]
@ -552,9 +552,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "2.0.100"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
@ -703,9 +703,9 @@ dependencies = [
[[package]]
name = "windows-core"
version = "0.61.0"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement",
"windows-interface",
@ -744,18 +744,18 @@ checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-result"
version = "0.3.2"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.0"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
"windows-link",
]
@ -771,18 +771,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.24"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.24"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
dependencies = [
"proc-macro2",
"quote",

View File

@ -16,10 +16,14 @@ heromodels-derive = { path = "../heromodels-derive" }
heromodels_core = { path = "../heromodels_core" }
rhai_autobind_macros = { path = "../../rhaj/rhai_autobind_macros" }
rhai_wrapper = { path = "../../rhaj/rhai_wrapper" }
rhai = { version = "1.21.0", features = ["std", "sync", "decimal"] } # Added "decimal" feature, sync for Arc<Mutex<>>
rhai = { version = "1.21.0", features = ["std", "sync", "decimal", "internals"] } # Added "decimal" feature, sync for Arc<Mutex<>>
adapter_macros = { path = "../adapter_macros" }
rhai_client_macros = { path = "../rhai_client_macros" }
[features]
default = []
rhai = []
[dev-dependencies]
chrono = "0.4"
@ -39,6 +43,14 @@ path = "examples/finance_example/main.rs"
name = "calendar_rhai"
path = "examples/calendar_rhai/example.rs"
[[example]]
name = "calendar_rhai_client"
path = "examples/calendar_rhai_client/example.rs"
[[example]]
name = "flow_rhai"
path = "examples/flow_rhai/example.rs"
[[example]]
name = "finance_rhai"
path = "examples/finance_rhai/example.rs"
@ -50,3 +62,23 @@ path = "examples/governance_rhai/example.rs"
[[example]]
name = "governance_rhai_client"
path = "examples/governance_rhai_client/example.rs"
[[example]]
name = "flow_example"
path = "examples/flow_example.rs"
[[example]]
name = "legal_rhai"
path = "examples/legal_rhai/example.rs"
required-features = ["rhai"]
[[example]]
name = "project_rhai"
path = "examples/project_rhai/example.rs"
required-features = ["rhai"]
[[example]]
name = "biz_rhai"
path = "examples/biz_rhai/example.rs"
required-features = ["rhai"]

BIN
heromodels/data/0.db Normal file

Binary file not shown.

BIN
heromodels/data/lookup/data Normal file

Binary file not shown.

View File

@ -0,0 +1,277 @@
// Hero Models - Biz Rhai Example
print("Hero Models - Biz Rhai Example");
print("===============================");
print("DB instance will be implicitly passed to DB functions.");
// --- Enum Constants ---
print("\n--- Enum Constants ---");
print(`CompanyStatus Active: ${CompanyStatusConstants::Active}`);
print(`CompanyStatus Inactive: ${CompanyStatusConstants::Inactive}`);
print(`BusinessType Coop: ${BusinessTypeConstants::Coop}`);
print(`BusinessType Global: ${BusinessTypeConstants::Global}`);
// --- Testing Company Model ---
print("\n--- Testing Company Model ---");
let company1_id = 1001;
let company1_uuid = "comp-uuid-alpha-001";
let company1_name = "Innovatech Solutions Ltd.";
let company1_reg = "REG-ISL-2024";
let company1_inc_date = 1672531200; // Jan 1, 2023
print(`Creating a new company (ID: ${company1_id}, UUID: ${company1_uuid})...`);
let company1 = new_company(company1_id, company1_name, company1_reg, company1_inc_date)
.email("contact@innovatech.com")
.phone("+1-555-0100")
.website("https://innovatech.com")
.address("123 Innovation Drive, Tech City, TC 54321")
.business_type(BusinessTypeConstants::Global)
.industry("Technology")
.description("Leading provider of innovative tech solutions.")
.status(CompanyStatusConstants::Active)
.fiscal_year_end("12-31")
.set_base_created_at(1672531200)
.set_base_modified_at(1672531205);
print(`Company 1 Name: ${company1.name}, Status: ${company1.status}`);
print(`Company 1 Email: ${company1.email}, Industry: ${company1.industry}`);
// Save the company to the database
print("\nSaving company1 to database...");
set_company(company1);
print("Company1 saved.");
// Retrieve the company
print(`\nRetrieving company by ID (${company1_id})...`);
let retrieved_company = get_company_by_id(company1_id);
print(`Retrieved Company: ${retrieved_company.name}, Status: ${retrieved_company.status}`);
print(`Retrieved Company Reg No: ${retrieved_company.registration_number}, Website: ${retrieved_company.website}`);
// --- Testing Shareholder Model ---
print("\n--- Testing Shareholder Model ---");
let sh1_id = 2001;
let sh1_user_id = 3001; // Example user ID
let sh1_name = "Alice Wonderland";
let sh1_since = 1672617600; // Example timestamp (Jan 2, 2023)
print(`Creating shareholder 1 (ID: ${sh1_id}) for company ${company1_id}...`);
let shareholder1 = new_shareholder(sh1_id)
.company_id(company1_id)
.user_id(sh1_user_id)
.name(sh1_name)
.shares(1000.0)
.percentage(10.0)
.type_(ShareholderTypeConstants::Individual)
.since(sh1_since)
.set_base_created_at(1672617600);
set_shareholder(shareholder1);
print("Shareholder 1 saved.");
let retrieved_sh1 = get_shareholder_by_id(sh1_id);
print(`Retrieved Shareholder 1: ${retrieved_sh1.name}, Type: ${retrieved_sh1.type_}, Shares: ${retrieved_sh1.shares}`);
let sh2_id = 2002;
let sh2_entity_id = 4001; // Example corporate entity ID
let sh2_name = "Mad Hatter Inc.";
let sh2_since = 1672704000; // Example timestamp (Jan 3, 2023)
print(`\nCreating shareholder 2 (ID: ${sh2_id}) for company ${company1_id}...`);
let shareholder2 = new_shareholder(sh2_id)
.company_id(company1_id)
.user_id(sh2_entity_id) // Using user_id field for entity_id for simplicity in example
.name(sh2_name)
.shares(5000.0)
.percentage(50.0)
.type_(ShareholderTypeConstants::Corporate)
.since(sh2_since)
.set_base_created_at(1672704000);
set_shareholder(shareholder2);
print("Shareholder 2 saved.");
let retrieved_sh2 = get_shareholder_by_id(sh2_id);
print(`Retrieved Shareholder 2: ${retrieved_sh2.name}, Type: ${retrieved_sh2.type_}, Percentage: ${retrieved_sh2.percentage}`);
// --- Testing Update for Company (Example - if setters were fully implemented for complex updates) ---
print("\n--- Testing Update for Company ---");
let updated_company = retrieved_company
.description("Leading global provider of cutting-edge technology solutions and services.")
.status(CompanyStatusConstants::Active)
.phone("+1-555-0199"); // Assume modified_at would be updated by set_company
print(`Updated Company - Name: ${updated_company.name}, New Phone: ${updated_company.phone}`);
set_company(updated_company);
print("Updated Company saved.");
let final_retrieved_company = get_company_by_id(company1_id);
print(`Final Retrieved Company - Description: '${final_retrieved_company.description}', Phone: ${final_retrieved_company.phone}`);
print("\n--- Testing Product Model ---");
// Print ProductType constants
print("\n--- ProductType Constants ---");
print(`Product Type Product: ${ProductTypeConstants::Product}`);
print(`Product Type Service: ${ProductTypeConstants::Service}`);
// Print ProductStatus constants
print("\n--- ProductStatus Constants ---");
print(`Product Status Available: ${ProductStatusConstants::Available}`);
print(`Product Status Unavailable: ${ProductStatusConstants::Unavailable}`);
// Create a product component
let component1 = new_product_component("Super Capacitor")
.description("High-capacity energy storage unit")
.quantity(2);
print(`\nCreated Product Component: ${component1.name}, Qty: ${component1.quantity}`);
// Create Product 1 (a physical product with a component)
let product1_id = 3001;
let product1_name = "Advanced Gadget X";
let product1_creation_time = 1672876800; // Example timestamp (Jan 5, 2023)
print(`\nCreating Product 1 (ID: ${product1_id}): ${product1_name}...`);
let product1 = new_product(product1_id)
.name(product1_name)
.description("A revolutionary gadget with cutting-edge features.")
.price(299.99)
.type_(ProductTypeConstants::Product)
.category("Electronics")
.status(ProductStatusConstants::Available)
.max_amount(1000)
.purchase_till(1704067199) // Dec 31, 2023, 23:59:59
.active_till(1735689599) // Dec 31, 2024, 23:59:59
.add_component(component1)
.set_base_created_at(product1_creation_time);
print("Saving Product 1...");
set_product(product1);
print("Product 1 saved.");
print(`\nRetrieving Product 1 (ID: ${product1_id})...`);
let retrieved_product1 = get_product_by_id(product1_id);
print(`Retrieved Product 1: ${retrieved_product1.name}, Price: ${retrieved_product1.price}, Type: ${retrieved_product1.type_}`);
if retrieved_product1.components.len() > 0 {
print(`Product 1 Component 1: ${retrieved_product1.components[0].name}, Desc: ${retrieved_product1.components[0].description}`);
} else {
print("Product 1 has no components.");
}
// Create Product 2 (a service)
let product2_id = 3002;
let product2_name = "Cloud Backup Service - Pro Plan";
let product2_creation_time = 1672963200; // Example timestamp (Jan 6, 2023)
print(`\nCreating Product 2 (ID: ${product2_id}): ${product2_name}...`);
let product2 = new_product(product2_id)
.name(product2_name)
.description("Unlimited cloud backup with 24/7 support.")
.price(19.99) // Monthly price
.type_(ProductTypeConstants::Service)
.category("Cloud Services")
.status(ProductStatusConstants::Available)
.active_till(1735689599) // Valid for a long time, or represents subscription cycle end
.set_base_created_at(product2_creation_time);
print("Saving Product 2...");
set_product(product2);
print("Product 2 saved.");
print(`\nRetrieving Product 2 (ID: ${product2_id})...`);
let retrieved_product2 = get_product_by_id(product2_id);
print(`Retrieved Product 2: ${retrieved_product2.name}, Category: ${retrieved_product2.category}, Status: ${retrieved_product2.status}`);
if retrieved_product2.components.len() > 0 {
print(`Product 2 has ${retrieved_product2.components.len()} components.`);
} else {
print("Product 2 has no components (as expected for a service).");
}
// --- Testing Sale Model ---
print("\n--- Testing Sale Model ---");
// Print SaleStatus constants
print("\n--- SaleStatus Constants ---");
print(`Sale Status Pending: ${SaleStatusConstants::Pending}`);
print(`Sale Status Completed: ${SaleStatusConstants::Completed}`);
print(`Sale Status Cancelled: ${SaleStatusConstants::Cancelled}`);
// Create SaleItem 1 (using product1 from above)
let sale_item1_product_id = product1_id; // Using product1_id from product example
let sale_item1_name = retrieved_product1.name; // Using name from retrieved product1
let sale_item1_qty = 2;
let sale_item1_unit_price = retrieved_product1.price;
let sale_item1_subtotal = sale_item1_qty * sale_item1_unit_price;
print(`\nCreating SaleItem 1 for Product ID: ${sale_item1_product_id}, Name: ${sale_item1_name}...`);
let sale_item1 = new_sale_item(sale_item1_product_id, sale_item1_name, sale_item1_qty, sale_item1_unit_price, sale_item1_subtotal);
print(`SaleItem 1: Product ID ${sale_item1.product_id}, Qty: ${sale_item1.quantity}, Subtotal: ${sale_item1.subtotal}`);
// Create SaleItem 2 (using product2 from above)
let sale_item2_product_id = product2_id; // Using product2_id from product example
let sale_item2_name = retrieved_product2.name;
let sale_item2_qty = 1;
let sale_item2_unit_price = retrieved_product2.price;
let sale_item2_subtotal = sale_item2_qty * sale_item2_unit_price;
print(`\nCreating SaleItem 2 for Product ID: ${sale_item2_product_id}, Name: ${sale_item2_name}...`);
let sale_item2 = new_sale_item(sale_item2_product_id, sale_item2_name, sale_item2_qty, sale_item2_unit_price, sale_item2_subtotal);
print(`SaleItem 2: Product ID ${sale_item2.product_id}, Qty: ${sale_item2.quantity}, Subtotal: ${sale_item2.subtotal}`);
// Create a Sale
let sale1_id = 4001;
let sale1_customer_id = company1_id; // Example: company1 is the customer
let sale1_date = 1673049600; // Example timestamp (Jan 7, 2023)
let sale1_total_amount = sale_item1.subtotal + sale_item2.subtotal;
print(`\nCreating Sale 1 (ID: ${sale1_id}) for Customer ID: ${sale1_customer_id}...`);
let sale1 = new_sale(
sale1_id,
sale1_customer_id, // for company_id_i64 in Rhai registration
"Temp Buyer Name", // for buyer_name in Rhai registration
"temp@buyer.com", // for buyer_email in Rhai registration
0.0, // for total_amount (will be overridden by builder)
SaleStatusConstants::Pending, // for status (will be overridden by builder)
0 // for sale_date (will be overridden by builder)
)
.customer_id(sale1_customer_id) // Actual field on Sale struct
.status(SaleStatusConstants::Pending)
.sale_date(sale1_date)
.add_item(sale_item1) // Add item one by one
.add_item(sale_item2)
// Alternatively, to set all items at once (if items were already in an array):
// .items([sale_item1, sale_item2])
.total_amount(sale1_total_amount)
.notes("First major sale of the year. Includes Advanced Gadget X and Cloud Backup Pro.")
.set_base_created_at(sale1_date)
.set_base_modified_at(sale1_date + 300) // 5 mins later
.add_base_comment(1) // Example comment ID
.add_base_comment(2);
print(`Sale 1 Created: ID ${sale1.id}, Customer ID: ${sale1.customer_id}, Status: ${sale1.status}, Total: ${sale1.total_amount}`);
print(`Sale 1 Notes: ${sale1.notes}`);
print(`Sale 1 Item Count: ${sale1.items.len()}`);
if sale1.items.len() > 0 {
print(`Sale 1 Item 1: ${sale1.items[0].name}, Qty: ${sale1.items[0].quantity}`);
}
if sale1.items.len() > 1 {
print(`Sale 1 Item 2: ${sale1.items[1].name}, Qty: ${sale1.items[1].quantity}`);
}
print(`Sale 1 Base Comments: ${sale1.comments}`);
// Save Sale 1 to database
print("\nSaving Sale 1 to database...");
set_sale(sale1);
print("Sale 1 saved.");
// Retrieve Sale 1 from database
print(`\nRetrieving Sale 1 by ID (${sale1_id})...`);
let retrieved_sale1 = get_sale_by_id(sale1_id);
print(`Retrieved Sale 1: ID ${retrieved_sale1.id}, Customer: ${retrieved_sale1.customer_id}, Status: ${retrieved_sale1.status}`);
print(`Retrieved Sale 1 Total: ${retrieved_sale1.total_amount}, Notes: '${retrieved_sale1.notes}'`);
if retrieved_sale1.items.len() > 0 {
print(`Retrieved Sale 1 Item 1: ${retrieved_sale1.items[0].name} (Product ID: ${retrieved_sale1.items[0].product_id})`);
}
print("\nBiz Rhai example script finished.");

View File

@ -0,0 +1,41 @@
use rhai::{Engine, EvalAltResult, Scope};
use std::sync::Arc;
use heromodels::db::hero::OurDB; // Corrected path for OurDB
use heromodels::models::biz::register_biz_rhai_module; // Corrected path
use std::fs;
fn main() -> Result<(), Box<EvalAltResult>> {
println!("Executing Rhai script: examples/biz_rhai/biz.rhai");
// Create a new Rhai engine
let mut engine = Engine::new();
// Create an Arc<Mutex<OurDB>> instance
// For this example, we'll use an in-memory DB.
// The actual DB path or configuration might come from elsewhere in a real app.
let db_instance = Arc::new(OurDB::new(".", false).expect("Failed to create DB")); // Corrected OurDB::new args and removed Mutex
// Register the biz module with the engine
register_biz_rhai_module(&mut engine, Arc::clone(&db_instance));
// Read the Rhai script from file
let script_path = "examples/biz_rhai/biz.rhai";
let script_content = fs::read_to_string(script_path)
.map_err(|e| Box::new(EvalAltResult::ErrorSystem(format!("Cannot read script file: {}", script_path), e.into())))?;
// Create a new scope
let mut scope = Scope::new();
// Execute the script
match engine.run_with_scope(&mut scope, &script_content) {
Ok(_) => {
println!("Rhai script executed successfully!");
Ok(())
}
Err(e) => {
println!("Rhai script execution failed: {}", e);
println!("Details: {:?}", e);
Err(e)
}
}
}

View File

@ -50,12 +50,12 @@ fn main() {
// --- Create Calendars ---
// Note: Calendar::new directly returns Calendar, no separate .build() step like the user example.
let calendar1 = Calendar::new(1, "Work Calendar")
let calendar1 = Calendar::new(1)
.description("Calendar for all work-related events.")
.add_event(event1.clone())
.add_event(event2.clone());
let calendar2 = Calendar::new(2, "Personal Calendar")
let calendar2 = Calendar::new(2)
.add_event(event3_for_calendar2.clone());

View File

@ -1,11 +1,20 @@
// Get the database instance
let db = get_db();
// Create a new calendar
let calendar = calendar__builder(1);
calendar.name = "My First Calendar";
set_description(calendar, "A calendar for testing Rhai integration");
// Create a new calendar using the constructor and builder methods
print("Creating a new calendar with ID 1 via registered constructor...");
let calendar = new_calendar(1).
name("My First Calendar").
description("A calendar for testing Rhai integration");
let event = new_event(1).
title("My First Event").
description("An event for testing Rhai integration")
.add_attendee(new_attendee(1));
calendar.add_event(1);
print("Type of calendar object: " + type_of(calendar));
print("Created calendar: " + calendar.name);
// Save the calendar to the database
@ -16,20 +25,24 @@ print("Calendar saved to database");
if calendar_exists(db, 1) {
let retrieved_calendar = get_calendar_by_id(db, 1);
print("Retrieved calendar: " + retrieved_calendar.name);
let desc = get_description(retrieved_calendar);
if desc != "" {
// Access the 'description' field directly.
// Note: 'description' is Option<String>. Rhai handles options.
// You might want to check for 'is_some()' or 'is_none()' or use 'unwrap_or()' pattern if needed.
let desc = retrieved_calendar.description;
if desc != () && desc != "" { // Check against '()' for None and empty string
print("Description: " + desc);
} else {
print("No description available");
print("No description available or it's None");
}
} else {
print("Failed to retrieve calendar with ID 1");
}
// Create another calendar
let calendar2 = calendar__builder(2);
calendar2.name = "My Second Calendar";
set_description(calendar2, "Another calendar for testing");
print("Creating a new calendar with ID 2 using builder methods...");
let calendar2 = new_calendar(2).
name("My Second Calendar").
description("Another calendar for testing");
set_calendar(db, calendar2);
print("Second calendar saved");
@ -38,8 +51,9 @@ print("Second calendar saved");
let all_calendars = get_all_calendars(db);
print("Total calendars: " + all_calendars.len());
for calendar in all_calendars {
print("Calendar ID: " + get_id(calendar) + ", Name: " + calendar.name);
for cal_item in all_calendars { // Renamed loop variable to avoid conflict if 'calendar' is still in scope
// Access 'base_data.id' and 'name' fields directly
print("Calendar ID: " + cal_item.base_data.id + ", Name: " + cal_item.name);
}
// Delete a calendar

View File

@ -1,7 +1,6 @@
use heromodels::db::hero::OurDB;
use heromodels::models::calendar::Calendar;
use heromodels::models::calendar::rhai::register_rhai_engine_functions;
use rhai::Engine;
use rhai_wrapper::wrap_vec_return;
use std::sync::Arc;
use std::{fs, path::Path};
@ -9,66 +8,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize Rhai engine
let mut engine = Engine::new();
// Initialize database
let db = Arc::new(OurDB::new("temp_calendar_db", true).expect("Failed to create database"));
// Initialize database with OurDB
let db = Arc::new(OurDB::new("temp_calendar_db", true).expect("Failed to create database"));
// Register the Calendar type with Rhai
// This function is generated by the #[rhai_model_export] attribute
Calendar::register_rhai_bindings_for_calendar(&mut engine, db.clone());
// Register a function to get the database instance
engine.register_fn("get_db", move || db.clone());
// Register a calendar builder function
engine.register_fn("calendar__builder", |id: i64| {
Calendar::new(id as u32, "New Calendar")
});
// Register setter methods for Calendar properties
engine.register_fn("set_description", |calendar: &mut Calendar, desc: String| {
calendar.description = Some(desc);
});
// Register getter methods for Calendar properties
engine.register_fn("get_description", |calendar: Calendar| -> String {
calendar.description.clone().unwrap_or_default()
});
// Register getter for base_data.id
engine.register_fn("get_id", |calendar: Calendar| -> i64 {
calendar.base_data.id as i64
});
// Register additional functions needed by the script
engine.register_fn("set_calendar", |_db: Arc<OurDB>, _calendar: Calendar| {
// In a real implementation, this would save the calendar to the database
println!("Calendar saved: {}", _calendar.name);
});
engine.register_fn("get_calendar_by_id", |_db: Arc<OurDB>, id: i64| -> Calendar {
// In a real implementation, this would retrieve the calendar from the database
Calendar::new(id as u32, "Retrieved Calendar")
});
// Register a function to check if a calendar exists
engine.register_fn("calendar_exists", |_db: Arc<OurDB>, id: i64| -> bool {
// In a real implementation, this would check if the calendar exists in the database
id == 1 || id == 2
});
// Define the function separately to use with the wrap_vec_return macro
fn get_all_calendars(_db: Arc<OurDB>) -> Vec<Calendar> {
// In a real implementation, this would retrieve all calendars from the database
vec![Calendar::new(1, "Calendar 1"), Calendar::new(2, "Calendar 2")]
}
// Register the function with the wrap_vec_return macro
engine.register_fn("get_all_calendars", wrap_vec_return!(get_all_calendars, Arc<OurDB> => Calendar));
engine.register_fn("delete_calendar_by_id", |_db: Arc<OurDB>, _id: i64| {
// In a real implementation, this would delete the calendar from the database
println!("Calendar deleted with ID: {}", _id);
});
// Register functions using the new centralized method
register_rhai_engine_functions(&mut engine, db.clone());
// Load and evaluate the Rhai script
let script_path = Path::new("examples/calendar_rhai/calendar.rhai");

View File

@ -0,0 +1,109 @@
use rhai::{Engine, Scope, EvalAltResult};
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use std::fs;
// Import the models and the registration function
use heromodels::models::finance::account::Account;
use heromodels::models::finance::asset::{Asset};
use heromodels::models::finance::marketplace::{Listing};
use heromodels::models::finance::rhai::register_rhai_engine_functions;
// Define a simple in-memory mock database for the example
#[derive(Clone, Debug)]
pub struct MockDb {
pub accounts: Arc<Mutex<HashMap<u32, Account>>>,
pub assets: Arc<Mutex<HashMap<u32, Asset>>>,
pub listings: Arc<Mutex<HashMap<u32, Listing>>>,
// Bids are often part of Listings, so a separate HashMap for Bids might not be needed
// unless we want to query bids globally by a unique bid ID.
}
impl MockDb {
fn new() -> Self {
Self {
accounts: Arc::new(Mutex::new(HashMap::new())),
assets: Arc::new(Mutex::new(HashMap::new())),
listings: Arc::new(Mutex::new(HashMap::new())),
}
}
}
fn main() -> Result<(), Box<EvalAltResult>> {
println!("--- Finance Rhai Example ---");
let mut engine = Engine::new();
let mut scope = Scope::new();
let mock_db = Arc::new(MockDb::new());
// Register finance functions and types with the engine
register_rhai_engine_functions(
&mut engine,
Arc::clone(&mock_db.accounts),
Arc::clone(&mock_db.assets),
Arc::clone(&mock_db.listings)
);
println!("Rhai functions registered.");
scope.push("db_instance", mock_db.clone());
let script_path = "examples/finance_rhai/finance.rhai";
println!("Loading script: {}", script_path);
let script = match fs::read_to_string(script_path) {
Ok(s) => s,
Err(e) => {
eprintln!("Error reading script file '{}': {}", script_path, e);
return Err(Box::new(EvalAltResult::ErrorSystem(
"Failed to read script".to_string(),
Box::new(e),
)));
}
};
println!("Executing script...");
match engine.run_with_scope(&mut scope, &script) {
Ok(_) => println!("Script executed successfully!"),
Err(e) => {
eprintln!("Script execution failed: {:?}", e);
return Err(e);
}
}
// Print final state of Accounts
let final_accounts = mock_db.accounts.lock().unwrap();
println!("\n--- Final Mock DB State (Accounts) ---");
if final_accounts.is_empty() {
println!("No accounts in mock DB.");
}
for (id, account) in final_accounts.iter() {
println!("Account ID: {}, Name: '{}', User ID: {}, Assets: {}",
id, account.name, account.user_id, account.assets.len());
}
// Print final state of Assets
let final_assets = mock_db.assets.lock().unwrap();
println!("\n--- Final Mock DB State (Assets) ---");
if final_assets.is_empty() {
println!("No assets in mock DB.");
}
for (id, asset) in final_assets.iter() {
println!("Asset ID: {}, Name: '{}', Amount: {}, Type: {:?}",
id, asset.name, asset.amount, asset.asset_type);
}
// Print final state of Listings
let final_listings = mock_db.listings.lock().unwrap();
println!("\n--- Final Mock DB State (Listings) ---");
if final_listings.is_empty() {
println!("No listings in mock DB.");
}
for (id, listing) in final_listings.iter() {
println!(
"Listing ID: {}, Title: '{}', Type: {:?}, Status: {:?}, Price: {}, Bids: {}",
id, listing.title, listing.listing_type, listing.status, listing.price, listing.bids.len()
);
}
Ok(())
}

View File

@ -0,0 +1,142 @@
// Finance Rhai Script Example
print("--- Starting Finance Rhai Script ---");
// 1. Create an Account
let acc1_id = 101;
let user1_id = 1;
let acc1 = new_account(acc1_id, "User1 Main Account", user1_id, "Primary account for User 1", "LedgerX", "0x123MainSt", "pubkeyUser1");
print(`Created account: ${acc1.name} with ID ${acc1.id}`);
// 2. Save Account to Mock DB
set_account(acc1);
print(`Account ${acc1.id} saved to DB.`);
// 3. Retrieve Account from Mock DB
let fetched_acc1 = get_account_by_id(acc1_id);
print(`Fetched account from DB: ${fetched_acc1.name}, User ID: ${fetched_acc1.user_id}`);
// 4. Create an Asset
let asset1_id = 201;
let asset1 = new_asset(asset1_id, "HeroCoin", "Utility token for Hero Platform", 1000.0, "0xTokenContract", "Erc20", 18);
print(`Created asset: ${asset1.name} (ID: ${asset1.id}), Amount: ${asset1.amount}, Type: ${asset1.asset_type_str}`);
// 5. Save Asset to Mock DB
set_asset(asset1);
print(`Asset ${asset1.id} saved to DB.`);
// 6. Retrieve Asset from Mock DB
let fetched_asset1 = get_asset_by_id(asset1_id);
print(`Fetched asset from DB: ${fetched_asset1.name}, Address: ${fetched_asset1.address}`);
// 7. Add Asset to Account (using the fetched instances)
// Note: Account::add_asset takes Asset by value. We have 'fetched_asset1'.
// The 'add_asset' method on the Account object in Rhai should work with the asset object.
// First, let's ensure fetched_acc1 is mutable if 'add_asset' modifies it directly.
// Or, if add_asset returns a new Account, we'd do: fetched_acc1 = fetched_acc1.add_asset(fetched_asset1);
// For now, assuming add_asset modifies in place (Rust methods taking `&mut self` often allow this if the object itself is mutable in Rhai scope)
// If Account.add_asset was registered as `fn(&mut Account, Asset)` then:
// fetched_acc1.add_asset(fetched_asset1);
// Let's assume for now that `add_asset` is available and works. We'll test this.
// For the current setup, `Account::add_asset` takes `&mut self`, so the object must be mutable in Rhai.
// We might need to re-fetch or re-set the account if we modify it and want the DB to know.
// Let's try to add and then re-save the account.
// For simplicity in this first script, let's focus on marketplace.
// Adding asset to account might require more setup in Rhai regarding object mutability
// or how the add_asset method is exposed. We can refine this later.
print("Skipping adding asset to account directly in this script version for brevity, focusing on listing.");
// 8. Create a Listing for the Asset
let listing1_id = 301;
let current_timestamp = timestamp(); // Rhai's built-in for current unix timestamp (seconds)
let expires_at_ts = current_timestamp + (24 * 60 * 60 * 7); // Expires in 7 days
let listing1 = new_listing(
listing1_id,
"Rare HeroCoin Batch",
"100 HeroCoins for sale",
asset1_id.to_string(), // asset_id as string
"Erc20", // asset_type as string
user1_id.to_string(), // seller_id as string
50.0, // price
"USD", // currency
"FixedPrice", // listing_type as string
expires_at_ts, // expires_at_ts_opt as i64
["token", "herocoin", "sale"], // tags as array of strings
() // image_url_opt as string or ()
);
print(`Created listing: ${listing1.title} (ID: ${listing1.id}), Price: ${listing1.price} ${listing1.currency}`);
print(`Listing type: ${listing1.listing_type_str}, Status: ${listing1.status_str}`);
print(`Listing expires_at_ts_opt: ${listing1.expires_at_ts_opt}`);
// 9. Save Listing to Mock DB
set_listing(listing1);
print(`Listing ${listing1.id} saved to DB.`);
// 10. Retrieve Listing from Mock DB
let fetched_listing1 = get_listing_by_id(listing1_id);
print(`Fetched listing from DB: ${fetched_listing1.title}, Seller ID: ${fetched_listing1.seller_id}`);
print(`Fetched listing asset_id: ${fetched_listing1.asset_id}, asset_type: ${fetched_listing1.asset_type_str}`);
// 11. Demonstrate an auction listing (basic structure)
let listing2_id = 302;
let auction_listing = new_listing(
listing2_id,
"Vintage Hero Figurine",
"Rare collectible, starting bid low!",
"asset_nft_123", // Mock asset ID for an NFT
"Erc721",
user1_id.to_string(),
10.0, // Starting price
"USD",
"Auction",
(), // No expiration for this example
["collectible", "rare", "auction"],
"http://example.com/figurine.png"
);
set_listing(auction_listing);
print(`Created auction listing: ${auction_listing.title} (ID: ${auction_listing.id})`);
// 12. Create a Bid for the auction listing
let bid1 = new_bid(auction_listing.id.to_string(), 2, 12.0, "USD"); // User 2 bids 12 USD
print(`Created bid for listing ${bid1.listing_id} by bidder ${bid1.bidder_id} for ${bid1.amount} ${bid1.currency}`);
print(`Bid status: ${bid1.status_str}, Created at: ${bid1.created_at_ts}`);
// Add bid to listing - this requires the listing object to be mutable
// and the add_listing_bid function to be correctly registered to modify it.
// For now, we'll assume the function works and we'd need to re-set the listing to DB if modified.
// To test `add_listing_bid` properly, we would typically do:
// let mut mutable_auction_listing = get_listing_by_id(listing2_id);
// mutable_auction_listing.add_listing_bid(bid1);
// set_listing(mutable_auction_listing);
// print(`Bid added to listing ${mutable_auction_listing.id}. New bid count: ${mutable_auction_listing.bids_cloned.len()}`);
// For this initial script, we will call the function but acknowledge the db update step for a full flow.
// Get the listing again to add a bid
let auction_listing_for_bid = get_listing_by_id(listing2_id);
print(`Listing '${auction_listing_for_bid.title}' fetched for bidding. Current price: ${auction_listing_for_bid.price}, Bids: ${auction_listing_for_bid.bids_cloned.len()}`);
try {
auction_listing_for_bid.add_listing_bid(bid1);
print(`Bid added to '${auction_listing_for_bid.title}'. New bid count: ${auction_listing_for_bid.bids_cloned.len()}, New price: ${auction_listing_for_bid.price};`);
set_listing(auction_listing_for_bid); // Save updated listing to DB
print("Auction listing with new bid saved to DB;");
} catch (err) {
print(`Error adding bid: ${err}`);
}
// Try to complete sale for the fixed price listing
let listing_to_sell = get_listing_by_id(listing1_id);
let buyer_user_id = 3;
print(`Attempting to complete sale for listing: ${listing_to_sell.title} by buyer ${buyer_user_id}`);
try {
listing_to_sell.complete_listing_sale(buyer_user_id.to_string(), listing_to_sell.price);
print(`Sale completed for listing ${listing_to_sell.id}. New status: ${listing_to_sell.status_str}`);
print(`Buyer ID: ${listing_to_sell.buyer_id_opt}, Sale Price: ${listing_to_sell.sale_price_opt}`);
set_listing(listing_to_sell); // Save updated listing
} catch (err) {
print(`Error completing sale: ${err}`);
}
print("--- Finance Rhai Script Finished ---");

View File

@ -0,0 +1,163 @@
use heromodels::db::{Collection, Db};
use heromodels::models::flow::flow::flow_index::flow_uuid as flow_uuid_idx;
use heromodels::models::flow::flow_step::flow_step_index::flow_id as flow_step_flow_id_idx;
use heromodels::models::flow::{Flow, FlowStep, SignatureRequirement};
use heromodels_core::Model;
// In a real application, you'd use the uuid crate for generating UUIDs:
// use uuid::Uuid;
fn main() {
// Create a new DB instance in /tmp/ourdb_flowbroker, and reset before every run
let db = heromodels::db::hero::OurDB::new("/tmp/ourdb_flowbroker", true)
.expect("Can create DB");
println!("Hero Models - Flow Example");
println!("===========================");
// --- Create a Flow ---
// In a real app: let new_flow_uuid = Uuid::new_v4().to_string();
let new_flow_uuid = "a1b2c3d4-e5f6-7890-1234-567890abcdef"; // Example UUID
let flow1 = Flow::new(
1, // id
new_flow_uuid, // flow_uuid
"Document Approval Flow", // name
"Pending", // status
);
db.collection().expect("can open flow collection").set(&flow1).expect("can set flow1");
println!("Created Flow: {:?}", flow1);
println!("Flow ID: {}", flow1.get_id());
println!("Flow DB Prefix: {}", Flow::db_prefix());
// --- Create FlowSteps for Flow1 ---
let step1_flow1 = FlowStep::new(
101, // id
flow1.get_id(), // flow_id
1, // step_order
"Pending", // status
)
.description("Initial review by manager");
db.collection().expect("can open flow_step collection").set(&step1_flow1).expect("can set step1_flow1");
println!("Created FlowStep: {:?}", step1_flow1);
let step2_flow1 = FlowStep::new(
102, // id
flow1.get_id(), // flow_id
2, // step_order
"Pending", // status
)
.description("Legal team sign-off");
db.collection().expect("can open flow_step collection").set(&step2_flow1).expect("can set step2_flow1");
println!("Created FlowStep: {:?}", step2_flow1);
// --- Create SignatureRequirements for step2_flow1 ---
let sig_req1_step2 = SignatureRequirement::new(
201, // id
step2_flow1.get_id(), // flow_step_id
"pubkey_legal_team_lead_hex", // public_key
"I approve this document for legal compliance.", // message
"Pending", // status
);
db.collection().expect("can open sig_req collection").set(&sig_req1_step2).expect("can set sig_req1_step2");
println!("Created SignatureRequirement: {:?}", sig_req1_step2);
let sig_req2_step2 = SignatureRequirement::new(
202, // id
step2_flow1.get_id(), // flow_step_id
"pubkey_general_counsel_hex", // public_key
"I, as General Counsel, approve this document.", // message
"Pending", // status
);
db.collection().expect("can open sig_req collection").set(&sig_req2_step2).expect("can set sig_req2_step2");
println!("Created SignatureRequirement: {:?}", sig_req2_step2);
// --- Retrieve and Verify ---
// Get Flow by ID
let retrieved_flow = db
.collection::<Flow>()
.expect("can open flow collection")
.get_by_id(flow1.get_id())
.expect("can load stored flow")
.unwrap();
assert_eq!(retrieved_flow.name, flow1.name);
assert_eq!(retrieved_flow.flow_uuid, flow1.flow_uuid);
println!("\nRetrieved Flow by ID: {:?}", retrieved_flow);
// Get Flow by flow_uuid (indexed lookup)
let flows_by_uuid = db
.collection::<Flow>()
.expect("can open flow collection")
.get::<flow_uuid_idx, _>(new_flow_uuid)
.expect("can load flows by uuid");
assert_eq!(flows_by_uuid.len(), 1);
assert_eq!(flows_by_uuid[0].name, flow1.name);
println!("Retrieved Flow by UUID (index): {:?}", flows_by_uuid[0]);
// Get FlowSteps for the retrieved_flow
let steps_for_flow1 = db
.collection::<FlowStep>()
.expect("can open flow_step collection")
.get::<flow_step_flow_id_idx, _>(&retrieved_flow.get_id())
.expect("can load steps for flow1");
assert_eq!(steps_for_flow1.len(), 2);
println!("Retrieved {} FlowSteps for Flow ID {}:", steps_for_flow1.len(), retrieved_flow.get_id());
for step in &steps_for_flow1 {
println!(" - Step ID: {}, Order: {}, Desc: {:?}", step.get_id(), step.step_order, step.description);
}
// --- Update a SignatureRequirement (simulate signing) ---
let mut retrieved_sig_req1 = db
.collection::<SignatureRequirement>()
.expect("can open sig_req collection")
.get_by_id(sig_req1_step2.get_id())
.expect("can load sig_req1")
.unwrap();
println!("\nUpdating SignatureRequirement ID: {}", retrieved_sig_req1.get_id());
retrieved_sig_req1.status = "Signed".to_string();
retrieved_sig_req1.signed_by = Some("pubkey_legal_team_lead_hex_actual_signer".to_string());
retrieved_sig_req1.signature = Some("mock_signature_base64_encoded".to_string());
db.collection().expect("can open sig_req collection").set(&retrieved_sig_req1).expect("can update sig_req1");
let updated_sig_req1 = db
.collection::<SignatureRequirement>()
.expect("can open sig_req collection")
.get_by_id(retrieved_sig_req1.get_id())
.expect("can load updated sig_req1")
.unwrap();
assert_eq!(updated_sig_req1.status, "Signed");
assert_eq!(updated_sig_req1.signature.as_deref(), Some("mock_signature_base64_encoded"));
println!("Updated SignatureRequirement: {:?}", updated_sig_req1);
// --- Delete a FlowStep ---
// (In a real app, you might also want to delete associated SignatureRequirements first, or handle via DB constraints/cascade if supported)
let step1_id_to_delete = step1_flow1.get_id();
db.collection::<FlowStep>()
.expect("can open flow_step collection")
.delete_by_id(step1_id_to_delete)
.expect("can delete step1_flow1");
println!("\nDeleted FlowStep ID: {}", step1_id_to_delete);
let deleted_step = db
.collection::<FlowStep>()
.expect("can open flow_step collection")
.get_by_id(step1_id_to_delete)
.expect("attempt to load deleted step");
assert!(deleted_step.is_none());
println!("Verified FlowStep ID {} is deleted.", step1_id_to_delete);
// Verify only one step remains for flow1
let remaining_steps_for_flow1 = db
.collection::<FlowStep>()
.expect("can open flow_step collection")
.get::<flow_step_flow_id_idx, _>(&retrieved_flow.get_id())
.expect("can load remaining steps for flow1");
assert_eq!(remaining_steps_for_flow1.len(), 1);
assert_eq!(remaining_steps_for_flow1[0].get_id(), step2_flow1.get_id());
println!("Remaining FlowSteps for Flow ID {}: count = {}", retrieved_flow.get_id(), remaining_steps_for_flow1.len());
println!("\nFlow example finished successfully!");
}

View File

@ -0,0 +1,36 @@
use heromodels::db::hero::OurDB;
use heromodels::models::flow::register_flow_rhai_module;
use rhai::Engine;
use std::sync::Arc;
use std::{fs, path::Path};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize Rhai engine
let mut engine = Engine::new();
// Initialize database with OurDB
// Using a temporary/in-memory database for the example
let db = Arc::new(OurDB::new("temp_flow_rhai_db", true).expect("Failed to create database"));
// Register flow Rhai module functions
register_flow_rhai_module(&mut engine, db.clone());
// Load and evaluate the Rhai script
let script_path_str = "examples/flow_rhai/flow.rhai";
let script_path = Path::new(script_path_str);
if !script_path.exists() {
eprintln!("Error: Rhai script not found at {}", script_path_str);
eprintln!("Please ensure the script 'flow.rhai' exists in the 'examples/flow_rhai/' directory.");
return Err(Box::new(std::io::Error::new(std::io::ErrorKind::NotFound, format!("Rhai script not found: {}", script_path_str))));
}
println!("Executing Rhai script: {}", script_path_str);
let script = fs::read_to_string(script_path)?;
match engine.eval::<()>(&script) {
Ok(_) => println!("\nRhai script executed successfully!"),
Err(e) => eprintln!("\nRhai script execution failed: {}\nDetails: {:#?}", e, e),
}
Ok(())
}

View File

@ -0,0 +1,107 @@
// Hero Models - Flow Rhai Example
print("Hero Models - Flow Rhai Example");
print("=============================");
// Helper to format Option<String> (Dynamic in Rhai: String or ()) for printing
fn format_optional(val, placeholder) {
if val == () {
placeholder
} else {
val
}
}
// The database instance is now implicitly passed to DB functions.
print("DB instance will be implicitly passed.");
// --- Test Flow Model ---
print("\n--- Testing Flow Model ---");
// Create a new flow using the constructor and builder methods
print("Creating a new flow (ID: 1, UUID: flow-uuid-001)...");
let flow1 = new_flow(1, "flow-uuid-001")
.name("Document Approval Workflow")
.status("Active");
print("Flow object created: " + flow1);
print("Flow ID: " + flow1.id);
print("Flow UUID: " + flow1.flow_uuid);
print("Flow Name: " + flow1.name);
print("Flow Status: " + flow1.status);
// Save the flow to the database
set_flow(flow1);
print("Flow saved to database.");
// Retrieve the flow
let retrieved_flow = get_flow_by_id(1);
print("Retrieved Flow by ID (1): " + retrieved_flow.name + ", Status: " + retrieved_flow.status);
// --- Test FlowStep Model (as part of Flow) ---
print("\n--- Testing FlowStep Model (as part of Flow) ---");
// Create FlowSteps
print("Creating flow steps and adding to flow...");
let step1 = new_flow_step(101, 1) // id, step_order
.description("Initial Review by Manager")
.status("Pending");
let step2 = new_flow_step(102, 2) // id, step_order. Note: FlowStep ID 102 will be used for sig_req1 & sig_req2
.description("Legal Team Sign-off")
.status("Pending");
// Add steps to the flow created earlier
flow1 = flow1.add_step(step1);
flow1 = flow1.add_step(step2);
print("Flow now has " + flow1.steps.len() + " steps.");
print("First step description: " + format_optional(flow1.steps[0].description, "[No Description]"));
// Re-save the flow with its steps
set_flow(flow1);
print("Flow with steps saved to database.");
// Retrieve the flow and check its steps
let retrieved_flow_with_steps = get_flow_by_id(1);
print("Retrieved Flow by ID (1) has " + retrieved_flow_with_steps.steps.len() + " step(s).");
if retrieved_flow_with_steps.steps.len() > 0 {
print("First step of retrieved flow: " + format_optional(retrieved_flow_with_steps.steps[0].description, "[No Description]"));
}
// --- Test SignatureRequirement Model ---
print("\n--- Testing SignatureRequirement Model ---");
// Create SignatureRequirements (referencing FlowStep ID 102, which is step2)
print("Creating signature requirements for step with ID 102...");
let sig_req1 = new_signature_requirement(201, 102, "pubkey_legal_lead", "Legal Lead: Approve terms.")
.status("Required");
let sig_req2 = new_signature_requirement(202, 102, "pubkey_general_counsel", "General Counsel: Final Approval.")
.status("Required"); // signed_by and signature will default to None (Rust) / () (Rhai)
print("SigReq 1: " + sig_req1.message + " for PubKey: " + sig_req1.public_key + " (Status: " + sig_req1.status + ")");
if sig_req2.signed_by == () {
print("SigReq 2: " + sig_req2.message + " for PubKey: " + sig_req2.public_key + " (Status: " + sig_req2.status + ", Not signed yet)");
} else {
print("SigReq 2: " + sig_req2.message + " for PubKey: " + sig_req2.public_key + " (Status: " + sig_req2.status + ", Signed by: " + format_optional(sig_req2.signed_by, "[Not Signed Yet]") + ")");
}
// Save signature requirements
set_signature_requirement(sig_req1);
set_signature_requirement(sig_req2);
print("SignatureRequirements saved to database.");
// Retrieve a signature requirement
let retrieved_sig_req = get_signature_requirement_by_id(201);
print("Retrieved SignatureRequirement by ID (201): " + retrieved_sig_req.message);
// --- Test updating a SignatureRequirement ---
print("\n--- Testing Update for SignatureRequirement ---");
let updated_sig_req = retrieved_sig_req
.status("Signed")
.signed_by("pubkey_legal_lead_actual_signer_id")
.signature("base64_encoded_signature_data_here");
print("Updated SigReq 1 - Status: " + updated_sig_req.status + ", Signed By: " + format_optional(updated_sig_req.signed_by, "[Not Signed Yet]") + ", Signature: " + format_optional(updated_sig_req.signature, "[No Signature]"));
set_signature_requirement(updated_sig_req); // Save updated
print("Updated SignatureRequirement saved.");
print("\nFlow Rhai example script finished.");

View File

@ -0,0 +1,366 @@
use heromodels::db::hero::OurDB;
use heromodels::models::governance::{Proposal, ProposalStatus, VoteEventStatus, VoteOption, Ballot};
use rhai::Engine;
use rhai_wrapper::wrap_vec_return;
use std::sync::Arc;
use chrono::{Utc, Duration};
use rhai_client_macros::rhai;
// Define the functions we want to expose to Rhai
// We'll only use the #[rhai] attribute on functions with simple types
// Create a proposal (returns a complex type, but takes simple parameters)
fn create_proposal(id: i64, creator_id: String, title: String, description: String) -> Proposal {
let start_date = Utc::now();
let end_date = start_date + Duration::days(14);
Proposal::new(id as u32, creator_id, title, description, start_date, end_date)
}
// Getter functions for Proposal properties
fn get_title(proposal: &Proposal) -> String {
proposal.title.clone()
}
fn get_description(proposal: &Proposal) -> String {
proposal.description.clone()
}
fn get_creator_id(proposal: &Proposal) -> String {
proposal.creator_id.clone()
}
fn get_id(proposal: &Proposal) -> i64 {
proposal.base_data.id as i64
}
fn get_status(proposal: &Proposal) -> String {
format!("{:?}", proposal.status)
}
fn get_vote_status(proposal: &Proposal) -> String {
format!("{:?}", proposal.vote_status)
}
// Functions that operate on Proposal objects
fn add_option_to_proposal(proposal: Proposal, option_id: i64, option_text: String) -> Proposal {
proposal.add_option(option_id as u8, option_text)
}
fn cast_vote_on_proposal(proposal: Proposal, ballot_id: i64, user_id: i64, option_id: i64, shares: i64) -> Proposal {
proposal.cast_vote(ballot_id as u32, user_id as u32, option_id as u8, shares)
}
fn change_proposal_status(proposal: Proposal, status_str: String) -> Proposal {
let new_status = match status_str.as_str() {
"Draft" => ProposalStatus::Draft,
"Active" => ProposalStatus::Active,
"Approved" => ProposalStatus::Approved,
"Rejected" => ProposalStatus::Rejected,
"Cancelled" => ProposalStatus::Cancelled,
_ => ProposalStatus::Draft,
};
proposal.change_proposal_status(new_status)
}
fn change_vote_event_status(proposal: Proposal, status_str: String) -> Proposal {
let new_status = match status_str.as_str() {
"Open" => VoteEventStatus::Open,
"Closed" => VoteEventStatus::Closed,
"Cancelled" => VoteEventStatus::Cancelled,
_ => VoteEventStatus::Open,
};
proposal.change_vote_event_status(new_status)
}
// Functions for accessing proposal options and ballots
fn get_option_count(proposal: &Proposal) -> i64 {
proposal.options.len() as i64
}
fn get_option_at(proposal: &Proposal, index: i64) -> VoteOption {
if index >= 0 && index < proposal.options.len() as i64 {
proposal.options[index as usize].clone()
} else {
VoteOption::new(0, "Invalid Option")
}
}
fn get_option_text(option: &VoteOption) -> String {
option.text.clone()
}
fn get_option_votes(option: &VoteOption) -> i64 {
option.count
}
fn get_ballot_count(proposal: &Proposal) -> i64 {
proposal.ballots.len() as i64
}
fn get_ballot_at(proposal: &Proposal, index: i64) -> Ballot {
if index >= 0 && index < proposal.ballots.len() as i64 {
proposal.ballots[index as usize].clone()
} else {
Ballot::new(0, 0, 0, 0)
}
}
fn get_ballot_user_id(ballot: &Ballot) -> i64 {
ballot.user_id as i64
}
fn get_ballot_option_id(ballot: &Ballot) -> i64 {
ballot.vote_option_id as i64
}
fn get_ballot_shares(ballot: &Ballot) -> i64 {
ballot.shares_count
}
// Simple functions that we can use with the #[rhai] attribute
#[rhai]
fn create_proposal_wrapper(id: i64, creator_id: String, title: String, description: String) -> String {
let proposal = create_proposal(id, creator_id, title, description);
format!("Created proposal with ID: {}", proposal.base_data.id)
}
#[rhai]
fn add_option_wrapper(id: i64, option_id: i64, option_text: String) -> String {
let proposal = create_proposal(id, "user".to_string(), "title".to_string(), "description".to_string());
let updated = add_option_to_proposal(proposal, option_id, option_text.clone());
format!("Added option '{}' to proposal {}", option_text, id)
}
// Database operations
fn save_proposal(_db: Arc<OurDB>, proposal: Proposal) {
println!("Proposal saved: {}", proposal.title);
}
fn get_all_proposals(_db: Arc<OurDB>) -> Vec<Proposal> {
// In a real implementation, this would retrieve all proposals from the database
let start_date = Utc::now();
let end_date = start_date + Duration::days(14);
vec![
Proposal::new(1, "Creator 1", "Proposal 1", "Description 1", start_date, end_date),
Proposal::new(2, "Creator 2", "Proposal 2", "Description 2", start_date, end_date)
]
}
fn delete_proposal_by_id(_db: Arc<OurDB>, id: i64) {
// In a real implementation, this would delete the proposal from the database
println!("Proposal deleted with ID: {}", id);
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize Rhai engine
let mut engine = Engine::new();
// Initialize database
let db = Arc::new(OurDB::new("temp_governance_db", true).expect("Failed to create database"));
// Register the Proposal type with Rhai
// This function is generated by the #[rhai_model_export] attribute
Proposal::register_rhai_bindings_for_proposal(&mut engine, db.clone());
// Register the Ballot type with Rhai
Ballot::register_rhai_bindings_for_ballot(&mut engine, db.clone());
// Create a clone of db for use in the get_db function
let db_for_get_db = db.clone();
// Register a function to get the database instance
engine.register_fn("get_db", move || db_for_get_db.clone());
// Register builder functions for Proposal and related types
engine.register_fn("create_proposal", |id: i64, creator_id: String, title: String, description: String| {
let start_date = Utc::now();
let end_date = start_date + Duration::days(14);
Proposal::new(id as u32, creator_id, title, description, start_date, end_date)
});
engine.register_fn("create_vote_option", |id: i64, text: String| {
VoteOption::new(id as u8, text)
});
engine.register_fn("create_ballot", |id: i64, user_id: i64, vote_option_id: i64, shares_count: i64| {
Ballot::new(id as u32, user_id as u32, vote_option_id as u8, shares_count)
});
// Register getter and setter methods for Proposal properties
engine.register_fn("get_title", get_title);
engine.register_fn("get_description", get_description);
engine.register_fn("get_creator_id", get_creator_id);
engine.register_fn("get_id", get_id);
engine.register_fn("get_status", get_status);
engine.register_fn("get_vote_status", get_vote_status);
// Register methods for proposal operations
engine.register_fn("add_option_to_proposal", add_option_to_proposal);
engine.register_fn("cast_vote_on_proposal", cast_vote_on_proposal);
engine.register_fn("change_proposal_status", change_proposal_status);
engine.register_fn("change_vote_event_status", change_vote_event_status);
// Register functions for database operations
engine.register_fn("save_proposal", save_proposal);
engine.register_fn("get_proposal_by_id", |_db: Arc<OurDB>, id: i64| -> Proposal {
// In a real implementation, this would retrieve the proposal from the database
let start_date = Utc::now();
let end_date = start_date + Duration::days(14);
Proposal::new(id as u32, "Retrieved Creator", "Retrieved Proposal", "Retrieved Description", start_date, end_date)
});
// Register a function to check if a proposal exists
engine.register_fn("proposal_exists", |_db: Arc<OurDB>, id: i64| -> bool {
// In a real implementation, this would check if the proposal exists in the database
id == 1 || id == 2
});
// Register the function with the wrap_vec_return macro
engine.register_fn("get_all_proposals", wrap_vec_return!(get_all_proposals, Arc<OurDB> => Proposal));
engine.register_fn("delete_proposal_by_id", delete_proposal_by_id);
// Register helper functions for accessing proposal options and ballots
engine.register_fn("get_option_count", get_option_count);
engine.register_fn("get_option_at", get_option_at);
engine.register_fn("get_option_text", get_option_text);
engine.register_fn("get_option_votes", get_option_votes);
engine.register_fn("get_ballot_count", get_ballot_count);
engine.register_fn("get_ballot_at", get_ballot_at);
engine.register_fn("get_ballot_user_id", get_ballot_user_id);
engine.register_fn("get_ballot_option_id", get_ballot_option_id);
engine.register_fn("get_ballot_shares", get_ballot_shares);
// Register our wrapper functions that use the #[rhai] attribute
engine.register_fn("create_proposal_wrapper", create_proposal_wrapper);
engine.register_fn("add_option_wrapper", add_option_wrapper);
// Now instead of loading and evaluating a Rhai script, we'll use direct function calls
// to implement the same functionality
// Use the database instance
// Create a new proposal
let proposal = create_proposal(1,
"user_creator_123".to_string(),
"Community Fund Allocation for Q3".to_string(),
"Proposal to allocate funds for community projects in the third quarter.".to_string());
println!("Created Proposal: '{}' (ID: {})",
get_title(&proposal),
get_id(&proposal));
println!("Status: {}, Vote Status: {}",
get_status(&proposal),
get_vote_status(&proposal));
// Add vote options
let mut proposal_with_options = add_option_to_proposal(
proposal, 1, "Approve Allocation".to_string());
proposal_with_options = add_option_to_proposal(
proposal_with_options, 2, "Reject Allocation".to_string());
proposal_with_options = add_option_to_proposal(
proposal_with_options, 3, "Abstain".to_string());
println!("\nAdded Vote Options:");
let option_count = get_option_count(&proposal_with_options);
for i in 0..option_count {
let option = get_option_at(&proposal_with_options, i);
println!("- Option ID: {}, Text: '{}', Votes: {}",
i, get_option_text(&option),
get_option_votes(&option));
}
// Save the proposal to the database
save_proposal(db.clone(), proposal_with_options.clone());
println!("\nProposal saved to database");
// Simulate casting votes
println!("\nSimulating Votes...");
// User 1 votes for 'Approve Allocation' with 100 shares
let mut proposal_with_votes = cast_vote_on_proposal(
proposal_with_options, 101, 1, 1, 100);
// User 2 votes for 'Reject Allocation' with 50 shares
proposal_with_votes = cast_vote_on_proposal(
proposal_with_votes, 102, 2, 2, 50);
// User 3 votes for 'Approve Allocation' with 75 shares
proposal_with_votes = cast_vote_on_proposal(
proposal_with_votes, 103, 3, 1, 75);
// User 4 abstains with 20 shares
proposal_with_votes = cast_vote_on_proposal(
proposal_with_votes, 104, 4, 3, 20);
println!("\nVote Counts After Simulation:");
let option_count = get_option_count(&proposal_with_votes);
for i in 0..option_count {
let option = get_option_at(&proposal_with_votes, i);
println!("- Option ID: {}, Text: '{}', Votes: {}",
i, get_option_text(&option),
get_option_votes(&option));
}
println!("\nBallots Cast:");
let ballot_count = get_ballot_count(&proposal_with_votes);
for i in 0..ballot_count {
let ballot = get_ballot_at(&proposal_with_votes, i);
println!("- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}",
i, get_ballot_user_id(&ballot),
get_ballot_option_id(&ballot),
get_ballot_shares(&ballot));
}
// Change proposal status
let active_proposal = change_proposal_status(
proposal_with_votes, "Active".to_string());
println!("\nChanged Proposal Status to: {}",
get_status(&active_proposal));
// Simulate closing the vote
let closed_proposal = change_vote_event_status(
active_proposal, "Closed".to_string());
println!("Changed Vote Event Status to: {}",
get_vote_status(&closed_proposal));
// Final proposal state
println!("\nFinal Proposal State:");
println!("Title: '{}'", get_title(&closed_proposal));
println!("Status: {}", get_status(&closed_proposal));
println!("Vote Status: {}", get_vote_status(&closed_proposal));
println!("Options:");
let option_count = get_option_count(&closed_proposal);
for i in 0..option_count {
let option = get_option_at(&closed_proposal, i);
println!(" - {}: {} (Votes: {})",
i, get_option_text(&option),
get_option_votes(&option));
}
println!("Total Ballots: {}", get_ballot_count(&closed_proposal));
// Get all proposals from the database
let all_proposals = get_all_proposals(db.clone());
println!("\nTotal Proposals in Database: {}", all_proposals.len());
for proposal in all_proposals {
println!("Proposal ID: {}, Title: '{}'",
get_id(&proposal),
get_title(&proposal));
}
// Delete a proposal
delete_proposal_by_id(db.clone(), 1);
println!("\nDeleted proposal with ID 1");
// Demonstrate the use of Rhai client functions for simple types
println!("\nUsing Rhai client functions for simple types:");
let create_result = create_proposal_wrapper_rhai_client(&engine, 2,
"rhai_user".to_string(),
"Rhai Proposal".to_string(),
"This proposal was created using a Rhai client function".to_string());
println!("{}", create_result);
let add_option_result = add_option_wrapper_rhai_client(&engine, 2, 4, "Rhai Option".to_string());
println!("{}", add_option_result);
println!("\nGovernance Proposal Example Finished.");
Ok(())
}

View File

@ -0,0 +1,115 @@
use heromodels::models::legal::{
Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus,
};
// If BaseModelData's touch method or new method isn't directly part of the public API
// of heromodels crate root, we might need to import heromodels_core.
// For now, assuming `contract.base_data.touch()` would work if BaseModelData has a public `touch` method.
// Or, if `heromodels::models::BaseModelData` is the path.
// A helper for current timestamp (seconds since epoch)
fn current_timestamp_secs() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
fn main() {
println!("Demonstrating Legal Contract Model Usage");
// Create contract signers
let signer1 = ContractSigner::new(
"signer-uuid-alice-001".to_string(),
"Alice Wonderland".to_string(),
"alice@example.com".to_string(),
)
.status(SignerStatus::Pending)
.comments("Awaiting Alice's review and signature.");
let signer2 = ContractSigner::new(
"signer-uuid-bob-002".to_string(),
"Bob The Builder".to_string(),
"bob@example.com".to_string(),
)
.status(SignerStatus::Signed)
.signed_at(current_timestamp_secs() - 86400) // Signed yesterday
.comments("Bob has signed the agreement.");
// Create contract revisions
let revision1 = ContractRevision::new(
1,
"Initial draft: This Service Agreement outlines the terms...".to_string(),
current_timestamp_secs() - (86400 * 2), // 2 days ago
"user-uuid-creator-charlie".to_string(),
)
.comments("Version 1.0 - Initial draft for review.");
let revision2 = ContractRevision::new(
2,
"Updated draft: Added clause 5.b regarding data privacy...".to_string(),
current_timestamp_secs() - 86400, // 1 day ago
"user-uuid-editor-diana".to_string(),
)
.comments("Version 2.0 - Incorporated feedback from legal team.");
// Create a new contract
// base_id (u32 for BaseModelData) and contract_id (String for UUID)
let mut contract = Contract::new(101, "contract-uuid-main-789".to_string())
.title("Master Service Agreement (MSA) - Q3 2025")
.description("Agreement for ongoing IT support services between TechSolutions Inc. and ClientCorp LLC.")
.contract_type("MSA".to_string())
.status(ContractStatus::PendingSignatures)
.created_by("user-uuid-admin-eve".to_string())
.terms_and_conditions("The full terms are detailed in the attached PDF, referenced as 'MSA_Q3_2025_Full.pdf'. This string can hold markdown or JSON summary.")
.start_date(current_timestamp_secs() + (86400 * 7)) // Starts in 7 days
.end_date(current_timestamp_secs() + (86400 * (365 + 7))) // Ends in 1 year + 7 days
.renewal_period_days(30)
.next_renewal_date(current_timestamp_secs() + (86400 * (365 + 7 - 30))) // Approx. 30 days before end_date
.current_version(2)
.add_signer(signer1.clone())
.add_signer(signer2.clone())
.add_revision(revision1.clone())
.add_revision(revision2.clone());
// The `#[model]` derive handles `created_at` and `updated_at` in `base_data`.
// `base_data.touch()` might be called internally by setters or needs explicit call if fields are set directly.
// For builder pattern, the final state of `base_data.updated_at` reflects the time of the last builder call if `touch()` is implicit.
// If not, one might call `contract.base_data.touch()` after building.
println!("\n--- Initial Contract Details ---");
println!("{:#?}", contract);
// Simulate a status change and signing
contract.set_status(ContractStatus::Signed); // This should call base_data.touch()
contract = contract.last_signed_date(current_timestamp_secs());
// If set_status doesn't touch, and last_signed_date is not a builder method that touches:
// contract.base_data.touch(); // Manually update timestamp if needed after direct field manipulation
println!("\n--- Contract Details After Signing ---");
println!("{:#?}", contract);
println!("\n--- Accessing Specific Fields ---");
println!("Contract Title: {}", contract.title);
println!("Contract Status: {:?}", contract.status);
println!("Contract ID (UUID): {}", contract.contract_id);
println!("Base Model ID (u32): {}", contract.base_data.id); // From BaseModelData
println!("Created At (timestamp): {}", contract.base_data.created_at); // From BaseModelData
println!("Updated At (timestamp): {}", contract.base_data.modified_at); // From BaseModelData
if let Some(first_signer_details) = contract.signers.first() {
println!("\nFirst Signer: {} ({})", first_signer_details.name, first_signer_details.email);
println!(" Status: {:?}", first_signer_details.status);
if let Some(signed_time) = first_signer_details.signed_at {
println!(" Signed At: {}", signed_time);
}
}
if let Some(latest_rev) = contract.revisions.iter().max_by_key(|r| r.version) {
println!("\nLatest Revision (v{}):", latest_rev.version);
println!(" Content Snippet: {:.60}...", latest_rev.content);
println!(" Created By: {}", latest_rev.created_by);
println!(" Revision Created At: {}", latest_rev.created_at);
}
println!("\nLegal Contract Model demonstration complete.");
}

View File

@ -0,0 +1,44 @@
use heromodels::db::hero::OurDB;
use heromodels::models::legal::register_legal_rhai_module;
use rhai::Engine;
use std::sync::Arc;
use std::{fs, path::Path};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize Rhai engine
let mut engine = Engine::new();
// Initialize database with OurDB
// Using a temporary/in-memory database for the example (creates files in current dir)
let db_name = "temp_legal_rhai_db";
let db = Arc::new(OurDB::new(db_name, true).expect("Failed to create database"));
// Register legal Rhai module functions
register_legal_rhai_module(&mut engine, db.clone());
// Load and evaluate the Rhai script
let script_path_str = "examples/legal_rhai/legal.rhai";
let script_path = Path::new(script_path_str);
if !script_path.exists() {
eprintln!("Error: Rhai script not found at {}", script_path_str);
eprintln!("Please ensure the script 'legal.rhai' exists in the 'examples/legal_rhai/' directory.");
return Err(Box::new(std::io::Error::new(std::io::ErrorKind::NotFound, format!("Rhai script not found: {}", script_path_str))));
}
println!("Executing Rhai script: {}", script_path_str);
let script = fs::read_to_string(script_path)?;
match engine.eval::<()>(&script) {
Ok(_) => println!("\nRhai script executed successfully!"),
Err(e) => {
eprintln!("\nRhai script execution failed: {}\nDetails: {:#?}", e, e);
// No explicit cleanup in this example, similar to flow_rhai_example
return Err(e.into()); // Propagate the Rhai error
}
}
// No explicit cleanup in this example, similar to flow_rhai_example
Ok(())
}

View File

@ -0,0 +1,119 @@
// heromodels - Legal Module Rhai Example
print("Hero Models - Legal Rhai Example");
print("===============================");
// Helper to format Option<String> (Dynamic in Rhai: String or ()) for printing
fn format_optional_string(val, placeholder) {
if val == () {
placeholder
} else {
val
}
}
// Helper to format Option<i64> (Dynamic in Rhai: i64 or ()) for printing
fn format_optional_int(val, placeholder) {
if val == () {
placeholder
} else {
"" + val // Convert int to string for concatenation
}
}
print("DB instance will be implicitly passed to DB functions.");
// --- Using Enum Constants ---
print(`\n--- Enum Constants ---`);
print(`ContractStatus Draft: ${ContractStatusConstants::Draft}`);
print(`ContractStatus Active: ${ContractStatusConstants::Active}`);
print(`SignerStatus Pending: ${SignerStatusConstants::Pending}`);
print(`SignerStatus Signed: ${SignerStatusConstants::Signed}`);
// --- Test ContractSigner Model ---
print("\n--- Testing ContractSigner Model ---");
let signer1_id = "signer-uuid-001";
let signer1 = new_contract_signer(signer1_id, "Alice Wonderland", "alice@example.com")
.status(SignerStatusConstants::Pending)
.comments("Alice is the primary signatory.");
print(`Signer 1 ID: ${signer1.id}, Name: ${signer1.name}, Email: ${signer1.email}`);
print(`Signer 1 Status: ${signer1.status}, Comments: ${format_optional_string(signer1.comments, "N/A")}`);
print(`Signer 1 Signed At: ${format_optional_int(signer1.signed_at, "Not signed")}`);
let signer2_id = "signer-uuid-002";
let signer2 = new_contract_signer(signer2_id, "Bob The Builder", "bob@example.com")
.status(SignerStatusConstants::Signed)
.signed_at(1678886400) // Example timestamp
.comments("Bob has already signed.");
print(`Signer 2 ID: ${signer2.id}, Name: ${signer2.name}, Status: ${signer2.status}, Signed At: ${format_optional_int(signer2.signed_at, "N/A")}`);
// --- Test ContractRevision Model ---
print("\n--- Testing ContractRevision Model ---");
let revision1_version = 1;
let revision1 = new_contract_revision(revision1_version, "Initial draft content for the agreement, version 1.", 1678880000, "user-admin-01")
.comments("First version of the contract.");
print(`Revision 1 Version: ${revision1.version}, Content: '${revision1.content}', Created At: ${revision1.created_at}, By: ${revision1.created_by}`);
print(`Revision 1 Comments: ${format_optional_string(revision1.comments, "N/A")}`);
let revision2_version = 2;
let revision2 = new_contract_revision(revision2_version, "Updated content with new clauses, version 2.", 1678882200, "user-legal-02");
// --- Test Contract Model ---
print("\n--- Testing Contract Model ---");
let contract1_base_id = 101;
let contract1_uuid = "contract-uuid-xyz-001";
print(`Creating a new contract (ID: ${contract1_base_id}, UUID: ${contract1_uuid})...`);
let contract1 = new_contract(contract1_base_id, contract1_uuid)
.title("Master Service Agreement")
.description("MSA between ACME Corp and Client Inc.")
.contract_type("Services")
.status(ContractStatusConstants::Draft)
.created_by("user-admin-01")
.terms_and_conditions("Standard terms and conditions apply. See Appendix A.")
.start_date(1678900000)
.current_version(revision1.version)
.add_signer(signer1)
.add_revision(revision1);
print(`Contract 1 Title: ${contract1.title}, Status: ${contract1.status}`);
print(`Contract 1 Signers: ${contract1.signers.len()}, Revisions: ${contract1.revisions.len()}`);
// Add more data
contract1 = contract1.add_signer(signer2).add_revision(revision2).current_version(revision2.version);
print(`Contract 1 Updated Signers: ${contract1.signers.len()}, Revisions: ${contract1.revisions.len()}, Current Version: ${contract1.current_version}`);
// Save the contract to the database
print("Saving contract1 to database...");
set_contract(contract1);
print("Contract1 saved.");
// Retrieve the contract
print(`Retrieving contract by ID (${contract1_base_id})...`);
let retrieved_contract = get_contract_by_id(contract1_base_id);
print(`Retrieved Contract: ${retrieved_contract.title}, Status: ${retrieved_contract.status}`);
print(`Retrieved Contract Signers: ${retrieved_contract.signers.len()}, Revisions: ${retrieved_contract.revisions.len()}`);
if retrieved_contract.signers.len() > 0 {
print(`First signer of retrieved contract: ${retrieved_contract.signers[0].name}`);
}
if retrieved_contract.revisions.len() > 0 {
print(`First revision content of retrieved contract: '${retrieved_contract.revisions[0].content}'`);
}
// --- Test updating a Contract ---
print("\n--- Testing Update for Contract ---");
let updated_contract = retrieved_contract
.status(ContractStatusConstants::Active)
.end_date(1700000000)
.description("MSA (Active) between ACME Corp and Client Inc. with new addendum.");
print(`Updated Contract - Title: ${updated_contract.title}, Status: ${updated_contract.status}, End Date: ${format_optional_int(updated_contract.end_date, "N/A")}`);
set_contract(updated_contract); // Save updated
print("Updated Contract saved.");
let final_retrieved_contract = get_contract_by_id(contract1_base_id);
print(`Final Retrieved Contract - Status: ${final_retrieved_contract.status}, Description: '${final_retrieved_contract.description}'`);
print("\nLegal Rhai example script finished.");

View File

@ -0,0 +1,39 @@
use rhai::{Engine, EvalAltResult, Scope};
use std::sync::Arc;
use heromodels::db::hero::OurDB;
use heromodels::models::projects::register_projects_rhai_module;
use std::fs;
fn main() -> Result<(), Box<EvalAltResult>> {
println!("Executing Rhai script: examples/project_rhai/project_test.rhai");
// Create a new Rhai engine
let mut engine = Engine::new();
// Create an Arc<OurDB> instance
let db_instance = Arc::new(OurDB::new(".", false).expect("Failed to create DB"));
// Register the projects module with the engine
register_projects_rhai_module(&mut engine, Arc::clone(&db_instance));
// Read the Rhai script from file
let script_path = "examples/project_rhai/project_test.rhai";
let script_content = fs::read_to_string(script_path)
.map_err(|e| Box::new(EvalAltResult::ErrorSystem(format!("Cannot read script file: {}", script_path), e.into())))?;
// Create a new scope
let mut scope = Scope::new();
// Execute the script
match engine.run_with_scope(&mut scope, &script_content) {
Ok(_) => {
println!("Rhai script executed successfully!");
Ok(())
}
Err(e) => {
println!("Rhai script execution failed: {}", e);
println!("Details: {:?}", e);
Err(e)
}
}
}

View File

@ -0,0 +1,72 @@
// Test script for Project Rhai integration
print("--- Testing Project Rhai Integration ---");
// Create a new project
let p1 = new_project()
.set_base_id(1)
.name("Project Alpha")
.description("This is the first test project.")
.owner_id(101)
.add_member_id(102)
.add_member_id(103)
.member_ids([201, 202, 203]) // Test setting multiple IDs
.add_tag("important")
.add_tag("rhai_test")
.tags(["core", "feature_test"]) // Test setting multiple tags
.status(Status::InProgress)
.priority(Priority::High)
.item_type(ItemType::Feature)
.set_base_created_at(1700000000)
.set_base_modified_at(1700000100)
.add_base_comment(1001);
print("Created project p1: " + p1);
print("p1.name: " + p1.name);
print("p1.description: " + p1.description);
print("p1.owner_id: " + p1.owner_id);
print("p1.member_ids: " + p1.member_ids);
print("p1.tags: " + p1.tags);
print(`p1.status: ${p1.status.to_string()}`);
print(`p1.priority: ${p1.priority.to_string()}`);
print(`p1.item_type: ${p1.item_type.to_string()}`);
print("p1.id: " + p1.id);
print("p1.created_at: " + p1.created_at);
print("p1.modified_at: " + p1.modified_at);
print("p1.comments: " + p1.comments);
// Save to DB
try {
set_project(p1);
print("Project p1 saved successfully.");
} catch (err) {
print("Error saving project p1: " + err);
}
// Retrieve from DB
try {
let retrieved_p1 = get_project_by_id(1);
if retrieved_p1 != () { // Check if Some(project) was returned (None becomes '()')
print("Retrieved project by ID 1: " + retrieved_p1);
print("Retrieved project name: " + retrieved_p1.name);
print("Retrieved project tags: " + retrieved_p1.tags);
} else {
print("Project with ID 1 not found.");
}
} catch (err) {
print("Error retrieving project by ID 1: " + err);
}
// Test non-existent project
try {
let non_existent_project = get_project_by_id(999);
if non_existent_project != () {
print("Error: Found non-existent project 999: " + non_existent_project);
} else {
print("Correctly did not find project with ID 999.");
}
} catch (err) {
print("Error checking for non-existent project: " + err);
}
print("--- Project Rhai Integration Test Complete ---");

View File

BIN
heromodels/index/0.db Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
74

Binary file not shown.

View File

@ -71,3 +71,13 @@ impl<E> From<bincode::error::EncodeError> for Error<E> {
Error::Encode(value)
}
}
impl<E: std::fmt::Debug + std::fmt::Display> std::fmt::Display for Error<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::DB(e) => write!(f, "Database error: {}", e),
Error::Decode(e) => write!(f, "Failed to decode model: {}", e),
Error::Encode(e) => write!(f, "Failed to encode model: {}", e),
}
}
}

View File

@ -228,7 +228,64 @@ where
}
fn get_all(&self) -> Result<Vec<M>, super::Error<Self::Error>> {
todo!("OurDB doesn't have a list all method yet")
let mut index_db = self.index.lock().expect("can lock index DB");
let mut data_db = self.data.lock().expect("can lock data DB");
let prefix = M::db_prefix();
let mut all_object_ids: HashSet<u32> = HashSet::new();
// Use getall to find all index entries (values are serialized HashSet<u32>) for the given model prefix.
match index_db.getall(prefix) {
Ok(list_of_raw_ids_set_bytes) => {
for raw_ids_set_bytes in list_of_raw_ids_set_bytes {
// Each item in the list is a bincode-serialized HashSet<u32> of object IDs.
match bincode::serde::decode_from_slice::<HashSet<u32>, _>(&raw_ids_set_bytes, BINCODE_CONFIG) {
Ok((ids_set, _)) => { // Destructure the tuple (HashSet<u32>, usize)
all_object_ids.extend(ids_set);
}
Err(e) => {
// If deserialization of an ID set fails, propagate as a decode error.
return Err(super::Error::Decode(e));
}
}
}
}
Err(tst::Error::PrefixNotFound(_)) => {
// No index entries found for this prefix, meaning no objects of this type exist.
// Note: tst::getall might return Ok(vec![]) in this case instead of PrefixNotFound.
// Depending on tst implementation, this arm might be redundant if getall returns empty vec.
return Ok(Vec::new());
}
Err(e) => {
// Other TST errors.
return Err(super::Error::DB(e));
}
}
let mut results: Vec<M> = Vec::with_capacity(all_object_ids.len());
for obj_id in all_object_ids {
match Self::get_ourdb_value::<M>(&mut data_db, obj_id) {
Ok(Some(obj)) => {
results.push(obj);
}
Ok(None) => {
// This case implies an inconsistency: an object ID was in an index,
// but the object itself was not found in the data store.
// Log this an issue, but continue processing other valid objects.
// Consider how strictly this should be handled (e.g., return error or log and skip).
eprintln!(
"[heromodels] Warning: Object ID {} found in index for model '{}' but not in data store.",
obj_id,
M::db_prefix()
);
}
Err(e) => {
// If fetching or decoding a specific object fails.
return Err(e);
}
}
}
Ok(results)
}
}

View File

@ -0,0 +1,178 @@
use serde::{Deserialize, Serialize};
use heromodels_core::{BaseModelData, Model, IndexKey, IndexKeyBuilder, Index};
use rhai::{CustomType, TypeBuilder}; // For #[derive(CustomType)]
// --- Enums ---
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum CompanyStatus {
Active,
Inactive,
Suspended,
}
impl Default for CompanyStatus {
fn default() -> Self {
CompanyStatus::Inactive
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BusinessType {
Coop,
Single,
Twin,
Starter,
Global,
}
impl Default for BusinessType {
fn default() -> Self {
BusinessType::Single
}
}
// --- Company Struct ---
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, CustomType)] // Added CustomType
pub struct Company {
pub base_data: BaseModelData,
pub name: String,
pub registration_number: String,
pub incorporation_date: i64, // Changed to i64 // Timestamp
pub fiscal_year_end: String, // e.g., "MM-DD"
pub email: String,
pub phone: String,
pub website: String,
pub address: String,
pub business_type: BusinessType,
pub industry: String,
pub description: String,
pub status: CompanyStatus,
}
// --- Model Trait Implementation ---
impl Model for Company {
fn db_prefix() -> &'static str {
"company"
}
fn get_id(&self) -> u32 {
self.base_data.id
}
fn base_data_mut(&mut self) -> &mut BaseModelData {
&mut self.base_data
}
// Override db_keys to provide custom indexes if needed
fn db_keys(&self) -> Vec<IndexKey> {
vec![
IndexKeyBuilder::new("name").value(self.name.clone()).build(),
IndexKeyBuilder::new("registration_number").value(self.registration_number.clone()).build(),
// Add other relevant keys, e.g., by status or type if frequently queried
]
}
}
// --- Index Implementations (Example) ---
pub struct CompanyNameIndex;
impl Index for CompanyNameIndex {
type Model = Company;
type Key = str;
fn key() -> &'static str {
"name"
}
}
pub struct CompanyRegistrationNumberIndex;
impl Index for CompanyRegistrationNumberIndex {
type Model = Company;
type Key = str;
fn key() -> &'static str {
"registration_number"
}
}
// --- Builder Pattern ---
impl Company {
pub fn new(id: u32, name: String, registration_number: String, incorporation_date: i64) -> Self { // incorporation_date to i64
Self {
base_data: BaseModelData::new(id),
name,
registration_number,
incorporation_date, // This is i64 now
fiscal_year_end: String::new(),
email: String::new(),
phone: String::new(),
website: String::new(),
address: String::new(),
business_type: BusinessType::default(),
industry: String::new(),
description: String::new(),
status: CompanyStatus::default(),
}
}
pub fn fiscal_year_end(mut self, fiscal_year_end: String) -> Self {
self.fiscal_year_end = fiscal_year_end;
self
}
pub fn email(mut self, email: String) -> Self {
self.email = email;
self
}
pub fn phone(mut self, phone: String) -> Self {
self.phone = phone;
self
}
pub fn website(mut self, website: String) -> Self {
self.website = website;
self
}
pub fn address(mut self, address: String) -> Self {
self.address = address;
self
}
pub fn business_type(mut self, business_type: BusinessType) -> Self {
self.business_type = business_type;
self
}
pub fn industry(mut self, industry: String) -> Self {
self.industry = industry;
self
}
pub fn description(mut self, description: String) -> Self {
self.description = description;
self
}
pub fn status(mut self, status: CompanyStatus) -> Self {
self.status = status;
self
}
// Setter for base_data fields if needed directly, though usually handled by Model trait or new()
// Builder methods for created_at and modified_at can be more specific if needed,
// but Model::build() updates modified_at, and BaseModelData::new() sets created_at.
// If direct setting is required for tests or specific scenarios:
pub fn set_base_created_at(mut self, created_at: i64) -> Self {
self.base_data.created_at = created_at;
self
}
pub fn set_base_modified_at(mut self, modified_at: i64) -> Self {
self.base_data.modified_at = modified_at;
self
}
}

View File

@ -0,0 +1,25 @@
// Business models module
// Sub-modules will be declared here
pub mod company;
pub mod product;
// pub mod sale;
// pub mod shareholder;
// pub mod user;
// Re-export main types from sub-modules
pub use company::{Company, CompanyStatus, BusinessType};
pub mod shareholder;
pub use shareholder::{Shareholder, ShareholderType};
pub use product::{Product, ProductType, ProductStatus, ProductComponent};
pub mod sale;
pub use sale::{Sale, SaleItem, SaleStatus};
// pub use user::{User}; // Assuming a simple User model for now
#[cfg(feature = "rhai")]
pub mod rhai;
#[cfg(feature = "rhai")]
pub use rhai::register_biz_rhai_module;

View File

@ -0,0 +1,179 @@
use serde::{Serialize, Deserialize};
use heromodels_core::BaseModelData;
use heromodels_core::Model;
// ProductType represents the type of a product
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub enum ProductType {
#[default]
Product,
Service,
}
// ProductStatus represents the status of a product
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub enum ProductStatus {
#[default]
Available,
Unavailable,
}
// ProductComponent represents a component or sub-part of a product.
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct ProductComponent {
pub name: String,
pub description: String,
pub quantity: u32,
}
impl ProductComponent {
// Minimal constructor
pub fn new(name: String) -> Self {
Self {
name,
description: String::new(),
quantity: 1, // Default quantity to 1
}
}
// Builder methods
pub fn description(mut self, description: String) -> Self {
self.description = description;
self
}
pub fn quantity(mut self, quantity: u32) -> Self {
self.quantity = quantity;
self
}
pub fn name(mut self, name: String) -> Self {
self.name = name;
self
}
}
// Product represents a product or service offered
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Product {
pub base_data: BaseModelData,
pub name: String,
pub description: String,
pub price: f64, // Representing currency.Currency for now
pub type_: ProductType,
pub category: String,
pub status: ProductStatus,
pub max_amount: u16,
pub purchase_till: i64, // Representing ourtime.OurTime
pub active_till: i64, // Representing ourtime.OurTime
pub components: Vec<ProductComponent>,
}
impl Model for Product {
fn get_id(&self) -> u32 {
self.base_data.id
}
fn db_prefix() -> &'static str {
"prod"
}
fn base_data_mut(&mut self) -> &mut BaseModelData {
&mut self.base_data
}
}
impl Product {
pub fn new(id: u32) -> Self {
Self {
base_data: BaseModelData::new(id),
name: String::new(),
description: String::new(),
price: 0.0,
type_: ProductType::default(),
category: String::new(),
status: ProductStatus::default(),
max_amount: 0,
purchase_till: 0,
active_till: 0,
components: Vec::new(),
}
}
// Builder methods
pub fn name(mut self, name: String) -> Self {
self.name = name;
self
}
pub fn description(mut self, description: String) -> Self {
self.description = description;
self
}
pub fn price(mut self, price: f64) -> Self {
self.price = price;
self
}
pub fn type_(mut self, type_: ProductType) -> Self {
self.type_ = type_;
self
}
pub fn category(mut self, category: String) -> Self {
self.category = category;
self
}
pub fn status(mut self, status: ProductStatus) -> Self {
self.status = status;
self
}
pub fn max_amount(mut self, max_amount: u16) -> Self {
self.max_amount = max_amount;
self
}
pub fn purchase_till(mut self, purchase_till: i64) -> Self {
self.purchase_till = purchase_till;
self
}
pub fn active_till(mut self, active_till: i64) -> Self {
self.active_till = active_till;
self
}
pub fn add_component(mut self, component: ProductComponent) -> Self {
self.components.push(component);
self
}
pub fn components(mut self, components: Vec<ProductComponent>) -> Self {
self.components = components;
self
}
// BaseModelData field setters
pub fn set_base_created_at(mut self, time: i64) -> Self {
self.base_data.created_at = time;
self
}
pub fn set_base_modified_at(mut self, time: i64) -> Self {
self.base_data.modified_at = time;
self
}
pub fn add_base_comment_id(mut self, comment_id: u32) -> Self {
self.base_data.comments.push(comment_id);
self
}
pub fn set_base_comment_ids(mut self, comment_ids: Vec<u32>) -> Self {
self.base_data.comments = comment_ids;
self
}
}

View File

@ -0,0 +1,324 @@
use rhai::{Engine, Module, Dynamic, EvalAltResult, Position};
use std::sync::Arc;
use crate::db::Collection; // For db.set and db.get_by_id
use crate::db::hero::OurDB;
use super::company::{Company, CompanyStatus, BusinessType};
use crate::models::biz::shareholder::{Shareholder, ShareholderType};
use crate::models::biz::product::{Product, ProductType, ProductStatus, ProductComponent};
use crate::models::biz::sale::{Sale, SaleItem, SaleStatus};
use heromodels_core::Model;
// Helper function to convert i64 to u32, returning a Rhai error if conversion fails
fn id_from_i64(id_val: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_val).map_err(|_| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert i64 '{}' to u32 for ID", id_val),
Position::NONE,
))
})
}
pub fn register_biz_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
let module = Module::new();
// --- Enum Constants: CompanyStatus ---
let mut status_constants_module = Module::new();
status_constants_module.set_var("Active", Dynamic::from(CompanyStatus::Active.clone()));
status_constants_module.set_var("Inactive", Dynamic::from(CompanyStatus::Inactive.clone()));
status_constants_module.set_var("Suspended", Dynamic::from(CompanyStatus::Suspended.clone()));
engine.register_static_module("CompanyStatusConstants", status_constants_module.into());
engine.register_type_with_name::<CompanyStatus>("CompanyStatus");
// --- Enum Constants: BusinessType ---
let mut business_type_constants_module = Module::new();
business_type_constants_module.set_var("Coop", Dynamic::from(BusinessType::Coop.clone()));
business_type_constants_module.set_var("Single", Dynamic::from(BusinessType::Single.clone()));
business_type_constants_module.set_var("Twin", Dynamic::from(BusinessType::Twin.clone()));
business_type_constants_module.set_var("Starter", Dynamic::from(BusinessType::Starter.clone()));
business_type_constants_module.set_var("Global", Dynamic::from(BusinessType::Global.clone()));
engine.register_static_module("BusinessTypeConstants", business_type_constants_module.into());
engine.register_type_with_name::<BusinessType>("BusinessType");
// --- Company ---
engine.register_type_with_name::<Company>("Company");
// Constructor
engine.register_fn("new_company", |id: i64, name: String, registration_number: String, incorporation_date: i64| -> Result<Company, Box<EvalAltResult>> { Ok(Company::new(id as u32, name, registration_number, incorporation_date)) });
// Getters for Company
engine.register_get("id", |company: &mut Company| -> Result<i64, Box<EvalAltResult>> { Ok(company.get_id() as i64) });
engine.register_get("created_at", |company: &mut Company| -> Result<i64, Box<EvalAltResult>> { Ok(company.base_data.created_at) });
engine.register_get("modified_at", |company: &mut Company| -> Result<i64, Box<EvalAltResult>> { Ok(company.base_data.modified_at) });
engine.register_get("name", |company: &mut Company| -> Result<String, Box<EvalAltResult>> { Ok(company.name.clone()) });
engine.register_get("registration_number", |company: &mut Company| -> Result<String, Box<EvalAltResult>> { Ok(company.registration_number.clone()) });
engine.register_get("incorporation_date", |company: &mut Company| -> Result<i64, Box<EvalAltResult>> { Ok(company.incorporation_date as i64) });
engine.register_get("fiscal_year_end", |company: &mut Company| -> Result<String, Box<EvalAltResult>> { Ok(company.fiscal_year_end.clone()) });
engine.register_get("email", |company: &mut Company| -> Result<String, Box<EvalAltResult>> { Ok(company.email.clone()) });
engine.register_get("phone", |company: &mut Company| -> Result<String, Box<EvalAltResult>> { Ok(company.phone.clone()) });
engine.register_get("website", |company: &mut Company| -> Result<String, Box<EvalAltResult>> { Ok(company.website.clone()) });
engine.register_get("address", |company: &mut Company| -> Result<String, Box<EvalAltResult>> { Ok(company.address.clone()) });
engine.register_get("business_type", |company: &mut Company| -> Result<BusinessType, Box<EvalAltResult>> { Ok(company.business_type.clone()) });
engine.register_get("industry", |company: &mut Company| -> Result<String, Box<EvalAltResult>> { Ok(company.industry.clone()) });
engine.register_get("description", |company: &mut Company| -> Result<String, Box<EvalAltResult>> { Ok(company.description.clone()) });
engine.register_get("status", |company: &mut Company| -> Result<CompanyStatus, Box<EvalAltResult>> { Ok(company.status.clone()) });
// Builder methods for Company
engine.register_fn("fiscal_year_end", |company: Company, fiscal_year_end: String| -> Result<Company, Box<EvalAltResult>> { Ok(company.fiscal_year_end(fiscal_year_end)) });
engine.register_fn("email", |company: Company, email: String| -> Result<Company, Box<EvalAltResult>> { Ok(company.email(email)) });
engine.register_fn("phone", |company: Company, phone: String| -> Result<Company, Box<EvalAltResult>> { Ok(company.phone(phone)) });
engine.register_fn("website", |company: Company, website: String| -> Result<Company, Box<EvalAltResult>> { Ok(company.website(website)) });
engine.register_fn("address", |company: Company, address: String| -> Result<Company, Box<EvalAltResult>> { Ok(company.address(address)) });
engine.register_fn("business_type", |company: Company, business_type: BusinessType| -> Result<Company, Box<EvalAltResult>> { Ok(company.business_type(business_type)) });
engine.register_fn("industry", |company: Company, industry: String| -> Result<Company, Box<EvalAltResult>> { Ok(company.industry(industry)) });
engine.register_fn("description", |company: Company, description: String| -> Result<Company, Box<EvalAltResult>> { Ok(company.description(description)) });
engine.register_fn("status", |company: Company, status: CompanyStatus| -> Result<Company, Box<EvalAltResult>> { Ok(company.status(status)) });
engine.register_fn("set_base_created_at", |company: Company, created_at: i64| -> Result<Company, Box<EvalAltResult>> { Ok(company.set_base_created_at(created_at)) });
engine.register_fn("set_base_modified_at", |company: Company, modified_at: i64| -> Result<Company, Box<EvalAltResult>> { Ok(company.set_base_modified_at(modified_at)) });
// --- Enum Constants: ShareholderType ---
let mut shareholder_type_constants_module = Module::new();
shareholder_type_constants_module.set_var("Individual", Dynamic::from(ShareholderType::Individual.clone()));
shareholder_type_constants_module.set_var("Corporate", Dynamic::from(ShareholderType::Corporate.clone()));
engine.register_static_module("ShareholderTypeConstants", shareholder_type_constants_module.into());
engine.register_type_with_name::<ShareholderType>("ShareholderType");
// --- Shareholder ---
engine.register_type_with_name::<Shareholder>("Shareholder");
// Constructor for Shareholder (minimal, takes only ID)
engine.register_fn("new_shareholder", |id: i64| -> Result<Shareholder, Box<EvalAltResult>> {
Ok(Shareholder::new(id as u32))
});
// Getters for Shareholder
engine.register_get("id", |shareholder: &mut Shareholder| -> Result<i64, Box<EvalAltResult>> { Ok(shareholder.get_id() as i64) });
engine.register_get("created_at", |shareholder: &mut Shareholder| -> Result<i64, Box<EvalAltResult>> { Ok(shareholder.base_data.created_at) });
engine.register_get("modified_at", |shareholder: &mut Shareholder| -> Result<i64, Box<EvalAltResult>> { Ok(shareholder.base_data.modified_at) });
engine.register_get("company_id", |shareholder: &mut Shareholder| -> Result<i64, Box<EvalAltResult>> { Ok(shareholder.company_id as i64) });
engine.register_get("user_id", |shareholder: &mut Shareholder| -> Result<i64, Box<EvalAltResult>> { Ok(shareholder.user_id as i64) });
engine.register_get("name", |shareholder: &mut Shareholder| -> Result<String, Box<EvalAltResult>> { Ok(shareholder.name.clone()) });
engine.register_get("shares", |shareholder: &mut Shareholder| -> Result<f64, Box<EvalAltResult>> { Ok(shareholder.shares) });
engine.register_get("percentage", |shareholder: &mut Shareholder| -> Result<f64, Box<EvalAltResult>> { Ok(shareholder.percentage) });
engine.register_get("type_", |shareholder: &mut Shareholder| -> Result<ShareholderType, Box<EvalAltResult>> { Ok(shareholder.type_.clone()) });
engine.register_get("since", |shareholder: &mut Shareholder| -> Result<i64, Box<EvalAltResult>> { Ok(shareholder.since) });
// Builder methods for Shareholder
engine.register_fn("company_id", |shareholder: Shareholder, company_id: i64| -> Result<Shareholder, Box<EvalAltResult>> { Ok(shareholder.company_id(company_id as u32)) });
engine.register_fn("user_id", |shareholder: Shareholder, user_id: i64| -> Result<Shareholder, Box<EvalAltResult>> { Ok(shareholder.user_id(user_id as u32)) });
engine.register_fn("name", |shareholder: Shareholder, name: String| -> Result<Shareholder, Box<EvalAltResult>> { Ok(shareholder.name(name)) });
engine.register_fn("shares", |shareholder: Shareholder, shares: f64| -> Result<Shareholder, Box<EvalAltResult>> { Ok(shareholder.shares(shares)) });
engine.register_fn("percentage", |shareholder: Shareholder, percentage: f64| -> Result<Shareholder, Box<EvalAltResult>> { Ok(shareholder.percentage(percentage)) });
engine.register_fn("type_", |shareholder: Shareholder, type_: ShareholderType| -> Result<Shareholder, Box<EvalAltResult>> { Ok(shareholder.type_(type_)) });
engine.register_fn("since", |shareholder: Shareholder, since: i64| -> Result<Shareholder, Box<EvalAltResult>> { Ok(shareholder.since(since)) });
engine.register_fn("set_base_created_at", |shareholder: Shareholder, created_at: i64| -> Result<Shareholder, Box<EvalAltResult>> { Ok(shareholder.set_base_created_at(created_at)) });
engine.register_fn("set_base_modified_at", |shareholder: Shareholder, modified_at: i64| -> Result<Shareholder, Box<EvalAltResult>> { Ok(shareholder.set_base_modified_at(modified_at)) });
// --- Enum Constants: ProductType ---
let mut product_type_constants_module = Module::new();
product_type_constants_module.set_var("Product", Dynamic::from(ProductType::Product.clone()));
product_type_constants_module.set_var("Service", Dynamic::from(ProductType::Service.clone()));
engine.register_static_module("ProductTypeConstants", product_type_constants_module.into());
engine.register_type_with_name::<ProductType>("ProductType");
// --- Enum Constants: ProductStatus ---
let mut product_status_constants_module = Module::new();
product_status_constants_module.set_var("Available", Dynamic::from(ProductStatus::Available.clone()));
product_status_constants_module.set_var("Unavailable", Dynamic::from(ProductStatus::Unavailable.clone()));
engine.register_static_module("ProductStatusConstants", product_status_constants_module.into());
engine.register_type_with_name::<ProductStatus>("ProductStatus");
// --- Enum Constants: SaleStatus ---
let mut sale_status_module = Module::new();
sale_status_module.set_var("Pending", Dynamic::from(SaleStatus::Pending.clone()));
sale_status_module.set_var("Completed", Dynamic::from(SaleStatus::Completed.clone()));
sale_status_module.set_var("Cancelled", Dynamic::from(SaleStatus::Cancelled.clone()));
engine.register_static_module("SaleStatusConstants", sale_status_module.into());
engine.register_type_with_name::<SaleStatus>("SaleStatus");
// --- ProductComponent ---
engine.register_type_with_name::<ProductComponent>("ProductComponent")
.register_fn("new_product_component", |name: String| -> Result<ProductComponent, Box<EvalAltResult>> { Ok(ProductComponent::new(name)) })
.register_get("name", |pc: &mut ProductComponent| -> Result<String, Box<EvalAltResult>> { Ok(pc.name.clone()) })
.register_fn("name", |pc: ProductComponent, name: String| -> Result<ProductComponent, Box<EvalAltResult>> { Ok(pc.name(name)) })
.register_get("description", |pc: &mut ProductComponent| -> Result<String, Box<EvalAltResult>> { Ok(pc.description.clone()) })
.register_fn("description", |pc: ProductComponent, description: String| -> Result<ProductComponent, Box<EvalAltResult>> { Ok(pc.description(description)) })
.register_get("quantity", |pc: &mut ProductComponent| -> Result<i64, Box<EvalAltResult>> { Ok(pc.quantity as i64) })
.register_fn("quantity", |pc: ProductComponent, quantity: i64| -> Result<ProductComponent, Box<EvalAltResult>> { Ok(pc.quantity(quantity as u32)) });
// --- Product ---
engine.register_type_with_name::<Product>("Product")
.register_fn("new_product", |id: i64| -> Result<Product, Box<EvalAltResult>> { Ok(Product::new(id as u32)) })
// Getters for Product
.register_get("id", |p: &mut Product| -> Result<i64, Box<EvalAltResult>> { Ok(p.base_data.id as i64) })
.register_get("name", |p: &mut Product| -> Result<String, Box<EvalAltResult>> { Ok(p.name.clone()) })
.register_get("description", |p: &mut Product| -> Result<String, Box<EvalAltResult>> { Ok(p.description.clone()) })
.register_get("price", |p: &mut Product| -> Result<f64, Box<EvalAltResult>> { Ok(p.price) })
.register_get("type_", |p: &mut Product| -> Result<ProductType, Box<EvalAltResult>> { Ok(p.type_.clone()) })
.register_get("category", |p: &mut Product| -> Result<String, Box<EvalAltResult>> { Ok(p.category.clone()) })
.register_get("status", |p: &mut Product| -> Result<ProductStatus, Box<EvalAltResult>> { Ok(p.status.clone()) })
.register_get("max_amount", |p: &mut Product| -> Result<i64, Box<EvalAltResult>> { Ok(p.max_amount as i64) })
.register_get("purchase_till", |p: &mut Product| -> Result<i64, Box<EvalAltResult>> { Ok(p.purchase_till) })
.register_get("active_till", |p: &mut Product| -> Result<i64, Box<EvalAltResult>> { Ok(p.active_till) })
.register_get("components", |p: &mut Product| -> Result<rhai::Array, Box<EvalAltResult>> {
let rhai_array = p.components.iter().cloned().map(Dynamic::from).collect::<rhai::Array>();
Ok(rhai_array)
})
// Getters for BaseModelData fields
.register_get("created_at", |p: &mut Product| -> Result<i64, Box<EvalAltResult>> { Ok(p.base_data.created_at) })
.register_get("modified_at", |p: &mut Product| -> Result<i64, Box<EvalAltResult>> { Ok(p.base_data.modified_at) })
.register_get("comments", |p: &mut Product| -> Result<Vec<i64>, Box<EvalAltResult>> { Ok(p.base_data.comments.iter().map(|&id| id as i64).collect()) })
// Builder methods for Product
.register_fn("name", |p: Product, name: String| -> Result<Product, Box<EvalAltResult>> { Ok(p.name(name)) })
.register_fn("description", |p: Product, description: String| -> Result<Product, Box<EvalAltResult>> { Ok(p.description(description)) })
.register_fn("price", |p: Product, price: f64| -> Result<Product, Box<EvalAltResult>> { Ok(p.price(price)) })
.register_fn("type_", |p: Product, type_: ProductType| -> Result<Product, Box<EvalAltResult>> { Ok(p.type_(type_)) })
.register_fn("category", |p: Product, category: String| -> Result<Product, Box<EvalAltResult>> { Ok(p.category(category)) })
.register_fn("status", |p: Product, status: ProductStatus| -> Result<Product, Box<EvalAltResult>> { Ok(p.status(status)) })
.register_fn("max_amount", |p: Product, max_amount: i64| -> Result<Product, Box<EvalAltResult>> { Ok(p.max_amount(max_amount as u16)) })
.register_fn("purchase_till", |p: Product, purchase_till: i64| -> Result<Product, Box<EvalAltResult>> { Ok(p.purchase_till(purchase_till)) })
.register_fn("active_till", |p: Product, active_till: i64| -> Result<Product, Box<EvalAltResult>> { Ok(p.active_till(active_till)) })
.register_fn("add_component", |p: Product, component: ProductComponent| -> Result<Product, Box<EvalAltResult>> { Ok(p.add_component(component)) })
.register_fn("components", |p: Product, components: Vec<ProductComponent>| -> Result<Product, Box<EvalAltResult>> { Ok(p.components(components)) })
.register_fn("set_base_created_at", |p: Product, time: i64| -> Result<Product, Box<EvalAltResult>> { Ok(p.set_base_created_at(time)) })
.register_fn("set_base_modified_at", |p: Product, time: i64| -> Result<Product, Box<EvalAltResult>> { Ok(p.set_base_modified_at(time)) })
.register_fn("add_base_comment_id", |p: Product, comment_id: i64| -> Result<Product, Box<EvalAltResult>> { Ok(p.add_base_comment_id(id_from_i64(comment_id)?)) })
.register_fn("set_base_comment_ids", |p: Product, comment_ids: Vec<i64>| -> Result<Product, Box<EvalAltResult>> {
let u32_ids = comment_ids.into_iter().map(id_from_i64).collect::<Result<Vec<u32>, _>>()?;
Ok(p.set_base_comment_ids(u32_ids))
});
// --- SaleItem ---
engine.register_type_with_name::<SaleItem>("SaleItem");
engine.register_fn("new_sale_item", |product_id_i64: i64, name: String, quantity_i64: i64, unit_price: f64, subtotal: f64| -> Result<SaleItem, Box<EvalAltResult>> {
Ok(SaleItem::new(id_from_i64(product_id_i64)?, name, quantity_i64 as i32, unit_price, subtotal))
});
// Getters for SaleItem
engine.register_get("product_id", |si: &mut SaleItem| -> Result<i64, Box<EvalAltResult>> { Ok(si.product_id as i64) });
engine.register_get("name", |si: &mut SaleItem| -> Result<String, Box<EvalAltResult>> { Ok(si.name.clone()) });
engine.register_get("quantity", |si: &mut SaleItem| -> Result<i64, Box<EvalAltResult>> { Ok(si.quantity as i64) });
engine.register_get("unit_price", |si: &mut SaleItem| -> Result<f64, Box<EvalAltResult>> { Ok(si.unit_price) });
engine.register_get("subtotal", |si: &mut SaleItem| -> Result<f64, Box<EvalAltResult>> { Ok(si.subtotal) });
engine.register_get("service_active_until", |si: &mut SaleItem| -> Result<Option<i64>, Box<EvalAltResult>> { Ok(si.service_active_until) });
// Builder-style methods for SaleItem
engine.register_type_with_name::<SaleItem>("SaleItem")
.register_fn("product_id", |item: SaleItem, product_id_i64: i64| -> Result<SaleItem, Box<EvalAltResult>> { Ok(item.product_id(id_from_i64(product_id_i64)?)) })
.register_fn("name", |item: SaleItem, name: String| -> Result<SaleItem, Box<EvalAltResult>> { Ok(item.name(name)) })
.register_fn("quantity", |item: SaleItem, quantity_i64: i64| -> Result<SaleItem, Box<EvalAltResult>> { Ok(item.quantity(quantity_i64 as i32)) })
.register_fn("unit_price", |item: SaleItem, unit_price: f64| -> Result<SaleItem, Box<EvalAltResult>> { Ok(item.unit_price(unit_price)) })
.register_fn("subtotal", |item: SaleItem, subtotal: f64| -> Result<SaleItem, Box<EvalAltResult>> { Ok(item.subtotal(subtotal)) })
.register_fn("service_active_until", |item: SaleItem, until: Option<i64>| -> Result<SaleItem, Box<EvalAltResult>> { Ok(item.service_active_until(until)) });
// --- Sale ---
engine.register_type_with_name::<Sale>("Sale");
engine.register_fn("new_sale", |id_i64: i64, company_id_i64: i64, buyer_name: String, buyer_email: String, total_amount: f64, status: SaleStatus, sale_date: i64| -> Result<Sale, Box<EvalAltResult>> {
Ok(Sale::new(id_from_i64(id_i64)?, id_from_i64(company_id_i64)?, buyer_name, buyer_email, total_amount, status, sale_date))
});
// Getters for Sale
engine.register_get("id", |s: &mut Sale| -> Result<i64, Box<EvalAltResult>> { Ok(s.get_id() as i64) });
engine.register_get("customer_id", |s: &mut Sale| -> Result<i64, Box<EvalAltResult>> { Ok(s.company_id as i64) });
engine.register_get("buyer_name", |s: &mut Sale| -> Result<String, Box<EvalAltResult>> { Ok(s.buyer_name.clone()) });
engine.register_get("buyer_email", |s: &mut Sale| -> Result<String, Box<EvalAltResult>> { Ok(s.buyer_email.clone()) });
engine.register_get("total_amount", |s: &mut Sale| -> Result<f64, Box<EvalAltResult>> { Ok(s.total_amount) });
engine.register_get("status", |s: &mut Sale| -> Result<SaleStatus, Box<EvalAltResult>> { Ok(s.status.clone()) });
engine.register_get("sale_date", |s: &mut Sale| -> Result<i64, Box<EvalAltResult>> { Ok(s.sale_date) });
engine.register_get("items", |s: &mut Sale| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(s.items.iter().cloned().map(Dynamic::from).collect::<rhai::Array>())
});
engine.register_get("notes", |s: &mut Sale| -> Result<String, Box<EvalAltResult>> { Ok(s.notes.clone()) });
engine.register_get("created_at", |s: &mut Sale| -> Result<i64, Box<EvalAltResult>> { Ok(s.base_data.created_at) });
engine.register_get("modified_at", |s: &mut Sale| -> Result<i64, Box<EvalAltResult>> { Ok(s.base_data.modified_at) });
// engine.register_get("uuid", |s: &mut Sale| -> Result<Option<String>, Box<EvalAltResult>> { Ok(s.base_data().uuid.clone()) }); // UUID not in BaseModelData
engine.register_get("comments", |s: &mut Sale| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(s.base_data.comments.iter().map(|&id| Dynamic::from(id as i64)).collect::<rhai::Array>())
});
// Builder-style methods for Sale
engine.register_type_with_name::<Sale>("Sale")
.register_fn("customer_id", |s: Sale, customer_id_i64: i64| -> Result<Sale, Box<EvalAltResult>> { Ok(s.company_id(id_from_i64(customer_id_i64)?)) })
.register_fn("buyer_name", |s: Sale, buyer_name: String| -> Result<Sale, Box<EvalAltResult>> { Ok(s.buyer_name(buyer_name)) })
.register_fn("buyer_email", |s: Sale, buyer_email: String| -> Result<Sale, Box<EvalAltResult>> { Ok(s.buyer_email(buyer_email)) })
.register_fn("total_amount", |s: Sale, total_amount: f64| -> Result<Sale, Box<EvalAltResult>> { Ok(s.total_amount(total_amount)) })
.register_fn("status", |s: Sale, status: SaleStatus| -> Result<Sale, Box<EvalAltResult>> { Ok(s.status(status)) })
.register_fn("sale_date", |s: Sale, sale_date: i64| -> Result<Sale, Box<EvalAltResult>> { Ok(s.sale_date(sale_date)) })
.register_fn("add_item", |s: Sale, item: SaleItem| -> Result<Sale, Box<EvalAltResult>> { Ok(s.add_item(item)) })
.register_fn("items", |s: Sale, items: Vec<SaleItem>| -> Result<Sale, Box<EvalAltResult>> { Ok(s.items(items)) })
.register_fn("notes", |s: Sale, notes: String| -> Result<Sale, Box<EvalAltResult>> { Ok(s.notes(notes)) })
.register_fn("set_base_id", |s: Sale, id_i64: i64| -> Result<Sale, Box<EvalAltResult>> { Ok(s.set_base_id(id_from_i64(id_i64)?)) })
// .register_fn("set_base_uuid", |s: Sale, uuid: Option<String>| -> Result<Sale, Box<EvalAltResult>> { Ok(s.set_base_uuid(uuid)) }) // UUID not in BaseModelData
.register_fn("set_base_created_at", |s: Sale, time: i64| -> Result<Sale, Box<EvalAltResult>> { Ok(s.set_base_created_at(time)) })
.register_fn("set_base_modified_at", |s: Sale, time: i64| -> Result<Sale, Box<EvalAltResult>> { Ok(s.set_base_modified_at(time)) })
.register_fn("add_base_comment", |s: Sale, comment_id_i64: i64| -> Result<Sale, Box<EvalAltResult>> { Ok(s.add_base_comment(id_from_i64(comment_id_i64)?)) })
.register_fn("set_base_comments", |s: Sale, comment_ids: Vec<i64>| -> Result<Sale, Box<EvalAltResult>> {
let u32_ids = comment_ids.into_iter().map(id_from_i64).collect::<Result<Vec<u32>, _>>()?;
Ok(s.set_base_comments(u32_ids))
});
// DB functions for Product
let captured_db_for_set_prod = Arc::clone(&db);
engine.register_fn("set_product", move |product: Product| -> Result<(), Box<EvalAltResult>> {
captured_db_for_set_prod.set(&product).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Product (ID: {}): {}", product.get_id(), e).into(), Position::NONE))
})
});
let captured_db_for_get_prod = Arc::clone(&db);
engine.register_fn("get_product_by_id", move |id_i64: i64| -> Result<Product, Box<EvalAltResult>> {
let id_u32 = id_i64 as u32;
captured_db_for_get_prod.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Error getting Product (ID: {}): {}", id_u32, e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Product with ID {} not found", id_u32).into(), Position::NONE)))
});
// DB functions for Sale
let captured_db_for_set_sale = Arc::clone(&db);
engine.register_fn("set_sale", move |sale: Sale| -> Result<(), Box<EvalAltResult>> {
captured_db_for_set_sale.set(&sale).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Sale (ID: {}): {}", sale.get_id(), e).into(), Position::NONE))
})
});
let captured_db_for_get_sale = Arc::clone(&db);
engine.register_fn("get_sale_by_id", move |id_i64: i64| -> Result<Sale, Box<EvalAltResult>> {
let id_u32 = id_from_i64(id_i64)?;
captured_db_for_get_sale.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Error getting Sale (ID: {}): {}", id_u32, e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Sale with ID {} not found", id_u32).into(), Position::NONE)))
});
// Mock DB functions for Shareholder
let captured_db_for_set_sh = Arc::clone(&db);
engine.register_fn("set_shareholder", move |shareholder: Shareholder| -> Result<(), Box<EvalAltResult>> {
captured_db_for_set_sh.set(&shareholder).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Shareholder (ID: {}): {}", shareholder.get_id(), e).into(), Position::NONE))
})
});
let captured_db_for_get_sh = Arc::clone(&db);
engine.register_fn("get_shareholder_by_id", move |id_i64: i64| -> Result<Shareholder, Box<EvalAltResult>> {
let id_u32 = id_i64 as u32;
captured_db_for_get_sh.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Error getting Shareholder (ID: {}): {}", id_u32, e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Shareholder with ID {} not found", id_u32).into(), Position::NONE)))
});
// Mock DB functions for Company
let captured_db_for_set = Arc::clone(&db);
engine.register_fn("set_company", move |company: Company| -> Result<(), Box<EvalAltResult>> {
captured_db_for_set.set(&company).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Company (ID: {}): {}", company.get_id(), e).into(), Position::NONE))
})
});
let captured_db_for_get = Arc::clone(&db);
engine.register_fn("get_company_by_id", move |id_i64: i64| -> Result<Company, Box<EvalAltResult>> {
let id_u32 = id_i64 as u32; // Assuming direct conversion is fine, or use a helper like in flow
captured_db_for_get.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Error getting Company (ID: {}): {}", id_u32, e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Company with ID {} not found", id_u32).into(), Position::NONE)))
});
engine.register_global_module(module.into());
}

View File

@ -0,0 +1,203 @@
use serde::{Deserialize, Serialize};
use heromodels_core::{BaseModelData, Model};
/// Represents the status of a sale.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SaleStatus {
Pending,
Completed,
Cancelled,
}
impl Default for SaleStatus {
fn default() -> Self {
SaleStatus::Pending
}
}
/// Represents an individual item within a Sale.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SaleItem {
pub product_id: u32,
pub name: String, // Denormalized product name at time of sale
pub quantity: i32,
pub unit_price: f64, // Price per unit at time of sale
pub subtotal: f64,
pub service_active_until: Option<i64>, // Optional: For services, date until this specific purchased instance is active
}
impl SaleItem {
/// Creates a new `SaleItem`.
pub fn new(product_id: u32, name: String, quantity: i32, unit_price: f64, subtotal: f64) -> Self {
SaleItem {
product_id,
name,
quantity,
unit_price,
subtotal,
service_active_until: None,
}
}
// Builder methods
pub fn product_id(mut self, product_id: u32) -> Self {
self.product_id = product_id;
self
}
pub fn name(mut self, name: String) -> Self {
self.name = name;
self
}
pub fn quantity(mut self, quantity: i32) -> Self {
self.quantity = quantity;
self
}
pub fn unit_price(mut self, unit_price: f64) -> Self {
self.unit_price = unit_price;
self
}
pub fn subtotal(mut self, subtotal: f64) -> Self {
self.subtotal = subtotal;
self
}
pub fn service_active_until(mut self, service_active_until: Option<i64>) -> Self {
self.service_active_until = service_active_until;
self
}
}
/// Represents a sale of products or services.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Sale {
pub base_data: BaseModelData,
pub company_id: u32,
pub buyer_name: String,
pub buyer_email: String,
pub total_amount: f64,
pub status: SaleStatus,
pub sale_date: i64,
pub items: Vec<SaleItem>,
pub notes: String,
}
impl Model for Sale {
fn db_prefix() -> &'static str {
"sale"
}
fn get_id(&self) -> u32 {
self.base_data.id
}
fn base_data_mut(&mut self) -> &mut BaseModelData {
&mut self.base_data
}
}
impl Sale {
/// Creates a new `Sale`.
pub fn new(
id: u32,
company_id: u32,
buyer_name: String,
buyer_email: String,
total_amount: f64,
status: SaleStatus,
sale_date: i64,
) -> Self {
Sale {
base_data: BaseModelData::new(id),
company_id,
buyer_name,
buyer_email,
total_amount,
status,
sale_date,
items: Vec::new(),
notes: String::new(),
}
}
// Builder methods for Sale
pub fn company_id(mut self, company_id: u32) -> Self {
self.company_id = company_id;
self
}
pub fn buyer_name(mut self, buyer_name: String) -> Self {
self.buyer_name = buyer_name;
self
}
pub fn buyer_email(mut self, buyer_email: String) -> Self {
self.buyer_email = buyer_email;
self
}
pub fn total_amount(mut self, total_amount: f64) -> Self {
self.total_amount = total_amount;
self
}
pub fn status(mut self, status: SaleStatus) -> Self {
self.status = status;
self
}
pub fn sale_date(mut self, sale_date: i64) -> Self {
self.sale_date = sale_date;
self
}
pub fn items(mut self, items: Vec<SaleItem>) -> Self {
self.items = items;
self
}
pub fn add_item(mut self, item: SaleItem) -> Self {
self.items.push(item);
self
}
pub fn notes(mut self, notes: String) -> Self {
self.notes = notes;
self
}
// Builder methods for BaseModelData fields, prefixed with base_
pub fn set_base_id(mut self, id: u32) -> Self {
self.base_data.id = id;
self
}
// UUID is not part of BaseModelData directly in heromodels_core
// pub fn set_base_uuid(mut self, uuid: Option<String>) -> Self {
// self.base_data.uuid = uuid; // Assuming uuid field exists if needed elsewhere
// self
// }
pub fn set_base_created_at(mut self, created_at: i64) -> Self {
self.base_data.created_at = created_at;
self
}
pub fn set_base_modified_at(mut self, modified_at: i64) -> Self {
self.base_data.modified_at = modified_at;
self
}
pub fn add_base_comment(mut self, comment_id: u32) -> Self {
self.base_data.comments.push(comment_id);
self
}
pub fn set_base_comments(mut self, comments: Vec<u32>) -> Self {
self.base_data.comments = comments;
self
}
}

View File

@ -0,0 +1,102 @@
use serde::{Deserialize, Serialize};
use heromodels_core::{BaseModelData, Model};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ShareholderType {
Individual,
Corporate,
}
impl Default for ShareholderType {
fn default() -> Self {
ShareholderType::Individual
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Shareholder {
pub base_data: BaseModelData,
pub company_id: u32,
pub user_id: u32, // Or other entity ID
pub name: String,
pub shares: f64,
pub percentage: f64,
pub type_: ShareholderType,
pub since: i64, // Timestamp
}
impl Shareholder {
pub fn new(id: u32) -> Self {
Self {
base_data: BaseModelData::new(id),
company_id: 0, // Default, to be set by builder
user_id: 0, // Default, to be set by builder
name: String::new(), // Default
shares: 0.0, // Default
percentage: 0.0, // Default
type_: ShareholderType::default(), // Uses ShareholderType's Default impl
since: 0, // Default timestamp, to be set by builder
}
}
// Builder methods
pub fn company_id(mut self, company_id: u32) -> Self {
self.company_id = company_id;
self
}
pub fn user_id(mut self, user_id: u32) -> Self {
self.user_id = user_id;
self
}
pub fn name(mut self, name: String) -> Self {
self.name = name;
self
}
pub fn shares(mut self, shares: f64) -> Self {
self.shares = shares;
self
}
pub fn percentage(mut self, percentage: f64) -> Self {
self.percentage = percentage;
self
}
pub fn type_(mut self, type_: ShareholderType) -> Self {
self.type_ = type_;
self
}
pub fn since(mut self, since: i64) -> Self {
self.since = since;
self
}
// Base data setters if needed for Rhai or specific scenarios
pub fn set_base_created_at(mut self, created_at: i64) -> Self {
self.base_data.created_at = created_at;
self
}
pub fn set_base_modified_at(mut self, modified_at: i64) -> Self {
self.base_data.modified_at = modified_at;
self
}
}
impl Model for Shareholder {
fn db_prefix() -> &'static str {
"shareholder"
}
fn get_id(&self) -> u32 {
self.base_data.id
}
fn base_data_mut(&mut self) -> &mut BaseModelData {
&mut self.base_data
}
}

View File

@ -1,9 +1,9 @@
use chrono::{DateTime, Utc};
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use serde::{Deserialize, Serialize};
use rhai_autobind_macros::rhai_model_export;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
/// Represents the status of an attendee for an event
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
@ -15,19 +15,19 @@ pub enum AttendanceStatus {
}
/// Represents an attendee of an event
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
pub struct Attendee {
/// ID of the user attending
// Assuming user_id might be queryable
pub user_id: String, // Using String for user_id similar to potential external IDs
pub contact_id: u32,
/// Attendance status of the user for the event
pub status: AttendanceStatus,
}
impl Attendee {
pub fn new(user_id: String) -> Self {
pub fn new(contact_id: u32) -> Self {
Self {
user_id,
contact_id,
status: AttendanceStatus::NoResponse,
}
}
@ -39,11 +39,11 @@ impl Attendee {
}
/// Represents an event in a calendar
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
pub struct Event {
/// Unique identifier for the event (e.g., could be a UUID string or u32 if internally managed)
// Events might be looked up by their ID
pub id: String,
/// Base model data
#[serde(flatten)]
pub base_data: BaseModelData,
/// Title of the event
pub title: String,
/// Optional description of the event
@ -60,18 +60,24 @@ pub struct Event {
impl Event {
/// Creates a new event
pub fn new(id: String, title: impl ToString, start_time: DateTime<Utc>, end_time: DateTime<Utc>) -> Self {
pub fn new(id: i64) -> Self {
Self {
id,
title: title.to_string(),
base_data: BaseModelData::new(id as u32),
title: String::new(),
description: None,
start_time,
end_time,
start_time: Utc::now(),
end_time: Utc::now(),
attendees: Vec::new(),
location: None,
}
}
/// Sets the title for the event
pub fn title(mut self, title: impl ToString) -> Self {
self.title = title.to_string();
self
}
/// Sets the description for the event
pub fn description(mut self, description: impl ToString) -> Self {
self.description = Some(description.to_string());
@ -86,22 +92,22 @@ impl Event {
/// Adds an attendee to the event
pub fn add_attendee(mut self, attendee: Attendee) -> Self {
// Prevent duplicate attendees by user_id
if !self.attendees.iter().any(|a| a.user_id == attendee.user_id) {
// Prevent duplicate attendees by contact_id
if !self.attendees.iter().any(|a| a.contact_id == attendee.contact_id) {
self.attendees.push(attendee);
}
self
}
/// Removes an attendee from the event by user_id
pub fn remove_attendee(mut self, user_id: &str) -> Self {
self.attendees.retain(|a| a.user_id != user_id);
pub fn remove_attendee(mut self, contact_id: u32) -> Self {
self.attendees.retain(|a| a.contact_id != contact_id);
self
}
/// Updates the status of an existing attendee
pub fn update_attendee_status(mut self, user_id: &str, status: AttendanceStatus) -> Self {
if let Some(attendee) = self.attendees.iter_mut().find(|a| a.user_id == user_id) {
pub fn update_attendee_status(mut self, contact_id: u32, status: AttendanceStatus) -> Self {
if let Some(attendee) = self.attendees.iter_mut().find(|a| a.contact_id == contact_id) {
attendee.status = status;
}
self
@ -120,11 +126,14 @@ impl Event {
}
/// Represents a calendar with events
#[rhai_model_export(db_type = "std::sync::Arc<crate::db::hero::OurDB>")]
#[rhai_model_export(
db_type = "std::sync::Arc<crate::db::hero::OurDB>",
)]
#[model]
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
pub struct Calendar {
/// Base model data
#[serde(flatten)]
pub base_data: BaseModelData,
/// Name of the calendar
@ -135,20 +144,26 @@ pub struct Calendar {
/// List of events in the calendar
// For now, events are embedded. If they become separate models, this would be Vec<[IDType]>.
pub events: Vec<Event>,
pub events: Vec<i64>,
}
impl Calendar {
/// Creates a new calendar
pub fn new(id: u32, name: impl ToString) -> Self {
pub fn new(id: u32) -> Self {
Self {
base_data: BaseModelData::new(id),
name: name.to_string(),
base_data: BaseModelData::new(id as u32),
name: String::new(),
description: None,
events: Vec::new(),
}
}
/// Sets the name for the calendar
pub fn name(mut self, name: impl ToString) -> Self {
self.name = name.to_string();
self
}
/// Sets the description for the calendar
pub fn description(mut self, description: impl ToString) -> Self {
self.description = Some(description.to_string());
@ -156,29 +171,17 @@ impl Calendar {
}
/// Adds an event to the calendar
pub fn add_event(mut self, event: Event) -> Self {
pub fn add_event(mut self, event_id: i64) -> Self {
// Prevent duplicate events by id
if !self.events.iter().any(|e| e.id == event.id) {
self.events.push(event);
if !self.events.iter().any(|e_id| *e_id == event_id) {
self.events.push(event_id);
}
self
}
/// Removes an event from the calendar by its ID
pub fn remove_event(mut self, event_id: &str) -> Self {
self.events.retain(|event| event.id != event_id);
self
}
/// Finds an event by its ID and allows modification
pub fn update_event<F>(mut self, event_id: &str, update_fn: F) -> Self
where
F: FnOnce(Event) -> Event,
{
if let Some(index) = self.events.iter().position(|e| e.id == event_id) {
let event = self.events.remove(index);
self.events.insert(index, update_fn(event));
}
pub fn remove_event(mut self, event_id_to_remove: i64) -> Self {
self.events.retain(|&event_id_in_vec| event_id_in_vec != event_id_to_remove);
self
}
}

View File

@ -1,5 +1,7 @@
// Export calendar module
pub mod calendar;
pub mod rhai;
// Re-export Calendar, Event, Attendee, and AttendanceStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs
pub use self::calendar::{Calendar, Event, Attendee, AttendanceStatus};
pub use rhai::register_rhai_engine_functions as register_calendar_rhai_module;

View File

@ -0,0 +1,79 @@
use rhai::{Engine, EvalAltResult, NativeCallContext};
use std::sync::Arc;
use heromodels_core::BaseModelData;
use crate::db::hero::OurDB;
use super::calendar::{Calendar, Event, Attendee, AttendanceStatus};
use adapter_macros::{adapt_rhai_i64_input_fn, adapt_rhai_i64_input_method};
use rhai_wrapper::wrap_vec_return;
// Helper function for get_all_calendars registration
fn get_all_calendars_helper(_db: Arc<OurDB>) -> Vec<Calendar> {
// In a real implementation, this would retrieve all calendars from the database
vec![Calendar::new(1 as u32), Calendar::new(2 as u32)]
}
pub fn register_rhai_engine_functions(engine: &mut Engine, db: Arc<OurDB>) {
engine.register_fn("new_calendar", adapt_rhai_i64_input_fn!(Calendar::new, u32));
engine.register_fn("name", move |calendar: Calendar, name: String| Calendar::name(calendar, name));
engine.register_fn("description", move |calendar: Calendar, description: String| Calendar::description(calendar, description));
engine.register_fn("add_event", Calendar::add_event); // Corrected: expects i64, Rhai provides i64
engine.register_fn("new_event", Event::new); // Corrected: expects i64, Rhai provides i64
engine.register_fn("title", move |event: Event, title: String| Event::title(event, title));
engine.register_fn("description", move |event: Event, description: String| Event::description(event, description));
engine.register_fn("add_attendee", move |event: Event, attendee: Attendee| Event::add_attendee(event, attendee));
engine.register_fn("remove_attendee", adapt_rhai_i64_input_method!(Event, remove_attendee, u32));
engine.register_fn("update_attendee_status", move |context: NativeCallContext, event: Event, contact_id_i64: i64, status: AttendanceStatus| -> Result<Event, Box<EvalAltResult>> {
let contact_id_u32: u32 = contact_id_i64.try_into().map_err(|_e| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Conversion error for contact_id in Event::update_attendee_status from i64 to u32"),
context.position(),
))
})?;
Ok(event.update_attendee_status(contact_id_u32, status))
});
engine.register_fn("new_attendee", adapt_rhai_i64_input_fn!(Attendee::new, u32));
// Register a function to get the database instance
engine.register_fn("get_db", move || db.clone());
// Register getters for Calendar
engine.register_get("id", |c: &mut Calendar| -> Result<i64, Box<EvalAltResult>> { Ok(c.base_data.id as i64) });
engine.register_get("name", |c: &mut Calendar| -> Result<String, Box<EvalAltResult>> {
// println!("Rhai attempting to get Calendar.name: {}", c.name); // Debug print
Ok(c.name.clone())
});
engine.register_get("description", |c: &mut Calendar| -> Result<Option<String>, Box<EvalAltResult>> { Ok(c.description.clone()) });
// Register getter for Calendar.base_data
engine.register_get("base_data", |c: &mut Calendar| -> Result<BaseModelData, Box<EvalAltResult>> { Ok(c.base_data.clone()) });
// Register getters for BaseModelData
engine.register_get("id", |bmd: &mut BaseModelData| -> Result<i64, Box<EvalAltResult>> { Ok(bmd.id.into()) });
// Mock database interaction functions from the example - these would typically interact with the `db` Arc
engine.register_fn("set_calendar", |_db: Arc<OurDB>, calendar: Calendar| {
println!("Mock save: Calendar saved: {}", calendar.name);
});
engine.register_fn("get_calendar_by_id", |_db: Arc<OurDB>, id_i64: i64| -> Calendar {
Calendar::new(id_i64 as u32)
});
engine.register_fn("calendar_exists", |_db: Arc<OurDB>, id_i64: i64| -> bool {
id_i64 == 1 || id_i64 == 2 // Mock check
});
engine.register_fn("get_all_calendars", wrap_vec_return!(get_all_calendars_helper, Arc<OurDB> => Calendar));
engine.register_fn("delete_calendar_by_id", |_db: Arc<OurDB>, id_i64: i64| {
println!("Mock delete: Calendar deleted with ID: {}", id_i64);
});
// Getters for Event
engine.register_get("id", |e: &mut Event| -> Result<i64, Box<EvalAltResult>> { Ok(e.base_data.id as i64) });
engine.register_get("title", |e: &mut Event| -> Result<String, Box<EvalAltResult>> { Ok(e.title.clone()) });
// Add more getters for Event fields as needed
}

View File

@ -1,13 +1,14 @@
// heromodels/src/models/finance/account.rs
use serde::{Deserialize, Serialize};
use rhai::{CustomType, TypeBuilder};
use heromodels_derive::model;
use heromodels_core::BaseModelData;
use super::asset::Asset;
/// Account represents a financial account owned by a user
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
#[model] // Has base.Base in V spec
pub struct Account {
pub base_data: BaseModelData,

View File

@ -1,6 +1,7 @@
// heromodels/src/models/finance/asset.rs
use serde::{Deserialize, Serialize};
use rhai::{CustomType, TypeBuilder};
use heromodels_derive::model;
use heromodels_core::BaseModelData;
@ -20,7 +21,7 @@ impl Default for AssetType {
}
/// Asset represents a digital asset or token
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
#[model] // Has base.Base in V spec
pub struct Asset {
pub base_data: BaseModelData,

View File

@ -1,9 +1,10 @@
// heromodels/src/models/finance/marketplace.rs
use serde::{Deserialize, Serialize};
use heromodels_derive::model;
use heromodels_core::BaseModelData;
use rhai::{CustomType, TypeBuilder};
use chrono::{DateTime, Utc};
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use super::asset::AssetType;
@ -52,7 +53,7 @@ impl Default for BidStatus {
}
/// Bid represents a bid on an auction listing
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
pub struct Bid {
pub listing_id: String, // ID of the listing this bid belongs to
pub bidder_id: u32, // ID of the user who placed the bid
@ -88,7 +89,7 @@ impl Bid {
}
/// Listing represents a marketplace listing for an asset
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
#[model] // Has base.Base in V spec
pub struct Listing {
pub base_data: BaseModelData,

View File

@ -4,7 +4,9 @@
pub mod account;
pub mod asset;
pub mod marketplace;
pub mod rhai;
// Re-export main models for easier access
pub use self::account::Account;
pub use self::asset::{Asset, AssetType};
pub use self::marketplace::{Listing, ListingStatus, ListingType, Bid, BidStatus};

View File

@ -0,0 +1,317 @@
use rhai::{Engine, Array, ImmutableString, INT, EvalAltResult};
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use chrono::Utc;
use adapter_macros::register_rhai_enum_accessors;
use adapter_macros::register_rhai_datetime_accessors;
use adapter_macros::register_rhai_vec_string_accessors;
use adapter_macros::rhai_timestamp_helpers;
use crate::models::finance::account::Account;
use crate::models::finance::asset::{Asset, AssetType};
use crate::models::finance::marketplace::{Listing, Bid, ListingStatus, ListingType, BidStatus};
// --- Enum to String & String to Enum Helper Functions (domain-specific) ---
// These remain here as they are specific to the finance models' enums.
fn asset_type_to_string(asset_type: &AssetType) -> ImmutableString {
format!("{:?}", asset_type).into()
}
fn string_to_asset_type(s: &str) -> Result<AssetType, Box<EvalAltResult>> {
match s {
"Erc20" => Ok(AssetType::Erc20),
"Erc721" => Ok(AssetType::Erc721),
"Erc1155" => Ok(AssetType::Erc1155),
"Native" => Ok(AssetType::Native),
_ => Err(format!("Invalid AssetType string: {}", s).into()),
}
}
fn listing_status_to_string(status: &ListingStatus) -> ImmutableString {
format!("{:?}", status).into()
}
fn string_to_listing_status(s: &str) -> Result<ListingStatus, Box<EvalAltResult>> {
match s.to_lowercase().as_str() {
"active" => Ok(ListingStatus::Active),
"sold" => Ok(ListingStatus::Sold),
"cancelled" => Ok(ListingStatus::Cancelled),
"expired" => Ok(ListingStatus::Expired),
_ => Err(format!("Invalid ListingStatus string: {}", s).into()),
}
}
fn listing_type_to_string(lt: &ListingType) -> ImmutableString {
format!("{:?}", lt).into()
}
fn string_to_listing_type(s: &str) -> Result<ListingType, Box<EvalAltResult>> {
match s.to_lowercase().as_str() {
"fixedprice" => Ok(ListingType::FixedPrice),
"auction" => Ok(ListingType::Auction),
"exchange" => Ok(ListingType::Exchange),
_ => Err(format!("Invalid ListingType string: {}", s).into()),
}
}
fn bid_status_to_string(status: &BidStatus) -> ImmutableString {
format!("{:?}", status).into()
}
fn string_to_bid_status(s: &str) -> Result<BidStatus, Box<EvalAltResult>> {
match s.to_lowercase().as_str() {
"active" => Ok(BidStatus::Active),
"accepted" => Ok(BidStatus::Accepted),
"rejected" => Ok(BidStatus::Rejected),
"cancelled" => Ok(BidStatus::Cancelled),
_ => Err(format!("Invalid BidStatus string: {}", s).into()),
}
}
pub fn register_rhai_engine_functions(
engine: &mut Engine,
db_accounts: Arc<Mutex<HashMap<u32, Account>>>,
db_assets: Arc<Mutex<HashMap<u32, Asset>>>,
db_listings: Arc<Mutex<HashMap<u32, Listing>>>,
) {
// --- Account model ---
engine.build_type::<Account>()
.register_fn("new_account",
|id_rhai: INT, name_rhai: ImmutableString, user_id_rhai: INT, desc_rhai: ImmutableString, ledger_rhai: ImmutableString, addr_rhai: ImmutableString, pubkey_rhai: ImmutableString| -> Account {
Account::new(id_rhai as u32, name_rhai, user_id_rhai as u32, desc_rhai, ledger_rhai, addr_rhai, pubkey_rhai)
}
)
.register_get_set("name",
|obj: &mut Account| -> ImmutableString { obj.name.clone().into() },
|obj: &mut Account, val: ImmutableString| obj.name = val.to_string()
)
.register_get_set("user_id",
|obj: &mut Account| obj.user_id as INT,
|obj: &mut Account, val: INT| obj.user_id = val as u32
)
.register_get_set("description",
|obj: &mut Account| -> ImmutableString { obj.description.clone().into() },
|obj: &mut Account, val: ImmutableString| obj.description = val.to_string()
)
.register_get_set("ledger",
|obj: &mut Account| -> ImmutableString { obj.ledger.clone().into() },
|obj: &mut Account, val: ImmutableString| obj.ledger = val.to_string()
)
.register_get_set("address",
|obj: &mut Account| -> ImmutableString { obj.address.clone().into() },
|obj: &mut Account, val: ImmutableString| obj.address = val.to_string()
)
.register_get_set("pubkey",
|obj: &mut Account| -> ImmutableString { obj.pubkey.clone().into() },
|obj: &mut Account, val: ImmutableString| obj.pubkey = val.to_string()
)
.register_get("created_at_ts", |obj: &mut Account| obj.base_data.created_at);
engine.register_get("assets_list", |acc: &mut Account| -> Array {
acc.assets.iter().cloned().map(rhai::Dynamic::from).collect()
});
// --- Asset model ---
engine.build_type::<Asset>()
.register_fn("new_asset",
|id_rhai: INT, name: ImmutableString, description: ImmutableString, amount: f64, address: ImmutableString, asset_type_str_rhai: ImmutableString, decimals_rhai: INT| -> Result<Asset, Box<EvalAltResult>> {
let asset_type = self::string_to_asset_type(asset_type_str_rhai.as_str())?;
Ok(Asset::new(id_rhai as u32, name, description, amount, address, asset_type, decimals_rhai as u8))
}
)
.register_get("id", |asset: &mut Asset| asset.base_data.id as INT)
.register_get_set("name",
|asset: &mut Asset| -> ImmutableString { asset.name.clone().into() },
|asset: &mut Asset, val: ImmutableString| asset.name = val.to_string()
)
.register_get_set("description",
|asset: &mut Asset| -> ImmutableString { asset.description.clone().into() },
|asset: &mut Asset, val: ImmutableString| asset.description = val.to_string()
)
.register_get_set("amount", |asset: &mut Asset| asset.amount, |asset: &mut Asset, amount_val: f64| asset.amount = amount_val)
.register_get_set("address",
|asset: &mut Asset| -> ImmutableString { asset.address.clone().into() },
|asset: &mut Asset, val: ImmutableString| asset.address = val.to_string()
)
.register_get("decimals", |asset: &mut Asset| asset.decimals as INT)
.register_get("created_at_ts", |obj: &mut Asset| obj.base_data.created_at);
register_rhai_enum_accessors!(engine, Asset, asset_type, "asset_type_str", self::asset_type_to_string, self::string_to_asset_type);
// --- Bid model ---
engine.register_type_with_name::<Bid>("Bid")
.register_fn("new_bid",
|listing_id_rhai: ImmutableString, bidder_id_rhai: INT, amount_rhai: f64, currency_rhai: ImmutableString| -> Bid {
Bid::new(listing_id_rhai, bidder_id_rhai as u32, amount_rhai, currency_rhai)
}
)
.register_get_set("listing_id",
|bid: &mut Bid| -> ImmutableString { bid.listing_id.clone().into() },
|bid: &mut Bid, val: ImmutableString| bid.listing_id = val.to_string()
)
.register_get_set("bidder_id", |bid: &mut Bid| bid.bidder_id as INT, |bid: &mut Bid, val: INT| bid.bidder_id = val as u32)
.register_get_set("amount", |bid: &mut Bid| bid.amount, |bid: &mut Bid, val: f64| bid.amount = val)
.register_get_set("currency",
|bid: &mut Bid| -> ImmutableString { bid.currency.clone().into() },
|bid: &mut Bid, val: ImmutableString| bid.currency = val.to_string()
);
register_rhai_enum_accessors!(engine, Bid, status, "status_str", self::bid_status_to_string, self::string_to_bid_status);
register_rhai_datetime_accessors!(engine, Bid, created_at, "created_at_ts", _required);
engine.register_fn("update_bid_status_script",
|bid: Bid, status_str_rhai: ImmutableString| -> Result<Bid, Box<EvalAltResult>> {
let status = self::string_to_bid_status(status_str_rhai.as_str())?;
let updated_bid = bid.update_status(status);
Ok(updated_bid)
}
);
// --- Listing --- (id is u32)
engine.register_type_with_name::<Listing>("Listing")
.register_fn("new_listing",
|id_rhai: INT, title_rhai: ImmutableString, description_rhai: ImmutableString,
asset_id_rhai: ImmutableString, asset_type_str_rhai: ImmutableString, seller_id_rhai: ImmutableString,
price_rhai: f64, currency_rhai: ImmutableString, listing_type_str_rhai: ImmutableString,
expires_at_ts_opt_rhai: Option<INT>, tags_dyn_rhai: Array, image_url_opt_rhai: Option<ImmutableString>|
-> Result<Listing, Box<EvalAltResult>> {
let asset_type = self::string_to_asset_type(asset_type_str_rhai.as_str())?;
let listing_type = self::string_to_listing_type(listing_type_str_rhai.as_str())?;
let expires_at = rhai_timestamp_helpers::option_rhai_timestamp_to_datetime(expires_at_ts_opt_rhai)?;
let tags = tags_dyn_rhai.into_iter().map(|d| d.into_string().unwrap_or_default()).collect();
let image_url = image_url_opt_rhai.map(|s| s.to_string());
Ok(Listing::new(
id_rhai as u32, title_rhai, description_rhai, asset_id_rhai,
asset_type, seller_id_rhai, price_rhai, currency_rhai, listing_type,
expires_at, tags, image_url
))
}
)
.register_get("id", |l: &mut Listing| l.base_data.id as INT)
.register_get_set("title",
|l: &mut Listing| -> ImmutableString { l.title.clone().into() },
|l: &mut Listing, v: ImmutableString| l.title = v.to_string()
)
.register_get_set("description",
|l: &mut Listing| -> ImmutableString { l.description.clone().into() },
|l: &mut Listing, v: ImmutableString| l.description = v.to_string()
)
.register_get_set("asset_id",
|l: &mut Listing| -> ImmutableString { l.asset_id.clone().into() },
|l: &mut Listing, v: ImmutableString| l.asset_id = v.to_string()
)
.register_get_set("seller_id",
|l: &mut Listing| -> ImmutableString { l.seller_id.clone().into() },
|l: &mut Listing, v: ImmutableString| l.seller_id = v.to_string()
)
.register_get_set("price", |l: &mut Listing| l.price, |l: &mut Listing, v: f64| l.price = v)
.register_get_set("currency",
|l: &mut Listing| -> ImmutableString { l.currency.clone().into() },
|l: &mut Listing, v: ImmutableString| l.currency = v.to_string()
)
.register_get_set("buyer_id",
|l: &mut Listing| -> Option<ImmutableString> { l.buyer_id.clone().map(ImmutableString::from) },
|l: &mut Listing, v_opt: Option<ImmutableString>| l.buyer_id = v_opt.map(|s| s.to_string())
)
.register_get_set("sale_price",
|l: &mut Listing| -> Option<f64> { l.sale_price },
|l: &mut Listing, v_opt: Option<f64>| l.sale_price = v_opt
)
.register_get_set("image_url",
|l: &mut Listing| -> Option<ImmutableString> { l.image_url.clone().map(ImmutableString::from) },
|l: &mut Listing, v: Option<ImmutableString>| l.image_url = v.map(|s| s.to_string())
)
.register_get("created_at_ts", |obj: &mut Listing| obj.base_data.created_at);
register_rhai_enum_accessors!(engine, Listing, listing_type, "listing_type_str", self::listing_type_to_string, self::string_to_listing_type);
register_rhai_enum_accessors!(engine, Listing, status, "status_str", self::listing_status_to_string, self::string_to_listing_status);
register_rhai_enum_accessors!(engine, Listing, asset_type, "asset_type_str_listing", self::asset_type_to_string, self::string_to_asset_type);
register_rhai_datetime_accessors!(engine, Listing, expires_at, "expires_at_ts_opt");
register_rhai_datetime_accessors!(engine, Listing, sold_at, "sold_at_ts_opt");
register_rhai_vec_string_accessors!(engine, Listing, tags, "tags_cloned");
engine.register_fn("add_listing_bid",
|listing: Listing, bid: Bid| -> Result<Listing, Box<EvalAltResult>> {
listing.add_bid(bid).map_err(|e| e.into())
}
);
engine.register_fn("get_bids_cloned", |listing: &mut Listing| listing.bids.clone());
engine.register_fn("complete_listing_sale_script",
|listing: Listing, buyer_id_rhai: ImmutableString, sale_price_rhai: f64| -> Result<Listing, Box<EvalAltResult>> {
listing.complete_sale(buyer_id_rhai.as_str(), sale_price_rhai).map_err(|e| e.into())
}
);
engine.register_fn("cancel_listing_script",
|listing: Listing| -> Result<Listing, Box<EvalAltResult>> {
listing.cancel().map_err(|e| e.into())
}
);
engine.register_fn("add_listing_tags_script",
|listing: Listing, tags_dyn_rhai: Array| -> Result<Listing, Box<EvalAltResult>> {
let tags_to_add: Vec<String> = tags_dyn_rhai.into_iter().map(|d| d.into_string().unwrap_or_default()).collect();
Ok(listing.add_tags(tags_to_add))
}
);
// --- Global Helper Functions (Enum conversions, potentially already covered by macros but good for direct script use) ---
// These are useful if scripts need to convert strings to enums outside of object setters.
engine.register_fn("str_to_asset_type", |s: ImmutableString| self::string_to_asset_type(s.as_str()));
engine.register_fn("asset_type_to_str", self::asset_type_to_string);
engine.register_fn("str_to_listing_status", |s: ImmutableString| self::string_to_listing_status(s.as_str()));
engine.register_fn("listing_status_to_str", self::listing_status_to_string);
engine.register_fn("str_to_listing_type", |s: ImmutableString| self::string_to_listing_type(s.as_str()));
engine.register_fn("listing_type_to_str", self::listing_type_to_string);
engine.register_fn("str_to_bid_status", |s: ImmutableString| self::string_to_bid_status(s.as_str()));
engine.register_fn("bid_status_to_str", self::bid_status_to_string);
// --- Mock DB functions ---
let accounts_db_clone = Arc::clone(&db_accounts);
engine.register_fn("set_account", move |account: Account| {
let mut db = accounts_db_clone.lock().unwrap();
db.insert(account.base_data.id, account);
});
let accounts_db_clone_get = Arc::clone(&db_accounts);
engine.register_fn("get_account_by_id", move |id_rhai: INT| -> Result<Account, Box<EvalAltResult>> {
let db = accounts_db_clone_get.lock().unwrap();
match db.get(&(id_rhai as u32)) {
Some(account) => Ok(account.clone()),
None => Err(format!("Account not found with ID: {}", id_rhai).into()),
}
});
let assets_db_clone = Arc::clone(&db_assets);
engine.register_fn("set_asset", move |asset: Asset| {
let mut db = assets_db_clone.lock().unwrap();
db.insert(asset.base_data.id, asset);
});
let assets_db_clone_get = Arc::clone(&db_assets);
engine.register_fn("get_asset_by_id", move |id_rhai: INT| -> Result<Asset, Box<EvalAltResult>> {
let db = assets_db_clone_get.lock().unwrap();
match db.get(&(id_rhai as u32)) {
Some(asset) => Ok(asset.clone()),
None => Err(format!("Asset not found with ID: {}", id_rhai).into()),
}
});
let listings_db_clone = Arc::clone(&db_listings);
engine.register_fn("set_listing", move |listing: Listing| {
let mut db = listings_db_clone.lock().unwrap();
db.insert(listing.base_data.id, listing);
});
let listings_db_clone_get = Arc::clone(&db_listings);
engine.register_fn("get_listing_by_id", move |id_rhai: INT| -> Result<Listing, Box<EvalAltResult>> {
let db = listings_db_clone_get.lock().unwrap();
match db.get(&(id_rhai as u32)) {
Some(listing) => Ok(listing.clone()),
None => Err(format!("Listing not found with ID: {}", id_rhai).into()),
}
});
// Global timestamp function for scripts to get current time
engine.register_fn("timestamp", || Utc::now().timestamp());
}

View File

@ -1,9 +1,10 @@
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use serde::{Deserialize, Serialize};
use super::flow_step::FlowStep;
/// Represents a signing flow.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[model]
pub struct Flow {
/// Base model data (id, created_at, updated_at).
@ -19,29 +20,37 @@ pub struct Flow {
/// Current status of the flow (e.g., "Pending", "InProgress", "Completed", "Failed").
pub status: String,
/// Steps involved in this flow.
pub steps: Vec<FlowStep>,
}
impl Flow {
/// Create a new flow.
/// The `id` is the database primary key.
/// The `flow_uuid` should be a Uuid::new_v4().to_string().
pub fn new(id: u32, flow_uuid: impl ToString, name: impl ToString, status: impl ToString) -> Self {
pub fn new(id: u32, flow_uuid: impl ToString) -> Self {
Self {
base_data: BaseModelData::new(id),
flow_uuid: flow_uuid.to_string(),
name: name.to_string(),
status: status.to_string(),
name: String::new(), // Default name, to be set by builder
status: String::from("Pending"), // Default status, to be set by builder
steps: Vec::new(),
}
}
// Builder methods for optional fields or to change initial values can be added here if needed.
// For example:
// pub fn name(mut self, name: impl ToString) -> Self {
// self.name = name.to_string();
// self
// }
// pub fn status(mut self, status: impl ToString) -> Self {
// self.status = status.to_string();
// self
// }
pub fn name(mut self, name: impl ToString) -> Self {
self.name = name.to_string();
self
}
pub fn status(mut self, status: impl ToString) -> Self {
self.status = status.to_string();
self
}
pub fn add_step(mut self, step: FlowStep) -> Self {
self.steps.push(step);
self
}
}

View File

@ -3,16 +3,12 @@ use heromodels_derive::model;
use serde::{Deserialize, Serialize};
/// Represents a step within a signing flow.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[model]
pub struct FlowStep {
/// Base model data.
pub base_data: BaseModelData,
/// Foreign key to the Flow this step belongs to.
#[index]
pub flow_id: u32,
/// Optional description for the step.
pub description: Option<String>,
@ -26,13 +22,12 @@ pub struct FlowStep {
impl FlowStep {
/// Create a new flow step.
pub fn new(id: u32, flow_id: u32, step_order: u32, status: impl ToString) -> Self {
pub fn new(id: u32, step_order: u32) -> Self {
Self {
base_data: BaseModelData::new(id),
flow_id,
description: None,
step_order,
status: status.to_string(),
status: String::from("Pending"), // Default status
}
}
@ -42,8 +37,8 @@ impl FlowStep {
self
}
// pub fn status(mut self, status: impl ToString) -> Self {
// self.status = status.to_string();
// self
// }
pub fn status(mut self, status: impl ToString) -> Self {
self.status = status.to_string();
self
}
}

View File

@ -1,9 +1,11 @@
// Export flowbroker model submodules
// Export flow model submodules
pub mod flow;
pub mod flow_step;
pub mod signature_requirement;
pub mod rhai;
// Re-export key types for convenience
pub use flow::Flow;
pub use flow_step::FlowStep;
pub use signature_requirement::SignatureRequirement;
pub use rhai::register_flow_rhai_module;

View File

@ -0,0 +1,140 @@
use rhai::{Dynamic, Engine, EvalAltResult, NativeCallContext, Position};
use std::sync::Arc;
use heromodels_core::BaseModelData;
use crate::db::hero::OurDB; // Import OurDB for actual DB operations
use crate::db::Collection; // Collection might be needed if we add more specific DB functions
use super::{
flow::Flow,
flow_step::FlowStep,
signature_requirement::SignatureRequirement,
};
// use rhai_wrapper::wrap_vec_return; // Not currently used for flow, but keep for potential future use.
// Helper function to convert Rhai's i64 to u32 for IDs
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,
))
})
}
pub fn register_flow_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// --- Flow Model ---
// Constructor: new_flow(id: u32, flow_uuid: String)
engine.register_fn("new_flow", move |context: NativeCallContext, id_i64: i64, flow_uuid: String| -> Result<Flow, Box<EvalAltResult>> {
let id_u32 = i64_to_u32(id_i64, context.position(), "id", "new_flow")?;
Ok(Flow::new(id_u32, flow_uuid))
});
// Builder methods for Flow
engine.register_fn("name", |flow: Flow, name_val: String| -> Flow { flow.name(name_val) });
engine.register_fn("status", |flow: Flow, status_val: String| -> Flow { flow.status(status_val) });
engine.register_fn("add_step", |flow: Flow, step: FlowStep| -> Flow { flow.add_step(step) });
// Getters for Flow fields
engine.register_get("id", |flow: &mut Flow| -> Result<i64, Box<EvalAltResult>> { Ok(flow.base_data.id as i64) });
engine.register_get("base_data", |flow: &mut Flow| -> Result<BaseModelData, Box<EvalAltResult>> { Ok(flow.base_data.clone()) });
engine.register_get("flow_uuid", |flow: &mut Flow| -> Result<String, Box<EvalAltResult>> { Ok(flow.flow_uuid.clone()) });
engine.register_get("name", |flow: &mut Flow| -> Result<String, Box<EvalAltResult>> { Ok(flow.name.clone()) });
engine.register_get("status", |flow: &mut Flow| -> Result<String, Box<EvalAltResult>> { Ok(flow.status.clone()) });
engine.register_get("steps", |flow: &mut Flow| -> Result<rhai::Array, Box<EvalAltResult>> {
let rhai_array = flow.steps.iter().cloned().map(Dynamic::from).collect::<rhai::Array>();
Ok(rhai_array)
});
// --- FlowStep Model ---
// Constructor: new_flow_step(id: u32, step_order: u32)
engine.register_fn("new_flow_step", move |context: NativeCallContext, id_i64: i64, step_order_i64: i64| -> Result<FlowStep, Box<EvalAltResult>> {
let id_u32 = i64_to_u32(id_i64, context.position(), "id", "new_flow_step")?;
let step_order_u32 = i64_to_u32(step_order_i64, context.position(), "step_order", "new_flow_step")?;
Ok(FlowStep::new(id_u32, step_order_u32))
});
// Builder methods for FlowStep
engine.register_fn("description", |fs: FlowStep, desc_val: String| -> FlowStep { fs.description(desc_val) }); // Assuming FlowStep::description takes impl ToString
engine.register_fn("status", |fs: FlowStep, status_val: String| -> FlowStep { fs.status(status_val) });
// Getters for FlowStep fields
engine.register_get("id", |step: &mut FlowStep| -> Result<i64, Box<EvalAltResult>> { Ok(step.base_data.id as i64) });
engine.register_get("base_data", |step: &mut FlowStep| -> Result<BaseModelData, Box<EvalAltResult>> { Ok(step.base_data.clone()) });
engine.register_get("description", |step: &mut FlowStep| -> Result<Dynamic, Box<EvalAltResult>> { Ok(match step.description.clone() { Some(s) => Dynamic::from(s), None => Dynamic::from(()) }) });
engine.register_get("step_order", |step: &mut FlowStep| -> Result<i64, Box<EvalAltResult>> { Ok(step.step_order as i64) });
engine.register_get("status", |step: &mut FlowStep| -> Result<String, Box<EvalAltResult>> { Ok(step.status.clone()) });
// --- SignatureRequirement Model ---
// Constructor: new_signature_requirement(id: u32, flow_step_id: u32, public_key: String, message: String)
engine.register_fn("new_signature_requirement",
move |context: NativeCallContext, id_i64: i64, flow_step_id_i64: i64, public_key: String, message: String|
-> Result<SignatureRequirement, Box<EvalAltResult>> {
let id_u32 = i64_to_u32(id_i64, context.position(), "id", "new_signature_requirement")?;
let flow_step_id_u32 = i64_to_u32(flow_step_id_i64, context.position(), "flow_step_id", "new_signature_requirement")?;
Ok(SignatureRequirement::new(id_u32, flow_step_id_u32, public_key, message))
});
// Builder methods for SignatureRequirement
engine.register_fn("signed_by", |sr: SignatureRequirement, signed_by_val: String| -> SignatureRequirement { sr.signed_by(signed_by_val) }); // Assuming SR::signed_by takes impl ToString
engine.register_fn("signature", |sr: SignatureRequirement, sig_val: String| -> SignatureRequirement { sr.signature(sig_val) }); // Assuming SR::signature takes impl ToString
engine.register_fn("status", |sr: SignatureRequirement, status_val: String| -> SignatureRequirement { sr.status(status_val) });
// Getters for SignatureRequirement fields
engine.register_get("id", |sr: &mut SignatureRequirement| -> Result<i64, Box<EvalAltResult>> { Ok(sr.base_data.id as i64) });
engine.register_get("base_data", |sr: &mut SignatureRequirement| -> Result<BaseModelData, Box<EvalAltResult>> { Ok(sr.base_data.clone()) });
engine.register_get("flow_step_id", |sr: &mut SignatureRequirement| -> Result<i64, Box<EvalAltResult>> { Ok(sr.flow_step_id as i64) });
engine.register_get("public_key", |sr: &mut SignatureRequirement| -> Result<String, Box<EvalAltResult>> { Ok(sr.public_key.clone()) });
engine.register_get("message", |sr: &mut SignatureRequirement| -> Result<String, Box<EvalAltResult>> { Ok(sr.message.clone()) });
engine.register_get("signed_by", |sr: &mut SignatureRequirement| -> Result<Dynamic, Box<EvalAltResult>> { Ok(match sr.signed_by.clone() { Some(s) => Dynamic::from(s), None => Dynamic::from(()) }) });
engine.register_get("signature", |sr: &mut SignatureRequirement| -> Result<Dynamic, Box<EvalAltResult>> { Ok(match sr.signature.clone() { Some(s) => Dynamic::from(s), None => Dynamic::from(()) }) });
engine.register_get("status", |sr: &mut SignatureRequirement| -> Result<String, Box<EvalAltResult>> { Ok(sr.status.clone()) });
// --- BaseModelData Getters (if not already globally registered) ---
// Assuming these might be specific to the context or shadowed, explicit registration is safer.
engine.register_get("id", |bmd: &mut BaseModelData| -> Result<i64, Box<EvalAltResult>> { Ok(bmd.id as i64) });
engine.register_get("created_at", |bmd: &mut BaseModelData| -> Result<i64, Box<EvalAltResult>> { Ok(bmd.created_at) });
engine.register_get("modified_at", |bmd: &mut BaseModelData| -> Result<i64, Box<EvalAltResult>> { Ok(bmd.modified_at) });
// engine.register_get("comments", |bmd: &mut BaseModelData| Ok(bmd.comments.clone())); // Rhai might need specific handling for Vec<String>
// --- Database Interaction Functions ---
let captured_db_for_set_flow = Arc::clone(&db);
engine.register_fn("set_flow", move |flow: Flow| -> Result<(), Box<EvalAltResult>> {
captured_db_for_set_flow.set(&flow).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Flow (ID: {}): {}", flow.base_data.id, e).into(), Position::NONE))
})
});
let captured_db_for_get_flow = Arc::clone(&db);
engine.register_fn("get_flow_by_id", move |context: NativeCallContext, id_i64: i64| -> Result<Flow, Box<EvalAltResult>> {
let id_u32 = i64_to_u32(id_i64, context.position(), "id", "get_flow_by_id")?;
captured_db_for_get_flow.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Error getting Flow (ID: {}): {}", id_u32, e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Flow with ID {} not found", id_u32).into(), Position::NONE)))
});
// Add get_flows_by_uuid, flow_exists etc. as needed, using wrap_vec_return for Vec results.
// FlowStep DB functions are removed as FlowSteps are now part of Flow.
let captured_db_for_set_sig_req = Arc::clone(&db);
engine.register_fn("set_signature_requirement", move |sr: SignatureRequirement| -> Result<(), Box<EvalAltResult>> {
captured_db_for_set_sig_req.set(&sr).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set SignatureRequirement (ID: {}): {}", sr.base_data.id, e).into(), Position::NONE))
})
});
let captured_db_for_get_sig_req = Arc::clone(&db);
engine.register_fn("get_signature_requirement_by_id",
move |context: NativeCallContext, id_i64: i64|
-> Result<SignatureRequirement, Box<EvalAltResult>> {
let id_u32 = i64_to_u32(id_i64, context.position(), "id", "get_signature_requirement_by_id")?;
captured_db_for_get_sig_req.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Error getting SignatureRequirement (ID: {}): {}", id_u32, e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("SignatureRequirement with ID {} not found", id_u32).into(), Position::NONE)))
});
println!("Flow Rhai module registered.");
}

View File

@ -31,7 +31,7 @@ pub struct SignatureRequirement {
impl SignatureRequirement {
/// Create a new signature requirement.
pub fn new(id: u32, flow_step_id: u32, public_key: impl ToString, message: impl ToString, status: impl ToString) -> Self {
pub fn new(id: u32, flow_step_id: u32, public_key: impl ToString, message: impl ToString) -> Self {
Self {
base_data: BaseModelData::new(id),
flow_step_id,
@ -39,7 +39,7 @@ impl SignatureRequirement {
message: message.to_string(),
signed_by: None,
signature: None,
status: status.to_string(),
status: String::from("Pending"), // Default status
}
}
@ -55,8 +55,8 @@ impl SignatureRequirement {
self
}
// pub fn status(mut self, status: impl ToString) -> Self {
// self.status = status.to_string();
// self
// }
pub fn status(mut self, status: impl ToString) -> Self {
self.status = status.to_string();
self
}
}

View File

@ -1,3 +1,5 @@
pub mod contract;
pub mod rhai;
pub use contract::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus};
pub use rhai::register_legal_rhai_module;

View File

@ -0,0 +1,265 @@
use rhai::{
Dynamic, Engine, EvalAltResult, NativeCallContext, Position, Module, Array,
};
use std::sync::Arc;
use crate::db::hero::OurDB; // Updated path based on compiler suggestion
// use heromodels_core::BaseModelData; // Removed as fields are accessed via contract.base_data directly
use crate::models::legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus};
use crate::db::Collection; // Import the Collection trait
// --- Helper Functions for ID and Timestamp 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 field '{}' in object '{}': cannot convert i64 ({}) to u32",
field_name,
object_name,
val
),
context_pos,
))
})
}
fn i64_to_u64(val: i64, context_pos: Position, field_name: &str, object_name: &str) -> Result<u64, Box<EvalAltResult>> {
val.try_into().map_err(|_e| {
Box::new(EvalAltResult::ErrorArithmetic(
format!(
"Conversion error for field '{}' in object '{}': cannot convert i64 ({}) to u64",
field_name,
object_name,
val
),
context_pos,
))
})
}
fn i64_to_i32(val: i64, context_pos: Position, field_name: &str, object_name: &str) -> Result<i32, Box<EvalAltResult>> {
val.try_into().map_err(|_e| {
Box::new(EvalAltResult::ErrorArithmetic(
format!(
"Conversion error for field '{}' in object '{}': cannot convert i64 ({}) to i32",
field_name,
object_name,
val
),
context_pos,
))
})
}
pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// --- ContractStatus Enum ---
// Register ContractStatus enum as constants
let mut contract_status_module = Module::new();
contract_status_module.set_var("Draft", ContractStatus::Draft);
contract_status_module.set_var("PendingSignatures", ContractStatus::PendingSignatures);
contract_status_module.set_var("Signed", ContractStatus::Signed);
contract_status_module.set_var("Active", ContractStatus::Active);
contract_status_module.set_var("Expired", ContractStatus::Expired);
contract_status_module.set_var("Cancelled", ContractStatus::Cancelled);
engine.register_static_module("ContractStatusConstants", contract_status_module.into());
engine.register_type_with_name::<ContractStatus>("ContractStatus"); // Expose the type itself
// Register SignerStatus enum as constants
let mut signer_status_module = Module::new();
signer_status_module.set_var("Pending", SignerStatus::Pending);
signer_status_module.set_var("Signed", SignerStatus::Signed);
signer_status_module.set_var("Rejected", SignerStatus::Rejected);
engine.register_static_module("SignerStatusConstants", signer_status_module.into());
engine.register_type_with_name::<SignerStatus>("SignerStatus"); // Expose the type itself
// --- ContractRevision ---
engine.register_type_with_name::<ContractRevision>("ContractRevision");
engine.register_fn(
"new_contract_revision",
move |context: NativeCallContext, version_i64: i64, content: String, created_at_i64: i64, created_by: String| -> Result<ContractRevision, Box<EvalAltResult>> {
let version = i64_to_u32(version_i64, context.position(), "version", "new_contract_revision")?;
let created_at = i64_to_u64(created_at_i64, context.position(), "created_at", "new_contract_revision")?;
Ok(ContractRevision::new(version, content, created_at, created_by))
}
);
engine.register_fn("comments", |mut revision: ContractRevision, comments: String| -> ContractRevision {
revision.comments = Some(comments);
revision
});
engine.register_get("version", |revision: &mut ContractRevision| -> Result<i64, Box<EvalAltResult>> { Ok(revision.version as i64) });
engine.register_get("content", |revision: &mut ContractRevision| -> Result<String, Box<EvalAltResult>> { Ok(revision.content.clone()) });
engine.register_get("created_at", |revision: &mut ContractRevision| -> Result<i64, Box<EvalAltResult>> { Ok(revision.created_at as i64) });
engine.register_get("created_by", |revision: &mut ContractRevision| -> Result<String, Box<EvalAltResult>> { Ok(revision.created_by.clone()) });
engine.register_get("comments", |revision: &mut ContractRevision| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(revision.comments.clone().map_or(Dynamic::UNIT, Dynamic::from))
});
// --- ContractSigner ---
engine.register_type_with_name::<ContractSigner>("ContractSigner");
engine.register_fn(
"new_contract_signer",
|id: String, name: String, email: String| -> ContractSigner {
ContractSigner::new(id, name, email)
}
);
engine.register_fn("status", |signer: ContractSigner, status: SignerStatus| -> ContractSigner { signer.status(status) });
engine.register_fn("signed_at", |context: NativeCallContext, signer: ContractSigner, signed_at_i64: i64| -> Result<ContractSigner, Box<EvalAltResult>> {
let signed_at_u64 = i64_to_u64(signed_at_i64, context.position(), "signed_at", "ContractSigner.signed_at")?;
Ok(signer.signed_at(signed_at_u64))
});
engine.register_fn("clear_signed_at", |signer: ContractSigner| -> ContractSigner { signer.clear_signed_at() });
engine.register_fn("comments", |signer: ContractSigner, comments: String| -> ContractSigner { signer.comments(comments) });
engine.register_fn("clear_comments", |signer: ContractSigner| -> ContractSigner { signer.clear_comments() });
engine.register_get("id", |signer: &mut ContractSigner| -> Result<String, Box<EvalAltResult>> { Ok(signer.id.clone()) });
engine.register_get("name", |signer: &mut ContractSigner| -> Result<String, Box<EvalAltResult>> { Ok(signer.name.clone()) });
engine.register_get("email", |signer: &mut ContractSigner| -> Result<String, Box<EvalAltResult>> { Ok(signer.email.clone()) });
engine.register_get("status", |signer: &mut ContractSigner| -> Result<SignerStatus, Box<EvalAltResult>> { Ok(signer.status.clone()) });
engine.register_get("signed_at_ts", |signer: &mut ContractSigner| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(signer.signed_at.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
});
engine.register_get("comments", |signer: &mut ContractSigner| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(signer.comments.clone().map_or(Dynamic::UNIT, Dynamic::from))
});
engine.register_get("signed_at", |signer: &mut ContractSigner| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(signer.signed_at.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts)))
});
// --- Contract ---
engine.register_type_with_name::<Contract>("Contract");
engine.register_fn(
"new_contract",
move |context: NativeCallContext, base_id_i64: i64, contract_id: String| -> Result<Contract, Box<EvalAltResult>> {
let base_id = i64_to_u32(base_id_i64, context.position(), "base_id", "new_contract")?;
Ok(Contract::new(base_id, contract_id))
}
);
// Builder methods
engine.register_fn("title", |contract: Contract, title: String| -> Contract { contract.title(title) });
engine.register_fn("description", |contract: Contract, description: String| -> Contract { contract.description(description) });
engine.register_fn("contract_type", |contract: Contract, contract_type: String| -> Contract { contract.contract_type(contract_type) });
engine.register_fn("status", |contract: Contract, status: ContractStatus| -> Contract { contract.status(status) });
engine.register_fn("created_by", |contract: Contract, created_by: String| -> Contract { contract.created_by(created_by) });
engine.register_fn("terms_and_conditions", |contract: Contract, terms: String| -> Contract { contract.terms_and_conditions(terms) });
engine.register_fn("start_date", |context: NativeCallContext, contract: Contract, start_date_i64: i64| -> Result<Contract, Box<EvalAltResult>> {
let start_date_u64 = i64_to_u64(start_date_i64, context.position(), "start_date", "Contract.start_date")?;
Ok(contract.start_date(start_date_u64))
});
engine.register_fn("clear_start_date", |contract: Contract| -> Contract { contract.clear_start_date() });
engine.register_fn("end_date", |context: NativeCallContext, contract: Contract, end_date_i64: i64| -> Result<Contract, Box<EvalAltResult>> {
let end_date_u64 = i64_to_u64(end_date_i64, context.position(), "end_date", "Contract.end_date")?;
Ok(contract.end_date(end_date_u64))
});
engine.register_fn("clear_end_date", |contract: Contract| -> Contract { contract.clear_end_date() });
engine.register_fn("renewal_period_days", |context: NativeCallContext, contract: Contract, days_i64: i64| -> Result<Contract, Box<EvalAltResult>> {
let days_i32 = i64_to_i32(days_i64, context.position(), "renewal_period_days", "Contract.renewal_period_days")?;
Ok(contract.renewal_period_days(days_i32))
});
engine.register_fn("clear_renewal_period_days", |contract: Contract| -> Contract { contract.clear_renewal_period_days() });
engine.register_fn("next_renewal_date", |context: NativeCallContext, contract: Contract, date_i64: i64| -> Result<Contract, Box<EvalAltResult>> {
let date_u64 = i64_to_u64(date_i64, context.position(), "next_renewal_date", "Contract.next_renewal_date")?;
Ok(contract.next_renewal_date(date_u64))
});
engine.register_fn("clear_next_renewal_date", |contract: Contract| -> Contract { contract.clear_next_renewal_date() });
engine.register_fn("add_signer", |contract: Contract, signer: ContractSigner| -> Contract { contract.add_signer(signer) });
engine.register_fn("signers", |contract: Contract, signers_array: Array| -> Contract {
let signers_vec = signers_array.into_iter().filter_map(|s| s.try_cast::<ContractSigner>()).collect();
contract.signers(signers_vec)
});
engine.register_fn("add_revision", |contract: Contract, revision: ContractRevision| -> Contract { contract.add_revision(revision) });
engine.register_fn("revisions", |contract: Contract, revisions_array: Array| -> Contract {
let revisions_vec = revisions_array.into_iter().filter_map(|r| r.try_cast::<ContractRevision>()).collect();
contract.revisions(revisions_vec)
});
engine.register_fn("current_version", |context: NativeCallContext, contract: Contract, version_i64: i64| -> Result<Contract, Box<EvalAltResult>> {
let version_u32 = i64_to_u32(version_i64, context.position(), "current_version", "Contract.current_version")?;
Ok(contract.current_version(version_u32))
});
engine.register_fn("last_signed_date", |context: NativeCallContext, contract: Contract, date_i64: i64| -> Result<Contract, Box<EvalAltResult>> {
let date_u64 = i64_to_u64(date_i64, context.position(), "last_signed_date", "Contract.last_signed_date")?;
Ok(contract.last_signed_date(date_u64))
});
engine.register_fn("clear_last_signed_date", |contract: Contract| -> Contract { contract.clear_last_signed_date() });
// Getters for Contract
engine.register_get("id", |contract: &mut Contract| -> Result<i64, Box<EvalAltResult>> { Ok(contract.base_data.id as i64) });
engine.register_get("created_at_ts", |contract: &mut Contract| -> Result<i64, Box<EvalAltResult>> { Ok(contract.base_data.created_at as i64) });
engine.register_get("updated_at_ts", |contract: &mut Contract| -> Result<i64, Box<EvalAltResult>> { Ok(contract.base_data.modified_at as i64) });
engine.register_get("contract_id", |contract: &mut Contract| -> Result<String, Box<EvalAltResult>> { Ok(contract.contract_id.clone()) });
engine.register_get("title", |contract: &mut Contract| -> Result<String, Box<EvalAltResult>> { Ok(contract.title.clone()) });
engine.register_get("description", |contract: &mut Contract| -> Result<String, Box<EvalAltResult>> { Ok(contract.description.clone()) });
engine.register_get("contract_type", |contract: &mut Contract| -> Result<String, Box<EvalAltResult>> { Ok(contract.contract_type.clone()) });
engine.register_get("status", |contract: &mut Contract| -> Result<ContractStatus, Box<EvalAltResult>> { Ok(contract.status.clone()) });
engine.register_get("created_by", |contract: &mut Contract| -> Result<String, Box<EvalAltResult>> { Ok(contract.created_by.clone()) });
engine.register_get("terms_and_conditions", |contract: &mut Contract| -> Result<String, Box<EvalAltResult>> { Ok(contract.terms_and_conditions.clone()) });
engine.register_get("start_date", |contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(contract.start_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
});
engine.register_get("end_date", |contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(contract.end_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
});
engine.register_get("renewal_period_days", |contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(contract.renewal_period_days.map_or(Dynamic::UNIT, |days| Dynamic::from(days as i64)))
});
engine.register_get("next_renewal_date", |contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(contract.next_renewal_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
});
engine.register_get("last_signed_date", |contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(contract.last_signed_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
});
engine.register_get("current_version", |contract: &mut Contract| -> Result<i64, Box<EvalAltResult>> { Ok(contract.current_version as i64) });
engine.register_get("signers", |contract: &mut Contract| -> Result<Array, Box<EvalAltResult>> {
let rhai_array = contract.signers.iter().cloned().map(Dynamic::from).collect::<Array>();
Ok(rhai_array)
});
engine.register_get("revisions", |contract: &mut Contract| -> Result<Array, Box<EvalAltResult>> {
let rhai_array = contract.revisions.iter().cloned().map(Dynamic::from).collect::<Array>();
Ok(rhai_array)
});
// Method set_status
engine.register_fn("set_contract_status", |contract: &mut Contract, status: ContractStatus| {
contract.set_status(status);
});
// --- Database Interaction ---
let captured_db_for_set = Arc::clone(&db);
engine.register_fn("set_contract",
move |contract: Contract| -> Result<(), Box<EvalAltResult>> {
captured_db_for_set.set(&contract).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to set Contract (ID: {}): {}", contract.base_data.id, e).into(),
Position::NONE,
))
})
});
let captured_db_for_get = Arc::clone(&db);
engine.register_fn("get_contract_by_id",
move |context: NativeCallContext, id_i64: i64| -> Result<Contract, Box<EvalAltResult>> {
let id_u32 = i64_to_u32(id_i64, context.position(), "id", "get_contract_by_id")?;
captured_db_for_get.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
format!("Error getting Contract (ID: {}): {}", id_u32, e).into(),
Position::NONE,
)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(
format!("Contract with ID {} not found", id_u32).into(),
Position::NONE,
)))
});
}

View File

@ -5,6 +5,10 @@ pub mod userexample;
pub mod calendar;
pub mod governance;
pub mod finance;
pub mod legal;
pub mod flow;
pub mod biz;
pub mod projects;
// Re-export key types for convenience
pub use core::Comment;
@ -14,3 +18,14 @@ pub use calendar::{Calendar, Event, Attendee, AttendanceStatus};
pub use governance::{Proposal, ProposalStatus, VoteEventStatus, Ballot, VoteOption};
pub use finance::{Account, Asset, AssetType};
pub use finance::marketplace::{Listing, ListingStatus, ListingType, Bid, BidStatus};
pub use legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus};
pub use flow::{Flow, FlowStep, SignatureRequirement};
pub use biz::{Sale, SaleItem, SaleStatus};
pub use flow::register_flow_rhai_module;
pub use calendar::register_calendar_rhai_module;
pub use legal::register_legal_rhai_module;
#[cfg(feature = "rhai")]
pub use biz::register_biz_rhai_module;
#[cfg(feature = "rhai")]
pub use projects::register_projects_rhai_module;

View File

@ -0,0 +1,341 @@
// heromodels/src/models/projects/base.rs
use serde::{Deserialize, Serialize};
use heromodels_core::{BaseModelData, Model};
#[cfg(feature = "rhai")]
use rhai::{CustomType, TypeBuilder}; // Removed Engine, EvalAltResult, Dynamic, Position, ImmutableString
// --- Enums ---
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
// #[cfg_attr(feature = "rhai", derive(CustomType))] // Removed derive for enum
pub enum Priority {
Critical,
High,
Medium,
Low,
None,
}
#[cfg(feature = "rhai")]
impl CustomType for Priority {
fn build(mut builder: TypeBuilder<Self>) { // Takes mut builder, returns ()
println!("DEBUG: CustomType::build for Priority called!");
builder
.with_name("Priority")
.with_fn("to_string", |p_mut: &mut Priority| {
let rust_string = ToString::to_string(p_mut);
println!("DEBUG: Priority.to_string() in Rust (via Rhai) produced: '{}'", rust_string);
rust_string
});
}
}
impl Default for Priority {
fn default() -> Self {
Priority::None
}
}
impl ToString for Priority {
fn to_string(&self) -> String {
match self {
Priority::Critical => "Critical".to_string(),
Priority::High => "High".to_string(),
Priority::Medium => "Medium".to_string(),
Priority::Low => "Low".to_string(),
Priority::None => "None".to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
// #[cfg_attr(feature = "rhai", derive(CustomType))] // Removed derive for enum
pub enum Status {
Todo,
InProgress,
Review,
Done,
Archived,
}
#[cfg(feature = "rhai")]
impl CustomType for Status {
fn build(mut builder: TypeBuilder<Self>) { // Takes mut builder, returns ()
println!("DEBUG: CustomType::build for Status called!");
builder
.with_name("Status")
.with_fn("to_string", |s_mut: &mut Status| {
let rust_string = ToString::to_string(s_mut);
println!("DEBUG: Status.to_string() in Rust (via Rhai) produced: '{}'", rust_string);
rust_string
});
}
}
impl Default for Status {
fn default() -> Self {
Status::Todo
}
}
impl ToString for Status {
fn to_string(&self) -> String {
match self {
Status::Todo => "Todo".to_string(),
Status::InProgress => "InProgress".to_string(),
Status::Review => "Review".to_string(),
Status::Done => "Done".to_string(),
Status::Archived => "Archived".to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
// #[cfg_attr(feature = "rhai", derive(CustomType))] // Removed derive for enum
pub enum ItemType {
Epic,
Story,
Task,
Bug,
Improvement,
Feature,
}
#[cfg(feature = "rhai")]
impl CustomType for ItemType {
fn build(mut builder: TypeBuilder<Self>) { // Takes mut builder, returns ()
println!("DEBUG: CustomType::build for ItemType called!");
builder
.with_name("ItemType")
.with_fn("to_string", |it_mut: &mut ItemType| {
let rust_string = ToString::to_string(it_mut);
println!("DEBUG: ItemType.to_string() in Rust (via Rhai) produced: '{}'", rust_string);
rust_string
});
}
}
impl Default for ItemType {
fn default() -> Self {
ItemType::Task
}
}
impl ToString for ItemType {
fn to_string(&self) -> String {
match self {
ItemType::Epic => "Epic".to_string(),
ItemType::Story => "Story".to_string(),
ItemType::Task => "Task".to_string(),
ItemType::Bug => "Bug".to_string(),
ItemType::Improvement => "Improvement".to_string(),
ItemType::Feature => "Feature".to_string(),
}
}
}
// --- Structs ---
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "rhai", derive(CustomType))]
pub struct Project {
pub base_data: BaseModelData,
pub name: String,
pub description: String,
pub owner_id: u32,
pub member_ids: Vec<u32>,
pub board_ids: Vec<u32>,
pub sprint_ids: Vec<u32>,
pub epic_ids: Vec<u32>,
pub tags: Vec<String>,
pub status: Status,
pub priority: Priority,
pub item_type: ItemType,
}
impl Model for Project {
fn get_id(&self) -> u32 {
self.base_data.id
}
fn db_prefix() -> &'static str {
"prj"
}
fn base_data_mut(&mut self) -> &mut BaseModelData {
&mut self.base_data
}
}
impl Project {
pub fn new(id: u32, name: String, description: String, owner_id: u32) -> Self {
Self {
base_data: BaseModelData::new(id),
name,
description,
owner_id,
member_ids: Vec::new(),
board_ids: Vec::new(),
sprint_ids: Vec::new(),
epic_ids: Vec::new(),
tags: Vec::new(),
status: Status::default(),
priority: Priority::default(),
item_type: ItemType::default(),
}
}
// Builder methods
pub fn name(mut self, name: String) -> Self {
self.name = name;
self
}
pub fn description(mut self, description: String) -> Self {
self.description = description;
self
}
pub fn owner_id(mut self, owner_id: u32) -> Self {
self.owner_id = owner_id;
self
}
pub fn add_member_id(mut self, member_id: u32) -> Self {
self.member_ids.push(member_id);
self
}
pub fn member_ids(mut self, member_ids: Vec<u32>) -> Self {
self.member_ids = member_ids;
self
}
pub fn add_board_id(mut self, board_id: u32) -> Self {
self.board_ids.push(board_id);
self
}
pub fn board_ids(mut self, board_ids: Vec<u32>) -> Self {
self.board_ids = board_ids;
self
}
pub fn add_sprint_id(mut self, sprint_id: u32) -> Self {
self.sprint_ids.push(sprint_id);
self
}
pub fn sprint_ids(mut self, sprint_ids: Vec<u32>) -> Self {
self.sprint_ids = sprint_ids;
self
}
pub fn add_epic_id(mut self, epic_id: u32) -> Self {
self.epic_ids.push(epic_id);
self
}
pub fn epic_ids(mut self, epic_ids: Vec<u32>) -> Self {
self.epic_ids = epic_ids;
self
}
pub fn add_tag(mut self, tag: String) -> Self {
self.tags.push(tag);
self
}
pub fn tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
pub fn status(mut self, status: Status) -> Self {
self.status = status;
self
}
pub fn priority(mut self, priority: Priority) -> Self {
self.priority = priority;
self
}
pub fn item_type(mut self, item_type: ItemType) -> Self {
self.item_type = item_type;
self
}
// Base model builder methods
pub fn set_base_id(mut self, id: u32) -> Self {
self.base_data.id = id;
self
}
pub fn set_base_created_at(mut self, time: i64) -> Self {
self.base_data.created_at = time;
self
}
pub fn set_base_modified_at(mut self, time: i64) -> Self {
self.base_data.modified_at = time;
self
}
pub fn add_base_comment(mut self, comment_id: u32) -> Self {
self.base_data.comments.push(comment_id);
self
}
pub fn set_base_comments(mut self, comment_ids: Vec<u32>) -> Self {
self.base_data.comments = comment_ids;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "rhai", derive(CustomType))]
pub struct Label {
pub base_data: BaseModelData,
pub name: String,
pub color: String, // Hex color code
}
impl Model for Label {
fn get_id(&self) -> u32 {
self.base_data.id
}
fn db_prefix() -> &'static str {
"lbl"
}
fn base_data_mut(&mut self) -> &mut BaseModelData {
&mut self.base_data
}
}
impl Label {
pub fn new(id: u32, name: String, color: String) -> Self {
Self {
base_data: BaseModelData::new(id),
name,
color,
}
}
// Builder methods
pub fn name(mut self, name: String) -> Self {
self.name = name;
self
}
pub fn color(mut self, color: String) -> Self {
self.color = color;
self
}
// Base model builder methods
pub fn set_base_id(mut self, id: u32) -> Self {
self.base_data.id = id;
self
}
pub fn set_base_created_at(mut self, time: i64) -> Self {
self.base_data.created_at = time;
self
}
pub fn set_base_modified_at(mut self, time: i64) -> Self {
self.base_data.modified_at = time;
self
}
pub fn add_base_comment(mut self, comment_id: u32) -> Self {
self.base_data.comments.push(comment_id);
self
}
pub fn set_base_comments(mut self, comment_ids: Vec<u32>) -> Self {
self.base_data.comments = comment_ids;
self
}
}

View File

@ -0,0 +1,21 @@
// heromodels/src/models/projects/mod.rs
pub mod base;
// pub mod epic;
// pub mod issue;
// pub mod kanban;
// pub mod sprint;
// pub mod story;
pub use base::*;
// pub use epic::*;
// pub use issue::*;
// pub use kanban::*;
// pub use sprint::*;
// pub use story::*;
#[cfg(feature = "rhai")]
pub mod rhai;
#[cfg(feature = "rhai")]
pub use rhai::register_projects_rhai_module;

View File

@ -0,0 +1,271 @@
// heromodels/src/models/projects/rhai.rs
use rhai::{Engine, EvalAltResult, Position, Dynamic};
use crate::db::Db; // Added to bring Db trait and .collection() method into scope
use std::sync::Arc;
use crate::db::hero::OurDB; // Corrected path
use heromodels_core::Model; // Added
use crate::db::Collection;
// Import models from the projects::base module
use super::base::{Project, /* Label, */ Priority, Status, ItemType}; // Label commented out as it's unused for now
// Helper function for ID conversion (if needed, similar to other rhai.rs files)
fn id_from_i64(val: i64) -> Result<u32, Box<EvalAltResult>> {
if val < 0 {
Err(EvalAltResult::ErrorArithmetic(
format!("ID value cannot be negative: {}", val),
rhai::Position::NONE,
)
.into())
} else {
Ok(val as u32)
}
}
pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// Register enums as constants (example for Priority)
engine.register_static_module("Priority", {
let mut module = rhai::Module::new();
module.set_var("Critical", Priority::Critical);
module.set_var("High", Priority::High);
module.set_var("Medium", Priority::Medium);
module.set_var("Low", Priority::Low);
module.set_var("None", Priority::None);
module.into()
});
engine.register_static_module("Status", {
let mut module = rhai::Module::new();
module.set_var("Todo", Status::Todo);
module.set_var("InProgress", Status::InProgress);
module.set_var("Review", Status::Review);
module.set_var("Done", Status::Done);
module.set_var("Archived", Status::Archived);
module.into()
});
engine.register_static_module("ItemType", {
let mut module = rhai::Module::new();
module.set_var("Epic", ItemType::Epic);
module.set_var("Story", ItemType::Story);
module.set_var("Task", ItemType::Task);
module.set_var("Bug", ItemType::Bug);
module.set_var("Improvement", ItemType::Improvement);
module.set_var("Feature", ItemType::Feature);
module.into()
});
// --- Enum Type Registration ---
engine.register_type_with_name::<Priority>("Priority");
engine.register_type_with_name::<Status>("Status");
engine.register_type_with_name::<ItemType>("ItemType");
// --- Project Registration ---
engine.register_type_with_name::<Project>("Project");
// Constructor for Project
// Zero-argument constructor
engine.register_fn("new_project", || -> Result<Project, Box<EvalAltResult>> {
// Assuming Project::new() or Project::default() can be used.
// If Project::new() requires args, this needs adjustment or Project needs Default impl.
Ok(Project::new(0, "".to_string(), "".to_string(), 0))
});
// Multi-argument constructor (renamed)
engine.register_fn("new_project_with_details", |id_i64: i64, name: String, description: String, owner_id_i64: i64| -> Result<Project, Box<EvalAltResult>> {
Ok(Project::new(id_from_i64(id_i64)?, name, description, id_from_i64(owner_id_i64)?))
});
// Getters for Project
engine.register_get("id", |p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.get_id() as i64) });
engine.register_get("name", |p: &mut Project| -> Result<String, Box<EvalAltResult>> { Ok(p.name.clone()) });
engine.register_get("description", |p: &mut Project| -> Result<String, Box<EvalAltResult>> { Ok(p.description.clone()) });
engine.register_get("owner_id", |p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.owner_id as i64) });
engine.register_get("member_ids", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.member_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect())
});
engine.register_get("board_ids", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.board_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect())
});
engine.register_get("sprint_ids", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.sprint_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect())
});
engine.register_get("epic_ids", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.epic_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect())
});
engine.register_get("tags", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.tags.iter().map(|tag| rhai::Dynamic::from(tag.clone())).collect())
});
engine.register_get("created_at", |p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.base_data.created_at) });
engine.register_get("modified_at", |p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.base_data.modified_at) });
engine.register_get("comments", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.base_data.comments.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect())
});
engine.register_get("status", |p: &mut Project| -> Result<Status, Box<EvalAltResult>> { Ok(p.status.clone()) });
engine.register_get("priority", |p: &mut Project| -> Result<Priority, Box<EvalAltResult>> { Ok(p.priority.clone()) });
engine.register_get("item_type", |p: &mut Project| -> Result<ItemType, Box<EvalAltResult>> { Ok(p.item_type.clone()) });
// Builder methods for Project
// let db_clone = db.clone(); // This was unused
engine.register_fn("name", |p: Project, name: String| -> Result<Project, Box<EvalAltResult>> { Ok(p.name(name)) });
engine.register_fn("description", |p: Project, description: String| -> Result<Project, Box<EvalAltResult>> { Ok(p.description(description)) });
engine.register_fn("owner_id", |p: Project, owner_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.owner_id(id_from_i64(owner_id_i64)?)) });
engine.register_fn("add_member_id", |p: Project, member_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_member_id(id_from_i64(member_id_i64)?)) });
engine.register_fn("member_ids", |p: Project, member_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = member_ids_i64
.into_iter()
.map(|id_dyn: Dynamic| {
let val_i64 = id_dyn.clone().try_cast::<i64>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected integer for ID".to_string(),
id_dyn.type_name().to_string(),
Position::NONE,
))
})?;
id_from_i64(val_i64)
})
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.member_ids(ids))
});
engine.register_fn("add_board_id", |p: Project, board_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_board_id(id_from_i64(board_id_i64)?)) });
engine.register_fn("board_ids", |p: Project, board_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = board_ids_i64
.into_iter()
.map(|id_dyn: Dynamic| {
let val_i64 = id_dyn.clone().try_cast::<i64>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected integer for ID".to_string(),
id_dyn.type_name().to_string(),
Position::NONE,
))
})?;
id_from_i64(val_i64)
})
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.board_ids(ids))
});
engine.register_fn("add_sprint_id", |p: Project, sprint_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_sprint_id(id_from_i64(sprint_id_i64)?)) });
engine.register_fn("sprint_ids", |p: Project, sprint_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = sprint_ids_i64
.into_iter()
.map(|id_dyn: Dynamic| {
let val_i64 = id_dyn.clone().try_cast::<i64>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected integer for ID".to_string(),
id_dyn.type_name().to_string(),
Position::NONE,
))
})?;
id_from_i64(val_i64)
})
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.sprint_ids(ids))
});
engine.register_fn("add_epic_id", |p: Project, epic_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_epic_id(id_from_i64(epic_id_i64)?)) });
engine.register_fn("epic_ids", |p: Project, epic_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = epic_ids_i64
.into_iter()
.map(|id_dyn: Dynamic| {
let val_i64 = id_dyn.clone().try_cast::<i64>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected integer for ID".to_string(),
id_dyn.type_name().to_string(),
Position::NONE,
))
})?;
id_from_i64(val_i64)
})
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.epic_ids(ids))
});
engine.register_fn("add_tag", |p: Project, tag: String| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_tag(tag)) });
engine.register_fn("tags", |p: Project, tags_dyn: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let tags_vec = tags_dyn
.into_iter()
.map(|tag_dyn: Dynamic| {
tag_dyn.clone().into_string().map_err(|_err| { // _err is Rhai's internal error, we create a new one
Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected string for tag".to_string(),
tag_dyn.type_name().to_string(),
Position::NONE,
))
})
})
.collect::<Result<Vec<String>, Box<EvalAltResult>>>()?;
Ok(p.tags(tags_vec))
});
engine.register_fn("status", |p: Project, status: Status| -> Result<Project, Box<EvalAltResult>> { Ok(p.status(status)) });
engine.register_fn("priority", |p: Project, priority: Priority| -> Result<Project, Box<EvalAltResult>> { Ok(p.priority(priority)) });
engine.register_fn("item_type", |p: Project, item_type: ItemType| -> Result<Project, Box<EvalAltResult>> { Ok(p.item_type(item_type)) });
// Base ModelData builders
engine.register_fn("set_base_id", |p: Project, id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.set_base_id(id_from_i64(id_i64)?)) });
engine.register_fn("set_base_created_at", |p: Project, time: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.set_base_created_at(time)) });
engine.register_fn("set_base_modified_at", |p: Project, time: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.set_base_modified_at(time)) });
engine.register_fn("add_base_comment", |p: Project, comment_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_base_comment(id_from_i64(comment_id_i64)?)) });
engine.register_fn("set_base_comments", |p: Project, comment_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = comment_ids_i64
.into_iter()
.map(|id_dyn: Dynamic| {
let val_i64 = id_dyn.clone().try_cast::<i64>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected integer for ID".to_string(),
id_dyn.type_name().to_string(),
Position::NONE,
))
})?;
id_from_i64(val_i64)
})
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.set_base_comments(ids))
});
// --- Database Interaction Functions ---
let db_clone_set = db.clone();
engine.register_fn("set_project", move |project: Project| -> Result<(), Box<EvalAltResult>> {
let collection = db_clone_set.collection::<Project>().map_err(|e| {
Box::new(EvalAltResult::ErrorSystem(
"Failed to access project collection".to_string(),
format!("DB operation failed: {:?}", e).into(),
))
})?;
collection.set(&project).map_err(|e| {
Box::new(EvalAltResult::ErrorSystem(
"Failed to save project".to_string(),
format!("DB operation failed: {:?}", e).into(),
))
})
});
let db_clone_get = db.clone();
engine.register_fn("get_project_by_id", move |id_i64: i64| -> Result<Dynamic, Box<EvalAltResult>> {
let id = id_from_i64(id_i64)?;
let collection = db_clone_get.collection::<Project>().map_err(|e| {
Box::new(EvalAltResult::ErrorSystem(
"Failed to access project collection".to_string(),
format!("DB operation failed: {:?}", e).into(),
))
})?;
match collection.get_by_id(id) {
Ok(Some(project)) => Ok(Dynamic::from(project)),
Ok(None) => Ok(Dynamic::UNIT), // Represents '()' in Rhai
Err(e) => Err(Box::new(EvalAltResult::ErrorSystem(
"Failed to retrieve project by ID".to_string(),
format!("DB operation failed: {:?}", e).into(),
))),
}
});
// TODO: Register Rhai bindings for the `Label` model if needed, or remove unused import.
// Register Label type and its methods/getters
// engine.register_type_with_name::<Label>("Label")
// .register_fn("new_label", Label::new) // Simplified
// // ... other Label methods and getters ...
// ;
// TODO: Add DB interaction functions like set_project, get_project_by_id etc.
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
2

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
2

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
103

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
2

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
78

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
2

Binary file not shown.

View File

@ -95,7 +95,7 @@ pub trait Index {
}
/// Base struct that all models should include
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct BaseModelData {
/// Unique incremental ID per circle
pub id: u32,

View File

@ -0,0 +1,192 @@
## 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.
// }
```