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

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 ---");