diff --git a/heromodels/Cargo.lock b/heromodels/Cargo.lock index c78e694..d8cd394 100644 --- a/heromodels/Cargo.lock +++ b/heromodels/Cargo.lock @@ -183,6 +183,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "heromodels" version = "0.1.0" @@ -199,6 +205,8 @@ dependencies = [ "rhai_wrapper", "serde", "serde_json", + "strum", + "strum_macros", "tst", ] @@ -415,7 +423,7 @@ dependencies = [ name = "rhai_autobind_macros" version = "0.1.0" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn", @@ -550,6 +558,25 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "2.0.101" diff --git a/heromodels/examples/biz_rhai/biz.rhai b/heromodels/examples/biz_rhai/biz.rhai index bbbba7c..1bcbfd3 100644 --- a/heromodels/examples/biz_rhai/biz.rhai +++ b/heromodels/examples/biz_rhai/biz.rhai @@ -14,14 +14,13 @@ 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) +print(`Creating a new company (UUID: ${company1_uuid})...`); +let company1 = new_company(company1_name, company1_reg, company1_inc_date) .email("contact@innovatech.com") .phone("+1-555-0100") .website("https://innovatech.com") @@ -38,26 +37,26 @@ 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); +company1 = set_company(company1); // Capture the company with the DB-assigned ID print("Company1 saved."); // Retrieve the company -print(`\nRetrieving company by ID (${company1_id})...`); -let retrieved_company = get_company_by_id(company1_id); +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) +print(`Creating shareholder 1 for company ${company1.id}...`); +let shareholder1 = new_shareholder() + .company_id(company1.id) .user_id(sh1_user_id) .name(sh1_name) .shares(1000.0) @@ -66,20 +65,20 @@ let shareholder1 = new_shareholder(sh1_id) .since(sh1_since) .set_base_created_at(1672617600); -set_shareholder(shareholder1); +shareholder1 = set_shareholder(shareholder1); print("Shareholder 1 saved."); -let retrieved_sh1 = get_shareholder_by_id(sh1_id); +let retrieved_sh1 = get_shareholder_by_id(shareholder1.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) +print(`\nCreating shareholder 2 for company ${company1.id}...`); +let shareholder2 = new_shareholder() + .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) @@ -88,10 +87,10 @@ let shareholder2 = new_shareholder(sh2_id) .since(sh2_since) .set_base_created_at(1672704000); -set_shareholder(shareholder2); +shareholder2 = set_shareholder(shareholder2); print("Shareholder 2 saved."); -let retrieved_sh2 = get_shareholder_by_id(sh2_id); +let retrieved_sh2 = get_shareholder_by_id(shareholder2.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) --- @@ -105,7 +104,7 @@ print(`Updated Company - Name: ${updated_company.name}, New Phone: ${updated_com set_company(updated_company); print("Updated Company saved."); -let final_retrieved_company = get_company_by_id(company1_id); +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 ---"); @@ -127,12 +126,12 @@ let component1 = new_product_component("Super Capacitor") 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) +print(`\nCreating Product 1: ${product1_name}...`); +let product1 = new_product() .name(product1_name) .description("A revolutionary gadget with cutting-edge features.") .price(299.99) @@ -146,11 +145,11 @@ let product1 = new_product(product1_id) .set_base_created_at(product1_creation_time); print("Saving Product 1..."); -set_product(product1); +product1 = set_product(product1); print("Product 1 saved."); -print(`\nRetrieving Product 1 (ID: ${product1_id})...`); -let retrieved_product1 = get_product_by_id(product1_id); +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}`); @@ -159,12 +158,12 @@ if retrieved_product1.components.len() > 0 { } // 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) +print(`\nCreating Product 2: ${product2_name}...`); +let product2 = new_product() .name(product2_name) .description("Unlimited cloud backup with 24/7 support.") .price(19.99) // Monthly price @@ -175,11 +174,11 @@ let product2 = new_product(product2_id) .set_base_created_at(product2_creation_time); print("Saving Product 2..."); -set_product(product2); +product2 = set_product(product2); print("Product 2 saved."); -print(`\nRetrieving Product 2 (ID: ${product2_id})...`); -let retrieved_product2 = get_product_by_id(product2_id); +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.`); @@ -198,7 +197,7 @@ 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_product_id = product1.id; // Using product1.id after it's set // 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; @@ -209,7 +208,7 @@ let sale_item1 = new_sale_item(sale_item1_product_id, sale_item1_name, sale_item 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_product_id = product2.id; // Using product2.id after it's set // 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; @@ -220,14 +219,13 @@ let sale_item2 = new_sale_item(sale_item2_product_id, sale_item2_name, sale_item 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_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}...`); +print(`\nCreating Sale 1 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 @@ -262,12 +260,12 @@ print(`Sale 1 Base Comments: ${sale1.comments}`); // Save Sale 1 to database print("\nSaving Sale 1 to database..."); -set_sale(sale1); +sale1 = 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(`\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 { diff --git a/heromodels/examples/calendar_rhai/calendar.rhai b/heromodels/examples/calendar_rhai/calendar.rhai index 40fa0da..f5886ab 100644 --- a/heromodels/examples/calendar_rhai/calendar.rhai +++ b/heromodels/examples/calendar_rhai/calendar.rhai @@ -2,29 +2,31 @@ let db = get_db(); // 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). +print("Creating a new calendar (ID will be DB-assigned) via registered constructor..."); +let calendar = new_calendar(). // ID removed name("My First Calendar"). description("A calendar for testing Rhai integration"); -let event = new_event(1). +let event = new_event(). // ID removed title("My First Event"). description("An event for testing Rhai integration") - .add_attendee(new_attendee(1)); + .add_attendee(new_attendee(1)); // new_attendee(contact_id), not Attendee ID -calendar.add_event(1); +// Add event's ID to calendar. event.id will be 0 if not saved separately. +// Calendar::add_event returns the modified calendar, so we re-assign. +calendar = calendar.add_event(event.id); print("Type of calendar object: " + type_of(calendar)); print("Created calendar: " + calendar.name); -// Save the calendar to the database -set_calendar(db, calendar); +// Save the calendar to the database and capture the result with DB-assigned ID +let calendar = set_calendar(db, calendar); // Capture result print("Calendar saved to database"); // Check if calendar exists and retrieve it -if calendar_exists(db, 1) { - let retrieved_calendar = get_calendar_by_id(db, 1); - print("Retrieved calendar: " + retrieved_calendar.name); +if calendar_exists(db, calendar.id) { // Use calendar.id from the saved object + let retrieved_calendar = get_calendar_by_id(db, calendar.id); // Use calendar.id + print("Retrieved calendar ID: " + retrieved_calendar.id + ", Name: " + retrieved_calendar.name); // Access the 'description' field directly. // Note: 'description' is Option. Rhai handles options. // You might want to check for 'is_some()' or 'is_none()' or use 'unwrap_or()' pattern if needed. @@ -35,17 +37,17 @@ if calendar_exists(db, 1) { print("No description available or it's None"); } } else { - print("Failed to retrieve calendar with ID 1"); + print("Failed to retrieve calendar with ID " + calendar.id); } // Create another calendar -print("Creating a new calendar with ID 2 using builder methods..."); -let calendar2 = new_calendar(2). +print("Creating another new calendar (ID will be DB-assigned) using builder methods..."); +let calendar2 = new_calendar(). // ID removed name("My Second Calendar"). description("Another calendar for testing"); -set_calendar(db, calendar2); -print("Second calendar saved"); +let calendar2 = set_calendar(db, calendar2); // Capture result +print("Second calendar saved with ID: " + calendar2.id); // Get all calendars let all_calendars = get_all_calendars(db); @@ -57,14 +59,14 @@ for cal_item in all_calendars { // Renamed loop variable to avoid conflict if 'c } // Delete a calendar -delete_calendar_by_id(db, 1); -print("Deleted calendar with ID 1"); +delete_calendar_by_id(db, calendar.id); // Use ID from the first calendar object +print("Attempted to delete calendar with ID " + calendar.id); // Verify deletion -if !calendar_exists(db, 1) { - print("Calendar with ID 1 was successfully deleted"); +if !calendar_exists(db, calendar.id) { // Use ID from the first calendar object + print("Calendar with ID " + calendar.id + " was successfully deleted"); } else { - print("Failed to delete calendar with ID 1"); + print("Failed to delete calendar with ID " + calendar.id + ", or it was not the one intended."); } // Count remaining calendars diff --git a/heromodels/examples/finance_rhai/finance.rhai b/heromodels/examples/finance_rhai/finance.rhai index 546d9ae..4589761 100644 --- a/heromodels/examples/finance_rhai/finance.rhai +++ b/heromodels/examples/finance_rhai/finance.rhai @@ -2,139 +2,141 @@ 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}`); +// 1. Create an Account using the builder pattern +let user1_id = 1; // Assuming this user_id is a separate concept, e.g. from an auth system +let acc1 = new_account() + .set_name("User1 Main Account") + .set_user_id(user1_id) // user_id is i64 in Rhai, u32 in Rust. Conversion handled by setter. + .set_description("Primary account for User 1") + .set_ledger("LedgerX") + .set_address("0x123MainSt") + .set_pubkey("pubkeyUser1"); +print(`Created account (pre-save): ${acc1.get_name()} with temp ID ${acc1.get_id()}`); -// 2. Save Account to Mock DB -set_account(acc1); -print(`Account ${acc1.id} saved to DB.`); +// 2. Save Account to Mock DB and get the version with DB-assigned ID +let acc1 = set_account(acc1); // Shadowing acc1 with the returned instance +print(`Account ${acc1.get_name()} saved to DB with ID ${acc1.get_id()}.`); -// 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}`); +// 3. Retrieve Account from Mock DB (using the new ID) +let fetched_acc1 = get_account_by_id(acc1.get_id()); // Use the ID from the saved acc1 +print(`Fetched account from DB: ${fetched_acc1.get_name()}, User ID: ${fetched_acc1.get_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}`); +// 4. Create an Asset using the builder pattern +let asset1 = new_asset() + .set_name("HeroCoin") + .set_description("Utility token for Hero Platform") + .set_amount(1000.0) + .set_address("0xTokenContract") + .set_asset_type("Erc20") // Setter handles string to enum + .set_decimals(18); +print(`Created asset (pre-save): ${asset1.get_name()} (temp ID: ${asset1.get_id()}), Amount: ${asset1.get_amount()}, Type: ${asset1.get_asset_type_str()}`); -// 5. Save Asset to Mock DB -set_asset(asset1); -print(`Asset ${asset1.id} saved to DB.`); +// 5. Save Asset to Mock DB and get the version with DB-assigned ID +let asset1 = set_asset(asset1); // Shadowing asset1 +print(`Asset ${asset1.get_name()} (ID: ${asset1.get_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}`); +// 6. Retrieve Asset from Mock DB (using the new ID) +let fetched_asset1 = get_asset_by_id(asset1.get_id()); // Use the ID from the saved asset1 +print(`Fetched asset from DB: ${fetched_asset1.get_name()}, Address: ${fetched_asset1.get_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. +// 7. Add Asset to Account +// We have 'acc1' and 'asset1' from previous steps, both saved to DB and have their IDs. +print(`Attempting to add asset ${asset1.get_id()} to account ${acc1.get_id()}`); -// 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."); +// Fetch the latest version of the account before modifying +// let mut acc1_for_update = get_account_by_id(acc1.get_id()); +// // Fetch the asset to add (or use fetched_asset1 if it's the correct one) +// let asset_to_add = get_asset_by_id(asset1.get_id()); +// +// try { +// acc1_for_update = acc1_for_update.add_asset(asset_to_add); // add_asset returns the modified account +// acc1_for_update = set_account(acc1_for_update); // Save the account with the new asset +// print(`Asset '${asset_to_add.get_name()}' added to account '${acc1_for_update.get_name()}'.`); +// print(`Account now has ${acc1_for_update.get_assets_cloned().len()} assets.`); +// // Verify the asset is there + if (acc1_for_update.get_assets_cloned().len() > 0) { + let first_asset_in_account = acc1_for_update.get_assets_cloned()[0]; + print(`First asset in account: ${first_asset_in_account.get_name()} (ID: ${first_asset_in_account.get_id()})`); + } +} catch (err) { + print(`Error adding asset to account: ${err}`); +} -// 8. Create a Listing for the Asset -let listing1_id = 301; +// 8. Create a Listing for the Asset using the builder pattern 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}`); +let listing1 = new_listing() + .set_title("Rare HeroCoin Batch") + .set_description("100 HeroCoins for sale") + .set_asset_id(asset1.get_id().to_string()) // Use ID from the saved asset1 + .set_asset_type("Erc20") // asset_type as string + .set_seller_id(user1_id.to_string()) // seller_id as string (using the predefined user1_id) + .set_price(50.0) // price + .set_currency("USD") // currency + .set_listing_type("FixedPrice") // listing_type as string + .set_expires_at_ts_opt(expires_at_ts) // expires_at_ts_opt as i64 + .set_tags(["token", "herocoin", "sale"]); // tags as array of strings + // image_url is None by default from new_listing(), so no need to call set_image_url_opt for None -// 9. Save Listing to Mock DB -set_listing(listing1); -print(`Listing ${listing1.id} saved to DB.`); +print(`Created listing (pre-save): ${listing1.get_title()} (temp ID: ${listing1.get_id()}), Price: ${listing1.get_price()} ${listing1.get_currency()}`); +print(`Listing type: ${listing1.get_listing_type_str()}, Status: ${listing1.get_status_str()}`); +print(`Listing expires_at_ts_opt: ${listing1.get_expires_at_ts_opt()}`); -// 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}`); +// 9. Save Listing to Mock DB and get the version with DB-assigned ID +let listing1 = set_listing(listing1); // Shadowing listing1 +print(`Listing ${listing1.get_title()} (ID: ${listing1.get_id()}) saved to DB.`); -// 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})`); +// 10. Retrieve Listing from Mock DB (using the new ID) +let fetched_listing1 = get_listing_by_id(listing1.get_id()); // Use the ID from the saved listing1 +print(`Fetched listing from DB: ${fetched_listing1.get_title()}, Seller ID: ${fetched_listing1.get_seller_id()}`); +print(`Fetched listing asset_id: ${fetched_listing1.get_asset_id()}, asset_type: ${fetched_listing1.get_asset_type_str()}`); -// 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 +// 11. Demonstrate an auction listing using the builder pattern +let auction_listing = new_listing() + .set_title("Vintage Hero Figurine") + .set_description("Rare collectible, starting bid low!") + .set_asset_id("asset_nft_123") // Mock asset ID for an NFT - this asset isn't created/saved in script + .set_asset_type("Erc721") + .set_seller_id(user1_id.to_string()) // Using the predefined user1_id + .set_price(10.0) // Starting price + .set_currency("USD") + .set_listing_type("Auction") + // expires_at_ts_opt is None by default + .set_tags(["collectible", "rare", "auction"]) + .set_image_url_opt("http://example.com/figurine.png"); + +// Save Auction Listing to Mock DB and get the version with DB-assigned ID +let auction_listing = set_listing(auction_listing); // Shadowing auction_listing +print(`Created auction listing: ${auction_listing.get_title()} (ID: ${auction_listing.get_id()})`); + +// 12. Create a Bid for the auction listing (Bid model not using builder pattern in this refactor) +let bid1 = new_bid(auction_listing.get_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()}`); +// 13. Add bid to listing +let mut auction_listing_for_bid = get_listing_by_id(auction_listing.get_id()); +print(`Listing '${auction_listing_for_bid.get_title()}' fetched for bidding. Current price: ${auction_listing_for_bid.get_price()}, Bids: ${auction_listing_for_bid.get_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 + let updated_listing_after_bid = auction_listing_for_bid.add_listing_bid(bid1); + print(`Bid added to '${updated_listing_after_bid.get_title()}'. New bid count: ${updated_listing_after_bid.get_bids_cloned().len()}, New price: ${updated_listing_after_bid.get_price()};`); + set_listing(updated_listing_after_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); +// 14. Try to complete sale for the fixed price listing +let mut listing_to_sell = get_listing_by_id(listing1.get_id()); let buyer_user_id = 3; -print(`Attempting to complete sale for listing: ${listing_to_sell.title} by buyer ${buyer_user_id}`); +print(`Attempting to complete sale for listing: ${listing_to_sell.get_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 + let sold_listing = listing_to_sell.complete_listing_sale(buyer_user_id.to_string(), listing_to_sell.get_price()); + print(`Sale completed for listing ${sold_listing.get_id()}. New status: ${sold_listing.get_status_str()}`); + print(`Buyer ID: ${sold_listing.get_buyer_id_opt()}, Sale Price: ${sold_listing.get_sale_price_opt()}`); + set_listing(sold_listing); // Save updated listing } catch (err) { print(`Error completing sale: ${err}`); } diff --git a/heromodels/examples/project_rhai_wasm/.cargo/config.toml b/heromodels/examples/project_rhai_wasm/.cargo/config.toml new file mode 100644 index 0000000..f603c8f --- /dev/null +++ b/heromodels/examples/project_rhai_wasm/.cargo/config.toml @@ -0,0 +1,9 @@ +[build] +# Set the default build target for this project +target = "wasm32-unknown-unknown" + +# Configuration for the wasm32-unknown-unknown target +[target.wasm32-unknown-unknown] +# Pass --cfg=wasm_js to rustc when compiling for this target. +# This is required by the getrandom crate. +rustflags = ["--cfg=wasm_js"] # For getrandom v0.3.x WASM support (required by rhai via ahash) diff --git a/heromodels/examples/project_rhai_wasm/Cargo.toml b/heromodels/examples/project_rhai_wasm/Cargo.toml new file mode 100644 index 0000000..cf9b426 --- /dev/null +++ b/heromodels/examples/project_rhai_wasm/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "project_rhai_wasm_example" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +heromodels = { path = "../..", features = ["rhai"] } # Match heromodels main crate +wasm-bindgen = "0.2" +web-sys = { version = "0.3", features = ["console"] } +console_error_panic_hook = "0.1.7" +js-sys = "0.3" +getrandom = { version = "0.3.3", features = ["js"] } # Align with rhai's dependency + +[profile.release] +# Tell `rustc` to optimize for small code size. +lto = true +opt-level = 's' diff --git a/heromodels/examples/project_rhai_wasm/index.html b/heromodels/examples/project_rhai_wasm/index.html new file mode 100644 index 0000000..ec6ccdb --- /dev/null +++ b/heromodels/examples/project_rhai_wasm/index.html @@ -0,0 +1,52 @@ + + + + + + HeroModels Project Rhai WASM Test + + + +

HeroModels Project Rhai WASM Test

+

Open your browser's developer console to see detailed logs from the Rhai script.

+ + + + + diff --git a/heromodels/examples/project_rhai_wasm/src/lib.rs b/heromodels/examples/project_rhai_wasm/src/lib.rs new file mode 100644 index 0000000..21aff17 --- /dev/null +++ b/heromodels/examples/project_rhai_wasm/src/lib.rs @@ -0,0 +1,126 @@ +use heromodels::db::{OurDB, Db}; // Import Db trait +use heromodels::models::projects::rhai::register_projects_rhai_module; +use rhai::{Engine, Scope, Dynamic, EvalAltResult, Position}; +use std::sync::Arc; +use wasm_bindgen::prelude::*; +use web_sys::console; + +// Called once when the WASM module is instantiated. +#[wasm_bindgen(start)] +pub fn main_wasm() -> Result<(), JsValue> { + // For better panic messages in the browser console + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + Ok(()) +} + +#[wasm_bindgen] +pub fn run_project_script_wasm() -> Result<(), JsValue> { + console::log_1(&"Starting Rhai script execution in WASM...".into()); + + let script = r#" +// Test script for Project Rhai integration + +print("--- Testing Project Rhai Integration (WASM) ---"); + +// Create a new project +let p1 = new_project() + .name("Project Alpha WASM") + .description("This is the first test project in WASM.") + .owner_id(101) + .add_member_id(102) + .add_member_id(103) + .member_ids([201, 202, 203]) + .add_tag("important") + .add_tag("rhai_test") + .add_tag("wasm") + .tags(["core", "feature_test", "wasm_run"]) + .status(Status::InProgress) + .priority(Priority::High) + .item_type(ItemType::Feature) + .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 (WASM) ---"); +"#; + + let mut engine = Engine::new(); + + // Redirect Rhai's print to browser console + engine.on_print(|text| { + console::log_1(&text.into()); + }); + + // Attempt to initialize OurDB. Sled's behavior in WASM with paths is experimental. + // It might work as an in-memory/temporary DB, or it might fail. + // Using a specific path and reset=true. + let db = match OurDB::new("/project_rhai_wasm_db", true) { + Ok(db_instance) => Arc::new(db_instance), + Err(e) => { + let err_msg = format!("Failed to initialize OurDB for WASM: {}", e); + console::error_1(&err_msg.into()); + return Err(JsValue::from_str(&err_msg)); + } + }; + + register_projects_rhai_module(&mut engine, db); + + let mut scope = Scope::new(); + + match engine.run_with_scope(&mut scope, script) { + Ok(_) => console::log_1(&"Rhai script executed successfully in WASM!".into()), + Err(e) => { + let err_msg = format!("Rhai script execution failed in WASM: {}\nDetails: {:?}", e, e.to_string()); + console::error_1(&err_msg.into()); + return Err(JsValue::from_str(&e.to_string())); + } + } + + Ok(()) +} diff --git a/heromodels/src/db.rs b/heromodels/src/db.rs index a03173a..e5736fd 100644 --- a/heromodels/src/db.rs +++ b/heromodels/src/db.rs @@ -97,8 +97,13 @@ impl std::fmt::Display for Error { 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), + Error::InvalidId(s) => write!(f, "Invalid ID: {}", s), + Error::IdMismatch(s) => write!(f, "ID Mismatch: {}", s), + Error::IdCollision(s) => write!(f, "ID Collision: {}", s), + Error::TypeError => write!(f, "Type error"), } } +} /// A transaction that can be committed or rolled back pub trait Transaction { /// Error type for transaction operations diff --git a/heromodels/src/models/biz/company.rs b/heromodels/src/models/biz/company.rs index 1b8753b..7fcd02d 100644 --- a/heromodels/src/models/biz/company.rs +++ b/heromodels/src/models/biz/company.rs @@ -99,9 +99,9 @@ impl Index for CompanyRegistrationNumberIndex { // --- Builder Pattern --- impl Company { - pub fn new(id: u32, name: String, registration_number: String, incorporation_date: i64) -> Self { // incorporation_date to i64 + pub fn new(name: String, registration_number: String, incorporation_date: i64) -> Self { // incorporation_date to i64 Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), name, registration_number, incorporation_date, // This is i64 now diff --git a/heromodels/src/models/biz/product.rs b/heromodels/src/models/biz/product.rs index c49f807..031a593 100644 --- a/heromodels/src/models/biz/product.rs +++ b/heromodels/src/models/biz/product.rs @@ -84,9 +84,9 @@ impl Model for Product { } impl Product { - pub fn new(id: u32) -> Self { + pub fn new() -> Self { Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), name: String::new(), description: String::new(), price: 0.0, diff --git a/heromodels/src/models/biz/rhai.rs b/heromodels/src/models/biz/rhai.rs index 46cbe2f..7a8c2ec 100644 --- a/heromodels/src/models/biz/rhai.rs +++ b/heromodels/src/models/biz/rhai.rs @@ -43,7 +43,7 @@ pub fn register_biz_rhai_module(engine: &mut Engine, db: Arc) { engine.register_type_with_name::("Company"); // Constructor - engine.register_fn("new_company", |id: i64, name: String, registration_number: String, incorporation_date: i64| -> Result> { Ok(Company::new(id as u32, name, registration_number, incorporation_date)) }); + engine.register_fn("new_company", |name: String, registration_number: String, incorporation_date: i64| -> Result> { Ok(Company::new(name, registration_number, incorporation_date)) }); // Getters for Company engine.register_get("id", |company: &mut Company| -> Result> { Ok(company.get_id() as i64) }); @@ -85,9 +85,7 @@ pub fn register_biz_rhai_module(engine: &mut Engine, db: Arc) { engine.register_type_with_name::("Shareholder"); // Constructor for Shareholder (minimal, takes only ID) - engine.register_fn("new_shareholder", |id: i64| -> Result> { - Ok(Shareholder::new(id as u32)) - }); + engine.register_fn("new_shareholder", || -> Result> { Ok(Shareholder::new()) }); // Getters for Shareholder engine.register_get("id", |shareholder: &mut Shareholder| -> Result> { Ok(shareholder.get_id() as i64) }); @@ -147,7 +145,7 @@ pub fn register_biz_rhai_module(engine: &mut Engine, db: Arc) { // --- Product --- engine.register_type_with_name::("Product") - .register_fn("new_product", |id: i64| -> Result> { Ok(Product::new(id as u32)) }) + .register_fn("new_product", || -> Result> { Ok(Product::new()) }) // Getters for Product .register_get("id", |p: &mut Product| -> Result> { Ok(p.base_data.id as i64) }) .register_get("name", |p: &mut Product| -> Result> { Ok(p.name.clone()) }) @@ -212,9 +210,7 @@ pub fn register_biz_rhai_module(engine: &mut Engine, db: Arc) { // --- Sale --- engine.register_type_with_name::("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> { - Ok(Sale::new(id_from_i64(id_i64)?, id_from_i64(company_id_i64)?, buyer_name, buyer_email, total_amount, status, sale_date)) - }); + engine.register_fn("new_sale", |company_id_i64: i64, buyer_name: String, buyer_email: String, total_amount: f64, status: SaleStatus, sale_date: i64| -> Result> { Ok(Sale::new(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> { Ok(s.get_id() as i64) }); @@ -257,11 +253,14 @@ pub fn register_biz_rhai_module(engine: &mut Engine, db: Arc) { }); // DB functions for Product - let captured_db_for_set_prod = Arc::clone(&db); - engine.register_fn("set_product", move |product: Product| -> Result<(), Box> { - 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_set_product = Arc::clone(&db); + engine.register_fn("set_product", move |product: Product| -> Result> { + let original_id_for_error = product.get_id(); + captured_db_for_set_product.set(&product) + .map(|(_id, updated_product)| updated_product) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Product (Original ID: {}): {}", original_id_for_error, e).into(), Position::NONE)) + }) }); let captured_db_for_get_prod = Arc::clone(&db); @@ -274,10 +273,13 @@ pub fn register_biz_rhai_module(engine: &mut Engine, db: Arc) { // DB functions for Sale let captured_db_for_set_sale = Arc::clone(&db); - engine.register_fn("set_sale", move |sale: Sale| -> Result<(), Box> { - 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)) - }) + engine.register_fn("set_sale", move |sale: Sale| -> Result> { + let original_id_for_error = sale.get_id(); + captured_db_for_set_sale.set(&sale) + .map(|(_id, updated_sale)| updated_sale) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Sale (Original ID: {}): {}", original_id_for_error, e).into(), Position::NONE)) + }) }); let captured_db_for_get_sale = Arc::clone(&db); @@ -289,11 +291,14 @@ pub fn register_biz_rhai_module(engine: &mut Engine, db: Arc) { }); // Mock DB functions for Shareholder - let captured_db_for_set_sh = Arc::clone(&db); - engine.register_fn("set_shareholder", move |shareholder: Shareholder| -> Result<(), Box> { - 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_set_shareholder = Arc::clone(&db); + engine.register_fn("set_shareholder", move |shareholder: Shareholder| -> Result> { + let original_id_for_error = shareholder.get_id(); + captured_db_for_set_shareholder.set(&shareholder) + .map(|(_id, updated_shareholder)| updated_shareholder) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Shareholder (Original ID: {}): {}", original_id_for_error, e).into(), Position::NONE)) + }) }); let captured_db_for_get_sh = Arc::clone(&db); @@ -305,11 +310,14 @@ pub fn register_biz_rhai_module(engine: &mut Engine, db: Arc) { }); // Mock DB functions for Company - let captured_db_for_set = Arc::clone(&db); - engine.register_fn("set_company", move |company: Company| -> Result<(), Box> { - 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_set_company = Arc::clone(&db); + engine.register_fn("set_company", move |company: Company| -> Result> { + let original_id_for_error = company.get_id(); // Capture ID before it's potentially changed by DB + captured_db_for_set_company.set(&company) + .map(|(_id, updated_company)| updated_company) // Use the model returned by db.set() + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Company (Original ID: {}): {}", original_id_for_error, e).into(), Position::NONE)) + }) }); let captured_db_for_get = Arc::clone(&db); diff --git a/heromodels/src/models/biz/sale.rs b/heromodels/src/models/biz/sale.rs index 3c45af4..2d6d39f 100644 --- a/heromodels/src/models/biz/sale.rs +++ b/heromodels/src/models/biz/sale.rs @@ -102,7 +102,6 @@ impl Model for Sale { impl Sale { /// Creates a new `Sale`. pub fn new( - id: u32, company_id: u32, buyer_name: String, buyer_email: String, @@ -111,7 +110,7 @@ impl Sale { sale_date: i64, ) -> Self { Sale { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), company_id, buyer_name, buyer_email, diff --git a/heromodels/src/models/biz/shareholder.rs b/heromodels/src/models/biz/shareholder.rs index 0b982f9..e449301 100644 --- a/heromodels/src/models/biz/shareholder.rs +++ b/heromodels/src/models/biz/shareholder.rs @@ -26,9 +26,9 @@ pub struct Shareholder { } impl Shareholder { - pub fn new(id: u32) -> Self { + pub fn new() -> Self { Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), company_id: 0, // Default, to be set by builder user_id: 0, // Default, to be set by builder name: String::new(), // Default diff --git a/heromodels/src/models/calendar/calendar.rs b/heromodels/src/models/calendar/calendar.rs index 404a2d3..2ca849c 100644 --- a/heromodels/src/models/calendar/calendar.rs +++ b/heromodels/src/models/calendar/calendar.rs @@ -60,17 +60,13 @@ pub struct Event { impl Event { /// Creates a new event - pub fn new( - id: i64, - title: impl ToString, - start_time: DateTime, - end_time: DateTime, - ) -> Self { - base_data: BaseModelData::new(id as u32), - title: String::new(), + pub fn new(title: impl ToString, start_time: DateTime, end_time: DateTime) -> Self { + Self { + base_data: BaseModelData::new(), + title: title.to_string(), description: None, - start_time: Utc::now(), - end_time: Utc::now(), + start_time, + end_time, attendees: Vec::new(), location: None, } diff --git a/heromodels/src/models/calendar/rhai.rs b/heromodels/src/models/calendar/rhai.rs index b91f3b2..d4e1f12 100644 --- a/heromodels/src/models/calendar/rhai.rs +++ b/heromodels/src/models/calendar/rhai.rs @@ -1,25 +1,50 @@ -use rhai::{Engine, EvalAltResult, NativeCallContext}; +use rhai::{Engine, EvalAltResult, NativeCallContext, ImmutableString}; 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; +use adapter_macros::rhai_timestamp_helpers; // Helper function for get_all_calendars registration -fn get_all_calendars_helper(_db: Arc) -> Vec { - // In a real implementation, this would retrieve all calendars from the database - vec![Calendar::new(1 as u32), Calendar::new(2 as u32)] + + +fn new_calendar_rhai(name: String) -> Result> { + Ok(Calendar::new(None, name)) +} + +fn new_event_rhai( + context: NativeCallContext, + title_rhai: ImmutableString, + start_time_ts: i64, + end_time_ts: i64, +) -> Result> { + let start_time = rhai_timestamp_helpers::rhai_timestamp_to_datetime(start_time_ts) + .map_err(|e_str| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to convert start_time for Event: {}", e_str).into(), + context.position(), + )))?; + + let end_time = rhai_timestamp_helpers::rhai_timestamp_to_datetime(end_time_ts) + .map_err(|e_str| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to convert end_time for Event: {}", e_str).into(), + context.position(), + )))?; + + Ok(Event::new(title_rhai.to_string(), start_time, end_time)) } pub fn register_rhai_engine_functions(engine: &mut Engine, db: Arc) { - 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("add_event", Calendar::add_event); + // Note: Event IDs are i64 in Calendar.events, but Event model's base_data.id is u32. + // This might require adjustment if events are fetched by ID from the DB via Calendar.events. - engine.register_fn("new_event", Event::new); // Corrected: expects i64, Rhai provides i64 + engine.register_fn("new_event", |context: NativeCallContext, title_rhai: ImmutableString, start_time_ts: i64, end_time_ts: i64| -> Result> { + new_event_rhai(context, title_rhai, start_time_ts, end_time_ts) + }); 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)); @@ -36,6 +61,8 @@ pub fn register_rhai_engine_functions(engine: &mut Engine, db: Arc) { engine.register_fn("new_attendee", adapt_rhai_i64_input_fn!(Attendee::new, u32)); + engine.register_fn("new_calendar", |name: String| -> Result> { new_calendar_rhai(name) }); + // Register a function to get the database instance engine.register_fn("get_db", move || db.clone()); @@ -53,24 +80,8 @@ pub fn register_rhai_engine_functions(engine: &mut Engine, db: Arc) { // Register getters for BaseModelData engine.register_get("id", |bmd: &mut BaseModelData| -> Result> { 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, calendar: Calendar| { - println!("Mock save: Calendar saved: {}", calendar.name); - }); - - engine.register_fn("get_calendar_by_id", |_db: Arc, id_i64: i64| -> Calendar { - Calendar::new(id_i64 as u32) - }); - - engine.register_fn("calendar_exists", |_db: Arc, 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 => Calendar)); - - engine.register_fn("delete_calendar_by_id", |_db: Arc, id_i64: i64| { - println!("Mock delete: Calendar deleted with ID: {}", id_i64); - }); + // Database interaction functions for Calendar are expected to be provided by #[rhai_model_export(..)] on the Calendar struct. + // Ensure that `db.rs` or similar correctly wires up the `OurDB` methods for these. // Getters for Event engine.register_get("id", |e: &mut Event| -> Result> { Ok(e.base_data.id as i64) }); diff --git a/heromodels/src/models/finance/account.rs b/heromodels/src/models/finance/account.rs index 719dcb5..d6f9648 100644 --- a/heromodels/src/models/finance/account.rs +++ b/heromodels/src/models/finance/account.rs @@ -4,8 +4,6 @@ use serde::{Deserialize, Serialize}; use rhai::{CustomType, TypeBuilder}; use heromodels_derive::model; use heromodels_core::BaseModelData; -use heromodels_derive::model; -use serde::{Deserialize, Serialize}; use super::asset::Asset; diff --git a/heromodels/src/models/finance/asset.rs b/heromodels/src/models/finance/asset.rs index 8363c22..40a5acf 100644 --- a/heromodels/src/models/finance/asset.rs +++ b/heromodels/src/models/finance/asset.rs @@ -4,8 +4,6 @@ use serde::{Deserialize, Serialize}; use rhai::{CustomType, TypeBuilder}; use heromodels_derive::model; use heromodels_core::BaseModelData; -use heromodels_derive::model; -use serde::{Deserialize, Serialize}; /// AssetType defines the type of blockchain asset #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/heromodels/src/models/finance/marketplace.rs b/heromodels/src/models/finance/marketplace.rs index 431251e..701e1cd 100644 --- a/heromodels/src/models/finance/marketplace.rs +++ b/heromodels/src/models/finance/marketplace.rs @@ -5,8 +5,6 @@ use rhai::{CustomType, TypeBuilder}; use chrono::{DateTime, Utc}; use heromodels_core::BaseModelData; use heromodels_derive::model; -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; use super::asset::AssetType; diff --git a/heromodels/src/models/finance/rhai.rs b/heromodels/src/models/finance/rhai.rs index 6aaae98..7185078 100644 --- a/heromodels/src/models/finance/rhai.rs +++ b/heromodels/src/models/finance/rhai.rs @@ -1,13 +1,22 @@ -use rhai::{Engine, Array, ImmutableString, INT, EvalAltResult}; +use rhai::{Engine, Array, Dynamic, ImmutableString, INT, EvalAltResult, NativeCallContext}; use std::sync::{Arc, Mutex}; use std::collections::HashMap; +use std::error::Error as StdError; // For Box -use chrono::Utc; +// Custom error type for Rhai that wraps a String +#[derive(Debug)] +struct RhaiStringError(String); -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; +impl std::fmt::Display for RhaiStringError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl StdError for RhaiStringError {} + + +use chrono::{DateTime, Utc}; use crate::models::finance::account::Account; use crate::models::finance::asset::{Asset, AssetType}; @@ -67,7 +76,6 @@ fn string_to_bid_status(s: &str) -> Result> { } } - pub fn register_rhai_engine_functions( engine: &mut Engine, db_accounts: Arc>>, @@ -75,68 +83,213 @@ pub fn register_rhai_engine_functions( db_listings: Arc>>, ) { // --- Account model --- - engine.build_type::() - .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() + engine.register_type_with_name::("Account"); + engine.register_fn("new_account", || -> Account { + Account::new(None, "", 0, "", "", "", "") + }); + // Getters + engine.register_get("id", |acc: &mut Account| acc.base_data.id as INT); + engine.register_get("created_at_ts", |acc: &mut Account| acc.base_data.created_at); + engine.register_get("name", |acc: &mut Account| -> ImmutableString { acc.name.clone().into() }); + engine.register_get("user_id", |acc: &mut Account| acc.user_id as INT); + engine.register_get("description", |acc: &mut Account| -> ImmutableString { acc.description.clone().into() }); + engine.register_get("ledger", |acc: &mut Account| -> ImmutableString { acc.ledger.clone().into() }); + engine.register_get("address", |acc: &mut Account| -> ImmutableString { acc.address.clone().into() }); + engine.register_get("pubkey", |acc: &mut Account| -> ImmutableString { acc.pubkey.clone().into() }); + engine.register_get("assets_list", |acc: &mut Account| -> Result> { + Ok(acc.assets.iter().cloned().map(rhai::Dynamic::from).collect()) + }); + engine.register_get("modified_at_ts", |acc: &mut Account| acc.base_data.modified_at); + + // Setters (Builder Pattern) + engine.register_fn("set_name", |mut acc: Account, name: ImmutableString| -> Result> { + acc.name = name.to_string(); Ok(acc) + }); + engine.register_fn("set_user_id", |mut acc: Account, user_id: INT| -> Result> { + acc.user_id = user_id as u32; Ok(acc) + }); + engine.register_fn("set_description", |mut acc: Account, description: ImmutableString| -> Result> { + acc.description = description.to_string(); Ok(acc) + }); + engine.register_fn("set_ledger", |mut acc: Account, ledger: ImmutableString| -> Result> { + acc.ledger = ledger.to_string(); Ok(acc) + }); + engine.register_fn("set_address", |mut acc: Account, address: ImmutableString| -> Result> { + acc.address = address.to_string(); Ok(acc) + }); + engine.register_fn("set_pubkey", |mut acc: Account, pubkey: ImmutableString| -> Result> { + acc.pubkey = pubkey.to_string(); Ok(acc) + }); + // Action: Add an Asset object to the account's asset list + engine.register_fn("add_asset", |mut acc: Account, asset: Asset| -> Result> { + acc.assets.push(asset); + Ok(acc) }); // --- Asset model --- - engine.build_type::() - .register_fn("new_asset", - |id_rhai: INT, name: ImmutableString, description: ImmutableString, amount: f64, address: ImmutableString, asset_type_str_rhai: ImmutableString, decimals_rhai: INT| -> Result> { - 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); + engine.register_type_with_name::("Asset"); + engine.register_fn("new_asset", || -> Asset { + Asset::new(None, "", "", 0.0, "", AssetType::Native, 0) + }); + // Getters + engine.register_get("id", |asset: &mut Asset| asset.base_data.id as INT); + engine.register_get("created_at_ts", |asset: &mut Asset| asset.base_data.created_at); + engine.register_get("name", |asset: &mut Asset| -> ImmutableString { asset.name.clone().into() }); + engine.register_get("description", |asset: &mut Asset| -> ImmutableString { asset.description.clone().into() }); + engine.register_get("amount", |asset: &mut Asset| asset.amount); + engine.register_get("address", |asset: &mut Asset| -> ImmutableString { asset.address.clone().into() }); + engine.register_get("asset_type_str", |asset: &mut Asset| -> ImmutableString { self::asset_type_to_string(&asset.asset_type) }); + engine.register_get("decimals", |asset: &mut Asset| asset.decimals as INT); + engine.register_get("modified_at_ts", |asset: &mut Asset| asset.base_data.modified_at); + + // Setters (Builder Pattern) + engine.register_fn("set_name", |mut asset: Asset, name: ImmutableString| -> Result> { + asset.name = name.to_string(); Ok(asset) + }); + engine.register_fn("set_description", |mut asset: Asset, description: ImmutableString| -> Result> { + asset.description = description.to_string(); Ok(asset) + }); + engine.register_fn("set_amount", |mut asset: Asset, amount: f64| -> Result> { + asset.amount = amount; Ok(asset) + }); + engine.register_fn("set_address", |mut asset: Asset, address: ImmutableString| -> Result> { + asset.address = address.to_string(); Ok(asset) + }); + engine.register_fn("set_asset_type_str", |mut asset: Asset, asset_type_str: ImmutableString| -> Result> { + asset.asset_type = self::string_to_asset_type(asset_type_str.as_str())?; + Ok(asset) + }); + engine.register_fn("set_decimals", |mut asset: Asset, decimals: INT| -> Result> { + asset.decimals = decimals as u8; Ok(asset) + }); - // --- Bid model --- + // --- Listing model --- + engine.register_type_with_name::("Listing"); + engine.register_fn("new_listing", || -> Listing { + Listing::new(None, "", "", "", AssetType::Native, "", 0.0, "", ListingType::FixedPrice, None, Vec::new(), None::) + }); + // Getters + engine.register_get("id", |l: &mut Listing| l.base_data.id as INT); + engine.register_get("created_at_ts", |l: &mut Listing| l.base_data.created_at); + engine.register_get("modified_at_ts", |l: &mut Listing| l.base_data.modified_at); + engine.register_get("title", |l: &mut Listing| -> ImmutableString { l.title.clone().into() }); + engine.register_get("description", |l: &mut Listing| -> ImmutableString { l.description.clone().into() }); + engine.register_get("asset_id", |l: &mut Listing| -> ImmutableString { l.asset_id.clone().into() }); + engine.register_get("asset_type_str", |l: &mut Listing| -> ImmutableString { self::asset_type_to_string(&l.asset_type) }); + engine.register_get("seller_id", |l: &mut Listing| -> ImmutableString { l.seller_id.clone().into() }); + engine.register_get("price", |l: &mut Listing| l.price); + engine.register_get("currency", |l: &mut Listing| -> ImmutableString { l.currency.clone().into() }); + engine.register_get("listing_type_str", |l: &mut Listing| -> ImmutableString { self::listing_type_to_string(&l.listing_type) }); + engine.register_get("status_str", |l: &mut Listing| -> ImmutableString { self::listing_status_to_string(&l.status) }); + engine.register_get("expires_at_ts", |l: &mut Listing| l.expires_at); + engine.register_get("tags", |l: &mut Listing| -> Result> { + Ok(l.tags.iter().map(|s| Dynamic::from(s.clone())).collect()) + }); + engine.register_get("image_url", |l: &mut Listing| -> Option { l.image_url.as_ref().map(|s| s.clone().into()) }); + engine.register_get("bids_list", |l: &mut Listing| -> Result> { + Ok(l.bids.iter().cloned().map(rhai::Dynamic::from).collect()) + }); + // Setters (Builder Pattern) + engine.register_fn("set_title", |mut l: Listing, title: ImmutableString| -> Result> { + l.title = title.to_string(); Ok(l) + }); + engine.register_fn("set_description", |mut l: Listing, description: ImmutableString| -> Result> { + l.description = description.to_string(); Ok(l) + }); + engine.register_fn("set_asset_id", |mut l: Listing, asset_id: ImmutableString| -> Result> { + l.asset_id = asset_id.to_string(); Ok(l) + }); + engine.register_fn("set_asset_type_str", |mut l: Listing, asset_type_str: ImmutableString| -> Result> { + l.asset_type = self::string_to_asset_type(asset_type_str.as_str())?; + Ok(l) + }); + engine.register_fn("set_seller_id", |mut l: Listing, seller_id: ImmutableString| -> Result> { + l.seller_id = seller_id.to_string(); Ok(l) + }); + engine.register_fn("set_price", |mut l: Listing, price: f64| -> Result> { + l.price = price; Ok(l) + }); + engine.register_fn("set_currency", |mut l: Listing, currency: ImmutableString| -> Result> { + l.currency = currency.to_string(); Ok(l) + }); + engine.register_fn("set_listing_type_str", |mut l: Listing, listing_type_str: ImmutableString| -> Result> { + l.listing_type = self::string_to_listing_type(listing_type_str.as_str())?; + Ok(l) + }); + engine.register_fn("set_status_str", |mut l: Listing, status_str: ImmutableString| -> Result> { + l.status = self::string_to_listing_status(status_str.as_str())?; + Ok(l) + }); + engine.register_fn("set_expires_at_ts", |mut l: Listing, expires_at_ts: Option| -> Result> { + l.expires_at = expires_at_ts.map(|ts| DateTime::from_timestamp(ts, 0).unwrap_or_else(|| Utc::now())); + Ok(l) + }); + engine.register_fn("set_tags", |mut l: Listing, tags_array: Array| -> Result> { + l.tags = tags_array.into_iter().map(|d| d.into_string().unwrap_or_default()).collect(); + Ok(l) + }); + engine.register_fn("add_tag", |mut l: Listing, tag: ImmutableString| -> Result> { + l.tags.push(tag.to_string()); + Ok(l) + }); + engine.register_fn("set_image_url", |mut l: Listing, image_url: Option| -> Result> { + l.image_url = image_url.map(|s| s.to_string()); + Ok(l) + }); + // Listing Action Methods (preserved) + engine.register_fn("add_listing_bid", |listing: Listing, bid: Bid| -> Result> { + listing.add_bid(bid).map_err(|e_str| { + Box::new(EvalAltResult::ErrorSystem( + "Failed to add bid".to_string(), + Box::new(RhaiStringError(e_str.to_string())), + )) + }) + }); + engine.register_fn("accept_listing_bid", |listing: Listing, bid_index_rhai: i64| -> Result> { + let bid_index = bid_index_rhai as usize; + if bid_index >= listing.bids.len() { + return Err(Box::new(EvalAltResult::ErrorSystem( + "Invalid bid index".to_string(), + Box::new(RhaiStringError(format!("Bid index {} out of bounds for {} bids", bid_index, listing.bids.len()))), + ))); + } + + let bid_to_accept = listing.bids[bid_index].clone(); + + if bid_to_accept.status != BidStatus::Active { + return Err(Box::new(EvalAltResult::ErrorSystem( + "Bid not active".to_string(), + Box::new(RhaiStringError(format!("Cannot accept bid at index {} as it is not active (status: {:?})", bid_index, bid_to_accept.status))), + ))); + } + + let mut listing_after_sale = listing.complete_sale(bid_to_accept.bidder_id.to_string(), bid_to_accept.amount) + .map_err(|e_str| Box::new(EvalAltResult::ErrorSystem( + "Failed to complete sale".to_string(), + Box::new(RhaiStringError(e_str.to_string())), + )))?; + + // Update bid statuses on the new listing state + for (idx, bid_in_list) in listing_after_sale.bids.iter_mut().enumerate() { + if idx == bid_index { + *bid_in_list = bid_in_list.clone().update_status(BidStatus::Accepted); + } else { + if bid_in_list.status == BidStatus::Active { // Only reject other active bids + *bid_in_list = bid_in_list.clone().update_status(BidStatus::Rejected); + } + } + } + Ok(listing_after_sale) + }); + engine.register_fn("cancel_listing", |_ctx: NativeCallContext, listing: Listing| -> Result> { + listing.cancel().map_err(|e_str| { + Box::new(EvalAltResult::ErrorSystem( + "Failed to cancel listing".to_string(), + Box::new(RhaiStringError(e_str.to_string())) + )) + }) + }); + // --- Bid model (preserved as is) --- engine.register_type_with_name::("Bid") .register_fn("new_bid", |listing_id_rhai: ImmutableString, bidder_id_rhai: INT, amount_rhai: f64, currency_rhai: ImmutableString| -> Bid { @@ -152,110 +305,41 @@ pub fn register_rhai_engine_functions( .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> { - let status = self::string_to_bid_status(status_str_rhai.as_str())?; - let updated_bid = bid.update_status(status); - Ok(updated_bid) + ) + .register_get("status_str", |bid: &mut Bid| -> ImmutableString { self::bid_status_to_string(&bid.status) }); + + engine.register_fn("accept_bid_script", |bid: Bid| -> Result> { + // Ensure the bid is active before accepting + if bid.status != BidStatus::Active { + return Err(Box::new(EvalAltResult::ErrorSystem( + "Bid not active".to_string(), + Box::new(RhaiStringError(format!("Cannot accept bid as it is not active (status: {:?})", bid.status))) + ))); } - ); - - // --- Listing --- (id is u32) - engine.register_type_with_name::("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, tags_dyn_rhai: Array, image_url_opt_rhai: Option| - -> Result> { - - 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 { l.buyer_id.clone().map(ImmutableString::from) }, - |l: &mut Listing, v_opt: Option| l.buyer_id = v_opt.map(|s| s.to_string()) - ) - .register_get_set("sale_price", - |l: &mut Listing| -> Option { l.sale_price }, - |l: &mut Listing, v_opt: Option| l.sale_price = v_opt - ) - .register_get_set("image_url", - |l: &mut Listing| -> Option { l.image_url.clone().map(ImmutableString::from) }, - |l: &mut Listing, v: Option| 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.add_bid(bid).map_err(|e| e.into()) + Ok(bid.update_status(BidStatus::Accepted)) + }); + engine.register_fn("reject_bid_script", |bid: Bid| -> Result> { + // Ensure the bid is active before rejecting + if bid.status != BidStatus::Active { + return Err(Box::new(EvalAltResult::ErrorSystem( + "Bid not active".to_string(), + Box::new(RhaiStringError(format!("Cannot reject bid as it is not active (status: {:?})", bid.status))) + ))); } - ); - 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.complete_sale(buyer_id_rhai.as_str(), sale_price_rhai).map_err(|e| e.into()) + Ok(bid.update_status(BidStatus::Rejected)) + }); + engine.register_fn("cancel_bid_script", |bid: Bid| -> Result> { + // Ensure the bid is active before cancelling + if bid.status != BidStatus::Active { + return Err(Box::new(EvalAltResult::ErrorSystem( + "Bid not active".to_string(), + Box::new(RhaiStringError(format!("Cannot cancel bid as it is not active (status: {:?})", bid.status))) + ))); } - ); - engine.register_fn("cancel_listing_script", - |listing: Listing| -> Result> { - listing.cancel().map_err(|e| e.into()) - } - ); - engine.register_fn("add_listing_tags_script", - |listing: Listing, tags_dyn_rhai: Array| -> Result> { - let tags_to_add: Vec = tags_dyn_rhai.into_iter().map(|d| d.into_string().unwrap_or_default()).collect(); - Ok(listing.add_tags(tags_to_add)) - } - ); + Ok(bid.update_status(BidStatus::Cancelled)) + }); // --- 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())); @@ -265,12 +349,16 @@ pub fn register_rhai_engine_functions( 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 --- + // --- Mock DB functions (preserved) --- let accounts_db_clone = Arc::clone(&db_accounts); - engine.register_fn("set_account", move |account: Account| { + engine.register_fn("set_account", move |mut account: Account| -> Account { let mut db = accounts_db_clone.lock().unwrap(); - db.insert(account.base_data.id, account); + if account.base_data.id == 0 { + let next_id = db.keys().max().cloned().unwrap_or(0) + 1; + account.base_data.update_id(next_id); + } + db.insert(account.base_data.id, account.clone()); + account }); let accounts_db_clone_get = Arc::clone(&db_accounts); @@ -283,9 +371,14 @@ pub fn register_rhai_engine_functions( }); let assets_db_clone = Arc::clone(&db_assets); - engine.register_fn("set_asset", move |asset: Asset| { + engine.register_fn("set_asset", move |mut asset: Asset| -> Asset { let mut db = assets_db_clone.lock().unwrap(); - db.insert(asset.base_data.id, asset); + if asset.base_data.id == 0 { + let next_id = db.keys().max().cloned().unwrap_or(0) + 1; + asset.base_data.update_id(next_id); + } + db.insert(asset.base_data.id, asset.clone()); + asset }); let assets_db_clone_get = Arc::clone(&db_assets); @@ -298,9 +391,14 @@ pub fn register_rhai_engine_functions( }); let listings_db_clone = Arc::clone(&db_listings); - engine.register_fn("set_listing", move |listing: Listing| { + engine.register_fn("set_listing", move |mut listing: Listing| -> Listing { let mut db = listings_db_clone.lock().unwrap(); - db.insert(listing.base_data.id, listing); + if listing.base_data.id == 0 { + let next_id = db.keys().max().cloned().unwrap_or(0) + 1; + listing.base_data.update_id(next_id); + } + db.insert(listing.base_data.id, listing.clone()); + listing }); let listings_db_clone_get = Arc::clone(&db_listings); diff --git a/heromodels/src/models/flow/flow.rs b/heromodels/src/models/flow/flow.rs index 6fb6e53..71a0e46 100644 --- a/heromodels/src/models/flow/flow.rs +++ b/heromodels/src/models/flow/flow.rs @@ -29,9 +29,9 @@ 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) -> Self { + pub fn new(_id: u32, flow_uuid: impl ToString) -> Self { Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), flow_uuid: flow_uuid.to_string(), name: String::new(), // Default name, to be set by builder status: String::from("Pending"), // Default status, to be set by builder diff --git a/heromodels/src/models/flow/flow_step.rs b/heromodels/src/models/flow/flow_step.rs index 908efa3..432279f 100644 --- a/heromodels/src/models/flow/flow_step.rs +++ b/heromodels/src/models/flow/flow_step.rs @@ -22,9 +22,9 @@ pub struct FlowStep { impl FlowStep { /// Create a new flow step. - pub fn new(id: u32, step_order: u32) -> Self { + pub fn new(_id: u32, step_order: u32) -> Self { Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), description: None, step_order, status: String::from("Pending"), // Default status diff --git a/heromodels/src/models/flow/rhai.rs b/heromodels/src/models/flow/rhai.rs index 362e751..dff653d 100644 --- a/heromodels/src/models/flow/rhai.rs +++ b/heromodels/src/models/flow/rhai.rs @@ -103,7 +103,7 @@ pub fn register_flow_rhai_module(engine: &mut Engine, db: Arc) { let captured_db_for_set_flow = Arc::clone(&db); engine.register_fn("set_flow", move |flow: Flow| -> Result<(), Box> { - captured_db_for_set_flow.set(&flow).map_err(|e| { + captured_db_for_set_flow.set(&flow).map(|_| ()).map_err(|e| { Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Flow (ID: {}): {}", flow.base_data.id, e).into(), Position::NONE)) }) }); @@ -121,7 +121,7 @@ pub fn register_flow_rhai_module(engine: &mut Engine, db: Arc) { let captured_db_for_set_sig_req = Arc::clone(&db); engine.register_fn("set_signature_requirement", move |sr: SignatureRequirement| -> Result<(), Box> { - captured_db_for_set_sig_req.set(&sr).map_err(|e| { + captured_db_for_set_sig_req.set(&sr).map(|_| ()).map_err(|e| { Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set SignatureRequirement (ID: {}): {}", sr.base_data.id, e).into(), Position::NONE)) }) }); diff --git a/heromodels/src/models/flow/signature_requirement.rs b/heromodels/src/models/flow/signature_requirement.rs index 46f4e69..44007cb 100644 --- a/heromodels/src/models/flow/signature_requirement.rs +++ b/heromodels/src/models/flow/signature_requirement.rs @@ -31,9 +31,9 @@ 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) -> Self { + pub fn new(_id: u32, flow_step_id: u32, public_key: impl ToString, message: impl ToString) -> Self { Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), flow_step_id, public_key: public_key.to_string(), message: message.to_string(), diff --git a/heromodels/src/models/legal/contract.rs b/heromodels/src/models/legal/contract.rs index 0b7fe8e..4e871fc 100644 --- a/heromodels/src/models/legal/contract.rs +++ b/heromodels/src/models/legal/contract.rs @@ -161,9 +161,9 @@ pub struct Contract { } impl Contract { - pub fn new(base_id: u32, contract_id: String) -> Self { + pub fn new(_base_id: u32, contract_id: String) -> Self { Self { - base_data: BaseModelData::new(base_id), + base_data: BaseModelData::new(), contract_id, title: String::new(), description: String::new(), diff --git a/heromodels/src/models/legal/rhai.rs b/heromodels/src/models/legal/rhai.rs index 5d9f5f6..d374d54 100644 --- a/heromodels/src/models/legal/rhai.rs +++ b/heromodels/src/models/legal/rhai.rs @@ -239,7 +239,7 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { let captured_db_for_set = Arc::clone(&db); engine.register_fn("set_contract", move |contract: Contract| -> Result<(), Box> { - captured_db_for_set.set(&contract).map_err(|e| { + captured_db_for_set.set(&contract).map(|_| ()).map_err(|e| { Box::new(EvalAltResult::ErrorRuntime( format!("Failed to set Contract (ID: {}): {}", contract.base_data.id, e).into(), Position::NONE, diff --git a/heromodels/src/models/projects/base.rs b/heromodels/src/models/projects/base.rs index b85119a..d3ffefd 100644 --- a/heromodels/src/models/projects/base.rs +++ b/heromodels/src/models/projects/base.rs @@ -91,9 +91,9 @@ impl Model for Project { } impl Project { - pub fn new(id: u32, name: String, description: String, owner_id: u32) -> Self { + pub fn new(_id: u32, name: String, description: String, owner_id: u32) -> Self { Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), name, description, owner_id, @@ -207,8 +207,10 @@ impl Model for Label { impl Label { pub fn new(id: u32, name: String, color: String) -> Self { + let mut base_data = BaseModelData::new(); + base_data.update_id(id); Self { - base_data: BaseModelData::new(id), + base_data, name, color, } diff --git a/heromodels/src/models/projects/rhai.rs b/heromodels/src/models/projects/rhai.rs index 89e8456..27fd7ce 100644 --- a/heromodels/src/models/projects/rhai.rs +++ b/heromodels/src/models/projects/rhai.rs @@ -217,7 +217,7 @@ pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc) { )) })?; - collection.set(&project).map_err(|e| { + collection.set(&project).map(|_| ()).map_err(|e| { Box::new(EvalAltResult::ErrorSystem( "Failed to save project".to_string(), format!("DB operation failed: {:?}", e).into(),