From e91a44ce3717484f7e31c6c1732ae979217e6458 Mon Sep 17 00:00:00 2001 From: timurgordon Date: Thu, 19 Jun 2025 13:18:10 +0300 Subject: [PATCH] fmt, fixes and additions --- heromodels-derive/tests/test_model_macro.rs | 20 +- heromodels/Cargo.toml | 4 - heromodels/examples/basic_user_example.rs | 30 +- heromodels/examples/biz_rhai/example.rs | 12 +- heromodels/examples/calendar_example/main.rs | 110 ++- heromodels/examples/calendar_rhai/example.rs | 39 +- heromodels/examples/custom_model_example.rs | 14 +- heromodels/examples/finance_example/main.rs | 305 ++++--- heromodels/examples/finance_rhai/example.rs | 36 +- heromodels/examples/flow_example.rs | 105 ++- heromodels/examples/flow_rhai/example.rs | 11 +- .../governance_proposal_example/main.rs | 58 +- .../governance_rhai_client/example.rs | 309 ++++--- heromodels/examples/legal_contract_example.rs | 11 +- heromodels/examples/legal_rhai/example.rs | 11 +- heromodels/examples/library_rhai/example.rs | 1 - heromodels/examples/model_macro_example.rs | 37 +- heromodels/examples/project_rhai/example.rs | 12 +- heromodels/examples/simple_model_example.rs | 15 +- heromodels/src/db.rs | 4 +- heromodels/src/db/hero.rs | 8 +- heromodels/src/models/access/access.rs | 2 +- heromodels/src/models/access/mod.rs | 2 +- heromodels/src/models/access/rhai.rs | 215 +++-- heromodels/src/models/biz/company.rs | 12 +- heromodels/src/models/biz/mod.rs | 5 +- heromodels/src/models/biz/product.rs | 4 +- heromodels/src/models/biz/rhai.rs | 590 ++++++++----- heromodels/src/models/biz/sale.rs | 2 +- heromodels/src/models/biz/shareholder.rs | 16 +- heromodels/src/models/calendar/calendar.rs | 23 +- heromodels/src/models/calendar/mod.rs | 2 +- heromodels/src/models/calendar/rhai.rs | 435 ++++++---- heromodels/src/models/circle/circle.rs | 14 +- heromodels/src/models/circle/mod.rs | 2 +- heromodels/src/models/circle/rhai.rs | 258 ++++-- heromodels/src/models/contact/contact.rs | 2 +- heromodels/src/models/contact/rhai.rs | 351 +++++--- heromodels/src/models/core/comment.rs | 2 +- heromodels/src/models/core/mod.rs | 1 - heromodels/src/models/core/model.rs | 1 + heromodels/src/models/finance/account.rs | 10 +- heromodels/src/models/finance/asset.rs | 6 +- heromodels/src/models/finance/marketplace.rs | 11 +- heromodels/src/models/finance/mod.rs | 2 +- heromodels/src/models/finance/rhai.rs | 394 ++++++--- heromodels/src/models/flow/flow.rs | 4 +- heromodels/src/models/flow/mod.rs | 4 +- heromodels/src/models/flow/rhai.rs | 568 +++++++----- .../src/models/flow/signature_requirement.rs | 7 +- heromodels/src/models/governance/mod.rs | 4 +- heromodels/src/models/governance/proposal.rs | 22 +- heromodels/src/models/legal/contract.rs | 22 +- heromodels/src/models/legal/rhai.rs | 666 ++++++++++---- heromodels/src/models/library/collection.rs | 2 +- heromodels/src/models/library/rhai.rs | 815 +++++++++++++----- heromodels/src/models/mod.rs | 40 +- heromodels/src/models/projects/base.rs | 8 +- heromodels/src/models/projects/epic.rs | 10 +- heromodels/src/models/projects/mod.rs | 14 +- heromodels/src/models/projects/rhai.rs | 464 ++++++---- heromodels/src/models/projects/sprint.rs | 8 +- heromodels/src/models/projects/task.rs | 10 +- heromodels/src/models/userexample/mod.rs | 2 +- ourdb/Cargo.toml | 6 +- ourdb/examples/advanced_usage.rs | 145 ++-- ourdb/examples/basic_usage.rs | 55 +- ourdb/examples/benchmark.rs | 93 +- ourdb/examples/main.rs | 41 +- ourdb/src/backend.rs | 122 +-- ourdb/src/error.rs | 10 +- ourdb/src/lib.rs | 62 +- ourdb/src/location.rs | 50 +- ourdb/src/lookup.rs | 118 +-- ourdb/tests/integration_tests.rs | 225 +++-- rhai_client_macros/src/lib.rs | 208 ++--- tst/examples/basic_usage.rs | 30 +- tst/examples/performance.rs | 109 ++- tst/examples/prefix_ops.rs | 152 +++- tst/src/error.rs | 16 +- tst/src/lib.rs | 4 +- tst/src/node.rs | 14 +- tst/src/operations.rs | 152 ++-- tst/src/serialize.rs | 76 +- tst/tests/basic_test.rs | 141 ++- tst/tests/prefix_test.rs | 123 +-- 86 files changed, 5292 insertions(+), 2844 deletions(-) diff --git a/heromodels-derive/tests/test_model_macro.rs b/heromodels-derive/tests/test_model_macro.rs index fab6d70..a57771b 100644 --- a/heromodels-derive/tests/test_model_macro.rs +++ b/heromodels-derive/tests/test_model_macro.rs @@ -1,5 +1,5 @@ use heromodels_derive::model; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; // Define the necessary structs and traits for testing #[derive(Debug, Clone, Serialize, Deserialize)] @@ -46,10 +46,10 @@ pub trait Index { #[model] struct TestUser { base_data: BaseModelData, - + #[index] username: String, - + #[index] is_active: bool, } @@ -59,10 +59,10 @@ struct TestUser { #[model] struct TestUserWithCustomIndex { base_data: BaseModelData, - + #[index(name = "custom_username")] username: String, - + #[index] is_active: bool, } @@ -70,13 +70,13 @@ struct TestUserWithCustomIndex { #[test] fn test_basic_model() { assert_eq!(TestUser::db_prefix(), "test_user"); - + let user = TestUser { base_data: BaseModelData::new(1), username: "test".to_string(), is_active: true, }; - + let keys = user.db_keys(); assert_eq!(keys.len(), 2); assert_eq!(keys[0].name, "username"); @@ -92,10 +92,10 @@ fn test_custom_index_name() { username: "test".to_string(), is_active: true, }; - + // Check that the Username struct uses the custom index name assert_eq!(Username::key(), "custom_username"); - + // Check that the db_keys method returns the correct keys let keys = user.db_keys(); assert_eq!(keys.len(), 2); @@ -103,4 +103,4 @@ fn test_custom_index_name() { assert_eq!(keys[0].value, "test"); assert_eq!(keys[1].name, "is_active"); assert_eq!(keys[1].value, "true"); -} \ No newline at end of file +} diff --git a/heromodels/Cargo.toml b/heromodels/Cargo.toml index 9b36157..e7ba96c 100644 --- a/heromodels/Cargo.toml +++ b/heromodels/Cargo.toml @@ -42,10 +42,6 @@ path = "examples/finance_example/main.rs" name = "calendar_rhai" path = "examples/calendar_rhai/example.rs" -[[example]] -name = "calendar_rhai_client" -path = "examples/calendar_rhai_client/example.rs" - [[example]] name = "flow_rhai" path = "examples/flow_rhai/example.rs" diff --git a/heromodels/examples/basic_user_example.rs b/heromodels/examples/basic_user_example.rs index cedb29a..953b571 100644 --- a/heromodels/examples/basic_user_example.rs +++ b/heromodels/examples/basic_user_example.rs @@ -68,10 +68,26 @@ fn main() { .build(); // Save all users to database and get their assigned IDs and updated models - let (user1_id, db_user1) = db.collection().expect("can open user collection").set(&user1).expect("can set user"); - let (user2_id, db_user2) = db.collection().expect("can open user collection").set(&user2).expect("can set user"); - let (user3_id, db_user3) = db.collection().expect("can open user collection").set(&user3).expect("can set user"); - let (user4_id, db_user4) = db.collection().expect("can open user collection").set(&user4).expect("can set user"); + let (user1_id, db_user1) = db + .collection() + .expect("can open user collection") + .set(&user1) + .expect("can set user"); + let (user2_id, db_user2) = db + .collection() + .expect("can open user collection") + .set(&user2) + .expect("can set user"); + let (user3_id, db_user3) = db + .collection() + .expect("can open user collection") + .set(&user3) + .expect("can set user"); + let (user4_id, db_user4) = db + .collection() + .expect("can open user collection") + .set(&user4) + .expect("can set user"); println!("User 1 assigned ID: {}", user1_id); println!("User 2 assigned ID: {}", user2_id); @@ -170,7 +186,8 @@ fn main() { .build(); // Save the comment and get its assigned ID and updated model - let (comment_id, db_comment) = db.collection() + let (comment_id, db_comment) = db + .collection() .expect("can open comment collection") .set(&comment) .expect("can set comment"); @@ -186,7 +203,8 @@ fn main() { updated_user.base_data.add_comment(db_comment.get_id()); // Save the updated user and get the new version - let (_, user_with_comment) = db.collection::() + let (_, user_with_comment) = db + .collection::() .expect("can open user collection") .set(&updated_user) .expect("can set updated user"); diff --git a/heromodels/examples/biz_rhai/example.rs b/heromodels/examples/biz_rhai/example.rs index e857f5a..f316fae 100644 --- a/heromodels/examples/biz_rhai/example.rs +++ b/heromodels/examples/biz_rhai/example.rs @@ -1,8 +1,8 @@ -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 rhai::{Engine, EvalAltResult, Scope}; use std::fs; +use std::sync::Arc; fn main() -> Result<(), Box> { println!("Executing Rhai script: examples/biz_rhai/biz.rhai"); @@ -20,8 +20,12 @@ fn main() -> Result<(), Box> { // 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())))?; + 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(); diff --git a/heromodels/examples/calendar_example/main.rs b/heromodels/examples/calendar_example/main.rs index 6afec70..ba5c51f 100644 --- a/heromodels/examples/calendar_example/main.rs +++ b/heromodels/examples/calendar_example/main.rs @@ -1,6 +1,6 @@ use chrono::{Duration, Utc}; use heromodels::db::{Collection, Db}; -use heromodels::models::calendar::{Attendee, AttendanceStatus, Calendar, Event}; +use heromodels::models::calendar::{AttendanceStatus, Attendee, Calendar, Event}; use heromodels_core::Model; fn main() { @@ -12,10 +12,8 @@ fn main() { println!("===================================="); // --- Create Attendees --- - let attendee1 = Attendee::new("user_123".to_string()) - .status(AttendanceStatus::Accepted); - let attendee2 = Attendee::new("user_456".to_string()) - .status(AttendanceStatus::Tentative); + let attendee1 = Attendee::new("user_123".to_string()).status(AttendanceStatus::Accepted); + let attendee2 = Attendee::new("user_456".to_string()).status(AttendanceStatus::Tentative); let attendee3 = Attendee::new("user_789".to_string()); // Default NoResponse // --- Create Events --- @@ -45,7 +43,7 @@ fn main() { "event_gamma".to_string(), "Client Call", now + Duration::days(2), - now + Duration::days(2) + Duration::seconds(3600) + now + Duration::days(2) + Duration::seconds(3600), ); // --- Create Calendars --- @@ -58,25 +56,43 @@ fn main() { .add_event(event2.clone()); // Create a calendar with auto-generated ID (explicit IDs are no longer supported) - let calendar2 = Calendar::new(None, "Personal Calendar") - .add_event(event3_for_calendar2.clone()); - + let calendar2 = + Calendar::new(None, "Personal Calendar").add_event(event3_for_calendar2.clone()); // --- Store Calendars in DB --- - let cal_collection = db.collection::().expect("can open calendar collection"); + let cal_collection = db + .collection::() + .expect("can open calendar collection"); let (_, calendar1) = cal_collection.set(&calendar1).expect("can set calendar1"); let (_, calendar2) = cal_collection.set(&calendar2).expect("can set calendar2"); - println!("Created calendar1 (ID: {}): Name - '{}'", calendar1.get_id(), calendar1.name); - println!("Created calendar2 (ID: {}): Name - '{}'", calendar2.get_id(), calendar2.name); + println!( + "Created calendar1 (ID: {}): Name - '{}'", + calendar1.get_id(), + calendar1.name + ); + println!( + "Created calendar2 (ID: {}): Name - '{}'", + calendar2.get_id(), + calendar2.name + ); // --- Retrieve a Calendar by ID --- - let stored_calendar1_opt = cal_collection.get_by_id(calendar1.get_id()).expect("can try to load calendar1"); - assert!(stored_calendar1_opt.is_some(), "Calendar1 should be found in DB"); + let stored_calendar1_opt = cal_collection + .get_by_id(calendar1.get_id()) + .expect("can try to load calendar1"); + assert!( + stored_calendar1_opt.is_some(), + "Calendar1 should be found in DB" + ); let mut stored_calendar1 = stored_calendar1_opt.unwrap(); - println!("\nRetrieved calendar1 from DB: Name - '{}', Events count: {}", stored_calendar1.name, stored_calendar1.events.len()); + println!( + "\nRetrieved calendar1 from DB: Name - '{}', Events count: {}", + stored_calendar1.name, + stored_calendar1.events.len() + ); assert_eq!(stored_calendar1.name, "Work Calendar"); assert_eq!(stored_calendar1.events.len(), 2); assert_eq!(stored_calendar1.events[0].title, "Team Meeting"); @@ -84,49 +100,83 @@ fn main() { // --- Modify a Calendar (Reschedule an Event) --- let event_id_to_reschedule = event1.id.as_str(); let new_start_time = now + Duration::seconds(10800); // 3 hours from now - let new_end_time = now + Duration::seconds(14400); // 4 hours from now + let new_end_time = now + Duration::seconds(14400); // 4 hours from now stored_calendar1 = stored_calendar1.update_event(event_id_to_reschedule, |event_to_update| { println!("Rescheduling event '{}'...", event_to_update.title); event_to_update.reschedule(new_start_time, new_end_time) }); - let rescheduled_event = stored_calendar1.events.iter().find(|e| e.id == event_id_to_reschedule) + let rescheduled_event = stored_calendar1 + .events + .iter() + .find(|e| e.id == event_id_to_reschedule) .expect("Rescheduled event should exist"); assert_eq!(rescheduled_event.start_time, new_start_time); assert_eq!(rescheduled_event.end_time, new_end_time); - println!("Event '{}' rescheduled in stored_calendar1.", rescheduled_event.title); + println!( + "Event '{}' rescheduled in stored_calendar1.", + rescheduled_event.title + ); // --- Store the modified calendar --- - let (_, mut stored_calendar1) = cal_collection.set(&stored_calendar1).expect("can set modified calendar1"); - let re_retrieved_calendar1_opt = cal_collection.get_by_id(calendar1.get_id()).expect("can try to load modified calendar1"); + let (_, mut stored_calendar1) = cal_collection + .set(&stored_calendar1) + .expect("can set modified calendar1"); + let re_retrieved_calendar1_opt = cal_collection + .get_by_id(calendar1.get_id()) + .expect("can try to load modified calendar1"); let re_retrieved_calendar1 = re_retrieved_calendar1_opt.unwrap(); - let re_retrieved_event = re_retrieved_calendar1.events.iter().find(|e| e.id == event_id_to_reschedule) + let re_retrieved_event = re_retrieved_calendar1 + .events + .iter() + .find(|e| e.id == event_id_to_reschedule) .expect("Rescheduled event should exist in re-retrieved calendar"); - assert_eq!(re_retrieved_event.start_time, new_start_time, "Reschedule not persisted correctly"); + assert_eq!( + re_retrieved_event.start_time, new_start_time, + "Reschedule not persisted correctly" + ); - println!("\nModified and re-saved calendar1. Rescheduled event start time: {}", re_retrieved_event.start_time); + println!( + "\nModified and re-saved calendar1. Rescheduled event start time: {}", + re_retrieved_event.start_time + ); // --- Add a new event to an existing calendar --- let event4_new = Event::new( "event_delta".to_string(), "1-on-1", now + Duration::days(3), - now + Duration::days(3) + Duration::seconds(1800) // 30 minutes + now + Duration::days(3) + Duration::seconds(1800), // 30 minutes ); stored_calendar1 = stored_calendar1.add_event(event4_new); assert_eq!(stored_calendar1.events.len(), 3); - let (_, stored_calendar1) = cal_collection.set(&stored_calendar1).expect("can set calendar1 after adding new event"); - println!("Added new event '1-on-1' to stored_calendar1. Total events: {}", stored_calendar1.events.len()); + let (_, stored_calendar1) = cal_collection + .set(&stored_calendar1) + .expect("can set calendar1 after adding new event"); + println!( + "Added new event '1-on-1' to stored_calendar1. Total events: {}", + stored_calendar1.events.len() + ); // --- Delete a Calendar --- - cal_collection.delete_by_id(calendar2.get_id()).expect("can delete calendar2"); - let deleted_calendar2_opt = cal_collection.get_by_id(calendar2.get_id()).expect("can try to load deleted calendar2"); - assert!(deleted_calendar2_opt.is_none(), "Calendar2 should be deleted from DB"); + cal_collection + .delete_by_id(calendar2.get_id()) + .expect("can delete calendar2"); + let deleted_calendar2_opt = cal_collection + .get_by_id(calendar2.get_id()) + .expect("can try to load deleted calendar2"); + assert!( + deleted_calendar2_opt.is_none(), + "Calendar2 should be deleted from DB" + ); println!("\nDeleted calendar2 (ID: {}) from DB.", calendar2.get_id()); println!("Calendar model DB Prefix: {}", Calendar::db_prefix()); println!("\nExample finished. DB stored at {}", db_path); - println!("To clean up, you can manually delete the directory: {}", db_path); + println!( + "To clean up, you can manually delete the directory: {}", + db_path + ); } diff --git a/heromodels/examples/calendar_rhai/example.rs b/heromodels/examples/calendar_rhai/example.rs index 08b5501..dc7c05f 100644 --- a/heromodels/examples/calendar_rhai/example.rs +++ b/heromodels/examples/calendar_rhai/example.rs @@ -1,18 +1,17 @@ use heromodels::db::hero::OurDB; -use heromodels::models::calendar::{Attendee, AttendanceStatus, Calendar, Event}; use heromodels::models::calendar::rhai::register_rhai_engine_functions; +use heromodels::models::calendar::{AttendanceStatus, Attendee, Calendar, Event}; use rhai::Engine; +use rhai_wrapper::wrap_vec_return; use std::sync::Arc; use std::{fs, path::Path}; -use rhai_wrapper::wrap_vec_return; - fn main() -> Result<(), Box> { // Initialize Rhai engine let mut engine = Engine::new(); // Initialize database with OurDB - let db = Arc::new(OurDB::new("temp_calendar_db", true).expect("Failed to create database")); + 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 @@ -29,9 +28,12 @@ fn main() -> Result<(), Box> { }); // Register setter methods for Calendar properties - engine.register_fn("set_description", |calendar: &mut Calendar, desc: String| { - calendar.description = Some(desc); - }); + 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 { @@ -49,10 +51,13 @@ fn main() -> Result<(), Box> { println!("Calendar saved: {}", _calendar.name); }); - engine.register_fn("get_calendar_by_id", |_db: Arc, id: i64| -> Calendar { - // In a real implementation, this would retrieve the calendar from the database - Calendar::new(Some(id as u32), "Retrieved Calendar") - }); + engine.register_fn( + "get_calendar_by_id", + |_db: Arc, id: i64| -> Calendar { + // In a real implementation, this would retrieve the calendar from the database + Calendar::new(Some(id as u32), "Retrieved Calendar") + }, + ); // Register a function to check if a calendar exists engine.register_fn("calendar_exists", |_db: Arc, id: i64| -> bool { @@ -63,11 +68,17 @@ fn main() -> Result<(), Box> { // Define the function separately to use with the wrap_vec_return macro fn get_all_calendars(_db: Arc) -> Vec { // In a real implementation, this would retrieve all calendars from the database - vec![Calendar::new(Some(1), "Calendar 1"), Calendar::new(Some(2), "Calendar 2")] + vec![ + Calendar::new(Some(1), "Calendar 1"), + Calendar::new(Some(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 => Calendar)); + engine.register_fn( + "get_all_calendars", + wrap_vec_return!(get_all_calendars, Arc => Calendar), + ); engine.register_fn("delete_calendar_by_id", |_db: Arc, _id: i64| { // In a real implementation, this would delete the calendar from the database @@ -84,4 +95,4 @@ fn main() -> Result<(), Box> { } Ok(()) -} \ No newline at end of file +} diff --git a/heromodels/examples/custom_model_example.rs b/heromodels/examples/custom_model_example.rs index 7a3cc71..46bb02e 100644 --- a/heromodels/examples/custom_model_example.rs +++ b/heromodels/examples/custom_model_example.rs @@ -41,11 +41,16 @@ fn main() { println!("Before saving - CustomUser DB Keys: {:?}", user.db_keys()); // Save the model to the database - let collection = db.collection::().expect("can open user collection"); + let collection = db + .collection::() + .expect("can open user collection"); let (user_id, saved_user) = collection.set(&user).expect("can save user"); println!("\nAfter saving - CustomUser ID: {}", saved_user.get_id()); - println!("After saving - CustomUser DB Keys: {:?}", saved_user.db_keys()); + println!( + "After saving - CustomUser DB Keys: {:?}", + saved_user.db_keys() + ); println!("Returned ID: {}", user_id); // Verify that the ID was auto-generated @@ -53,5 +58,8 @@ fn main() { assert_ne!(saved_user.get_id(), 0); println!("\nExample finished. DB stored at {}", db_path); - println!("To clean up, you can manually delete the directory: {}", db_path); + println!( + "To clean up, you can manually delete the directory: {}", + db_path + ); } diff --git a/heromodels/examples/finance_example/main.rs b/heromodels/examples/finance_example/main.rs index acae3cc..1854a2e 100644 --- a/heromodels/examples/finance_example/main.rs +++ b/heromodels/examples/finance_example/main.rs @@ -1,8 +1,10 @@ // heromodels/examples/finance_example/main.rs -use chrono::{Utc, Duration}; +use chrono::{Duration, Utc}; +use heromodels::models::finance::marketplace::{ + Bid, BidStatus, Listing, ListingStatus, ListingType, +}; use heromodels::models::finance::{Account, Asset, AssetType}; -use heromodels::models::finance::marketplace::{Listing, ListingType, ListingStatus, Bid, BidStatus}; fn main() { println!("Finance Models Example\n"); @@ -12,16 +14,19 @@ fn main() { // Create a new account with auto-generated ID let mut account = Account::new( - None, // id (auto-generated) - "Main ETH Wallet", // name - 1001, // user_id - "My primary Ethereum wallet", // description - "ethereum", // ledger + None, // id (auto-generated) + "Main ETH Wallet", // name + 1001, // user_id + "My primary Ethereum wallet", // description + "ethereum", // ledger "0x1234567890abcdef1234567890abcdef12345678", // address - "0xpubkey123456789" // pubkey + "0xpubkey123456789", // pubkey ); - println!("Created Account: '{}' (ID: {})", account.name, account.base_data.id); + println!( + "Created Account: '{}' (ID: {})", + account.name, account.base_data.id + ); println!("Owner: User {}", account.user_id); println!("Blockchain: {}", account.ledger); println!("Address: {}", account.address); @@ -30,34 +35,34 @@ fn main() { // Create some assets // Asset with auto-generated ID let eth_asset = Asset::new( - None, // id (auto-generated) - "Ethereum", // name - "Native ETH cryptocurrency", // description - 1.5, // amount + None, // id (auto-generated) + "Ethereum", // name + "Native ETH cryptocurrency", // description + 1.5, // amount "0x0000000000000000000000000000000000000000", // address (ETH has no contract address) - AssetType::Native, // asset_type - 18, // decimals + AssetType::Native, // asset_type + 18, // decimals ); // Assets with explicit IDs let usdc_asset = Asset::new( - Some(102), // id - "USDC", // name - "USD Stablecoin on Ethereum", // description - 1000.0, // amount + Some(102), // id + "USDC", // name + "USD Stablecoin on Ethereum", // description + 1000.0, // amount "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // address (USDC contract) - AssetType::Erc20, // asset_type - 6, // decimals + AssetType::Erc20, // asset_type + 6, // decimals ); let nft_asset = Asset::new( - Some(103), // id - "CryptoPunk #1234", // name - "Rare digital collectible", // description - 1.0, // amount + Some(103), // id + "CryptoPunk #1234", // name + "Rare digital collectible", // description + 1.0, // amount "0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb", // address (CryptoPunks contract) - AssetType::Erc721, // asset_type - 0, // decimals + AssetType::Erc721, // asset_type + 0, // decimals ); // Add assets to the account @@ -67,7 +72,12 @@ fn main() { println!("Added Assets to Account:"); for asset in &account.assets { - println!("- {} ({:?}): {} units", asset.name, asset.asset_type, asset.formatted_amount()); + println!( + "- {} ({:?}): {} units", + asset.name, + asset.asset_type, + asset.formatted_amount() + ); } println!("\nTotal Account Value (raw sum): {}", account.total_value()); @@ -75,10 +85,10 @@ fn main() { // Update account details account = account.update_details( - Some("Primary Ethereum Wallet"), // new name - None::, // keep same description - None::, // keep same address - Some("0xnewpubkey987654321"), // new pubkey + Some("Primary Ethereum Wallet"), // new name + None::, // keep same description + None::, // keep same address + Some("0xnewpubkey987654321"), // new pubkey ); println!("Updated Account Details:"); @@ -99,23 +109,32 @@ fn main() { // Create a fixed price listing with auto-generated ID let mut fixed_price_listing = Listing::new( - None, // id (auto-generated) - "1000 USDC for Sale", // title - "Selling 1000 USDC tokens at fixed price", // description - "102", // asset_id (referencing the USDC asset) - AssetType::Erc20, // asset_type - "1001", // seller_id - 1.05, // price (in ETH) - "ETH", // currency - ListingType::FixedPrice, // listing_type + None, // id (auto-generated) + "1000 USDC for Sale", // title + "Selling 1000 USDC tokens at fixed price", // description + "102", // asset_id (referencing the USDC asset) + AssetType::Erc20, // asset_type + "1001", // seller_id + 1.05, // price (in ETH) + "ETH", // currency + ListingType::FixedPrice, // listing_type Some(Utc::now() + Duration::days(7)), // expires_at (7 days from now) vec!["token".to_string(), "stablecoin".to_string()], // tags Some("https://example.com/usdc.png"), // image_url ); - println!("Created Fixed Price Listing: '{}' (ID: {})", fixed_price_listing.title, fixed_price_listing.base_data.id); - println!("Price: {} {}", fixed_price_listing.price, fixed_price_listing.currency); - println!("Type: {:?}, Status: {:?}", fixed_price_listing.listing_type, fixed_price_listing.status); + println!( + "Created Fixed Price Listing: '{}' (ID: {})", + fixed_price_listing.title, fixed_price_listing.base_data.id + ); + println!( + "Price: {} {}", + fixed_price_listing.price, fixed_price_listing.currency + ); + println!( + "Type: {:?}, Status: {:?}", + fixed_price_listing.listing_type, fixed_price_listing.status + ); println!("Expires: {}", fixed_price_listing.expires_at.unwrap()); println!(""); @@ -126,54 +145,71 @@ fn main() { println!("Fixed Price Sale Completed:"); println!("Status: {:?}", fixed_price_listing.status); println!("Buyer: {}", fixed_price_listing.buyer_id.unwrap()); - println!("Sale Price: {} {}", fixed_price_listing.sale_price.unwrap(), fixed_price_listing.currency); + println!( + "Sale Price: {} {}", + fixed_price_listing.sale_price.unwrap(), + fixed_price_listing.currency + ); println!("Sold At: {}", fixed_price_listing.sold_at.unwrap()); println!(""); - }, + } Err(e) => println!("Error completing sale: {}", e), } // Create an auction listing for the NFT with explicit ID let mut auction_listing = Listing::new( - Some(202), // id (explicit) - "CryptoPunk #1234 Auction", // title - "Rare CryptoPunk NFT for auction", // description - "103", // asset_id (referencing the NFT asset) - AssetType::Erc721, // asset_type - "1001", // seller_id - 10.0, // starting_price (in ETH) - "ETH", // currency - ListingType::Auction, // listing_type + Some(202), // id (explicit) + "CryptoPunk #1234 Auction", // title + "Rare CryptoPunk NFT for auction", // description + "103", // asset_id (referencing the NFT asset) + AssetType::Erc721, // asset_type + "1001", // seller_id + 10.0, // starting_price (in ETH) + "ETH", // currency + ListingType::Auction, // listing_type Some(Utc::now() + Duration::days(3)), // expires_at (3 days from now) - vec!["nft".to_string(), "collectible".to_string(), "cryptopunk".to_string()], // tags + vec![ + "nft".to_string(), + "collectible".to_string(), + "cryptopunk".to_string(), + ], // tags Some("https://example.com/cryptopunk1234.png"), // image_url ); - println!("Created Auction Listing: '{}' (ID: {})", auction_listing.title, auction_listing.base_data.id); - println!("Starting Price: {} {}", auction_listing.price, auction_listing.currency); - println!("Type: {:?}, Status: {:?}", auction_listing.listing_type, auction_listing.status); + println!( + "Created Auction Listing: '{}' (ID: {})", + auction_listing.title, auction_listing.base_data.id + ); + println!( + "Starting Price: {} {}", + auction_listing.price, auction_listing.currency + ); + println!( + "Type: {:?}, Status: {:?}", + auction_listing.listing_type, auction_listing.status + ); println!(""); // Create some bids let bid1 = Bid::new( auction_listing.base_data.id.to_string(), // listing_id - 2001, // bidder_id - 11.0, // amount - "ETH", // currency + 2001, // bidder_id + 11.0, // amount + "ETH", // currency ); let bid2 = Bid::new( auction_listing.base_data.id.to_string(), // listing_id - 2002, // bidder_id - 12.5, // amount - "ETH", // currency + 2002, // bidder_id + 12.5, // amount + "ETH", // currency ); let bid3 = Bid::new( auction_listing.base_data.id.to_string(), // listing_id - 2003, // bidder_id - 15.0, // amount - "ETH", // currency + 2003, // bidder_id + 15.0, // amount + "ETH", // currency ); // Add bids to the auction @@ -184,7 +220,7 @@ fn main() { Ok(updated_listing) => { auction_listing = updated_listing; println!("- Bid added: 11.0 ETH from User 2001"); - }, + } Err(e) => println!("Error adding bid: {}", e), } @@ -192,7 +228,7 @@ fn main() { Ok(updated_listing) => { auction_listing = updated_listing; println!("- Bid added: 12.5 ETH from User 2002"); - }, + } Err(e) => println!("Error adding bid: {}", e), } @@ -200,18 +236,21 @@ fn main() { Ok(updated_listing) => { auction_listing = updated_listing; println!("- Bid added: 15.0 ETH from User 2003"); - }, + } Err(e) => println!("Error adding bid: {}", e), } println!("\nCurrent Auction Status:"); - println!("Current Price: {} {}", auction_listing.price, auction_listing.currency); + println!( + "Current Price: {} {}", + auction_listing.price, auction_listing.currency + ); if let Some(highest_bid) = auction_listing.highest_bid() { - println!("Highest Bid: {} {} from User {}", - highest_bid.amount, - highest_bid.currency, - highest_bid.bidder_id); + println!( + "Highest Bid: {} {} from User {}", + highest_bid.amount, highest_bid.currency, highest_bid.bidder_id + ); } println!("Total Bids: {}", auction_listing.bids.len()); @@ -223,42 +262,57 @@ fn main() { auction_listing = updated_listing; println!("Auction Completed:"); println!("Status: {:?}", auction_listing.status); - println!("Winner: User {}", auction_listing.buyer_id.as_ref().unwrap()); - println!("Winning Bid: {} {}", auction_listing.sale_price.as_ref().unwrap(), auction_listing.currency); + println!( + "Winner: User {}", + auction_listing.buyer_id.as_ref().unwrap() + ); + println!( + "Winning Bid: {} {}", + auction_listing.sale_price.as_ref().unwrap(), + auction_listing.currency + ); println!(""); println!("Final Bid Statuses:"); for bid in &auction_listing.bids { - println!("- User {}: {} {} (Status: {:?})", - bid.bidder_id, - bid.amount, - bid.currency, - bid.status); + println!( + "- User {}: {} {} (Status: {:?})", + bid.bidder_id, bid.amount, bid.currency, bid.status + ); } println!(""); - }, + } Err(e) => println!("Error completing auction: {}", e), } // Create an exchange listing with auto-generated ID let exchange_listing = Listing::new( - None, // id (auto-generated) - "ETH for BTC Exchange", // title - "Looking to exchange ETH for BTC", // description - "101", // asset_id (referencing the ETH asset) - AssetType::Native, // asset_type - "1001", // seller_id - 1.0, // amount (1 ETH) - "BTC", // currency (what they want in exchange) - ListingType::Exchange, // listing_type - Some(Utc::now() + Duration::days(14)), // expires_at (14 days from now) + None, // id (auto-generated) + "ETH for BTC Exchange", // title + "Looking to exchange ETH for BTC", // description + "101", // asset_id (referencing the ETH asset) + AssetType::Native, // asset_type + "1001", // seller_id + 1.0, // amount (1 ETH) + "BTC", // currency (what they want in exchange) + ListingType::Exchange, // listing_type + Some(Utc::now() + Duration::days(14)), // expires_at (14 days from now) vec!["exchange".to_string(), "crypto".to_string()], // tags - None::, // image_url + None::, // image_url ); - println!("Created Exchange Listing: '{}' (ID: {})", exchange_listing.title, exchange_listing.base_data.id); - println!("Offering: Asset {} ({:?})", exchange_listing.asset_id, exchange_listing.asset_type); - println!("Wanted: {} {}", exchange_listing.price, exchange_listing.currency); + println!( + "Created Exchange Listing: '{}' (ID: {})", + exchange_listing.title, exchange_listing.base_data.id + ); + println!( + "Offering: Asset {} ({:?})", + exchange_listing.asset_id, exchange_listing.asset_type + ); + println!( + "Wanted: {} {}", + exchange_listing.price, exchange_listing.currency + ); println!(""); // --- PART 3: DEMONSTRATING EDGE CASES --- @@ -266,26 +320,26 @@ fn main() { // Create a new auction listing for edge case testing with explicit ID let test_auction = Listing::new( - Some(205), // id (explicit) - "Test Auction", // title - "For testing edge cases", // description - "101", // asset_id - AssetType::Native, // asset_type - "1001", // seller_id - 10.0, // starting_price - "ETH", // currency - ListingType::Auction, // listing_type + Some(205), // id (explicit) + "Test Auction", // title + "For testing edge cases", // description + "101", // asset_id + AssetType::Native, // asset_type + "1001", // seller_id + 10.0, // starting_price + "ETH", // currency + ListingType::Auction, // listing_type Some(Utc::now() + Duration::days(1)), // expires_at - vec![], // tags - None::, // image_url + vec![], // tags + None::, // image_url ); // Try to add a bid that's too low let low_bid = Bid::new( test_auction.base_data.id.to_string(), // listing_id - 2004, // bidder_id - 5.0, // amount (lower than starting price) - "ETH", // currency + 2004, // bidder_id + 5.0, // amount (lower than starting price) + "ETH", // currency ); println!("Attempting to add a bid that's too low (5.0 ETH):"); @@ -305,21 +359,24 @@ fn main() { // Create a listing that will expire with auto-generated ID let mut expiring_listing = Listing::new( - None, // id (auto-generated) - "About to Expire", // title + None, // id (auto-generated) + "About to Expire", // title "This listing will expire immediately", // description - "101", // asset_id - AssetType::Native, // asset_type - "1001", // seller_id - 0.1, // price - "ETH", // currency - ListingType::FixedPrice, // listing_type - Some(Utc::now() - Duration::hours(1)), // expires_at (1 hour ago) - vec![], // tags - None::, // image_url + "101", // asset_id + AssetType::Native, // asset_type + "1001", // seller_id + 0.1, // price + "ETH", // currency + ListingType::FixedPrice, // listing_type + Some(Utc::now() - Duration::hours(1)), // expires_at (1 hour ago) + vec![], // tags + None::, // image_url ); - println!("Created Expiring Listing: '{}' (ID: {})", expiring_listing.title, expiring_listing.base_data.id); + println!( + "Created Expiring Listing: '{}' (ID: {})", + expiring_listing.title, expiring_listing.base_data.id + ); println!("Initial Status: {:?}", expiring_listing.status); // Check expiration diff --git a/heromodels/examples/finance_rhai/example.rs b/heromodels/examples/finance_rhai/example.rs index 23ea125..10f8fde 100644 --- a/heromodels/examples/finance_rhai/example.rs +++ b/heromodels/examples/finance_rhai/example.rs @@ -1,12 +1,12 @@ -use rhai::{Engine, Scope, EvalAltResult}; -use std::sync::{Arc, Mutex}; +use rhai::{Engine, EvalAltResult, Scope}; use std::collections::HashMap; use std::fs; +use std::sync::{Arc, Mutex}; // 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::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 @@ -39,10 +39,10 @@ fn main() -> Result<(), Box> { // Register finance functions and types with the engine register_rhai_engine_functions( - &mut engine, + &mut engine, Arc::clone(&mock_db.accounts), Arc::clone(&mock_db.assets), - Arc::clone(&mock_db.listings) + Arc::clone(&mock_db.listings), ); println!("Rhai functions registered."); @@ -77,8 +77,13 @@ fn main() -> Result<(), Box> { 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()); + println!( + "Account ID: {}, Name: '{}', User ID: {}, Assets: {}", + id, + account.name, + account.user_id, + account.assets.len() + ); } // Print final state of Assets @@ -88,8 +93,10 @@ fn main() -> Result<(), Box> { 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); + println!( + "Asset ID: {}, Name: '{}', Amount: {}, Type: {:?}", + id, asset.name, asset.amount, asset.asset_type + ); } // Print final state of Listings @@ -100,8 +107,13 @@ fn main() -> Result<(), Box> { } 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() + "Listing ID: {}, Title: '{}', Type: {:?}, Status: {:?}, Price: {}, Bids: {}", + id, + listing.title, + listing.listing_type, + listing.status, + listing.price, + listing.bids.len() ); } diff --git a/heromodels/examples/flow_example.rs b/heromodels/examples/flow_example.rs index 3a489e8..b4bdc02 100644 --- a/heromodels/examples/flow_example.rs +++ b/heromodels/examples/flow_example.rs @@ -9,8 +9,8 @@ use heromodels_core::Model; 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"); + let db = + heromodels::db::hero::OurDB::new("/tmp/ourdb_flowbroker", true).expect("Can create DB"); println!("Hero Models - Flow Example"); println!("==========================="); @@ -20,56 +20,71 @@ fn main() { 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 + 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"); + 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 + 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"); + 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 + 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"); + 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 + 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 + "Pending", // status ); - db.collection().expect("can open sig_req collection").set(&sig_req1_step2).expect("can set sig_req1_step2"); + 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 + 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 + "Pending", // status ); - db.collection().expect("can open sig_req collection").set(&sig_req2_step2).expect("can set sig_req2_step2"); + 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 --- @@ -101,9 +116,18 @@ fn main() { .get::(&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()); + 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); + println!( + " - Step ID: {}, Order: {}, Desc: {:?}", + step.get_id(), + step.step_order, + step.description + ); } // --- Update a SignatureRequirement (simulate signing) --- @@ -114,12 +138,18 @@ fn main() { .expect("can load sig_req1") .unwrap(); - println!("\nUpdating SignatureRequirement ID: {}", retrieved_sig_req1.get_id()); + 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"); + db.collection() + .expect("can open sig_req collection") + .set(&retrieved_sig_req1) + .expect("can update sig_req1"); let updated_sig_req1 = db .collection::() @@ -129,10 +159,13 @@ fn main() { .unwrap(); assert_eq!(updated_sig_req1.status, "Signed"); - assert_eq!(updated_sig_req1.signature.as_deref(), Some("mock_signature_base64_encoded")); + assert_eq!( + updated_sig_req1.signature.as_deref(), + Some("mock_signature_base64_encoded") + ); println!("Updated SignatureRequirement: {:?}", updated_sig_req1); - // --- Delete a FlowStep --- + // --- 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::() @@ -157,7 +190,11 @@ fn main() { .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!( + "Remaining FlowSteps for Flow ID {}: count = {}", + retrieved_flow.get_id(), + remaining_steps_for_flow1.len() + ); println!("\nFlow example finished successfully!"); } diff --git a/heromodels/examples/flow_rhai/example.rs b/heromodels/examples/flow_rhai/example.rs index a6cdd67..6c106d4 100644 --- a/heromodels/examples/flow_rhai/example.rs +++ b/heromodels/examples/flow_rhai/example.rs @@ -20,13 +20,18 @@ fn main() -> Result<(), Box> { 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)))); + 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), diff --git a/heromodels/examples/governance_proposal_example/main.rs b/heromodels/examples/governance_proposal_example/main.rs index bfbe78a..7906923 100644 --- a/heromodels/examples/governance_proposal_example/main.rs +++ b/heromodels/examples/governance_proposal_example/main.rs @@ -101,23 +101,23 @@ fn main() { // Example of voting with comments using the cast_vote_with_comment method println!("Adding votes with comments..."); - + // User 7 votes for 'Approve Allocation' with a comment proposal = proposal.cast_vote_with_comment( Some(110), // ballot_id - 7, // user_id - 1, // chosen_option_id (Approve Allocation) - 80, // shares - "I strongly support this proposal because it aligns with our community values." + 7, // user_id + 1, // chosen_option_id (Approve Allocation) + 80, // shares + "I strongly support this proposal because it aligns with our community values.", ); - + // User 8 votes for 'Reject Allocation' with a comment proposal = proposal.cast_vote_with_comment( Some(111), // ballot_id - 8, // user_id - 2, // chosen_option_id (Reject Allocation) - 60, // shares - "I have concerns about the allocation priorities." + 8, // user_id + 2, // chosen_option_id (Reject Allocation) + 60, // shares + "I have concerns about the allocation priorities.", ); println!("\nBallots with Comments:"); @@ -218,34 +218,34 @@ fn main() { // Example of voting with comments on a private proposal println!("\nAdding votes with comments to private proposal..."); - + // User 20 (eligible) votes with a comment private_proposal = private_proposal.cast_vote_with_comment( - Some(202), // ballot_id - 20, // user_id (eligible) - 1, // chosen_option_id - 75, // shares - "I support this restructuring plan with some reservations." + Some(202), // ballot_id + 20, // user_id (eligible) + 1, // chosen_option_id + 75, // shares + "I support this restructuring plan with some reservations.", ); - + // User 30 (eligible) votes with a comment private_proposal = private_proposal.cast_vote_with_comment( - Some(203), // ballot_id - 30, // user_id (eligible) - 2, // chosen_option_id - 90, // shares - "I believe we should reconsider the timing of these changes." + Some(203), // ballot_id + 30, // user_id (eligible) + 2, // chosen_option_id + 90, // shares + "I believe we should reconsider the timing of these changes.", ); - + // User 40 (ineligible) tries to vote with a comment private_proposal = private_proposal.cast_vote_with_comment( - Some(204), // ballot_id - 40, // user_id (ineligible) - 1, // chosen_option_id - 50, // shares - "This restructuring seems unnecessary." + Some(204), // ballot_id + 40, // user_id (ineligible) + 1, // chosen_option_id + 50, // shares + "This restructuring seems unnecessary.", ); - + println!("Eligible users 20 and 30 added votes with comments."); println!("Ineligible user 40 attempted to vote with a comment (should be rejected)."); diff --git a/heromodels/examples/governance_rhai_client/example.rs b/heromodels/examples/governance_rhai_client/example.rs index 6dcc648..d68e3ab 100644 --- a/heromodels/examples/governance_rhai_client/example.rs +++ b/heromodels/examples/governance_rhai_client/example.rs @@ -1,10 +1,12 @@ +use chrono::{Duration, Utc}; use heromodels::db::hero::OurDB; -use heromodels::models::governance::{Proposal, ProposalStatus, VoteEventStatus, VoteOption, Ballot}; +use heromodels::models::governance::{ + Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption, +}; use rhai::Engine; +use rhai_client_macros::rhai; 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 @@ -13,7 +15,14 @@ use rhai_client_macros::rhai; 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) + Proposal::new( + id as u32, + creator_id, + title, + description, + start_date, + end_date, + ) } // Getter functions for Proposal properties @@ -46,7 +55,13 @@ fn add_option_to_proposal(proposal: Proposal, option_id: i64, option_text: Strin 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 { +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) } @@ -119,14 +134,24 @@ fn get_ballot_shares(ballot: &Ballot) -> i64 { // 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 { +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 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) } @@ -141,8 +166,22 @@ fn get_all_proposals(_db: Arc) -> Vec { 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) + 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, + ), ] } @@ -161,31 +200,49 @@ fn main() -> Result<(), Box> { // 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_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) - }); - + + 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); @@ -193,34 +250,47 @@ fn main() -> Result<(), Box> { 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, 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) - }); - + + engine.register_fn( + "get_proposal_by_id", + |_db: Arc, 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, 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 => Proposal)); - + engine.register_fn( + "get_all_proposals", + wrap_vec_return!(get_all_proposals, Arc => 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); @@ -231,96 +301,108 @@ fn main() -> Result<(), Box> { 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)); - + 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()); - + 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)); + 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); + 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); + 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); + 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); - + 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!( + "- 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)); + 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)); - + 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)); - + 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)); @@ -330,37 +412,46 @@ fn main() -> Result<(), Box> { 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!( + " - {}: {} (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)); + 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()); + 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()); + + 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(()) } diff --git a/heromodels/examples/legal_contract_example.rs b/heromodels/examples/legal_contract_example.rs index cc0fe44..43402b9 100644 --- a/heromodels/examples/legal_contract_example.rs +++ b/heromodels/examples/legal_contract_example.rs @@ -70,7 +70,7 @@ fn main() { .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. @@ -87,7 +87,7 @@ fn main() { println!("\n--- Contract Details After Signing ---"); println!("{:#?}", contract); - + println!("\n--- Accessing Specific Fields ---"); println!("Contract Title: {}", contract.title); println!("Contract Status: {:?}", contract.status); @@ -97,7 +97,10 @@ fn main() { 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!( + "\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); @@ -110,6 +113,6 @@ fn main() { println!(" Created By: {}", latest_rev.created_by); println!(" Revision Created At: {}", latest_rev.created_at); } - + println!("\nLegal Contract Model demonstration complete."); } diff --git a/heromodels/examples/legal_rhai/example.rs b/heromodels/examples/legal_rhai/example.rs index b5a3e53..0b42f0d 100644 --- a/heromodels/examples/legal_rhai/example.rs +++ b/heromodels/examples/legal_rhai/example.rs @@ -22,13 +22,18 @@ fn main() -> Result<(), Box> { 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)))); + 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) => { diff --git a/heromodels/examples/library_rhai/example.rs b/heromodels/examples/library_rhai/example.rs index 08b41e0..13955f6 100644 --- a/heromodels/examples/library_rhai/example.rs +++ b/heromodels/examples/library_rhai/example.rs @@ -33,6 +33,5 @@ fn main() -> Result<(), Box> { fs::remove_dir_all(db_path)?; println!("--- Cleaned up temporary database. ---"); - Ok(()) } diff --git a/heromodels/examples/model_macro_example.rs b/heromodels/examples/model_macro_example.rs index 3e954ee..d43487a 100644 --- a/heromodels/examples/model_macro_example.rs +++ b/heromodels/examples/model_macro_example.rs @@ -59,21 +59,39 @@ fn main() { println!("Before saving - SimpleUser DB Keys: {:?}", user.db_keys()); println!("\nBefore saving - CustomUser ID: {}", custom_user.get_id()); - println!("Before saving - CustomUser DB Keys: {:?}", custom_user.db_keys()); + println!( + "Before saving - CustomUser DB Keys: {:?}", + custom_user.db_keys() + ); // Save the models to the database - let simple_collection = db.collection::().expect("can open simple user collection"); - let custom_collection = db.collection::().expect("can open custom user collection"); + let simple_collection = db + .collection::() + .expect("can open simple user collection"); + let custom_collection = db + .collection::() + .expect("can open custom user collection"); let (user_id, saved_user) = simple_collection.set(&user).expect("can save simple user"); - let (custom_user_id, saved_custom_user) = custom_collection.set(&custom_user).expect("can save custom user"); + let (custom_user_id, saved_custom_user) = custom_collection + .set(&custom_user) + .expect("can save custom user"); println!("\nAfter saving - SimpleUser ID: {}", saved_user.get_id()); - println!("After saving - SimpleUser DB Keys: {:?}", saved_user.db_keys()); + println!( + "After saving - SimpleUser DB Keys: {:?}", + saved_user.db_keys() + ); println!("Returned SimpleUser ID: {}", user_id); - println!("\nAfter saving - CustomUser ID: {}", saved_custom_user.get_id()); - println!("After saving - CustomUser DB Keys: {:?}", saved_custom_user.db_keys()); + println!( + "\nAfter saving - CustomUser ID: {}", + saved_custom_user.get_id() + ); + println!( + "After saving - CustomUser DB Keys: {:?}", + saved_custom_user.db_keys() + ); println!("Returned CustomUser ID: {}", custom_user_id); // Verify that the IDs were auto-generated @@ -83,5 +101,8 @@ fn main() { assert_ne!(saved_custom_user.get_id(), 0); println!("\nExample finished. DB stored at {}", db_path); - println!("To clean up, you can manually delete the directory: {}", db_path); + println!( + "To clean up, you can manually delete the directory: {}", + db_path + ); } diff --git a/heromodels/examples/project_rhai/example.rs b/heromodels/examples/project_rhai/example.rs index 08ac43b..d70bf0e 100644 --- a/heromodels/examples/project_rhai/example.rs +++ b/heromodels/examples/project_rhai/example.rs @@ -1,8 +1,8 @@ -use rhai::{Engine, EvalAltResult, Scope}; -use std::sync::Arc; use heromodels::db::hero::OurDB; use heromodels::models::projects::register_projects_rhai_module; +use rhai::{Engine, EvalAltResult, Scope}; use std::fs; +use std::sync::Arc; fn main() -> Result<(), Box> { println!("Executing Rhai script: examples/project_rhai/project_test.rhai"); @@ -18,8 +18,12 @@ fn main() -> Result<(), Box> { // 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())))?; + 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(); diff --git a/heromodels/examples/simple_model_example.rs b/heromodels/examples/simple_model_example.rs index fa53b4b..9981899 100644 --- a/heromodels/examples/simple_model_example.rs +++ b/heromodels/examples/simple_model_example.rs @@ -34,11 +34,16 @@ fn main() { println!("Before saving - SimpleUser DB Keys: {:?}", user.db_keys()); // Save the user to the database - let collection = db.collection::().expect("can open user collection"); + let collection = db + .collection::() + .expect("can open user collection"); let (user_id, saved_user) = collection.set(&user).expect("can save user"); println!("\nAfter saving - SimpleUser ID: {}", saved_user.get_id()); - println!("After saving - SimpleUser DB Keys: {:?}", saved_user.db_keys()); + println!( + "After saving - SimpleUser DB Keys: {:?}", + saved_user.db_keys() + ); println!("Returned ID: {}", user_id); // Verify that the ID was auto-generated @@ -46,6 +51,8 @@ fn main() { assert_ne!(saved_user.get_id(), 0); println!("\nExample finished. DB stored at {}", db_path); - println!("To clean up, you can manually delete the directory: {}", db_path); + println!( + "To clean up, you can manually delete the directory: {}", + db_path + ); } - diff --git a/heromodels/src/db.rs b/heromodels/src/db.rs index e5736fd..5691e11 100644 --- a/heromodels/src/db.rs +++ b/heromodels/src/db.rs @@ -57,7 +57,9 @@ where fn get_all(&self) -> Result, Error>; /// Begin a transaction for this collection - fn begin_transaction(&self) -> Result>, Error>; + fn begin_transaction( + &self, + ) -> Result>, Error>; } /// Errors returned by the DB implementation diff --git a/heromodels/src/db/hero.rs b/heromodels/src/db/hero.rs index dff28b1..61beb2f 100644 --- a/heromodels/src/db/hero.rs +++ b/heromodels/src/db/hero.rs @@ -436,8 +436,12 @@ where Ok(list_of_raw_ids_set_bytes) => { for raw_ids_set_bytes in list_of_raw_ids_set_bytes { // Each item in the list is a bincode-serialized HashSet of object IDs. - match bincode::serde::decode_from_slice::, _>(&raw_ids_set_bytes, BINCODE_CONFIG) { - Ok((ids_set, _)) => { // Destructure the tuple (HashSet, usize) + match bincode::serde::decode_from_slice::, _>( + &raw_ids_set_bytes, + BINCODE_CONFIG, + ) { + Ok((ids_set, _)) => { + // Destructure the tuple (HashSet, usize) all_object_ids.extend(ids_set); } Err(e) => { diff --git a/heromodels/src/models/access/access.rs b/heromodels/src/models/access/access.rs index 560301e..bdc4907 100644 --- a/heromodels/src/models/access/access.rs +++ b/heromodels/src/models/access/access.rs @@ -55,4 +55,4 @@ impl Access { self.expires_at = expires_at; self } -} \ No newline at end of file +} diff --git a/heromodels/src/models/access/mod.rs b/heromodels/src/models/access/mod.rs index a891652..4ecbdc2 100644 --- a/heromodels/src/models/access/mod.rs +++ b/heromodels/src/models/access/mod.rs @@ -3,5 +3,5 @@ pub mod access; pub mod rhai; // Re-export contact, Group from the inner contact module (contact.rs) within src/models/contact/mod.rs -pub use self::access::{Access}; +pub use self::access::Access; pub use rhai::register_access_rhai_module; diff --git a/heromodels/src/models/access/rhai.rs b/heromodels/src/models/access/rhai.rs index 0ae4d38..307b417 100644 --- a/heromodels/src/models/access/rhai.rs +++ b/heromodels/src/models/access/rhai.rs @@ -1,22 +1,22 @@ -use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; -use std::sync::Arc; -use std::mem; use crate::db::Db; +use rhai::plugin::*; +use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; +use std::mem; +use std::sync::Arc; -use super::access::{Access}; +use super::access::Access; type RhaiAccess = Access; -use crate::db::hero::OurDB; use crate::db::Collection; +use crate::db::hero::OurDB; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE + Position::NONE, )) - ) + }) } #[export_module] @@ -27,13 +27,16 @@ mod rhai_access_module { let access = Access::new(); Ok(access) } - + /// Sets the access name #[rhai_fn(name = "object_id", return_raw, global, pure)] - pub fn access_object_id(access: &mut RhaiAccess, object_id: u32) -> Result> { + pub fn access_object_id( + access: &mut RhaiAccess, + object_id: u32, + ) -> Result> { // Create a default Access to replace the taken one let default_access = Access::new(); - + // Take ownership of the access, apply the builder method, then put it back let owned_access = std::mem::replace(access, default_access); *access = owned_access.object_id(object_id); @@ -41,21 +44,27 @@ mod rhai_access_module { } #[rhai_fn(name = "circle_id", return_raw, global, pure)] - pub fn access_circle_id(access: &mut RhaiAccess, circle_id: u32) -> Result> { + pub fn access_circle_id( + access: &mut RhaiAccess, + circle_id: u32, + ) -> Result> { // Create a default Access to replace the taken one let default_access = Access::new(); - + // Take ownership of the access, apply the builder method, then put it back let owned_access = std::mem::replace(access, default_access); *access = owned_access.circle_id(circle_id); Ok(access.clone()) } - + #[rhai_fn(name = "group_id", return_raw, global, pure)] - pub fn access_group_id(access: &mut RhaiAccess, group_id: u32) -> Result> { + pub fn access_group_id( + access: &mut RhaiAccess, + group_id: u32, + ) -> Result> { // Create a default Access to replace the taken one let default_access = Access::new(); - + // Take ownership of the access, apply the builder method, then put it back let owned_access = std::mem::replace(access, default_access); *access = owned_access.group_id(group_id); @@ -63,10 +72,13 @@ mod rhai_access_module { } #[rhai_fn(name = "contact_id", return_raw, global, pure)] - pub fn access_contact_id(access: &mut RhaiAccess, contact_id: u32) -> Result> { + pub fn access_contact_id( + access: &mut RhaiAccess, + contact_id: u32, + ) -> Result> { // Create a default Access to replace the taken one let default_access = Access::new(); - + // Take ownership of the access, apply the builder method, then put it back let owned_access = std::mem::replace(access, default_access); *access = owned_access.contact_id(contact_id); @@ -74,10 +86,13 @@ mod rhai_access_module { } #[rhai_fn(name = "expires_at", return_raw, global, pure)] - pub fn access_expires_at(access: &mut RhaiAccess, expires_at: Option) -> Result> { + pub fn access_expires_at( + access: &mut RhaiAccess, + expires_at: Option, + ) -> Result> { // Create a default Access to replace the taken one let default_access = Access::new(); - + // Take ownership of the access, apply the builder method, then put it back let owned_access = std::mem::replace(access, default_access); *access = owned_access.expires_at(expires_at); @@ -86,90 +101,136 @@ mod rhai_access_module { // Access Getters #[rhai_fn(get = "id", pure)] - pub fn get_access_id(access: &mut RhaiAccess) -> i64 { access.base_data.id as i64 } - + pub fn get_access_id(access: &mut RhaiAccess) -> i64 { + access.base_data.id as i64 + } + #[rhai_fn(get = "object_id", pure)] - pub fn get_access_object_id(access: &mut RhaiAccess) -> i64 { access.object_id as i64 } + pub fn get_access_object_id(access: &mut RhaiAccess) -> i64 { + access.object_id as i64 + } #[rhai_fn(get = "circle_id", pure)] - pub fn get_access_circle_id(access: &mut RhaiAccess) -> i64 { access.circle_id as i64 } + pub fn get_access_circle_id(access: &mut RhaiAccess) -> i64 { + access.circle_id as i64 + } #[rhai_fn(get = "group_id", pure)] - pub fn get_access_group_id(access: &mut RhaiAccess) -> i64 { access.group_id as i64 } + pub fn get_access_group_id(access: &mut RhaiAccess) -> i64 { + access.group_id as i64 + } #[rhai_fn(get = "contact_id", pure)] - pub fn get_access_contact_id(access: &mut RhaiAccess) -> i64 { access.contact_id as i64 } + pub fn get_access_contact_id(access: &mut RhaiAccess) -> i64 { + access.contact_id as i64 + } #[rhai_fn(get = "expires_at", pure)] - pub fn get_access_expires_at(access: &mut RhaiAccess) -> i64 { access.expires_at.unwrap_or(0) as i64 } + pub fn get_access_expires_at(access: &mut RhaiAccess) -> i64 { + access.expires_at.unwrap_or(0) as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_access_created_at(access: &mut RhaiAccess) -> i64 { access.base_data.created_at } - + pub fn get_access_created_at(access: &mut RhaiAccess) -> i64 { + access.base_data.created_at + } + #[rhai_fn(get = "modified_at", pure)] - pub fn get_access_modified_at(access: &mut RhaiAccess) -> i64 { access.base_data.modified_at } + pub fn get_access_modified_at(access: &mut RhaiAccess) -> i64 { + access.base_data.modified_at + } } pub fn register_access_rhai_module(engine: &mut Engine, db: Arc) { // Register the exported module globally let module = exported_module!(rhai_access_module); engine.register_global_module(module.into()); - + // Create a module for database functions let mut db_module = Module::new(); - + let db_clone_set_access = db.clone(); - db_module.set_native_fn("save_access", move |access: Access| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_access.set(&access) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_access: {}", e).into(), Position::NONE)))?; - - // Return the updated access with the correct ID - Ok(result.1) - }); + db_module.set_native_fn( + "save_access", + move |access: Access| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_access.set(&access).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error set_access: {}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated access with the correct ID + Ok(result.1) + }, + ); // Manually register database functions as they need to capture 'db' let db_clone_delete_access = db.clone(); - db_module.set_native_fn("delete_access", move |access: Access| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_access.collection::() - .expect("can open access collection") - .delete_by_id(access.base_data.id) - .expect("can delete event"); - - // Return the updated event with the correct ID - Ok(result) - }); - + db_module.set_native_fn( + "delete_access", + move |access: Access| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_access + .collection::() + .expect("can open access collection") + .delete_by_id(access.base_data.id) + .expect("can delete event"); + + // Return the updated event with the correct ID + Ok(result) + }, + ); + let db_clone_get_access = db.clone(); - db_module.set_native_fn("get_access_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_access.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_access_by_id: {}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Access with ID {} not found", id_u32).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_access_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_access + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_access_by_id: {}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Access with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); // Add list_accesss function to get all accesss let db_clone_list_accesss = db.clone(); - db_module.set_native_fn("list_accesss", move || -> Result> { - let collection = db_clone_list_accesss.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get access collection: {:?}", e).into(), - Position::NONE - )))?; - let accesss = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all accesss: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for access in accesss { - array.push(Dynamic::from(access)); - } - Ok(Dynamic::from(array)) - }); - + db_module.set_native_fn( + "list_accesss", + move || -> Result> { + let collection = db_clone_list_accesss.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get access collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let accesss = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all accesss: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for access in accesss { + array.push(Dynamic::from(access)); + } + Ok(Dynamic::from(array)) + }, + ); + // Register the database module globally engine.register_global_module(db_module.into()); diff --git a/heromodels/src/models/biz/company.rs b/heromodels/src/models/biz/company.rs index 7393f2e..d5fe452 100644 --- a/heromodels/src/models/biz/company.rs +++ b/heromodels/src/models/biz/company.rs @@ -1,8 +1,8 @@ -use serde::{Deserialize, Serialize}; -use heromodels_core::{BaseModelData, Index}; -use rhai::{CustomType, TypeBuilder}; // For #[derive(CustomType)] use heromodels_core::BaseModelDataOps; +use heromodels_core::{BaseModelData, Index}; use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; // For #[derive(CustomType)] +use serde::{Deserialize, Serialize}; // --- Enums --- @@ -100,17 +100,17 @@ impl Company { status: CompanyStatus::default(), } } - + pub fn name(mut self, name: impl ToString) -> Self { self.name = name.to_string(); self } - + pub fn registration_number(mut self, registration_number: impl ToString) -> Self { self.registration_number = registration_number.to_string(); self } - + pub fn incorporation_date(mut self, incorporation_date: i64) -> Self { self.incorporation_date = incorporation_date; self diff --git a/heromodels/src/models/biz/mod.rs b/heromodels/src/models/biz/mod.rs index 27a1718..abadf00 100644 --- a/heromodels/src/models/biz/mod.rs +++ b/heromodels/src/models/biz/mod.rs @@ -8,17 +8,16 @@ pub mod product; // pub mod user; // Re-export main types from sub-modules -pub use company::{Company, CompanyStatus, BusinessType}; +pub use company::{BusinessType, Company, CompanyStatus}; pub mod shareholder; +pub use product::{Product, ProductComponent, ProductStatus, ProductType}; pub use shareholder::{Shareholder, ShareholderType}; -pub use product::{Product, ProductType, ProductStatus, ProductComponent}; pub mod sale; pub use sale::{Sale, SaleItem, SaleStatus}; // pub use user::{User}; // Assuming a simple User model for now - #[cfg(feature = "rhai")] pub mod rhai; #[cfg(feature = "rhai")] diff --git a/heromodels/src/models/biz/product.rs b/heromodels/src/models/biz/product.rs index 0d9f3d0..c282918 100644 --- a/heromodels/src/models/biz/product.rs +++ b/heromodels/src/models/biz/product.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; use heromodels_core::BaseModelData; use heromodels_derive::model; +use serde::{Deserialize, Serialize}; // ProductType represents the type of a product #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] @@ -137,7 +137,7 @@ impl Product { self.components.push(component); self } - + pub fn components(mut self, components: Vec) -> Self { self.components = components; self diff --git a/heromodels/src/models/biz/rhai.rs b/heromodels/src/models/biz/rhai.rs index 6fe6081..22ef6d8 100644 --- a/heromodels/src/models/biz/rhai.rs +++ b/heromodels/src/models/biz/rhai.rs @@ -1,15 +1,15 @@ -use rhai::plugin::*; -use rhai::{Engine, Module, Dynamic, EvalAltResult, Position, INT}; -use std::sync::Arc; -use std::mem; use crate::db::Collection; // For db.set and db.get_by_id -use crate::db::hero::OurDB; use crate::db::Db; +use crate::db::hero::OurDB; +use rhai::plugin::*; +use rhai::{Dynamic, Engine, EvalAltResult, INT, Module, Position}; +use std::mem; +use std::sync::Arc; -use super::company::{Company, CompanyStatus, BusinessType}; -use crate::models::biz::shareholder::{Shareholder, ShareholderType}; -use crate::models::biz::product::{Product, ProductType, ProductStatus, ProductComponent}; +use super::company::{BusinessType, Company, CompanyStatus}; +use crate::models::biz::product::{Product, ProductComponent, ProductStatus, ProductType}; use crate::models::biz::sale::{Sale, SaleItem, SaleStatus}; +use crate::models::biz::shareholder::{Shareholder, ShareholderType}; use heromodels_core::Model; type RhaiCompany = Company; @@ -21,12 +21,12 @@ type RhaiSaleItem = SaleItem; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE + Position::NONE, )) - ) + }) } #[export_module] @@ -36,443 +36,541 @@ mod rhai_biz_module { pub fn new_company() -> RhaiCompany { Company::new() } - + // Company builder methods #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn company_name(company: &mut RhaiCompany, name: String) -> Result> { + pub fn company_name( + company: &mut RhaiCompany, + name: String, + ) -> Result> { let owned_company = mem::take(company); *company = owned_company.name(name); Ok(company.clone()) } - + #[rhai_fn(name = "fiscal_year_end", return_raw, global, pure)] - pub fn company_fiscal_year_end(company: &mut RhaiCompany, fiscal_year_end: String) -> Result> { + pub fn company_fiscal_year_end( + company: &mut RhaiCompany, + fiscal_year_end: String, + ) -> Result> { let owned_company = mem::take(company); *company = owned_company.fiscal_year_end(fiscal_year_end); Ok(company.clone()) } - + #[rhai_fn(name = "registration_number", return_raw, global, pure)] - pub fn company_registration_number(company: &mut RhaiCompany, reg_num: String) -> Result> { + pub fn company_registration_number( + company: &mut RhaiCompany, + reg_num: String, + ) -> Result> { let owned_company = mem::take(company); *company = owned_company.registration_number(reg_num); Ok(company.clone()) } - + #[rhai_fn(name = "incorporation_date", return_raw, global, pure)] - pub fn company_incorporation_date(company: &mut RhaiCompany, date: i64) -> Result> { + pub fn company_incorporation_date( + company: &mut RhaiCompany, + date: i64, + ) -> Result> { let owned_company = mem::take(company); *company = owned_company.incorporation_date(date); Ok(company.clone()) } - + #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn company_status(company: &mut RhaiCompany, status: CompanyStatus) -> Result> { + pub fn company_status( + company: &mut RhaiCompany, + status: CompanyStatus, + ) -> Result> { let owned_company = mem::take(company); *company = owned_company.status(status); Ok(company.clone()) } - + #[rhai_fn(name = "business_type", return_raw, global, pure)] - pub fn company_business_type(company: &mut RhaiCompany, business_type: BusinessType) -> Result> { + pub fn company_business_type( + company: &mut RhaiCompany, + business_type: BusinessType, + ) -> Result> { let owned_company = mem::take(company); *company = owned_company.business_type(business_type); Ok(company.clone()) } - + // Company getters #[rhai_fn(name = "get_company_id")] pub fn get_company_id(company: &mut RhaiCompany) -> i64 { company.get_id() as i64 } - + #[rhai_fn(name = "get_company_name")] pub fn get_company_name(company: &mut RhaiCompany) -> String { company.name.clone() } - + #[rhai_fn(name = "get_company_created_at")] pub fn get_company_created_at(company: &mut RhaiCompany) -> i64 { company.base_data.created_at } - + #[rhai_fn(name = "get_company_modified_at")] pub fn get_company_modified_at(company: &mut RhaiCompany) -> i64 { company.base_data.modified_at } - + #[rhai_fn(name = "get_company_registration_number")] pub fn get_company_registration_number(company: &mut RhaiCompany) -> String { company.registration_number.clone() } - + #[rhai_fn(name = "get_company_fiscal_year_end")] pub fn get_company_fiscal_year_end(company: &mut RhaiCompany) -> String { company.fiscal_year_end.clone() } - + #[rhai_fn(name = "get_company_incorporation_date")] pub fn get_company_incorporation_date(company: &mut RhaiCompany) -> i64 { company.incorporation_date } - + #[rhai_fn(name = "get_company_status")] pub fn get_company_status(company: &mut RhaiCompany) -> CompanyStatus { company.status.clone() } - + #[rhai_fn(name = "get_company_business_type")] pub fn get_company_business_type(company: &mut RhaiCompany) -> BusinessType { company.business_type.clone() } - + // --- Shareholder Functions --- #[rhai_fn(name = "new_shareholder")] pub fn new_shareholder() -> RhaiShareholder { Shareholder::new() } - + // Shareholder builder methods #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn shareholder_name(shareholder: &mut RhaiShareholder, name: String) -> Result> { + pub fn shareholder_name( + shareholder: &mut RhaiShareholder, + name: String, + ) -> Result> { let owned_shareholder = mem::take(shareholder); *shareholder = owned_shareholder.name(name); Ok(shareholder.clone()) } - + #[rhai_fn(name = "company_id", return_raw, global, pure)] - pub fn shareholder_company_id(shareholder: &mut RhaiShareholder, company_id: i64) -> Result> { + pub fn shareholder_company_id( + shareholder: &mut RhaiShareholder, + company_id: i64, + ) -> Result> { let company_id_u32 = id_from_i64_to_u32(company_id)?; let owned_shareholder = mem::take(shareholder); *shareholder = owned_shareholder.company_id(company_id_u32); Ok(shareholder.clone()) } - + #[rhai_fn(name = "share_count", return_raw, global, pure)] - pub fn shareholder_share_count(shareholder: &mut RhaiShareholder, share_count: f64) -> Result> { - let owned_shareholder = mem::take(shareholder); + pub fn shareholder_share_count( + shareholder: &mut RhaiShareholder, + share_count: f64, + ) -> Result> { shareholder.shares = share_count; Ok(shareholder.clone()) } - + #[rhai_fn(name = "type_", return_raw, global, pure)] - pub fn shareholder_type(shareholder: &mut RhaiShareholder, type_: ShareholderType) -> Result> { + pub fn shareholder_type( + shareholder: &mut RhaiShareholder, + type_: ShareholderType, + ) -> Result> { let owned_shareholder = mem::take(shareholder); *shareholder = owned_shareholder.type_(type_); Ok(shareholder.clone()) } - + // Shareholder getters #[rhai_fn(name = "get_shareholder_id")] pub fn get_shareholder_id(shareholder: &mut RhaiShareholder) -> i64 { shareholder.get_id() as i64 } - + #[rhai_fn(name = "get_shareholder_name")] pub fn get_shareholder_name(shareholder: &mut RhaiShareholder) -> String { shareholder.name.clone() } - + #[rhai_fn(name = "get_shareholder_company_id")] pub fn get_shareholder_company_id(shareholder: &mut RhaiShareholder) -> i64 { shareholder.company_id as i64 } - + #[rhai_fn(name = "get_shareholder_share_count")] pub fn get_shareholder_share_count(shareholder: &mut RhaiShareholder) -> i64 { shareholder.shares as i64 } - + #[rhai_fn(name = "get_shareholder_type")] pub fn get_shareholder_type(shareholder: &mut RhaiShareholder) -> ShareholderType { shareholder.type_.clone() } - + // --- ProductComponent Functions --- #[rhai_fn(name = "new_product_component")] pub fn new_product_component() -> RhaiProductComponent { ProductComponent::new() } - + // ProductComponent builder methods #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn product_component_name(component: &mut RhaiProductComponent, name: String) -> Result> { + pub fn product_component_name( + component: &mut RhaiProductComponent, + name: String, + ) -> Result> { let owned_component = mem::take(component); *component = owned_component.name(name); Ok(component.clone()) } - + #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn product_component_description(component: &mut RhaiProductComponent, description: String) -> Result> { + pub fn product_component_description( + component: &mut RhaiProductComponent, + description: String, + ) -> Result> { let owned_component = mem::take(component); *component = owned_component.description(description); Ok(component.clone()) } - + #[rhai_fn(name = "quantity", return_raw, global, pure)] - pub fn product_component_quantity(component: &mut RhaiProductComponent, quantity: i64) -> Result> { + pub fn product_component_quantity( + component: &mut RhaiProductComponent, + quantity: i64, + ) -> Result> { let owned_component = mem::take(component); *component = owned_component.quantity(quantity as u32); Ok(component.clone()) } - + // ProductComponent getters #[rhai_fn(name = "get_product_component_name")] pub fn get_product_component_name(component: &mut RhaiProductComponent) -> String { component.name.clone() } - + #[rhai_fn(name = "get_product_component_description")] pub fn get_product_component_description(component: &mut RhaiProductComponent) -> String { component.description.clone() } - + #[rhai_fn(name = "get_product_component_quantity")] pub fn get_product_component_quantity(component: &mut RhaiProductComponent) -> i64 { component.quantity as i64 } - + // --- Product Functions --- #[rhai_fn(name = "new_product")] pub fn new_product() -> RhaiProduct { Product::new() } - + // Product builder methods #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn product_name(product: &mut RhaiProduct, name: String) -> Result> { + pub fn product_name( + product: &mut RhaiProduct, + name: String, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.name(name); Ok(product.clone()) } - + #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn product_description(product: &mut RhaiProduct, description: String) -> Result> { + pub fn product_description( + product: &mut RhaiProduct, + description: String, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.description(description); Ok(product.clone()) } - + #[rhai_fn(name = "price", return_raw, global, pure)] - pub fn product_price(product: &mut RhaiProduct, price: f64) -> Result> { + pub fn product_price( + product: &mut RhaiProduct, + price: f64, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.price(price); Ok(product.clone()) } - + #[rhai_fn(name = "type_", return_raw, global, pure)] - pub fn product_type(product: &mut RhaiProduct, type_: ProductType) -> Result> { + pub fn product_type( + product: &mut RhaiProduct, + type_: ProductType, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.type_(type_); Ok(product.clone()) } - + #[rhai_fn(name = "category", return_raw, global, pure)] - pub fn product_category(product: &mut RhaiProduct, category: String) -> Result> { + pub fn product_category( + product: &mut RhaiProduct, + category: String, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.category(category); Ok(product.clone()) } - + #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn product_status(product: &mut RhaiProduct, status: ProductStatus) -> Result> { + pub fn product_status( + product: &mut RhaiProduct, + status: ProductStatus, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.status(status); Ok(product.clone()) } - + #[rhai_fn(name = "max_amount", return_raw, global, pure)] - pub fn product_max_amount(product: &mut RhaiProduct, max_amount: i64) -> Result> { + pub fn product_max_amount( + product: &mut RhaiProduct, + max_amount: i64, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.max_amount(max_amount as u16); Ok(product.clone()) } - + #[rhai_fn(name = "purchase_till", return_raw, global, pure)] - pub fn product_purchase_till(product: &mut RhaiProduct, purchase_till: i64) -> Result> { + pub fn product_purchase_till( + product: &mut RhaiProduct, + purchase_till: i64, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.purchase_till(purchase_till); Ok(product.clone()) } - + #[rhai_fn(name = "active_till", return_raw, global, pure)] - pub fn product_active_till(product: &mut RhaiProduct, active_till: i64) -> Result> { + pub fn product_active_till( + product: &mut RhaiProduct, + active_till: i64, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.active_till(active_till); Ok(product.clone()) } - + #[rhai_fn(name = "add_component", return_raw, global, pure)] - pub fn product_add_component(product: &mut RhaiProduct, component: RhaiProductComponent) -> Result> { + pub fn product_add_component( + product: &mut RhaiProduct, + component: RhaiProductComponent, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.add_component(component); Ok(product.clone()) } - + #[rhai_fn(name = "components", return_raw, global, pure)] - pub fn product_components(product: &mut RhaiProduct, components: Vec) -> Result> { + pub fn product_components( + product: &mut RhaiProduct, + components: Vec, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.components(components); Ok(product.clone()) } - + // Product getters #[rhai_fn(name = "get_product_id")] pub fn get_product_id(product: &mut RhaiProduct) -> i64 { product.get_id() as i64 } - + #[rhai_fn(name = "get_product_name")] pub fn get_product_name(product: &mut RhaiProduct) -> String { product.name.clone() } - + #[rhai_fn(name = "get_product_description")] pub fn get_product_description(product: &mut RhaiProduct) -> String { product.description.clone() } - + #[rhai_fn(name = "get_product_price")] pub fn get_product_price(product: &mut RhaiProduct) -> f64 { product.price } - + #[rhai_fn(name = "get_product_type")] pub fn get_product_type(product: &mut RhaiProduct) -> ProductType { product.type_.clone() } - + #[rhai_fn(name = "get_product_category")] pub fn get_product_category(product: &mut RhaiProduct) -> String { product.category.clone() } - + #[rhai_fn(name = "get_product_status")] pub fn get_product_status(product: &mut RhaiProduct) -> ProductStatus { product.status.clone() } - + #[rhai_fn(name = "get_product_max_amount")] pub fn get_product_max_amount(product: &mut RhaiProduct) -> i64 { product.max_amount as i64 } - + #[rhai_fn(name = "get_product_purchase_till")] pub fn get_product_purchase_till(product: &mut RhaiProduct) -> i64 { product.purchase_till } - + #[rhai_fn(name = "get_product_active_till")] pub fn get_product_active_till(product: &mut RhaiProduct) -> i64 { product.active_till } - + #[rhai_fn(name = "get_product_components")] pub fn get_product_components(product: &mut RhaiProduct) -> Vec { product.components.clone() } - + #[rhai_fn(name = "get_product_created_at")] pub fn get_product_created_at(product: &mut RhaiProduct) -> i64 { product.base_data.created_at } - + #[rhai_fn(name = "get_product_modified_at")] pub fn get_product_modified_at(product: &mut RhaiProduct) -> i64 { product.base_data.modified_at } - + #[rhai_fn(name = "get_product_comments")] pub fn get_product_comments(product: &mut RhaiProduct) -> Vec { - product.base_data.comments.iter().map(|&id| id as i64).collect() + product + .base_data + .comments + .iter() + .map(|&id| id as i64) + .collect() } - + // --- SaleItem Functions --- #[rhai_fn(name = "new_sale_item")] pub fn new_sale_item() -> RhaiSaleItem { SaleItem::new() } - + // SaleItem builder methods #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn sale_item_name(item: &mut RhaiSaleItem, name: String) -> Result> { + pub fn sale_item_name( + item: &mut RhaiSaleItem, + name: String, + ) -> Result> { let owned_item = mem::take(item); *item = owned_item.name(name); Ok(item.clone()) } - + #[rhai_fn(name = "price", return_raw, global, pure)] - pub fn sale_item_price(item: &mut RhaiSaleItem, price: f64) -> Result> { - let owned_item = mem::take(item); + pub fn sale_item_price( + item: &mut RhaiSaleItem, + price: f64, + ) -> Result> { item.unit_price = price; Ok(item.clone()) } - + #[rhai_fn(name = "quantity", return_raw, global, pure)] - pub fn sale_item_quantity(item: &mut RhaiSaleItem, quantity: i64) -> Result> { + pub fn sale_item_quantity( + item: &mut RhaiSaleItem, + quantity: i64, + ) -> Result> { let owned_item = mem::take(item); *item = owned_item.quantity(quantity.try_into().unwrap()); Ok(item.clone()) } - + #[rhai_fn(name = "product_id", return_raw, global, pure)] - pub fn sale_item_product_id(item: &mut RhaiSaleItem, product_id: i64) -> Result> { + pub fn sale_item_product_id( + item: &mut RhaiSaleItem, + product_id: i64, + ) -> Result> { let product_id_u32 = id_from_i64_to_u32(product_id)?; let owned_item = mem::take(item); *item = owned_item.product_id(product_id_u32); Ok(item.clone()) } - + // SaleItem getters #[rhai_fn(name = "get_sale_item_name")] pub fn get_sale_item_name(item: &mut RhaiSaleItem) -> String { item.name.clone() } - + #[rhai_fn(name = "get_sale_item_price")] pub fn get_sale_item_price(item: &mut RhaiSaleItem) -> f64 { item.unit_price } - + #[rhai_fn(name = "get_sale_item_quantity")] pub fn get_sale_item_quantity(item: &mut RhaiSaleItem) -> i64 { item.quantity as i64 } - + #[rhai_fn(name = "get_sale_item_product_id")] pub fn get_sale_item_product_id(item: &mut RhaiSaleItem) -> i64 { item.product_id as i64 } - + // --- Sale Functions --- #[rhai_fn(name = "new_sale")] pub fn new_sale() -> RhaiSale { Sale::new() } - + #[rhai_fn(name = "transaction_id", return_raw, global, pure)] - pub fn sale_transaction_id(sale: &mut RhaiSale, transaction_id: u32) -> Result> { - let owned_sale = mem::take(sale); + pub fn sale_transaction_id( + sale: &mut RhaiSale, + transaction_id: u32, + ) -> Result> { sale.transaction_id = transaction_id; Ok(sale.clone()) } - + #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn sale_status(sale: &mut RhaiSale, status: SaleStatus) -> Result> { + pub fn sale_status( + sale: &mut RhaiSale, + status: SaleStatus, + ) -> Result> { let owned_sale = mem::take(sale); *sale = owned_sale.status(status); Ok(sale.clone()) } - + #[rhai_fn(name = "add_item", return_raw, global, pure)] - pub fn sale_add_item(sale: &mut RhaiSale, item: RhaiSaleItem) -> Result> { + pub fn sale_add_item( + sale: &mut RhaiSale, + item: RhaiSaleItem, + ) -> Result> { let owned_sale = mem::take(sale); *sale = owned_sale.add_item(item); Ok(sale.clone()) } - + #[rhai_fn(name = "items", return_raw, global, pure)] - pub fn sale_items(sale: &mut RhaiSale, items: Vec) -> Result> { + pub fn sale_items( + sale: &mut RhaiSale, + items: Vec, + ) -> Result> { let owned_sale = mem::take(sale); *sale = owned_sale.items(items); Ok(sale.clone()) @@ -483,35 +581,39 @@ mod rhai_biz_module { pub fn get_sale_id(sale: &mut RhaiSale) -> i64 { sale.get_id() as i64 } - + #[rhai_fn(name = "get_sale_transaction_id")] pub fn get_sale_transaction_id(sale: &mut RhaiSale) -> u32 { sale.transaction_id } - + #[rhai_fn(name = "get_sale_status")] pub fn get_sale_status(sale: &mut RhaiSale) -> SaleStatus { sale.status.clone() } - + #[rhai_fn(name = "get_sale_items")] pub fn get_sale_items(sale: &mut RhaiSale) -> Vec { sale.items.clone() } - + #[rhai_fn(name = "get_sale_created_at")] pub fn get_sale_created_at(sale: &mut RhaiSale) -> i64 { sale.base_data.created_at } - + #[rhai_fn(name = "get_sale_modified_at")] pub fn get_sale_modified_at(sale: &mut RhaiSale) -> i64 { sale.base_data.modified_at } - + #[rhai_fn(name = "get_sale_comments")] pub fn get_sale_comments(sale: &mut RhaiSale) -> Vec { - sale.base_data.comments.iter().map(|&id| id as i64).collect() + sale.base_data + .comments + .iter() + .map(|&id| id as i64) + .collect() } } @@ -519,110 +621,174 @@ pub fn register_biz_rhai_module(engine: &mut Engine, db: Arc) { // Register the exported module globally let module = exported_module!(rhai_biz_module); engine.register_global_module(module.into()); - + // Create a new module for database operations let mut db_module = Module::new(); // Database operations will obtain fresh collection handles directly. - + // Add database functions for Company let db_for_set_company = Arc::clone(&db); - db_module.set_native_fn("set_company", move |company: Company| -> Result> { - let company_collection_set = db_for_set_company.collection::().expect("Failed to get company collection for set in closure"); - company_collection_set.set(&company) - .map(|(id_val, _)| id_val as INT) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save company: {:?}", e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "set_company", + move |company: Company| -> Result> { + let company_collection_set = db_for_set_company + .collection::() + .expect("Failed to get company collection for set in closure"); + company_collection_set + .set(&company) + .map(|(id_val, _)| id_val as INT) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save company: {:?}", e).into(), + Position::NONE, + )) + }) + }, + ); + let db_for_get_company = Arc::clone(&db); - db_module.set_native_fn("get_company_by_id", move |id: INT| -> Result> { - let company_collection_get = db_for_get_company.collection::().expect("Failed to get company collection for get in closure"); - let id_u32 = id_from_i64_to_u32(id)?; - company_collection_get.get_by_id(id_u32) - .map(Dynamic::from) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get company with id {}: {:?}", id, e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "get_company_by_id", + move |id: INT| -> Result> { + let company_collection_get = db_for_get_company + .collection::() + .expect("Failed to get company collection for get in closure"); + let id_u32 = id_from_i64_to_u32(id)?; + company_collection_get + .get_by_id(id_u32) + .map(Dynamic::from) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get company with id {}: {:?}", id, e).into(), + Position::NONE, + )) + }) + }, + ); + // Add database functions for Shareholder let db_for_set_shareholder = Arc::clone(&db); - db_module.set_native_fn("set_shareholder", move |shareholder: Shareholder| -> Result> { - let shareholder_collection_set = db_for_set_shareholder.collection::().expect("Failed to get shareholder collection for set in closure"); - shareholder_collection_set.set(&shareholder) - .map(|(id_val, _)| id_val as INT) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save shareholder: {:?}", e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "set_shareholder", + move |shareholder: Shareholder| -> Result> { + let shareholder_collection_set = db_for_set_shareholder + .collection::() + .expect("Failed to get shareholder collection for set in closure"); + shareholder_collection_set + .set(&shareholder) + .map(|(id_val, _)| id_val as INT) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save shareholder: {:?}", e).into(), + Position::NONE, + )) + }) + }, + ); + let db_for_get_shareholder = Arc::clone(&db); - db_module.set_native_fn("get_shareholder_by_id", move |id: INT| -> Result> { - let shareholder_collection_get = db_for_get_shareholder.collection::().expect("Failed to get shareholder collection for get in closure"); - let id_u32 = id_from_i64_to_u32(id)?; - shareholder_collection_get.get_by_id(id_u32) - .map(Dynamic::from) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get shareholder with id {}: {:?}", id, e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "get_shareholder_by_id", + move |id: INT| -> Result> { + let shareholder_collection_get = db_for_get_shareholder + .collection::() + .expect("Failed to get shareholder collection for get in closure"); + let id_u32 = id_from_i64_to_u32(id)?; + shareholder_collection_get + .get_by_id(id_u32) + .map(Dynamic::from) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get shareholder with id {}: {:?}", id, e).into(), + Position::NONE, + )) + }) + }, + ); + // Add database functions for Product let db_for_set_product = Arc::clone(&db); - db_module.set_native_fn("set_product", move |product: Product| -> Result> { - let product_collection_set = db_for_set_product.collection::().expect("Failed to get product collection for set in closure"); - product_collection_set.set(&product) - .map(|(id_val, _)| id_val as INT) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save product: {:?}", e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "set_product", + move |product: Product| -> Result> { + let product_collection_set = db_for_set_product + .collection::() + .expect("Failed to get product collection for set in closure"); + product_collection_set + .set(&product) + .map(|(id_val, _)| id_val as INT) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save product: {:?}", e).into(), + Position::NONE, + )) + }) + }, + ); + let db_for_get_product = Arc::clone(&db); - db_module.set_native_fn("get_product_by_id", move |id: INT| -> Result> { - let product_collection_get = db_for_get_product.collection::().expect("Failed to get product collection for get in closure"); - let id_u32 = id_from_i64_to_u32(id)?; - product_collection_get.get_by_id(id_u32) - .map(Dynamic::from) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get product with id {}: {:?}", id, e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "get_product_by_id", + move |id: INT| -> Result> { + let product_collection_get = db_for_get_product + .collection::() + .expect("Failed to get product collection for get in closure"); + let id_u32 = id_from_i64_to_u32(id)?; + product_collection_get + .get_by_id(id_u32) + .map(Dynamic::from) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get product with id {}: {:?}", id, e).into(), + Position::NONE, + )) + }) + }, + ); + // Add database functions for Sale let db_for_set_sale = Arc::clone(&db); - db_module.set_native_fn("set_sale", move |sale: Sale| -> Result> { - let sale_collection_set = db_for_set_sale.collection::().expect("Failed to get sale collection for set in closure"); - sale_collection_set.set(&sale) - .map(|(id_val, _)| id_val as INT) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save sale: {:?}", e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "set_sale", + move |sale: Sale| -> Result> { + let sale_collection_set = db_for_set_sale + .collection::() + .expect("Failed to get sale collection for set in closure"); + sale_collection_set + .set(&sale) + .map(|(id_val, _)| id_val as INT) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save sale: {:?}", e).into(), + Position::NONE, + )) + }) + }, + ); + let db_for_get_sale = Arc::clone(&db); - db_module.set_native_fn("get_sale_by_id", move |id: INT| -> Result> { - let sale_collection_get = db_for_get_sale.collection::().expect("Failed to get sale collection for get in closure"); - let id_u32 = id_from_i64_to_u32(id)?; - sale_collection_get.get_by_id(id_u32) - .map(Dynamic::from) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get sale with id {}: {:?}", id, e).into(), - Position::NONE - ))) - }); + db_module.set_native_fn( + "get_sale_by_id", + move |id: INT| -> Result> { + let sale_collection_get = db_for_get_sale + .collection::() + .expect("Failed to get sale collection for get in closure"); + let id_u32 = id_from_i64_to_u32(id)?; + sale_collection_get + .get_by_id(id_u32) + .map(Dynamic::from) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get sale with id {}: {:?}", id, e).into(), + Position::NONE, + )) + }) + }, + ); // Register the database module globally engine.register_global_module(db_module.into()); - + println!("Successfully registered biz Rhai module using export_module approach."); } diff --git a/heromodels/src/models/biz/sale.rs b/heromodels/src/models/biz/sale.rs index a699329..938f58d 100644 --- a/heromodels/src/models/biz/sale.rs +++ b/heromodels/src/models/biz/sale.rs @@ -1,5 +1,5 @@ +use heromodels_core::{BaseModelData, BaseModelDataOps, Model}; use serde::{Deserialize, Serialize}; -use heromodels_core::{BaseModelData, Model, BaseModelDataOps}; /// Represents the status of a sale. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/heromodels/src/models/biz/shareholder.rs b/heromodels/src/models/biz/shareholder.rs index e6af823..ce7b58a 100644 --- a/heromodels/src/models/biz/shareholder.rs +++ b/heromodels/src/models/biz/shareholder.rs @@ -1,6 +1,6 @@ -use serde::{Deserialize, Serialize}; use heromodels_core::BaseModelData; use heromodels_derive::model; +use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ShareholderType { @@ -31,13 +31,13 @@ impl Shareholder { pub fn new() -> Self { Self { 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 - shares: 0.0, // Default - percentage: 0.0, // Default + company_id: 0, // Default, to be set by builder + user_id: 0, // Default, to be set by builder + name: String::new(), // Default + shares: 0.0, // Default + percentage: 0.0, // Default type_: ShareholderType::default(), // Uses ShareholderType's Default impl - since: 0, // Default timestamp, to be set by builder + since: 0, // Default timestamp, to be set by builder } } @@ -78,4 +78,4 @@ impl Shareholder { } // Base data operations are now handled by BaseModelDataOps trait -} \ No newline at end of file +} diff --git a/heromodels/src/models/calendar/calendar.rs b/heromodels/src/models/calendar/calendar.rs index b9317a4..64edfa3 100644 --- a/heromodels/src/models/calendar/calendar.rs +++ b/heromodels/src/models/calendar/calendar.rs @@ -26,7 +26,7 @@ impl AttendanceStatus { _ => Err(format!("Invalid attendance status: '{}'", s)), } } - + /// Convert an AttendanceStatus to a string pub fn to_string(&self) -> String { match self { @@ -134,7 +134,11 @@ impl Event { /// Adds an attendee to the event pub fn add_attendee(mut self, attendee: Attendee) -> Self { // Prevent duplicate attendees by contact_id - if !self.attendees.iter().any(|a| a.contact_id == attendee.contact_id) { + if !self + .attendees + .iter() + .any(|a| a.contact_id == attendee.contact_id) + { self.attendees.push(attendee); } self @@ -148,18 +152,18 @@ impl Event { /// Updates the status of an existing attendee pub fn update_attendee_status(mut self, contact_id: u32, status: AttendanceStatus) -> Self { - if let Some(attendee) = self.attendees.iter_mut().find(|a| a.contact_id == contact_id) { + if let Some(attendee) = self + .attendees + .iter_mut() + .find(|a| a.contact_id == contact_id) + { attendee.status = status; } self } /// Reschedules the event to new start and end times - pub fn reschedule( - mut self, - new_start_time: i64, - new_end_time: i64, - ) -> Self { + pub fn reschedule(mut self, new_start_time: i64, new_end_time: i64) -> Self { // Basic validation: end_time should be after start_time if new_end_time > new_start_time { self.start_time = new_start_time; @@ -236,7 +240,8 @@ impl Calendar { /// Removes an event from the calendar by its ID pub fn remove_event(mut self, event_id_to_remove: i64) -> Self { - self.events.retain(|&event_id_in_vec| event_id_in_vec != event_id_to_remove); + self.events + .retain(|&event_id_in_vec| event_id_in_vec != event_id_to_remove); self } } diff --git a/heromodels/src/models/calendar/mod.rs b/heromodels/src/models/calendar/mod.rs index fb0216a..fc7beb4 100644 --- a/heromodels/src/models/calendar/mod.rs +++ b/heromodels/src/models/calendar/mod.rs @@ -3,5 +3,5 @@ pub mod calendar; pub mod rhai; // Re-export Calendar, Event, Attendee, and AttendanceStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs -pub use self::calendar::{Calendar, Event, Attendee, AttendanceStatus}; +pub use self::calendar::{AttendanceStatus, Attendee, Calendar, Event}; pub use rhai::register_calendar_rhai_module; diff --git a/heromodels/src/models/calendar/rhai.rs b/heromodels/src/models/calendar/rhai.rs index 6e754c3..a8e6af7 100644 --- a/heromodels/src/models/calendar/rhai.rs +++ b/heromodels/src/models/calendar/rhai.rs @@ -1,24 +1,24 @@ -use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; -use std::sync::Arc; -use std::mem; use crate::db::Db; +use rhai::plugin::*; +use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; +use std::mem; +use std::sync::Arc; -use super::calendar::{Event, Attendee, Calendar, AttendanceStatus}; +use super::calendar::{AttendanceStatus, Attendee, Calendar, Event}; type RhaiEvent = Event; type RhaiAttendee = Attendee; type RhaiCalendar = Calendar; -use crate::db::hero::OurDB; use crate::db::Collection; +use crate::db::hero::OurDB; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE + Position::NONE, )) - ) + }) } #[export_module] @@ -28,26 +28,35 @@ mod rhai_calendar_module { pub fn new_event() -> RhaiEvent { Event::new() } - + /// Sets the event title #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn event_title(event: &mut RhaiEvent, title: String) -> Result> { + pub fn event_title( + event: &mut RhaiEvent, + title: String, + ) -> Result> { let owned_event = mem::take(event); *event = owned_event.title(title); Ok(event.clone()) } - + /// Sets the event description #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn event_description(event: &mut RhaiEvent, description: String) -> Result> { + pub fn event_description( + event: &mut RhaiEvent, + description: String, + ) -> Result> { let owned_event = mem::take(event); *event = owned_event.description(description); Ok(event.clone()) } - + /// Sets the event location #[rhai_fn(name = "location", return_raw, global, pure)] - pub fn event_location(event: &mut RhaiEvent, location: String) -> Result> { + pub fn event_location( + event: &mut RhaiEvent, + location: String, + ) -> Result> { let owned_event = mem::take(event); *event = owned_event.location(location); Ok(event.clone()) @@ -55,7 +64,10 @@ mod rhai_calendar_module { /// Adds an attendee to the event #[rhai_fn(name = "add_attendee", return_raw, global, pure)] - pub fn event_add_attendee(event: &mut RhaiEvent, attendee: RhaiAttendee) -> Result> { + pub fn event_add_attendee( + event: &mut RhaiEvent, + attendee: RhaiAttendee, + ) -> Result> { // Use take to get ownership of the event let owned_event = mem::take(event); *event = owned_event.add_attendee(attendee); @@ -64,30 +76,38 @@ mod rhai_calendar_module { /// Reschedules the event with new start and end times #[rhai_fn(name = "reschedule", return_raw, global, pure)] - pub fn event_reschedule(event: &mut RhaiEvent, new_start_time: i64, new_end_time: i64) -> Result> { + pub fn event_reschedule( + event: &mut RhaiEvent, + new_start_time: i64, + new_end_time: i64, + ) -> Result> { // Validate timestamps if new_end_time <= new_start_time { return Err(Box::new(EvalAltResult::ErrorRuntime( "End time must be after start time".into(), - Position::NONE + Position::NONE, ))); } - + // Use take to get ownership of the event let owned_event = mem::take(event); *event = owned_event.reschedule(new_start_time, new_end_time); Ok(event.clone()) } - + /// Updates an attendee's status in the event #[rhai_fn(name = "update_attendee_status", return_raw, global, pure)] - pub fn event_update_attendee_status(event: &mut RhaiEvent, contact_id: i64, status_str: String) -> Result> { + pub fn event_update_attendee_status( + event: &mut RhaiEvent, + contact_id: i64, + status_str: String, + ) -> Result> { let status_enum = AttendanceStatus::from_string(&status_str) .map_err(|_| Box::new(EvalAltResult::ErrorRuntime( format!("Invalid attendance status: '{}'. Expected one of: Pending, Accepted, Declined, Tentative", status_str).into(), Position::NONE )))?; - + // Use take to get ownership of the event let owned_event = mem::take(event); *event = owned_event.update_attendee_status(id_from_i64_to_u32(contact_id)?, status_enum); @@ -96,50 +116,74 @@ mod rhai_calendar_module { // Event Getters #[rhai_fn(get = "id", pure)] - pub fn get_event_id(event: &mut RhaiEvent) -> i64 { event.base_data.id as i64 } + pub fn get_event_id(event: &mut RhaiEvent) -> i64 { + event.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_event_created_at(event: &mut RhaiEvent) -> i64 { event.base_data.created_at } + pub fn get_event_created_at(event: &mut RhaiEvent) -> i64 { + event.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_event_modified_at(event: &mut RhaiEvent) -> i64 { event.base_data.modified_at } - + pub fn get_event_modified_at(event: &mut RhaiEvent) -> i64 { + event.base_data.modified_at + } + #[rhai_fn(get = "title", pure)] - pub fn get_event_title(event: &mut RhaiEvent) -> String { event.title.clone() } + pub fn get_event_title(event: &mut RhaiEvent) -> String { + event.title.clone() + } #[rhai_fn(get = "description", pure)] - pub fn get_event_description(event: &mut RhaiEvent) -> Option { event.description.clone() } + pub fn get_event_description(event: &mut RhaiEvent) -> Option { + event.description.clone() + } #[rhai_fn(get = "start_time", pure)] - pub fn get_event_start_time(event: &mut RhaiEvent) -> i64 { event.start_time } + pub fn get_event_start_time(event: &mut RhaiEvent) -> i64 { + event.start_time + } #[rhai_fn(get = "end_time", pure)] - pub fn get_event_end_time(event: &mut RhaiEvent) -> i64 { event.end_time } + pub fn get_event_end_time(event: &mut RhaiEvent) -> i64 { + event.end_time + } #[rhai_fn(get = "location", pure)] - pub fn get_event_location(event: &mut RhaiEvent) -> Option { event.location.clone() } + pub fn get_event_location(event: &mut RhaiEvent) -> Option { + event.location.clone() + } #[rhai_fn(get = "attendees", pure)] - pub fn get_event_attendees(event: &mut RhaiEvent) -> Vec { event.attendees.clone() } + pub fn get_event_attendees(event: &mut RhaiEvent) -> Vec { + event.attendees.clone() + } // --- Attendee Functions --- #[rhai_fn(name = "new_attendee")] pub fn new_attendee() -> RhaiAttendee { Attendee::new(0) // Default contact_id, will be set via builder } - + /// Sets the contact ID for an attendee #[rhai_fn(name = "with_contact_id", return_raw, global, pure)] - pub fn attendee_with_contact_id(attendee: &mut RhaiAttendee, contact_id: i64) -> Result> { + pub fn attendee_with_contact_id( + attendee: &mut RhaiAttendee, + contact_id: i64, + ) -> Result> { let new_contact_id = id_from_i64_to_u32(contact_id).unwrap_or(0); let owned_attendee = mem::replace(attendee, Attendee::new(0)); *attendee = Attendee::new(new_contact_id); attendee.status = owned_attendee.status; Ok(attendee.clone()) } - + /// Sets the status for an attendee #[rhai_fn(name = "with_status", return_raw, global, pure)] - pub fn attendee_with_status(attendee: &mut RhaiAttendee, status_str: String) -> Result> { + pub fn attendee_with_status( + attendee: &mut RhaiAttendee, + status_str: String, + ) -> Result> { let status_enum = AttendanceStatus::from_string(&status_str) .map_err(|_| Box::new(EvalAltResult::ErrorRuntime( format!("Invalid attendance status: '{}'. Expected one of: Accepted, Declined, Tentative, NoResponse", status_str).into(), Position::NONE )))?; - + let owned_attendee = mem::replace(attendee, Attendee::new(0)); *attendee = owned_attendee.status(status_enum); Ok(attendee.clone()) @@ -149,9 +193,13 @@ mod rhai_calendar_module { // Attendee Getters #[rhai_fn(get = "contact_id", pure)] - pub fn get_attendee_contact_id(attendee: &mut RhaiAttendee) -> i64 { attendee.contact_id as i64 } + pub fn get_attendee_contact_id(attendee: &mut RhaiAttendee) -> i64 { + attendee.contact_id as i64 + } #[rhai_fn(get = "status", pure)] - pub fn get_attendee_status(attendee: &mut RhaiAttendee) -> String { attendee.status.to_string() } + pub fn get_attendee_status(attendee: &mut RhaiAttendee) -> String { + attendee.status.to_string() + } // --- Calendar Functions --- #[rhai_fn(name = "new_calendar", return_raw)] @@ -159,25 +207,31 @@ mod rhai_calendar_module { let calendar = Calendar::new(None, ""); Ok(calendar) } - + /// Sets the calendar name #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn calendar_name(calendar: &mut RhaiCalendar, name: String) -> Result> { + pub fn calendar_name( + calendar: &mut RhaiCalendar, + name: String, + ) -> Result> { // Create a default Calendar to replace the taken one let default_calendar = Calendar::new(None, ""); - + // Take ownership of the calendar, apply the builder method, then put it back let owned_calendar = std::mem::replace(calendar, default_calendar); *calendar = Calendar::new(Some(owned_calendar.base_data.id), name); Ok(calendar.clone()) } - + /// Sets the calendar description #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn calendar_description(calendar: &mut RhaiCalendar, description: String) -> Result> { + pub fn calendar_description( + calendar: &mut RhaiCalendar, + description: String, + ) -> Result> { // Create a default Calendar to replace the taken one let default_calendar = Calendar::new(None, ""); - + // Take ownership of the calendar, apply the builder method, then put it back let owned_calendar = std::mem::replace(calendar, default_calendar); let updated_calendar = owned_calendar.description(description); @@ -186,10 +240,13 @@ mod rhai_calendar_module { } #[rhai_fn(name = "add_event_to_calendar", return_raw, global, pure)] - pub fn calendar_add_event(calendar: &mut RhaiCalendar, event: RhaiEvent) -> Result> { + pub fn calendar_add_event( + calendar: &mut RhaiCalendar, + event: RhaiEvent, + ) -> Result> { // Create a default Calendar to replace the taken one let default_calendar = Calendar::new(None, ""); - + // Take ownership of the calendar, apply the builder method, then put it back let owned_calendar = std::mem::replace(calendar, default_calendar); *calendar = owned_calendar.add_event(event.base_data.id as i64); @@ -197,10 +254,13 @@ mod rhai_calendar_module { } #[rhai_fn(name = "remove_event_from_calendar", return_raw)] - pub fn calendar_remove_event(calendar: &mut RhaiCalendar, event_id: i64) -> Result<(), Box> { + pub fn calendar_remove_event( + calendar: &mut RhaiCalendar, + event_id: i64, + ) -> Result<(), Box> { // Create a default Calendar to replace the taken one let default_calendar = Calendar::new(None, ""); - + // Take ownership of the calendar, apply the builder method, then put it back let owned_calendar = std::mem::replace(calendar, default_calendar); *calendar = owned_calendar.remove_event(id_from_i64_to_u32(event_id)? as i64); @@ -209,140 +269,213 @@ mod rhai_calendar_module { // Calendar Getters #[rhai_fn(get = "id", pure)] - pub fn get_calendar_id(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.id as i64 } - + pub fn get_calendar_id(calendar: &mut RhaiCalendar) -> i64 { + calendar.base_data.id as i64 + } + #[rhai_fn(get = "name", pure)] - pub fn get_calendar_name(calendar: &mut RhaiCalendar) -> String { calendar.name.clone() } - + pub fn get_calendar_name(calendar: &mut RhaiCalendar) -> String { + calendar.name.clone() + } + #[rhai_fn(get = "created_at", pure)] - pub fn get_calendar_created_at(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.created_at } - + pub fn get_calendar_created_at(calendar: &mut RhaiCalendar) -> i64 { + calendar.base_data.created_at + } + #[rhai_fn(get = "modified_at", pure)] - pub fn get_calendar_modified_at(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.modified_at } - + pub fn get_calendar_modified_at(calendar: &mut RhaiCalendar) -> i64 { + calendar.base_data.modified_at + } + #[rhai_fn(get = "events", pure)] - pub fn get_calendar_events(calendar: &mut RhaiCalendar) -> Vec { calendar.events.clone() } - + pub fn get_calendar_events(calendar: &mut RhaiCalendar) -> Vec { + calendar.events.clone() + } + #[rhai_fn(get = "description", pure)] - pub fn get_calendar_description(calendar: &mut RhaiCalendar) -> Option { calendar.description.clone() } - + pub fn get_calendar_description(calendar: &mut RhaiCalendar) -> Option { + calendar.description.clone() + } + // Calendar doesn't have an owner_id field in the current implementation // pub fn get_calendar_owner_id(calendar: &mut RhaiCalendar) -> i64 { calendar.owner_id as i64 } - } pub fn register_calendar_rhai_module(engine: &mut Engine, db: Arc) { // Register the exported module globally let module = exported_module!(rhai_calendar_module); engine.register_global_module(module.into()); - + // Create a module for database functions let mut db_module = Module::new(); // Manually register database functions as they need to capture 'db' let db_clone_set_event = db.clone(); - db_module.set_native_fn("save_event", move |event: Event| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_event.set(&event) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_event: {}", e).into(), Position::NONE)))?; - - // Return the updated event with the correct ID - Ok(result.1) - }); + db_module.set_native_fn( + "save_event", + move |event: Event| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_event.set(&event).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error set_event: {}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated event with the correct ID + Ok(result.1) + }, + ); // Manually register database functions as they need to capture 'db' let db_clone_delete_event = db.clone(); - db_module.set_native_fn("delete_event", move |event: Event| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_event.collection::() - .expect("can open event collection") - .delete_by_id(event.base_data.id) - .expect("can delete event"); - - // Return the updated event with the correct ID - Ok(result) - }); - + db_module.set_native_fn( + "delete_event", + move |event: Event| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_event + .collection::() + .expect("can open event collection") + .delete_by_id(event.base_data.id) + .expect("can delete event"); + + // Return the updated event with the correct ID + Ok(result) + }, + ); + let db_clone_get_event = db.clone(); - db_module.set_native_fn("get_event_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_event.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_event_by_id: {}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Event with ID {} not found", id_u32).into(), Position::NONE))) - }); - + db_module.set_native_fn( + "get_event_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_event + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_event_by_id: {}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Event with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); + let db_clone_set_calendar = db.clone(); - db_module.set_native_fn("save_calendar", move |calendar: Calendar| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_calendar.set(&calendar) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_calendar: {}", e).into(), Position::NONE)))?; - - // Return the updated calendar with the correct ID - Ok(result.1) - }); + db_module.set_native_fn( + "save_calendar", + move |calendar: Calendar| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_calendar.set(&calendar).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error set_calendar: {}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated calendar with the correct ID + Ok(result.1) + }, + ); // Manually register database functions as they need to capture 'db' let db_clone_delete_calendar = db.clone(); - db_module.set_native_fn("delete_calendar", move |calendar: Calendar| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_calendar.collection::() - .expect("can open calendar collection") - .delete_by_id(calendar.base_data.id) - .expect("can delete event"); - - // Return the updated event with the correct ID - Ok(result) - }); - + db_module.set_native_fn( + "delete_calendar", + move |calendar: Calendar| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_calendar + .collection::() + .expect("can open calendar collection") + .delete_by_id(calendar.base_data.id) + .expect("can delete event"); + + // Return the updated event with the correct ID + Ok(result) + }, + ); + let db_clone_get_calendar = db.clone(); - db_module.set_native_fn("get_calendar_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_calendar.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_calendar_by_id: {}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Calendar with ID {} not found", id_u32).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_calendar_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_calendar + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_calendar_by_id: {}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Calendar with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); // Add list_calendars function to get all calendars let db_clone_list_calendars = db.clone(); - db_module.set_native_fn("list_calendars", move || -> Result> { - let collection = db_clone_list_calendars.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get calendar collection: {:?}", e).into(), - Position::NONE - )))?; - let calendars = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all calendars: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for calendar in calendars { - array.push(Dynamic::from(calendar)); - } - Ok(Dynamic::from(array)) - }); - + db_module.set_native_fn( + "list_calendars", + move || -> Result> { + let collection = db_clone_list_calendars + .collection::() + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get calendar collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let calendars = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all calendars: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for calendar in calendars { + array.push(Dynamic::from(calendar)); + } + Ok(Dynamic::from(array)) + }, + ); + // Add list_events function to get all events let db_clone_list_events = db.clone(); - db_module.set_native_fn("list_events", move || -> Result> { - let collection = db_clone_list_events.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get event collection: {:?}", e).into(), - Position::NONE - )))?; - let events = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all events: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for event in events { - array.push(Dynamic::from(event)); - } - Ok(Dynamic::from(array)) - }); + db_module.set_native_fn( + "list_events", + move || -> Result> { + let collection = db_clone_list_events.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get event collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let events = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all events: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for event in events { + array.push(Dynamic::from(event)); + } + Ok(Dynamic::from(array)) + }, + ); // Register the database module globally engine.register_global_module(db_module.into()); diff --git a/heromodels/src/models/circle/circle.rs b/heromodels/src/models/circle/circle.rs index 43d4b11..450444b 100644 --- a/heromodels/src/models/circle/circle.rs +++ b/heromodels/src/models/circle/circle.rs @@ -19,6 +19,8 @@ pub struct Circle { pub description: Option, /// List of related circles pub circles: Vec, + /// List of members in the circle (their public keys) + pub members: Vec, /// Logo URL or symbol for the circle pub logo: Option, /// Theme settings for the circle (colors, styling, etc.) @@ -35,6 +37,7 @@ impl Circle { description: None, circles: Vec::new(), logo: None, + members: Vec::new(), theme: HashMap::new(), } } @@ -83,4 +86,13 @@ impl Circle { } self } -} \ No newline at end of file + + /// Adds a member to the circle + pub fn add_member(mut self, member: String) -> Self { + // Prevent duplicate members + if !self.members.iter().any(|a| *a == member) { + self.members.push(member); + } + self + } +} diff --git a/heromodels/src/models/circle/mod.rs b/heromodels/src/models/circle/mod.rs index f761c82..cb5c353 100644 --- a/heromodels/src/models/circle/mod.rs +++ b/heromodels/src/models/circle/mod.rs @@ -3,5 +3,5 @@ pub mod circle; pub mod rhai; // Re-export Calendar, Event, Attendee, and AttendanceStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs -pub use self::circle::{Circle}; +pub use self::circle::Circle; pub use rhai::register_circle_rhai_module; diff --git a/heromodels/src/models/circle/rhai.rs b/heromodels/src/models/circle/rhai.rs index 07f9f10..3bab892 100644 --- a/heromodels/src/models/circle/rhai.rs +++ b/heromodels/src/models/circle/rhai.rs @@ -1,16 +1,16 @@ -use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array, CustomType}; -use std::sync::Arc; -use std::mem; use crate::db::Db; +use rhai::plugin::*; +use rhai::{Array, CustomType, Dynamic, Engine, EvalAltResult, INT, Module, Position}; +use std::mem; +use std::sync::Arc; -use super::circle::{Circle}; +use super::circle::Circle; type RhaiCircle = Circle; -use crate::db::hero::OurDB; use crate::db::Collection; +use crate::db::hero::OurDB; use serde::Serialize; -use std::collections::HashMap; use serde_json; +use std::collections::HashMap; /// Registers a `.json()` method for any type `T` that implements the required traits. fn register_json_method(engine: &mut Engine) @@ -31,12 +31,12 @@ where // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE + Position::NONE, )) - ) + }) } #[export_module] @@ -46,10 +46,13 @@ mod rhai_circle_module { pub fn new_circle() -> RhaiCircle { Circle::new() } - + /// Sets the circle title #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn circle_title(circle: &mut RhaiCircle, title: String) -> Result> { + pub fn circle_title( + circle: &mut RhaiCircle, + title: String, + ) -> Result> { let owned_circle = mem::take(circle); *circle = owned_circle.title(title); Ok(circle.clone()) @@ -57,15 +60,21 @@ mod rhai_circle_module { /// Sets the circle ws_url #[rhai_fn(name = "ws_url", return_raw, global, pure)] - pub fn circle_ws_url(circle: &mut RhaiCircle, ws_url: String) -> Result> { + pub fn circle_ws_url( + circle: &mut RhaiCircle, + ws_url: String, + ) -> Result> { let owned_circle = mem::take(circle); *circle = owned_circle.ws_url(ws_url); Ok(circle.clone()) } - + /// Sets the circle description #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn circle_description(circle: &mut RhaiCircle, description: String) -> Result> { + pub fn circle_description( + circle: &mut RhaiCircle, + description: String, + ) -> Result> { let owned_circle = mem::take(circle); *circle = owned_circle.description(description); Ok(circle.clone()) @@ -73,7 +82,10 @@ mod rhai_circle_module { /// Sets the circle logo #[rhai_fn(name = "logo", return_raw, global, pure)] - pub fn circle_logo(circle: &mut RhaiCircle, logo: String) -> Result> { + pub fn circle_logo( + circle: &mut RhaiCircle, + logo: String, + ) -> Result> { let owned_circle = mem::take(circle); *circle = owned_circle.logo(logo); Ok(circle.clone()) @@ -81,118 +93,194 @@ mod rhai_circle_module { /// Sets the circle theme #[rhai_fn(name = "theme", return_raw, global, pure)] - pub fn circle_theme(circle: &mut RhaiCircle, theme: HashMap) -> Result> { + pub fn circle_theme( + circle: &mut RhaiCircle, + theme: HashMap, + ) -> Result> { let owned_circle = mem::take(circle); *circle = owned_circle.theme(theme); Ok(circle.clone()) } - + /// Adds an attendee to the circle #[rhai_fn(name = "add_circle", return_raw, global, pure)] - pub fn circle_add_circle(circle: &mut RhaiCircle, added_circle: String) -> Result> { + pub fn circle_add_circle( + circle: &mut RhaiCircle, + added_circle: String, + ) -> Result> { // Use take to get ownership of the circle let owned_circle = mem::take(circle); *circle = owned_circle.add_circle(added_circle); Ok(circle.clone()) } + /// Adds an attendee to the circle + #[rhai_fn(name = "add_member", return_raw, global, pure)] + pub fn circle_add_member( + circle: &mut RhaiCircle, + added_member: String, + ) -> Result> { + // Use take to get ownership of the circle + let owned_circle = mem::take(circle); + *circle = owned_circle.add_member(added_member); + Ok(circle.clone()) + } + // Circle Getters #[rhai_fn(get = "id", pure)] - pub fn get_circle_id(circle: &mut RhaiCircle) -> i64 { circle.base_data.id as i64 } + pub fn get_circle_id(circle: &mut RhaiCircle) -> i64 { + circle.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_circle_created_at(circle: &mut RhaiCircle) -> i64 { circle.base_data.created_at } + pub fn get_circle_created_at(circle: &mut RhaiCircle) -> i64 { + circle.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_circle_modified_at(circle: &mut RhaiCircle) -> i64 { circle.base_data.modified_at } - + pub fn get_circle_modified_at(circle: &mut RhaiCircle) -> i64 { + circle.base_data.modified_at + } + #[rhai_fn(get = "title", pure)] - pub fn get_circle_title(circle: &mut RhaiCircle) -> String { circle.title.clone() } + pub fn get_circle_title(circle: &mut RhaiCircle) -> String { + circle.title.clone() + } #[rhai_fn(get = "description", pure)] - pub fn get_circle_description(circle: &mut RhaiCircle) -> Option { circle.description.clone() } + pub fn get_circle_description(circle: &mut RhaiCircle) -> Option { + circle.description.clone() + } #[rhai_fn(get = "circles", pure)] - pub fn get_circle_circles(circle: &mut RhaiCircle) -> Vec { circle.circles.clone() } + pub fn get_circle_circles(circle: &mut RhaiCircle) -> Vec { + circle.circles.clone() + } #[rhai_fn(get = "ws_url", pure)] - pub fn get_circle_ws_url(circle: &mut RhaiCircle) -> String { circle.ws_url.clone() } + pub fn get_circle_ws_url(circle: &mut RhaiCircle) -> String { + circle.ws_url.clone() + } #[rhai_fn(get = "logo", pure)] - pub fn get_circle_logo(circle: &mut RhaiCircle) -> Option { circle.logo.clone() } + pub fn get_circle_logo(circle: &mut RhaiCircle) -> Option { + circle.logo.clone() + } #[rhai_fn(get = "theme", pure)] - pub fn get_circle_theme(circle: &mut RhaiCircle) -> HashMap { circle.theme.clone() } + pub fn get_circle_theme(circle: &mut RhaiCircle) -> HashMap { + circle.theme.clone() + } } pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { // Register the exported module globally let module = exported_module!(rhai_circle_module); engine.register_global_module(module.into()); - + // Create a module for database functions let mut db_module = Module::new(); // Manually register database functions as they need to capture 'db' let db_clone_set_circle = db.clone(); - db_module.set_native_fn("save_circle", move |circle: Circle| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_circle.set(&circle) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_circle: {}", e).into(), Position::NONE)))?; - - // Return the updated circle with the correct ID - Ok(result.1) - }); + db_module.set_native_fn( + "save_circle", + move |circle: Circle| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_circle.set(&circle).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error set_circle: {}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated circle with the correct ID + Ok(result.1) + }, + ); register_json_method::(engine); // Manually register database functions as they need to capture 'db' let db_clone_delete_circle = db.clone(); - db_module.set_native_fn("delete_circle", move |circle: Circle| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_circle.collection::() - .expect("can open circle collection") - .delete_by_id(circle.base_data.id) - .expect("can delete circle"); - - // Return the updated circle with the correct ID - Ok(result) - }); - - let db_clone_get_circle = db.clone(); - db_module.set_native_fn("get_circle", move || -> Result> { - // Use the Collection trait method directly - let all_circles: Vec = db_clone_get_circle.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_circle: {}", e).into(), Position::NONE)))?; + db_module.set_native_fn( + "delete_circle", + move |circle: Circle| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_circle + .collection::() + .expect("can open circle collection") + .delete_by_id(circle.base_data.id) + .expect("can delete circle"); - if let Some(first_circle) = all_circles.first() { - Ok(first_circle.clone()) - } else { - Err(Box::new(EvalAltResult::ErrorRuntime("Circle not found".into(), Position::NONE))) - } - }); + // Return the updated circle with the correct ID + Ok(result) + }, + ); + + let db_clone_get_circle = db.clone(); + db_module.set_native_fn( + "get_circle", + move || -> Result> { + // Use the Collection trait method directly + let all_circles: Vec = db_clone_get_circle.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_circle: {}", e).into(), + Position::NONE, + )) + })?; + + if let Some(first_circle) = all_circles.first() { + Ok(first_circle.clone()) + } else { + Err(Box::new(EvalAltResult::ErrorRuntime( + "Circle not found".into(), + Position::NONE, + ))) + } + }, + ); let db_clone_get_circle_by_id = db.clone(); - db_module.set_native_fn("get_circle_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_circle_by_id.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_circle_by_id: {}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Circle with ID {} not found", id_u32).into(), Position::NONE))) - }); - + db_module.set_native_fn( + "get_circle_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_circle_by_id + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_circle_by_id: {}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Circle with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); + // Add list_circles function to get all circles let db_clone_list_circles = db.clone(); - db_module.set_native_fn("list_circles", move || -> Result> { - let collection = db_clone_list_circles.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get circle collection: {:?}", e).into(), - Position::NONE - )))?; - let circles = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all circles: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for circle in circles { - array.push(Dynamic::from(circle)); - } - Ok(Dynamic::from(array)) - }); + db_module.set_native_fn( + "list_circles", + move || -> Result> { + let collection = db_clone_list_circles.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get circle collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let circles = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all circles: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for circle in circles { + array.push(Dynamic::from(circle)); + } + Ok(Dynamic::from(array)) + }, + ); // Register the database module globally engine.register_global_module(db_module.into()); diff --git a/heromodels/src/models/contact/contact.rs b/heromodels/src/models/contact/contact.rs index 32095a1..1b9da79 100644 --- a/heromodels/src/models/contact/contact.rs +++ b/heromodels/src/models/contact/contact.rs @@ -112,4 +112,4 @@ impl Group { self.contacts.push(contact); self } -} \ No newline at end of file +} diff --git a/heromodels/src/models/contact/rhai.rs b/heromodels/src/models/contact/rhai.rs index 12b6518..fb0b01d 100644 --- a/heromodels/src/models/contact/rhai.rs +++ b/heromodels/src/models/contact/rhai.rs @@ -1,23 +1,23 @@ -use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; -use std::sync::Arc; -use std::mem; use crate::db::Db; +use rhai::plugin::*; +use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; +use std::mem; +use std::sync::Arc; -use super::contact::{Group, Contact}; +use super::contact::{Contact, Group}; type RhaiGroup = Group; type RhaiContact = Contact; -use crate::db::hero::OurDB; use crate::db::Collection; +use crate::db::hero::OurDB; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE + Position::NONE, )) - ) + }) } #[export_module] @@ -27,18 +27,24 @@ mod rhai_contact_module { pub fn new_group() -> RhaiGroup { Group::new() } - + /// Sets the event title #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn group_name(group: &mut RhaiGroup, name: String) -> Result> { + pub fn group_name( + group: &mut RhaiGroup, + name: String, + ) -> Result> { let owned_group = mem::take(group); *group = owned_group.name(name); Ok(group.clone()) } - + /// Sets the event description #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn group_description(group: &mut RhaiGroup, description: String) -> Result> { + pub fn group_description( + group: &mut RhaiGroup, + description: String, + ) -> Result> { let owned_group = mem::take(group); *group = owned_group.description(description); Ok(group.clone()) @@ -46,7 +52,10 @@ mod rhai_contact_module { /// Adds an attendee to the event #[rhai_fn(name = "add_contact", return_raw, global, pure)] - pub fn group_add_contact(group: &mut RhaiGroup, contact_id: i64) -> Result> { + pub fn group_add_contact( + group: &mut RhaiGroup, + contact_id: i64, + ) -> Result> { // Use take to get ownership of the event let owned_group = mem::take(group); *group = owned_group.add_contact(contact_id as u32); @@ -54,21 +63,37 @@ mod rhai_contact_module { } #[rhai_fn(get = "contacts", pure)] - pub fn get_group_contacts(group: &mut RhaiGroup) -> Vec { group.contacts.clone().into_iter().map(|id| id as i64).collect() } + pub fn get_group_contacts(group: &mut RhaiGroup) -> Vec { + group + .contacts + .clone() + .into_iter() + .map(|id| id as i64) + .collect() + } // Group Getters #[rhai_fn(get = "id", pure)] - pub fn get_group_id(group: &mut RhaiGroup) -> i64 { group.base_data.id as i64 } + pub fn get_group_id(group: &mut RhaiGroup) -> i64 { + group.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_group_created_at(group: &mut RhaiGroup) -> i64 { group.base_data.created_at } + pub fn get_group_created_at(group: &mut RhaiGroup) -> i64 { + group.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_group_modified_at(group: &mut RhaiGroup) -> i64 { group.base_data.modified_at } - - #[rhai_fn(get = "name", pure)] - pub fn get_group_name(group: &mut RhaiGroup) -> String { group.name.clone() } - #[rhai_fn(get = "description", pure)] - pub fn get_group_description(group: &mut RhaiGroup) -> Option { group.description.clone() } + pub fn get_group_modified_at(group: &mut RhaiGroup) -> i64 { + group.base_data.modified_at + } + #[rhai_fn(get = "name", pure)] + pub fn get_group_name(group: &mut RhaiGroup) -> String { + group.name.clone() + } + #[rhai_fn(get = "description", pure)] + pub fn get_group_description(group: &mut RhaiGroup) -> Option { + group.description.clone() + } // --- Contact Functions --- #[rhai_fn(name = "new_contact", return_raw)] @@ -76,25 +101,31 @@ mod rhai_contact_module { let contact = Contact::new(); Ok(contact) } - + /// Sets the contact name #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn contact_name(contact: &mut RhaiContact, name: String) -> Result> { + pub fn contact_name( + contact: &mut RhaiContact, + name: String, + ) -> Result> { // Create a default Contact to replace the taken one let default_contact = Contact::new(); - + // Take ownership of the contact, apply the builder method, then put it back let owned_contact = std::mem::replace(contact, default_contact); *contact = owned_contact.name(name); Ok(contact.clone()) } - + /// Sets the contact description #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn contact_description(contact: &mut RhaiContact, description: String) -> Result> { + pub fn contact_description( + contact: &mut RhaiContact, + description: String, + ) -> Result> { // Create a default Contact to replace the taken one let default_contact = Contact::new(); - + // Take ownership of the contact, apply the builder method, then put it back let owned_contact = std::mem::replace(contact, default_contact); *contact = owned_contact.description(description); @@ -103,130 +134,200 @@ mod rhai_contact_module { // Contact Getters #[rhai_fn(get = "id", pure)] - pub fn get_contact_id(contact: &mut RhaiContact) -> i64 { contact.base_data.id as i64 } - + pub fn get_contact_id(contact: &mut RhaiContact) -> i64 { + contact.base_data.id as i64 + } + #[rhai_fn(get = "name", pure)] - pub fn get_contact_name(contact: &mut RhaiContact) -> String { contact.name.clone() } - + pub fn get_contact_name(contact: &mut RhaiContact) -> String { + contact.name.clone() + } + #[rhai_fn(get = "created_at", pure)] - pub fn get_contact_created_at(contact: &mut RhaiContact) -> i64 { contact.base_data.created_at } - + pub fn get_contact_created_at(contact: &mut RhaiContact) -> i64 { + contact.base_data.created_at + } + #[rhai_fn(get = "modified_at", pure)] - pub fn get_contact_modified_at(contact: &mut RhaiContact) -> i64 { contact.base_data.modified_at } + pub fn get_contact_modified_at(contact: &mut RhaiContact) -> i64 { + contact.base_data.modified_at + } } pub fn register_contact_rhai_module(engine: &mut Engine, db: Arc) { // Register the exported module globally let module = exported_module!(rhai_contact_module); engine.register_global_module(module.into()); - + // Create a module for database functions let mut db_module = Module::new(); // Manually register database functions as they need to capture 'db' let db_clone_set_group = db.clone(); - db_module.set_native_fn("save_group", move |group: Group| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_group.set(&group) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_group: {}", e).into(), Position::NONE)))?; - - // Return the updated event with the correct ID - Ok(result.1) - }); + db_module.set_native_fn( + "save_group", + move |group: Group| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_group.set(&group).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error set_group: {}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated event with the correct ID + Ok(result.1) + }, + ); // Manually register database functions as they need to capture 'db' let db_clone_delete_group = db.clone(); - db_module.set_native_fn("delete_group", move |group: Group| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_group.collection::() - .expect("can open group collection") - .delete_by_id(group.base_data.id) - .expect("can delete group"); - - // Return the updated event with the correct ID - Ok(result) - }); - + db_module.set_native_fn( + "delete_group", + move |group: Group| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_group + .collection::() + .expect("can open group collection") + .delete_by_id(group.base_data.id) + .expect("can delete group"); + + // Return the updated event with the correct ID + Ok(result) + }, + ); + let db_clone_get_group = db.clone(); - db_module.set_native_fn("get_group_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_group.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_event_by_id: {}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Event with ID {} not found", id_u32).into(), Position::NONE))) - }); - + db_module.set_native_fn( + "get_group_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_group + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_event_by_id: {}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Event with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); + let db_clone_set_contact = db.clone(); - db_module.set_native_fn("save_contact", move |contact: Contact| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_contact.set(&contact) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_contact: {}", e).into(), Position::NONE)))?; - - // Return the updated contact with the correct ID - Ok(result.1) - }); + db_module.set_native_fn( + "save_contact", + move |contact: Contact| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_contact.set(&contact).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error set_contact: {}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated contact with the correct ID + Ok(result.1) + }, + ); // Manually register database functions as they need to capture 'db' let db_clone_delete_contact = db.clone(); - db_module.set_native_fn("delete_contact", move |contact: Contact| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_contact.collection::() - .expect("can open contact collection") - .delete_by_id(contact.base_data.id) - .expect("can delete event"); - - // Return the updated event with the correct ID - Ok(result) - }); - + db_module.set_native_fn( + "delete_contact", + move |contact: Contact| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_contact + .collection::() + .expect("can open contact collection") + .delete_by_id(contact.base_data.id) + .expect("can delete event"); + + // Return the updated event with the correct ID + Ok(result) + }, + ); + let db_clone_get_contact = db.clone(); - db_module.set_native_fn("get_contact_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_contact.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_contact_by_id: {}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Contact with ID {} not found", id_u32).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_contact_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_contact + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_contact_by_id: {}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Contact with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); // Add list_contacts function to get all contacts let db_clone_list_contacts = db.clone(); - db_module.set_native_fn("list_contacts", move || -> Result> { - let collection = db_clone_list_contacts.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get contact collection: {:?}", e).into(), - Position::NONE - )))?; - let contacts = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all contacts: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for contact in contacts { - array.push(Dynamic::from(contact)); - } - Ok(Dynamic::from(array)) - }); - + db_module.set_native_fn( + "list_contacts", + move || -> Result> { + let collection = db_clone_list_contacts + .collection::() + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get contact collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let contacts = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all contacts: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for contact in contacts { + array.push(Dynamic::from(contact)); + } + Ok(Dynamic::from(array)) + }, + ); + // Add list_events function to get all events let db_clone_list_groups = db.clone(); - db_module.set_native_fn("list_groups", move || -> Result> { - let collection = db_clone_list_groups.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get group collection: {:?}", e).into(), - Position::NONE - )))?; - let groups = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all groups: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for group in groups { - array.push(Dynamic::from(group)); - } - Ok(Dynamic::from(array)) - }); + db_module.set_native_fn( + "list_groups", + move || -> Result> { + let collection = db_clone_list_groups.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get group collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let groups = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all groups: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for group in groups { + array.push(Dynamic::from(group)); + } + Ok(Dynamic::from(array)) + }, + ); // Register the database module globally engine.register_global_module(db_module.into()); diff --git a/heromodels/src/models/core/comment.rs b/heromodels/src/models/core/comment.rs index 0122b02..715c2d3 100644 --- a/heromodels/src/models/core/comment.rs +++ b/heromodels/src/models/core/comment.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; pub struct Comment { pub base_data: BaseModelData, // Provides id, created_at, updated_at #[index] - pub user_id: u32, // Maps to commenter_id + pub user_id: u32, // Maps to commenter_id pub content: String, // Maps to text pub parent_comment_id: Option, // For threaded comments } diff --git a/heromodels/src/models/core/mod.rs b/heromodels/src/models/core/mod.rs index f7fde31..e535e98 100644 --- a/heromodels/src/models/core/mod.rs +++ b/heromodels/src/models/core/mod.rs @@ -4,4 +4,3 @@ pub mod model; // Re-export key types for convenience pub use comment::Comment; - diff --git a/heromodels/src/models/core/model.rs b/heromodels/src/models/core/model.rs index e69de29..8b13789 100644 --- a/heromodels/src/models/core/model.rs +++ b/heromodels/src/models/core/model.rs @@ -0,0 +1 @@ + diff --git a/heromodels/src/models/finance/account.rs b/heromodels/src/models/finance/account.rs index 7bb9e6c..eb72c8d 100644 --- a/heromodels/src/models/finance/account.rs +++ b/heromodels/src/models/finance/account.rs @@ -1,9 +1,9 @@ // heromodels/src/models/finance/account.rs -use serde::{Deserialize, Serialize}; -use rhai::{CustomType, TypeBuilder}; -use heromodels_derive::model; use heromodels_core::BaseModelData; +use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; use super::asset::Asset; @@ -18,7 +18,7 @@ pub struct Account { pub ledger: String, // describes the ledger/blockchain where the account is located pub address: String, // address of the account on the blockchain pub pubkey: String, // public key - pub assets: Vec, // list of assets in this account + pub assets: Vec, // list of assets in this account } impl Account { @@ -87,6 +87,6 @@ impl Account { /// Find an asset by name pub fn find_asset_by_name(&self, _name: &str) -> Option<&Asset> { // TODO: implement - return None + return None; } } diff --git a/heromodels/src/models/finance/asset.rs b/heromodels/src/models/finance/asset.rs index 20dbff1..7eb59cf 100644 --- a/heromodels/src/models/finance/asset.rs +++ b/heromodels/src/models/finance/asset.rs @@ -1,9 +1,9 @@ // heromodels/src/models/finance/asset.rs -use serde::{Deserialize, Serialize}; -use rhai::{CustomType, TypeBuilder}; -use heromodels_derive::model; use heromodels_core::BaseModelData; +use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; +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 f82bd90..0f5300d 100644 --- a/heromodels/src/models/finance/marketplace.rs +++ b/heromodels/src/models/finance/marketplace.rs @@ -1,10 +1,10 @@ // heromodels/src/models/finance/marketplace.rs -use serde::{Deserialize, Serialize}; -use rhai::{CustomType, TypeBuilder}; use chrono::{DateTime, Utc}; use heromodels_core::BaseModelData; use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; use super::asset::AssetType; @@ -53,8 +53,7 @@ impl Default for BidStatus { } /// Bid represents a bid on an auction listing -#[derive(Debug, Clone, Serialize, Deserialize, CustomType)] -#[derive(Default)] +#[derive(Debug, Clone, Serialize, Deserialize, CustomType, Default)] pub struct Bid { pub listing_id: String, // ID of the listing this bid belongs to pub bidder_id: u32, // ID of the user who placed the bid @@ -272,7 +271,7 @@ impl Listing { } self.status = ListingStatus::Sold; - + if self.sold_at.is_none() { self.sold_at = Some(Utc::now()); } @@ -281,7 +280,7 @@ impl Listing { if self.listing_type == ListingType::Auction { let buyer_id_str = self.buyer_id.as_ref().unwrap().to_string(); let sale_price_val = self.sale_price.unwrap(); - + for bid in &mut self.bids { if bid.bidder_id.to_string() == buyer_id_str && bid.amount == sale_price_val { bid.status = BidStatus::Accepted; diff --git a/heromodels/src/models/finance/mod.rs b/heromodels/src/models/finance/mod.rs index bf94b7e..871c4f9 100644 --- a/heromodels/src/models/finance/mod.rs +++ b/heromodels/src/models/finance/mod.rs @@ -9,4 +9,4 @@ pub mod rhai; // Re-export main models for easier access pub use self::account::Account; pub use self::asset::{Asset, AssetType}; -pub use self::marketplace::{Listing, ListingStatus, ListingType, Bid, BidStatus}; +pub use self::marketplace::{Bid, BidStatus, Listing, ListingStatus, ListingType}; diff --git a/heromodels/src/models/finance/rhai.rs b/heromodels/src/models/finance/rhai.rs index 0633904..e45057e 100644 --- a/heromodels/src/models/finance/rhai.rs +++ b/heromodels/src/models/finance/rhai.rs @@ -1,12 +1,12 @@ -use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; -use std::sync::Arc; -use std::mem; use chrono::Utc; +use rhai::plugin::*; +use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; +use std::mem; +use std::sync::Arc; use super::account::Account; use super::asset::{Asset, AssetType}; -use super::marketplace::{Listing, Bid, ListingStatus, ListingType, BidStatus}; +use super::marketplace::{Bid, BidStatus, Listing, ListingStatus, ListingType}; use crate::db::hero::OurDB; use crate::db::{Collection, Db}; @@ -18,12 +18,12 @@ type RhaiBid = Bid; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE + Position::NONE, )) - ) + }) } // Helper functions for enum conversions @@ -45,8 +45,12 @@ fn string_to_asset_type(s: &str) -> Result> { "Erc721" => Ok(AssetType::Erc721), "Erc1155" => Ok(AssetType::Erc1155), _ => Err(Box::new(EvalAltResult::ErrorRuntime( - format!("Invalid asset type: '{}'. Expected one of: Native, Erc20, Erc721, Erc1155", s).into(), - Position::NONE + format!( + "Invalid asset type: '{}'. Expected one of: Native, Erc20, Erc721, Erc1155", + s + ) + .into(), + Position::NONE, ))), } } @@ -68,8 +72,12 @@ fn string_to_listing_status(s: &str) -> Result "Cancelled" => Ok(ListingStatus::Cancelled), "Expired" => Ok(ListingStatus::Expired), _ => Err(Box::new(EvalAltResult::ErrorRuntime( - format!("Invalid listing status: '{}'. Expected one of: Active, Sold, Cancelled, Expired", s).into(), - Position::NONE + format!( + "Invalid listing status: '{}'. Expected one of: Active, Sold, Cancelled, Expired", + s + ) + .into(), + Position::NONE, ))), } } @@ -89,8 +97,12 @@ fn string_to_listing_type(s: &str) -> Result> { "Auction" => Ok(ListingType::Auction), "Exchange" => Ok(ListingType::Exchange), _ => Err(Box::new(EvalAltResult::ErrorRuntime( - format!("Invalid listing type: '{}'. Expected one of: FixedPrice, Auction, Exchange", s).into(), - Position::NONE + format!( + "Invalid listing type: '{}'. Expected one of: FixedPrice, Auction, Exchange", + s + ) + .into(), + Position::NONE, ))), } } @@ -112,8 +124,12 @@ fn string_to_bid_status(s: &str) -> Result> { "Rejected" => Ok(BidStatus::Rejected), "Cancelled" => Ok(BidStatus::Cancelled), _ => Err(Box::new(EvalAltResult::ErrorRuntime( - format!("Invalid bid status: '{}'. Expected one of: Active, Accepted, Rejected, Cancelled", s).into(), - Position::NONE + format!( + "Invalid bid status: '{}'. Expected one of: Active, Accepted, Rejected, Cancelled", + s + ) + .into(), + Position::NONE, ))), } } @@ -181,7 +197,10 @@ mod account_module { // Setters using builder pattern with proper mutability handling #[rhai_fn(return_raw, global)] - pub fn name(account: &mut RhaiAccount, name: String) -> Result> { + pub fn name( + account: &mut RhaiAccount, + name: String, + ) -> Result> { let mut acc = mem::take(account); acc = acc.name(name); *account = acc; @@ -189,7 +208,10 @@ mod account_module { } #[rhai_fn(return_raw, global)] - pub fn user_id(account: &mut RhaiAccount, user_id: INT) -> Result> { + pub fn user_id( + account: &mut RhaiAccount, + user_id: INT, + ) -> Result> { let user_id = id_from_i64_to_u32(user_id)?; let mut acc = mem::take(account); acc = acc.user_id(user_id); @@ -198,7 +220,10 @@ mod account_module { } #[rhai_fn(return_raw, global)] - pub fn description(account: &mut RhaiAccount, description: String) -> Result> { + pub fn description( + account: &mut RhaiAccount, + description: String, + ) -> Result> { let mut acc = mem::take(account); acc = acc.description(description); *account = acc; @@ -206,7 +231,10 @@ mod account_module { } #[rhai_fn(return_raw, global)] - pub fn ledger(account: &mut RhaiAccount, ledger: String) -> Result> { + pub fn ledger( + account: &mut RhaiAccount, + ledger: String, + ) -> Result> { let mut acc = mem::take(account); acc = acc.ledger(ledger); *account = acc; @@ -214,7 +242,10 @@ mod account_module { } #[rhai_fn(return_raw, global)] - pub fn address(account: &mut RhaiAccount, address: String) -> Result> { + pub fn address( + account: &mut RhaiAccount, + address: String, + ) -> Result> { let mut acc = mem::take(account); acc = acc.address(address); *account = acc; @@ -222,7 +253,10 @@ mod account_module { } #[rhai_fn(return_raw, global)] - pub fn pubkey(account: &mut RhaiAccount, pubkey: String) -> Result> { + pub fn pubkey( + account: &mut RhaiAccount, + pubkey: String, + ) -> Result> { let mut acc = mem::take(account); acc = acc.pubkey(pubkey); *account = acc; @@ -230,7 +264,10 @@ mod account_module { } #[rhai_fn(return_raw, global)] - pub fn add_asset(account: &mut RhaiAccount, asset_id: INT) -> Result> { + pub fn add_asset( + account: &mut RhaiAccount, + asset_id: INT, + ) -> Result> { let asset_id = id_from_i64_to_u32(asset_id)?; let mut acc = mem::take(account); acc = acc.add_asset(asset_id); @@ -301,7 +338,10 @@ mod asset_module { } #[rhai_fn(return_raw, global)] - pub fn description(asset: &mut RhaiAsset, description: String) -> Result> { + pub fn description( + asset: &mut RhaiAsset, + description: String, + ) -> Result> { let mut ast = mem::take(asset); ast = ast.description(description); *asset = ast; @@ -317,7 +357,10 @@ mod asset_module { } #[rhai_fn(return_raw, global)] - pub fn address(asset: &mut RhaiAsset, address: String) -> Result> { + pub fn address( + asset: &mut RhaiAsset, + address: String, + ) -> Result> { let mut ast = mem::take(asset); ast = ast.address(address); *asset = ast; @@ -325,7 +368,10 @@ mod asset_module { } #[rhai_fn(return_raw, global)] - pub fn asset_type(asset: &mut RhaiAsset, asset_type_str: String) -> Result> { + pub fn asset_type( + asset: &mut RhaiAsset, + asset_type_str: String, + ) -> Result> { let asset_type_enum = string_to_asset_type(&asset_type_str)?; let mut ast = mem::take(asset); ast = ast.asset_type(asset_type_enum); @@ -338,7 +384,7 @@ mod asset_module { if decimals < 0 || decimals > 255 { return Err(Box::new(EvalAltResult::ErrorArithmetic( format!("Decimals value must be between 0 and 255, got {}", decimals).into(), - Position::NONE + Position::NONE, ))); } let mut ast = mem::take(asset); @@ -441,7 +487,10 @@ mod listing_module { // Setters using builder pattern with proper mutability handling #[rhai_fn(return_raw, global)] - pub fn seller_id(listing: &mut RhaiListing, seller_id: INT) -> Result> { + pub fn seller_id( + listing: &mut RhaiListing, + seller_id: INT, + ) -> Result> { let seller_id = id_from_i64_to_u32(seller_id)?; let mut lst = mem::take(listing); lst = lst.seller_id(seller_id); @@ -450,7 +499,10 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn asset_id(listing: &mut RhaiListing, asset_id: INT) -> Result> { + pub fn asset_id( + listing: &mut RhaiListing, + asset_id: INT, + ) -> Result> { let asset_id = id_from_i64_to_u32(asset_id)?; let mut lst = mem::take(listing); lst = lst.asset_id(asset_id); @@ -467,7 +519,10 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn currency(listing: &mut RhaiListing, currency: String) -> Result> { + pub fn currency( + listing: &mut RhaiListing, + currency: String, + ) -> Result> { let mut lst = mem::take(listing); lst = lst.currency(currency); *listing = lst; @@ -475,7 +530,10 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn listing_type(listing: &mut RhaiListing, listing_type_str: String) -> Result> { + pub fn listing_type( + listing: &mut RhaiListing, + listing_type_str: String, + ) -> Result> { let listing_type_enum = string_to_listing_type(&listing_type_str)?; let mut lst = mem::take(listing); lst = lst.listing_type(listing_type_enum); @@ -484,7 +542,10 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn title(listing: &mut RhaiListing, title: String) -> Result> { + pub fn title( + listing: &mut RhaiListing, + title: String, + ) -> Result> { let mut lst = mem::take(listing); lst = lst.title(title); *listing = lst; @@ -492,7 +553,10 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn description(listing: &mut RhaiListing, description: String) -> Result> { + pub fn description( + listing: &mut RhaiListing, + description: String, + ) -> Result> { let mut lst = mem::take(listing); lst = lst.description(description); *listing = lst; @@ -500,7 +564,10 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn image_url(listing: &mut RhaiListing, image_url: String) -> Result> { + pub fn image_url( + listing: &mut RhaiListing, + image_url: String, + ) -> Result> { let mut lst = mem::take(listing); lst = lst.image_url(Some(image_url)); *listing = lst; @@ -508,15 +575,21 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn expires_at(listing: &mut RhaiListing, end_date_ts: INT) -> Result> { + pub fn expires_at( + listing: &mut RhaiListing, + end_date_ts: INT, + ) -> Result> { use chrono::TimeZone; - let end_date = chrono::Utc.timestamp_opt(end_date_ts, 0) + let end_date = chrono::Utc + .timestamp_opt(end_date_ts, 0) .single() - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime( - format!("Invalid timestamp: {}", end_date_ts).into(), - Position::NONE - )))?; - + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Invalid timestamp: {}", end_date_ts).into(), + Position::NONE, + )) + })?; + let mut lst = mem::take(listing); lst = lst.expires_at(Some(end_date)); *listing = lst; @@ -524,7 +597,10 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn add_tag(listing: &mut RhaiListing, tag: String) -> Result> { + pub fn add_tag( + listing: &mut RhaiListing, + tag: String, + ) -> Result> { let mut lst = mem::take(listing); lst = lst.add_tag(tag); *listing = lst; @@ -532,44 +608,50 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn add_bid(listing: &mut RhaiListing, bid: RhaiBid) -> Result> { + pub fn add_bid( + listing: &mut RhaiListing, + bid: RhaiBid, + ) -> Result> { let lst = mem::take(listing); match lst.clone().add_bid(bid) { Ok(updated_lst) => { *listing = updated_lst; Ok(listing.clone()) - }, + } Err(err) => { // Put back the original listing since the add_bid failed *listing = lst; Err(Box::new(EvalAltResult::ErrorRuntime( format!("Failed to add bid: {}", err).into(), - Position::NONE + Position::NONE, ))) } } } #[rhai_fn(return_raw, global)] - pub fn complete_sale(listing: &mut RhaiListing, buyer_id: INT) -> Result> { + pub fn complete_sale( + listing: &mut RhaiListing, + buyer_id: INT, + ) -> Result> { let buyer_id = id_from_i64_to_u32(buyer_id)?; let lst = mem::take(listing); - + // First set the buyer_id and sale_price let lst_with_buyer = lst.clone().buyer_id(buyer_id); - + // Now complete the sale match lst_with_buyer.complete_sale() { Ok(updated_lst) => { *listing = updated_lst; Ok(listing.clone()) - }, + } Err(err) => { // Put back the original listing since the complete_sale failed *listing = lst; Err(Box::new(EvalAltResult::ErrorRuntime( format!("Failed to complete sale: {}", err).into(), - Position::NONE + Position::NONE, ))) } } @@ -582,13 +664,13 @@ mod listing_module { Ok(updated_lst) => { *listing = updated_lst; Ok(listing.clone()) - }, + } Err(err) => { // Put back the original listing since the cancel failed *listing = lst; Err(Box::new(EvalAltResult::ErrorRuntime( format!("Failed to cancel listing: {}", err).into(), - Position::NONE + Position::NONE, ))) } } @@ -647,7 +729,10 @@ mod bid_module { // Setters using builder pattern with proper mutability handling #[rhai_fn(return_raw, global)] - pub fn listing_id(bid: &mut RhaiBid, listing_id: String) -> Result> { + pub fn listing_id( + bid: &mut RhaiBid, + listing_id: String, + ) -> Result> { let mut b = mem::take(bid); b = b.listing_id(listing_id); *bid = b; @@ -680,7 +765,10 @@ mod bid_module { } #[rhai_fn(return_raw, global)] - pub fn update_status(bid: &mut RhaiBid, status_str: String) -> Result> { + pub fn update_status( + bid: &mut RhaiBid, + status_str: String, + ) -> Result> { let status = string_to_bid_status(&status_str)?; let mut b = mem::take(bid); b = b.status(status); @@ -706,13 +794,21 @@ pub fn register_finance_rhai_module(engine: &mut Engine, db: Arc) { engine.register_global_module(bid_module.into()); // --- Global Helper Functions (Enum conversions) --- - engine.register_fn("str_to_asset_type", |s: ImmutableString| self::string_to_asset_type(s.as_str())); + engine.register_fn("str_to_asset_type", |s: ImmutableString| { + self::string_to_asset_type(s.as_str()) + }); engine.register_fn("asset_type_to_str", self::asset_type_to_string); - engine.register_fn("str_to_listing_status", |s: ImmutableString| self::string_to_listing_status(s.as_str())); + engine.register_fn("str_to_listing_status", |s: ImmutableString| { + self::string_to_listing_status(s.as_str()) + }); engine.register_fn("listing_status_to_str", self::listing_status_to_string); - engine.register_fn("str_to_listing_type", |s: ImmutableString| self::string_to_listing_type(s.as_str())); + engine.register_fn("str_to_listing_type", |s: ImmutableString| { + self::string_to_listing_type(s.as_str()) + }); engine.register_fn("listing_type_to_str", self::listing_type_to_string); - engine.register_fn("str_to_bid_status", |s: ImmutableString| self::string_to_bid_status(s.as_str())); + engine.register_fn("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); // --- Database interaction functions (registered in a separate db_module) --- @@ -720,68 +816,158 @@ pub fn register_finance_rhai_module(engine: &mut Engine, db: Arc) { // Account DB functions let db_set_account = Arc::clone(&db); - db_module.set_native_fn("set_account", move |account: Account| -> Result> { - let collection = db_set_account.collection::().map_err(|e| - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account collection: {:?}", e).into(), Position::NONE)) )?; - collection.set(&account) - .map(|(id, _)| id as INT) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Account: {:?}", e).into(), Position::NONE))) - }); + db_module.set_native_fn( + "set_account", + move |account: Account| -> Result> { + let collection = db_set_account.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Account collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection + .set(&account) + .map(|(id, _)| id as INT) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save Account: {:?}", e).into(), + Position::NONE, + )) + }) + }, + ); let db_get_account = Arc::clone(&db); - db_module.set_native_fn("get_account_by_id", move |id_rhai: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_rhai)?; - let collection = db_get_account.collection::().map_err(|e| - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account collection: {:?}", e).into(), Position::NONE)) )?; - collection.get_by_id(id_u32) - .map(|opt_account| opt_account.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_account_by_id", + move |id_rhai: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_rhai)?; + let collection = db_get_account.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Account collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection + .get_by_id(id_u32) + .map(|opt_account| { + opt_account + .map(Dynamic::from) + .unwrap_or_else(|| Dynamic::UNIT) + }) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Account with ID {}: {:?}", id_rhai, e).into(), + Position::NONE, + )) + }) + }, + ); // Asset DB functions let db_set_asset = Arc::clone(&db); - db_module.set_native_fn("set_asset", move |asset: Asset| -> Result> { - let collection = db_set_asset.collection::().map_err(|e| - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset collection: {:?}", e).into(), Position::NONE)) )?; - collection.set(&asset) - .map(|(id, _)| id as INT) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Asset: {:?}", e).into(), Position::NONE))) - }); + db_module.set_native_fn( + "set_asset", + move |asset: Asset| -> Result> { + let collection = db_set_asset.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Asset collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection + .set(&asset) + .map(|(id, _)| id as INT) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save Asset: {:?}", e).into(), + Position::NONE, + )) + }) + }, + ); let db_get_asset = Arc::clone(&db); - db_module.set_native_fn("get_asset_by_id", move |id_rhai: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_rhai)?; - let collection = db_get_asset.collection::().map_err(|e| - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset collection: {:?}", e).into(), Position::NONE)) )?; - collection.get_by_id(id_u32) - .map(|opt_asset| opt_asset.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_asset_by_id", + move |id_rhai: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_rhai)?; + let collection = db_get_asset.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Asset collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection + .get_by_id(id_u32) + .map(|opt_asset| { + opt_asset + .map(Dynamic::from) + .unwrap_or_else(|| Dynamic::UNIT) + }) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Asset with ID {}: {:?}", id_rhai, e).into(), + Position::NONE, + )) + }) + }, + ); // Listing DB functions let db_set_listing = Arc::clone(&db); - db_module.set_native_fn("set_listing", move |listing: Listing| -> Result> { - let collection = db_set_listing.collection::().map_err(|e| - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing collection: {:?}", e).into(), Position::NONE)) )?; - collection.set(&listing) - .map(|(id, _)| id as INT) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Listing: {:?}", e).into(), Position::NONE))) - }); + db_module.set_native_fn( + "set_listing", + move |listing: Listing| -> Result> { + let collection = db_set_listing.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Listing collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection + .set(&listing) + .map(|(id, _)| id as INT) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save Listing: {:?}", e).into(), + Position::NONE, + )) + }) + }, + ); let db_get_listing = Arc::clone(&db); - db_module.set_native_fn("get_listing_by_id", move |id_rhai: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_rhai)?; - let collection = db_get_listing.collection::().map_err(|e| - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing collection: {:?}", e).into(), Position::NONE)) )?; - collection.get_by_id(id_u32) - .map(|opt_listing| opt_listing.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_listing_by_id", + move |id_rhai: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_rhai)?; + let collection = db_get_listing.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Listing collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection + .get_by_id(id_u32) + .map(|opt_listing| { + opt_listing + .map(Dynamic::from) + .unwrap_or_else(|| Dynamic::UNIT) + }) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Listing with ID {}: {:?}", id_rhai, e).into(), + Position::NONE, + )) + }) + }, + ); engine.register_global_module(db_module.into()); // Global timestamp function for scripts to get current time engine.register_fn("timestamp", || Utc::now().timestamp()); - + println!("Successfully registered finance Rhai module."); } diff --git a/heromodels/src/models/flow/flow.rs b/heromodels/src/models/flow/flow.rs index c586df4..28d16f9 100644 --- a/heromodels/src/models/flow/flow.rs +++ b/heromodels/src/models/flow/flow.rs @@ -1,7 +1,7 @@ +use super::flow_step::FlowStep; use heromodels_core::BaseModelData; use heromodels_derive::model; use serde::{Deserialize, Serialize}; -use super::flow_step::FlowStep; /// Represents a signing flow. #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)] @@ -33,7 +33,7 @@ impl Flow { Self { base_data: BaseModelData::new(), flow_uuid: flow_uuid.to_string(), - name: String::new(), // Default name, to be set by builder + name: String::new(), // Default name, to be set by builder status: String::from("Pending"), // Default status, to be set by builder steps: Vec::new(), } diff --git a/heromodels/src/models/flow/mod.rs b/heromodels/src/models/flow/mod.rs index f729594..8dd2206 100644 --- a/heromodels/src/models/flow/mod.rs +++ b/heromodels/src/models/flow/mod.rs @@ -1,11 +1,11 @@ // Export flow model submodules pub mod flow; pub mod flow_step; -pub mod signature_requirement; pub mod rhai; +pub mod signature_requirement; // Re-export key types for convenience pub use flow::Flow; pub use flow_step::FlowStep; -pub use signature_requirement::SignatureRequirement; pub use rhai::register_flow_rhai_module; +pub use signature_requirement::SignatureRequirement; diff --git a/heromodels/src/models/flow/rhai.rs b/heromodels/src/models/flow/rhai.rs index b0ea9eb..03f96b9 100644 --- a/heromodels/src/models/flow/rhai.rs +++ b/heromodels/src/models/flow/rhai.rs @@ -1,7 +1,7 @@ use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; -use std::sync::Arc; +use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; use std::mem; +use std::sync::Arc; use super::flow::Flow; use super::flow_step::FlowStep; @@ -9,18 +9,18 @@ use super::signature_requirement::SignatureRequirement; type RhaiFlow = Flow; type RhaiFlowStep = FlowStep; type RhaiSignatureRequirement = SignatureRequirement; -use crate::db::hero::OurDB; use crate::db::Collection; use crate::db::Db; +use crate::db::hero::OurDB; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE + Position::NONE, )) - ) + }) } #[export_module] @@ -30,7 +30,7 @@ mod rhai_flow_module { pub fn new_flow(flow_uuid: String) -> RhaiFlow { Flow::new(flow_uuid) } - + /// Sets the flow name #[rhai_fn(name = "name", return_raw, global, pure)] pub fn flow_name(flow: &mut RhaiFlow, name: String) -> Result> { @@ -38,41 +38,63 @@ mod rhai_flow_module { *flow = owned_flow.name(name); Ok(flow.clone()) } - + /// Sets the flow status #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn flow_status(flow: &mut RhaiFlow, status: String) -> Result> { + pub fn flow_status( + flow: &mut RhaiFlow, + status: String, + ) -> Result> { let owned_flow = mem::replace(flow, Flow::new("")); // Dummy for replacement *flow = owned_flow.status(status); Ok(flow.clone()) } - + /// Adds a step to the flow #[rhai_fn(name = "add_step", return_raw, global, pure)] - pub fn flow_add_step(flow: &mut RhaiFlow, step: RhaiFlowStep) -> Result> { + pub fn flow_add_step( + flow: &mut RhaiFlow, + step: RhaiFlowStep, + ) -> Result> { let owned_flow = mem::replace(flow, Flow::new("")); // Dummy for replacement *flow = owned_flow.add_step(step); Ok(flow.clone()) } - + // Flow Getters #[rhai_fn(get = "id", pure)] - pub fn get_id(flow: &mut RhaiFlow) -> i64 { flow.base_data.id as i64 } + pub fn get_id(flow: &mut RhaiFlow) -> i64 { + flow.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_created_at(flow: &mut RhaiFlow) -> i64 { flow.base_data.created_at } + pub fn get_created_at(flow: &mut RhaiFlow) -> i64 { + flow.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_modified_at(flow: &mut RhaiFlow) -> i64 { flow.base_data.modified_at } + pub fn get_modified_at(flow: &mut RhaiFlow) -> i64 { + flow.base_data.modified_at + } #[rhai_fn(get = "flow_uuid", pure)] - pub fn get_flow_uuid(flow: &mut RhaiFlow) -> String { flow.flow_uuid.clone() } + pub fn get_flow_uuid(flow: &mut RhaiFlow) -> String { + flow.flow_uuid.clone() + } #[rhai_fn(get = "name", pure)] - pub fn get_name(flow: &mut RhaiFlow) -> String { flow.name.clone() } + pub fn get_name(flow: &mut RhaiFlow) -> String { + flow.name.clone() + } #[rhai_fn(get = "status", pure)] - pub fn get_status(flow: &mut RhaiFlow) -> String { flow.status.clone() } + pub fn get_status(flow: &mut RhaiFlow) -> String { + flow.status.clone() + } #[rhai_fn(get = "steps", pure)] pub fn get_steps(flow: &mut RhaiFlow) -> Array { - flow.steps.iter().cloned().map(Dynamic::from).collect::() + flow.steps + .iter() + .cloned() + .map(Dynamic::from) + .collect::() } - + // --- FlowStep Functions --- #[rhai_fn(global)] pub fn new_flow_step(step_order_i64: i64) -> Dynamic { @@ -81,34 +103,46 @@ mod rhai_flow_module { let mut flow_step = FlowStep::default(); flow_step.step_order = step_order; Dynamic::from(flow_step) - }, - Err(err) => Dynamic::from(err.to_string()) + } + Err(err) => Dynamic::from(err.to_string()), } } - + /// Sets the flow step description #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn flow_step_description(step: &mut RhaiFlowStep, description: String) -> Result> { + pub fn flow_step_description( + step: &mut RhaiFlowStep, + description: String, + ) -> Result> { let owned_step = mem::replace(step, FlowStep::default()); // Use Default trait *step = owned_step.description(description); Ok(step.clone()) } - + /// Sets the flow step status #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn flow_step_status(step: &mut RhaiFlowStep, status: String) -> Result> { + pub fn flow_step_status( + step: &mut RhaiFlowStep, + status: String, + ) -> Result> { let owned_step = mem::replace(step, FlowStep::default()); // Use Default trait *step = owned_step.status(status); Ok(step.clone()) } - + // FlowStep Getters #[rhai_fn(get = "id", pure)] - pub fn get_step_id(step: &mut RhaiFlowStep) -> i64 { step.base_data.id as i64 } + pub fn get_step_id(step: &mut RhaiFlowStep) -> i64 { + step.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_step_created_at(step: &mut RhaiFlowStep) -> i64 { step.base_data.created_at } + pub fn get_step_created_at(step: &mut RhaiFlowStep) -> i64 { + step.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_step_modified_at(step: &mut RhaiFlowStep) -> i64 { step.base_data.modified_at } + pub fn get_step_modified_at(step: &mut RhaiFlowStep) -> i64 { + step.base_data.modified_at + } #[rhai_fn(get = "description", pure)] pub fn get_step_description(step: &mut RhaiFlowStep) -> Dynamic { match &step.description { @@ -117,14 +151,22 @@ mod rhai_flow_module { } } #[rhai_fn(get = "step_order", pure)] - pub fn get_step_order(step: &mut RhaiFlowStep) -> i64 { step.step_order as i64 } + pub fn get_step_order(step: &mut RhaiFlowStep) -> i64 { + step.step_order as i64 + } #[rhai_fn(get = "status", pure)] - pub fn get_step_status(step: &mut RhaiFlowStep) -> String { step.status.clone() } - + pub fn get_step_status(step: &mut RhaiFlowStep) -> String { + step.status.clone() + } + // --- SignatureRequirement Functions --- /// Create a new signature requirement #[rhai_fn(global)] - pub fn new_signature_requirement(flow_step_id_i64: i64, public_key: String, message: String) -> Dynamic { + pub fn new_signature_requirement( + flow_step_id_i64: i64, + public_key: String, + message: String, + ) -> Dynamic { match id_from_i64_to_u32(flow_step_id_i64) { Ok(flow_step_id) => { let mut signature_requirement = SignatureRequirement::default(); @@ -132,48 +174,69 @@ mod rhai_flow_module { signature_requirement.public_key = public_key; signature_requirement.message = message; Dynamic::from(signature_requirement) - }, - Err(err) => Dynamic::from(err.to_string()) + } + Err(err) => Dynamic::from(err.to_string()), } } - + /// Sets the signed_by field #[rhai_fn(name = "signed_by", return_raw, global, pure)] - pub fn signature_requirement_signed_by(sr: &mut RhaiSignatureRequirement, signed_by: String) -> Result> { + pub fn signature_requirement_signed_by( + sr: &mut RhaiSignatureRequirement, + signed_by: String, + ) -> Result> { let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait *sr = owned_sr.signed_by(signed_by); Ok(sr.clone()) } - + /// Sets the signature field #[rhai_fn(name = "signature", return_raw, global, pure)] - pub fn signature_requirement_signature(sr: &mut RhaiSignatureRequirement, signature: String) -> Result> { + pub fn signature_requirement_signature( + sr: &mut RhaiSignatureRequirement, + signature: String, + ) -> Result> { let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait *sr = owned_sr.signature(signature); Ok(sr.clone()) } - + /// Sets the status field #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn signature_requirement_status(sr: &mut RhaiSignatureRequirement, status: String) -> Result> { + pub fn signature_requirement_status( + sr: &mut RhaiSignatureRequirement, + status: String, + ) -> Result> { let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait *sr = owned_sr.status(status); Ok(sr.clone()) } - + // SignatureRequirement Getters #[rhai_fn(get = "id", pure)] - pub fn get_sr_id(sr: &mut RhaiSignatureRequirement) -> i64 { sr.base_data.id as i64 } + pub fn get_sr_id(sr: &mut RhaiSignatureRequirement) -> i64 { + sr.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_sr_created_at(sr: &mut RhaiSignatureRequirement) -> i64 { sr.base_data.created_at } + pub fn get_sr_created_at(sr: &mut RhaiSignatureRequirement) -> i64 { + sr.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_sr_modified_at(sr: &mut RhaiSignatureRequirement) -> i64 { sr.base_data.modified_at } + pub fn get_sr_modified_at(sr: &mut RhaiSignatureRequirement) -> i64 { + sr.base_data.modified_at + } #[rhai_fn(get = "flow_step_id", pure)] - pub fn get_sr_flow_step_id(sr: &mut RhaiSignatureRequirement) -> i64 { sr.flow_step_id as i64 } + pub fn get_sr_flow_step_id(sr: &mut RhaiSignatureRequirement) -> i64 { + sr.flow_step_id as i64 + } #[rhai_fn(get = "public_key", pure)] - pub fn get_sr_public_key(sr: &mut RhaiSignatureRequirement) -> String { sr.public_key.clone() } + pub fn get_sr_public_key(sr: &mut RhaiSignatureRequirement) -> String { + sr.public_key.clone() + } #[rhai_fn(get = "message", pure)] - pub fn get_sr_message(sr: &mut RhaiSignatureRequirement) -> String { sr.message.clone() } + pub fn get_sr_message(sr: &mut RhaiSignatureRequirement) -> String { + sr.message.clone() + } #[rhai_fn(get = "signed_by", pure)] pub fn get_sr_signed_by(sr: &mut RhaiSignatureRequirement) -> Dynamic { match &sr.signed_by { @@ -189,185 +252,284 @@ mod rhai_flow_module { } } #[rhai_fn(get = "status", pure)] - pub fn get_sr_status(sr: &mut RhaiSignatureRequirement) -> String { sr.status.clone() } + pub fn get_sr_status(sr: &mut RhaiSignatureRequirement) -> String { + sr.status.clone() + } } /// Register the flow module with the Rhai engine pub fn register_flow_rhai_module(engine: &mut Engine, db: Arc) { // Create a module for database functions let mut db_module = Module::new(); - + // Flow database functions let db_clone = Arc::clone(&db); - db_module.set_native_fn("save_flow", move |flow: Flow| -> Result> { - // Use the Collection trait method directly - let result = db_clone.set(&flow) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_flow: {:?}", e).into(), Position::NONE)))?; - - // Return the updated flow with the correct ID - Ok(result.1) - }); - + db_module.set_native_fn( + "save_flow", + move |flow: Flow| -> Result> { + // Use the Collection trait method directly + let result = db_clone.set(&flow).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error save_flow: {:?}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated flow with the correct ID + Ok(result.1) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("get_flow_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_flow_by_id: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Flow with ID {} not found", id_u32).into(), Position::NONE))) - }); - + db_module.set_native_fn( + "get_flow_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_flow_by_id: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Flow with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("delete_flow", move |id_i64: INT| -> Result<(), Box> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - let collection = db_clone.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get flow collection: {:?}", e).into(), - Position::NONE - )))?; - collection.delete_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to delete Flow (ID: {}): {:?}", id_u32, e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "delete_flow", + move |id_i64: INT| -> Result<(), Box> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + let collection = db_clone.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get flow collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection.delete_by_id(id_u32).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to delete Flow (ID: {}): {:?}", id_u32, e).into(), + Position::NONE, + )) + }) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("list_flows", move || -> Result> { - let collection = db_clone.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get flow collection: {:?}", e).into(), - Position::NONE - )))?; - let flows = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all flows: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for flow in flows { - array.push(Dynamic::from(flow)); - } - Ok(Dynamic::from(array)) - }); - + db_module.set_native_fn( + "list_flows", + move || -> Result> { + let collection = db_clone.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get flow collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let flows = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all flows: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for flow in flows { + array.push(Dynamic::from(flow)); + } + Ok(Dynamic::from(array)) + }, + ); + // FlowStep database functions let db_clone = Arc::clone(&db); - db_module.set_native_fn("save_flow_step", move |step: FlowStep| -> Result> { - // Use the Collection trait method directly - let result = db_clone.set(&step) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_flow_step: {:?}", e).into(), Position::NONE)))?; - - // Return the updated flow step with the correct ID - Ok(result.1) - }); - + db_module.set_native_fn( + "save_flow_step", + move |step: FlowStep| -> Result> { + // Use the Collection trait method directly + let result = db_clone.set(&step).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error save_flow_step: {:?}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated flow step with the correct ID + Ok(result.1) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("get_flow_step_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_flow_step_by_id: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("FlowStep with ID {} not found", id_u32).into(), Position::NONE))) - }); - + db_module.set_native_fn( + "get_flow_step_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_flow_step_by_id: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("FlowStep with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("delete_flow_step", move |id_i64: INT| -> Result<(), Box> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - let collection = db_clone.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get flow step collection: {:?}", e).into(), - Position::NONE - )))?; - collection.delete_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to delete FlowStep (ID: {}): {:?}", id_u32, e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "delete_flow_step", + move |id_i64: INT| -> Result<(), Box> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + let collection = db_clone.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get flow step collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection.delete_by_id(id_u32).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to delete FlowStep (ID: {}): {:?}", id_u32, e).into(), + Position::NONE, + )) + }) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("list_flow_steps", move || -> Result> { - let collection = db_clone.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get flow step collection: {:?}", e).into(), - Position::NONE - )))?; - let steps = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all flow steps: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for step in steps { - array.push(Dynamic::from(step)); - } - Ok(Dynamic::from(array)) - }); - + db_module.set_native_fn( + "list_flow_steps", + move || -> Result> { + let collection = db_clone.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get flow step collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let steps = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all flow steps: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for step in steps { + array.push(Dynamic::from(step)); + } + Ok(Dynamic::from(array)) + }, + ); + // SignatureRequirement database functions let db_clone = Arc::clone(&db); - db_module.set_native_fn("save_signature_requirement", move |sr: SignatureRequirement| -> Result> { - // Use the Collection trait method directly - let result = db_clone.set(&sr) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_signature_requirement: {:?}", e).into(), Position::NONE)))?; - - // Return the updated signature requirement with the correct ID - Ok(result.1) - }); - + db_module.set_native_fn( + "save_signature_requirement", + move |sr: SignatureRequirement| -> Result> { + // Use the Collection trait method directly + let result = db_clone.set(&sr).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error save_signature_requirement: {:?}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated signature requirement with the correct ID + Ok(result.1) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("get_signature_requirement_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_signature_requirement_by_id: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("SignatureRequirement with ID {} not found", id_u32).into(), Position::NONE))) - }); - + db_module.set_native_fn( + "get_signature_requirement_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_signature_requirement_by_id: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("SignatureRequirement with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("delete_signature_requirement", move |id_i64: INT| -> Result<(), Box> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - let collection = db_clone.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get signature requirement collection: {:?}", e).into(), - Position::NONE - )))?; - collection.delete_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to delete SignatureRequirement (ID: {}): {:?}", id_u32, e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "delete_signature_requirement", + move |id_i64: INT| -> Result<(), Box> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + let collection = db_clone.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get signature requirement collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection.delete_by_id(id_u32).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!( + "Failed to delete SignatureRequirement (ID: {}): {:?}", + id_u32, e + ) + .into(), + Position::NONE, + )) + }) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("list_signature_requirements", move || -> Result> { - let collection = db_clone.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get signature requirement collection: {:?}", e).into(), - Position::NONE - )))?; - let srs = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all signature requirements: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for sr in srs { - array.push(Dynamic::from(sr)); - } - Ok(Dynamic::from(array)) - }); - + db_module.set_native_fn( + "list_signature_requirements", + move || -> Result> { + let collection = db_clone.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get signature requirement collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let srs = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all signature requirements: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for sr in srs { + array.push(Dynamic::from(sr)); + } + Ok(Dynamic::from(array)) + }, + ); + // Register the database module globally engine.register_static_module("db", db_module.into()); - + // Register the flow module using exported_module! macro let module = exported_module!(rhai_flow_module); engine.register_global_module(module.into()); - + println!("Flow Rhai module registered."); } diff --git a/heromodels/src/models/flow/signature_requirement.rs b/heromodels/src/models/flow/signature_requirement.rs index 05093f6..453b99d 100644 --- a/heromodels/src/models/flow/signature_requirement.rs +++ b/heromodels/src/models/flow/signature_requirement.rs @@ -32,7 +32,12 @@ 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(), flow_step_id, diff --git a/heromodels/src/models/governance/mod.rs b/heromodels/src/models/governance/mod.rs index 5570233..e15c0f0 100644 --- a/heromodels/src/models/governance/mod.rs +++ b/heromodels/src/models/governance/mod.rs @@ -4,5 +4,5 @@ pub mod proposal; pub mod attached_file; -pub use self::proposal::{Proposal, Ballot, VoteOption, ProposalStatus, VoteEventStatus}; -pub use self::attached_file::AttachedFile; \ No newline at end of file +pub use self::attached_file::AttachedFile; +pub use self::proposal::{Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption}; diff --git a/heromodels/src/models/governance/proposal.rs b/heromodels/src/models/governance/proposal.rs index 8e9a1a3..6d64d31 100644 --- a/heromodels/src/models/governance/proposal.rs +++ b/heromodels/src/models/governance/proposal.rs @@ -5,9 +5,9 @@ use heromodels_derive::model; // For #[model] use rhai::{CustomType, TypeBuilder}; use serde::{Deserialize, Serialize}; -use heromodels_core::BaseModelData; -use crate::models::core::Comment; use super::AttachedFile; +use crate::models::core::Comment; +use heromodels_core::BaseModelData; // --- Enums --- @@ -72,9 +72,9 @@ impl VoteOption { #[model] // Has base.Base in V spec pub struct Ballot { pub base_data: BaseModelData, - pub user_id: u32, // The ID of the user who cast this ballot - pub vote_option_id: u8, // The 'id' of the VoteOption chosen - pub shares_count: i64, // Number of shares/tokens/voting power + pub user_id: u32, // The ID of the user who cast this ballot + pub vote_option_id: u8, // The 'id' of the VoteOption chosen + pub shares_count: i64, // Number of shares/tokens/voting power pub comment: Option, // Optional comment from the voter } @@ -281,7 +281,7 @@ impl Proposal { eprintln!("Voting is not open for proposal '{}'", self.title); return self; } - + // Check if the option exists if !self.options.iter().any(|opt| opt.id == chosen_option_id) { eprintln!( @@ -290,7 +290,7 @@ impl Proposal { ); return self; } - + // Check eligibility for private proposals if let Some(group) = &self.private_group { if !group.contains(&user_id) { @@ -301,14 +301,14 @@ impl Proposal { return self; } } - + // Create a new ballot with the comment let mut new_ballot = Ballot::new(ballot_id, user_id, chosen_option_id, shares); new_ballot.comment = Some(comment.to_string()); - + // Add the ballot to the proposal self.ballots.push(new_ballot); - + // Update the vote count for the chosen option if let Some(option) = self .options @@ -317,7 +317,7 @@ impl Proposal { { option.count += shares; } - + self } } diff --git a/heromodels/src/models/legal/contract.rs b/heromodels/src/models/legal/contract.rs index f6cdd79..25fcbc9 100644 --- a/heromodels/src/models/legal/contract.rs +++ b/heromodels/src/models/legal/contract.rs @@ -1,7 +1,7 @@ use heromodels_core::BaseModelData; use heromodels_derive::model; -use std::fmt; use serde::{Deserialize, Serialize}; +use std::fmt; // --- Enums --- @@ -109,7 +109,7 @@ impl ContractSigner { self.signed_at = Some(signed_at); self } - + pub fn clear_signed_at(mut self) -> Self { self.signed_at = None; self @@ -139,21 +139,21 @@ pub struct Contract { pub title: String, pub description: String, - + #[index] pub contract_type: String, - + #[index] pub status: crate::models::ContractStatus, // Use re-exported path for #[model] macro - + pub created_by: String, pub terms_and_conditions: String, - + pub start_date: Option, pub end_date: Option, pub renewal_period_days: Option, pub next_renewal_date: Option, - + pub signers: Vec, pub revisions: Vec, pub current_version: u32, @@ -217,7 +217,7 @@ impl Contract { self.start_date = Some(start_date); self } - + pub fn clear_start_date(mut self) -> Self { self.start_date = None; self @@ -257,7 +257,7 @@ impl Contract { self.signers.push(signer); self } - + pub fn signers(mut self, signers: Vec) -> Self { self.signers = signers; self @@ -272,7 +272,7 @@ impl Contract { self.revisions = revisions; self } - + pub fn current_version(mut self, version: u32) -> Self { self.current_version = version; self @@ -287,7 +287,7 @@ impl Contract { self.last_signed_date = None; self } - + // Example methods for state changes pub fn set_status(&mut self, status: crate::models::ContractStatus) { self.status = status; diff --git a/heromodels/src/models/legal/rhai.rs b/heromodels/src/models/legal/rhai.rs index d374d54..afb83e8 100644 --- a/heromodels/src/models/legal/rhai.rs +++ b/heromodels/src/models/legal/rhai.rs @@ -1,51 +1,60 @@ -use rhai::{ - Dynamic, Engine, EvalAltResult, NativeCallContext, Position, Module, Array, -}; +use rhai::{Array, Dynamic, Engine, EvalAltResult, Module, NativeCallContext, Position}; use std::sync::Arc; use crate::db::hero::OurDB; // Updated path based on compiler suggestion // use heromodels_core::BaseModelData; // Removed as fields are accessed via contract.base_data directly -use crate::models::legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus}; +use crate::models::legal::{ + Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus, +}; use crate::db::Collection; // Import the Collection trait // --- Helper Functions for ID and Timestamp Conversion --- -fn i64_to_u32(val: i64, context_pos: Position, field_name: &str, object_name: &str) -> Result> { +fn i64_to_u32( + val: i64, + context_pos: Position, + field_name: &str, + object_name: &str, +) -> Result> { val.try_into().map_err(|_e| { Box::new(EvalAltResult::ErrorArithmetic( format!( "Conversion error for field '{}' in object '{}': cannot convert i64 ({}) to u32", - field_name, - object_name, - val + field_name, object_name, val ), context_pos, )) }) } -fn i64_to_u64(val: i64, context_pos: Position, field_name: &str, object_name: &str) -> Result> { +fn i64_to_u64( + val: i64, + context_pos: Position, + field_name: &str, + object_name: &str, +) -> Result> { val.try_into().map_err(|_e| { Box::new(EvalAltResult::ErrorArithmetic( format!( "Conversion error for field '{}' in object '{}': cannot convert i64 ({}) to u64", - field_name, - object_name, - val + field_name, object_name, val ), context_pos, )) }) } -fn i64_to_i32(val: i64, context_pos: Position, field_name: &str, object_name: &str) -> Result> { +fn i64_to_i32( + val: i64, + context_pos: Position, + field_name: &str, + object_name: &str, +) -> Result> { val.try_into().map_err(|_e| { Box::new(EvalAltResult::ErrorArithmetic( format!( "Conversion error for field '{}' in object '{}': cannot convert i64 ({}) to i32", - field_name, - object_name, - val + field_name, object_name, val ), context_pos, )) @@ -73,193 +82,532 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { engine.register_static_module("SignerStatusConstants", signer_status_module.into()); engine.register_type_with_name::("SignerStatus"); // Expose the type itself - // --- ContractRevision --- + // --- ContractRevision --- engine.register_type_with_name::("ContractRevision"); engine.register_fn( "new_contract_revision", - move |context: NativeCallContext, version_i64: i64, content: String, created_at_i64: i64, created_by: String| -> Result> { - let version = i64_to_u32(version_i64, context.position(), "version", "new_contract_revision")?; - let created_at = i64_to_u64(created_at_i64, context.position(), "created_at", "new_contract_revision")?; - Ok(ContractRevision::new(version, content, created_at, created_by)) - } + move |context: NativeCallContext, + version_i64: i64, + content: String, + created_at_i64: i64, + created_by: String| + -> Result> { + let version = i64_to_u32( + version_i64, + context.call_position(), + "version", + "new_contract_revision", + )?; + let created_at = i64_to_u64( + created_at_i64, + context.call_position(), + "created_at", + "new_contract_revision", + )?; + Ok(ContractRevision::new( + version, content, created_at, created_by, + )) + }, + ); + engine.register_fn( + "comments", + |mut revision: ContractRevision, comments: String| -> ContractRevision { + revision.comments = Some(comments); + revision + }, + ); + engine.register_get( + "version", + |revision: &mut ContractRevision| -> Result> { + Ok(revision.version as i64) + }, + ); + engine.register_get( + "content", + |revision: &mut ContractRevision| -> Result> { + Ok(revision.content.clone()) + }, + ); + engine.register_get( + "created_at", + |revision: &mut ContractRevision| -> Result> { + Ok(revision.created_at as i64) + }, + ); + engine.register_get( + "created_by", + |revision: &mut ContractRevision| -> Result> { + Ok(revision.created_by.clone()) + }, + ); + engine.register_get( + "comments", + |revision: &mut ContractRevision| -> Result> { + Ok(revision + .comments + .clone() + .map_or(Dynamic::UNIT, Dynamic::from)) + }, ); - engine.register_fn("comments", |mut revision: ContractRevision, comments: String| -> ContractRevision { - revision.comments = Some(comments); - revision - }); - engine.register_get("version", |revision: &mut ContractRevision| -> Result> { Ok(revision.version as i64) }); - engine.register_get("content", |revision: &mut ContractRevision| -> Result> { Ok(revision.content.clone()) }); - engine.register_get("created_at", |revision: &mut ContractRevision| -> Result> { Ok(revision.created_at as i64) }); - engine.register_get("created_by", |revision: &mut ContractRevision| -> Result> { Ok(revision.created_by.clone()) }); - engine.register_get("comments", |revision: &mut ContractRevision| -> Result> { - Ok(revision.comments.clone().map_or(Dynamic::UNIT, Dynamic::from)) - }); - // --- ContractSigner --- + // --- ContractSigner --- engine.register_type_with_name::("ContractSigner"); engine.register_fn( "new_contract_signer", |id: String, name: String, email: String| -> ContractSigner { ContractSigner::new(id, name, email) - } + }, + ); + engine.register_fn( + "status", + |signer: ContractSigner, status: SignerStatus| -> ContractSigner { signer.status(status) }, + ); + engine.register_fn( + "signed_at", + |context: NativeCallContext, + signer: ContractSigner, + signed_at_i64: i64| + -> Result> { + let signed_at_u64 = i64_to_u64( + signed_at_i64, + context.call_position(), + "signed_at", + "ContractSigner.signed_at", + )?; + Ok(signer.signed_at(signed_at_u64)) + }, + ); + engine.register_fn( + "clear_signed_at", + |signer: ContractSigner| -> ContractSigner { signer.clear_signed_at() }, + ); + engine.register_fn( + "comments", + |signer: ContractSigner, comments: String| -> ContractSigner { signer.comments(comments) }, + ); + engine.register_fn( + "clear_comments", + |signer: ContractSigner| -> ContractSigner { signer.clear_comments() }, ); - engine.register_fn("status", |signer: ContractSigner, status: SignerStatus| -> ContractSigner { signer.status(status) }); - engine.register_fn("signed_at", |context: NativeCallContext, signer: ContractSigner, signed_at_i64: i64| -> Result> { - let signed_at_u64 = i64_to_u64(signed_at_i64, context.position(), "signed_at", "ContractSigner.signed_at")?; - Ok(signer.signed_at(signed_at_u64)) - }); - engine.register_fn("clear_signed_at", |signer: ContractSigner| -> ContractSigner { signer.clear_signed_at() }); - engine.register_fn("comments", |signer: ContractSigner, comments: String| -> ContractSigner { signer.comments(comments) }); - engine.register_fn("clear_comments", |signer: ContractSigner| -> ContractSigner { signer.clear_comments() }); - engine.register_get("id", |signer: &mut ContractSigner| -> Result> { Ok(signer.id.clone()) }); - engine.register_get("name", |signer: &mut ContractSigner| -> Result> { Ok(signer.name.clone()) }); - engine.register_get("email", |signer: &mut ContractSigner| -> Result> { Ok(signer.email.clone()) }); - engine.register_get("status", |signer: &mut ContractSigner| -> Result> { Ok(signer.status.clone()) }); - engine.register_get("signed_at_ts", |signer: &mut ContractSigner| -> Result> { - Ok(signer.signed_at.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }); - engine.register_get("comments", |signer: &mut ContractSigner| -> Result> { - Ok(signer.comments.clone().map_or(Dynamic::UNIT, Dynamic::from)) - }); - engine.register_get("signed_at", |signer: &mut ContractSigner| -> Result> { - Ok(signer.signed_at.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts))) - }); + engine.register_get( + "id", + |signer: &mut ContractSigner| -> Result> { + Ok(signer.id.clone()) + }, + ); + engine.register_get( + "name", + |signer: &mut ContractSigner| -> Result> { + Ok(signer.name.clone()) + }, + ); + engine.register_get( + "email", + |signer: &mut ContractSigner| -> Result> { + Ok(signer.email.clone()) + }, + ); + engine.register_get( + "status", + |signer: &mut ContractSigner| -> Result> { + Ok(signer.status.clone()) + }, + ); + engine.register_get( + "signed_at_ts", + |signer: &mut ContractSigner| -> Result> { + Ok(signer + .signed_at + .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) + }, + ); + engine.register_get( + "comments", + |signer: &mut ContractSigner| -> Result> { + Ok(signer.comments.clone().map_or(Dynamic::UNIT, Dynamic::from)) + }, + ); + engine.register_get( + "signed_at", + |signer: &mut ContractSigner| -> Result> { + Ok(signer + .signed_at + .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts))) + }, + ); - // --- Contract --- + // --- Contract --- engine.register_type_with_name::("Contract"); engine.register_fn( "new_contract", - move |context: NativeCallContext, base_id_i64: i64, contract_id: String| -> Result> { - let base_id = i64_to_u32(base_id_i64, context.position(), "base_id", "new_contract")?; + move |context: NativeCallContext, + base_id_i64: i64, + contract_id: String| + -> Result> { + let base_id = i64_to_u32( + base_id_i64, + context.call_position(), + "base_id", + "new_contract", + )?; Ok(Contract::new(base_id, contract_id)) - } + }, ); // Builder methods - engine.register_fn("title", |contract: Contract, title: String| -> Contract { contract.title(title) }); - engine.register_fn("description", |contract: Contract, description: String| -> Contract { contract.description(description) }); - engine.register_fn("contract_type", |contract: Contract, contract_type: String| -> Contract { contract.contract_type(contract_type) }); - engine.register_fn("status", |contract: Contract, status: ContractStatus| -> Contract { contract.status(status) }); - engine.register_fn("created_by", |contract: Contract, created_by: String| -> Contract { contract.created_by(created_by) }); - engine.register_fn("terms_and_conditions", |contract: Contract, terms: String| -> Contract { contract.terms_and_conditions(terms) }); - - engine.register_fn("start_date", |context: NativeCallContext, contract: Contract, start_date_i64: i64| -> Result> { - let start_date_u64 = i64_to_u64(start_date_i64, context.position(), "start_date", "Contract.start_date")?; - Ok(contract.start_date(start_date_u64)) + engine.register_fn("title", |contract: Contract, title: String| -> Contract { + contract.title(title) }); - engine.register_fn("clear_start_date", |contract: Contract| -> Contract { contract.clear_start_date() }); + engine.register_fn( + "description", + |contract: Contract, description: String| -> Contract { contract.description(description) }, + ); + engine.register_fn( + "contract_type", + |contract: Contract, contract_type: String| -> Contract { + contract.contract_type(contract_type) + }, + ); + engine.register_fn( + "status", + |contract: Contract, status: ContractStatus| -> Contract { contract.status(status) }, + ); + engine.register_fn( + "created_by", + |contract: Contract, created_by: String| -> Contract { contract.created_by(created_by) }, + ); + engine.register_fn( + "terms_and_conditions", + |contract: Contract, terms: String| -> Contract { contract.terms_and_conditions(terms) }, + ); - engine.register_fn("end_date", |context: NativeCallContext, contract: Contract, end_date_i64: i64| -> Result> { - let end_date_u64 = i64_to_u64(end_date_i64, context.position(), "end_date", "Contract.end_date")?; - Ok(contract.end_date(end_date_u64)) - }); - engine.register_fn("clear_end_date", |contract: Contract| -> Contract { contract.clear_end_date() }); - - engine.register_fn("renewal_period_days", |context: NativeCallContext, contract: Contract, days_i64: i64| -> Result> { - let days_i32 = i64_to_i32(days_i64, context.position(), "renewal_period_days", "Contract.renewal_period_days")?; - Ok(contract.renewal_period_days(days_i32)) - }); - engine.register_fn("clear_renewal_period_days", |contract: Contract| -> Contract { contract.clear_renewal_period_days() }); - - engine.register_fn("next_renewal_date", |context: NativeCallContext, contract: Contract, date_i64: i64| -> Result> { - let date_u64 = i64_to_u64(date_i64, context.position(), "next_renewal_date", "Contract.next_renewal_date")?; - Ok(contract.next_renewal_date(date_u64)) - }); - engine.register_fn("clear_next_renewal_date", |contract: Contract| -> Contract { contract.clear_next_renewal_date() }); - - engine.register_fn("add_signer", |contract: Contract, signer: ContractSigner| -> Contract { contract.add_signer(signer) }); - engine.register_fn("signers", |contract: Contract, signers_array: Array| -> Contract { - let signers_vec = signers_array.into_iter().filter_map(|s| s.try_cast::()).collect(); - contract.signers(signers_vec) + engine.register_fn( + "start_date", + |context: NativeCallContext, + contract: Contract, + start_date_i64: i64| + -> Result> { + let start_date_u64 = i64_to_u64( + start_date_i64, + context.call_position(), + "start_date", + "Contract.start_date", + )?; + Ok(contract.start_date(start_date_u64)) + }, + ); + engine.register_fn("clear_start_date", |contract: Contract| -> Contract { + contract.clear_start_date() }); - engine.register_fn("add_revision", |contract: Contract, revision: ContractRevision| -> Contract { contract.add_revision(revision) }); - engine.register_fn("revisions", |contract: Contract, revisions_array: Array| -> Contract { - let revisions_vec = revisions_array.into_iter().filter_map(|r| r.try_cast::()).collect(); - contract.revisions(revisions_vec) + engine.register_fn( + "end_date", + |context: NativeCallContext, + contract: Contract, + end_date_i64: i64| + -> Result> { + let end_date_u64 = i64_to_u64( + end_date_i64, + context.call_position(), + "end_date", + "Contract.end_date", + )?; + Ok(contract.end_date(end_date_u64)) + }, + ); + engine.register_fn("clear_end_date", |contract: Contract| -> Contract { + contract.clear_end_date() }); - engine.register_fn("current_version", |context: NativeCallContext, contract: Contract, version_i64: i64| -> Result> { - let version_u32 = i64_to_u32(version_i64, context.position(), "current_version", "Contract.current_version")?; - Ok(contract.current_version(version_u32)) - }); + engine.register_fn( + "renewal_period_days", + |context: NativeCallContext, + contract: Contract, + days_i64: i64| + -> Result> { + let days_i32 = i64_to_i32( + days_i64, + context.call_position(), + "renewal_period_days", + "Contract.renewal_period_days", + )?; + Ok(contract.renewal_period_days(days_i32)) + }, + ); + engine.register_fn( + "clear_renewal_period_days", + |contract: Contract| -> Contract { contract.clear_renewal_period_days() }, + ); - engine.register_fn("last_signed_date", |context: NativeCallContext, contract: Contract, date_i64: i64| -> Result> { - let date_u64 = i64_to_u64(date_i64, context.position(), "last_signed_date", "Contract.last_signed_date")?; - Ok(contract.last_signed_date(date_u64)) + engine.register_fn( + "next_renewal_date", + |context: NativeCallContext, + contract: Contract, + date_i64: i64| + -> Result> { + let date_u64 = i64_to_u64( + date_i64, + context.call_position(), + "next_renewal_date", + "Contract.next_renewal_date", + )?; + Ok(contract.next_renewal_date(date_u64)) + }, + ); + engine.register_fn( + "clear_next_renewal_date", + |contract: Contract| -> Contract { contract.clear_next_renewal_date() }, + ); + + engine.register_fn( + "add_signer", + |contract: Contract, signer: ContractSigner| -> Contract { contract.add_signer(signer) }, + ); + engine.register_fn( + "signers", + |contract: Contract, signers_array: Array| -> Contract { + let signers_vec = signers_array + .into_iter() + .filter_map(|s| s.try_cast::()) + .collect(); + contract.signers(signers_vec) + }, + ); + + engine.register_fn( + "add_revision", + |contract: Contract, revision: ContractRevision| -> Contract { + contract.add_revision(revision) + }, + ); + engine.register_fn( + "revisions", + |contract: Contract, revisions_array: Array| -> Contract { + let revisions_vec = revisions_array + .into_iter() + .filter_map(|r| r.try_cast::()) + .collect(); + contract.revisions(revisions_vec) + }, + ); + + engine.register_fn( + "current_version", + |context: NativeCallContext, + contract: Contract, + version_i64: i64| + -> Result> { + let version_u32 = i64_to_u32( + version_i64, + context.call_position(), + "current_version", + "Contract.current_version", + )?; + Ok(contract.current_version(version_u32)) + }, + ); + + engine.register_fn( + "last_signed_date", + |context: NativeCallContext, + contract: Contract, + date_i64: i64| + -> Result> { + let date_u64 = i64_to_u64( + date_i64, + context.call_position(), + "last_signed_date", + "Contract.last_signed_date", + )?; + Ok(contract.last_signed_date(date_u64)) + }, + ); + engine.register_fn("clear_last_signed_date", |contract: Contract| -> Contract { + contract.clear_last_signed_date() }); - engine.register_fn("clear_last_signed_date", |contract: Contract| -> Contract { contract.clear_last_signed_date() }); // Getters for Contract - engine.register_get("id", |contract: &mut Contract| -> Result> { Ok(contract.base_data.id as i64) }); - engine.register_get("created_at_ts", |contract: &mut Contract| -> Result> { Ok(contract.base_data.created_at as i64) }); - engine.register_get("updated_at_ts", |contract: &mut Contract| -> Result> { Ok(contract.base_data.modified_at as i64) }); - engine.register_get("contract_id", |contract: &mut Contract| -> Result> { Ok(contract.contract_id.clone()) }); - engine.register_get("title", |contract: &mut Contract| -> Result> { Ok(contract.title.clone()) }); - engine.register_get("description", |contract: &mut Contract| -> Result> { Ok(contract.description.clone()) }); - engine.register_get("contract_type", |contract: &mut Contract| -> Result> { Ok(contract.contract_type.clone()) }); - engine.register_get("status", |contract: &mut Contract| -> Result> { Ok(contract.status.clone()) }); - engine.register_get("created_by", |contract: &mut Contract| -> Result> { Ok(contract.created_by.clone()) }); - engine.register_get("terms_and_conditions", |contract: &mut Contract| -> Result> { Ok(contract.terms_and_conditions.clone()) }); - - engine.register_get("start_date", |contract: &mut Contract| -> Result> { - Ok(contract.start_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }); - engine.register_get("end_date", |contract: &mut Contract| -> Result> { - Ok(contract.end_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }); - engine.register_get("renewal_period_days", |contract: &mut Contract| -> Result> { - Ok(contract.renewal_period_days.map_or(Dynamic::UNIT, |days| Dynamic::from(days as i64))) - }); - engine.register_get("next_renewal_date", |contract: &mut Contract| -> Result> { - Ok(contract.next_renewal_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }); - engine.register_get("last_signed_date", |contract: &mut Contract| -> Result> { - Ok(contract.last_signed_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }); + engine.register_get( + "id", + |contract: &mut Contract| -> Result> { + Ok(contract.base_data.id as i64) + }, + ); + engine.register_get( + "created_at_ts", + |contract: &mut Contract| -> Result> { + Ok(contract.base_data.created_at as i64) + }, + ); + engine.register_get( + "updated_at_ts", + |contract: &mut Contract| -> Result> { + Ok(contract.base_data.modified_at as i64) + }, + ); + engine.register_get( + "contract_id", + |contract: &mut Contract| -> Result> { + Ok(contract.contract_id.clone()) + }, + ); + engine.register_get( + "title", + |contract: &mut Contract| -> Result> { + Ok(contract.title.clone()) + }, + ); + engine.register_get( + "description", + |contract: &mut Contract| -> Result> { + Ok(contract.description.clone()) + }, + ); + engine.register_get( + "contract_type", + |contract: &mut Contract| -> Result> { + Ok(contract.contract_type.clone()) + }, + ); + engine.register_get( + "status", + |contract: &mut Contract| -> Result> { + Ok(contract.status.clone()) + }, + ); + engine.register_get( + "created_by", + |contract: &mut Contract| -> Result> { + Ok(contract.created_by.clone()) + }, + ); + engine.register_get( + "terms_and_conditions", + |contract: &mut Contract| -> Result> { + Ok(contract.terms_and_conditions.clone()) + }, + ); - engine.register_get("current_version", |contract: &mut Contract| -> Result> { Ok(contract.current_version as i64) }); + engine.register_get( + "start_date", + |contract: &mut Contract| -> Result> { + Ok(contract + .start_date + .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) + }, + ); + engine.register_get( + "end_date", + |contract: &mut Contract| -> Result> { + Ok(contract + .end_date + .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) + }, + ); + engine.register_get( + "renewal_period_days", + |contract: &mut Contract| -> Result> { + Ok(contract + .renewal_period_days + .map_or(Dynamic::UNIT, |days| Dynamic::from(days as i64))) + }, + ); + engine.register_get( + "next_renewal_date", + |contract: &mut Contract| -> Result> { + Ok(contract + .next_renewal_date + .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) + }, + ); + engine.register_get( + "last_signed_date", + |contract: &mut Contract| -> Result> { + Ok(contract + .last_signed_date + .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) + }, + ); - engine.register_get("signers", |contract: &mut Contract| -> Result> { - let rhai_array = contract.signers.iter().cloned().map(Dynamic::from).collect::(); - Ok(rhai_array) - }); - engine.register_get("revisions", |contract: &mut Contract| -> Result> { - let rhai_array = contract.revisions.iter().cloned().map(Dynamic::from).collect::(); - Ok(rhai_array) - }); + engine.register_get( + "current_version", + |contract: &mut Contract| -> Result> { + Ok(contract.current_version as i64) + }, + ); + + engine.register_get( + "signers", + |contract: &mut Contract| -> Result> { + let rhai_array = contract + .signers + .iter() + .cloned() + .map(Dynamic::from) + .collect::(); + Ok(rhai_array) + }, + ); + engine.register_get( + "revisions", + |contract: &mut Contract| -> Result> { + let rhai_array = contract + .revisions + .iter() + .cloned() + .map(Dynamic::from) + .collect::(); + Ok(rhai_array) + }, + ); // Method set_status - engine.register_fn("set_contract_status", |contract: &mut Contract, status: ContractStatus| { - contract.set_status(status); - }); + engine.register_fn( + "set_contract_status", + |contract: &mut Contract, status: ContractStatus| { + contract.set_status(status); + }, + ); // --- Database Interaction --- let captured_db_for_set = Arc::clone(&db); - engine.register_fn("set_contract", + engine.register_fn( + "set_contract", move |contract: Contract| -> Result<(), Box> { 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(), + format!( + "Failed to set Contract (ID: {}): {}", + contract.base_data.id, e + ) + .into(), Position::NONE, )) }) - }); + }, + ); let captured_db_for_get = Arc::clone(&db); - engine.register_fn("get_contract_by_id", + engine.register_fn( + "get_contract_by_id", move |context: NativeCallContext, id_i64: i64| -> Result> { - let id_u32 = i64_to_u32(id_i64, context.position(), "id", "get_contract_by_id")?; - - captured_db_for_get.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Error getting Contract (ID: {}): {}", id_u32, e).into(), - Position::NONE, - )))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime( - format!("Contract with ID {} not found", id_u32).into(), - Position::NONE, - ))) - }); + let id_u32 = i64_to_u32(id_i64, context.call_position(), "id", "get_contract_by_id")?; + + captured_db_for_get + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Error getting Contract (ID: {}): {}", id_u32, e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Contract with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); } diff --git a/heromodels/src/models/library/collection.rs b/heromodels/src/models/library/collection.rs index 220109c..c0acead 100644 --- a/heromodels/src/models/library/collection.rs +++ b/heromodels/src/models/library/collection.rs @@ -1,7 +1,7 @@ use heromodels_core::BaseModelData; use heromodels_derive::model; -use serde::{Deserialize, Serialize}; use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; /// Represents a collection of library items. #[model] diff --git a/heromodels/src/models/library/rhai.rs b/heromodels/src/models/library/rhai.rs index 1bd2cdd..734f04b 100644 --- a/heromodels/src/models/library/rhai.rs +++ b/heromodels/src/models/library/rhai.rs @@ -1,26 +1,28 @@ -use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, CustomType, TypeBuilder, Position, Module, Dynamic, Array}; -use std::sync::Arc; -use std::mem; use crate::db::Db; +use rhai::plugin::*; +use rhai::{CustomType, Dynamic, Engine, EvalAltResult, Module, Position, TypeBuilder}; use serde::Serialize; use serde_json; +use std::mem; +use std::sync::Arc; - -use super::collection::{Collection as RhaiCollection}; -use super::items::{Image as RhaiImage, Pdf as RhaiPdf, Markdown as RhaiMarkdown, Book as RhaiBook, Slides as RhaiSlides, TocEntry as RhaiTocEntry}; -use crate::db::hero::OurDB; +use super::collection::Collection as RhaiCollection; +use super::items::{ + Book as RhaiBook, Image as RhaiImage, Markdown as RhaiMarkdown, Pdf as RhaiPdf, + Slides as RhaiSlides, TocEntry as RhaiTocEntry, +}; use crate::db::Collection as DbCollectionTrait; +use crate::db::hero::OurDB; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorMismatchDataType( "u32".to_string(), // Expected type format!("i64 value ({}) that cannot be represented as u32", id_i64), // Actual type/value description - Position::NONE + Position::NONE, )) - ) + }) } /// Registers a `.json()` method for any type `T` that implements the required traits. @@ -33,7 +35,7 @@ where let to_json_fn = |obj: &mut T| -> Result> { // Use serde_json to serialize the object to a pretty-formatted string. // The '?' will automatically convert any serialization error into a Rhai error. - serde_json::to_string(obj).map_err(|e| e.to_string().into()) + serde_json::to_string_pretty(obj).map_err(|e| e.to_string().into()) }; // Register the function as a method named "json" for the type 'T'. @@ -45,7 +47,6 @@ where #[rhai_type(name = "CollectionArray")] pub struct RhaiCollectionArray(pub Vec); - #[export_module] mod rhai_library_module { // --- Collection Functions --- @@ -55,29 +56,41 @@ mod rhai_library_module { } #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn collection_title(collection: &mut RhaiCollection, title: String) -> Result> { + pub fn collection_title( + collection: &mut RhaiCollection, + title: String, + ) -> Result> { let owned = mem::take(collection); *collection = owned.title(title); Ok(collection.clone()) } #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn collection_description(collection: &mut RhaiCollection, description: String) -> Result> { + pub fn collection_description( + collection: &mut RhaiCollection, + description: String, + ) -> Result> { let owned = mem::take(collection); *collection = owned.description(description); Ok(collection.clone()) } #[rhai_fn(name = "add_image", return_raw, global, pure)] - pub fn collection_add_image(collection: &mut RhaiCollection, image_id: i64) -> Result> { + pub fn collection_add_image( + collection: &mut RhaiCollection, + image_id: i64, + ) -> Result> { let id = id_from_i64_to_u32(image_id)?; let owned = mem::take(collection); *collection = owned.add_image(id); Ok(collection.clone()) } - + #[rhai_fn(name = "add_pdf", return_raw, global, pure)] - pub fn collection_add_pdf(collection: &mut RhaiCollection, pdf_id: i64) -> Result> { + pub fn collection_add_pdf( + collection: &mut RhaiCollection, + pdf_id: i64, + ) -> Result> { let id = id_from_i64_to_u32(pdf_id)?; let owned = mem::take(collection); *collection = owned.add_pdf(id); @@ -85,7 +98,10 @@ mod rhai_library_module { } #[rhai_fn(name = "add_markdown", return_raw, global, pure)] - pub fn collection_add_markdown(collection: &mut RhaiCollection, markdown_id: i64) -> Result> { + pub fn collection_add_markdown( + collection: &mut RhaiCollection, + markdown_id: i64, + ) -> Result> { let id = id_from_i64_to_u32(markdown_id)?; let owned = mem::take(collection); *collection = owned.add_markdown(id); @@ -93,7 +109,10 @@ mod rhai_library_module { } #[rhai_fn(name = "add_book", return_raw, global, pure)] - pub fn collection_add_book(collection: &mut RhaiCollection, book_id: i64) -> Result> { + pub fn collection_add_book( + collection: &mut RhaiCollection, + book_id: i64, + ) -> Result> { let id = id_from_i64_to_u32(book_id)?; let owned = mem::take(collection); *collection = owned.add_book(id); @@ -101,7 +120,10 @@ mod rhai_library_module { } #[rhai_fn(name = "add_slides", return_raw, global, pure)] - pub fn collection_add_slides(collection: &mut RhaiCollection, slides_id: i64) -> Result> { + pub fn collection_add_slides( + collection: &mut RhaiCollection, + slides_id: i64, + ) -> Result> { let id = id_from_i64_to_u32(slides_id)?; let owned = mem::take(collection); *collection = owned.add_slides(id); @@ -109,34 +131,79 @@ mod rhai_library_module { } #[rhai_fn(get = "id", pure)] - pub fn get_collection_id(collection: &mut RhaiCollection) -> i64 { collection.base_data.id as i64 } - + pub fn get_collection_id(collection: &mut RhaiCollection) -> i64 { + collection.base_data.id as i64 + } + #[rhai_fn(get = "created_at", pure)] - pub fn get_collection_created_at(collection: &mut RhaiCollection) -> i64 { collection.base_data.created_at } - + pub fn get_collection_created_at(collection: &mut RhaiCollection) -> i64 { + collection.base_data.created_at + } + #[rhai_fn(get = "modified_at", pure)] - pub fn get_collection_modified_at(collection: &mut RhaiCollection) -> i64 { collection.base_data.modified_at } - + pub fn get_collection_modified_at(collection: &mut RhaiCollection) -> i64 { + collection.base_data.modified_at + } + #[rhai_fn(get = "title", pure)] - pub fn get_collection_title(collection: &mut RhaiCollection) -> String { collection.title.clone() } - + pub fn get_collection_title(collection: &mut RhaiCollection) -> String { + collection.title.clone() + } + #[rhai_fn(get = "description", pure)] - pub fn get_collection_description(collection: &mut RhaiCollection) -> Option { collection.description.clone() } - + pub fn get_collection_description(collection: &mut RhaiCollection) -> Option { + collection.description.clone() + } + #[rhai_fn(get = "images", pure)] - pub fn get_collection_images(collection: &mut RhaiCollection) -> Vec { collection.images.clone().into_iter().map(|id| id as i64).collect() } - + pub fn get_collection_images(collection: &mut RhaiCollection) -> Vec { + collection + .images + .clone() + .into_iter() + .map(|id| id as i64) + .collect() + } + #[rhai_fn(get = "pdfs", pure)] - pub fn get_collection_pdfs(collection: &mut RhaiCollection) -> Vec { collection.pdfs.clone().into_iter().map(|id| id as i64).collect() } - + pub fn get_collection_pdfs(collection: &mut RhaiCollection) -> Vec { + collection + .pdfs + .clone() + .into_iter() + .map(|id| id as i64) + .collect() + } + #[rhai_fn(get = "markdowns", pure)] - pub fn get_collection_markdowns(collection: &mut RhaiCollection) -> Vec { collection.markdowns.clone().into_iter().map(|id| id as i64).collect() } - + pub fn get_collection_markdowns(collection: &mut RhaiCollection) -> Vec { + collection + .markdowns + .clone() + .into_iter() + .map(|id| id as i64) + .collect() + } + #[rhai_fn(get = "books", pure)] - pub fn get_collection_books(collection: &mut RhaiCollection) -> Vec { collection.books.clone().into_iter().map(|id| id as i64).collect() } - + pub fn get_collection_books(collection: &mut RhaiCollection) -> Vec { + collection + .books + .clone() + .into_iter() + .map(|id| id as i64) + .collect() + } + #[rhai_fn(get = "slides", pure)] - pub fn get_collection_slides(collection: &mut RhaiCollection) -> Vec { collection.slides.clone().into_iter().map(|id| id as i64).collect() } + pub fn get_collection_slides(collection: &mut RhaiCollection) -> Vec { + collection + .slides + .clone() + .into_iter() + .map(|id| id as i64) + .collect() + } // --- Image Functions --- #[rhai_fn(name = "new_image")] @@ -145,14 +212,20 @@ mod rhai_library_module { } #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn image_title(image: &mut RhaiImage, title: String) -> Result> { + pub fn image_title( + image: &mut RhaiImage, + title: String, + ) -> Result> { let owned = mem::take(image); *image = owned.title(title); Ok(image.clone()) } #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn image_description(image: &mut RhaiImage, description: String) -> Result> { + pub fn image_description( + image: &mut RhaiImage, + description: String, + ) -> Result> { let owned = mem::take(image); *image = owned.description(description); Ok(image.clone()) @@ -173,35 +246,54 @@ mod rhai_library_module { } #[rhai_fn(name = "height", return_raw, global, pure)] - pub fn image_height(image: &mut RhaiImage, height: i64) -> Result> { + pub fn image_height( + image: &mut RhaiImage, + height: i64, + ) -> Result> { let owned = mem::take(image); *image = owned.height(height as u32); Ok(image.clone()) } #[rhai_fn(get = "id", pure)] - pub fn get_image_id(image: &mut RhaiImage) -> i64 { image.base_data.id as i64 } + pub fn get_image_id(image: &mut RhaiImage) -> i64 { + image.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_image_created_at(image: &mut RhaiImage) -> i64 { image.base_data.created_at } + pub fn get_image_created_at(image: &mut RhaiImage) -> i64 { + image.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_image_modified_at(image: &mut RhaiImage) -> i64 { image.base_data.modified_at } + pub fn get_image_modified_at(image: &mut RhaiImage) -> i64 { + image.base_data.modified_at + } #[rhai_fn(get = "title", pure)] - pub fn get_image_title(image: &mut RhaiImage) -> String { image.title.clone() } + pub fn get_image_title(image: &mut RhaiImage) -> String { + image.title.clone() + } #[rhai_fn(get = "description", pure)] - pub fn get_image_description(image: &mut RhaiImage) -> Option { image.description.clone() } + pub fn get_image_description(image: &mut RhaiImage) -> Option { + image.description.clone() + } #[rhai_fn(get = "url", pure)] - pub fn get_image_url(image: &mut RhaiImage) -> String { image.url.clone() } + pub fn get_image_url(image: &mut RhaiImage) -> String { + image.url.clone() + } #[rhai_fn(get = "width", pure)] - pub fn get_image_width(image: &mut RhaiImage) -> u32 { image.width } + pub fn get_image_width(image: &mut RhaiImage) -> u32 { + image.width + } #[rhai_fn(get = "height", pure)] - pub fn get_image_height(image: &mut RhaiImage) -> u32 { image.height } + pub fn get_image_height(image: &mut RhaiImage) -> u32 { + image.height + } // --- Pdf Functions --- #[rhai_fn(name = "new_pdf")] @@ -217,7 +309,10 @@ mod rhai_library_module { } #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn pdf_description(pdf: &mut RhaiPdf, description: String) -> Result> { + pub fn pdf_description( + pdf: &mut RhaiPdf, + description: String, + ) -> Result> { let owned = mem::take(pdf); *pdf = owned.description(description); Ok(pdf.clone()) @@ -231,32 +326,49 @@ mod rhai_library_module { } #[rhai_fn(name = "page_count", return_raw, global, pure)] - pub fn pdf_page_count(pdf: &mut RhaiPdf, page_count: i64) -> Result> { + pub fn pdf_page_count( + pdf: &mut RhaiPdf, + page_count: i64, + ) -> Result> { let owned = mem::take(pdf); *pdf = owned.page_count(page_count as u32); Ok(pdf.clone()) } #[rhai_fn(get = "id", pure)] - pub fn get_pdf_id(pdf: &mut RhaiPdf) -> i64 { pdf.base_data.id as i64 } + pub fn get_pdf_id(pdf: &mut RhaiPdf) -> i64 { + pdf.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_pdf_created_at(pdf: &mut RhaiPdf) -> i64 { pdf.base_data.created_at } + pub fn get_pdf_created_at(pdf: &mut RhaiPdf) -> i64 { + pdf.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_pdf_modified_at(pdf: &mut RhaiPdf) -> i64 { pdf.base_data.modified_at } + pub fn get_pdf_modified_at(pdf: &mut RhaiPdf) -> i64 { + pdf.base_data.modified_at + } #[rhai_fn(get = "title", pure)] - pub fn get_pdf_title(pdf: &mut RhaiPdf) -> String { pdf.title.clone() } + pub fn get_pdf_title(pdf: &mut RhaiPdf) -> String { + pdf.title.clone() + } #[rhai_fn(get = "description", pure)] - pub fn get_pdf_description(pdf: &mut RhaiPdf) -> Option { pdf.description.clone() } + pub fn get_pdf_description(pdf: &mut RhaiPdf) -> Option { + pdf.description.clone() + } #[rhai_fn(get = "url", pure)] - pub fn get_pdf_url(pdf: &mut RhaiPdf) -> String { pdf.url.clone() } + pub fn get_pdf_url(pdf: &mut RhaiPdf) -> String { + pdf.url.clone() + } #[rhai_fn(get = "page_count", pure)] - pub fn get_pdf_page_count(pdf: &mut RhaiPdf) -> u32 { pdf.page_count } + pub fn get_pdf_page_count(pdf: &mut RhaiPdf) -> u32 { + pdf.page_count + } // --- Markdown Functions --- #[rhai_fn(name = "new_markdown")] @@ -265,43 +377,64 @@ mod rhai_library_module { } #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn markdown_title(markdown: &mut RhaiMarkdown, title: String) -> Result> { + pub fn markdown_title( + markdown: &mut RhaiMarkdown, + title: String, + ) -> Result> { let owned = mem::take(markdown); *markdown = owned.title(title); Ok(markdown.clone()) } #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn markdown_description(markdown: &mut RhaiMarkdown, description: String) -> Result> { + pub fn markdown_description( + markdown: &mut RhaiMarkdown, + description: String, + ) -> Result> { let owned = mem::take(markdown); *markdown = owned.description(description); Ok(markdown.clone()) } #[rhai_fn(name = "content", return_raw, global, pure)] - pub fn markdown_content(markdown: &mut RhaiMarkdown, content: String) -> Result> { + pub fn markdown_content( + markdown: &mut RhaiMarkdown, + content: String, + ) -> Result> { let owned = mem::take(markdown); *markdown = owned.content(content); Ok(markdown.clone()) } #[rhai_fn(get = "id", pure)] - pub fn get_markdown_id(markdown: &mut RhaiMarkdown) -> i64 { markdown.base_data.id as i64 } + pub fn get_markdown_id(markdown: &mut RhaiMarkdown) -> i64 { + markdown.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_markdown_created_at(markdown: &mut RhaiMarkdown) -> i64 { markdown.base_data.created_at } + pub fn get_markdown_created_at(markdown: &mut RhaiMarkdown) -> i64 { + markdown.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_markdown_modified_at(markdown: &mut RhaiMarkdown) -> i64 { markdown.base_data.modified_at } + pub fn get_markdown_modified_at(markdown: &mut RhaiMarkdown) -> i64 { + markdown.base_data.modified_at + } #[rhai_fn(get = "title", pure)] - pub fn get_markdown_title(markdown: &mut RhaiMarkdown) -> String { markdown.title.clone() } + pub fn get_markdown_title(markdown: &mut RhaiMarkdown) -> String { + markdown.title.clone() + } #[rhai_fn(get = "description", pure)] - pub fn get_markdown_description(markdown: &mut RhaiMarkdown) -> Option { markdown.description.clone() } + pub fn get_markdown_description(markdown: &mut RhaiMarkdown) -> Option { + markdown.description.clone() + } #[rhai_fn(get = "content", pure)] - pub fn get_markdown_content(markdown: &mut RhaiMarkdown) -> String { markdown.content.clone() } + pub fn get_markdown_content(markdown: &mut RhaiMarkdown) -> String { + markdown.content.clone() + } // --- TocEntry Functions --- #[rhai_fn(name = "new_toc_entry")] @@ -310,34 +443,49 @@ mod rhai_library_module { } #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn toc_entry_title(entry: &mut RhaiTocEntry, title: String) -> Result> { + pub fn toc_entry_title( + entry: &mut RhaiTocEntry, + title: String, + ) -> Result> { let owned = mem::take(entry); *entry = owned.title(title); Ok(entry.clone()) } #[rhai_fn(name = "page", return_raw, global, pure)] - pub fn toc_entry_page(entry: &mut RhaiTocEntry, page: i64) -> Result> { + pub fn toc_entry_page( + entry: &mut RhaiTocEntry, + page: i64, + ) -> Result> { let owned = mem::take(entry); *entry = owned.page(page as u32); Ok(entry.clone()) } #[rhai_fn(name = "add_subsection", return_raw, global, pure)] - pub fn toc_entry_add_subsection(entry: &mut RhaiTocEntry, subsection: RhaiTocEntry) -> Result> { + pub fn toc_entry_add_subsection( + entry: &mut RhaiTocEntry, + subsection: RhaiTocEntry, + ) -> Result> { let owned = mem::take(entry); *entry = owned.add_subsection(subsection); Ok(entry.clone()) } #[rhai_fn(get = "title", pure)] - pub fn get_toc_entry_title(entry: &mut RhaiTocEntry) -> String { entry.title.clone() } + pub fn get_toc_entry_title(entry: &mut RhaiTocEntry) -> String { + entry.title.clone() + } #[rhai_fn(get = "page", pure)] - pub fn get_toc_entry_page(entry: &mut RhaiTocEntry) -> u32 { entry.page } + pub fn get_toc_entry_page(entry: &mut RhaiTocEntry) -> u32 { + entry.page + } #[rhai_fn(get = "subsections", pure)] - pub fn get_toc_entry_subsections(entry: &mut RhaiTocEntry) -> Vec { entry.subsections.clone() } + pub fn get_toc_entry_subsections(entry: &mut RhaiTocEntry) -> Vec { + entry.subsections.clone() + } // --- Book Functions --- #[rhai_fn(name = "new_book")] @@ -353,46 +501,69 @@ mod rhai_library_module { } #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn book_description(book: &mut RhaiBook, description: String) -> Result> { + pub fn book_description( + book: &mut RhaiBook, + description: String, + ) -> Result> { let owned = mem::take(book); *book = owned.description(description); Ok(book.clone()) } #[rhai_fn(name = "add_page", return_raw, global, pure)] - pub fn book_add_page(book: &mut RhaiBook, content: String) -> Result> { + pub fn book_add_page( + book: &mut RhaiBook, + content: String, + ) -> Result> { let owned = mem::take(book); *book = owned.add_page(content); Ok(book.clone()) } #[rhai_fn(name = "add_toc_entry", return_raw, global, pure)] - pub fn book_add_toc_entry(book: &mut RhaiBook, entry: RhaiTocEntry) -> Result> { + pub fn book_add_toc_entry( + book: &mut RhaiBook, + entry: RhaiTocEntry, + ) -> Result> { let owned = mem::take(book); *book = owned.add_toc_entry(entry); Ok(book.clone()) } #[rhai_fn(get = "id", pure)] - pub fn get_book_id(book: &mut RhaiBook) -> i64 { book.base_data.id as i64 } + pub fn get_book_id(book: &mut RhaiBook) -> i64 { + book.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_book_created_at(book: &mut RhaiBook) -> i64 { book.base_data.created_at } + pub fn get_book_created_at(book: &mut RhaiBook) -> i64 { + book.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_book_modified_at(book: &mut RhaiBook) -> i64 { book.base_data.modified_at } + pub fn get_book_modified_at(book: &mut RhaiBook) -> i64 { + book.base_data.modified_at + } #[rhai_fn(get = "title", pure)] - pub fn get_book_title(book: &mut RhaiBook) -> String { book.title.clone() } + pub fn get_book_title(book: &mut RhaiBook) -> String { + book.title.clone() + } #[rhai_fn(get = "description", pure)] - pub fn get_book_description(book: &mut RhaiBook) -> Option { book.description.clone() } + pub fn get_book_description(book: &mut RhaiBook) -> Option { + book.description.clone() + } #[rhai_fn(get = "table_of_contents", pure)] - pub fn get_book_table_of_contents(book: &mut RhaiBook) -> Vec { book.table_of_contents.clone() } + pub fn get_book_table_of_contents(book: &mut RhaiBook) -> Vec { + book.table_of_contents.clone() + } #[rhai_fn(get = "pages", pure)] - pub fn get_book_pages(book: &mut RhaiBook) -> Vec { book.pages.clone() } + pub fn get_book_pages(book: &mut RhaiBook) -> Vec { + book.pages.clone() + } // --- Slides Functions --- #[rhai_fn(name = "new_slides")] @@ -401,21 +572,31 @@ mod rhai_library_module { } #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn slides_title(slides: &mut RhaiSlides, title: String) -> Result> { + pub fn slides_title( + slides: &mut RhaiSlides, + title: String, + ) -> Result> { let owned = mem::take(slides); *slides = owned.title(title); Ok(slides.clone()) } #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn slides_description(slides: &mut RhaiSlides, description: String) -> Result> { + pub fn slides_description( + slides: &mut RhaiSlides, + description: String, + ) -> Result> { let owned = mem::take(slides); *slides = owned.description(description); Ok(slides.clone()) } #[rhai_fn(name = "add_slide", return_raw, global, pure)] - pub fn slides_add_slide(slides: &mut RhaiSlides, url: String, title: String) -> Result> { + pub fn slides_add_slide( + slides: &mut RhaiSlides, + url: String, + title: String, + ) -> Result> { let owned = mem::take(slides); let title_opt = if title.is_empty() { None } else { Some(title) }; *slides = owned.add_slide(url, title_opt); @@ -423,32 +604,49 @@ mod rhai_library_module { } #[rhai_fn(name = "add_slide", return_raw, global, pure)] - pub fn slides_add_slide_no_title(slides: &mut RhaiSlides, url: String) -> Result> { + pub fn slides_add_slide_no_title( + slides: &mut RhaiSlides, + url: String, + ) -> Result> { let owned = mem::take(slides); *slides = owned.add_slide(url, None); Ok(slides.clone()) } #[rhai_fn(get = "id", pure)] - pub fn get_slides_id(slides: &mut RhaiSlides) -> i64 { slides.base_data.id as i64 } + pub fn get_slides_id(slides: &mut RhaiSlides) -> i64 { + slides.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_slides_created_at(slides: &mut RhaiSlides) -> i64 { slides.base_data.created_at } + pub fn get_slides_created_at(slides: &mut RhaiSlides) -> i64 { + slides.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_slides_modified_at(slides: &mut RhaiSlides) -> i64 { slides.base_data.modified_at } + pub fn get_slides_modified_at(slides: &mut RhaiSlides) -> i64 { + slides.base_data.modified_at + } #[rhai_fn(get = "title", pure)] - pub fn get_slides_title(slides: &mut RhaiSlides) -> String { slides.title.clone() } + pub fn get_slides_title(slides: &mut RhaiSlides) -> String { + slides.title.clone() + } #[rhai_fn(get = "description", pure)] - pub fn get_slides_description(slides: &mut RhaiSlides) -> Option { slides.description.clone() } + pub fn get_slides_description(slides: &mut RhaiSlides) -> Option { + slides.description.clone() + } #[rhai_fn(get = "slide_urls", pure)] - pub fn get_slides_slide_urls(slides: &mut RhaiSlides) -> Vec { slides.slide_urls.clone() } + pub fn get_slides_slide_urls(slides: &mut RhaiSlides) -> Vec { + slides.slide_urls.clone() + } #[rhai_fn(get = "slide_titles", pure)] - pub fn get_slides_slide_titles(slides: &mut RhaiSlides) -> Vec> { slides.slide_titles.clone() } + pub fn get_slides_slide_titles(slides: &mut RhaiSlides) -> Vec> { + slides.slide_titles.clone() + } } pub fn register_library_rhai_module(engine: &mut Engine, db: Arc) { @@ -470,157 +668,362 @@ pub fn register_library_rhai_module(engine: &mut Engine, db: Arc) { // --- Collection DB Functions --- let db_clone = db.clone(); - db_module.set_native_fn("save_collection", move |collection: RhaiCollection| -> Result> { - let result = db_clone.set(&collection) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(result.1) - }); + db_module.set_native_fn( + "save_collection", + move |collection: RhaiCollection| -> Result> { + let result = db_clone.set(&collection).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("get_collection", move |id: i64| -> Result> { - let collection_id = id_from_i64_to_u32(id)?; - db_clone.get_by_id(collection_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Collection with ID {} not found", collection_id).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_collection", + move |id: i64| -> Result> { + let collection_id = id_from_i64_to_u32(id)?; + db_clone + .get_by_id(collection_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Collection with ID {} not found", collection_id).into(), + Position::NONE, + )) + }) + }, + ); let db_clone_list_collections = db.clone(); - db_module.set_native_fn("list_collections", move || -> Result> { - let collections_vec: Vec = db_clone_list_collections - .collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error (list_collections - access): {:?}", e).into(), Position::NONE)))? - .get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error (list_collections - get_all): {:?}", e).into(), Position::NONE)))?; - Ok(RhaiCollectionArray(collections_vec)) // Wrap in RhaiCollectionArray - }); + db_module.set_native_fn( + "list_collections", + move || -> Result> { + let collections_vec: Vec = db_clone_list_collections + .collection::() + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error (list_collections - access): {:?}", e).into(), + Position::NONE, + )) + })? + .get_all() + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error (list_collections - get_all): {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(RhaiCollectionArray(collections_vec)) // Wrap in RhaiCollectionArray + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("delete_collection", move |id: i64| -> Result<(), Box> { - let collection_id = id_from_i64_to_u32(id)?; - db_clone.collection::().unwrap().delete_by_id(collection_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(()) - }); + db_module.set_native_fn( + "delete_collection", + move |id: i64| -> Result<(), Box> { + let collection_id = id_from_i64_to_u32(id)?; + db_clone + .collection::() + .unwrap() + .delete_by_id(collection_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(()) + }, + ); // --- Image DB Functions --- let db_clone = db.clone(); - db_module.set_native_fn("save_image", move |image: RhaiImage| -> Result> { - let result = db_clone.set(&image) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(result.1) - }); + db_module.set_native_fn( + "save_image", + move |image: RhaiImage| -> Result> { + let result = db_clone.set(&image).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("get_image", move |id: i64| -> Result> { - let image_id = id_from_i64_to_u32(id)?; - db_clone.get_by_id(image_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Image with ID {} not found", image_id).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_image", + move |id: i64| -> Result> { + let image_id = id_from_i64_to_u32(id)?; + db_clone + .get_by_id(image_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Image with ID {} not found", image_id).into(), + Position::NONE, + )) + }) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("delete_image", move |id: i64| -> Result<(), Box> { - let image_id = id_from_i64_to_u32(id)?; - db_clone.collection::().unwrap().delete_by_id(image_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(()) - }); + db_module.set_native_fn( + "delete_image", + move |id: i64| -> Result<(), Box> { + let image_id = id_from_i64_to_u32(id)?; + db_clone + .collection::() + .unwrap() + .delete_by_id(image_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(()) + }, + ); // --- Pdf DB Functions --- let db_clone = db.clone(); - db_module.set_native_fn("save_pdf", move |pdf: RhaiPdf| -> Result> { - let result = db_clone.set(&pdf) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(result.1) - }); + db_module.set_native_fn( + "save_pdf", + move |pdf: RhaiPdf| -> Result> { + let result = db_clone.set(&pdf).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("get_pdf", move |id: i64| -> Result> { - let pdf_id = id_from_i64_to_u32(id)?; - db_clone.get_by_id(pdf_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Pdf with ID {} not found", pdf_id).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_pdf", + move |id: i64| -> Result> { + let pdf_id = id_from_i64_to_u32(id)?; + db_clone + .get_by_id(pdf_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Pdf with ID {} not found", pdf_id).into(), + Position::NONE, + )) + }) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("delete_pdf", move |id: i64| -> Result<(), Box> { - let pdf_id = id_from_i64_to_u32(id)?; - db_clone.collection::().unwrap().delete_by_id(pdf_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(()) - }); + db_module.set_native_fn( + "delete_pdf", + move |id: i64| -> Result<(), Box> { + let pdf_id = id_from_i64_to_u32(id)?; + db_clone + .collection::() + .unwrap() + .delete_by_id(pdf_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(()) + }, + ); // --- Markdown DB Functions --- let db_clone = db.clone(); - db_module.set_native_fn("save_markdown", move |markdown: RhaiMarkdown| -> Result> { - let result = db_clone.set(&markdown) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(result.1) - }); + db_module.set_native_fn( + "save_markdown", + move |markdown: RhaiMarkdown| -> Result> { + let result = db_clone.set(&markdown).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("get_markdown", move |id: i64| -> Result> { - let markdown_id = id_from_i64_to_u32(id)?; - db_clone.get_by_id(markdown_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Markdown with ID {} not found", markdown_id).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_markdown", + move |id: i64| -> Result> { + let markdown_id = id_from_i64_to_u32(id)?; + db_clone + .get_by_id(markdown_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Markdown with ID {} not found", markdown_id).into(), + Position::NONE, + )) + }) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("delete_markdown", move |id: i64| -> Result<(), Box> { - let markdown_id = id_from_i64_to_u32(id)?; - db_clone.collection::().unwrap().delete_by_id(markdown_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(()) - }); + db_module.set_native_fn( + "delete_markdown", + move |id: i64| -> Result<(), Box> { + let markdown_id = id_from_i64_to_u32(id)?; + db_clone + .collection::() + .unwrap() + .delete_by_id(markdown_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(()) + }, + ); // --- Book DB Functions --- let db_clone = db.clone(); - db_module.set_native_fn("save_book", move |book: RhaiBook| -> Result> { - let result = db_clone.set(&book) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(result.1) - }); + db_module.set_native_fn( + "save_book", + move |book: RhaiBook| -> Result> { + let result = db_clone.set(&book).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("get_book", move |id: i64| -> Result> { - let book_id = id_from_i64_to_u32(id)?; - db_clone.get_by_id(book_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Book with ID {} not found", book_id).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_book", + move |id: i64| -> Result> { + let book_id = id_from_i64_to_u32(id)?; + db_clone + .get_by_id(book_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Book with ID {} not found", book_id).into(), + Position::NONE, + )) + }) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("delete_book", move |id: i64| -> Result<(), Box> { - let book_id = id_from_i64_to_u32(id)?; - db_clone.collection::().unwrap().delete_by_id(book_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(()) - }); + db_module.set_native_fn( + "delete_book", + move |id: i64| -> Result<(), Box> { + let book_id = id_from_i64_to_u32(id)?; + db_clone + .collection::() + .unwrap() + .delete_by_id(book_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(()) + }, + ); // --- Slides DB Functions --- let db_clone = db.clone(); - db_module.set_native_fn("save_slides", move |slides: RhaiSlides| -> Result> { - let result = db_clone.set(&slides) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(result.1) - }); + db_module.set_native_fn( + "save_slides", + move |slides: RhaiSlides| -> Result> { + let result = db_clone.set(&slides).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("get_slides", move |id: i64| -> Result> { - let slides_id = id_from_i64_to_u32(id)?; - db_clone.get_by_id(slides_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Slides with ID {} not found", slides_id).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_slides", + move |id: i64| -> Result> { + let slides_id = id_from_i64_to_u32(id)?; + db_clone + .get_by_id(slides_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Slides with ID {} not found", slides_id).into(), + Position::NONE, + )) + }) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("delete_slides", move |id: i64| -> Result<(), Box> { - let slides_id = id_from_i64_to_u32(id)?; - db_clone.collection::().unwrap().delete_by_id(slides_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(()) - }); + db_module.set_native_fn( + "delete_slides", + move |id: i64| -> Result<(), Box> { + let slides_id = id_from_i64_to_u32(id)?; + db_clone + .collection::() + .unwrap() + .delete_by_id(slides_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(()) + }, + ); engine.register_global_module(db_module.into()); } diff --git a/heromodels/src/models/mod.rs b/heromodels/src/models/mod.rs index 42cc76e..7763482 100644 --- a/heromodels/src/models/mod.rs +++ b/heromodels/src/models/mod.rs @@ -3,41 +3,41 @@ pub mod core; pub mod userexample; // pub mod productexample; // Temporarily remove as files are missing pub mod access; -pub mod calendar; -pub mod contact; -pub mod circle; -pub mod governance; -pub mod finance; -pub mod library; -pub mod legal; -pub mod flow; pub mod biz; +pub mod calendar; +pub mod circle; +pub mod contact; +pub mod finance; +pub mod flow; +pub mod governance; +pub mod legal; +pub mod library; pub mod projects; // Re-export key types for convenience pub use core::Comment; pub use userexample::User; // pub use productexample::Product; // Temporarily remove -pub use calendar::{Calendar, Event, Attendee, AttendanceStatus}; -pub use circle::{Circle}; -pub use governance::{Proposal, ProposalStatus, VoteEventStatus, Ballot, VoteOption, AttachedFile}; -pub use finance::{Account, Asset, AssetType}; -pub use finance::marketplace::{Listing, ListingStatus, ListingType, Bid, BidStatus}; -pub use legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus}; -pub use flow::{Flow, FlowStep, SignatureRequirement}; pub use biz::{Sale, SaleItem, SaleStatus}; -pub use library::items::{Image, Pdf, Markdown}; +pub use calendar::{AttendanceStatus, Attendee, Calendar, Event}; +pub use circle::Circle; +pub use finance::marketplace::{Bid, BidStatus, Listing, ListingStatus, ListingType}; +pub use finance::{Account, Asset, AssetType}; +pub use flow::{Flow, FlowStep, SignatureRequirement}; +pub use governance::{AttachedFile, Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption}; +pub use legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus}; pub use library::collection::Collection; +pub use library::items::{Image, Markdown, Pdf}; -pub use flow::register_flow_rhai_module; +#[cfg(feature = "rhai")] +pub use biz::register_biz_rhai_module; #[cfg(feature = "rhai")] pub use calendar::register_calendar_rhai_module; #[cfg(feature = "rhai")] pub use circle::register_circle_rhai_module; +pub use flow::register_flow_rhai_module; pub use legal::register_legal_rhai_module; #[cfg(feature = "rhai")] -pub use biz::register_biz_rhai_module; +pub use library::register_library_rhai_module; #[cfg(feature = "rhai")] pub use projects::register_projects_rhai_module; -#[cfg(feature = "rhai")] -pub use library::register_library_rhai_module; diff --git a/heromodels/src/models/projects/base.rs b/heromodels/src/models/projects/base.rs index d3ffefd..adbc059 100644 --- a/heromodels/src/models/projects/base.rs +++ b/heromodels/src/models/projects/base.rs @@ -1,8 +1,8 @@ // heromodels/src/models/projects/base.rs -use serde::{Deserialize, Serialize}; -use heromodels_core::{BaseModelData, Model, BaseModelDataOps}; +use heromodels_core::{BaseModelData, BaseModelDataOps, Model}; #[cfg(feature = "rhai")] use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; use strum_macros::Display; // Made unconditional as Display derive is used on non-rhai-gated enums // --- Enums --- @@ -50,7 +50,7 @@ pub enum ItemType { impl Default for ItemType { fn default() -> Self { ItemType::Task - } + } } // --- Structs --- @@ -178,7 +178,6 @@ impl Project { } } - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[cfg_attr(feature = "rhai", derive(CustomType))] pub struct Label { @@ -226,4 +225,3 @@ impl Label { self } } - diff --git a/heromodels/src/models/projects/epic.rs b/heromodels/src/models/projects/epic.rs index d96b4d2..657cbee 100644 --- a/heromodels/src/models/projects/epic.rs +++ b/heromodels/src/models/projects/epic.rs @@ -3,8 +3,8 @@ use chrono::{DateTime, Utc}; use heromodels_core::BaseModelData; use heromodels_derive::model; -use serde::{Deserialize, Serialize}; use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; use super::base::Status as ProjectStatus; // Using the generic project status for now @@ -15,17 +15,17 @@ pub struct Epic { pub name: String, pub description: Option, pub status: ProjectStatus, // Or a new EpicStatus enum if needed - + pub project_id: Option, // Link to a general project/board pub start_date: Option>, pub due_date: Option>, - + pub tags: Vec, - + // Explicitly list task IDs belonging to this epic // This helps in querying and avoids relying solely on tasks pointing to the epic. - pub child_task_ids: Vec, + pub child_task_ids: Vec, } impl Epic { diff --git a/heromodels/src/models/projects/mod.rs b/heromodels/src/models/projects/mod.rs index ca20fb8..afdab0e 100644 --- a/heromodels/src/models/projects/mod.rs +++ b/heromodels/src/models/projects/mod.rs @@ -1,11 +1,11 @@ // heromodels/src/models/projects/mod.rs pub mod base; -pub mod task_enums; -pub mod task; pub mod epic; -pub mod sprint_enums; pub mod sprint; +pub mod sprint_enums; +pub mod task; +pub mod task_enums; // pub mod epic; // pub mod issue; // pub mod kanban; @@ -13,11 +13,11 @@ pub mod sprint; // pub mod story; pub use base::*; -pub use task_enums::*; -pub use task::*; pub use epic::*; -pub use sprint_enums::*; pub use sprint::*; +pub use sprint_enums::*; +pub use task::*; +pub use task_enums::*; // pub use epic::*; // pub use issue::*; // pub use kanban::*; @@ -25,7 +25,7 @@ pub use sprint::*; // pub use story::*; #[cfg(feature = "rhai")] -pub mod rhai; +pub mod rhai; #[cfg(feature = "rhai")] pub use rhai::register_projects_rhai_module; diff --git a/heromodels/src/models/projects/rhai.rs b/heromodels/src/models/projects/rhai.rs index 27fd7ce..6f38196 100644 --- a/heromodels/src/models/projects/rhai.rs +++ b/heromodels/src/models/projects/rhai.rs @@ -1,14 +1,13 @@ // heromodels/src/models/projects/rhai.rs -use rhai::{Engine, EvalAltResult, Dynamic, Position}; -use std::sync::Arc; use crate::db::hero::OurDB; -use heromodels_core::{Model, BaseModelDataOps}; -use crate::db::{Db, Collection}; - +use crate::db::{Collection, Db}; +use heromodels_core::{BaseModelDataOps, Model}; +use rhai::{Dynamic, Engine, EvalAltResult, Position}; +use std::sync::Arc; // Import models from the projects::base module -use super::base::{Project, /* Label, */ Priority, Status, ItemType}; // Label commented out as it's unused for now +use super::base::{ItemType, /* Label, */ Priority, Project, Status}; // Label commented out as it's unused for now // Helper function for ID conversion (if needed, similar to other rhai.rs files) fn id_from_i64(val: i64) -> Result> { @@ -78,172 +77,325 @@ pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc) { }); // Multi-argument constructor (renamed) - engine.register_fn("new_project_with_details", |id_i64: i64, name: String, description: String, owner_id_i64: i64| -> Result> { - Ok(Project::new(id_from_i64(id_i64)?, name, description, id_from_i64(owner_id_i64)?)) - }); + engine.register_fn( + "new_project_with_details", + |id_i64: i64, + name: String, + description: String, + owner_id_i64: i64| + -> Result> { + Ok(Project::new( + id_from_i64(id_i64)?, + name, + description, + id_from_i64(owner_id_i64)?, + )) + }, + ); // Getters for Project - engine.register_get("id", |p: &mut Project| -> Result> { Ok(p.get_id() as i64) }); - engine.register_get("name", |p: &mut Project| -> Result> { Ok(p.name.clone()) }); - engine.register_get("description", |p: &mut Project| -> Result> { Ok(p.description.clone()) }); - engine.register_get("owner_id", |p: &mut Project| -> Result> { Ok(p.owner_id as i64) }); - engine.register_get("member_ids", |p: &mut Project| -> Result> { - Ok(p.member_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect()) + engine.register_get("id", |p: &mut Project| -> Result> { + Ok(p.get_id() as i64) }); - engine.register_get("board_ids", |p: &mut Project| -> Result> { - Ok(p.board_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect()) - }); - engine.register_get("sprint_ids", |p: &mut Project| -> Result> { - Ok(p.sprint_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect()) - }); - engine.register_get("epic_ids", |p: &mut Project| -> Result> { - Ok(p.epic_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect()) - }); - engine.register_get("tags", |p: &mut Project| -> Result> { - Ok(p.tags.iter().map(|tag| rhai::Dynamic::from(tag.clone())).collect()) - }); - engine.register_get("created_at", |p: &mut Project| -> Result> { Ok(p.base_data.created_at) }); - engine.register_get("modified_at", |p: &mut Project| -> Result> { Ok(p.base_data.modified_at) }); - engine.register_get("comments", |p: &mut Project| -> Result> { - Ok(p.base_data.comments.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect()) - }); - engine.register_get("status", |p: &mut Project| -> Result> { Ok(p.status.clone()) }); - engine.register_get("priority", |p: &mut Project| -> Result> { Ok(p.priority.clone()) }); - engine.register_get("item_type", |p: &mut Project| -> Result> { Ok(p.item_type.clone()) }); + engine.register_get( + "name", + |p: &mut Project| -> Result> { Ok(p.name.clone()) }, + ); + engine.register_get( + "description", + |p: &mut Project| -> Result> { Ok(p.description.clone()) }, + ); + engine.register_get( + "owner_id", + |p: &mut Project| -> Result> { Ok(p.owner_id as i64) }, + ); + engine.register_get( + "member_ids", + |p: &mut Project| -> Result> { + Ok(p.member_ids + .iter() + .map(|&id| rhai::Dynamic::from(id as i64)) + .collect()) + }, + ); + engine.register_get( + "board_ids", + |p: &mut Project| -> Result> { + Ok(p.board_ids + .iter() + .map(|&id| rhai::Dynamic::from(id as i64)) + .collect()) + }, + ); + engine.register_get( + "sprint_ids", + |p: &mut Project| -> Result> { + Ok(p.sprint_ids + .iter() + .map(|&id| rhai::Dynamic::from(id as i64)) + .collect()) + }, + ); + engine.register_get( + "epic_ids", + |p: &mut Project| -> Result> { + Ok(p.epic_ids + .iter() + .map(|&id| rhai::Dynamic::from(id as i64)) + .collect()) + }, + ); + engine.register_get( + "tags", + |p: &mut Project| -> Result> { + Ok(p.tags + .iter() + .map(|tag| rhai::Dynamic::from(tag.clone())) + .collect()) + }, + ); + engine.register_get( + "created_at", + |p: &mut Project| -> Result> { Ok(p.base_data.created_at) }, + ); + engine.register_get( + "modified_at", + |p: &mut Project| -> Result> { Ok(p.base_data.modified_at) }, + ); + engine.register_get( + "comments", + |p: &mut Project| -> Result> { + Ok(p.base_data + .comments + .iter() + .map(|&id| rhai::Dynamic::from(id as i64)) + .collect()) + }, + ); + engine.register_get( + "status", + |p: &mut Project| -> Result> { Ok(p.status.clone()) }, + ); + engine.register_get( + "priority", + |p: &mut Project| -> Result> { Ok(p.priority.clone()) }, + ); + engine.register_get( + "item_type", + |p: &mut Project| -> Result> { Ok(p.item_type.clone()) }, + ); // Builder methods for Project // let db_clone = db.clone(); // This was unused - engine.register_fn("name", |p: Project, name: String| -> Result> { Ok(p.name(name)) }); - engine.register_fn("description", |p: Project, description: String| -> Result> { Ok(p.description(description)) }); - engine.register_fn("owner_id", |p: Project, owner_id_i64: i64| -> Result> { Ok(p.owner_id(id_from_i64(owner_id_i64)?)) }); - engine.register_fn("add_member_id", |p: Project, member_id_i64: i64| -> Result> { Ok(p.add_member_id(id_from_i64(member_id_i64)?)) }); - engine.register_fn("member_ids", |p: Project, member_ids_i64: rhai::Array| -> Result> { - let ids = member_ids_i64 - .into_iter() - .map(|id_dyn: Dynamic| { - let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected integer for ID".to_string(), - id_dyn.type_name().to_string(), - Position::NONE, - )) - })?; - id_from_i64(val_i64) - }) - .collect::, Box>>()?; - Ok(p.member_ids(ids)) - }); - engine.register_fn("add_board_id", |p: Project, board_id_i64: i64| -> Result> { Ok(p.add_board_id(id_from_i64(board_id_i64)?)) }); - engine.register_fn("board_ids", |p: Project, board_ids_i64: rhai::Array| -> Result> { - let ids = board_ids_i64 - .into_iter() - .map(|id_dyn: Dynamic| { - let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected integer for ID".to_string(), - id_dyn.type_name().to_string(), - Position::NONE, - )) - })?; - id_from_i64(val_i64) - }) - .collect::, Box>>()?; - Ok(p.board_ids(ids)) - }); - engine.register_fn("add_sprint_id", |p: Project, sprint_id_i64: i64| -> Result> { Ok(p.add_sprint_id(id_from_i64(sprint_id_i64)?)) }); - engine.register_fn("sprint_ids", |p: Project, sprint_ids_i64: rhai::Array| -> Result> { - let ids = sprint_ids_i64 - .into_iter() - .map(|id_dyn: Dynamic| { - let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected integer for ID".to_string(), - id_dyn.type_name().to_string(), - Position::NONE, - )) - })?; - id_from_i64(val_i64) - }) - .collect::, Box>>()?; - Ok(p.sprint_ids(ids)) - }); - engine.register_fn("add_epic_id", |p: Project, epic_id_i64: i64| -> Result> { Ok(p.add_epic_id(id_from_i64(epic_id_i64)?)) }); - engine.register_fn("epic_ids", |p: Project, epic_ids_i64: rhai::Array| -> Result> { - let ids = epic_ids_i64 - .into_iter() - .map(|id_dyn: Dynamic| { - let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected integer for ID".to_string(), - id_dyn.type_name().to_string(), - Position::NONE, - )) - })?; - id_from_i64(val_i64) - }) - .collect::, Box>>()?; - Ok(p.epic_ids(ids)) - }); - engine.register_fn("add_tag", |p: Project, tag: String| -> Result> { Ok(p.add_tag(tag)) }); - engine.register_fn("tags", |p: Project, tags_dyn: rhai::Array| -> Result> { - let tags_vec = tags_dyn - .into_iter() - .map(|tag_dyn: Dynamic| { - tag_dyn.clone().into_string().map_err(|_err| { // _err is Rhai's internal error, we create a new one - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected string for tag".to_string(), - tag_dyn.type_name().to_string(), - Position::NONE, - )) + engine.register_fn( + "name", + |p: Project, name: String| -> Result> { Ok(p.name(name)) }, + ); + engine.register_fn( + "description", + |p: Project, description: String| -> Result> { + Ok(p.description(description)) + }, + ); + engine.register_fn( + "owner_id", + |p: Project, owner_id_i64: i64| -> Result> { + Ok(p.owner_id(id_from_i64(owner_id_i64)?)) + }, + ); + engine.register_fn( + "add_member_id", + |p: Project, member_id_i64: i64| -> Result> { + Ok(p.add_member_id(id_from_i64(member_id_i64)?)) + }, + ); + engine.register_fn( + "member_ids", + |p: Project, member_ids_i64: rhai::Array| -> Result> { + let ids = member_ids_i64 + .into_iter() + .map(|id_dyn: Dynamic| { + let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { + Box::new(EvalAltResult::ErrorMismatchDataType( + "Expected integer for ID".to_string(), + id_dyn.type_name().to_string(), + Position::NONE, + )) + })?; + id_from_i64(val_i64) }) - }) - .collect::, Box>>()?; - Ok(p.tags(tags_vec)) - }); + .collect::, Box>>()?; + Ok(p.member_ids(ids)) + }, + ); + engine.register_fn( + "add_board_id", + |p: Project, board_id_i64: i64| -> Result> { + Ok(p.add_board_id(id_from_i64(board_id_i64)?)) + }, + ); + engine.register_fn( + "board_ids", + |p: Project, board_ids_i64: rhai::Array| -> Result> { + let ids = board_ids_i64 + .into_iter() + .map(|id_dyn: Dynamic| { + let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { + Box::new(EvalAltResult::ErrorMismatchDataType( + "Expected integer for ID".to_string(), + id_dyn.type_name().to_string(), + Position::NONE, + )) + })?; + id_from_i64(val_i64) + }) + .collect::, Box>>()?; + Ok(p.board_ids(ids)) + }, + ); + engine.register_fn( + "add_sprint_id", + |p: Project, sprint_id_i64: i64| -> Result> { + Ok(p.add_sprint_id(id_from_i64(sprint_id_i64)?)) + }, + ); + engine.register_fn( + "sprint_ids", + |p: Project, sprint_ids_i64: rhai::Array| -> Result> { + let ids = sprint_ids_i64 + .into_iter() + .map(|id_dyn: Dynamic| { + let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { + Box::new(EvalAltResult::ErrorMismatchDataType( + "Expected integer for ID".to_string(), + id_dyn.type_name().to_string(), + Position::NONE, + )) + })?; + id_from_i64(val_i64) + }) + .collect::, Box>>()?; + Ok(p.sprint_ids(ids)) + }, + ); + engine.register_fn( + "add_epic_id", + |p: Project, epic_id_i64: i64| -> Result> { + Ok(p.add_epic_id(id_from_i64(epic_id_i64)?)) + }, + ); + engine.register_fn( + "epic_ids", + |p: Project, epic_ids_i64: rhai::Array| -> Result> { + let ids = epic_ids_i64 + .into_iter() + .map(|id_dyn: Dynamic| { + let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { + Box::new(EvalAltResult::ErrorMismatchDataType( + "Expected integer for ID".to_string(), + id_dyn.type_name().to_string(), + Position::NONE, + )) + })?; + id_from_i64(val_i64) + }) + .collect::, Box>>()?; + Ok(p.epic_ids(ids)) + }, + ); + engine.register_fn( + "add_tag", + |p: Project, tag: String| -> Result> { Ok(p.add_tag(tag)) }, + ); + engine.register_fn( + "tags", + |p: Project, tags_dyn: rhai::Array| -> Result> { + let tags_vec = tags_dyn + .into_iter() + .map(|tag_dyn: Dynamic| { + tag_dyn.clone().into_string().map_err(|_err| { + // _err is Rhai's internal error, we create a new one + Box::new(EvalAltResult::ErrorMismatchDataType( + "Expected string for tag".to_string(), + tag_dyn.type_name().to_string(), + Position::NONE, + )) + }) + }) + .collect::, Box>>()?; + Ok(p.tags(tags_vec)) + }, + ); - engine.register_fn("status", |p: Project, status: Status| -> Result> { Ok(p.status(status)) }); - engine.register_fn("priority", |p: Project, priority: Priority| -> Result> { Ok(p.priority(priority)) }); - engine.register_fn("item_type", |p: Project, item_type: ItemType| -> Result> { Ok(p.item_type(item_type)) }); + engine.register_fn( + "status", + |p: Project, status: Status| -> Result> { + Ok(p.status(status)) + }, + ); + engine.register_fn( + "priority", + |p: Project, priority: Priority| -> Result> { + Ok(p.priority(priority)) + }, + ); + engine.register_fn( + "item_type", + |p: Project, item_type: ItemType| -> Result> { + Ok(p.item_type(item_type)) + }, + ); // Base ModelData builders - engine.register_fn("add_base_comment", |p: Project, comment_id_i64: i64| -> Result> { Ok(p.add_base_comment(id_from_i64(comment_id_i64)?)) }); + engine.register_fn( + "add_base_comment", + |p: Project, comment_id_i64: i64| -> Result> { + Ok(p.add_base_comment(id_from_i64(comment_id_i64)?)) + }, + ); // --- Database Interaction Functions --- let db_clone_set = db.clone(); - engine.register_fn("set_project", move |project: Project| -> Result<(), Box> { - let collection = db_clone_set.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorSystem( - "Failed to access project collection".to_string(), - format!("DB operation failed: {:?}", e).into(), - )) - })?; + engine.register_fn( + "set_project", + move |project: Project| -> Result<(), Box> { + let collection = db_clone_set.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorSystem( + "Failed to access project collection".to_string(), + format!("DB operation failed: {:?}", e).into(), + )) + })?; - collection.set(&project).map(|_| ()).map_err(|e| { - Box::new(EvalAltResult::ErrorSystem( - "Failed to save project".to_string(), - format!("DB operation failed: {:?}", e).into(), - )) - }) - }); + collection.set(&project).map(|_| ()).map_err(|e| { + Box::new(EvalAltResult::ErrorSystem( + "Failed to save project".to_string(), + format!("DB operation failed: {:?}", e).into(), + )) + }) + }, + ); let db_clone_get = db.clone(); - engine.register_fn("get_project_by_id", move |id_i64: i64| -> Result> { - let id = id_from_i64(id_i64)?; - let collection = db_clone_get.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorSystem( - "Failed to access project collection".to_string(), - format!("DB operation failed: {:?}", e).into(), - )) - })?; + engine.register_fn( + "get_project_by_id", + move |id_i64: i64| -> Result> { + let id = id_from_i64(id_i64)?; + let collection = db_clone_get.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorSystem( + "Failed to access project collection".to_string(), + format!("DB operation failed: {:?}", e).into(), + )) + })?; - match collection.get_by_id(id) { - Ok(Some(project)) => Ok(Dynamic::from(project)), - Ok(None) => Ok(Dynamic::UNIT), // Represents '()' in Rhai - Err(e) => Err(Box::new(EvalAltResult::ErrorSystem( - "Failed to retrieve project by ID".to_string(), - format!("DB operation failed: {:?}", e).into(), - ))), - } - }); + match collection.get_by_id(id) { + Ok(Some(project)) => Ok(Dynamic::from(project)), + Ok(None) => Ok(Dynamic::UNIT), // Represents '()' in Rhai + Err(e) => Err(Box::new(EvalAltResult::ErrorSystem( + "Failed to retrieve project by ID".to_string(), + format!("DB operation failed: {:?}", e).into(), + ))), + } + }, + ); // TODO: Register Rhai bindings for the `Label` model if needed, or remove unused import. // Register Label type and its methods/getters diff --git a/heromodels/src/models/projects/sprint.rs b/heromodels/src/models/projects/sprint.rs index 883857f..d73d1d6 100644 --- a/heromodels/src/models/projects/sprint.rs +++ b/heromodels/src/models/projects/sprint.rs @@ -3,8 +3,8 @@ use chrono::{DateTime, Utc}; use heromodels_core::BaseModelData; use heromodels_derive::model; -use serde::{Deserialize, Serialize}; use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; use super::sprint_enums::SprintStatus; // Import our new enum @@ -16,14 +16,14 @@ pub struct Sprint { pub description: Option, pub status: SprintStatus, pub goal: Option, // Sprint goal - + pub project_id: Option, // Link to a general project/board pub start_date: Option>, pub end_date: Option>, // Changed from due_date for sprints - + // Explicitly list task IDs belonging to this sprint - pub task_ids: Vec, + pub task_ids: Vec, } impl Sprint { diff --git a/heromodels/src/models/projects/task.rs b/heromodels/src/models/projects/task.rs index 4d008dc..c36e3bd 100644 --- a/heromodels/src/models/projects/task.rs +++ b/heromodels/src/models/projects/task.rs @@ -3,10 +3,10 @@ use chrono::{DateTime, Utc}; use heromodels_core::BaseModelData; use heromodels_derive::model; -use serde::{Deserialize, Serialize}; -use rhai::{CustomType, TypeBuilder}; // Assuming rhai might be used +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; // Assuming rhai might be used -use super::task_enums::{TaskStatus, TaskPriority}; // Import our new enums +use super::task_enums::{TaskPriority, TaskStatus}; // Import our new enums #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] #[model] // This will provide id, created_at, updated_at via base_data @@ -16,10 +16,10 @@ pub struct Task { pub description: Option, pub status: TaskStatus, pub priority: TaskPriority, - + pub assignee_id: Option, // User ID pub reporter_id: Option, // User ID - + pub parent_task_id: Option, // For subtasks pub epic_id: Option, pub sprint_id: Option, diff --git a/heromodels/src/models/userexample/mod.rs b/heromodels/src/models/userexample/mod.rs index 66a4c00..2f9514d 100644 --- a/heromodels/src/models/userexample/mod.rs +++ b/heromodels/src/models/userexample/mod.rs @@ -2,4 +2,4 @@ pub mod user; // Re-export User for convenience -pub use user::User; \ No newline at end of file +pub use user::User; diff --git a/ourdb/Cargo.toml b/ourdb/Cargo.toml index becf1f3..6ff8e8e 100644 --- a/ourdb/Cargo.toml +++ b/ourdb/Cargo.toml @@ -15,9 +15,9 @@ rand = "0.8.5" criterion = "0.5.1" tempfile = "3.8.0" -[[bench]] -name = "ourdb_benchmarks" -harness = false +# [[bench]] +# name = "ourdb_benchmarks" +# harness = false [[example]] name = "basic_usage" diff --git a/ourdb/examples/advanced_usage.rs b/ourdb/examples/advanced_usage.rs index da15a3a..831a767 100644 --- a/ourdb/examples/advanced_usage.rs +++ b/ourdb/examples/advanced_usage.rs @@ -6,18 +6,18 @@ fn main() -> Result<(), ourdb::Error> { // Create a temporary directory for the database let db_path = std::env::temp_dir().join("ourdb_advanced_example"); std::fs::create_dir_all(&db_path)?; - + println!("Creating database at: {}", db_path.display()); - + // Demonstrate key-value mode (non-incremental) key_value_mode_example(&db_path)?; - + // Demonstrate incremental mode incremental_mode_example(&db_path)?; - + // Demonstrate performance benchmarking performance_benchmark(&db_path)?; - + // Clean up (optional) if std::env::var("KEEP_DB").is_err() { std::fs::remove_dir_all(&db_path)?; @@ -25,16 +25,16 @@ fn main() -> Result<(), ourdb::Error> { } else { println!("Database kept at: {}", db_path.display()); } - + Ok(()) } fn key_value_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> { println!("\n=== Key-Value Mode Example ==="); - + let db_path = base_path.join("key_value"); std::fs::create_dir_all(&db_path)?; - + // Create a new database with key-value mode (non-incremental) let config = OurDBConfig { path: db_path, @@ -43,52 +43,62 @@ fn key_value_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> { keysize: Some(2), // Small key size for demonstration reset: None, // Don't reset existing database }; - + let mut db = OurDB::new(config)?; - + // In key-value mode, we must provide IDs explicitly let custom_ids = [100, 200, 300, 400, 500]; - + // Store data with custom IDs for (i, &id) in custom_ids.iter().enumerate() { let data = format!("Record with custom ID {}", id); - db.set(OurDBSetArgs { id: Some(id), data: data.as_bytes() })?; - println!("Stored record {} with custom ID: {}", i+1, id); + db.set(OurDBSetArgs { + id: Some(id), + data: data.as_bytes(), + })?; + println!("Stored record {} with custom ID: {}", i + 1, id); } - + // Retrieve data by custom IDs for &id in &custom_ids { let retrieved = db.get(id)?; - println!("Retrieved ID {}: {}", id, String::from_utf8_lossy(&retrieved)); + println!( + "Retrieved ID {}: {}", + id, + String::from_utf8_lossy(&retrieved) + ); } - + // Update and track history let id_to_update = custom_ids[2]; // ID 300 for i in 1..=3 { let updated_data = format!("Updated record {} (version {})", id_to_update, i); - db.set(OurDBSetArgs { id: Some(id_to_update), data: updated_data.as_bytes() })?; + db.set(OurDBSetArgs { + id: Some(id_to_update), + data: updated_data.as_bytes(), + })?; println!("Updated ID {} (version {})", id_to_update, i); } - + // Get history for the updated record let history = db.get_history(id_to_update, 5)?; println!("History for ID {} (most recent first):", id_to_update); for (i, entry) in history.iter().enumerate() { println!(" Version {}: {}", i, String::from_utf8_lossy(entry)); } - + db.close()?; println!("Key-value mode example completed"); - + Ok(()) } fn incremental_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> { println!("\n=== Incremental Mode Example ==="); - + let db_path = base_path.join("incremental"); std::fs::create_dir_all(&db_path)?; - + // Create a new database with incremental mode let config = OurDBConfig { path: db_path, @@ -97,42 +107,49 @@ fn incremental_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> { keysize: Some(3), // 3-byte keys reset: None, // Don't reset existing database }; - + let mut db = OurDB::new(config)?; - + // In incremental mode, IDs are auto-generated let mut assigned_ids = Vec::new(); - + // Store multiple records and collect assigned IDs for i in 1..=5 { let data = format!("Auto-increment record {}", i); - let id = db.set(OurDBSetArgs { id: None, data: data.as_bytes() })?; + let id = db.set(OurDBSetArgs { + id: None, + data: data.as_bytes(), + })?; assigned_ids.push(id); println!("Stored record {} with auto-assigned ID: {}", i, id); } - + // Check next ID let next_id = db.get_next_id()?; println!("Next ID to be assigned: {}", next_id); - + // Retrieve all records for &id in &assigned_ids { let retrieved = db.get(id)?; - println!("Retrieved ID {}: {}", id, String::from_utf8_lossy(&retrieved)); + println!( + "Retrieved ID {}: {}", + id, + String::from_utf8_lossy(&retrieved) + ); } - + db.close()?; println!("Incremental mode example completed"); - + Ok(()) } fn performance_benchmark(base_path: &PathBuf) -> Result<(), ourdb::Error> { println!("\n=== Performance Benchmark ==="); - + let db_path = base_path.join("benchmark"); std::fs::create_dir_all(&db_path)?; - + // Create a new database let config = OurDBConfig { path: db_path, @@ -141,62 +158,74 @@ fn performance_benchmark(base_path: &PathBuf) -> Result<(), ourdb::Error> { keysize: Some(4), // 4-byte keys reset: None, // Don't reset existing database }; - + let mut db = OurDB::new(config)?; - + // Number of operations for the benchmark let num_operations = 1000; let data_size = 100; // bytes per record - + // Prepare test data let test_data = vec![b'A'; data_size]; - + // Benchmark write operations println!("Benchmarking {} write operations...", num_operations); let start = Instant::now(); - + let mut ids = Vec::with_capacity(num_operations); for _ in 0..num_operations { - let id = db.set(OurDBSetArgs { id: None, data: &test_data })?; + let id = db.set(OurDBSetArgs { + id: None, + data: &test_data, + })?; ids.push(id); } - + let write_duration = start.elapsed(); let writes_per_second = num_operations as f64 / write_duration.as_secs_f64(); - println!("Write performance: {:.2} ops/sec ({:.2} ms/op)", - writes_per_second, - write_duration.as_secs_f64() * 1000.0 / num_operations as f64); - + println!( + "Write performance: {:.2} ops/sec ({:.2} ms/op)", + writes_per_second, + write_duration.as_secs_f64() * 1000.0 / num_operations as f64 + ); + // Benchmark read operations println!("Benchmarking {} read operations...", num_operations); let start = Instant::now(); - + for &id in &ids { let _ = db.get(id)?; } - + let read_duration = start.elapsed(); let reads_per_second = num_operations as f64 / read_duration.as_secs_f64(); - println!("Read performance: {:.2} ops/sec ({:.2} ms/op)", - reads_per_second, - read_duration.as_secs_f64() * 1000.0 / num_operations as f64); - + println!( + "Read performance: {:.2} ops/sec ({:.2} ms/op)", + reads_per_second, + read_duration.as_secs_f64() * 1000.0 / num_operations as f64 + ); + // Benchmark update operations println!("Benchmarking {} update operations...", num_operations); let start = Instant::now(); - + for &id in &ids { - db.set(OurDBSetArgs { id: Some(id), data: &test_data })?; + db.set(OurDBSetArgs { + id: Some(id), + data: &test_data, + })?; } - + let update_duration = start.elapsed(); let updates_per_second = num_operations as f64 / update_duration.as_secs_f64(); - println!("Update performance: {:.2} ops/sec ({:.2} ms/op)", - updates_per_second, - update_duration.as_secs_f64() * 1000.0 / num_operations as f64); - + println!( + "Update performance: {:.2} ops/sec ({:.2} ms/op)", + updates_per_second, + update_duration.as_secs_f64() * 1000.0 / num_operations as f64 + ); + db.close()?; println!("Performance benchmark completed"); - + Ok(()) } diff --git a/ourdb/examples/basic_usage.rs b/ourdb/examples/basic_usage.rs index 5faf88c..6d160e7 100644 --- a/ourdb/examples/basic_usage.rs +++ b/ourdb/examples/basic_usage.rs @@ -4,9 +4,9 @@ fn main() -> Result<(), ourdb::Error> { // Create a temporary directory for the database let db_path = std::env::temp_dir().join("ourdb_example"); std::fs::create_dir_all(&db_path)?; - + println!("Creating database at: {}", db_path.display()); - + // Create a new database with incremental mode enabled let config = OurDBConfig { path: db_path.clone(), @@ -15,51 +15,68 @@ fn main() -> Result<(), ourdb::Error> { keysize: None, // Use default (4 bytes) reset: None, // Don't reset existing database }; - + let mut db = OurDB::new(config)?; - + // Store some data with auto-generated IDs let data1 = b"First record"; - let id1 = db.set(OurDBSetArgs { id: None, data: data1 })?; + let id1 = db.set(OurDBSetArgs { + id: None, + data: data1, + })?; println!("Stored first record with ID: {}", id1); - + let data2 = b"Second record"; - let id2 = db.set(OurDBSetArgs { id: None, data: data2 })?; + let id2 = db.set(OurDBSetArgs { + id: None, + data: data2, + })?; println!("Stored second record with ID: {}", id2); - + // Retrieve and print the data let retrieved1 = db.get(id1)?; - println!("Retrieved ID {}: {}", id1, String::from_utf8_lossy(&retrieved1)); - + println!( + "Retrieved ID {}: {}", + id1, + String::from_utf8_lossy(&retrieved1) + ); + let retrieved2 = db.get(id2)?; - println!("Retrieved ID {}: {}", id2, String::from_utf8_lossy(&retrieved2)); - + println!( + "Retrieved ID {}: {}", + id2, + String::from_utf8_lossy(&retrieved2) + ); + // Update a record to demonstrate history tracking let updated_data = b"Updated first record"; - db.set(OurDBSetArgs { id: Some(id1), data: updated_data })?; + db.set(OurDBSetArgs { + id: Some(id1), + data: updated_data, + })?; println!("Updated record with ID: {}", id1); - + // Get history for the updated record let history = db.get_history(id1, 2)?; println!("History for ID {}:", id1); for (i, entry) in history.iter().enumerate() { println!(" Version {}: {}", i, String::from_utf8_lossy(entry)); } - + // Delete a record db.delete(id2)?; println!("Deleted record with ID: {}", id2); - + // Verify deletion match db.get(id2) { Ok(_) => println!("Record still exists (unexpected)"), Err(e) => println!("Verified deletion: {}", e), } - + // Close the database db.close()?; println!("Database closed successfully"); - + // Clean up (optional) if std::env::var("KEEP_DB").is_err() { std::fs::remove_dir_all(&db_path)?; @@ -67,6 +84,6 @@ fn main() -> Result<(), ourdb::Error> { } else { println!("Database kept at: {}", db_path.display()); } - + Ok(()) } diff --git a/ourdb/examples/benchmark.rs b/ourdb/examples/benchmark.rs index b670de8..1004dde 100644 --- a/ourdb/examples/benchmark.rs +++ b/ourdb/examples/benchmark.rs @@ -4,12 +4,12 @@ use std::time::Instant; fn main() -> Result<(), ourdb::Error> { // Parse command-line arguments let args: Vec = std::env::args().collect(); - + // Default values let mut incremental_mode = true; let mut keysize: u8 = 4; let mut num_operations = 10000; - + // Parse arguments for i in 1..args.len() { if args[i] == "--no-incremental" { @@ -20,13 +20,13 @@ fn main() -> Result<(), ourdb::Error> { num_operations = args[i + 1].parse().unwrap_or(10000); } } - + // Create a temporary directory for the database let db_path = std::env::temp_dir().join("ourdb_benchmark"); std::fs::create_dir_all(&db_path)?; - + println!("Database path: {}", db_path.display()); - + // Create a new database let config = OurDBConfig { path: db_path.clone(), @@ -35,73 +35,90 @@ fn main() -> Result<(), ourdb::Error> { keysize: Some(keysize), reset: Some(true), // Reset the database for benchmarking }; - + let mut db = OurDB::new(config)?; - + // Prepare test data (100 bytes per record) let test_data = vec![b'A'; 100]; - + // Benchmark write operations - println!("Benchmarking {} write operations (incremental: {}, keysize: {})...", - num_operations, incremental_mode, keysize); - + println!( + "Benchmarking {} write operations (incremental: {}, keysize: {})...", + num_operations, incremental_mode, keysize + ); + let start = Instant::now(); - + let mut ids = Vec::with_capacity(num_operations); for _ in 0..num_operations { let id = if incremental_mode { - db.set(OurDBSetArgs { id: None, data: &test_data })? + db.set(OurDBSetArgs { + id: None, + data: &test_data, + })? } else { // In non-incremental mode, we need to provide IDs let id = ids.len() as u32 + 1; - db.set(OurDBSetArgs { id: Some(id), data: &test_data })?; + db.set(OurDBSetArgs { + id: Some(id), + data: &test_data, + })?; id }; ids.push(id); } - + let write_duration = start.elapsed(); let writes_per_second = num_operations as f64 / write_duration.as_secs_f64(); - - println!("Write performance: {:.2} ops/sec ({:.2} ms/op)", - writes_per_second, - write_duration.as_secs_f64() * 1000.0 / num_operations as f64); - + + println!( + "Write performance: {:.2} ops/sec ({:.2} ms/op)", + writes_per_second, + write_duration.as_secs_f64() * 1000.0 / num_operations as f64 + ); + // Benchmark read operations println!("Benchmarking {} read operations...", num_operations); - + let start = Instant::now(); - + for &id in &ids { let _ = db.get(id)?; } - + let read_duration = start.elapsed(); let reads_per_second = num_operations as f64 / read_duration.as_secs_f64(); - - println!("Read performance: {:.2} ops/sec ({:.2} ms/op)", - reads_per_second, - read_duration.as_secs_f64() * 1000.0 / num_operations as f64); - + + println!( + "Read performance: {:.2} ops/sec ({:.2} ms/op)", + reads_per_second, + read_duration.as_secs_f64() * 1000.0 / num_operations as f64 + ); + // Benchmark update operations println!("Benchmarking {} update operations...", num_operations); - + let start = Instant::now(); - + for &id in &ids { - db.set(OurDBSetArgs { id: Some(id), data: &test_data })?; + db.set(OurDBSetArgs { + id: Some(id), + data: &test_data, + })?; } - + let update_duration = start.elapsed(); let updates_per_second = num_operations as f64 / update_duration.as_secs_f64(); - - println!("Update performance: {:.2} ops/sec ({:.2} ms/op)", - updates_per_second, - update_duration.as_secs_f64() * 1000.0 / num_operations as f64); - + + println!( + "Update performance: {:.2} ops/sec ({:.2} ms/op)", + updates_per_second, + update_duration.as_secs_f64() * 1000.0 / num_operations as f64 + ); + // Clean up db.close()?; std::fs::remove_dir_all(&db_path)?; - + Ok(()) } diff --git a/ourdb/examples/main.rs b/ourdb/examples/main.rs index 6d1514e..546eff1 100644 --- a/ourdb/examples/main.rs +++ b/ourdb/examples/main.rs @@ -13,9 +13,9 @@ fn main() -> Result<(), Box> { .as_secs(); let db_path = temp_dir().join(format!("ourdb_example_{}", timestamp)); std::fs::create_dir_all(&db_path)?; - + println!("Creating database at: {}", db_path.display()); - + // Create a new OurDB instance let config = OurDBConfig { path: db_path.clone(), @@ -24,51 +24,60 @@ fn main() -> Result<(), Box> { keysize: None, reset: Some(false), }; - + let mut db = OurDB::new(config)?; println!("Database created successfully"); - + // Store some data let test_data = b"Hello, OurDB!"; - let id = db.set(OurDBSetArgs { id: None, data: test_data })?; + let id = db.set(OurDBSetArgs { + id: None, + data: test_data, + })?; println!("\nStored data with ID: {}", id); - + // Retrieve the data let retrieved = db.get(id)?; println!("Retrieved data: {}", String::from_utf8_lossy(&retrieved)); - + // Update the data let updated_data = b"Updated data in OurDB!"; - db.set(OurDBSetArgs { id: Some(id), data: updated_data })?; + db.set(OurDBSetArgs { + id: Some(id), + data: updated_data, + })?; println!("\nUpdated data with ID: {}", id); - + // Retrieve the updated data let retrieved = db.get(id)?; - println!("Retrieved updated data: {}", String::from_utf8_lossy(&retrieved)); - + println!( + "Retrieved updated data: {}", + String::from_utf8_lossy(&retrieved) + ); + // Get history let history = db.get_history(id, 2)?; println!("\nHistory for ID {}:", id); for (i, data) in history.iter().enumerate() { println!(" Version {}: {}", i + 1, String::from_utf8_lossy(data)); } - + // Delete the data db.delete(id)?; println!("\nDeleted data with ID: {}", id); - + // Try to retrieve the deleted data (should fail) match db.get(id) { Ok(_) => println!("Data still exists (unexpected)"), Err(e) => println!("Verified deletion: {}", e), } - + println!("\nExample completed successfully!"); - + // Clean up db.close()?; std::fs::remove_dir_all(&db_path)?; println!("Cleaned up database directory"); - + Ok(()) } diff --git a/ourdb/src/backend.rs b/ourdb/src/backend.rs index d7d94fe..0a8dbe2 100644 --- a/ourdb/src/backend.rs +++ b/ourdb/src/backend.rs @@ -1,7 +1,6 @@ use std::fs::{self, File, OpenOptions}; use std::io::{Read, Seek, SeekFrom, Write}; - use crc32fast::Hasher; use crate::error::Error; @@ -27,11 +26,8 @@ impl OurDB { } // Open the file fresh - let file = OpenOptions::new() - .read(true) - .write(true) - .open(&path)?; - + let file = OpenOptions::new().read(true).write(true).open(&path)?; + self.file = Some(file); self.file_nr = file_nr; @@ -42,10 +38,10 @@ impl OurDB { pub(crate) fn create_new_db_file(&mut self, file_nr: u16) -> Result<(), Error> { let new_file_path = self.path.join(format!("{}.db", file_nr)); let mut file = File::create(&new_file_path)?; - + // Write a single byte to make all positions start from 1 file.write_all(&[0u8])?; - + Ok(()) } @@ -54,17 +50,17 @@ impl OurDB { // For keysize 2, 3, or 4, we can only use file_nr 0 if self.lookup.keysize() <= 4 { let path = self.path.join("0.db"); - + if !path.exists() { self.create_new_db_file(0)?; } - + return Ok(0); } - + // For keysize 6, we can use multiple files let path = self.path.join(format!("{}.db", self.last_used_file_nr)); - + if !path.exists() { self.create_new_db_file(self.last_used_file_nr)?; return Ok(self.last_used_file_nr); @@ -80,30 +76,36 @@ impl OurDB { } /// Stores data at the specified ID with history tracking - pub(crate) fn set_(&mut self, id: u32, old_location: Location, data: &[u8]) -> Result<(), Error> { + pub(crate) fn set_( + &mut self, + id: u32, + old_location: Location, + data: &[u8], + ) -> Result<(), Error> { // Validate data size - maximum is u16::MAX (65535 bytes or ~64KB) if data.len() > u16::MAX as usize { - return Err(Error::InvalidOperation( - format!("Data size exceeds maximum allowed size of {} bytes", u16::MAX) - )); + return Err(Error::InvalidOperation(format!( + "Data size exceeds maximum allowed size of {} bytes", + u16::MAX + ))); } // Get file number to use let file_nr = self.get_file_nr()?; - + // Select the file self.db_file_select(file_nr)?; // Get current file position for lookup - let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?; + let file = self + .file + .as_mut() + .ok_or_else(|| Error::Other("No file open".to_string()))?; file.seek(SeekFrom::End(0))?; let position = file.stream_position()? as u32; // Create new location - let new_location = Location { - file_nr, - position, - }; + let new_location = Location { file_nr, position }; // Calculate CRC of data let crc = calculate_crc(data); @@ -144,13 +146,19 @@ impl OurDB { /// Retrieves data at the specified location pub(crate) fn get_(&mut self, location: Location) -> Result, Error> { if location.position == 0 { - return Err(Error::NotFound(format!("Record not found, location: {:?}", location))); + return Err(Error::NotFound(format!( + "Record not found, location: {:?}", + location + ))); } // Select the file self.db_file_select(location.file_nr)?; - let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?; + let file = self + .file + .as_mut() + .ok_or_else(|| Error::Other("No file open".to_string()))?; // Read header file.seek(SeekFrom::Start(location.position as u64))?; @@ -161,10 +169,10 @@ impl OurDB { let size = u16::from(header[0]) | (u16::from(header[1]) << 8); // Parse CRC (4 bytes) - let stored_crc = u32::from(header[2]) - | (u32::from(header[3]) << 8) - | (u32::from(header[4]) << 16) - | (u32::from(header[5]) << 24); + let stored_crc = u32::from(header[2]) + | (u32::from(header[3]) << 8) + | (u32::from(header[4]) << 16) + | (u32::from(header[5]) << 24); // Read data let mut data = vec![0u8; size as usize]; @@ -173,7 +181,9 @@ impl OurDB { // Verify CRC let calculated_crc = calculate_crc(&data); if calculated_crc != stored_crc { - return Err(Error::DataCorruption("CRC mismatch: data corruption detected".to_string())); + return Err(Error::DataCorruption( + "CRC mismatch: data corruption detected".to_string(), + )); } Ok(data) @@ -188,7 +198,10 @@ impl OurDB { // Select the file self.db_file_select(location.file_nr)?; - let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?; + let file = self + .file + .as_mut() + .ok_or_else(|| Error::Other("No file open".to_string()))?; // Skip size and CRC (6 bytes) file.seek(SeekFrom::Start(location.position as u64 + 6))?; @@ -210,7 +223,10 @@ impl OurDB { // Select the file self.db_file_select(location.file_nr)?; - let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?; + let file = self + .file + .as_mut() + .ok_or_else(|| Error::Other("No file open".to_string()))?; // Read size first file.seek(SeekFrom::Start(location.position as u64))?; @@ -240,7 +256,7 @@ impl OurDB { for entry in fs::read_dir(&self.path)? { let entry = entry?; let path = entry.path(); - + if path.is_file() && path.extension().map_or(false, |ext| ext == "db") { if let Some(stem) = path.file_stem() { if let Ok(file_nr) = stem.to_string_lossy().parse::() { @@ -254,42 +270,42 @@ impl OurDB { for file_nr in file_numbers { let src_path = self.path.join(format!("{}.db", file_nr)); let temp_file_path = temp_path.join(format!("{}.db", file_nr)); - + // Create new file let mut temp_file = File::create(&temp_file_path)?; temp_file.write_all(&[0u8])?; // Initialize with a byte - + // Open source file let mut src_file = File::open(&src_path)?; - + // Read and process records let mut buffer = vec![0u8; 1024]; // Read in chunks let mut _position = 0; - + while let Ok(bytes_read) = src_file.read(&mut buffer) { if bytes_read == 0 { break; } - + // Process the chunk // This is a simplified version - in a real implementation, // you would need to handle records that span chunk boundaries - + _position += bytes_read; } - + // TODO: Implement proper record copying and position updating // This would involve: // 1. Reading each record from the source file // 2. If not deleted (all zeros), copy to temp file // 3. Update lookup table with new positions } - + // TODO: Replace original files with temp files - + // Clean up fs::remove_dir_all(&temp_path)?; - + Ok(()) } } @@ -304,7 +320,7 @@ fn calculate_crc(data: &[u8]) -> u32 { #[cfg(test)] mod tests { use std::path::PathBuf; - + use crate::{OurDB, OurDBConfig, OurDBSetArgs}; use std::env::temp_dir; use std::time::{SystemTime, UNIX_EPOCH}; @@ -320,26 +336,30 @@ mod tests { #[test] fn test_backend_operations() { let temp_dir = get_temp_dir(); - + let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: false, file_size: None, keysize: None, - reset: None, // Don't reset existing database + reset: None, // Don't reset existing database }; - + let mut db = OurDB::new(config).unwrap(); - + // Test set and get let test_data = b"Test data for backend operations"; let id = 1; - - db.set(OurDBSetArgs { id: Some(id), data: test_data }).unwrap(); - + + db.set(OurDBSetArgs { + id: Some(id), + data: test_data, + }) + .unwrap(); + let retrieved = db.get(id).unwrap(); assert_eq!(retrieved, test_data); - + // Clean up db.destroy().unwrap(); } diff --git a/ourdb/src/error.rs b/ourdb/src/error.rs index 9819066..5b240d2 100644 --- a/ourdb/src/error.rs +++ b/ourdb/src/error.rs @@ -6,23 +6,23 @@ pub enum Error { /// IO errors from file operations #[error("IO error: {0}")] Io(#[from] std::io::Error), - + /// Data corruption errors #[error("Data corruption: {0}")] DataCorruption(String), - + /// Invalid operation errors #[error("Invalid operation: {0}")] InvalidOperation(String), - + /// Lookup table errors #[error("Lookup error: {0}")] LookupError(String), - + /// Record not found errors #[error("Record not found: {0}")] NotFound(String), - + /// Other errors #[error("Error: {0}")] Other(String), diff --git a/ourdb/src/lib.rs b/ourdb/src/lib.rs index abdce97..aee3a4a 100644 --- a/ourdb/src/lib.rs +++ b/ourdb/src/lib.rs @@ -1,7 +1,7 @@ +mod backend; mod error; mod location; mod lookup; -mod backend; pub use error::Error; pub use location::Location; @@ -62,7 +62,7 @@ impl OurDB { if config.reset.unwrap_or(false) && config.path.exists() { std::fs::remove_dir_all(&config.path)?; } - + // Create directory if it doesn't exist std::fs::create_dir_all(&config.path)?; @@ -96,11 +96,11 @@ impl OurDB { } /// Sets a value in the database - /// + /// /// In incremental mode: /// - If ID is provided, it updates an existing record /// - If ID is not provided, it creates a new record with auto-generated ID - /// + /// /// In key-value mode: /// - ID must be provided pub fn set(&mut self, args: OurDBSetArgs) -> Result { @@ -110,7 +110,7 @@ impl OurDB { let location = self.lookup.get(id)?; if location.position == 0 { return Err(Error::InvalidOperation( - "Cannot set ID for insertions when incremental mode is enabled".to_string() + "Cannot set ID for insertions when incremental mode is enabled".to_string(), )); } @@ -124,10 +124,12 @@ impl OurDB { } } else { // Using key-value mode - let id = args.id.ok_or_else(|| Error::InvalidOperation( - "ID must be provided when incremental is disabled".to_string() - ))?; - + let id = args.id.ok_or_else(|| { + Error::InvalidOperation( + "ID must be provided when incremental is disabled".to_string(), + ) + })?; + let location = self.lookup.get(id)?; self.set_(id, location, args.data)?; Ok(id) @@ -141,7 +143,7 @@ impl OurDB { } /// Retrieves a list of previous values for the specified key - /// + /// /// The depth parameter controls how many historical values to retrieve (maximum) pub fn get_history(&mut self, id: u32, depth: u8) -> Result>, Error> { let mut result = Vec::new(); @@ -179,7 +181,9 @@ impl OurDB { /// Returns the next ID which will be used when storing in incremental mode pub fn get_next_id(&mut self) -> Result { if !self.incremental_mode { - return Err(Error::InvalidOperation("Incremental mode is not enabled".to_string())); + return Err(Error::InvalidOperation( + "Incremental mode is not enabled".to_string(), + )); } self.lookup.get_next_id() } @@ -212,7 +216,8 @@ impl OurDB { } fn save(&mut self) -> Result<(), Error> { - self.lookup.export_sparse(&self.lookup_dump_path().to_string_lossy())?; + self.lookup + .export_sparse(&self.lookup_dump_path().to_string_lossy())?; Ok(()) } @@ -238,41 +243,50 @@ mod tests { #[test] fn test_basic_operations() { let temp_dir = get_temp_dir(); - + let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: true, file_size: None, keysize: None, - reset: None, // Don't reset existing database + reset: None, // Don't reset existing database }; - + let mut db = OurDB::new(config).unwrap(); - + // Test set and get let test_data = b"Hello, OurDB!"; - let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap(); - + let id = db + .set(OurDBSetArgs { + id: None, + data: test_data, + }) + .unwrap(); + let retrieved = db.get(id).unwrap(); assert_eq!(retrieved, test_data); - + // Test update let updated_data = b"Updated data"; - db.set(OurDBSetArgs { id: Some(id), data: updated_data }).unwrap(); - + db.set(OurDBSetArgs { + id: Some(id), + data: updated_data, + }) + .unwrap(); + let retrieved = db.get(id).unwrap(); assert_eq!(retrieved, updated_data); - + // Test history let history = db.get_history(id, 2).unwrap(); assert_eq!(history.len(), 2); assert_eq!(history[0], updated_data); assert_eq!(history[1], test_data); - + // Test delete db.delete(id).unwrap(); assert!(db.get(id).is_err()); - + // Clean up db.destroy().unwrap(); } diff --git a/ourdb/src/location.rs b/ourdb/src/location.rs index 83af1e5..06a7a89 100644 --- a/ourdb/src/location.rs +++ b/ourdb/src/location.rs @@ -1,7 +1,7 @@ use crate::error::Error; /// Location represents a physical position in a database file -/// +/// /// It consists of a file number and a position within that file. /// This allows OurDB to span multiple files for large datasets. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] @@ -14,7 +14,7 @@ pub struct Location { impl Location { /// Creates a new Location from bytes based on keysize - /// + /// /// - keysize = 2: Only position (2 bytes), file_nr = 0 /// - keysize = 3: Only position (3 bytes), file_nr = 0 /// - keysize = 4: Only position (4 bytes), file_nr = 0 @@ -22,13 +22,18 @@ impl Location { pub fn from_bytes(bytes: &[u8], keysize: u8) -> Result { // Validate keysize if ![2, 3, 4, 6].contains(&keysize) { - return Err(Error::InvalidOperation(format!("Invalid keysize: {}", keysize))); + return Err(Error::InvalidOperation(format!( + "Invalid keysize: {}", + keysize + ))); } // Create padded bytes let mut padded = vec![0u8; keysize as usize]; if bytes.len() > keysize as usize { - return Err(Error::InvalidOperation("Input bytes exceed keysize".to_string())); + return Err(Error::InvalidOperation( + "Input bytes exceed keysize".to_string(), + )); } let start_idx = keysize as usize - bytes.len(); @@ -49,34 +54,39 @@ impl Location { // Verify limits if location.position > 0xFFFF { return Err(Error::InvalidOperation( - "Position exceeds max value for keysize=2 (max 65535)".to_string() + "Position exceeds max value for keysize=2 (max 65535)".to_string(), )); } - }, + } 3 => { // Only position, 3 bytes big endian - location.position = u32::from(padded[0]) << 16 | u32::from(padded[1]) << 8 | u32::from(padded[2]); + location.position = + u32::from(padded[0]) << 16 | u32::from(padded[1]) << 8 | u32::from(padded[2]); location.file_nr = 0; // Verify limits if location.position > 0xFFFFFF { return Err(Error::InvalidOperation( - "Position exceeds max value for keysize=3 (max 16777215)".to_string() + "Position exceeds max value for keysize=3 (max 16777215)".to_string(), )); } - }, + } 4 => { // Only position, 4 bytes big endian - location.position = u32::from(padded[0]) << 24 | u32::from(padded[1]) << 16 - | u32::from(padded[2]) << 8 | u32::from(padded[3]); + location.position = u32::from(padded[0]) << 24 + | u32::from(padded[1]) << 16 + | u32::from(padded[2]) << 8 + | u32::from(padded[3]); location.file_nr = 0; - }, + } 6 => { // 2 bytes file_nr + 4 bytes position, all big endian location.file_nr = u16::from(padded[0]) << 8 | u16::from(padded[1]); - location.position = u32::from(padded[2]) << 24 | u32::from(padded[3]) << 16 - | u32::from(padded[4]) << 8 | u32::from(padded[5]); - }, + location.position = u32::from(padded[2]) << 24 + | u32::from(padded[3]) << 16 + | u32::from(padded[4]) << 8 + | u32::from(padded[5]); + } _ => unreachable!(), } @@ -84,26 +94,26 @@ impl Location { } /// Converts the location to bytes (always 6 bytes) - /// + /// /// Format: [file_nr (2 bytes)][position (4 bytes)] pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::with_capacity(6); - + // Put file_nr first (2 bytes) bytes.push((self.file_nr >> 8) as u8); bytes.push(self.file_nr as u8); - + // Put position next (4 bytes) bytes.push((self.position >> 24) as u8); bytes.push((self.position >> 16) as u8); bytes.push((self.position >> 8) as u8); bytes.push(self.position as u8); - + bytes } /// Converts the location to a u64 value - /// + /// /// The file_nr is stored in the most significant bits pub fn to_u64(&self) -> u64 { (u64::from(self.file_nr) << 32) | u64::from(self.position) diff --git a/ourdb/src/lookup.rs b/ourdb/src/lookup.rs index a636fb0..34d4ed4 100644 --- a/ourdb/src/lookup.rs +++ b/ourdb/src/lookup.rs @@ -16,7 +16,7 @@ pub struct LookupConfig { /// - 2: For databases with < 65,536 records (single file) /// - 3: For databases with < 16,777,216 records (single file) /// - 4: For databases with < 4,294,967,296 records (single file) - /// - 6: For large databases requiring multiple files + /// - 6: For large databases requiring multiple files pub keysize: u8, /// Path for disk-based lookup pub lookuppath: String, @@ -46,7 +46,10 @@ impl LookupTable { pub fn new(config: LookupConfig) -> Result { // Verify keysize is valid if ![2, 3, 4, 6].contains(&config.keysize) { - return Err(Error::InvalidOperation(format!("Invalid keysize: {}", config.keysize))); + return Err(Error::InvalidOperation(format!( + "Invalid keysize: {}", + config.keysize + ))); } let incremental = if config.incremental_mode { @@ -90,7 +93,7 @@ impl LookupTable { if !self.lookuppath.is_empty() { // Disk-based lookup let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); - + // Check file size first let file_size = fs::metadata(&data_path)?.len(); let start_pos = id as u64 * entry_size as u64; @@ -98,7 +101,9 @@ impl LookupTable { if start_pos + entry_size as u64 > file_size { return Err(Error::LookupError(format!( "Invalid read for get in lut: {}: {} would exceed file size {}", - self.lookuppath, start_pos + entry_size as u64, file_size + self.lookuppath, + start_pos + entry_size as u64, + file_size ))); } @@ -108,14 +113,14 @@ impl LookupTable { let mut data = vec![0u8; entry_size]; let bytes_read = file.read(&mut data)?; - + if bytes_read < entry_size { return Err(Error::LookupError(format!( "Incomplete read: expected {} bytes but got {}", entry_size, bytes_read ))); } - + return Location::from_bytes(&data, self.keysize); } @@ -126,7 +131,7 @@ impl LookupTable { let start = (id * self.keysize as u32) as usize; let end = start + entry_size; - + Location::from_bytes(&self.data[start..end], self.keysize) } @@ -142,7 +147,7 @@ impl LookupTable { if id > incremental { return Err(Error::InvalidOperation( - "Cannot set ID for insertions when incremental mode is enabled".to_string() + "Cannot set ID for insertions when incremental mode is enabled".to_string(), )); } } @@ -151,53 +156,64 @@ impl LookupTable { let location_bytes = match self.keysize { 2 => { if location.file_nr != 0 { - return Err(Error::InvalidOperation("file_nr must be 0 for keysize=2".to_string())); + return Err(Error::InvalidOperation( + "file_nr must be 0 for keysize=2".to_string(), + )); } if location.position > 0xFFFF { return Err(Error::InvalidOperation( - "position exceeds max value for keysize=2 (max 65535)".to_string() + "position exceeds max value for keysize=2 (max 65535)".to_string(), )); } vec![(location.position >> 8) as u8, location.position as u8] - }, + } 3 => { if location.file_nr != 0 { - return Err(Error::InvalidOperation("file_nr must be 0 for keysize=3".to_string())); + return Err(Error::InvalidOperation( + "file_nr must be 0 for keysize=3".to_string(), + )); } if location.position > 0xFFFFFF { return Err(Error::InvalidOperation( - "position exceeds max value for keysize=3 (max 16777215)".to_string() + "position exceeds max value for keysize=3 (max 16777215)".to_string(), )); } vec![ (location.position >> 16) as u8, (location.position >> 8) as u8, - location.position as u8 + location.position as u8, ] - }, + } 4 => { if location.file_nr != 0 { - return Err(Error::InvalidOperation("file_nr must be 0 for keysize=4".to_string())); + return Err(Error::InvalidOperation( + "file_nr must be 0 for keysize=4".to_string(), + )); } vec![ (location.position >> 24) as u8, (location.position >> 16) as u8, (location.position >> 8) as u8, - location.position as u8 + location.position as u8, ] - }, + } 6 => { // Full location with file_nr and position location.to_bytes() - }, - _ => return Err(Error::InvalidOperation(format!("Invalid keysize: {}", self.keysize))), + } + _ => { + return Err(Error::InvalidOperation(format!( + "Invalid keysize: {}", + self.keysize + ))) + } }; if !self.lookuppath.is_empty() { // Disk-based lookup let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); let mut file = OpenOptions::new().write(true).open(data_path)?; - + let start_pos = id as u64 * entry_size as u64; file.seek(SeekFrom::Start(start_pos))?; file.write_all(&location_bytes)?; @@ -207,7 +223,7 @@ impl LookupTable { if start + entry_size > self.data.len() { return Err(Error::LookupError("Index out of bounds".to_string())); } - + for (i, &byte) in location_bytes.iter().enumerate() { self.data[start + i] = byte; } @@ -224,9 +240,9 @@ impl LookupTable { /// Gets the next available ID in incremental mode pub fn get_next_id(&self) -> Result { - let incremental = self.incremental.ok_or_else(|| + let incremental = self.incremental.ok_or_else(|| { Error::InvalidOperation("Lookup table not in incremental mode".to_string()) - )?; + })?; let table_size = if !self.lookuppath.is_empty() { let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); @@ -244,9 +260,9 @@ impl LookupTable { /// Increments the index in incremental mode pub fn increment_index(&mut self) -> Result<(), Error> { - let mut incremental = self.incremental.ok_or_else(|| + let mut incremental = self.incremental.ok_or_else(|| { Error::InvalidOperation("Lookup table not in incremental mode".to_string()) - )?; + })?; incremental += 1; self.incremental = Some(incremental); @@ -299,10 +315,10 @@ impl LookupTable { for id in 0..max_entries { file.seek(SeekFrom::Start(id * entry_size as u64))?; - + let mut buffer = vec![0u8; entry_size]; let bytes_read = file.read(&mut buffer)?; - + if bytes_read < entry_size { break; } @@ -317,11 +333,11 @@ impl LookupTable { } else { // For memory-based lookup let max_entries = self.data.len() / entry_size; - + for id in 0..max_entries { let start = id * entry_size; let entry = &self.data[start..start + entry_size]; - + // Check if entry is non-zero if entry.iter().any(|&b| b != 0) { // Write ID (4 bytes) + entry @@ -344,7 +360,7 @@ impl LookupTable { if data.len() % record_size != 0 { return Err(Error::DataCorruption( - "Invalid sparse data format: size mismatch".to_string() + "Invalid sparse data format: size mismatch".to_string(), )); } @@ -359,10 +375,10 @@ impl LookupTable { // Extract entry let entry = &data[chunk_start + 4..chunk_start + record_size]; - + // Create location from entry let location = Location::from_bytes(entry, self.keysize)?; - + // Set the entry self.set(id, location)?; } @@ -380,13 +396,13 @@ impl LookupTable { let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); let mut file = File::open(&data_path)?; let file_size = fs::metadata(&data_path)?.len(); - + let mut buffer = vec![0u8; entry_size]; let mut pos = 0u32; while (pos as u64 * entry_size as u64) < file_size { file.seek(SeekFrom::Start(pos as u64 * entry_size as u64))?; - + let bytes_read = file.read(&mut buffer)?; if bytes_read == 0 || bytes_read < entry_size { break; @@ -396,7 +412,7 @@ impl LookupTable { if location.position != 0 || location.file_nr != 0 { last_id = pos; } - + pos += 1; } } else { @@ -422,7 +438,7 @@ fn get_incremental_info(config: &LookupConfig) -> Result { if !config.lookuppath.is_empty() { let inc_path = Path::new(&config.lookuppath).join(INCREMENTAL_FILE_NAME); - + if !inc_path.exists() { // Create a separate file for storing the incremental value fs::write(&inc_path, "1")?; @@ -437,7 +453,7 @@ fn get_incremental_info(config: &LookupConfig) -> Result { 1 } }; - + Ok(incremental) } else { // For memory-based lookup, start with 1 @@ -447,9 +463,9 @@ fn get_incremental_info(config: &LookupConfig) -> Result { #[cfg(test)] mod tests { - use std::path::PathBuf; use super::*; use std::env::temp_dir; + use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; fn get_temp_dir() -> PathBuf { @@ -468,25 +484,25 @@ mod tests { lookuppath: String::new(), incremental_mode: true, }; - + let mut lookup = LookupTable::new(config).unwrap(); - + // Test set and get let location = Location { file_nr: 0, position: 12345, }; - + lookup.set(1, location).unwrap(); let retrieved = lookup.get(1).unwrap(); - + assert_eq!(retrieved.file_nr, location.file_nr); assert_eq!(retrieved.position, location.position); - + // Test incremental mode let next_id = lookup.get_next_id().unwrap(); assert_eq!(next_id, 2); - + lookup.increment_index().unwrap(); let next_id = lookup.get_next_id().unwrap(); assert_eq!(next_id, 3); @@ -496,28 +512,28 @@ mod tests { fn test_disk_lookup() { let temp_dir = get_temp_dir(); fs::create_dir_all(&temp_dir).unwrap(); - + let config = LookupConfig { size: 1000, keysize: 4, lookuppath: temp_dir.to_string_lossy().to_string(), incremental_mode: true, }; - + let mut lookup = LookupTable::new(config).unwrap(); - + // Test set and get let location = Location { file_nr: 0, position: 12345, }; - + lookup.set(1, location).unwrap(); let retrieved = lookup.get(1).unwrap(); - + assert_eq!(retrieved.file_nr, location.file_nr); assert_eq!(retrieved.position, location.position); - + // Clean up fs::remove_dir_all(temp_dir).unwrap(); } diff --git a/ourdb/tests/integration_tests.rs b/ourdb/tests/integration_tests.rs index 02d2930..f4e09f8 100644 --- a/ourdb/tests/integration_tests.rs +++ b/ourdb/tests/integration_tests.rs @@ -1,9 +1,9 @@ use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; +use rand; use std::env::temp_dir; use std::fs; use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; -use rand; // Helper function to create a unique temporary directory for tests fn get_temp_dir() -> PathBuf { @@ -13,56 +13,64 @@ fn get_temp_dir() -> PathBuf { .as_nanos(); let random_part = rand::random::(); let dir = temp_dir().join(format!("ourdb_test_{}_{}", timestamp, random_part)); - + // Ensure the directory exists and is empty if dir.exists() { std::fs::remove_dir_all(&dir).unwrap(); } std::fs::create_dir_all(&dir).unwrap(); - + dir } #[test] fn test_basic_operations() { let temp_dir = get_temp_dir(); - + // Create a new database with incremental mode let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: true, file_size: None, keysize: None, - reset: None + reset: None, }; - - + let mut db = OurDB::new(config).unwrap(); - + // Test set and get let test_data = b"Hello, OurDB!"; - let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap(); - + let id = db + .set(OurDBSetArgs { + id: None, + data: test_data, + }) + .unwrap(); + let retrieved = db.get(id).unwrap(); assert_eq!(retrieved, test_data); - + // Test update let updated_data = b"Updated data"; - db.set(OurDBSetArgs { id: Some(id), data: updated_data }).unwrap(); - + db.set(OurDBSetArgs { + id: Some(id), + data: updated_data, + }) + .unwrap(); + let retrieved = db.get(id).unwrap(); assert_eq!(retrieved, updated_data); - + // Test history let history = db.get_history(id, 2).unwrap(); assert_eq!(history.len(), 2); assert_eq!(history[0], updated_data); assert_eq!(history[1], test_data); - + // Test delete db.delete(id).unwrap(); assert!(db.get(id).is_err()); - + // Clean up db.destroy().unwrap(); } @@ -70,30 +78,33 @@ fn test_basic_operations() { #[test] fn test_key_value_mode() { let temp_dir = get_temp_dir(); - - + // Create a new database with key-value mode let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: false, file_size: None, keysize: None, - reset: None + reset: None, }; - + let mut db = OurDB::new(config).unwrap(); - + // Test set with explicit ID let test_data = b"Key-value data"; let id = 42; - db.set(OurDBSetArgs { id: Some(id), data: test_data }).unwrap(); - + db.set(OurDBSetArgs { + id: Some(id), + data: test_data, + }) + .unwrap(); + let retrieved = db.get(id).unwrap(); assert_eq!(retrieved, test_data); - + // Verify next_id fails in key-value mode assert!(db.get_next_id().is_err()); - + // Clean up db.destroy().unwrap(); } @@ -101,33 +112,42 @@ fn test_key_value_mode() { #[test] fn test_incremental_mode() { let temp_dir = get_temp_dir(); - + // Create a new database with incremental mode let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: true, file_size: None, keysize: None, - reset: None + reset: None, }; - - + let mut db = OurDB::new(config).unwrap(); - + // Test auto-increment IDs let data1 = b"First record"; - let id1 = db.set(OurDBSetArgs { id: None, data: data1 }).unwrap(); - + let id1 = db + .set(OurDBSetArgs { + id: None, + data: data1, + }) + .unwrap(); + let data2 = b"Second record"; - let id2 = db.set(OurDBSetArgs { id: None, data: data2 }).unwrap(); - + let id2 = db + .set(OurDBSetArgs { + id: None, + data: data2, + }) + .unwrap(); + // IDs should be sequential assert_eq!(id2, id1 + 1); - + // Verify get_next_id works let next_id = db.get_next_id().unwrap(); assert_eq!(next_id, id2 + 1); - + // Clean up db.destroy().unwrap(); } @@ -135,8 +155,7 @@ fn test_incremental_mode() { #[test] fn test_persistence() { let temp_dir = get_temp_dir(); - - + // Create data in a new database { let config = OurDBConfig { @@ -144,21 +163,26 @@ fn test_persistence() { incremental_mode: true, file_size: None, keysize: None, - reset: None + reset: None, }; - + let mut db = OurDB::new(config).unwrap(); - + let test_data = b"Persistent data"; - let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap(); - + let id = db + .set(OurDBSetArgs { + id: None, + data: test_data, + }) + .unwrap(); + // Explicitly close the database db.close().unwrap(); - + // ID should be 1 in a new database assert_eq!(id, 1); } - + // Reopen the database and verify data persists { let config = OurDBConfig { @@ -166,19 +190,19 @@ fn test_persistence() { incremental_mode: true, file_size: None, keysize: None, - reset: None + reset: None, }; - + let mut db = OurDB::new(config).unwrap(); - + // Verify data is still there let retrieved = db.get(1).unwrap(); assert_eq!(retrieved, b"Persistent data"); - + // Verify incremental counter persisted let next_id = db.get_next_id().unwrap(); assert_eq!(next_id, 2); - + // Clean up db.destroy().unwrap(); } @@ -188,28 +212,33 @@ fn test_persistence() { fn test_different_keysizes() { for keysize in [2, 3, 4, 6].iter() { let temp_dir = get_temp_dir(); - + // Ensure the directory exists std::fs::create_dir_all(&temp_dir).unwrap(); - + // Create a new database with specified keysize let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: true, file_size: None, keysize: Some(*keysize), - reset: None + reset: None, }; - + let mut db = OurDB::new(config).unwrap(); - + // Test basic operations let test_data = b"Keysize test data"; - let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap(); - + let id = db + .set(OurDBSetArgs { + id: None, + data: test_data, + }) + .unwrap(); + let retrieved = db.get(id).unwrap(); assert_eq!(retrieved, test_data); - + // Clean up db.destroy().unwrap(); } @@ -218,28 +247,33 @@ fn test_different_keysizes() { #[test] fn test_large_data() { let temp_dir = get_temp_dir(); - + // Create a new database let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: true, file_size: None, keysize: None, - reset: None + reset: None, }; - + let mut db = OurDB::new(config).unwrap(); - + // Create a large data set (60KB - within the 64KB limit) let large_data = vec![b'X'; 60 * 1024]; - + // Store and retrieve large data - let id = db.set(OurDBSetArgs { id: None, data: &large_data }).unwrap(); + let id = db + .set(OurDBSetArgs { + id: None, + data: &large_data, + }) + .unwrap(); let retrieved = db.get(id).unwrap(); - + assert_eq!(retrieved.len(), large_data.len()); assert_eq!(retrieved, large_data); - + // Clean up db.destroy().unwrap(); } @@ -247,27 +281,33 @@ fn test_large_data() { #[test] fn test_exceed_size_limit() { let temp_dir = get_temp_dir(); - + // Create a new database let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: true, file_size: None, keysize: None, - reset: None + reset: None, }; - + let mut db = OurDB::new(config).unwrap(); - + // Create data larger than the 64KB limit (70KB) let oversized_data = vec![b'X'; 70 * 1024]; - + // Attempt to store data that exceeds the size limit - let result = db.set(OurDBSetArgs { id: None, data: &oversized_data }); - + let result = db.set(OurDBSetArgs { + id: None, + data: &oversized_data, + }); + // Verify that an error is returned - assert!(result.is_err(), "Expected an error when storing data larger than 64KB"); - + assert!( + result.is_err(), + "Expected an error when storing data larger than 64KB" + ); + // Clean up db.destroy().unwrap(); } @@ -275,46 +315,55 @@ fn test_exceed_size_limit() { #[test] fn test_multiple_files() { let temp_dir = get_temp_dir(); - - + // Create a new database with small file size to force multiple files let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: true, file_size: Some(1024), // Very small file size (1KB) keysize: Some(6), // 6-byte keysize for multiple files - reset: None + reset: None, }; - + let mut db = OurDB::new(config).unwrap(); - + // Store enough data to span multiple files let data_size = 500; // bytes per record let test_data = vec![b'A'; data_size]; - + let mut ids = Vec::new(); for _ in 0..10 { - let id = db.set(OurDBSetArgs { id: None, data: &test_data }).unwrap(); + let id = db + .set(OurDBSetArgs { + id: None, + data: &test_data, + }) + .unwrap(); ids.push(id); } - + // Verify all data can be retrieved for &id in &ids { let retrieved = db.get(id).unwrap(); assert_eq!(retrieved.len(), data_size); } - + // Verify multiple files were created - let files = fs::read_dir(&temp_dir).unwrap() + let files = fs::read_dir(&temp_dir) + .unwrap() .filter_map(Result::ok) .filter(|entry| { let path = entry.path(); path.is_file() && path.extension().map_or(false, |ext| ext == "db") }) .count(); - - assert!(files > 1, "Expected multiple database files, found {}", files); - + + assert!( + files > 1, + "Expected multiple database files, found {}", + files + ); + // Clean up db.destroy().unwrap(); } diff --git a/rhai_client_macros/src/lib.rs b/rhai_client_macros/src/lib.rs index 02a9d97..8b2a17c 100644 --- a/rhai_client_macros/src/lib.rs +++ b/rhai_client_macros/src/lib.rs @@ -1,23 +1,23 @@ use proc_macro::TokenStream; -use quote::{quote, format_ident}; -use syn::{parse_macro_input, ItemFn, FnArg, Pat, PatType, ReturnType, parse_quote}; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, parse_quote, FnArg, ItemFn, Pat, PatType, ReturnType}; /// Procedural macro that generates a Rhai client function for a Rust function. -/// +/// /// When applied to a Rust function, it generates a corresponding function with a '_rhai_client' suffix /// that calls the original function through the Rhai engine. -/// +/// /// # Example -/// +/// /// ```rust /// #[rhai] /// fn hello(name: String) -> String { /// format!("Hello, {}!", name) /// } /// ``` -/// +/// /// This will generate: -/// +/// /// ```rust /// fn hello_rhai_client(engine: &rhai::Engine, name: String) -> String { /// let script = format!("hello(\"{}\")", name.replace("\"", "\\\"")); @@ -27,7 +27,7 @@ use syn::{parse_macro_input, ItemFn, FnArg, Pat, PatType, ReturnType, parse_quot /// }) /// } /// ``` -/// +/// /// Note: The macro handles type conversions between Rust and Rhai types, /// particularly for integer types (Rhai uses i64 internally). #[proc_macro_attribute] @@ -36,15 +36,15 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { let input_fn = parse_macro_input!(item as ItemFn); let fn_name = &input_fn.sig.ident; let fn_name_str = fn_name.to_string(); - + // Create the client function name (original + _rhai_client) let client_fn_name = format_ident!("{}_rhai_client", fn_name); - + // Extract function parameters let mut param_names = Vec::new(); let mut param_types = Vec::new(); let mut param_declarations = Vec::new(); - + for arg in &input_fn.sig.inputs { match arg { FnArg::Typed(PatType { pat, ty, .. }) => { @@ -61,48 +61,55 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { } } } - + // Determine return type let return_type = match &input_fn.sig.output { ReturnType::Default => parse_quote!(()), ReturnType::Type(_, ty) => ty.clone(), }; - + // Generate parameter formatting for the Rhai script - let param_format_strings = param_names.iter().zip(param_types.iter()).map(|(name, ty)| { - let type_str = quote! { #ty }.to_string(); - - // Handle different parameter types - if type_str.contains("String") { - quote! { - format!("\"{}\"" , #name.replace("\"", "\\\"")) + let param_format_strings = param_names + .iter() + .zip(param_types.iter()) + .map(|(name, ty)| { + let type_str = quote! { #ty }.to_string(); + + // Handle different parameter types + if type_str.contains("String") { + quote! { + format!("\"{}\"" , #name.replace("\"", "\\\"")) + } + } else if type_str.contains("bool") { + quote! { + format!("{}", #name) + } + } else if type_str.contains("i32") || type_str.contains("u32") { + // Convert smaller integer types to i64 for Rhai + quote! { + format!("{}", #name as i64) + } + } else if type_str.contains("i64") + || type_str.contains("u64") + || type_str.contains("f32") + || type_str.contains("f64") + { + // Other numeric types + quote! { + format!("{}", #name) + } + } else { + // For complex types, just pass the variable name + // The Rhai engine will handle the conversion + quote! { + #name.to_string() + } } - } else if type_str.contains("bool") { - quote! { - format!("{}", #name) - } - } else if type_str.contains("i32") || type_str.contains("u32") { - // Convert smaller integer types to i64 for Rhai - quote! { - format!("{}", #name as i64) - } - } else if type_str.contains("i64") || type_str.contains("u64") || type_str.contains("f32") || type_str.contains("f64") { - // Other numeric types - quote! { - format!("{}", #name) - } - } else { - // For complex types, just pass the variable name - // The Rhai engine will handle the conversion - quote! { - #name.to_string() - } - } - }); - + }); + // Determine if the return type needs conversion let return_type_str = quote! { #return_type }.to_string(); - + // Generate the client function with appropriate type conversions let client_fn = if return_type_str.contains("i32") || return_type_str.contains("u32") { // For integer return types that need conversion @@ -113,7 +120,7 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { #fn_name_str, &[#(#param_format_strings),*].join(", ") ); - + match engine.eval::(&script) { Ok(result) => result as #return_type, Err(err) => { @@ -132,7 +139,7 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { #fn_name_str, &[#(#param_format_strings),*].join(", ") ); - + match engine.eval::<#return_type>(&script) { Ok(result) => result, Err(err) => { @@ -151,7 +158,7 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { #fn_name_str, &[#(#param_format_strings),*].join(", ") ); - + match engine.eval::<#return_type>(&script) { Ok(result) => result, Err(err) => { @@ -170,7 +177,7 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { #fn_name_str, &[#(#param_format_strings),*].join(", ") ); - + match engine.eval::<#return_type>(&script) { Ok(result) => result, Err(err) => { @@ -181,19 +188,19 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { } } }; - + // Combine the original function and the generated client function let output = quote! { #input_fn - + #client_fn }; - + output.into() } /// A more advanced version of the rhai macro that handles different parameter types better. -/// +/// /// This version properly escapes strings and handles different parameter types more accurately. /// It's recommended to use this version for more complex functions. #[proc_macro_attribute] @@ -202,15 +209,15 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream { let input_fn = parse_macro_input!(item as ItemFn); let fn_name = &input_fn.sig.ident; let fn_name_str = fn_name.to_string(); - + // Create the client function name (original + _rhai_client) let client_fn_name = format_ident!("{}_rhai_client", fn_name); - + // Extract function parameters let mut param_names = Vec::new(); let mut param_types = Vec::new(); let mut param_declarations = Vec::new(); - + for arg in &input_fn.sig.inputs { match arg { FnArg::Typed(PatType { pat, ty, .. }) => { @@ -227,48 +234,53 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream { } } } - + // Determine return type let return_type = match &input_fn.sig.output { ReturnType::Default => parse_quote!(()), ReturnType::Type(_, ty) => ty.clone(), }; - + // Generate parameter formatting for the Rhai script - let param_format_expressions = param_names.iter().zip(param_types.iter()).map(|(name, ty)| { - let type_str = quote! { #ty }.to_string(); - - // Handle different parameter types - if type_str.contains("String") { - quote! { - format!("\"{}\"", #name.replace("\"", "\\\"")) + let param_format_expressions = param_names + .iter() + .zip(param_types.iter()) + .map(|(name, ty)| { + let type_str = quote! { #ty }.to_string(); + + // Handle different parameter types + if type_str.contains("String") { + quote! { + format!("\"{}\"", #name.replace("\"", "\\\"")) + } + } else if type_str.contains("bool") { + quote! { + format!("{}", #name) + } + } else if type_str.contains("i32") || type_str.contains("u32") { + // Convert smaller integer types to i64 for Rhai + quote! { + format!("{}", #name as i64) + } + } else if type_str.contains("i") || type_str.contains("u") || type_str.contains("f") { + // Other numeric types + quote! { + format!("{}", #name) + } + } else { + // Default for other types + quote! { + format!("{:?}", #name) + } } - } else if type_str.contains("bool") { - quote! { - format!("{}", #name) - } - } else if type_str.contains("i32") || type_str.contains("u32") { - // Convert smaller integer types to i64 for Rhai - quote! { - format!("{}", #name as i64) - } - } else if type_str.contains("i") || type_str.contains("u") || type_str.contains("f") { - // Other numeric types - quote! { - format!("{}", #name) - } - } else { - // Default for other types - quote! { - format!("{:?}", #name) - } - } - }).collect::>(); - + }) + .collect::>(); + // Determine if the return type needs conversion let return_type_str = quote! { #return_type }.to_string(); - let needs_return_conversion = return_type_str.contains("i32") || return_type_str.contains("u32"); - + let needs_return_conversion = + return_type_str.contains("i32") || return_type_str.contains("u32"); + // Generate the client function with appropriate type conversions let client_fn = if needs_return_conversion { quote! { @@ -278,7 +290,7 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream { #fn_name_str, &[#(#param_format_expressions),*].join(", ") ); - + match engine.eval::(&script) { Ok(result) => result as #return_type, Err(err) => { @@ -296,7 +308,7 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream { #fn_name_str, &[#(#param_format_expressions),*].join(", ") ); - + match engine.eval::<#return_type>(&script) { Ok(result) => result, Err(err) => { @@ -307,19 +319,19 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream { } } }; - + // Combine the original function and the generated client function let output = quote! { #input_fn - + #client_fn }; - + output.into() } /// Macro that generates a module with Rhai client functions for all functions in scope. -/// +/// /// This macro should be used at the module level to generate Rhai client functions for all /// functions marked with the #[rhai] attribute. #[proc_macro] @@ -329,20 +341,20 @@ pub fn generate_rhai_module(_item: TokenStream) -> TokenStream { // client functions for all of them. // // For simplicity, we'll just return a placeholder implementation - + let output = quote! { /// Register all functions marked with #[rhai] in this module with the Rhai engine. - /// + /// /// This function handles type conversions between Rust and Rhai types automatically. /// For example, it converts between Rust's i32 and Rhai's i64 types. pub fn register_rhai_functions(engine: &mut rhai::Engine) { // This would be generated based on the functions in the module println!("Registering Rhai functions..."); - + // In a real implementation, this would iterate through all functions // marked with #[rhai] and register them with the engine. } }; - + output.into() } diff --git a/tst/examples/basic_usage.rs b/tst/examples/basic_usage.rs index a9bea8a..3bdf6a7 100644 --- a/tst/examples/basic_usage.rs +++ b/tst/examples/basic_usage.rs @@ -1,16 +1,16 @@ -use tst::TST; use std::time::Instant; +use tst::TST; fn main() -> Result<(), tst::Error> { // Create a temporary directory for the database let db_path = std::env::temp_dir().join("tst_example"); std::fs::create_dir_all(&db_path)?; - + println!("Creating ternary search tree at: {}", db_path.display()); - + // Create a new TST let mut tree = TST::new(db_path.to_str().unwrap(), true)?; - + // Store some data println!("Inserting data..."); tree.set("hello", b"world".to_vec())?; @@ -19,50 +19,50 @@ fn main() -> Result<(), tst::Error> { tree.set("apple", b"fruit".to_vec())?; tree.set("application", b"software".to_vec())?; tree.set("banana", b"yellow".to_vec())?; - + // Retrieve and print the data let value = tree.get("hello")?; println!("hello: {}", String::from_utf8_lossy(&value)); - + // List keys with prefix println!("\nListing keys with prefix 'hel':"); let start = Instant::now(); let keys = tree.list("hel")?; let duration = start.elapsed(); - + for key in &keys { println!(" {}", key); } println!("Found {} keys in {:?}", keys.len(), duration); - + // Get all values with prefix println!("\nGetting all values with prefix 'app':"); let start = Instant::now(); let values = tree.getall("app")?; let duration = start.elapsed(); - + for (i, value) in values.iter().enumerate() { println!(" Value {}: {}", i + 1, String::from_utf8_lossy(value)); } println!("Found {} values in {:?}", values.len(), duration); - + // Delete a key println!("\nDeleting 'help'..."); tree.delete("help")?; - + // Verify deletion println!("Listing keys with prefix 'hel' after deletion:"); let keys_after = tree.list("hel")?; for key in &keys_after { println!(" {}", key); } - + // Try to get a deleted key match tree.get("help") { Ok(_) => println!("Unexpectedly found 'help' after deletion!"), Err(e) => println!("As expected, 'help' was not found: {}", e), } - + // Clean up (optional) if std::env::var("KEEP_DB").is_err() { std::fs::remove_dir_all(&db_path)?; @@ -70,6 +70,6 @@ fn main() -> Result<(), tst::Error> { } else { println!("\nDatabase kept at: {}", db_path.display()); } - + Ok(()) -} \ No newline at end of file +} diff --git a/tst/examples/performance.rs b/tst/examples/performance.rs index 87523de..632b592 100644 --- a/tst/examples/performance.rs +++ b/tst/examples/performance.rs @@ -1,20 +1,20 @@ -use tst::TST; -use std::time::{Duration, Instant}; use std::io::{self, Write}; +use std::time::{Duration, Instant}; +use tst::TST; // Function to generate a test value of specified size fn generate_test_value(index: usize, size: usize) -> Vec { let base_value = format!("val{:08}", index); let mut value = Vec::with_capacity(size); - + // Fill with repeating pattern to reach desired size while value.len() < size { value.extend_from_slice(base_value.as_bytes()); } - + // Truncate to exact size value.truncate(size); - + value } @@ -28,39 +28,39 @@ const PERFORMANCE_SAMPLE_SIZE: usize = 100; fn main() -> Result<(), tst::Error> { // Create a temporary directory for the database let db_path = std::env::temp_dir().join("tst_performance_test"); - + // Completely remove and recreate the directory to ensure a clean start if db_path.exists() { std::fs::remove_dir_all(&db_path)?; } std::fs::create_dir_all(&db_path)?; - + println!("Creating ternary search tree at: {}", db_path.display()); println!("Will insert {} records and show progress...", TOTAL_RECORDS); - + // Create a new TST let mut tree = TST::new(db_path.to_str().unwrap(), true)?; - + // Track overall time let start_time = Instant::now(); - + // Track performance metrics let mut insertion_times = Vec::with_capacity(TOTAL_RECORDS / PROGRESS_INTERVAL); let mut last_batch_time = Instant::now(); let mut last_batch_records = 0; - + // Insert records and track progress for i in 0..TOTAL_RECORDS { let key = format!("key:{:08}", i); // Generate a 100-byte value let value = generate_test_value(i, 100); - + // Time the insertion of every Nth record for performance sampling if i % PERFORMANCE_SAMPLE_SIZE == 0 { let insert_start = Instant::now(); tree.set(&key, value)?; let insert_duration = insert_start.elapsed(); - + // Only print detailed timing for specific samples to avoid flooding output if i % (PERFORMANCE_SAMPLE_SIZE * 10) == 0 { println!("Record {}: Insertion took {:?}", i, insert_duration); @@ -68,76 +68,93 @@ fn main() -> Result<(), tst::Error> { } else { tree.set(&key, value)?; } - + // Show progress at intervals if (i + 1) % PROGRESS_INTERVAL == 0 || i == TOTAL_RECORDS - 1 { let records_in_batch = i + 1 - last_batch_records; let batch_duration = last_batch_time.elapsed(); let records_per_second = records_in_batch as f64 / batch_duration.as_secs_f64(); - + insertion_times.push((i + 1, batch_duration)); - - print!("\rProgress: {}/{} records ({:.2}%) - {:.2} records/sec", - i + 1, TOTAL_RECORDS, - (i + 1) as f64 / TOTAL_RECORDS as f64 * 100.0, - records_per_second); + + print!( + "\rProgress: {}/{} records ({:.2}%) - {:.2} records/sec", + i + 1, + TOTAL_RECORDS, + (i + 1) as f64 / TOTAL_RECORDS as f64 * 100.0, + records_per_second + ); io::stdout().flush().unwrap(); - + last_batch_time = Instant::now(); last_batch_records = i + 1; } } - + let total_duration = start_time.elapsed(); println!("\n\nPerformance Summary:"); - println!("Total time to insert {} records: {:?}", TOTAL_RECORDS, total_duration); - println!("Average insertion rate: {:.2} records/second", - TOTAL_RECORDS as f64 / total_duration.as_secs_f64()); - + println!( + "Total time to insert {} records: {:?}", + TOTAL_RECORDS, total_duration + ); + println!( + "Average insertion rate: {:.2} records/second", + TOTAL_RECORDS as f64 / total_duration.as_secs_f64() + ); + // Show performance trend println!("\nPerformance Trend (records inserted vs. time per batch):"); for (i, (record_count, duration)) in insertion_times.iter().enumerate() { - if i % 10 == 0 || i == insertion_times.len() - 1 { // Only show every 10th point to avoid too much output - println!(" After {} records: {:?} for {} records ({:.2} records/sec)", - record_count, - duration, - PROGRESS_INTERVAL, - PROGRESS_INTERVAL as f64 / duration.as_secs_f64()); + if i % 10 == 0 || i == insertion_times.len() - 1 { + // Only show every 10th point to avoid too much output + println!( + " After {} records: {:?} for {} records ({:.2} records/sec)", + record_count, + duration, + PROGRESS_INTERVAL, + PROGRESS_INTERVAL as f64 / duration.as_secs_f64() + ); } } - + // Test access performance with distributed samples println!("\nTesting access performance with distributed samples..."); let mut total_get_time = Duration::new(0, 0); let num_samples = 1000; - + // Use a simple distribution pattern instead of random for i in 0..num_samples { // Distribute samples across the entire range let sample_id = (i * (TOTAL_RECORDS / num_samples)) % TOTAL_RECORDS; let key = format!("key:{:08}", sample_id); - + let get_start = Instant::now(); let _ = tree.get(&key)?; total_get_time += get_start.elapsed(); } - - println!("Average time to retrieve a record: {:?}", - total_get_time / num_samples as u32); - + + println!( + "Average time to retrieve a record: {:?}", + total_get_time / num_samples as u32 + ); + // Test prefix search performance println!("\nTesting prefix search performance..."); let prefixes = ["key:0", "key:1", "key:5", "key:9"]; - + for prefix in &prefixes { let list_start = Instant::now(); let keys = tree.list(prefix)?; let list_duration = list_start.elapsed(); - - println!("Found {} keys with prefix '{}' in {:?}", - keys.len(), prefix, list_duration); + + println!( + "Found {} keys with prefix '{}' in {:?}", + keys.len(), + prefix, + list_duration + ); } - + // Clean up (optional) if std::env::var("KEEP_DB").is_err() { std::fs::remove_dir_all(&db_path)?; @@ -145,6 +162,6 @@ fn main() -> Result<(), tst::Error> { } else { println!("\nDatabase kept at: {}", db_path.display()); } - + Ok(()) -} \ No newline at end of file +} diff --git a/tst/examples/prefix_ops.rs b/tst/examples/prefix_ops.rs index 7d364ef..efbb870 100644 --- a/tst/examples/prefix_ops.rs +++ b/tst/examples/prefix_ops.rs @@ -1,82 +1,137 @@ -use tst::TST; use std::time::Instant; +use tst::TST; fn main() -> Result<(), tst::Error> { // Create a temporary directory for the database let db_path = std::env::temp_dir().join("tst_prefix_example"); std::fs::create_dir_all(&db_path)?; - + println!("Creating ternary search tree at: {}", db_path.display()); - + // Create a new TST let mut tree = TST::new(db_path.to_str().unwrap(), true)?; - + // Insert a variety of keys with different prefixes println!("Inserting data with various prefixes..."); - + // Names let names = [ - "Alice", "Alexander", "Amanda", "Andrew", "Amy", - "Bob", "Barbara", "Benjamin", "Brenda", "Brian", - "Charlie", "Catherine", "Christopher", "Cynthia", "Carl", - "David", "Diana", "Daniel", "Deborah", "Donald", - "Edward", "Elizabeth", "Eric", "Emily", "Ethan" + "Alice", + "Alexander", + "Amanda", + "Andrew", + "Amy", + "Bob", + "Barbara", + "Benjamin", + "Brenda", + "Brian", + "Charlie", + "Catherine", + "Christopher", + "Cynthia", + "Carl", + "David", + "Diana", + "Daniel", + "Deborah", + "Donald", + "Edward", + "Elizabeth", + "Eric", + "Emily", + "Ethan", ]; - + for (i, name) in names.iter().enumerate() { let value = format!("person-{}", i).into_bytes(); tree.set(name, value)?; } - + // Cities let cities = [ - "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", - "Philadelphia", "San Antonio", "San Diego", "Dallas", "San Jose", - "Austin", "Jacksonville", "Fort Worth", "Columbus", "San Francisco", - "Charlotte", "Indianapolis", "Seattle", "Denver", "Washington" + "New York", + "Los Angeles", + "Chicago", + "Houston", + "Phoenix", + "Philadelphia", + "San Antonio", + "San Diego", + "Dallas", + "San Jose", + "Austin", + "Jacksonville", + "Fort Worth", + "Columbus", + "San Francisco", + "Charlotte", + "Indianapolis", + "Seattle", + "Denver", + "Washington", ]; - + for (i, city) in cities.iter().enumerate() { let value = format!("city-{}", i).into_bytes(); tree.set(city, value)?; } - + // Countries let countries = [ - "United States", "Canada", "Mexico", "Brazil", "Argentina", - "United Kingdom", "France", "Germany", "Italy", "Spain", - "China", "Japan", "India", "Australia", "Russia" + "United States", + "Canada", + "Mexico", + "Brazil", + "Argentina", + "United Kingdom", + "France", + "Germany", + "Italy", + "Spain", + "China", + "Japan", + "India", + "Australia", + "Russia", ]; - + for (i, country) in countries.iter().enumerate() { let value = format!("country-{}", i).into_bytes(); tree.set(country, value)?; } - - println!("Total items inserted: {}", names.len() + cities.len() + countries.len()); - + + println!( + "Total items inserted: {}", + names.len() + cities.len() + countries.len() + ); + // Test prefix operations test_prefix(&mut tree, "A")?; test_prefix(&mut tree, "B")?; test_prefix(&mut tree, "C")?; test_prefix(&mut tree, "San")?; test_prefix(&mut tree, "United")?; - + // Test non-existent prefix test_prefix(&mut tree, "Z")?; - + // Test empty prefix (should return all keys) println!("\nTesting empty prefix (should return all keys):"); let start = Instant::now(); let all_keys = tree.list("")?; let duration = start.elapsed(); - - println!("Found {} keys with empty prefix in {:?}", all_keys.len(), duration); + + println!( + "Found {} keys with empty prefix in {:?}", + all_keys.len(), + duration + ); println!("First 5 keys (alphabetically):"); for key in all_keys.iter().take(5) { println!(" {}", key); } - + // Clean up (optional) if std::env::var("KEEP_DB").is_err() { std::fs::remove_dir_all(&db_path)?; @@ -84,39 +139,46 @@ fn main() -> Result<(), tst::Error> { } else { println!("\nDatabase kept at: {}", db_path.display()); } - + Ok(()) } fn test_prefix(tree: &mut TST, prefix: &str) -> Result<(), tst::Error> { println!("\nTesting prefix '{}':", prefix); - + // Test list operation let start = Instant::now(); let keys = tree.list(prefix)?; let list_duration = start.elapsed(); - - println!("Found {} keys with prefix '{}' in {:?}", keys.len(), prefix, list_duration); - + + println!( + "Found {} keys with prefix '{}' in {:?}", + keys.len(), + prefix, + list_duration + ); + if !keys.is_empty() { println!("Keys:"); for key in &keys { println!(" {}", key); } - + // Test getall operation let start = Instant::now(); let values = tree.getall(prefix)?; let getall_duration = start.elapsed(); - + println!("Retrieved {} values in {:?}", values.len(), getall_duration); - println!("First value: {}", - if !values.is_empty() { - String::from_utf8_lossy(&values[0]) - } else { - "None".into() - }); + println!( + "First value: {}", + if !values.is_empty() { + String::from_utf8_lossy(&values[0]) + } else { + "None".into() + } + ); } - + Ok(()) -} \ No newline at end of file +} diff --git a/tst/src/error.rs b/tst/src/error.rs index cbb6e0f..e44ccaa 100644 --- a/tst/src/error.rs +++ b/tst/src/error.rs @@ -1,7 +1,7 @@ //! Error types for the TST module. -use thiserror::Error; use std::io; +use thiserror::Error; /// Error type for TST operations. #[derive(Debug, Error)] @@ -9,28 +9,28 @@ pub enum Error { /// Error from OurDB operations. #[error("OurDB error: {0}")] OurDB(#[from] ourdb::Error), - + /// Error when a key is not found. #[error("Key not found: {0}")] KeyNotFound(String), - + /// Error when a prefix is not found. #[error("Prefix not found: {0}")] PrefixNotFound(String), - + /// Error during serialization. #[error("Serialization error: {0}")] Serialization(String), - + /// Error during deserialization. #[error("Deserialization error: {0}")] Deserialization(String), - + /// Error for invalid operations. #[error("Invalid operation: {0}")] InvalidOperation(String), - + /// IO error. #[error("IO error: {0}")] IO(#[from] io::Error), -} \ No newline at end of file +} diff --git a/tst/src/lib.rs b/tst/src/lib.rs index 10a4e57..3943074 100644 --- a/tst/src/lib.rs +++ b/tst/src/lib.rs @@ -18,7 +18,7 @@ use ourdb::OurDB; pub struct TST { /// Database for persistent storage db: OurDB, - + /// Database ID of the root node root_id: Option, } @@ -119,4 +119,4 @@ impl TST { pub fn getall(&mut self, prefix: &str) -> Result>, Error> { operations::getall(self, prefix) } -} \ No newline at end of file +} diff --git a/tst/src/node.rs b/tst/src/node.rs index badb512..83294d0 100644 --- a/tst/src/node.rs +++ b/tst/src/node.rs @@ -5,19 +5,19 @@ pub struct TSTNode { /// The character stored at this node. pub character: char, - + /// Value stored at this node (empty if not end of key). pub value: Vec, - + /// Whether this node represents the end of a key. pub is_end_of_key: bool, - + /// Reference to the left child node (for characters < current character). pub left_id: Option, - + /// Reference to the middle child node (for next character in key). pub middle_id: Option, - + /// Reference to the right child node (for characters > current character). pub right_id: Option, } @@ -34,7 +34,7 @@ impl TSTNode { right_id: None, } } - + /// Creates a new root node. pub fn new_root() -> Self { Self { @@ -46,4 +46,4 @@ impl TSTNode { right_id: None, } } -} \ No newline at end of file +} diff --git a/tst/src/operations.rs b/tst/src/operations.rs index 49f98fc..a82b48d 100644 --- a/tst/src/operations.rs +++ b/tst/src/operations.rs @@ -9,19 +9,19 @@ use std::path::PathBuf; /// Creates a new TST with the specified database path. pub fn new_tst(path: &str, reset: bool) -> Result { let path_buf = PathBuf::from(path); - + // Create the configuration for OurDB with reset parameter let config = OurDBConfig { path: path_buf.clone(), incremental_mode: true, file_size: Some(1024 * 1024), // 1MB file size for better performance with large datasets - keysize: Some(4), // Use keysize=4 (default) - reset: Some(reset), // Use the reset parameter + keysize: Some(4), // Use keysize=4 (default) + reset: Some(reset), // Use the reset parameter }; - + // Create a new OurDB instance (it will handle reset internally) let mut db = OurDB::new(config)?; - + let root_id = if db.get_next_id()? == 1 || reset { // Create a new root node let root = TSTNode::new_root(); @@ -29,17 +29,14 @@ pub fn new_tst(path: &str, reset: bool) -> Result { id: None, data: &root.serialize(), })?; - + Some(root_id) } else { // Use existing root node Some(1) // Root node always has ID 1 }; - - Ok(TST { - db, - root_id, - }) + + Ok(TST { db, root_id }) } /// Sets a key-value pair in the tree. @@ -47,45 +44,51 @@ pub fn set(tree: &mut TST, key: &str, value: Vec) -> Result<(), Error> { if key.is_empty() { return Err(Error::InvalidOperation("Empty key not allowed".to_string())); } - + let root_id = match tree.root_id { Some(id) => id, None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), }; - + let chars: Vec = key.chars().collect(); set_recursive(tree, root_id, &chars, 0, value)?; - + Ok(()) } /// Recursive helper function for setting a key-value pair. -fn set_recursive(tree: &mut TST, node_id: u32, chars: &[char], pos: usize, value: Vec) -> Result { +fn set_recursive( + tree: &mut TST, + node_id: u32, + chars: &[char], + pos: usize, + value: Vec, +) -> Result { let mut node = tree.get_node(node_id)?; - + if pos >= chars.len() { // We've reached the end of the key node.is_end_of_key = true; node.value = value; return tree.save_node(Some(node_id), &node); } - + let current_char = chars[pos]; - + if node.character == '\0' { // Root node or empty node, set the character node.character = current_char; let node_id = tree.save_node(Some(node_id), &node)?; - + // Continue with the next character if pos + 1 < chars.len() { let new_node = TSTNode::new(chars[pos + 1], Vec::new(), false); let new_id = tree.save_node(None, &new_node)?; - + let mut updated_node = tree.get_node(node_id)?; updated_node.middle_id = Some(new_id); tree.save_node(Some(node_id), &updated_node)?; - + return set_recursive(tree, new_id, chars, pos + 1, value); } else { // This is the last character @@ -95,7 +98,7 @@ fn set_recursive(tree: &mut TST, node_id: u32, chars: &[char], pos: usize, value return tree.save_node(Some(node_id), &updated_node); } } - + if current_char < node.character { // Go left if let Some(left_id) = node.left_id { @@ -104,11 +107,11 @@ fn set_recursive(tree: &mut TST, node_id: u32, chars: &[char], pos: usize, value // Create new left node let new_node = TSTNode::new(current_char, Vec::new(), false); let new_id = tree.save_node(None, &new_node)?; - + // Update current node node.left_id = Some(new_id); tree.save_node(Some(node_id), &node)?; - + return set_recursive(tree, new_id, chars, pos, value); } } else if current_char > node.character { @@ -119,11 +122,11 @@ fn set_recursive(tree: &mut TST, node_id: u32, chars: &[char], pos: usize, value // Create new right node let new_node = TSTNode::new(current_char, Vec::new(), false); let new_id = tree.save_node(None, &new_node)?; - + // Update current node node.right_id = Some(new_id); tree.save_node(Some(node_id), &node)?; - + return set_recursive(tree, new_id, chars, pos, value); } } else { @@ -134,18 +137,18 @@ fn set_recursive(tree: &mut TST, node_id: u32, chars: &[char], pos: usize, value node.value = value; return tree.save_node(Some(node_id), &node); } - + if let Some(middle_id) = node.middle_id { return set_recursive(tree, middle_id, chars, pos + 1, value); } else { // Create new middle node let new_node = TSTNode::new(chars[pos + 1], Vec::new(), false); let new_id = tree.save_node(None, &new_node)?; - + // Update current node node.middle_id = Some(new_id); tree.save_node(Some(node_id), &node)?; - + return set_recursive(tree, new_id, chars, pos + 1, value); } } @@ -156,15 +159,15 @@ pub fn get(tree: &mut TST, key: &str) -> Result, Error> { if key.is_empty() { return Err(Error::InvalidOperation("Empty key not allowed".to_string())); } - + let root_id = match tree.root_id { Some(id) => id, None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), }; - + let chars: Vec = key.chars().collect(); let node_id = find_node(tree, root_id, &chars, 0)?; - + let node = tree.get_node(node_id)?; if node.is_end_of_key { Ok(node.value.clone()) @@ -176,13 +179,13 @@ pub fn get(tree: &mut TST, key: &str) -> Result, Error> { /// Finds a node by key. fn find_node(tree: &mut TST, node_id: u32, chars: &[char], pos: usize) -> Result { let node = tree.get_node(node_id)?; - + if pos >= chars.len() { return Ok(node_id); } - + let current_char = chars[pos]; - + if current_char < node.character { // Go left if let Some(left_id) = node.left_id { @@ -216,21 +219,21 @@ pub fn delete(tree: &mut TST, key: &str) -> Result<(), Error> { if key.is_empty() { return Err(Error::InvalidOperation("Empty key not allowed".to_string())); } - + let root_id = match tree.root_id { Some(id) => id, None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), }; - + let chars: Vec = key.chars().collect(); let node_id = find_node(tree, root_id, &chars, 0)?; - + let mut node = tree.get_node(node_id)?; - + if !node.is_end_of_key { return Err(Error::KeyNotFound(key.to_string())); } - + // If the node has a middle child, just mark it as not end of key if node.middle_id.is_some() || node.left_id.is_some() || node.right_id.is_some() { node.is_end_of_key = false; @@ -238,14 +241,14 @@ pub fn delete(tree: &mut TST, key: &str) -> Result<(), Error> { tree.save_node(Some(node_id), &node)?; return Ok(()); } - + // Otherwise, we need to remove the node and update its parent // This is more complex and would require tracking the path to the node // For simplicity, we'll just mark it as not end of key for now node.is_end_of_key = false; node.value = Vec::new(); tree.save_node(Some(node_id), &node)?; - + Ok(()) } @@ -255,46 +258,51 @@ pub fn list(tree: &mut TST, prefix: &str) -> Result, Error> { Some(id) => id, None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), }; - + let mut result = Vec::new(); - + // Handle empty prefix case - will return all keys if prefix.is_empty() { collect_all_keys(tree, root_id, String::new(), &mut result)?; return Ok(result); } - + // Find the node corresponding to the prefix let chars: Vec = prefix.chars().collect(); let node_id = match find_prefix_node(tree, root_id, &chars, 0) { Ok(id) => id, Err(_) => return Ok(Vec::new()), // Prefix not found, return empty list }; - + // For empty prefix, we start with an empty string // For non-empty prefix, we start with the prefix minus the last character // (since the last character is in the node we found) let prefix_base = if chars.len() > 1 { - chars[0..chars.len()-1].iter().collect() + chars[0..chars.len() - 1].iter().collect() } else { String::new() }; - + // Collect all keys from the subtree collect_keys_with_prefix(tree, node_id, prefix_base, &mut result)?; - + Ok(result) } /// Finds the node corresponding to a prefix. -fn find_prefix_node(tree: &mut TST, node_id: u32, chars: &[char], pos: usize) -> Result { +fn find_prefix_node( + tree: &mut TST, + node_id: u32, + chars: &[char], + pos: usize, +) -> Result { if pos >= chars.len() { return Ok(node_id); } - + let node = tree.get_node(node_id)?; let current_char = chars[pos]; - + if current_char < node.character { // Go left if let Some(left_id) = node.left_id { @@ -331,32 +339,32 @@ fn collect_keys_with_prefix( result: &mut Vec, ) -> Result<(), Error> { let node = tree.get_node(node_id)?; - + let mut new_path = current_path.clone(); - + // For non-root nodes, add the character to the path if node.character != '\0' { new_path.push(node.character); } - + // If this node is an end of key, add it to the result if node.is_end_of_key { result.push(new_path.clone()); } - + // Recursively collect keys from all children if let Some(left_id) = node.left_id { collect_keys_with_prefix(tree, left_id, current_path.clone(), result)?; } - + if let Some(middle_id) = node.middle_id { collect_keys_with_prefix(tree, middle_id, new_path.clone(), result)?; } - + if let Some(right_id) = node.right_id { collect_keys_with_prefix(tree, right_id, current_path.clone(), result)?; } - + Ok(()) } @@ -368,32 +376,32 @@ fn collect_all_keys( result: &mut Vec, ) -> Result<(), Error> { let node = tree.get_node(node_id)?; - + let mut new_path = current_path.clone(); - + // Skip adding the character for the root node if node.character != '\0' { new_path.push(node.character); } - + // If this node is an end of key, add it to the result if node.is_end_of_key { result.push(new_path.clone()); } - + // Recursively collect keys from all children if let Some(left_id) = node.left_id { collect_all_keys(tree, left_id, current_path.clone(), result)?; } - + if let Some(middle_id) = node.middle_id { collect_all_keys(tree, middle_id, new_path.clone(), result)?; } - + if let Some(right_id) = node.right_id { collect_all_keys(tree, right_id, current_path.clone(), result)?; } - + Ok(()) } @@ -401,23 +409,23 @@ fn collect_all_keys( pub fn getall(tree: &mut TST, prefix: &str) -> Result>, Error> { // Get all matching keys let keys = list(tree, prefix)?; - + // Get values for each key let mut values = Vec::new(); let mut errors = Vec::new(); - + for key in keys { match get(tree, &key) { Ok(value) => values.push(value), - Err(e) => errors.push(format!("Error getting value for key '{}': {:?}", key, e)) + Err(e) => errors.push(format!("Error getting value for key '{}': {:?}", key, e)), } } - + // If we couldn't get any values but had keys, return the first error if values.is_empty() && !errors.is_empty() { return Err(Error::InvalidOperation(errors.join("; "))); } - + Ok(values) } @@ -442,4 +450,4 @@ impl TST { Err(err) => Err(Error::OurDB(err)), } } -} \ No newline at end of file +} diff --git a/tst/src/serialize.rs b/tst/src/serialize.rs index 6c9c715..76e68b4 100644 --- a/tst/src/serialize.rs +++ b/tst/src/serialize.rs @@ -10,17 +10,17 @@ impl TSTNode { /// Serializes a node to bytes for storage. pub fn serialize(&self) -> Vec { let mut buffer = Vec::new(); - + // Version buffer.push(VERSION); - + // Character (as UTF-32) let char_bytes = (self.character as u32).to_le_bytes(); buffer.extend_from_slice(&char_bytes); - + // Is end of key buffer.push(if self.is_end_of_key { 1 } else { 0 }); - + // Value (only if is_end_of_key) if self.is_end_of_key { let value_len = (self.value.len() as u32).to_le_bytes(); @@ -30,88 +30,100 @@ impl TSTNode { // Zero length buffer.extend_from_slice(&[0, 0, 0, 0]); } - + // Child pointers let left_id = self.left_id.unwrap_or(0).to_le_bytes(); buffer.extend_from_slice(&left_id); - + let middle_id = self.middle_id.unwrap_or(0).to_le_bytes(); buffer.extend_from_slice(&middle_id); - + let right_id = self.right_id.unwrap_or(0).to_le_bytes(); buffer.extend_from_slice(&right_id); - + buffer } /// Deserializes bytes to a node. pub fn deserialize(data: &[u8]) -> Result { - if data.len() < 14 { // Minimum size: version + char + is_end + value_len + 3 child IDs + if data.len() < 14 { + // Minimum size: version + char + is_end + value_len + 3 child IDs return Err(Error::Deserialization("Data too short".to_string())); } - + let mut pos = 0; - + // Version let version = data[pos]; pos += 1; - + if version != VERSION { - return Err(Error::Deserialization(format!("Unsupported version: {}", version))); + return Err(Error::Deserialization(format!( + "Unsupported version: {}", + version + ))); } - + // Character - let char_bytes = [data[pos], data[pos+1], data[pos+2], data[pos+3]]; + let char_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; let char_code = u32::from_le_bytes(char_bytes); let character = char::from_u32(char_code) .ok_or_else(|| Error::Deserialization("Invalid character".to_string()))?; pos += 4; - + // Is end of key let is_end_of_key = data[pos] != 0; pos += 1; - + // Value length - let value_len_bytes = [data[pos], data[pos+1], data[pos+2], data[pos+3]]; + let value_len_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; let value_len = u32::from_le_bytes(value_len_bytes) as usize; pos += 4; - + // Value let value = if value_len > 0 { if pos + value_len > data.len() { - return Err(Error::Deserialization("Value length exceeds data".to_string())); + return Err(Error::Deserialization( + "Value length exceeds data".to_string(), + )); } - data[pos..pos+value_len].to_vec() + data[pos..pos + value_len].to_vec() } else { Vec::new() }; pos += value_len; - + // Child pointers if pos + 12 > data.len() { - return Err(Error::Deserialization("Data too short for child pointers".to_string())); + return Err(Error::Deserialization( + "Data too short for child pointers".to_string(), + )); } - - let left_id_bytes = [data[pos], data[pos+1], data[pos+2], data[pos+3]]; + + let left_id_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; let left_id = u32::from_le_bytes(left_id_bytes); pos += 4; - - let middle_id_bytes = [data[pos], data[pos+1], data[pos+2], data[pos+3]]; + + let middle_id_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; let middle_id = u32::from_le_bytes(middle_id_bytes); pos += 4; - - let right_id_bytes = [data[pos], data[pos+1], data[pos+2], data[pos+3]]; + + let right_id_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; let right_id = u32::from_le_bytes(right_id_bytes); - + Ok(TSTNode { character, value, is_end_of_key, left_id: if left_id == 0 { None } else { Some(left_id) }, - middle_id: if middle_id == 0 { None } else { Some(middle_id) }, + middle_id: if middle_id == 0 { + None + } else { + Some(middle_id) + }, right_id: if right_id == 0 { None } else { Some(right_id) }, }) } } -// Function removed as it was unused \ No newline at end of file +// Function removed as it was unused diff --git a/tst/tests/basic_test.rs b/tst/tests/basic_test.rs index 7d6c6cb..295836b 100644 --- a/tst/tests/basic_test.rs +++ b/tst/tests/basic_test.rs @@ -1,24 +1,24 @@ -use tst::TST; use std::env::temp_dir; use std::fs; use std::time::SystemTime; +use tst::TST; fn get_test_db_path() -> String { let timestamp = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_nanos(); - + let path = temp_dir().join(format!("tst_test_{}", timestamp)); - + // If the path exists, remove it first if path.exists() { let _ = fs::remove_dir_all(&path); } - + // Create the directory fs::create_dir_all(&path).unwrap(); - + path.to_string_lossy().to_string() } @@ -30,44 +30,44 @@ fn cleanup_test_db(path: &str) { #[test] fn test_create_tst() { let path = get_test_db_path(); - + let result = TST::new(&path, true); match &result { Ok(_) => (), Err(e) => println!("Error creating TST: {:?}", e), } assert!(result.is_ok()); - + if let Ok(mut tst) = result { // Make sure we can perform a basic operation let set_result = tst.set("test_key", b"test_value".to_vec()); assert!(set_result.is_ok()); } - + cleanup_test_db(&path); } #[test] fn test_set_and_get() { let path = get_test_db_path(); - + // Create a new TST with reset=true to ensure a clean state let result = TST::new(&path, true); assert!(result.is_ok()); - + let mut tree = result.unwrap(); - + // Test setting and getting a key let key = "test_key"; let value = b"test_value".to_vec(); - + let set_result = tree.set(key, value.clone()); assert!(set_result.is_ok()); - + let get_result = tree.get(key); assert!(get_result.is_ok()); assert_eq!(get_result.unwrap(), value); - + // Make sure to clean up properly cleanup_test_db(&path); } @@ -75,45 +75,45 @@ fn test_set_and_get() { #[test] fn test_get_nonexistent_key() { let path = get_test_db_path(); - + let mut tree = TST::new(&path, true).unwrap(); - + // Test getting a key that doesn't exist let get_result = tree.get("nonexistent_key"); assert!(get_result.is_err()); - + cleanup_test_db(&path); } #[test] fn test_delete() { let path = get_test_db_path(); - + // Create a new TST with reset=true to ensure a clean state let result = TST::new(&path, true); assert!(result.is_ok()); - + let mut tree = result.unwrap(); - + // Set a key let key = "delete_test"; let value = b"to_be_deleted".to_vec(); - + let set_result = tree.set(key, value); assert!(set_result.is_ok()); - + // Verify it exists let get_result = tree.get(key); assert!(get_result.is_ok()); - + // Delete it let delete_result = tree.delete(key); assert!(delete_result.is_ok()); - + // Verify it's gone let get_after_delete = tree.get(key); assert!(get_after_delete.is_err()); - + // Make sure to clean up properly cleanup_test_db(&path); } @@ -121,28 +121,28 @@ fn test_delete() { #[test] fn test_multiple_keys() { let path = get_test_db_path(); - + // Create a new TST with reset=true to ensure a clean state let result = TST::new(&path, true); assert!(result.is_ok()); - + let mut tree = result.unwrap(); - + // Insert multiple keys - use fewer keys to avoid filling the lookup table let keys = ["apple", "banana", "cherry"]; - + for (i, key) in keys.iter().enumerate() { let value = format!("value_{}", i).into_bytes(); let set_result = tree.set(key, value); - + // Print error if set fails if set_result.is_err() { println!("Error setting key '{}': {:?}", key, set_result); } - + assert!(set_result.is_ok()); } - + // Verify all keys exist for (i, key) in keys.iter().enumerate() { let expected_value = format!("value_{}", i).into_bytes(); @@ -150,7 +150,7 @@ fn test_multiple_keys() { assert!(get_result.is_ok()); assert_eq!(get_result.unwrap(), expected_value); } - + // Make sure to clean up properly cleanup_test_db(&path); } @@ -158,56 +158,53 @@ fn test_multiple_keys() { #[test] fn test_list_prefix() { let path = get_test_db_path(); - + // Create a new TST with reset=true to ensure a clean state let result = TST::new(&path, true); assert!(result.is_ok()); - + let mut tree = result.unwrap(); - + // Insert keys with common prefixes - use fewer keys to avoid filling the lookup table - let keys = [ - "apple", "application", "append", - "banana", "bandana" - ]; - + let keys = ["apple", "application", "append", "banana", "bandana"]; + for key in &keys { let set_result = tree.set(key, key.as_bytes().to_vec()); assert!(set_result.is_ok()); } - + // Test prefix "app" let list_result = tree.list("app"); assert!(list_result.is_ok()); - + let app_keys = list_result.unwrap(); - + // Print the keys for debugging println!("Keys with prefix 'app':"); for key in &app_keys { println!(" {}", key); } - + // Check that each key is present assert!(app_keys.contains(&"apple".to_string())); assert!(app_keys.contains(&"application".to_string())); assert!(app_keys.contains(&"append".to_string())); - + // Test prefix "ban" let list_result = tree.list("ban"); assert!(list_result.is_ok()); - + let ban_keys = list_result.unwrap(); assert!(ban_keys.contains(&"banana".to_string())); assert!(ban_keys.contains(&"bandana".to_string())); - + // Test non-existent prefix let list_result = tree.list("z"); assert!(list_result.is_ok()); - + let z_keys = list_result.unwrap(); assert_eq!(z_keys.len(), 0); - + // Make sure to clean up properly cleanup_test_db(&path); } @@ -215,46 +212,44 @@ fn test_list_prefix() { #[test] fn test_getall_prefix() { let path = get_test_db_path(); - + // Create a new TST with reset=true to ensure a clean state let result = TST::new(&path, true); assert!(result.is_ok()); - + let mut tree = result.unwrap(); - + // Insert keys with common prefixes - use fewer keys to avoid filling the lookup table - let keys = [ - "apple", "application", "append" - ]; - + let keys = ["apple", "application", "append"]; + for key in &keys { let set_result = tree.set(key, key.as_bytes().to_vec()); assert!(set_result.is_ok()); } - + // Test getall with prefix "app" let getall_result = tree.getall("app"); assert!(getall_result.is_ok()); - + let app_values = getall_result.unwrap(); - + // Convert values to strings for easier comparison let app_value_strings: Vec = app_values .iter() .map(|v| String::from_utf8_lossy(v).to_string()) .collect(); - + // Print the values for debugging println!("Values with prefix 'app':"); for value in &app_value_strings { println!(" {}", value); } - + // Check that each value is present assert!(app_value_strings.contains(&"apple".to_string())); assert!(app_value_strings.contains(&"application".to_string())); assert!(app_value_strings.contains(&"append".to_string())); - + // Make sure to clean up properly cleanup_test_db(&path); } @@ -262,38 +257,38 @@ fn test_getall_prefix() { #[test] fn test_empty_prefix() { let path = get_test_db_path(); - + // Create a new TST with reset=true to ensure a clean state let result = TST::new(&path, true); assert!(result.is_ok()); - + let mut tree = result.unwrap(); - + // Insert some keys let keys = ["apple", "banana", "cherry"]; - + for key in &keys { let set_result = tree.set(key, key.as_bytes().to_vec()); assert!(set_result.is_ok()); } - + // Test list with empty prefix (should return all keys) let list_result = tree.list(""); assert!(list_result.is_ok()); - + let all_keys = list_result.unwrap(); - + // Print the keys for debugging println!("Keys with empty prefix:"); for key in &all_keys { println!(" {}", key); } - + // Check that each key is present for key in &keys { assert!(all_keys.contains(&key.to_string())); } - + // Make sure to clean up properly cleanup_test_db(&path); -} \ No newline at end of file +} diff --git a/tst/tests/prefix_test.rs b/tst/tests/prefix_test.rs index 630ef19..b50c17d 100644 --- a/tst/tests/prefix_test.rs +++ b/tst/tests/prefix_test.rs @@ -1,24 +1,24 @@ -use tst::TST; use std::env::temp_dir; use std::fs; use std::time::SystemTime; +use tst::TST; fn get_test_db_path() -> String { let timestamp = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_nanos(); - + let path = temp_dir().join(format!("tst_prefix_test_{}", timestamp)); - + // If the path exists, remove it first if path.exists() { let _ = fs::remove_dir_all(&path); } - + // Create the directory fs::create_dir_all(&path).unwrap(); - + path.to_string_lossy().to_string() } @@ -30,9 +30,9 @@ fn cleanup_test_db(path: &str) { #[test] fn test_prefix_with_common_prefixes() { let path = get_test_db_path(); - + let mut tree = TST::new(&path, true).unwrap(); - + // Insert keys with common prefixes let test_data = [ ("test", b"value1".to_vec()), @@ -41,34 +41,34 @@ fn test_prefix_with_common_prefixes() { ("tests", b"value4".to_vec()), ("tester", b"value5".to_vec()), ]; - + for (key, value) in &test_data { tree.set(key, value.clone()).unwrap(); } - + // Test prefix "test" let keys = tree.list("test").unwrap(); assert_eq!(keys.len(), 5); - + for (key, _) in &test_data { assert!(keys.contains(&key.to_string())); } - + // Test prefix "teste" let keys = tree.list("teste").unwrap(); assert_eq!(keys.len(), 2); assert!(keys.contains(&"tested".to_string())); assert!(keys.contains(&"tester".to_string())); - + cleanup_test_db(&path); } #[test] fn test_prefix_with_different_prefixes() { let path = get_test_db_path(); - + let mut tree = TST::new(&path, true).unwrap(); - + // Insert keys with different prefixes let test_data = [ ("apple", b"fruit1".to_vec()), @@ -77,64 +77,64 @@ fn test_prefix_with_different_prefixes() { ("date", b"fruit4".to_vec()), ("elderberry", b"fruit5".to_vec()), ]; - + for (key, value) in &test_data { tree.set(key, value.clone()).unwrap(); } - + // Test each prefix for (key, _) in &test_data { let prefix = &key[0..1]; // First character let keys = tree.list(prefix).unwrap(); assert!(keys.contains(&key.to_string())); } - + // Test non-existent prefix let keys = tree.list("z").unwrap(); assert_eq!(keys.len(), 0); - + cleanup_test_db(&path); } #[test] fn test_prefix_with_empty_string() { let path = get_test_db_path(); - + // Create a new TST with reset=true to ensure a clean state let result = TST::new(&path, true); assert!(result.is_ok()); - + let mut tree = result.unwrap(); - + // Insert some keys let test_data = [ ("apple", b"fruit1".to_vec()), ("banana", b"fruit2".to_vec()), ("cherry", b"fruit3".to_vec()), ]; - + for (key, value) in &test_data { let set_result = tree.set(key, value.clone()); assert!(set_result.is_ok()); } - + // Test empty prefix (should return all keys) let list_result = tree.list(""); assert!(list_result.is_ok()); - + let keys = list_result.unwrap(); - + // Print the keys for debugging println!("Keys with empty prefix:"); for key in &keys { println!(" {}", key); } - + // Check that each key is present for (key, _) in &test_data { assert!(keys.contains(&key.to_string())); } - + // Make sure to clean up properly cleanup_test_db(&path); } @@ -142,9 +142,9 @@ fn test_prefix_with_empty_string() { #[test] fn test_getall_with_prefix() { let path = get_test_db_path(); - + let mut tree = TST::new(&path, true).unwrap(); - + // Insert keys with common prefixes let test_data = [ ("test", b"value1".to_vec()), @@ -153,28 +153,28 @@ fn test_getall_with_prefix() { ("tests", b"value4".to_vec()), ("tester", b"value5".to_vec()), ]; - + for (key, value) in &test_data { tree.set(key, value.clone()).unwrap(); } - + // Test getall with prefix "test" let values = tree.getall("test").unwrap(); assert_eq!(values.len(), 5); - + for (_, value) in &test_data { assert!(values.contains(value)); } - + cleanup_test_db(&path); } #[test] fn test_prefix_with_unicode_characters() { let path = get_test_db_path(); - + let mut tree = TST::new(&path, true).unwrap(); - + // Insert keys with Unicode characters let test_data = [ ("café", b"coffee".to_vec()), @@ -182,77 +182,86 @@ fn test_prefix_with_unicode_characters() { ("caffè", b"italian coffee".to_vec()), ("café au lait", b"coffee with milk".to_vec()), ]; - + for (key, value) in &test_data { tree.set(key, value.clone()).unwrap(); } - + // Test prefix "café" let keys = tree.list("café").unwrap(); - + // Print the keys for debugging println!("Keys with prefix 'café':"); for key in &keys { println!(" {}", key); } - + // Check that the keys we expect are present assert!(keys.contains(&"café".to_string())); assert!(keys.contains(&"café au lait".to_string())); - + // We don't assert on the exact count because Unicode handling can vary - + // Test prefix "caf" let keys = tree.list("caf").unwrap(); - + // Print the keys for debugging println!("Keys with prefix 'caf':"); for key in &keys { println!(" {}", key); } - + // Check that each key is present individually // Due to Unicode handling, we need to be careful with exact matching // The important thing is that we can find the keys we need - + // Check that we have at least the café and café au lait keys assert!(keys.contains(&"café".to_string())); assert!(keys.contains(&"café au lait".to_string())); - + // We don't assert on the exact count because Unicode handling can vary - + cleanup_test_db(&path); } #[test] fn test_prefix_with_long_keys() { let path = get_test_db_path(); - + let mut tree = TST::new(&path, true).unwrap(); - + // Insert long keys let test_data = [ - ("this_is_a_very_long_key_for_testing_purposes_1", b"value1".to_vec()), - ("this_is_a_very_long_key_for_testing_purposes_2", b"value2".to_vec()), - ("this_is_a_very_long_key_for_testing_purposes_3", b"value3".to_vec()), + ( + "this_is_a_very_long_key_for_testing_purposes_1", + b"value1".to_vec(), + ), + ( + "this_is_a_very_long_key_for_testing_purposes_2", + b"value2".to_vec(), + ), + ( + "this_is_a_very_long_key_for_testing_purposes_3", + b"value3".to_vec(), + ), ("this_is_another_long_key_for_testing", b"value4".to_vec()), ]; - + for (key, value) in &test_data { tree.set(key, value.clone()).unwrap(); } - + // Test prefix "this_is_a_very" let keys = tree.list("this_is_a_very").unwrap(); assert_eq!(keys.len(), 3); - + // Test prefix "this_is" let keys = tree.list("this_is").unwrap(); assert_eq!(keys.len(), 4); - + for (key, _) in &test_data { assert!(keys.contains(&key.to_string())); } - + cleanup_test_db(&path); -} \ No newline at end of file +}