fmt, fixes and additions

This commit is contained in:
timurgordon 2025-06-19 13:18:10 +03:00
parent 6b3cbfc4b2
commit e91a44ce37
86 changed files with 5292 additions and 2844 deletions

View File

@ -1,5 +1,5 @@
use heromodels_derive::model; use heromodels_derive::model;
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
// Define the necessary structs and traits for testing // Define the necessary structs and traits for testing
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -46,10 +46,10 @@ pub trait Index {
#[model] #[model]
struct TestUser { struct TestUser {
base_data: BaseModelData, base_data: BaseModelData,
#[index] #[index]
username: String, username: String,
#[index] #[index]
is_active: bool, is_active: bool,
} }
@ -59,10 +59,10 @@ struct TestUser {
#[model] #[model]
struct TestUserWithCustomIndex { struct TestUserWithCustomIndex {
base_data: BaseModelData, base_data: BaseModelData,
#[index(name = "custom_username")] #[index(name = "custom_username")]
username: String, username: String,
#[index] #[index]
is_active: bool, is_active: bool,
} }
@ -70,13 +70,13 @@ struct TestUserWithCustomIndex {
#[test] #[test]
fn test_basic_model() { fn test_basic_model() {
assert_eq!(TestUser::db_prefix(), "test_user"); assert_eq!(TestUser::db_prefix(), "test_user");
let user = TestUser { let user = TestUser {
base_data: BaseModelData::new(1), base_data: BaseModelData::new(1),
username: "test".to_string(), username: "test".to_string(),
is_active: true, is_active: true,
}; };
let keys = user.db_keys(); let keys = user.db_keys();
assert_eq!(keys.len(), 2); assert_eq!(keys.len(), 2);
assert_eq!(keys[0].name, "username"); assert_eq!(keys[0].name, "username");
@ -92,10 +92,10 @@ fn test_custom_index_name() {
username: "test".to_string(), username: "test".to_string(),
is_active: true, is_active: true,
}; };
// Check that the Username struct uses the custom index name // Check that the Username struct uses the custom index name
assert_eq!(Username::key(), "custom_username"); assert_eq!(Username::key(), "custom_username");
// Check that the db_keys method returns the correct keys // Check that the db_keys method returns the correct keys
let keys = user.db_keys(); let keys = user.db_keys();
assert_eq!(keys.len(), 2); assert_eq!(keys.len(), 2);
@ -103,4 +103,4 @@ fn test_custom_index_name() {
assert_eq!(keys[0].value, "test"); assert_eq!(keys[0].value, "test");
assert_eq!(keys[1].name, "is_active"); assert_eq!(keys[1].name, "is_active");
assert_eq!(keys[1].value, "true"); assert_eq!(keys[1].value, "true");
} }

View File

@ -42,10 +42,6 @@ path = "examples/finance_example/main.rs"
name = "calendar_rhai" name = "calendar_rhai"
path = "examples/calendar_rhai/example.rs" path = "examples/calendar_rhai/example.rs"
[[example]]
name = "calendar_rhai_client"
path = "examples/calendar_rhai_client/example.rs"
[[example]] [[example]]
name = "flow_rhai" name = "flow_rhai"
path = "examples/flow_rhai/example.rs" path = "examples/flow_rhai/example.rs"

View File

@ -68,10 +68,26 @@ fn main() {
.build(); .build();
// Save all users to database and get their assigned IDs and updated models // 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 (user1_id, db_user1) = db
let (user2_id, db_user2) = db.collection().expect("can open user collection").set(&user2).expect("can set user"); .collection()
let (user3_id, db_user3) = db.collection().expect("can open user collection").set(&user3).expect("can set user"); .expect("can open user collection")
let (user4_id, db_user4) = db.collection().expect("can open user collection").set(&user4).expect("can set user"); .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 1 assigned ID: {}", user1_id);
println!("User 2 assigned ID: {}", user2_id); println!("User 2 assigned ID: {}", user2_id);
@ -170,7 +186,8 @@ fn main() {
.build(); .build();
// Save the comment and get its assigned ID and updated model // 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") .expect("can open comment collection")
.set(&comment) .set(&comment)
.expect("can set comment"); .expect("can set comment");
@ -186,7 +203,8 @@ fn main() {
updated_user.base_data.add_comment(db_comment.get_id()); updated_user.base_data.add_comment(db_comment.get_id());
// Save the updated user and get the new version // Save the updated user and get the new version
let (_, user_with_comment) = db.collection::<User>() let (_, user_with_comment) = db
.collection::<User>()
.expect("can open user collection") .expect("can open user collection")
.set(&updated_user) .set(&updated_user)
.expect("can set updated user"); .expect("can set updated user");

View File

@ -1,8 +1,8 @@
use rhai::{Engine, EvalAltResult, Scope};
use std::sync::Arc;
use heromodels::db::hero::OurDB; // Corrected path for OurDB use heromodels::db::hero::OurDB; // Corrected path for OurDB
use heromodels::models::biz::register_biz_rhai_module; // Corrected path use heromodels::models::biz::register_biz_rhai_module; // Corrected path
use rhai::{Engine, EvalAltResult, Scope};
use std::fs; use std::fs;
use std::sync::Arc;
fn main() -> Result<(), Box<EvalAltResult>> { fn main() -> Result<(), Box<EvalAltResult>> {
println!("Executing Rhai script: examples/biz_rhai/biz.rhai"); println!("Executing Rhai script: examples/biz_rhai/biz.rhai");
@ -20,8 +20,12 @@ fn main() -> Result<(), Box<EvalAltResult>> {
// Read the Rhai script from file // Read the Rhai script from file
let script_path = "examples/biz_rhai/biz.rhai"; let script_path = "examples/biz_rhai/biz.rhai";
let script_content = fs::read_to_string(script_path) let script_content = fs::read_to_string(script_path).map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorSystem(format!("Cannot read script file: {}", script_path), e.into())))?; Box::new(EvalAltResult::ErrorSystem(
format!("Cannot read script file: {}", script_path),
e.into(),
))
})?;
// Create a new scope // Create a new scope
let mut scope = Scope::new(); let mut scope = Scope::new();

View File

@ -1,6 +1,6 @@
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
use heromodels::db::{Collection, Db}; 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; use heromodels_core::Model;
fn main() { fn main() {
@ -12,10 +12,8 @@ fn main() {
println!("===================================="); println!("====================================");
// --- Create Attendees --- // --- Create Attendees ---
let attendee1 = Attendee::new("user_123".to_string()) let attendee1 = Attendee::new("user_123".to_string()).status(AttendanceStatus::Accepted);
.status(AttendanceStatus::Accepted); let attendee2 = Attendee::new("user_456".to_string()).status(AttendanceStatus::Tentative);
let attendee2 = Attendee::new("user_456".to_string())
.status(AttendanceStatus::Tentative);
let attendee3 = Attendee::new("user_789".to_string()); // Default NoResponse let attendee3 = Attendee::new("user_789".to_string()); // Default NoResponse
// --- Create Events --- // --- Create Events ---
@ -45,7 +43,7 @@ fn main() {
"event_gamma".to_string(), "event_gamma".to_string(),
"Client Call", "Client Call",
now + Duration::days(2), now + Duration::days(2),
now + Duration::days(2) + Duration::seconds(3600) now + Duration::days(2) + Duration::seconds(3600),
); );
// --- Create Calendars --- // --- Create Calendars ---
@ -58,25 +56,43 @@ fn main() {
.add_event(event2.clone()); .add_event(event2.clone());
// Create a calendar with auto-generated ID (explicit IDs are no longer supported) // Create a calendar with auto-generated ID (explicit IDs are no longer supported)
let calendar2 = Calendar::new(None, "Personal Calendar") let calendar2 =
.add_event(event3_for_calendar2.clone()); Calendar::new(None, "Personal Calendar").add_event(event3_for_calendar2.clone());
// --- Store Calendars in DB --- // --- Store Calendars in DB ---
let cal_collection = db.collection::<Calendar>().expect("can open calendar collection"); let cal_collection = db
.collection::<Calendar>()
.expect("can open calendar collection");
let (_, calendar1) = cal_collection.set(&calendar1).expect("can set calendar1"); let (_, calendar1) = cal_collection.set(&calendar1).expect("can set calendar1");
let (_, calendar2) = cal_collection.set(&calendar2).expect("can set calendar2"); let (_, calendar2) = cal_collection.set(&calendar2).expect("can set calendar2");
println!("Created calendar1 (ID: {}): Name - '{}'", calendar1.get_id(), calendar1.name); println!(
println!("Created calendar2 (ID: {}): Name - '{}'", calendar2.get_id(), calendar2.name); "Created calendar1 (ID: {}): Name - '{}'",
calendar1.get_id(),
calendar1.name
);
println!(
"Created calendar2 (ID: {}): Name - '{}'",
calendar2.get_id(),
calendar2.name
);
// --- Retrieve a Calendar by ID --- // --- Retrieve a Calendar by ID ---
let stored_calendar1_opt = cal_collection.get_by_id(calendar1.get_id()).expect("can try to load calendar1"); let stored_calendar1_opt = cal_collection
assert!(stored_calendar1_opt.is_some(), "Calendar1 should be found in DB"); .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(); 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.name, "Work Calendar");
assert_eq!(stored_calendar1.events.len(), 2); assert_eq!(stored_calendar1.events.len(), 2);
assert_eq!(stored_calendar1.events[0].title, "Team Meeting"); assert_eq!(stored_calendar1.events[0].title, "Team Meeting");
@ -84,49 +100,83 @@ fn main() {
// --- Modify a Calendar (Reschedule an Event) --- // --- Modify a Calendar (Reschedule an Event) ---
let event_id_to_reschedule = event1.id.as_str(); let event_id_to_reschedule = event1.id.as_str();
let new_start_time = now + Duration::seconds(10800); // 3 hours from now 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| { stored_calendar1 = stored_calendar1.update_event(event_id_to_reschedule, |event_to_update| {
println!("Rescheduling event '{}'...", event_to_update.title); println!("Rescheduling event '{}'...", event_to_update.title);
event_to_update.reschedule(new_start_time, new_end_time) 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"); .expect("Rescheduled event should exist");
assert_eq!(rescheduled_event.start_time, new_start_time); assert_eq!(rescheduled_event.start_time, new_start_time);
assert_eq!(rescheduled_event.end_time, new_end_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 --- // --- Store the modified calendar ---
let (_, mut stored_calendar1) = cal_collection.set(&stored_calendar1).expect("can set modified calendar1"); let (_, mut stored_calendar1) = cal_collection
let re_retrieved_calendar1_opt = cal_collection.get_by_id(calendar1.get_id()).expect("can try to load modified calendar1"); .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_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"); .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 --- // --- Add a new event to an existing calendar ---
let event4_new = Event::new( let event4_new = Event::new(
"event_delta".to_string(), "event_delta".to_string(),
"1-on-1", "1-on-1",
now + Duration::days(3), 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); stored_calendar1 = stored_calendar1.add_event(event4_new);
assert_eq!(stored_calendar1.events.len(), 3); assert_eq!(stored_calendar1.events.len(), 3);
let (_, stored_calendar1) = cal_collection.set(&stored_calendar1).expect("can set calendar1 after adding new event"); let (_, stored_calendar1) = cal_collection
println!("Added new event '1-on-1' to stored_calendar1. Total events: {}", stored_calendar1.events.len()); .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 --- // --- Delete a Calendar ---
cal_collection.delete_by_id(calendar2.get_id()).expect("can delete calendar2"); cal_collection
let deleted_calendar2_opt = cal_collection.get_by_id(calendar2.get_id()).expect("can try to load deleted calendar2"); .delete_by_id(calendar2.get_id())
assert!(deleted_calendar2_opt.is_none(), "Calendar2 should be deleted from DB"); .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!("\nDeleted calendar2 (ID: {}) from DB.", calendar2.get_id());
println!("Calendar model DB Prefix: {}", Calendar::db_prefix()); println!("Calendar model DB Prefix: {}", Calendar::db_prefix());
println!("\nExample finished. DB stored at {}", db_path); 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
);
} }

View File

@ -1,18 +1,17 @@
use heromodels::db::hero::OurDB; 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::rhai::register_rhai_engine_functions;
use heromodels::models::calendar::{AttendanceStatus, Attendee, Calendar, Event};
use rhai::Engine; use rhai::Engine;
use rhai_wrapper::wrap_vec_return;
use std::sync::Arc; use std::sync::Arc;
use std::{fs, path::Path}; use std::{fs, path::Path};
use rhai_wrapper::wrap_vec_return;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize Rhai engine // Initialize Rhai engine
let mut engine = Engine::new(); let mut engine = Engine::new();
// Initialize database with OurDB // 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 // Register the Calendar type with Rhai
// This function is generated by the #[rhai_model_export] attribute // This function is generated by the #[rhai_model_export] attribute
@ -29,9 +28,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}); });
// Register setter methods for Calendar properties // Register setter methods for Calendar properties
engine.register_fn("set_description", |calendar: &mut Calendar, desc: String| { engine.register_fn(
calendar.description = Some(desc); "set_description",
}); |calendar: &mut Calendar, desc: String| {
calendar.description = Some(desc);
},
);
// Register getter methods for Calendar properties // Register getter methods for Calendar properties
engine.register_fn("get_description", |calendar: Calendar| -> String { engine.register_fn("get_description", |calendar: Calendar| -> String {
@ -49,10 +51,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Calendar saved: {}", _calendar.name); println!("Calendar saved: {}", _calendar.name);
}); });
engine.register_fn("get_calendar_by_id", |_db: Arc<OurDB>, id: i64| -> Calendar { engine.register_fn(
// In a real implementation, this would retrieve the calendar from the database "get_calendar_by_id",
Calendar::new(Some(id as u32), "Retrieved Calendar") |_db: Arc<OurDB>, 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 // Register a function to check if a calendar exists
engine.register_fn("calendar_exists", |_db: Arc<OurDB>, id: i64| -> bool { engine.register_fn("calendar_exists", |_db: Arc<OurDB>, id: i64| -> bool {
@ -63,11 +68,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Define the function separately to use with the wrap_vec_return macro // Define the function separately to use with the wrap_vec_return macro
fn get_all_calendars(_db: Arc<OurDB>) -> Vec<Calendar> { fn get_all_calendars(_db: Arc<OurDB>) -> Vec<Calendar> {
// In a real implementation, this would retrieve all calendars from the database // 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 // Register the function with the wrap_vec_return macro
engine.register_fn("get_all_calendars", wrap_vec_return!(get_all_calendars, Arc<OurDB> => Calendar)); engine.register_fn(
"get_all_calendars",
wrap_vec_return!(get_all_calendars, Arc<OurDB> => Calendar),
);
engine.register_fn("delete_calendar_by_id", |_db: Arc<OurDB>, _id: i64| { engine.register_fn("delete_calendar_by_id", |_db: Arc<OurDB>, _id: i64| {
// In a real implementation, this would delete the calendar from the database // In a real implementation, this would delete the calendar from the database
@ -84,4 +95,4 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
Ok(()) Ok(())
} }

View File

@ -41,11 +41,16 @@ fn main() {
println!("Before saving - CustomUser DB Keys: {:?}", user.db_keys()); println!("Before saving - CustomUser DB Keys: {:?}", user.db_keys());
// Save the model to the database // Save the model to the database
let collection = db.collection::<CustomUser>().expect("can open user collection"); let collection = db
.collection::<CustomUser>()
.expect("can open user collection");
let (user_id, saved_user) = collection.set(&user).expect("can save user"); let (user_id, saved_user) = collection.set(&user).expect("can save user");
println!("\nAfter saving - CustomUser ID: {}", saved_user.get_id()); 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); println!("Returned ID: {}", user_id);
// Verify that the ID was auto-generated // Verify that the ID was auto-generated
@ -53,5 +58,8 @@ fn main() {
assert_ne!(saved_user.get_id(), 0); assert_ne!(saved_user.get_id(), 0);
println!("\nExample finished. DB stored at {}", db_path); 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
);
} }

View File

@ -1,8 +1,10 @@
// heromodels/examples/finance_example/main.rs // 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::{Account, Asset, AssetType};
use heromodels::models::finance::marketplace::{Listing, ListingType, ListingStatus, Bid, BidStatus};
fn main() { fn main() {
println!("Finance Models Example\n"); println!("Finance Models Example\n");
@ -12,16 +14,19 @@ fn main() {
// Create a new account with auto-generated ID // Create a new account with auto-generated ID
let mut account = Account::new( let mut account = Account::new(
None, // id (auto-generated) None, // id (auto-generated)
"Main ETH Wallet", // name "Main ETH Wallet", // name
1001, // user_id 1001, // user_id
"My primary Ethereum wallet", // description "My primary Ethereum wallet", // description
"ethereum", // ledger "ethereum", // ledger
"0x1234567890abcdef1234567890abcdef12345678", // address "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!("Owner: User {}", account.user_id);
println!("Blockchain: {}", account.ledger); println!("Blockchain: {}", account.ledger);
println!("Address: {}", account.address); println!("Address: {}", account.address);
@ -30,34 +35,34 @@ fn main() {
// Create some assets // Create some assets
// Asset with auto-generated ID // Asset with auto-generated ID
let eth_asset = Asset::new( let eth_asset = Asset::new(
None, // id (auto-generated) None, // id (auto-generated)
"Ethereum", // name "Ethereum", // name
"Native ETH cryptocurrency", // description "Native ETH cryptocurrency", // description
1.5, // amount 1.5, // amount
"0x0000000000000000000000000000000000000000", // address (ETH has no contract address) "0x0000000000000000000000000000000000000000", // address (ETH has no contract address)
AssetType::Native, // asset_type AssetType::Native, // asset_type
18, // decimals 18, // decimals
); );
// Assets with explicit IDs // Assets with explicit IDs
let usdc_asset = Asset::new( let usdc_asset = Asset::new(
Some(102), // id Some(102), // id
"USDC", // name "USDC", // name
"USD Stablecoin on Ethereum", // description "USD Stablecoin on Ethereum", // description
1000.0, // amount 1000.0, // amount
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // address (USDC contract) "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // address (USDC contract)
AssetType::Erc20, // asset_type AssetType::Erc20, // asset_type
6, // decimals 6, // decimals
); );
let nft_asset = Asset::new( let nft_asset = Asset::new(
Some(103), // id Some(103), // id
"CryptoPunk #1234", // name "CryptoPunk #1234", // name
"Rare digital collectible", // description "Rare digital collectible", // description
1.0, // amount 1.0, // amount
"0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb", // address (CryptoPunks contract) "0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb", // address (CryptoPunks contract)
AssetType::Erc721, // asset_type AssetType::Erc721, // asset_type
0, // decimals 0, // decimals
); );
// Add assets to the account // Add assets to the account
@ -67,7 +72,12 @@ fn main() {
println!("Added Assets to Account:"); println!("Added Assets to Account:");
for asset in &account.assets { 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()); println!("\nTotal Account Value (raw sum): {}", account.total_value());
@ -75,10 +85,10 @@ fn main() {
// Update account details // Update account details
account = account.update_details( account = account.update_details(
Some("Primary Ethereum Wallet"), // new name Some("Primary Ethereum Wallet"), // new name
None::<String>, // keep same description None::<String>, // keep same description
None::<String>, // keep same address None::<String>, // keep same address
Some("0xnewpubkey987654321"), // new pubkey Some("0xnewpubkey987654321"), // new pubkey
); );
println!("Updated Account Details:"); println!("Updated Account Details:");
@ -99,23 +109,32 @@ fn main() {
// Create a fixed price listing with auto-generated ID // Create a fixed price listing with auto-generated ID
let mut fixed_price_listing = Listing::new( let mut fixed_price_listing = Listing::new(
None, // id (auto-generated) None, // id (auto-generated)
"1000 USDC for Sale", // title "1000 USDC for Sale", // title
"Selling 1000 USDC tokens at fixed price", // description "Selling 1000 USDC tokens at fixed price", // description
"102", // asset_id (referencing the USDC asset) "102", // asset_id (referencing the USDC asset)
AssetType::Erc20, // asset_type AssetType::Erc20, // asset_type
"1001", // seller_id "1001", // seller_id
1.05, // price (in ETH) 1.05, // price (in ETH)
"ETH", // currency "ETH", // currency
ListingType::FixedPrice, // listing_type ListingType::FixedPrice, // listing_type
Some(Utc::now() + Duration::days(7)), // expires_at (7 days from now) Some(Utc::now() + Duration::days(7)), // expires_at (7 days from now)
vec!["token".to_string(), "stablecoin".to_string()], // tags vec!["token".to_string(), "stablecoin".to_string()], // tags
Some("https://example.com/usdc.png"), // image_url 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!(
println!("Price: {} {}", fixed_price_listing.price, fixed_price_listing.currency); "Created Fixed Price Listing: '{}' (ID: {})",
println!("Type: {:?}, Status: {:?}", fixed_price_listing.listing_type, fixed_price_listing.status); 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!("Expires: {}", fixed_price_listing.expires_at.unwrap());
println!(""); println!("");
@ -126,54 +145,71 @@ fn main() {
println!("Fixed Price Sale Completed:"); println!("Fixed Price Sale Completed:");
println!("Status: {:?}", fixed_price_listing.status); println!("Status: {:?}", fixed_price_listing.status);
println!("Buyer: {}", fixed_price_listing.buyer_id.unwrap()); 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!("Sold At: {}", fixed_price_listing.sold_at.unwrap());
println!(""); println!("");
}, }
Err(e) => println!("Error completing sale: {}", e), Err(e) => println!("Error completing sale: {}", e),
} }
// Create an auction listing for the NFT with explicit ID // Create an auction listing for the NFT with explicit ID
let mut auction_listing = Listing::new( let mut auction_listing = Listing::new(
Some(202), // id (explicit) Some(202), // id (explicit)
"CryptoPunk #1234 Auction", // title "CryptoPunk #1234 Auction", // title
"Rare CryptoPunk NFT for auction", // description "Rare CryptoPunk NFT for auction", // description
"103", // asset_id (referencing the NFT asset) "103", // asset_id (referencing the NFT asset)
AssetType::Erc721, // asset_type AssetType::Erc721, // asset_type
"1001", // seller_id "1001", // seller_id
10.0, // starting_price (in ETH) 10.0, // starting_price (in ETH)
"ETH", // currency "ETH", // currency
ListingType::Auction, // listing_type ListingType::Auction, // listing_type
Some(Utc::now() + Duration::days(3)), // expires_at (3 days from now) 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 Some("https://example.com/cryptopunk1234.png"), // image_url
); );
println!("Created Auction Listing: '{}' (ID: {})", auction_listing.title, auction_listing.base_data.id); println!(
println!("Starting Price: {} {}", auction_listing.price, auction_listing.currency); "Created Auction Listing: '{}' (ID: {})",
println!("Type: {:?}, Status: {:?}", auction_listing.listing_type, auction_listing.status); 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!(""); println!("");
// Create some bids // Create some bids
let bid1 = Bid::new( let bid1 = Bid::new(
auction_listing.base_data.id.to_string(), // listing_id auction_listing.base_data.id.to_string(), // listing_id
2001, // bidder_id 2001, // bidder_id
11.0, // amount 11.0, // amount
"ETH", // currency "ETH", // currency
); );
let bid2 = Bid::new( let bid2 = Bid::new(
auction_listing.base_data.id.to_string(), // listing_id auction_listing.base_data.id.to_string(), // listing_id
2002, // bidder_id 2002, // bidder_id
12.5, // amount 12.5, // amount
"ETH", // currency "ETH", // currency
); );
let bid3 = Bid::new( let bid3 = Bid::new(
auction_listing.base_data.id.to_string(), // listing_id auction_listing.base_data.id.to_string(), // listing_id
2003, // bidder_id 2003, // bidder_id
15.0, // amount 15.0, // amount
"ETH", // currency "ETH", // currency
); );
// Add bids to the auction // Add bids to the auction
@ -184,7 +220,7 @@ fn main() {
Ok(updated_listing) => { Ok(updated_listing) => {
auction_listing = updated_listing; auction_listing = updated_listing;
println!("- Bid added: 11.0 ETH from User 2001"); println!("- Bid added: 11.0 ETH from User 2001");
}, }
Err(e) => println!("Error adding bid: {}", e), Err(e) => println!("Error adding bid: {}", e),
} }
@ -192,7 +228,7 @@ fn main() {
Ok(updated_listing) => { Ok(updated_listing) => {
auction_listing = updated_listing; auction_listing = updated_listing;
println!("- Bid added: 12.5 ETH from User 2002"); println!("- Bid added: 12.5 ETH from User 2002");
}, }
Err(e) => println!("Error adding bid: {}", e), Err(e) => println!("Error adding bid: {}", e),
} }
@ -200,18 +236,21 @@ fn main() {
Ok(updated_listing) => { Ok(updated_listing) => {
auction_listing = updated_listing; auction_listing = updated_listing;
println!("- Bid added: 15.0 ETH from User 2003"); println!("- Bid added: 15.0 ETH from User 2003");
}, }
Err(e) => println!("Error adding bid: {}", e), Err(e) => println!("Error adding bid: {}", e),
} }
println!("\nCurrent Auction Status:"); 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() { if let Some(highest_bid) = auction_listing.highest_bid() {
println!("Highest Bid: {} {} from User {}", println!(
highest_bid.amount, "Highest Bid: {} {} from User {}",
highest_bid.currency, highest_bid.amount, highest_bid.currency, highest_bid.bidder_id
highest_bid.bidder_id); );
} }
println!("Total Bids: {}", auction_listing.bids.len()); println!("Total Bids: {}", auction_listing.bids.len());
@ -223,42 +262,57 @@ fn main() {
auction_listing = updated_listing; auction_listing = updated_listing;
println!("Auction Completed:"); println!("Auction Completed:");
println!("Status: {:?}", auction_listing.status); println!("Status: {:?}", auction_listing.status);
println!("Winner: User {}", auction_listing.buyer_id.as_ref().unwrap()); println!(
println!("Winning Bid: {} {}", auction_listing.sale_price.as_ref().unwrap(), auction_listing.currency); "Winner: User {}",
auction_listing.buyer_id.as_ref().unwrap()
);
println!(
"Winning Bid: {} {}",
auction_listing.sale_price.as_ref().unwrap(),
auction_listing.currency
);
println!(""); println!("");
println!("Final Bid Statuses:"); println!("Final Bid Statuses:");
for bid in &auction_listing.bids { for bid in &auction_listing.bids {
println!("- User {}: {} {} (Status: {:?})", println!(
bid.bidder_id, "- User {}: {} {} (Status: {:?})",
bid.amount, bid.bidder_id, bid.amount, bid.currency, bid.status
bid.currency, );
bid.status);
} }
println!(""); println!("");
}, }
Err(e) => println!("Error completing auction: {}", e), Err(e) => println!("Error completing auction: {}", e),
} }
// Create an exchange listing with auto-generated ID // Create an exchange listing with auto-generated ID
let exchange_listing = Listing::new( let exchange_listing = Listing::new(
None, // id (auto-generated) None, // id (auto-generated)
"ETH for BTC Exchange", // title "ETH for BTC Exchange", // title
"Looking to exchange ETH for BTC", // description "Looking to exchange ETH for BTC", // description
"101", // asset_id (referencing the ETH asset) "101", // asset_id (referencing the ETH asset)
AssetType::Native, // asset_type AssetType::Native, // asset_type
"1001", // seller_id "1001", // seller_id
1.0, // amount (1 ETH) 1.0, // amount (1 ETH)
"BTC", // currency (what they want in exchange) "BTC", // currency (what they want in exchange)
ListingType::Exchange, // listing_type ListingType::Exchange, // listing_type
Some(Utc::now() + Duration::days(14)), // expires_at (14 days from now) Some(Utc::now() + Duration::days(14)), // expires_at (14 days from now)
vec!["exchange".to_string(), "crypto".to_string()], // tags vec!["exchange".to_string(), "crypto".to_string()], // tags
None::<String>, // image_url None::<String>, // image_url
); );
println!("Created Exchange Listing: '{}' (ID: {})", exchange_listing.title, exchange_listing.base_data.id); println!(
println!("Offering: Asset {} ({:?})", exchange_listing.asset_id, exchange_listing.asset_type); "Created Exchange Listing: '{}' (ID: {})",
println!("Wanted: {} {}", exchange_listing.price, exchange_listing.currency); 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!(""); println!("");
// --- PART 3: DEMONSTRATING EDGE CASES --- // --- PART 3: DEMONSTRATING EDGE CASES ---
@ -266,26 +320,26 @@ fn main() {
// Create a new auction listing for edge case testing with explicit ID // Create a new auction listing for edge case testing with explicit ID
let test_auction = Listing::new( let test_auction = Listing::new(
Some(205), // id (explicit) Some(205), // id (explicit)
"Test Auction", // title "Test Auction", // title
"For testing edge cases", // description "For testing edge cases", // description
"101", // asset_id "101", // asset_id
AssetType::Native, // asset_type AssetType::Native, // asset_type
"1001", // seller_id "1001", // seller_id
10.0, // starting_price 10.0, // starting_price
"ETH", // currency "ETH", // currency
ListingType::Auction, // listing_type ListingType::Auction, // listing_type
Some(Utc::now() + Duration::days(1)), // expires_at Some(Utc::now() + Duration::days(1)), // expires_at
vec![], // tags vec![], // tags
None::<String>, // image_url None::<String>, // image_url
); );
// Try to add a bid that's too low // Try to add a bid that's too low
let low_bid = Bid::new( let low_bid = Bid::new(
test_auction.base_data.id.to_string(), // listing_id test_auction.base_data.id.to_string(), // listing_id
2004, // bidder_id 2004, // bidder_id
5.0, // amount (lower than starting price) 5.0, // amount (lower than starting price)
"ETH", // currency "ETH", // currency
); );
println!("Attempting to add a bid that's too low (5.0 ETH):"); 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 // Create a listing that will expire with auto-generated ID
let mut expiring_listing = Listing::new( let mut expiring_listing = Listing::new(
None, // id (auto-generated) None, // id (auto-generated)
"About to Expire", // title "About to Expire", // title
"This listing will expire immediately", // description "This listing will expire immediately", // description
"101", // asset_id "101", // asset_id
AssetType::Native, // asset_type AssetType::Native, // asset_type
"1001", // seller_id "1001", // seller_id
0.1, // price 0.1, // price
"ETH", // currency "ETH", // currency
ListingType::FixedPrice, // listing_type ListingType::FixedPrice, // listing_type
Some(Utc::now() - Duration::hours(1)), // expires_at (1 hour ago) Some(Utc::now() - Duration::hours(1)), // expires_at (1 hour ago)
vec![], // tags vec![], // tags
None::<String>, // image_url None::<String>, // 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); println!("Initial Status: {:?}", expiring_listing.status);
// Check expiration // Check expiration

View File

@ -1,12 +1,12 @@
use rhai::{Engine, Scope, EvalAltResult}; use rhai::{Engine, EvalAltResult, Scope};
use std::sync::{Arc, Mutex};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
use std::sync::{Arc, Mutex};
// Import the models and the registration function // Import the models and the registration function
use heromodels::models::finance::account::Account; use heromodels::models::finance::account::Account;
use heromodels::models::finance::asset::{Asset}; use heromodels::models::finance::asset::Asset;
use heromodels::models::finance::marketplace::{Listing}; use heromodels::models::finance::marketplace::Listing;
use heromodels::models::finance::rhai::register_rhai_engine_functions; use heromodels::models::finance::rhai::register_rhai_engine_functions;
// Define a simple in-memory mock database for the example // Define a simple in-memory mock database for the example
@ -39,10 +39,10 @@ fn main() -> Result<(), Box<EvalAltResult>> {
// Register finance functions and types with the engine // Register finance functions and types with the engine
register_rhai_engine_functions( register_rhai_engine_functions(
&mut engine, &mut engine,
Arc::clone(&mock_db.accounts), Arc::clone(&mock_db.accounts),
Arc::clone(&mock_db.assets), Arc::clone(&mock_db.assets),
Arc::clone(&mock_db.listings) Arc::clone(&mock_db.listings),
); );
println!("Rhai functions registered."); println!("Rhai functions registered.");
@ -77,8 +77,13 @@ fn main() -> Result<(), Box<EvalAltResult>> {
println!("No accounts in mock DB."); println!("No accounts in mock DB.");
} }
for (id, account) in final_accounts.iter() { for (id, account) in final_accounts.iter() {
println!("Account ID: {}, Name: '{}', User ID: {}, Assets: {}", println!(
id, account.name, account.user_id, account.assets.len()); "Account ID: {}, Name: '{}', User ID: {}, Assets: {}",
id,
account.name,
account.user_id,
account.assets.len()
);
} }
// Print final state of Assets // Print final state of Assets
@ -88,8 +93,10 @@ fn main() -> Result<(), Box<EvalAltResult>> {
println!("No assets in mock DB."); println!("No assets in mock DB.");
} }
for (id, asset) in final_assets.iter() { for (id, asset) in final_assets.iter() {
println!("Asset ID: {}, Name: '{}', Amount: {}, Type: {:?}", println!(
id, asset.name, asset.amount, asset.asset_type); "Asset ID: {}, Name: '{}', Amount: {}, Type: {:?}",
id, asset.name, asset.amount, asset.asset_type
);
} }
// Print final state of Listings // Print final state of Listings
@ -100,8 +107,13 @@ fn main() -> Result<(), Box<EvalAltResult>> {
} }
for (id, listing) in final_listings.iter() { for (id, listing) in final_listings.iter() {
println!( println!(
"Listing ID: {}, Title: '{}', Type: {:?}, Status: {:?}, Price: {}, Bids: {}", "Listing ID: {}, Title: '{}', Type: {:?}, Status: {:?}, Price: {}, Bids: {}",
id, listing.title, listing.listing_type, listing.status, listing.price, listing.bids.len() id,
listing.title,
listing.listing_type,
listing.status,
listing.price,
listing.bids.len()
); );
} }

View File

@ -9,8 +9,8 @@ use heromodels_core::Model;
fn main() { fn main() {
// Create a new DB instance in /tmp/ourdb_flowbroker, and reset before every run // 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) let db =
.expect("Can create DB"); heromodels::db::hero::OurDB::new("/tmp/ourdb_flowbroker", true).expect("Can create DB");
println!("Hero Models - Flow Example"); println!("Hero Models - Flow Example");
println!("==========================="); println!("===========================");
@ -20,56 +20,71 @@ fn main() {
let new_flow_uuid = "a1b2c3d4-e5f6-7890-1234-567890abcdef"; // Example UUID let new_flow_uuid = "a1b2c3d4-e5f6-7890-1234-567890abcdef"; // Example UUID
let flow1 = Flow::new( let flow1 = Flow::new(
1, // id 1, // id
new_flow_uuid, // flow_uuid new_flow_uuid, // flow_uuid
"Document Approval Flow", // name "Document Approval Flow", // name
"Pending", // status "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!("Created Flow: {:?}", flow1);
println!("Flow ID: {}", flow1.get_id()); println!("Flow ID: {}", flow1.get_id());
println!("Flow DB Prefix: {}", Flow::db_prefix()); println!("Flow DB Prefix: {}", Flow::db_prefix());
// --- Create FlowSteps for Flow1 --- // --- Create FlowSteps for Flow1 ---
let step1_flow1 = FlowStep::new( let step1_flow1 = FlowStep::new(
101, // id 101, // id
flow1.get_id(), // flow_id flow1.get_id(), // flow_id
1, // step_order 1, // step_order
"Pending", // status "Pending", // status
) )
.description("Initial review by manager"); .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); println!("Created FlowStep: {:?}", step1_flow1);
let step2_flow1 = FlowStep::new( let step2_flow1 = FlowStep::new(
102, // id 102, // id
flow1.get_id(), // flow_id flow1.get_id(), // flow_id
2, // step_order 2, // step_order
"Pending", // status "Pending", // status
) )
.description("Legal team sign-off"); .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); println!("Created FlowStep: {:?}", step2_flow1);
// --- Create SignatureRequirements for step2_flow1 --- // --- Create SignatureRequirements for step2_flow1 ---
let sig_req1_step2 = SignatureRequirement::new( let sig_req1_step2 = SignatureRequirement::new(
201, // id 201, // id
step2_flow1.get_id(), // flow_step_id step2_flow1.get_id(), // flow_step_id
"pubkey_legal_team_lead_hex", // public_key "pubkey_legal_team_lead_hex", // public_key
"I approve this document for legal compliance.", // message "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); println!("Created SignatureRequirement: {:?}", sig_req1_step2);
let sig_req2_step2 = SignatureRequirement::new( let sig_req2_step2 = SignatureRequirement::new(
202, // id 202, // id
step2_flow1.get_id(), // flow_step_id step2_flow1.get_id(), // flow_step_id
"pubkey_general_counsel_hex", // public_key "pubkey_general_counsel_hex", // public_key
"I, as General Counsel, approve this document.", // message "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); println!("Created SignatureRequirement: {:?}", sig_req2_step2);
// --- Retrieve and Verify --- // --- Retrieve and Verify ---
@ -101,9 +116,18 @@ fn main() {
.get::<flow_step_flow_id_idx, _>(&retrieved_flow.get_id()) .get::<flow_step_flow_id_idx, _>(&retrieved_flow.get_id())
.expect("can load steps for flow1"); .expect("can load steps for flow1");
assert_eq!(steps_for_flow1.len(), 2); 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 { 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) --- // --- Update a SignatureRequirement (simulate signing) ---
@ -114,12 +138,18 @@ fn main() {
.expect("can load sig_req1") .expect("can load sig_req1")
.unwrap(); .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.status = "Signed".to_string();
retrieved_sig_req1.signed_by = Some("pubkey_legal_team_lead_hex_actual_signer".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()); 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 let updated_sig_req1 = db
.collection::<SignatureRequirement>() .collection::<SignatureRequirement>()
@ -129,10 +159,13 @@ fn main() {
.unwrap(); .unwrap();
assert_eq!(updated_sig_req1.status, "Signed"); 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); 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) // (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(); let step1_id_to_delete = step1_flow1.get_id();
db.collection::<FlowStep>() db.collection::<FlowStep>()
@ -157,7 +190,11 @@ fn main() {
.expect("can load remaining steps for flow1"); .expect("can load remaining steps for flow1");
assert_eq!(remaining_steps_for_flow1.len(), 1); assert_eq!(remaining_steps_for_flow1.len(), 1);
assert_eq!(remaining_steps_for_flow1[0].get_id(), step2_flow1.get_id()); 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!"); println!("\nFlow example finished successfully!");
} }

View File

@ -20,13 +20,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let script_path = Path::new(script_path_str); let script_path = Path::new(script_path_str);
if !script_path.exists() { if !script_path.exists() {
eprintln!("Error: Rhai script not found at {}", script_path_str); eprintln!("Error: Rhai script not found at {}", script_path_str);
eprintln!("Please ensure the script 'flow.rhai' exists in the 'examples/flow_rhai/' directory."); eprintln!(
return Err(Box::new(std::io::Error::new(std::io::ErrorKind::NotFound, format!("Rhai script not found: {}", script_path_str)))); "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); println!("Executing Rhai script: {}", script_path_str);
let script = fs::read_to_string(script_path)?; let script = fs::read_to_string(script_path)?;
match engine.eval::<()>(&script) { match engine.eval::<()>(&script) {
Ok(_) => println!("\nRhai script executed successfully!"), Ok(_) => println!("\nRhai script executed successfully!"),
Err(e) => eprintln!("\nRhai script execution failed: {}\nDetails: {:#?}", e, e), Err(e) => eprintln!("\nRhai script execution failed: {}\nDetails: {:#?}", e, e),

View File

@ -101,23 +101,23 @@ fn main() {
// Example of voting with comments using the cast_vote_with_comment method // Example of voting with comments using the cast_vote_with_comment method
println!("Adding votes with comments..."); println!("Adding votes with comments...");
// User 7 votes for 'Approve Allocation' with a comment // User 7 votes for 'Approve Allocation' with a comment
proposal = proposal.cast_vote_with_comment( proposal = proposal.cast_vote_with_comment(
Some(110), // ballot_id Some(110), // ballot_id
7, // user_id 7, // user_id
1, // chosen_option_id (Approve Allocation) 1, // chosen_option_id (Approve Allocation)
80, // shares 80, // shares
"I strongly support this proposal because it aligns with our community values." "I strongly support this proposal because it aligns with our community values.",
); );
// User 8 votes for 'Reject Allocation' with a comment // User 8 votes for 'Reject Allocation' with a comment
proposal = proposal.cast_vote_with_comment( proposal = proposal.cast_vote_with_comment(
Some(111), // ballot_id Some(111), // ballot_id
8, // user_id 8, // user_id
2, // chosen_option_id (Reject Allocation) 2, // chosen_option_id (Reject Allocation)
60, // shares 60, // shares
"I have concerns about the allocation priorities." "I have concerns about the allocation priorities.",
); );
println!("\nBallots with Comments:"); println!("\nBallots with Comments:");
@ -218,34 +218,34 @@ fn main() {
// Example of voting with comments on a private proposal // Example of voting with comments on a private proposal
println!("\nAdding votes with comments to private proposal..."); println!("\nAdding votes with comments to private proposal...");
// User 20 (eligible) votes with a comment // User 20 (eligible) votes with a comment
private_proposal = private_proposal.cast_vote_with_comment( private_proposal = private_proposal.cast_vote_with_comment(
Some(202), // ballot_id Some(202), // ballot_id
20, // user_id (eligible) 20, // user_id (eligible)
1, // chosen_option_id 1, // chosen_option_id
75, // shares 75, // shares
"I support this restructuring plan with some reservations." "I support this restructuring plan with some reservations.",
); );
// User 30 (eligible) votes with a comment // User 30 (eligible) votes with a comment
private_proposal = private_proposal.cast_vote_with_comment( private_proposal = private_proposal.cast_vote_with_comment(
Some(203), // ballot_id Some(203), // ballot_id
30, // user_id (eligible) 30, // user_id (eligible)
2, // chosen_option_id 2, // chosen_option_id
90, // shares 90, // shares
"I believe we should reconsider the timing of these changes." "I believe we should reconsider the timing of these changes.",
); );
// User 40 (ineligible) tries to vote with a comment // User 40 (ineligible) tries to vote with a comment
private_proposal = private_proposal.cast_vote_with_comment( private_proposal = private_proposal.cast_vote_with_comment(
Some(204), // ballot_id Some(204), // ballot_id
40, // user_id (ineligible) 40, // user_id (ineligible)
1, // chosen_option_id 1, // chosen_option_id
50, // shares 50, // shares
"This restructuring seems unnecessary." "This restructuring seems unnecessary.",
); );
println!("Eligible users 20 and 30 added votes with comments."); println!("Eligible users 20 and 30 added votes with comments.");
println!("Ineligible user 40 attempted to vote with a comment (should be rejected)."); println!("Ineligible user 40 attempted to vote with a comment (should be rejected).");

View File

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

View File

@ -70,7 +70,7 @@ fn main() {
.add_signer(signer2.clone()) .add_signer(signer2.clone())
.add_revision(revision1.clone()) .add_revision(revision1.clone())
.add_revision(revision2.clone()); .add_revision(revision2.clone());
// The `#[model]` derive handles `created_at` and `updated_at` in `base_data`. // 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. // `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. // 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!("\n--- Contract Details After Signing ---");
println!("{:#?}", contract); println!("{:#?}", contract);
println!("\n--- Accessing Specific Fields ---"); println!("\n--- Accessing Specific Fields ---");
println!("Contract Title: {}", contract.title); println!("Contract Title: {}", contract.title);
println!("Contract Status: {:?}", contract.status); println!("Contract Status: {:?}", contract.status);
@ -97,7 +97,10 @@ fn main() {
println!("Updated At (timestamp): {}", contract.base_data.modified_at); // From BaseModelData println!("Updated At (timestamp): {}", contract.base_data.modified_at); // From BaseModelData
if let Some(first_signer_details) = contract.signers.first() { 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); println!(" Status: {:?}", first_signer_details.status);
if let Some(signed_time) = first_signer_details.signed_at { if let Some(signed_time) = first_signer_details.signed_at {
println!(" Signed At: {}", signed_time); println!(" Signed At: {}", signed_time);
@ -110,6 +113,6 @@ fn main() {
println!(" Created By: {}", latest_rev.created_by); println!(" Created By: {}", latest_rev.created_by);
println!(" Revision Created At: {}", latest_rev.created_at); println!(" Revision Created At: {}", latest_rev.created_at);
} }
println!("\nLegal Contract Model demonstration complete."); println!("\nLegal Contract Model demonstration complete.");
} }

View File

@ -22,13 +22,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
if !script_path.exists() { if !script_path.exists() {
eprintln!("Error: Rhai script not found at {}", script_path_str); eprintln!("Error: Rhai script not found at {}", script_path_str);
eprintln!("Please ensure the script 'legal.rhai' exists in the 'examples/legal_rhai/' directory."); eprintln!(
return Err(Box::new(std::io::Error::new(std::io::ErrorKind::NotFound, format!("Rhai script not found: {}", script_path_str)))); "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); println!("Executing Rhai script: {}", script_path_str);
let script = fs::read_to_string(script_path)?; let script = fs::read_to_string(script_path)?;
match engine.eval::<()>(&script) { match engine.eval::<()>(&script) {
Ok(_) => println!("\nRhai script executed successfully!"), Ok(_) => println!("\nRhai script executed successfully!"),
Err(e) => { Err(e) => {

View File

@ -33,6 +33,5 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
fs::remove_dir_all(db_path)?; fs::remove_dir_all(db_path)?;
println!("--- Cleaned up temporary database. ---"); println!("--- Cleaned up temporary database. ---");
Ok(()) Ok(())
} }

View File

@ -59,21 +59,39 @@ fn main() {
println!("Before saving - SimpleUser DB Keys: {:?}", user.db_keys()); println!("Before saving - SimpleUser DB Keys: {:?}", user.db_keys());
println!("\nBefore saving - CustomUser ID: {}", custom_user.get_id()); 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 // Save the models to the database
let simple_collection = db.collection::<SimpleUser>().expect("can open simple user collection"); let simple_collection = db
let custom_collection = db.collection::<CustomUser>().expect("can open custom user collection"); .collection::<SimpleUser>()
.expect("can open simple user collection");
let custom_collection = db
.collection::<CustomUser>()
.expect("can open custom user collection");
let (user_id, saved_user) = simple_collection.set(&user).expect("can save simple user"); 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!("\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!("Returned SimpleUser ID: {}", user_id);
println!("\nAfter saving - CustomUser ID: {}", saved_custom_user.get_id()); println!(
println!("After saving - CustomUser DB Keys: {:?}", saved_custom_user.db_keys()); "\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); println!("Returned CustomUser ID: {}", custom_user_id);
// Verify that the IDs were auto-generated // Verify that the IDs were auto-generated
@ -83,5 +101,8 @@ fn main() {
assert_ne!(saved_custom_user.get_id(), 0); assert_ne!(saved_custom_user.get_id(), 0);
println!("\nExample finished. DB stored at {}", db_path); 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
);
} }

View File

@ -1,8 +1,8 @@
use rhai::{Engine, EvalAltResult, Scope};
use std::sync::Arc;
use heromodels::db::hero::OurDB; use heromodels::db::hero::OurDB;
use heromodels::models::projects::register_projects_rhai_module; use heromodels::models::projects::register_projects_rhai_module;
use rhai::{Engine, EvalAltResult, Scope};
use std::fs; use std::fs;
use std::sync::Arc;
fn main() -> Result<(), Box<EvalAltResult>> { fn main() -> Result<(), Box<EvalAltResult>> {
println!("Executing Rhai script: examples/project_rhai/project_test.rhai"); println!("Executing Rhai script: examples/project_rhai/project_test.rhai");
@ -18,8 +18,12 @@ fn main() -> Result<(), Box<EvalAltResult>> {
// Read the Rhai script from file // Read the Rhai script from file
let script_path = "examples/project_rhai/project_test.rhai"; let script_path = "examples/project_rhai/project_test.rhai";
let script_content = fs::read_to_string(script_path) let script_content = fs::read_to_string(script_path).map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorSystem(format!("Cannot read script file: {}", script_path), e.into())))?; Box::new(EvalAltResult::ErrorSystem(
format!("Cannot read script file: {}", script_path),
e.into(),
))
})?;
// Create a new scope // Create a new scope
let mut scope = Scope::new(); let mut scope = Scope::new();

View File

@ -34,11 +34,16 @@ fn main() {
println!("Before saving - SimpleUser DB Keys: {:?}", user.db_keys()); println!("Before saving - SimpleUser DB Keys: {:?}", user.db_keys());
// Save the user to the database // Save the user to the database
let collection = db.collection::<SimpleUser>().expect("can open user collection"); let collection = db
.collection::<SimpleUser>()
.expect("can open user collection");
let (user_id, saved_user) = collection.set(&user).expect("can save user"); let (user_id, saved_user) = collection.set(&user).expect("can save user");
println!("\nAfter saving - SimpleUser ID: {}", saved_user.get_id()); 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); println!("Returned ID: {}", user_id);
// Verify that the ID was auto-generated // Verify that the ID was auto-generated
@ -46,6 +51,8 @@ fn main() {
assert_ne!(saved_user.get_id(), 0); assert_ne!(saved_user.get_id(), 0);
println!("\nExample finished. DB stored at {}", db_path); 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
);
} }

View File

@ -57,7 +57,9 @@ where
fn get_all(&self) -> Result<Vec<V>, Error<Self::Error>>; fn get_all(&self) -> Result<Vec<V>, Error<Self::Error>>;
/// Begin a transaction for this collection /// Begin a transaction for this collection
fn begin_transaction(&self) -> Result<Box<dyn Transaction<Error = Self::Error>>, Error<Self::Error>>; fn begin_transaction(
&self,
) -> Result<Box<dyn Transaction<Error = Self::Error>>, Error<Self::Error>>;
} }
/// Errors returned by the DB implementation /// Errors returned by the DB implementation

View File

@ -436,8 +436,12 @@ where
Ok(list_of_raw_ids_set_bytes) => { Ok(list_of_raw_ids_set_bytes) => {
for raw_ids_set_bytes in list_of_raw_ids_set_bytes { for raw_ids_set_bytes in list_of_raw_ids_set_bytes {
// Each item in the list is a bincode-serialized HashSet<u32> of object IDs. // Each item in the list is a bincode-serialized HashSet<u32> of object IDs.
match bincode::serde::decode_from_slice::<HashSet<u32>, _>(&raw_ids_set_bytes, BINCODE_CONFIG) { match bincode::serde::decode_from_slice::<HashSet<u32>, _>(
Ok((ids_set, _)) => { // Destructure the tuple (HashSet<u32>, usize) &raw_ids_set_bytes,
BINCODE_CONFIG,
) {
Ok((ids_set, _)) => {
// Destructure the tuple (HashSet<u32>, usize)
all_object_ids.extend(ids_set); all_object_ids.extend(ids_set);
} }
Err(e) => { Err(e) => {

View File

@ -55,4 +55,4 @@ impl Access {
self.expires_at = expires_at; self.expires_at = expires_at;
self self
} }
} }

View File

@ -3,5 +3,5 @@ pub mod access;
pub mod rhai; pub mod rhai;
// Re-export contact, Group from the inner contact module (contact.rs) within src/models/contact/mod.rs // 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; pub use rhai::register_access_rhai_module;

View File

@ -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 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; type RhaiAccess = Access;
use crate::db::hero::OurDB;
use crate::db::Collection; use crate::db::Collection;
use crate::db::hero::OurDB;
// Helper to convert i64 from Rhai to u32 for IDs // Helper to convert i64 from Rhai to u32 for IDs
fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> { fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_| u32::try_from(id_i64).map_err(|_| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert ID '{}' to u32", id_i64).into(), format!("Failed to convert ID '{}' to u32", id_i64).into(),
Position::NONE Position::NONE,
)) ))
) })
} }
#[export_module] #[export_module]
@ -27,13 +27,16 @@ mod rhai_access_module {
let access = Access::new(); let access = Access::new();
Ok(access) Ok(access)
} }
/// Sets the access name /// Sets the access name
#[rhai_fn(name = "object_id", return_raw, global, pure)] #[rhai_fn(name = "object_id", return_raw, global, pure)]
pub fn access_object_id(access: &mut RhaiAccess, object_id: u32) -> Result<RhaiAccess, Box<EvalAltResult>> { pub fn access_object_id(
access: &mut RhaiAccess,
object_id: u32,
) -> Result<RhaiAccess, Box<EvalAltResult>> {
// Create a default Access to replace the taken one // Create a default Access to replace the taken one
let default_access = Access::new(); let default_access = Access::new();
// Take ownership of the access, apply the builder method, then put it back // Take ownership of the access, apply the builder method, then put it back
let owned_access = std::mem::replace(access, default_access); let owned_access = std::mem::replace(access, default_access);
*access = owned_access.object_id(object_id); *access = owned_access.object_id(object_id);
@ -41,21 +44,27 @@ mod rhai_access_module {
} }
#[rhai_fn(name = "circle_id", return_raw, global, pure)] #[rhai_fn(name = "circle_id", return_raw, global, pure)]
pub fn access_circle_id(access: &mut RhaiAccess, circle_id: u32) -> Result<RhaiAccess, Box<EvalAltResult>> { pub fn access_circle_id(
access: &mut RhaiAccess,
circle_id: u32,
) -> Result<RhaiAccess, Box<EvalAltResult>> {
// Create a default Access to replace the taken one // Create a default Access to replace the taken one
let default_access = Access::new(); let default_access = Access::new();
// Take ownership of the access, apply the builder method, then put it back // Take ownership of the access, apply the builder method, then put it back
let owned_access = std::mem::replace(access, default_access); let owned_access = std::mem::replace(access, default_access);
*access = owned_access.circle_id(circle_id); *access = owned_access.circle_id(circle_id);
Ok(access.clone()) Ok(access.clone())
} }
#[rhai_fn(name = "group_id", return_raw, global, pure)] #[rhai_fn(name = "group_id", return_raw, global, pure)]
pub fn access_group_id(access: &mut RhaiAccess, group_id: u32) -> Result<RhaiAccess, Box<EvalAltResult>> { pub fn access_group_id(
access: &mut RhaiAccess,
group_id: u32,
) -> Result<RhaiAccess, Box<EvalAltResult>> {
// Create a default Access to replace the taken one // Create a default Access to replace the taken one
let default_access = Access::new(); let default_access = Access::new();
// Take ownership of the access, apply the builder method, then put it back // Take ownership of the access, apply the builder method, then put it back
let owned_access = std::mem::replace(access, default_access); let owned_access = std::mem::replace(access, default_access);
*access = owned_access.group_id(group_id); *access = owned_access.group_id(group_id);
@ -63,10 +72,13 @@ mod rhai_access_module {
} }
#[rhai_fn(name = "contact_id", return_raw, global, pure)] #[rhai_fn(name = "contact_id", return_raw, global, pure)]
pub fn access_contact_id(access: &mut RhaiAccess, contact_id: u32) -> Result<RhaiAccess, Box<EvalAltResult>> { pub fn access_contact_id(
access: &mut RhaiAccess,
contact_id: u32,
) -> Result<RhaiAccess, Box<EvalAltResult>> {
// Create a default Access to replace the taken one // Create a default Access to replace the taken one
let default_access = Access::new(); let default_access = Access::new();
// Take ownership of the access, apply the builder method, then put it back // Take ownership of the access, apply the builder method, then put it back
let owned_access = std::mem::replace(access, default_access); let owned_access = std::mem::replace(access, default_access);
*access = owned_access.contact_id(contact_id); *access = owned_access.contact_id(contact_id);
@ -74,10 +86,13 @@ mod rhai_access_module {
} }
#[rhai_fn(name = "expires_at", return_raw, global, pure)] #[rhai_fn(name = "expires_at", return_raw, global, pure)]
pub fn access_expires_at(access: &mut RhaiAccess, expires_at: Option<u64>) -> Result<RhaiAccess, Box<EvalAltResult>> { pub fn access_expires_at(
access: &mut RhaiAccess,
expires_at: Option<u64>,
) -> Result<RhaiAccess, Box<EvalAltResult>> {
// Create a default Access to replace the taken one // Create a default Access to replace the taken one
let default_access = Access::new(); let default_access = Access::new();
// Take ownership of the access, apply the builder method, then put it back // Take ownership of the access, apply the builder method, then put it back
let owned_access = std::mem::replace(access, default_access); let owned_access = std::mem::replace(access, default_access);
*access = owned_access.expires_at(expires_at); *access = owned_access.expires_at(expires_at);
@ -86,90 +101,136 @@ mod rhai_access_module {
// Access Getters // Access Getters
#[rhai_fn(get = "id", pure)] #[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)] #[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)] #[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)] #[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)] #[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)] #[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)] #[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)] #[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<OurDB>) { pub fn register_access_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// Register the exported module globally // Register the exported module globally
let module = exported_module!(rhai_access_module); let module = exported_module!(rhai_access_module);
engine.register_global_module(module.into()); engine.register_global_module(module.into());
// Create a module for database functions // Create a module for database functions
let mut db_module = Module::new(); let mut db_module = Module::new();
let db_clone_set_access = db.clone(); let db_clone_set_access = db.clone();
db_module.set_native_fn("save_access", move |access: Access| -> Result<Access, Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "save_access",
let result = db_clone_set_access.set(&access) move |access: Access| -> Result<Access, Box<EvalAltResult>> {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_access: {}", e).into(), Position::NONE)))?; // Use the Collection trait method directly
let result = db_clone_set_access.set(&access).map_err(|e| {
// Return the updated access with the correct ID Box::new(EvalAltResult::ErrorRuntime(
Ok(result.1) 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' // Manually register database functions as they need to capture 'db'
let db_clone_delete_access = db.clone(); let db_clone_delete_access = db.clone();
db_module.set_native_fn("delete_access", move |access: Access| -> Result<(), Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "delete_access",
let result = db_clone_delete_access.collection::<Access>() move |access: Access| -> Result<(), Box<EvalAltResult>> {
.expect("can open access collection") // Use the Collection trait method directly
.delete_by_id(access.base_data.id) let result = db_clone_delete_access
.expect("can delete event"); .collection::<Access>()
.expect("can open access collection")
// Return the updated event with the correct ID .delete_by_id(access.base_data.id)
Ok(result) .expect("can delete event");
});
// Return the updated event with the correct ID
Ok(result)
},
);
let db_clone_get_access = db.clone(); let db_clone_get_access = db.clone();
db_module.set_native_fn("get_access_by_id", move |id_i64: INT| -> Result<Access, Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_i64)?; "get_access_by_id",
// Use the Collection trait method directly move |id_i64: INT| -> Result<Access, Box<EvalAltResult>> {
db_clone_get_access.get_by_id(id_u32) let id_u32 = id_from_i64_to_u32(id_i64)?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_access_by_id: {}", e).into(), Position::NONE)))? // Use the Collection trait method directly
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Access with ID {} not found", id_u32).into(), Position::NONE))) 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 // Add list_accesss function to get all accesss
let db_clone_list_accesss = db.clone(); let db_clone_list_accesss = db.clone();
db_module.set_native_fn("list_accesss", move || -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let collection = db_clone_list_accesss.collection::<Access>() "list_accesss",
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( move || -> Result<Dynamic, Box<EvalAltResult>> {
format!("Failed to get access collection: {:?}", e).into(), let collection = db_clone_list_accesss.collection::<Access>().map_err(|e| {
Position::NONE Box::new(EvalAltResult::ErrorRuntime(
)))?; format!("Failed to get access collection: {:?}", e).into(),
let accesss = collection.get_all() Position::NONE,
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( ))
format!("Failed to get all accesss: {:?}", e).into(), })?;
Position::NONE let accesss = collection.get_all().map_err(|e| {
)))?; Box::new(EvalAltResult::ErrorRuntime(
let mut array = Array::new(); format!("Failed to get all accesss: {:?}", e).into(),
for access in accesss { Position::NONE,
array.push(Dynamic::from(access)); ))
} })?;
Ok(Dynamic::from(array)) let mut array = Array::new();
}); for access in accesss {
array.push(Dynamic::from(access));
}
Ok(Dynamic::from(array))
},
);
// Register the database module globally // Register the database module globally
engine.register_global_module(db_module.into()); engine.register_global_module(db_module.into());

View File

@ -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::BaseModelDataOps;
use heromodels_core::{BaseModelData, Index};
use heromodels_derive::model; use heromodels_derive::model;
use rhai::{CustomType, TypeBuilder}; // For #[derive(CustomType)]
use serde::{Deserialize, Serialize};
// --- Enums --- // --- Enums ---
@ -100,17 +100,17 @@ impl Company {
status: CompanyStatus::default(), status: CompanyStatus::default(),
} }
} }
pub fn name(mut self, name: impl ToString) -> Self { pub fn name(mut self, name: impl ToString) -> Self {
self.name = name.to_string(); self.name = name.to_string();
self self
} }
pub fn registration_number(mut self, registration_number: impl ToString) -> Self { pub fn registration_number(mut self, registration_number: impl ToString) -> Self {
self.registration_number = registration_number.to_string(); self.registration_number = registration_number.to_string();
self self
} }
pub fn incorporation_date(mut self, incorporation_date: i64) -> Self { pub fn incorporation_date(mut self, incorporation_date: i64) -> Self {
self.incorporation_date = incorporation_date; self.incorporation_date = incorporation_date;
self self

View File

@ -8,17 +8,16 @@ pub mod product;
// pub mod user; // pub mod user;
// Re-export main types from sub-modules // Re-export main types from sub-modules
pub use company::{Company, CompanyStatus, BusinessType}; pub use company::{BusinessType, Company, CompanyStatus};
pub mod shareholder; pub mod shareholder;
pub use product::{Product, ProductComponent, ProductStatus, ProductType};
pub use shareholder::{Shareholder, ShareholderType}; pub use shareholder::{Shareholder, ShareholderType};
pub use product::{Product, ProductType, ProductStatus, ProductComponent};
pub mod sale; pub mod sale;
pub use sale::{Sale, SaleItem, SaleStatus}; pub use sale::{Sale, SaleItem, SaleStatus};
// pub use user::{User}; // Assuming a simple User model for now // pub use user::{User}; // Assuming a simple User model for now
#[cfg(feature = "rhai")] #[cfg(feature = "rhai")]
pub mod rhai; pub mod rhai;
#[cfg(feature = "rhai")] #[cfg(feature = "rhai")]

View File

@ -1,6 +1,6 @@
use serde::{Serialize, Deserialize};
use heromodels_core::BaseModelData; use heromodels_core::BaseModelData;
use heromodels_derive::model; use heromodels_derive::model;
use serde::{Deserialize, Serialize};
// ProductType represents the type of a product // ProductType represents the type of a product
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
@ -137,7 +137,7 @@ impl Product {
self.components.push(component); self.components.push(component);
self self
} }
pub fn components(mut self, components: Vec<ProductComponent>) -> Self { pub fn components(mut self, components: Vec<ProductComponent>) -> Self {
self.components = components; self.components = components;
self self

View File

@ -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::Collection; // For db.set and db.get_by_id
use crate::db::hero::OurDB;
use crate::db::Db; 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 super::company::{BusinessType, Company, CompanyStatus};
use crate::models::biz::shareholder::{Shareholder, ShareholderType}; use crate::models::biz::product::{Product, ProductComponent, ProductStatus, ProductType};
use crate::models::biz::product::{Product, ProductType, ProductStatus, ProductComponent};
use crate::models::biz::sale::{Sale, SaleItem, SaleStatus}; use crate::models::biz::sale::{Sale, SaleItem, SaleStatus};
use crate::models::biz::shareholder::{Shareholder, ShareholderType};
use heromodels_core::Model; use heromodels_core::Model;
type RhaiCompany = Company; type RhaiCompany = Company;
@ -21,12 +21,12 @@ type RhaiSaleItem = SaleItem;
// Helper to convert i64 from Rhai to u32 for IDs // Helper to convert i64 from Rhai to u32 for IDs
fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> { fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_| u32::try_from(id_i64).map_err(|_| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert ID '{}' to u32", id_i64).into(), format!("Failed to convert ID '{}' to u32", id_i64).into(),
Position::NONE Position::NONE,
)) ))
) })
} }
#[export_module] #[export_module]
@ -36,443 +36,541 @@ mod rhai_biz_module {
pub fn new_company() -> RhaiCompany { pub fn new_company() -> RhaiCompany {
Company::new() Company::new()
} }
// Company builder methods // Company builder methods
#[rhai_fn(name = "name", return_raw, global, pure)] #[rhai_fn(name = "name", return_raw, global, pure)]
pub fn company_name(company: &mut RhaiCompany, name: String) -> Result<RhaiCompany, Box<EvalAltResult>> { pub fn company_name(
company: &mut RhaiCompany,
name: String,
) -> Result<RhaiCompany, Box<EvalAltResult>> {
let owned_company = mem::take(company); let owned_company = mem::take(company);
*company = owned_company.name(name); *company = owned_company.name(name);
Ok(company.clone()) Ok(company.clone())
} }
#[rhai_fn(name = "fiscal_year_end", return_raw, global, pure)] #[rhai_fn(name = "fiscal_year_end", return_raw, global, pure)]
pub fn company_fiscal_year_end(company: &mut RhaiCompany, fiscal_year_end: String) -> Result<RhaiCompany, Box<EvalAltResult>> { pub fn company_fiscal_year_end(
company: &mut RhaiCompany,
fiscal_year_end: String,
) -> Result<RhaiCompany, Box<EvalAltResult>> {
let owned_company = mem::take(company); let owned_company = mem::take(company);
*company = owned_company.fiscal_year_end(fiscal_year_end); *company = owned_company.fiscal_year_end(fiscal_year_end);
Ok(company.clone()) Ok(company.clone())
} }
#[rhai_fn(name = "registration_number", return_raw, global, pure)] #[rhai_fn(name = "registration_number", return_raw, global, pure)]
pub fn company_registration_number(company: &mut RhaiCompany, reg_num: String) -> Result<RhaiCompany, Box<EvalAltResult>> { pub fn company_registration_number(
company: &mut RhaiCompany,
reg_num: String,
) -> Result<RhaiCompany, Box<EvalAltResult>> {
let owned_company = mem::take(company); let owned_company = mem::take(company);
*company = owned_company.registration_number(reg_num); *company = owned_company.registration_number(reg_num);
Ok(company.clone()) Ok(company.clone())
} }
#[rhai_fn(name = "incorporation_date", return_raw, global, pure)] #[rhai_fn(name = "incorporation_date", return_raw, global, pure)]
pub fn company_incorporation_date(company: &mut RhaiCompany, date: i64) -> Result<RhaiCompany, Box<EvalAltResult>> { pub fn company_incorporation_date(
company: &mut RhaiCompany,
date: i64,
) -> Result<RhaiCompany, Box<EvalAltResult>> {
let owned_company = mem::take(company); let owned_company = mem::take(company);
*company = owned_company.incorporation_date(date); *company = owned_company.incorporation_date(date);
Ok(company.clone()) Ok(company.clone())
} }
#[rhai_fn(name = "status", return_raw, global, pure)] #[rhai_fn(name = "status", return_raw, global, pure)]
pub fn company_status(company: &mut RhaiCompany, status: CompanyStatus) -> Result<RhaiCompany, Box<EvalAltResult>> { pub fn company_status(
company: &mut RhaiCompany,
status: CompanyStatus,
) -> Result<RhaiCompany, Box<EvalAltResult>> {
let owned_company = mem::take(company); let owned_company = mem::take(company);
*company = owned_company.status(status); *company = owned_company.status(status);
Ok(company.clone()) Ok(company.clone())
} }
#[rhai_fn(name = "business_type", return_raw, global, pure)] #[rhai_fn(name = "business_type", return_raw, global, pure)]
pub fn company_business_type(company: &mut RhaiCompany, business_type: BusinessType) -> Result<RhaiCompany, Box<EvalAltResult>> { pub fn company_business_type(
company: &mut RhaiCompany,
business_type: BusinessType,
) -> Result<RhaiCompany, Box<EvalAltResult>> {
let owned_company = mem::take(company); let owned_company = mem::take(company);
*company = owned_company.business_type(business_type); *company = owned_company.business_type(business_type);
Ok(company.clone()) Ok(company.clone())
} }
// Company getters // Company getters
#[rhai_fn(name = "get_company_id")] #[rhai_fn(name = "get_company_id")]
pub fn get_company_id(company: &mut RhaiCompany) -> i64 { pub fn get_company_id(company: &mut RhaiCompany) -> i64 {
company.get_id() as i64 company.get_id() as i64
} }
#[rhai_fn(name = "get_company_name")] #[rhai_fn(name = "get_company_name")]
pub fn get_company_name(company: &mut RhaiCompany) -> String { pub fn get_company_name(company: &mut RhaiCompany) -> String {
company.name.clone() company.name.clone()
} }
#[rhai_fn(name = "get_company_created_at")] #[rhai_fn(name = "get_company_created_at")]
pub fn get_company_created_at(company: &mut RhaiCompany) -> i64 { pub fn get_company_created_at(company: &mut RhaiCompany) -> i64 {
company.base_data.created_at company.base_data.created_at
} }
#[rhai_fn(name = "get_company_modified_at")] #[rhai_fn(name = "get_company_modified_at")]
pub fn get_company_modified_at(company: &mut RhaiCompany) -> i64 { pub fn get_company_modified_at(company: &mut RhaiCompany) -> i64 {
company.base_data.modified_at company.base_data.modified_at
} }
#[rhai_fn(name = "get_company_registration_number")] #[rhai_fn(name = "get_company_registration_number")]
pub fn get_company_registration_number(company: &mut RhaiCompany) -> String { pub fn get_company_registration_number(company: &mut RhaiCompany) -> String {
company.registration_number.clone() company.registration_number.clone()
} }
#[rhai_fn(name = "get_company_fiscal_year_end")] #[rhai_fn(name = "get_company_fiscal_year_end")]
pub fn get_company_fiscal_year_end(company: &mut RhaiCompany) -> String { pub fn get_company_fiscal_year_end(company: &mut RhaiCompany) -> String {
company.fiscal_year_end.clone() company.fiscal_year_end.clone()
} }
#[rhai_fn(name = "get_company_incorporation_date")] #[rhai_fn(name = "get_company_incorporation_date")]
pub fn get_company_incorporation_date(company: &mut RhaiCompany) -> i64 { pub fn get_company_incorporation_date(company: &mut RhaiCompany) -> i64 {
company.incorporation_date company.incorporation_date
} }
#[rhai_fn(name = "get_company_status")] #[rhai_fn(name = "get_company_status")]
pub fn get_company_status(company: &mut RhaiCompany) -> CompanyStatus { pub fn get_company_status(company: &mut RhaiCompany) -> CompanyStatus {
company.status.clone() company.status.clone()
} }
#[rhai_fn(name = "get_company_business_type")] #[rhai_fn(name = "get_company_business_type")]
pub fn get_company_business_type(company: &mut RhaiCompany) -> BusinessType { pub fn get_company_business_type(company: &mut RhaiCompany) -> BusinessType {
company.business_type.clone() company.business_type.clone()
} }
// --- Shareholder Functions --- // --- Shareholder Functions ---
#[rhai_fn(name = "new_shareholder")] #[rhai_fn(name = "new_shareholder")]
pub fn new_shareholder() -> RhaiShareholder { pub fn new_shareholder() -> RhaiShareholder {
Shareholder::new() Shareholder::new()
} }
// Shareholder builder methods // Shareholder builder methods
#[rhai_fn(name = "name", return_raw, global, pure)] #[rhai_fn(name = "name", return_raw, global, pure)]
pub fn shareholder_name(shareholder: &mut RhaiShareholder, name: String) -> Result<RhaiShareholder, Box<EvalAltResult>> { pub fn shareholder_name(
shareholder: &mut RhaiShareholder,
name: String,
) -> Result<RhaiShareholder, Box<EvalAltResult>> {
let owned_shareholder = mem::take(shareholder); let owned_shareholder = mem::take(shareholder);
*shareholder = owned_shareholder.name(name); *shareholder = owned_shareholder.name(name);
Ok(shareholder.clone()) Ok(shareholder.clone())
} }
#[rhai_fn(name = "company_id", return_raw, global, pure)] #[rhai_fn(name = "company_id", return_raw, global, pure)]
pub fn shareholder_company_id(shareholder: &mut RhaiShareholder, company_id: i64) -> Result<RhaiShareholder, Box<EvalAltResult>> { pub fn shareholder_company_id(
shareholder: &mut RhaiShareholder,
company_id: i64,
) -> Result<RhaiShareholder, Box<EvalAltResult>> {
let company_id_u32 = id_from_i64_to_u32(company_id)?; let company_id_u32 = id_from_i64_to_u32(company_id)?;
let owned_shareholder = mem::take(shareholder); let owned_shareholder = mem::take(shareholder);
*shareholder = owned_shareholder.company_id(company_id_u32); *shareholder = owned_shareholder.company_id(company_id_u32);
Ok(shareholder.clone()) Ok(shareholder.clone())
} }
#[rhai_fn(name = "share_count", return_raw, global, pure)] #[rhai_fn(name = "share_count", return_raw, global, pure)]
pub fn shareholder_share_count(shareholder: &mut RhaiShareholder, share_count: f64) -> Result<RhaiShareholder, Box<EvalAltResult>> { pub fn shareholder_share_count(
let owned_shareholder = mem::take(shareholder); shareholder: &mut RhaiShareholder,
share_count: f64,
) -> Result<RhaiShareholder, Box<EvalAltResult>> {
shareholder.shares = share_count; shareholder.shares = share_count;
Ok(shareholder.clone()) Ok(shareholder.clone())
} }
#[rhai_fn(name = "type_", return_raw, global, pure)] #[rhai_fn(name = "type_", return_raw, global, pure)]
pub fn shareholder_type(shareholder: &mut RhaiShareholder, type_: ShareholderType) -> Result<RhaiShareholder, Box<EvalAltResult>> { pub fn shareholder_type(
shareholder: &mut RhaiShareholder,
type_: ShareholderType,
) -> Result<RhaiShareholder, Box<EvalAltResult>> {
let owned_shareholder = mem::take(shareholder); let owned_shareholder = mem::take(shareholder);
*shareholder = owned_shareholder.type_(type_); *shareholder = owned_shareholder.type_(type_);
Ok(shareholder.clone()) Ok(shareholder.clone())
} }
// Shareholder getters // Shareholder getters
#[rhai_fn(name = "get_shareholder_id")] #[rhai_fn(name = "get_shareholder_id")]
pub fn get_shareholder_id(shareholder: &mut RhaiShareholder) -> i64 { pub fn get_shareholder_id(shareholder: &mut RhaiShareholder) -> i64 {
shareholder.get_id() as i64 shareholder.get_id() as i64
} }
#[rhai_fn(name = "get_shareholder_name")] #[rhai_fn(name = "get_shareholder_name")]
pub fn get_shareholder_name(shareholder: &mut RhaiShareholder) -> String { pub fn get_shareholder_name(shareholder: &mut RhaiShareholder) -> String {
shareholder.name.clone() shareholder.name.clone()
} }
#[rhai_fn(name = "get_shareholder_company_id")] #[rhai_fn(name = "get_shareholder_company_id")]
pub fn get_shareholder_company_id(shareholder: &mut RhaiShareholder) -> i64 { pub fn get_shareholder_company_id(shareholder: &mut RhaiShareholder) -> i64 {
shareholder.company_id as i64 shareholder.company_id as i64
} }
#[rhai_fn(name = "get_shareholder_share_count")] #[rhai_fn(name = "get_shareholder_share_count")]
pub fn get_shareholder_share_count(shareholder: &mut RhaiShareholder) -> i64 { pub fn get_shareholder_share_count(shareholder: &mut RhaiShareholder) -> i64 {
shareholder.shares as i64 shareholder.shares as i64
} }
#[rhai_fn(name = "get_shareholder_type")] #[rhai_fn(name = "get_shareholder_type")]
pub fn get_shareholder_type(shareholder: &mut RhaiShareholder) -> ShareholderType { pub fn get_shareholder_type(shareholder: &mut RhaiShareholder) -> ShareholderType {
shareholder.type_.clone() shareholder.type_.clone()
} }
// --- ProductComponent Functions --- // --- ProductComponent Functions ---
#[rhai_fn(name = "new_product_component")] #[rhai_fn(name = "new_product_component")]
pub fn new_product_component() -> RhaiProductComponent { pub fn new_product_component() -> RhaiProductComponent {
ProductComponent::new() ProductComponent::new()
} }
// ProductComponent builder methods // ProductComponent builder methods
#[rhai_fn(name = "name", return_raw, global, pure)] #[rhai_fn(name = "name", return_raw, global, pure)]
pub fn product_component_name(component: &mut RhaiProductComponent, name: String) -> Result<RhaiProductComponent, Box<EvalAltResult>> { pub fn product_component_name(
component: &mut RhaiProductComponent,
name: String,
) -> Result<RhaiProductComponent, Box<EvalAltResult>> {
let owned_component = mem::take(component); let owned_component = mem::take(component);
*component = owned_component.name(name); *component = owned_component.name(name);
Ok(component.clone()) Ok(component.clone())
} }
#[rhai_fn(name = "description", return_raw, global, pure)] #[rhai_fn(name = "description", return_raw, global, pure)]
pub fn product_component_description(component: &mut RhaiProductComponent, description: String) -> Result<RhaiProductComponent, Box<EvalAltResult>> { pub fn product_component_description(
component: &mut RhaiProductComponent,
description: String,
) -> Result<RhaiProductComponent, Box<EvalAltResult>> {
let owned_component = mem::take(component); let owned_component = mem::take(component);
*component = owned_component.description(description); *component = owned_component.description(description);
Ok(component.clone()) Ok(component.clone())
} }
#[rhai_fn(name = "quantity", return_raw, global, pure)] #[rhai_fn(name = "quantity", return_raw, global, pure)]
pub fn product_component_quantity(component: &mut RhaiProductComponent, quantity: i64) -> Result<RhaiProductComponent, Box<EvalAltResult>> { pub fn product_component_quantity(
component: &mut RhaiProductComponent,
quantity: i64,
) -> Result<RhaiProductComponent, Box<EvalAltResult>> {
let owned_component = mem::take(component); let owned_component = mem::take(component);
*component = owned_component.quantity(quantity as u32); *component = owned_component.quantity(quantity as u32);
Ok(component.clone()) Ok(component.clone())
} }
// ProductComponent getters // ProductComponent getters
#[rhai_fn(name = "get_product_component_name")] #[rhai_fn(name = "get_product_component_name")]
pub fn get_product_component_name(component: &mut RhaiProductComponent) -> String { pub fn get_product_component_name(component: &mut RhaiProductComponent) -> String {
component.name.clone() component.name.clone()
} }
#[rhai_fn(name = "get_product_component_description")] #[rhai_fn(name = "get_product_component_description")]
pub fn get_product_component_description(component: &mut RhaiProductComponent) -> String { pub fn get_product_component_description(component: &mut RhaiProductComponent) -> String {
component.description.clone() component.description.clone()
} }
#[rhai_fn(name = "get_product_component_quantity")] #[rhai_fn(name = "get_product_component_quantity")]
pub fn get_product_component_quantity(component: &mut RhaiProductComponent) -> i64 { pub fn get_product_component_quantity(component: &mut RhaiProductComponent) -> i64 {
component.quantity as i64 component.quantity as i64
} }
// --- Product Functions --- // --- Product Functions ---
#[rhai_fn(name = "new_product")] #[rhai_fn(name = "new_product")]
pub fn new_product() -> RhaiProduct { pub fn new_product() -> RhaiProduct {
Product::new() Product::new()
} }
// Product builder methods // Product builder methods
#[rhai_fn(name = "name", return_raw, global, pure)] #[rhai_fn(name = "name", return_raw, global, pure)]
pub fn product_name(product: &mut RhaiProduct, name: String) -> Result<RhaiProduct, Box<EvalAltResult>> { pub fn product_name(
product: &mut RhaiProduct,
name: String,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product); let owned_product = mem::take(product);
*product = owned_product.name(name); *product = owned_product.name(name);
Ok(product.clone()) Ok(product.clone())
} }
#[rhai_fn(name = "description", return_raw, global, pure)] #[rhai_fn(name = "description", return_raw, global, pure)]
pub fn product_description(product: &mut RhaiProduct, description: String) -> Result<RhaiProduct, Box<EvalAltResult>> { pub fn product_description(
product: &mut RhaiProduct,
description: String,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product); let owned_product = mem::take(product);
*product = owned_product.description(description); *product = owned_product.description(description);
Ok(product.clone()) Ok(product.clone())
} }
#[rhai_fn(name = "price", return_raw, global, pure)] #[rhai_fn(name = "price", return_raw, global, pure)]
pub fn product_price(product: &mut RhaiProduct, price: f64) -> Result<RhaiProduct, Box<EvalAltResult>> { pub fn product_price(
product: &mut RhaiProduct,
price: f64,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product); let owned_product = mem::take(product);
*product = owned_product.price(price); *product = owned_product.price(price);
Ok(product.clone()) Ok(product.clone())
} }
#[rhai_fn(name = "type_", return_raw, global, pure)] #[rhai_fn(name = "type_", return_raw, global, pure)]
pub fn product_type(product: &mut RhaiProduct, type_: ProductType) -> Result<RhaiProduct, Box<EvalAltResult>> { pub fn product_type(
product: &mut RhaiProduct,
type_: ProductType,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product); let owned_product = mem::take(product);
*product = owned_product.type_(type_); *product = owned_product.type_(type_);
Ok(product.clone()) Ok(product.clone())
} }
#[rhai_fn(name = "category", return_raw, global, pure)] #[rhai_fn(name = "category", return_raw, global, pure)]
pub fn product_category(product: &mut RhaiProduct, category: String) -> Result<RhaiProduct, Box<EvalAltResult>> { pub fn product_category(
product: &mut RhaiProduct,
category: String,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product); let owned_product = mem::take(product);
*product = owned_product.category(category); *product = owned_product.category(category);
Ok(product.clone()) Ok(product.clone())
} }
#[rhai_fn(name = "status", return_raw, global, pure)] #[rhai_fn(name = "status", return_raw, global, pure)]
pub fn product_status(product: &mut RhaiProduct, status: ProductStatus) -> Result<RhaiProduct, Box<EvalAltResult>> { pub fn product_status(
product: &mut RhaiProduct,
status: ProductStatus,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product); let owned_product = mem::take(product);
*product = owned_product.status(status); *product = owned_product.status(status);
Ok(product.clone()) Ok(product.clone())
} }
#[rhai_fn(name = "max_amount", return_raw, global, pure)] #[rhai_fn(name = "max_amount", return_raw, global, pure)]
pub fn product_max_amount(product: &mut RhaiProduct, max_amount: i64) -> Result<RhaiProduct, Box<EvalAltResult>> { pub fn product_max_amount(
product: &mut RhaiProduct,
max_amount: i64,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product); let owned_product = mem::take(product);
*product = owned_product.max_amount(max_amount as u16); *product = owned_product.max_amount(max_amount as u16);
Ok(product.clone()) Ok(product.clone())
} }
#[rhai_fn(name = "purchase_till", return_raw, global, pure)] #[rhai_fn(name = "purchase_till", return_raw, global, pure)]
pub fn product_purchase_till(product: &mut RhaiProduct, purchase_till: i64) -> Result<RhaiProduct, Box<EvalAltResult>> { pub fn product_purchase_till(
product: &mut RhaiProduct,
purchase_till: i64,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product); let owned_product = mem::take(product);
*product = owned_product.purchase_till(purchase_till); *product = owned_product.purchase_till(purchase_till);
Ok(product.clone()) Ok(product.clone())
} }
#[rhai_fn(name = "active_till", return_raw, global, pure)] #[rhai_fn(name = "active_till", return_raw, global, pure)]
pub fn product_active_till(product: &mut RhaiProduct, active_till: i64) -> Result<RhaiProduct, Box<EvalAltResult>> { pub fn product_active_till(
product: &mut RhaiProduct,
active_till: i64,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product); let owned_product = mem::take(product);
*product = owned_product.active_till(active_till); *product = owned_product.active_till(active_till);
Ok(product.clone()) Ok(product.clone())
} }
#[rhai_fn(name = "add_component", return_raw, global, pure)] #[rhai_fn(name = "add_component", return_raw, global, pure)]
pub fn product_add_component(product: &mut RhaiProduct, component: RhaiProductComponent) -> Result<RhaiProduct, Box<EvalAltResult>> { pub fn product_add_component(
product: &mut RhaiProduct,
component: RhaiProductComponent,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product); let owned_product = mem::take(product);
*product = owned_product.add_component(component); *product = owned_product.add_component(component);
Ok(product.clone()) Ok(product.clone())
} }
#[rhai_fn(name = "components", return_raw, global, pure)] #[rhai_fn(name = "components", return_raw, global, pure)]
pub fn product_components(product: &mut RhaiProduct, components: Vec<RhaiProductComponent>) -> Result<RhaiProduct, Box<EvalAltResult>> { pub fn product_components(
product: &mut RhaiProduct,
components: Vec<RhaiProductComponent>,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product); let owned_product = mem::take(product);
*product = owned_product.components(components); *product = owned_product.components(components);
Ok(product.clone()) Ok(product.clone())
} }
// Product getters // Product getters
#[rhai_fn(name = "get_product_id")] #[rhai_fn(name = "get_product_id")]
pub fn get_product_id(product: &mut RhaiProduct) -> i64 { pub fn get_product_id(product: &mut RhaiProduct) -> i64 {
product.get_id() as i64 product.get_id() as i64
} }
#[rhai_fn(name = "get_product_name")] #[rhai_fn(name = "get_product_name")]
pub fn get_product_name(product: &mut RhaiProduct) -> String { pub fn get_product_name(product: &mut RhaiProduct) -> String {
product.name.clone() product.name.clone()
} }
#[rhai_fn(name = "get_product_description")] #[rhai_fn(name = "get_product_description")]
pub fn get_product_description(product: &mut RhaiProduct) -> String { pub fn get_product_description(product: &mut RhaiProduct) -> String {
product.description.clone() product.description.clone()
} }
#[rhai_fn(name = "get_product_price")] #[rhai_fn(name = "get_product_price")]
pub fn get_product_price(product: &mut RhaiProduct) -> f64 { pub fn get_product_price(product: &mut RhaiProduct) -> f64 {
product.price product.price
} }
#[rhai_fn(name = "get_product_type")] #[rhai_fn(name = "get_product_type")]
pub fn get_product_type(product: &mut RhaiProduct) -> ProductType { pub fn get_product_type(product: &mut RhaiProduct) -> ProductType {
product.type_.clone() product.type_.clone()
} }
#[rhai_fn(name = "get_product_category")] #[rhai_fn(name = "get_product_category")]
pub fn get_product_category(product: &mut RhaiProduct) -> String { pub fn get_product_category(product: &mut RhaiProduct) -> String {
product.category.clone() product.category.clone()
} }
#[rhai_fn(name = "get_product_status")] #[rhai_fn(name = "get_product_status")]
pub fn get_product_status(product: &mut RhaiProduct) -> ProductStatus { pub fn get_product_status(product: &mut RhaiProduct) -> ProductStatus {
product.status.clone() product.status.clone()
} }
#[rhai_fn(name = "get_product_max_amount")] #[rhai_fn(name = "get_product_max_amount")]
pub fn get_product_max_amount(product: &mut RhaiProduct) -> i64 { pub fn get_product_max_amount(product: &mut RhaiProduct) -> i64 {
product.max_amount as i64 product.max_amount as i64
} }
#[rhai_fn(name = "get_product_purchase_till")] #[rhai_fn(name = "get_product_purchase_till")]
pub fn get_product_purchase_till(product: &mut RhaiProduct) -> i64 { pub fn get_product_purchase_till(product: &mut RhaiProduct) -> i64 {
product.purchase_till product.purchase_till
} }
#[rhai_fn(name = "get_product_active_till")] #[rhai_fn(name = "get_product_active_till")]
pub fn get_product_active_till(product: &mut RhaiProduct) -> i64 { pub fn get_product_active_till(product: &mut RhaiProduct) -> i64 {
product.active_till product.active_till
} }
#[rhai_fn(name = "get_product_components")] #[rhai_fn(name = "get_product_components")]
pub fn get_product_components(product: &mut RhaiProduct) -> Vec<RhaiProductComponent> { pub fn get_product_components(product: &mut RhaiProduct) -> Vec<RhaiProductComponent> {
product.components.clone() product.components.clone()
} }
#[rhai_fn(name = "get_product_created_at")] #[rhai_fn(name = "get_product_created_at")]
pub fn get_product_created_at(product: &mut RhaiProduct) -> i64 { pub fn get_product_created_at(product: &mut RhaiProduct) -> i64 {
product.base_data.created_at product.base_data.created_at
} }
#[rhai_fn(name = "get_product_modified_at")] #[rhai_fn(name = "get_product_modified_at")]
pub fn get_product_modified_at(product: &mut RhaiProduct) -> i64 { pub fn get_product_modified_at(product: &mut RhaiProduct) -> i64 {
product.base_data.modified_at product.base_data.modified_at
} }
#[rhai_fn(name = "get_product_comments")] #[rhai_fn(name = "get_product_comments")]
pub fn get_product_comments(product: &mut RhaiProduct) -> Vec<i64> { pub fn get_product_comments(product: &mut RhaiProduct) -> Vec<i64> {
product.base_data.comments.iter().map(|&id| id as i64).collect() product
.base_data
.comments
.iter()
.map(|&id| id as i64)
.collect()
} }
// --- SaleItem Functions --- // --- SaleItem Functions ---
#[rhai_fn(name = "new_sale_item")] #[rhai_fn(name = "new_sale_item")]
pub fn new_sale_item() -> RhaiSaleItem { pub fn new_sale_item() -> RhaiSaleItem {
SaleItem::new() SaleItem::new()
} }
// SaleItem builder methods // SaleItem builder methods
#[rhai_fn(name = "name", return_raw, global, pure)] #[rhai_fn(name = "name", return_raw, global, pure)]
pub fn sale_item_name(item: &mut RhaiSaleItem, name: String) -> Result<RhaiSaleItem, Box<EvalAltResult>> { pub fn sale_item_name(
item: &mut RhaiSaleItem,
name: String,
) -> Result<RhaiSaleItem, Box<EvalAltResult>> {
let owned_item = mem::take(item); let owned_item = mem::take(item);
*item = owned_item.name(name); *item = owned_item.name(name);
Ok(item.clone()) Ok(item.clone())
} }
#[rhai_fn(name = "price", return_raw, global, pure)] #[rhai_fn(name = "price", return_raw, global, pure)]
pub fn sale_item_price(item: &mut RhaiSaleItem, price: f64) -> Result<RhaiSaleItem, Box<EvalAltResult>> { pub fn sale_item_price(
let owned_item = mem::take(item); item: &mut RhaiSaleItem,
price: f64,
) -> Result<RhaiSaleItem, Box<EvalAltResult>> {
item.unit_price = price; item.unit_price = price;
Ok(item.clone()) Ok(item.clone())
} }
#[rhai_fn(name = "quantity", return_raw, global, pure)] #[rhai_fn(name = "quantity", return_raw, global, pure)]
pub fn sale_item_quantity(item: &mut RhaiSaleItem, quantity: i64) -> Result<RhaiSaleItem, Box<EvalAltResult>> { pub fn sale_item_quantity(
item: &mut RhaiSaleItem,
quantity: i64,
) -> Result<RhaiSaleItem, Box<EvalAltResult>> {
let owned_item = mem::take(item); let owned_item = mem::take(item);
*item = owned_item.quantity(quantity.try_into().unwrap()); *item = owned_item.quantity(quantity.try_into().unwrap());
Ok(item.clone()) Ok(item.clone())
} }
#[rhai_fn(name = "product_id", return_raw, global, pure)] #[rhai_fn(name = "product_id", return_raw, global, pure)]
pub fn sale_item_product_id(item: &mut RhaiSaleItem, product_id: i64) -> Result<RhaiSaleItem, Box<EvalAltResult>> { pub fn sale_item_product_id(
item: &mut RhaiSaleItem,
product_id: i64,
) -> Result<RhaiSaleItem, Box<EvalAltResult>> {
let product_id_u32 = id_from_i64_to_u32(product_id)?; let product_id_u32 = id_from_i64_to_u32(product_id)?;
let owned_item = mem::take(item); let owned_item = mem::take(item);
*item = owned_item.product_id(product_id_u32); *item = owned_item.product_id(product_id_u32);
Ok(item.clone()) Ok(item.clone())
} }
// SaleItem getters // SaleItem getters
#[rhai_fn(name = "get_sale_item_name")] #[rhai_fn(name = "get_sale_item_name")]
pub fn get_sale_item_name(item: &mut RhaiSaleItem) -> String { pub fn get_sale_item_name(item: &mut RhaiSaleItem) -> String {
item.name.clone() item.name.clone()
} }
#[rhai_fn(name = "get_sale_item_price")] #[rhai_fn(name = "get_sale_item_price")]
pub fn get_sale_item_price(item: &mut RhaiSaleItem) -> f64 { pub fn get_sale_item_price(item: &mut RhaiSaleItem) -> f64 {
item.unit_price item.unit_price
} }
#[rhai_fn(name = "get_sale_item_quantity")] #[rhai_fn(name = "get_sale_item_quantity")]
pub fn get_sale_item_quantity(item: &mut RhaiSaleItem) -> i64 { pub fn get_sale_item_quantity(item: &mut RhaiSaleItem) -> i64 {
item.quantity as i64 item.quantity as i64
} }
#[rhai_fn(name = "get_sale_item_product_id")] #[rhai_fn(name = "get_sale_item_product_id")]
pub fn get_sale_item_product_id(item: &mut RhaiSaleItem) -> i64 { pub fn get_sale_item_product_id(item: &mut RhaiSaleItem) -> i64 {
item.product_id as i64 item.product_id as i64
} }
// --- Sale Functions --- // --- Sale Functions ---
#[rhai_fn(name = "new_sale")] #[rhai_fn(name = "new_sale")]
pub fn new_sale() -> RhaiSale { pub fn new_sale() -> RhaiSale {
Sale::new() Sale::new()
} }
#[rhai_fn(name = "transaction_id", return_raw, global, pure)] #[rhai_fn(name = "transaction_id", return_raw, global, pure)]
pub fn sale_transaction_id(sale: &mut RhaiSale, transaction_id: u32) -> Result<RhaiSale, Box<EvalAltResult>> { pub fn sale_transaction_id(
let owned_sale = mem::take(sale); sale: &mut RhaiSale,
transaction_id: u32,
) -> Result<RhaiSale, Box<EvalAltResult>> {
sale.transaction_id = transaction_id; sale.transaction_id = transaction_id;
Ok(sale.clone()) Ok(sale.clone())
} }
#[rhai_fn(name = "status", return_raw, global, pure)] #[rhai_fn(name = "status", return_raw, global, pure)]
pub fn sale_status(sale: &mut RhaiSale, status: SaleStatus) -> Result<RhaiSale, Box<EvalAltResult>> { pub fn sale_status(
sale: &mut RhaiSale,
status: SaleStatus,
) -> Result<RhaiSale, Box<EvalAltResult>> {
let owned_sale = mem::take(sale); let owned_sale = mem::take(sale);
*sale = owned_sale.status(status); *sale = owned_sale.status(status);
Ok(sale.clone()) Ok(sale.clone())
} }
#[rhai_fn(name = "add_item", return_raw, global, pure)] #[rhai_fn(name = "add_item", return_raw, global, pure)]
pub fn sale_add_item(sale: &mut RhaiSale, item: RhaiSaleItem) -> Result<RhaiSale, Box<EvalAltResult>> { pub fn sale_add_item(
sale: &mut RhaiSale,
item: RhaiSaleItem,
) -> Result<RhaiSale, Box<EvalAltResult>> {
let owned_sale = mem::take(sale); let owned_sale = mem::take(sale);
*sale = owned_sale.add_item(item); *sale = owned_sale.add_item(item);
Ok(sale.clone()) Ok(sale.clone())
} }
#[rhai_fn(name = "items", return_raw, global, pure)] #[rhai_fn(name = "items", return_raw, global, pure)]
pub fn sale_items(sale: &mut RhaiSale, items: Vec<RhaiSaleItem>) -> Result<RhaiSale, Box<EvalAltResult>> { pub fn sale_items(
sale: &mut RhaiSale,
items: Vec<RhaiSaleItem>,
) -> Result<RhaiSale, Box<EvalAltResult>> {
let owned_sale = mem::take(sale); let owned_sale = mem::take(sale);
*sale = owned_sale.items(items); *sale = owned_sale.items(items);
Ok(sale.clone()) Ok(sale.clone())
@ -483,35 +581,39 @@ mod rhai_biz_module {
pub fn get_sale_id(sale: &mut RhaiSale) -> i64 { pub fn get_sale_id(sale: &mut RhaiSale) -> i64 {
sale.get_id() as i64 sale.get_id() as i64
} }
#[rhai_fn(name = "get_sale_transaction_id")] #[rhai_fn(name = "get_sale_transaction_id")]
pub fn get_sale_transaction_id(sale: &mut RhaiSale) -> u32 { pub fn get_sale_transaction_id(sale: &mut RhaiSale) -> u32 {
sale.transaction_id sale.transaction_id
} }
#[rhai_fn(name = "get_sale_status")] #[rhai_fn(name = "get_sale_status")]
pub fn get_sale_status(sale: &mut RhaiSale) -> SaleStatus { pub fn get_sale_status(sale: &mut RhaiSale) -> SaleStatus {
sale.status.clone() sale.status.clone()
} }
#[rhai_fn(name = "get_sale_items")] #[rhai_fn(name = "get_sale_items")]
pub fn get_sale_items(sale: &mut RhaiSale) -> Vec<RhaiSaleItem> { pub fn get_sale_items(sale: &mut RhaiSale) -> Vec<RhaiSaleItem> {
sale.items.clone() sale.items.clone()
} }
#[rhai_fn(name = "get_sale_created_at")] #[rhai_fn(name = "get_sale_created_at")]
pub fn get_sale_created_at(sale: &mut RhaiSale) -> i64 { pub fn get_sale_created_at(sale: &mut RhaiSale) -> i64 {
sale.base_data.created_at sale.base_data.created_at
} }
#[rhai_fn(name = "get_sale_modified_at")] #[rhai_fn(name = "get_sale_modified_at")]
pub fn get_sale_modified_at(sale: &mut RhaiSale) -> i64 { pub fn get_sale_modified_at(sale: &mut RhaiSale) -> i64 {
sale.base_data.modified_at sale.base_data.modified_at
} }
#[rhai_fn(name = "get_sale_comments")] #[rhai_fn(name = "get_sale_comments")]
pub fn get_sale_comments(sale: &mut RhaiSale) -> Vec<i64> { pub fn get_sale_comments(sale: &mut RhaiSale) -> Vec<i64> {
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<OurDB>) {
// Register the exported module globally // Register the exported module globally
let module = exported_module!(rhai_biz_module); let module = exported_module!(rhai_biz_module);
engine.register_global_module(module.into()); engine.register_global_module(module.into());
// Create a new module for database operations // Create a new module for database operations
let mut db_module = Module::new(); let mut db_module = Module::new();
// Database operations will obtain fresh collection handles directly. // Database operations will obtain fresh collection handles directly.
// Add database functions for Company // Add database functions for Company
let db_for_set_company = Arc::clone(&db); let db_for_set_company = Arc::clone(&db);
db_module.set_native_fn("set_company", move |company: Company| -> Result<INT, Box<EvalAltResult>> { db_module.set_native_fn(
let company_collection_set = db_for_set_company.collection::<Company>().expect("Failed to get company collection for set in closure"); "set_company",
company_collection_set.set(&company) move |company: Company| -> Result<INT, Box<EvalAltResult>> {
.map(|(id_val, _)| id_val as INT) let company_collection_set = db_for_set_company
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .collection::<Company>()
format!("Failed to save company: {:?}", e).into(), .expect("Failed to get company collection for set in closure");
Position::NONE 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); let db_for_get_company = Arc::clone(&db);
db_module.set_native_fn("get_company_by_id", move |id: INT| -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let company_collection_get = db_for_get_company.collection::<Company>().expect("Failed to get company collection for get in closure"); "get_company_by_id",
let id_u32 = id_from_i64_to_u32(id)?; move |id: INT| -> Result<Dynamic, Box<EvalAltResult>> {
company_collection_get.get_by_id(id_u32) let company_collection_get = db_for_get_company
.map(Dynamic::from) .collection::<Company>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .expect("Failed to get company collection for get in closure");
format!("Failed to get company with id {}: {:?}", id, e).into(), let id_u32 = id_from_i64_to_u32(id)?;
Position::NONE 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 // Add database functions for Shareholder
let db_for_set_shareholder = Arc::clone(&db); let db_for_set_shareholder = Arc::clone(&db);
db_module.set_native_fn("set_shareholder", move |shareholder: Shareholder| -> Result<INT, Box<EvalAltResult>> { db_module.set_native_fn(
let shareholder_collection_set = db_for_set_shareholder.collection::<Shareholder>().expect("Failed to get shareholder collection for set in closure"); "set_shareholder",
shareholder_collection_set.set(&shareholder) move |shareholder: Shareholder| -> Result<INT, Box<EvalAltResult>> {
.map(|(id_val, _)| id_val as INT) let shareholder_collection_set = db_for_set_shareholder
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .collection::<Shareholder>()
format!("Failed to save shareholder: {:?}", e).into(), .expect("Failed to get shareholder collection for set in closure");
Position::NONE 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); let db_for_get_shareholder = Arc::clone(&db);
db_module.set_native_fn("get_shareholder_by_id", move |id: INT| -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let shareholder_collection_get = db_for_get_shareholder.collection::<Shareholder>().expect("Failed to get shareholder collection for get in closure"); "get_shareholder_by_id",
let id_u32 = id_from_i64_to_u32(id)?; move |id: INT| -> Result<Dynamic, Box<EvalAltResult>> {
shareholder_collection_get.get_by_id(id_u32) let shareholder_collection_get = db_for_get_shareholder
.map(Dynamic::from) .collection::<Shareholder>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .expect("Failed to get shareholder collection for get in closure");
format!("Failed to get shareholder with id {}: {:?}", id, e).into(), let id_u32 = id_from_i64_to_u32(id)?;
Position::NONE 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 // Add database functions for Product
let db_for_set_product = Arc::clone(&db); let db_for_set_product = Arc::clone(&db);
db_module.set_native_fn("set_product", move |product: Product| -> Result<INT, Box<EvalAltResult>> { db_module.set_native_fn(
let product_collection_set = db_for_set_product.collection::<Product>().expect("Failed to get product collection for set in closure"); "set_product",
product_collection_set.set(&product) move |product: Product| -> Result<INT, Box<EvalAltResult>> {
.map(|(id_val, _)| id_val as INT) let product_collection_set = db_for_set_product
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .collection::<Product>()
format!("Failed to save product: {:?}", e).into(), .expect("Failed to get product collection for set in closure");
Position::NONE 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); let db_for_get_product = Arc::clone(&db);
db_module.set_native_fn("get_product_by_id", move |id: INT| -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let product_collection_get = db_for_get_product.collection::<Product>().expect("Failed to get product collection for get in closure"); "get_product_by_id",
let id_u32 = id_from_i64_to_u32(id)?; move |id: INT| -> Result<Dynamic, Box<EvalAltResult>> {
product_collection_get.get_by_id(id_u32) let product_collection_get = db_for_get_product
.map(Dynamic::from) .collection::<Product>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .expect("Failed to get product collection for get in closure");
format!("Failed to get product with id {}: {:?}", id, e).into(), let id_u32 = id_from_i64_to_u32(id)?;
Position::NONE 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 // Add database functions for Sale
let db_for_set_sale = Arc::clone(&db); let db_for_set_sale = Arc::clone(&db);
db_module.set_native_fn("set_sale", move |sale: Sale| -> Result<INT, Box<EvalAltResult>> { db_module.set_native_fn(
let sale_collection_set = db_for_set_sale.collection::<Sale>().expect("Failed to get sale collection for set in closure"); "set_sale",
sale_collection_set.set(&sale) move |sale: Sale| -> Result<INT, Box<EvalAltResult>> {
.map(|(id_val, _)| id_val as INT) let sale_collection_set = db_for_set_sale
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .collection::<Sale>()
format!("Failed to save sale: {:?}", e).into(), .expect("Failed to get sale collection for set in closure");
Position::NONE 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); let db_for_get_sale = Arc::clone(&db);
db_module.set_native_fn("get_sale_by_id", move |id: INT| -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let sale_collection_get = db_for_get_sale.collection::<Sale>().expect("Failed to get sale collection for get in closure"); "get_sale_by_id",
let id_u32 = id_from_i64_to_u32(id)?; move |id: INT| -> Result<Dynamic, Box<EvalAltResult>> {
sale_collection_get.get_by_id(id_u32) let sale_collection_get = db_for_get_sale
.map(Dynamic::from) .collection::<Sale>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .expect("Failed to get sale collection for get in closure");
format!("Failed to get sale with id {}: {:?}", id, e).into(), let id_u32 = id_from_i64_to_u32(id)?;
Position::NONE 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 // Register the database module globally
engine.register_global_module(db_module.into()); engine.register_global_module(db_module.into());
println!("Successfully registered biz Rhai module using export_module approach."); println!("Successfully registered biz Rhai module using export_module approach.");
} }

View File

@ -1,5 +1,5 @@
use heromodels_core::{BaseModelData, BaseModelDataOps, Model};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use heromodels_core::{BaseModelData, Model, BaseModelDataOps};
/// Represents the status of a sale. /// Represents the status of a sale.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use heromodels_core::BaseModelData; use heromodels_core::BaseModelData;
use heromodels_derive::model; use heromodels_derive::model;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ShareholderType { pub enum ShareholderType {
@ -31,13 +31,13 @@ impl Shareholder {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
base_data: BaseModelData::new(), base_data: BaseModelData::new(),
company_id: 0, // Default, to be set by builder company_id: 0, // Default, to be set by builder
user_id: 0, // Default, to be set by builder user_id: 0, // Default, to be set by builder
name: String::new(), // Default name: String::new(), // Default
shares: 0.0, // Default shares: 0.0, // Default
percentage: 0.0, // Default percentage: 0.0, // Default
type_: ShareholderType::default(), // Uses ShareholderType's Default impl 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 // Base data operations are now handled by BaseModelDataOps trait
} }

View File

@ -26,7 +26,7 @@ impl AttendanceStatus {
_ => Err(format!("Invalid attendance status: '{}'", s)), _ => Err(format!("Invalid attendance status: '{}'", s)),
} }
} }
/// Convert an AttendanceStatus to a string /// Convert an AttendanceStatus to a string
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
match self { match self {
@ -134,7 +134,11 @@ impl Event {
/// Adds an attendee to the event /// Adds an attendee to the event
pub fn add_attendee(mut self, attendee: Attendee) -> Self { pub fn add_attendee(mut self, attendee: Attendee) -> Self {
// Prevent duplicate attendees by contact_id // 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.attendees.push(attendee);
} }
self self
@ -148,18 +152,18 @@ impl Event {
/// Updates the status of an existing attendee /// Updates the status of an existing attendee
pub fn update_attendee_status(mut self, contact_id: u32, status: AttendanceStatus) -> Self { 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; attendee.status = status;
} }
self self
} }
/// Reschedules the event to new start and end times /// Reschedules the event to new start and end times
pub fn reschedule( pub fn reschedule(mut self, new_start_time: i64, new_end_time: i64) -> Self {
mut self,
new_start_time: i64,
new_end_time: i64,
) -> Self {
// Basic validation: end_time should be after start_time // Basic validation: end_time should be after start_time
if new_end_time > new_start_time { if new_end_time > new_start_time {
self.start_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 /// Removes an event from the calendar by its ID
pub fn remove_event(mut self, event_id_to_remove: i64) -> Self { 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 self
} }
} }

View File

@ -3,5 +3,5 @@ pub mod calendar;
pub mod rhai; pub mod rhai;
// Re-export Calendar, Event, Attendee, and AttendanceStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs // 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; pub use rhai::register_calendar_rhai_module;

View File

@ -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 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 RhaiEvent = Event;
type RhaiAttendee = Attendee; type RhaiAttendee = Attendee;
type RhaiCalendar = Calendar; type RhaiCalendar = Calendar;
use crate::db::hero::OurDB;
use crate::db::Collection; use crate::db::Collection;
use crate::db::hero::OurDB;
// Helper to convert i64 from Rhai to u32 for IDs // Helper to convert i64 from Rhai to u32 for IDs
fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> { fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_| u32::try_from(id_i64).map_err(|_| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert ID '{}' to u32", id_i64).into(), format!("Failed to convert ID '{}' to u32", id_i64).into(),
Position::NONE Position::NONE,
)) ))
) })
} }
#[export_module] #[export_module]
@ -28,26 +28,35 @@ mod rhai_calendar_module {
pub fn new_event() -> RhaiEvent { pub fn new_event() -> RhaiEvent {
Event::new() Event::new()
} }
/// Sets the event title /// Sets the event title
#[rhai_fn(name = "title", return_raw, global, pure)] #[rhai_fn(name = "title", return_raw, global, pure)]
pub fn event_title(event: &mut RhaiEvent, title: String) -> Result<RhaiEvent, Box<EvalAltResult>> { pub fn event_title(
event: &mut RhaiEvent,
title: String,
) -> Result<RhaiEvent, Box<EvalAltResult>> {
let owned_event = mem::take(event); let owned_event = mem::take(event);
*event = owned_event.title(title); *event = owned_event.title(title);
Ok(event.clone()) Ok(event.clone())
} }
/// Sets the event description /// Sets the event description
#[rhai_fn(name = "description", return_raw, global, pure)] #[rhai_fn(name = "description", return_raw, global, pure)]
pub fn event_description(event: &mut RhaiEvent, description: String) -> Result<RhaiEvent, Box<EvalAltResult>> { pub fn event_description(
event: &mut RhaiEvent,
description: String,
) -> Result<RhaiEvent, Box<EvalAltResult>> {
let owned_event = mem::take(event); let owned_event = mem::take(event);
*event = owned_event.description(description); *event = owned_event.description(description);
Ok(event.clone()) Ok(event.clone())
} }
/// Sets the event location /// Sets the event location
#[rhai_fn(name = "location", return_raw, global, pure)] #[rhai_fn(name = "location", return_raw, global, pure)]
pub fn event_location(event: &mut RhaiEvent, location: String) -> Result<RhaiEvent, Box<EvalAltResult>> { pub fn event_location(
event: &mut RhaiEvent,
location: String,
) -> Result<RhaiEvent, Box<EvalAltResult>> {
let owned_event = mem::take(event); let owned_event = mem::take(event);
*event = owned_event.location(location); *event = owned_event.location(location);
Ok(event.clone()) Ok(event.clone())
@ -55,7 +64,10 @@ mod rhai_calendar_module {
/// Adds an attendee to the event /// Adds an attendee to the event
#[rhai_fn(name = "add_attendee", return_raw, global, pure)] #[rhai_fn(name = "add_attendee", return_raw, global, pure)]
pub fn event_add_attendee(event: &mut RhaiEvent, attendee: RhaiAttendee) -> Result<RhaiEvent, Box<EvalAltResult>> { pub fn event_add_attendee(
event: &mut RhaiEvent,
attendee: RhaiAttendee,
) -> Result<RhaiEvent, Box<EvalAltResult>> {
// Use take to get ownership of the event // Use take to get ownership of the event
let owned_event = mem::take(event); let owned_event = mem::take(event);
*event = owned_event.add_attendee(attendee); *event = owned_event.add_attendee(attendee);
@ -64,30 +76,38 @@ mod rhai_calendar_module {
/// Reschedules the event with new start and end times /// Reschedules the event with new start and end times
#[rhai_fn(name = "reschedule", return_raw, global, pure)] #[rhai_fn(name = "reschedule", return_raw, global, pure)]
pub fn event_reschedule(event: &mut RhaiEvent, new_start_time: i64, new_end_time: i64) -> Result<RhaiEvent, Box<EvalAltResult>> { pub fn event_reschedule(
event: &mut RhaiEvent,
new_start_time: i64,
new_end_time: i64,
) -> Result<RhaiEvent, Box<EvalAltResult>> {
// Validate timestamps // Validate timestamps
if new_end_time <= new_start_time { if new_end_time <= new_start_time {
return Err(Box::new(EvalAltResult::ErrorRuntime( return Err(Box::new(EvalAltResult::ErrorRuntime(
"End time must be after start time".into(), "End time must be after start time".into(),
Position::NONE Position::NONE,
))); )));
} }
// Use take to get ownership of the event // Use take to get ownership of the event
let owned_event = mem::take(event); let owned_event = mem::take(event);
*event = owned_event.reschedule(new_start_time, new_end_time); *event = owned_event.reschedule(new_start_time, new_end_time);
Ok(event.clone()) Ok(event.clone())
} }
/// Updates an attendee's status in the event /// Updates an attendee's status in the event
#[rhai_fn(name = "update_attendee_status", return_raw, global, pure)] #[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<RhaiEvent, Box<EvalAltResult>> { pub fn event_update_attendee_status(
event: &mut RhaiEvent,
contact_id: i64,
status_str: String,
) -> Result<RhaiEvent, Box<EvalAltResult>> {
let status_enum = AttendanceStatus::from_string(&status_str) let status_enum = AttendanceStatus::from_string(&status_str)
.map_err(|_| Box::new(EvalAltResult::ErrorRuntime( .map_err(|_| Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid attendance status: '{}'. Expected one of: Pending, Accepted, Declined, Tentative", status_str).into(), format!("Invalid attendance status: '{}'. Expected one of: Pending, Accepted, Declined, Tentative", status_str).into(),
Position::NONE Position::NONE
)))?; )))?;
// Use take to get ownership of the event // Use take to get ownership of the event
let owned_event = mem::take(event); let owned_event = mem::take(event);
*event = owned_event.update_attendee_status(id_from_i64_to_u32(contact_id)?, status_enum); *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 // Event Getters
#[rhai_fn(get = "id", pure)] #[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)] #[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)] #[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)] #[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)] #[rhai_fn(get = "description", pure)]
pub fn get_event_description(event: &mut RhaiEvent) -> Option<String> { event.description.clone() } pub fn get_event_description(event: &mut RhaiEvent) -> Option<String> {
event.description.clone()
}
#[rhai_fn(get = "start_time", pure)] #[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)] #[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)] #[rhai_fn(get = "location", pure)]
pub fn get_event_location(event: &mut RhaiEvent) -> Option<String> { event.location.clone() } pub fn get_event_location(event: &mut RhaiEvent) -> Option<String> {
event.location.clone()
}
#[rhai_fn(get = "attendees", pure)] #[rhai_fn(get = "attendees", pure)]
pub fn get_event_attendees(event: &mut RhaiEvent) -> Vec<RhaiAttendee> { event.attendees.clone() } pub fn get_event_attendees(event: &mut RhaiEvent) -> Vec<RhaiAttendee> {
event.attendees.clone()
}
// --- Attendee Functions --- // --- Attendee Functions ---
#[rhai_fn(name = "new_attendee")] #[rhai_fn(name = "new_attendee")]
pub fn new_attendee() -> RhaiAttendee { pub fn new_attendee() -> RhaiAttendee {
Attendee::new(0) // Default contact_id, will be set via builder Attendee::new(0) // Default contact_id, will be set via builder
} }
/// Sets the contact ID for an attendee /// Sets the contact ID for an attendee
#[rhai_fn(name = "with_contact_id", return_raw, global, pure)] #[rhai_fn(name = "with_contact_id", return_raw, global, pure)]
pub fn attendee_with_contact_id(attendee: &mut RhaiAttendee, contact_id: i64) -> Result<RhaiAttendee, Box<EvalAltResult>> { pub fn attendee_with_contact_id(
attendee: &mut RhaiAttendee,
contact_id: i64,
) -> Result<RhaiAttendee, Box<EvalAltResult>> {
let new_contact_id = id_from_i64_to_u32(contact_id).unwrap_or(0); let new_contact_id = id_from_i64_to_u32(contact_id).unwrap_or(0);
let owned_attendee = mem::replace(attendee, Attendee::new(0)); let owned_attendee = mem::replace(attendee, Attendee::new(0));
*attendee = Attendee::new(new_contact_id); *attendee = Attendee::new(new_contact_id);
attendee.status = owned_attendee.status; attendee.status = owned_attendee.status;
Ok(attendee.clone()) Ok(attendee.clone())
} }
/// Sets the status for an attendee /// Sets the status for an attendee
#[rhai_fn(name = "with_status", return_raw, global, pure)] #[rhai_fn(name = "with_status", return_raw, global, pure)]
pub fn attendee_with_status(attendee: &mut RhaiAttendee, status_str: String) -> Result<RhaiAttendee, Box<EvalAltResult>> { pub fn attendee_with_status(
attendee: &mut RhaiAttendee,
status_str: String,
) -> Result<RhaiAttendee, Box<EvalAltResult>> {
let status_enum = AttendanceStatus::from_string(&status_str) let status_enum = AttendanceStatus::from_string(&status_str)
.map_err(|_| Box::new(EvalAltResult::ErrorRuntime( .map_err(|_| Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid attendance status: '{}'. Expected one of: Accepted, Declined, Tentative, NoResponse", status_str).into(), format!("Invalid attendance status: '{}'. Expected one of: Accepted, Declined, Tentative, NoResponse", status_str).into(),
Position::NONE Position::NONE
)))?; )))?;
let owned_attendee = mem::replace(attendee, Attendee::new(0)); let owned_attendee = mem::replace(attendee, Attendee::new(0));
*attendee = owned_attendee.status(status_enum); *attendee = owned_attendee.status(status_enum);
Ok(attendee.clone()) Ok(attendee.clone())
@ -149,9 +193,13 @@ mod rhai_calendar_module {
// Attendee Getters // Attendee Getters
#[rhai_fn(get = "contact_id", pure)] #[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)] #[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 --- // --- Calendar Functions ---
#[rhai_fn(name = "new_calendar", return_raw)] #[rhai_fn(name = "new_calendar", return_raw)]
@ -159,25 +207,31 @@ mod rhai_calendar_module {
let calendar = Calendar::new(None, ""); let calendar = Calendar::new(None, "");
Ok(calendar) Ok(calendar)
} }
/// Sets the calendar name /// Sets the calendar name
#[rhai_fn(name = "name", return_raw, global, pure)] #[rhai_fn(name = "name", return_raw, global, pure)]
pub fn calendar_name(calendar: &mut RhaiCalendar, name: String) -> Result<RhaiCalendar, Box<EvalAltResult>> { pub fn calendar_name(
calendar: &mut RhaiCalendar,
name: String,
) -> Result<RhaiCalendar, Box<EvalAltResult>> {
// Create a default Calendar to replace the taken one // Create a default Calendar to replace the taken one
let default_calendar = Calendar::new(None, ""); let default_calendar = Calendar::new(None, "");
// Take ownership of the calendar, apply the builder method, then put it back // Take ownership of the calendar, apply the builder method, then put it back
let owned_calendar = std::mem::replace(calendar, default_calendar); let owned_calendar = std::mem::replace(calendar, default_calendar);
*calendar = Calendar::new(Some(owned_calendar.base_data.id), name); *calendar = Calendar::new(Some(owned_calendar.base_data.id), name);
Ok(calendar.clone()) Ok(calendar.clone())
} }
/// Sets the calendar description /// Sets the calendar description
#[rhai_fn(name = "description", return_raw, global, pure)] #[rhai_fn(name = "description", return_raw, global, pure)]
pub fn calendar_description(calendar: &mut RhaiCalendar, description: String) -> Result<RhaiCalendar, Box<EvalAltResult>> { pub fn calendar_description(
calendar: &mut RhaiCalendar,
description: String,
) -> Result<RhaiCalendar, Box<EvalAltResult>> {
// Create a default Calendar to replace the taken one // Create a default Calendar to replace the taken one
let default_calendar = Calendar::new(None, ""); let default_calendar = Calendar::new(None, "");
// Take ownership of the calendar, apply the builder method, then put it back // Take ownership of the calendar, apply the builder method, then put it back
let owned_calendar = std::mem::replace(calendar, default_calendar); let owned_calendar = std::mem::replace(calendar, default_calendar);
let updated_calendar = owned_calendar.description(description); 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)] #[rhai_fn(name = "add_event_to_calendar", return_raw, global, pure)]
pub fn calendar_add_event(calendar: &mut RhaiCalendar, event: RhaiEvent) -> Result<RhaiCalendar, Box<EvalAltResult>> { pub fn calendar_add_event(
calendar: &mut RhaiCalendar,
event: RhaiEvent,
) -> Result<RhaiCalendar, Box<EvalAltResult>> {
// Create a default Calendar to replace the taken one // Create a default Calendar to replace the taken one
let default_calendar = Calendar::new(None, ""); let default_calendar = Calendar::new(None, "");
// Take ownership of the calendar, apply the builder method, then put it back // Take ownership of the calendar, apply the builder method, then put it back
let owned_calendar = std::mem::replace(calendar, default_calendar); let owned_calendar = std::mem::replace(calendar, default_calendar);
*calendar = owned_calendar.add_event(event.base_data.id as i64); *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)] #[rhai_fn(name = "remove_event_from_calendar", return_raw)]
pub fn calendar_remove_event(calendar: &mut RhaiCalendar, event_id: i64) -> Result<(), Box<EvalAltResult>> { pub fn calendar_remove_event(
calendar: &mut RhaiCalendar,
event_id: i64,
) -> Result<(), Box<EvalAltResult>> {
// Create a default Calendar to replace the taken one // Create a default Calendar to replace the taken one
let default_calendar = Calendar::new(None, ""); let default_calendar = Calendar::new(None, "");
// Take ownership of the calendar, apply the builder method, then put it back // Take ownership of the calendar, apply the builder method, then put it back
let owned_calendar = std::mem::replace(calendar, default_calendar); let owned_calendar = std::mem::replace(calendar, default_calendar);
*calendar = owned_calendar.remove_event(id_from_i64_to_u32(event_id)? as i64); *calendar = owned_calendar.remove_event(id_from_i64_to_u32(event_id)? as i64);
@ -209,140 +269,213 @@ mod rhai_calendar_module {
// Calendar Getters // Calendar Getters
#[rhai_fn(get = "id", pure)] #[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)] #[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)] #[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)] #[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)] #[rhai_fn(get = "events", pure)]
pub fn get_calendar_events(calendar: &mut RhaiCalendar) -> Vec<i64> { calendar.events.clone() } pub fn get_calendar_events(calendar: &mut RhaiCalendar) -> Vec<i64> {
calendar.events.clone()
}
#[rhai_fn(get = "description", pure)] #[rhai_fn(get = "description", pure)]
pub fn get_calendar_description(calendar: &mut RhaiCalendar) -> Option<String> { calendar.description.clone() } pub fn get_calendar_description(calendar: &mut RhaiCalendar) -> Option<String> {
calendar.description.clone()
}
// Calendar doesn't have an owner_id field in the current implementation // 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 get_calendar_owner_id(calendar: &mut RhaiCalendar) -> i64 { calendar.owner_id as i64 }
} }
pub fn register_calendar_rhai_module(engine: &mut Engine, db: Arc<OurDB>) { pub fn register_calendar_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// Register the exported module globally // Register the exported module globally
let module = exported_module!(rhai_calendar_module); let module = exported_module!(rhai_calendar_module);
engine.register_global_module(module.into()); engine.register_global_module(module.into());
// Create a module for database functions // Create a module for database functions
let mut db_module = Module::new(); let mut db_module = Module::new();
// Manually register database functions as they need to capture 'db' // Manually register database functions as they need to capture 'db'
let db_clone_set_event = db.clone(); let db_clone_set_event = db.clone();
db_module.set_native_fn("save_event", move |event: Event| -> Result<Event, Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "save_event",
let result = db_clone_set_event.set(&event) move |event: Event| -> Result<Event, Box<EvalAltResult>> {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_event: {}", e).into(), Position::NONE)))?; // Use the Collection trait method directly
let result = db_clone_set_event.set(&event).map_err(|e| {
// Return the updated event with the correct ID Box::new(EvalAltResult::ErrorRuntime(
Ok(result.1) 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' // Manually register database functions as they need to capture 'db'
let db_clone_delete_event = db.clone(); let db_clone_delete_event = db.clone();
db_module.set_native_fn("delete_event", move |event: Event| -> Result<(), Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "delete_event",
let result = db_clone_delete_event.collection::<Event>() move |event: Event| -> Result<(), Box<EvalAltResult>> {
.expect("can open event collection") // Use the Collection trait method directly
.delete_by_id(event.base_data.id) let result = db_clone_delete_event
.expect("can delete event"); .collection::<Event>()
.expect("can open event collection")
// Return the updated event with the correct ID .delete_by_id(event.base_data.id)
Ok(result) .expect("can delete event");
});
// Return the updated event with the correct ID
Ok(result)
},
);
let db_clone_get_event = db.clone(); let db_clone_get_event = db.clone();
db_module.set_native_fn("get_event_by_id", move |id_i64: INT| -> Result<Event, Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_i64)?; "get_event_by_id",
// Use the Collection trait method directly move |id_i64: INT| -> Result<Event, Box<EvalAltResult>> {
db_clone_get_event.get_by_id(id_u32) let id_u32 = id_from_i64_to_u32(id_i64)?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_event_by_id: {}", e).into(), Position::NONE)))? // Use the Collection trait method directly
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Event with ID {} not found", id_u32).into(), Position::NONE))) 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(); let db_clone_set_calendar = db.clone();
db_module.set_native_fn("save_calendar", move |calendar: Calendar| -> Result<Calendar, Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "save_calendar",
let result = db_clone_set_calendar.set(&calendar) move |calendar: Calendar| -> Result<Calendar, Box<EvalAltResult>> {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_calendar: {}", e).into(), Position::NONE)))?; // Use the Collection trait method directly
let result = db_clone_set_calendar.set(&calendar).map_err(|e| {
// Return the updated calendar with the correct ID Box::new(EvalAltResult::ErrorRuntime(
Ok(result.1) 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' // Manually register database functions as they need to capture 'db'
let db_clone_delete_calendar = db.clone(); let db_clone_delete_calendar = db.clone();
db_module.set_native_fn("delete_calendar", move |calendar: Calendar| -> Result<(), Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "delete_calendar",
let result = db_clone_delete_calendar.collection::<Calendar>() move |calendar: Calendar| -> Result<(), Box<EvalAltResult>> {
.expect("can open calendar collection") // Use the Collection trait method directly
.delete_by_id(calendar.base_data.id) let result = db_clone_delete_calendar
.expect("can delete event"); .collection::<Calendar>()
.expect("can open calendar collection")
// Return the updated event with the correct ID .delete_by_id(calendar.base_data.id)
Ok(result) .expect("can delete event");
});
// Return the updated event with the correct ID
Ok(result)
},
);
let db_clone_get_calendar = db.clone(); let db_clone_get_calendar = db.clone();
db_module.set_native_fn("get_calendar_by_id", move |id_i64: INT| -> Result<Calendar, Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_i64)?; "get_calendar_by_id",
// Use the Collection trait method directly move |id_i64: INT| -> Result<Calendar, Box<EvalAltResult>> {
db_clone_get_calendar.get_by_id(id_u32) let id_u32 = id_from_i64_to_u32(id_i64)?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_calendar_by_id: {}", e).into(), Position::NONE)))? // Use the Collection trait method directly
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Calendar with ID {} not found", id_u32).into(), Position::NONE))) 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 // Add list_calendars function to get all calendars
let db_clone_list_calendars = db.clone(); let db_clone_list_calendars = db.clone();
db_module.set_native_fn("list_calendars", move || -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let collection = db_clone_list_calendars.collection::<Calendar>() "list_calendars",
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( move || -> Result<Dynamic, Box<EvalAltResult>> {
format!("Failed to get calendar collection: {:?}", e).into(), let collection = db_clone_list_calendars
Position::NONE .collection::<Calendar>()
)))?; .map_err(|e| {
let calendars = collection.get_all() Box::new(EvalAltResult::ErrorRuntime(
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( format!("Failed to get calendar collection: {:?}", e).into(),
format!("Failed to get all calendars: {:?}", e).into(), Position::NONE,
Position::NONE ))
)))?; })?;
let mut array = Array::new(); let calendars = collection.get_all().map_err(|e| {
for calendar in calendars { Box::new(EvalAltResult::ErrorRuntime(
array.push(Dynamic::from(calendar)); format!("Failed to get all calendars: {:?}", e).into(),
} Position::NONE,
Ok(Dynamic::from(array)) ))
}); })?;
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 // Add list_events function to get all events
let db_clone_list_events = db.clone(); let db_clone_list_events = db.clone();
db_module.set_native_fn("list_events", move || -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let collection = db_clone_list_events.collection::<Event>() "list_events",
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( move || -> Result<Dynamic, Box<EvalAltResult>> {
format!("Failed to get event collection: {:?}", e).into(), let collection = db_clone_list_events.collection::<Event>().map_err(|e| {
Position::NONE Box::new(EvalAltResult::ErrorRuntime(
)))?; format!("Failed to get event collection: {:?}", e).into(),
let events = collection.get_all() Position::NONE,
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( ))
format!("Failed to get all events: {:?}", e).into(), })?;
Position::NONE let events = collection.get_all().map_err(|e| {
)))?; Box::new(EvalAltResult::ErrorRuntime(
let mut array = Array::new(); format!("Failed to get all events: {:?}", e).into(),
for event in events { Position::NONE,
array.push(Dynamic::from(event)); ))
} })?;
Ok(Dynamic::from(array)) let mut array = Array::new();
}); for event in events {
array.push(Dynamic::from(event));
}
Ok(Dynamic::from(array))
},
);
// Register the database module globally // Register the database module globally
engine.register_global_module(db_module.into()); engine.register_global_module(db_module.into());

View File

@ -19,6 +19,8 @@ pub struct Circle {
pub description: Option<String>, pub description: Option<String>,
/// List of related circles /// List of related circles
pub circles: Vec<String>, pub circles: Vec<String>,
/// List of members in the circle (their public keys)
pub members: Vec<String>,
/// Logo URL or symbol for the circle /// Logo URL or symbol for the circle
pub logo: Option<String>, pub logo: Option<String>,
/// Theme settings for the circle (colors, styling, etc.) /// Theme settings for the circle (colors, styling, etc.)
@ -35,6 +37,7 @@ impl Circle {
description: None, description: None,
circles: Vec::new(), circles: Vec::new(),
logo: None, logo: None,
members: Vec::new(),
theme: HashMap::new(), theme: HashMap::new(),
} }
} }
@ -83,4 +86,13 @@ impl Circle {
} }
self self
} }
}
/// 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
}
}

View File

@ -3,5 +3,5 @@ pub mod circle;
pub mod rhai; pub mod rhai;
// Re-export Calendar, Event, Attendee, and AttendanceStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs // 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; pub use rhai::register_circle_rhai_module;

View File

@ -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 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; type RhaiCircle = Circle;
use crate::db::hero::OurDB;
use crate::db::Collection; use crate::db::Collection;
use crate::db::hero::OurDB;
use serde::Serialize; use serde::Serialize;
use std::collections::HashMap;
use serde_json; use serde_json;
use std::collections::HashMap;
/// Registers a `.json()` method for any type `T` that implements the required traits. /// Registers a `.json()` method for any type `T` that implements the required traits.
fn register_json_method<T>(engine: &mut Engine) fn register_json_method<T>(engine: &mut Engine)
@ -31,12 +31,12 @@ where
// Helper to convert i64 from Rhai to u32 for IDs // Helper to convert i64 from Rhai to u32 for IDs
fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> { fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_| u32::try_from(id_i64).map_err(|_| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert ID '{}' to u32", id_i64).into(), format!("Failed to convert ID '{}' to u32", id_i64).into(),
Position::NONE Position::NONE,
)) ))
) })
} }
#[export_module] #[export_module]
@ -46,10 +46,13 @@ mod rhai_circle_module {
pub fn new_circle() -> RhaiCircle { pub fn new_circle() -> RhaiCircle {
Circle::new() Circle::new()
} }
/// Sets the circle title /// Sets the circle title
#[rhai_fn(name = "title", return_raw, global, pure)] #[rhai_fn(name = "title", return_raw, global, pure)]
pub fn circle_title(circle: &mut RhaiCircle, title: String) -> Result<RhaiCircle, Box<EvalAltResult>> { pub fn circle_title(
circle: &mut RhaiCircle,
title: String,
) -> Result<RhaiCircle, Box<EvalAltResult>> {
let owned_circle = mem::take(circle); let owned_circle = mem::take(circle);
*circle = owned_circle.title(title); *circle = owned_circle.title(title);
Ok(circle.clone()) Ok(circle.clone())
@ -57,15 +60,21 @@ mod rhai_circle_module {
/// Sets the circle ws_url /// Sets the circle ws_url
#[rhai_fn(name = "ws_url", return_raw, global, pure)] #[rhai_fn(name = "ws_url", return_raw, global, pure)]
pub fn circle_ws_url(circle: &mut RhaiCircle, ws_url: String) -> Result<RhaiCircle, Box<EvalAltResult>> { pub fn circle_ws_url(
circle: &mut RhaiCircle,
ws_url: String,
) -> Result<RhaiCircle, Box<EvalAltResult>> {
let owned_circle = mem::take(circle); let owned_circle = mem::take(circle);
*circle = owned_circle.ws_url(ws_url); *circle = owned_circle.ws_url(ws_url);
Ok(circle.clone()) Ok(circle.clone())
} }
/// Sets the circle description /// Sets the circle description
#[rhai_fn(name = "description", return_raw, global, pure)] #[rhai_fn(name = "description", return_raw, global, pure)]
pub fn circle_description(circle: &mut RhaiCircle, description: String) -> Result<RhaiCircle, Box<EvalAltResult>> { pub fn circle_description(
circle: &mut RhaiCircle,
description: String,
) -> Result<RhaiCircle, Box<EvalAltResult>> {
let owned_circle = mem::take(circle); let owned_circle = mem::take(circle);
*circle = owned_circle.description(description); *circle = owned_circle.description(description);
Ok(circle.clone()) Ok(circle.clone())
@ -73,7 +82,10 @@ mod rhai_circle_module {
/// Sets the circle logo /// Sets the circle logo
#[rhai_fn(name = "logo", return_raw, global, pure)] #[rhai_fn(name = "logo", return_raw, global, pure)]
pub fn circle_logo(circle: &mut RhaiCircle, logo: String) -> Result<RhaiCircle, Box<EvalAltResult>> { pub fn circle_logo(
circle: &mut RhaiCircle,
logo: String,
) -> Result<RhaiCircle, Box<EvalAltResult>> {
let owned_circle = mem::take(circle); let owned_circle = mem::take(circle);
*circle = owned_circle.logo(logo); *circle = owned_circle.logo(logo);
Ok(circle.clone()) Ok(circle.clone())
@ -81,118 +93,194 @@ mod rhai_circle_module {
/// Sets the circle theme /// Sets the circle theme
#[rhai_fn(name = "theme", return_raw, global, pure)] #[rhai_fn(name = "theme", return_raw, global, pure)]
pub fn circle_theme(circle: &mut RhaiCircle, theme: HashMap<String, String>) -> Result<RhaiCircle, Box<EvalAltResult>> { pub fn circle_theme(
circle: &mut RhaiCircle,
theme: HashMap<String, String>,
) -> Result<RhaiCircle, Box<EvalAltResult>> {
let owned_circle = mem::take(circle); let owned_circle = mem::take(circle);
*circle = owned_circle.theme(theme); *circle = owned_circle.theme(theme);
Ok(circle.clone()) Ok(circle.clone())
} }
/// Adds an attendee to the circle /// Adds an attendee to the circle
#[rhai_fn(name = "add_circle", return_raw, global, pure)] #[rhai_fn(name = "add_circle", return_raw, global, pure)]
pub fn circle_add_circle(circle: &mut RhaiCircle, added_circle: String) -> Result<RhaiCircle, Box<EvalAltResult>> { pub fn circle_add_circle(
circle: &mut RhaiCircle,
added_circle: String,
) -> Result<RhaiCircle, Box<EvalAltResult>> {
// Use take to get ownership of the circle // Use take to get ownership of the circle
let owned_circle = mem::take(circle); let owned_circle = mem::take(circle);
*circle = owned_circle.add_circle(added_circle); *circle = owned_circle.add_circle(added_circle);
Ok(circle.clone()) 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<RhaiCircle, Box<EvalAltResult>> {
// 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 // Circle Getters
#[rhai_fn(get = "id", pure)] #[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)] #[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)] #[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)] #[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)] #[rhai_fn(get = "description", pure)]
pub fn get_circle_description(circle: &mut RhaiCircle) -> Option<String> { circle.description.clone() } pub fn get_circle_description(circle: &mut RhaiCircle) -> Option<String> {
circle.description.clone()
}
#[rhai_fn(get = "circles", pure)] #[rhai_fn(get = "circles", pure)]
pub fn get_circle_circles(circle: &mut RhaiCircle) -> Vec<String> { circle.circles.clone() } pub fn get_circle_circles(circle: &mut RhaiCircle) -> Vec<String> {
circle.circles.clone()
}
#[rhai_fn(get = "ws_url", pure)] #[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)] #[rhai_fn(get = "logo", pure)]
pub fn get_circle_logo(circle: &mut RhaiCircle) -> Option<String> { circle.logo.clone() } pub fn get_circle_logo(circle: &mut RhaiCircle) -> Option<String> {
circle.logo.clone()
}
#[rhai_fn(get = "theme", pure)] #[rhai_fn(get = "theme", pure)]
pub fn get_circle_theme(circle: &mut RhaiCircle) -> HashMap<String, String> { circle.theme.clone() } pub fn get_circle_theme(circle: &mut RhaiCircle) -> HashMap<String, String> {
circle.theme.clone()
}
} }
pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc<OurDB>) { pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// Register the exported module globally // Register the exported module globally
let module = exported_module!(rhai_circle_module); let module = exported_module!(rhai_circle_module);
engine.register_global_module(module.into()); engine.register_global_module(module.into());
// Create a module for database functions // Create a module for database functions
let mut db_module = Module::new(); let mut db_module = Module::new();
// Manually register database functions as they need to capture 'db' // Manually register database functions as they need to capture 'db'
let db_clone_set_circle = db.clone(); let db_clone_set_circle = db.clone();
db_module.set_native_fn("save_circle", move |circle: Circle| -> Result<Circle, Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "save_circle",
let result = db_clone_set_circle.set(&circle) move |circle: Circle| -> Result<Circle, Box<EvalAltResult>> {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_circle: {}", e).into(), Position::NONE)))?; // Use the Collection trait method directly
let result = db_clone_set_circle.set(&circle).map_err(|e| {
// Return the updated circle with the correct ID Box::new(EvalAltResult::ErrorRuntime(
Ok(result.1) format!("DB Error set_circle: {}", e).into(),
}); Position::NONE,
))
})?;
// Return the updated circle with the correct ID
Ok(result.1)
},
);
register_json_method::<Circle>(engine); register_json_method::<Circle>(engine);
// Manually register database functions as they need to capture 'db' // Manually register database functions as they need to capture 'db'
let db_clone_delete_circle = db.clone(); let db_clone_delete_circle = db.clone();
db_module.set_native_fn("delete_circle", move |circle: Circle| -> Result<(), Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "delete_circle",
let result = db_clone_delete_circle.collection::<Circle>() move |circle: Circle| -> Result<(), Box<EvalAltResult>> {
.expect("can open circle collection") // Use the Collection trait method directly
.delete_by_id(circle.base_data.id) let result = db_clone_delete_circle
.expect("can delete circle"); .collection::<Circle>()
.expect("can open circle collection")
// Return the updated circle with the correct ID .delete_by_id(circle.base_data.id)
Ok(result) .expect("can delete circle");
});
let db_clone_get_circle = db.clone();
db_module.set_native_fn("get_circle", move || -> Result<Circle, Box<EvalAltResult>> {
// Use the Collection trait method directly
let all_circles: Vec<Circle> = 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() { // Return the updated circle with the correct ID
Ok(first_circle.clone()) Ok(result)
} else { },
Err(Box::new(EvalAltResult::ErrorRuntime("Circle not found".into(), Position::NONE))) );
}
}); let db_clone_get_circle = db.clone();
db_module.set_native_fn(
"get_circle",
move || -> Result<Circle, Box<EvalAltResult>> {
// Use the Collection trait method directly
let all_circles: Vec<Circle> = 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(); let db_clone_get_circle_by_id = db.clone();
db_module.set_native_fn("get_circle_by_id", move |id_i64: INT| -> Result<Circle, Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_i64)?; "get_circle_by_id",
// Use the Collection trait method directly move |id_i64: INT| -> Result<Circle, Box<EvalAltResult>> {
db_clone_get_circle_by_id.get_by_id(id_u32) let id_u32 = id_from_i64_to_u32(id_i64)?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_circle_by_id: {}", e).into(), Position::NONE)))? // Use the Collection trait method directly
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Circle with ID {} not found", id_u32).into(), Position::NONE))) 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 // Add list_circles function to get all circles
let db_clone_list_circles = db.clone(); let db_clone_list_circles = db.clone();
db_module.set_native_fn("list_circles", move || -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let collection = db_clone_list_circles.collection::<Circle>() "list_circles",
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( move || -> Result<Dynamic, Box<EvalAltResult>> {
format!("Failed to get circle collection: {:?}", e).into(), let collection = db_clone_list_circles.collection::<Circle>().map_err(|e| {
Position::NONE Box::new(EvalAltResult::ErrorRuntime(
)))?; format!("Failed to get circle collection: {:?}", e).into(),
let circles = collection.get_all() Position::NONE,
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( ))
format!("Failed to get all circles: {:?}", e).into(), })?;
Position::NONE let circles = collection.get_all().map_err(|e| {
)))?; Box::new(EvalAltResult::ErrorRuntime(
let mut array = Array::new(); format!("Failed to get all circles: {:?}", e).into(),
for circle in circles { Position::NONE,
array.push(Dynamic::from(circle)); ))
} })?;
Ok(Dynamic::from(array)) let mut array = Array::new();
}); for circle in circles {
array.push(Dynamic::from(circle));
}
Ok(Dynamic::from(array))
},
);
// Register the database module globally // Register the database module globally
engine.register_global_module(db_module.into()); engine.register_global_module(db_module.into());

View File

@ -112,4 +112,4 @@ impl Group {
self.contacts.push(contact); self.contacts.push(contact);
self self
} }
} }

View File

@ -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 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 RhaiGroup = Group;
type RhaiContact = Contact; type RhaiContact = Contact;
use crate::db::hero::OurDB;
use crate::db::Collection; use crate::db::Collection;
use crate::db::hero::OurDB;
// Helper to convert i64 from Rhai to u32 for IDs // Helper to convert i64 from Rhai to u32 for IDs
fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> { fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_| u32::try_from(id_i64).map_err(|_| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert ID '{}' to u32", id_i64).into(), format!("Failed to convert ID '{}' to u32", id_i64).into(),
Position::NONE Position::NONE,
)) ))
) })
} }
#[export_module] #[export_module]
@ -27,18 +27,24 @@ mod rhai_contact_module {
pub fn new_group() -> RhaiGroup { pub fn new_group() -> RhaiGroup {
Group::new() Group::new()
} }
/// Sets the event title /// Sets the event title
#[rhai_fn(name = "name", return_raw, global, pure)] #[rhai_fn(name = "name", return_raw, global, pure)]
pub fn group_name(group: &mut RhaiGroup, name: String) -> Result<RhaiGroup, Box<EvalAltResult>> { pub fn group_name(
group: &mut RhaiGroup,
name: String,
) -> Result<RhaiGroup, Box<EvalAltResult>> {
let owned_group = mem::take(group); let owned_group = mem::take(group);
*group = owned_group.name(name); *group = owned_group.name(name);
Ok(group.clone()) Ok(group.clone())
} }
/// Sets the event description /// Sets the event description
#[rhai_fn(name = "description", return_raw, global, pure)] #[rhai_fn(name = "description", return_raw, global, pure)]
pub fn group_description(group: &mut RhaiGroup, description: String) -> Result<RhaiGroup, Box<EvalAltResult>> { pub fn group_description(
group: &mut RhaiGroup,
description: String,
) -> Result<RhaiGroup, Box<EvalAltResult>> {
let owned_group = mem::take(group); let owned_group = mem::take(group);
*group = owned_group.description(description); *group = owned_group.description(description);
Ok(group.clone()) Ok(group.clone())
@ -46,7 +52,10 @@ mod rhai_contact_module {
/// Adds an attendee to the event /// Adds an attendee to the event
#[rhai_fn(name = "add_contact", return_raw, global, pure)] #[rhai_fn(name = "add_contact", return_raw, global, pure)]
pub fn group_add_contact(group: &mut RhaiGroup, contact_id: i64) -> Result<RhaiGroup, Box<EvalAltResult>> { pub fn group_add_contact(
group: &mut RhaiGroup,
contact_id: i64,
) -> Result<RhaiGroup, Box<EvalAltResult>> {
// Use take to get ownership of the event // Use take to get ownership of the event
let owned_group = mem::take(group); let owned_group = mem::take(group);
*group = owned_group.add_contact(contact_id as u32); *group = owned_group.add_contact(contact_id as u32);
@ -54,21 +63,37 @@ mod rhai_contact_module {
} }
#[rhai_fn(get = "contacts", pure)] #[rhai_fn(get = "contacts", pure)]
pub fn get_group_contacts(group: &mut RhaiGroup) -> Vec<i64> { group.contacts.clone().into_iter().map(|id| id as i64).collect() } pub fn get_group_contacts(group: &mut RhaiGroup) -> Vec<i64> {
group
.contacts
.clone()
.into_iter()
.map(|id| id as i64)
.collect()
}
// Group Getters // Group Getters
#[rhai_fn(get = "id", pure)] #[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)] #[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)] #[rhai_fn(get = "modified_at", pure)]
pub fn get_group_modified_at(group: &mut RhaiGroup) -> i64 { group.base_data.modified_at } 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<String> { group.description.clone() }
#[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<String> {
group.description.clone()
}
// --- Contact Functions --- // --- Contact Functions ---
#[rhai_fn(name = "new_contact", return_raw)] #[rhai_fn(name = "new_contact", return_raw)]
@ -76,25 +101,31 @@ mod rhai_contact_module {
let contact = Contact::new(); let contact = Contact::new();
Ok(contact) Ok(contact)
} }
/// Sets the contact name /// Sets the contact name
#[rhai_fn(name = "name", return_raw, global, pure)] #[rhai_fn(name = "name", return_raw, global, pure)]
pub fn contact_name(contact: &mut RhaiContact, name: String) -> Result<RhaiContact, Box<EvalAltResult>> { pub fn contact_name(
contact: &mut RhaiContact,
name: String,
) -> Result<RhaiContact, Box<EvalAltResult>> {
// Create a default Contact to replace the taken one // Create a default Contact to replace the taken one
let default_contact = Contact::new(); let default_contact = Contact::new();
// Take ownership of the contact, apply the builder method, then put it back // Take ownership of the contact, apply the builder method, then put it back
let owned_contact = std::mem::replace(contact, default_contact); let owned_contact = std::mem::replace(contact, default_contact);
*contact = owned_contact.name(name); *contact = owned_contact.name(name);
Ok(contact.clone()) Ok(contact.clone())
} }
/// Sets the contact description /// Sets the contact description
#[rhai_fn(name = "description", return_raw, global, pure)] #[rhai_fn(name = "description", return_raw, global, pure)]
pub fn contact_description(contact: &mut RhaiContact, description: String) -> Result<RhaiContact, Box<EvalAltResult>> { pub fn contact_description(
contact: &mut RhaiContact,
description: String,
) -> Result<RhaiContact, Box<EvalAltResult>> {
// Create a default Contact to replace the taken one // Create a default Contact to replace the taken one
let default_contact = Contact::new(); let default_contact = Contact::new();
// Take ownership of the contact, apply the builder method, then put it back // Take ownership of the contact, apply the builder method, then put it back
let owned_contact = std::mem::replace(contact, default_contact); let owned_contact = std::mem::replace(contact, default_contact);
*contact = owned_contact.description(description); *contact = owned_contact.description(description);
@ -103,130 +134,200 @@ mod rhai_contact_module {
// Contact Getters // Contact Getters
#[rhai_fn(get = "id", pure)] #[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)] #[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)] #[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)] #[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<OurDB>) { pub fn register_contact_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// Register the exported module globally // Register the exported module globally
let module = exported_module!(rhai_contact_module); let module = exported_module!(rhai_contact_module);
engine.register_global_module(module.into()); engine.register_global_module(module.into());
// Create a module for database functions // Create a module for database functions
let mut db_module = Module::new(); let mut db_module = Module::new();
// Manually register database functions as they need to capture 'db' // Manually register database functions as they need to capture 'db'
let db_clone_set_group = db.clone(); let db_clone_set_group = db.clone();
db_module.set_native_fn("save_group", move |group: Group| -> Result<Group, Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "save_group",
let result = db_clone_set_group.set(&group) move |group: Group| -> Result<Group, Box<EvalAltResult>> {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_group: {}", e).into(), Position::NONE)))?; // Use the Collection trait method directly
let result = db_clone_set_group.set(&group).map_err(|e| {
// Return the updated event with the correct ID Box::new(EvalAltResult::ErrorRuntime(
Ok(result.1) 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' // Manually register database functions as they need to capture 'db'
let db_clone_delete_group = db.clone(); let db_clone_delete_group = db.clone();
db_module.set_native_fn("delete_group", move |group: Group| -> Result<(), Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "delete_group",
let result = db_clone_delete_group.collection::<Group>() move |group: Group| -> Result<(), Box<EvalAltResult>> {
.expect("can open group collection") // Use the Collection trait method directly
.delete_by_id(group.base_data.id) let result = db_clone_delete_group
.expect("can delete group"); .collection::<Group>()
.expect("can open group collection")
// Return the updated event with the correct ID .delete_by_id(group.base_data.id)
Ok(result) .expect("can delete group");
});
// Return the updated event with the correct ID
Ok(result)
},
);
let db_clone_get_group = db.clone(); let db_clone_get_group = db.clone();
db_module.set_native_fn("get_group_by_id", move |id_i64: INT| -> Result<Group, Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_i64)?; "get_group_by_id",
// Use the Collection trait method directly move |id_i64: INT| -> Result<Group, Box<EvalAltResult>> {
db_clone_get_group.get_by_id(id_u32) let id_u32 = id_from_i64_to_u32(id_i64)?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_event_by_id: {}", e).into(), Position::NONE)))? // Use the Collection trait method directly
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Event with ID {} not found", id_u32).into(), Position::NONE))) 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(); let db_clone_set_contact = db.clone();
db_module.set_native_fn("save_contact", move |contact: Contact| -> Result<Contact, Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "save_contact",
let result = db_clone_set_contact.set(&contact) move |contact: Contact| -> Result<Contact, Box<EvalAltResult>> {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_contact: {}", e).into(), Position::NONE)))?; // Use the Collection trait method directly
let result = db_clone_set_contact.set(&contact).map_err(|e| {
// Return the updated contact with the correct ID Box::new(EvalAltResult::ErrorRuntime(
Ok(result.1) 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' // Manually register database functions as they need to capture 'db'
let db_clone_delete_contact = db.clone(); let db_clone_delete_contact = db.clone();
db_module.set_native_fn("delete_contact", move |contact: Contact| -> Result<(), Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "delete_contact",
let result = db_clone_delete_contact.collection::<Contact>() move |contact: Contact| -> Result<(), Box<EvalAltResult>> {
.expect("can open contact collection") // Use the Collection trait method directly
.delete_by_id(contact.base_data.id) let result = db_clone_delete_contact
.expect("can delete event"); .collection::<Contact>()
.expect("can open contact collection")
// Return the updated event with the correct ID .delete_by_id(contact.base_data.id)
Ok(result) .expect("can delete event");
});
// Return the updated event with the correct ID
Ok(result)
},
);
let db_clone_get_contact = db.clone(); let db_clone_get_contact = db.clone();
db_module.set_native_fn("get_contact_by_id", move |id_i64: INT| -> Result<Contact, Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_i64)?; "get_contact_by_id",
// Use the Collection trait method directly move |id_i64: INT| -> Result<Contact, Box<EvalAltResult>> {
db_clone_get_contact.get_by_id(id_u32) let id_u32 = id_from_i64_to_u32(id_i64)?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_contact_by_id: {}", e).into(), Position::NONE)))? // Use the Collection trait method directly
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Contact with ID {} not found", id_u32).into(), Position::NONE))) 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 // Add list_contacts function to get all contacts
let db_clone_list_contacts = db.clone(); let db_clone_list_contacts = db.clone();
db_module.set_native_fn("list_contacts", move || -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let collection = db_clone_list_contacts.collection::<Contact>() "list_contacts",
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( move || -> Result<Dynamic, Box<EvalAltResult>> {
format!("Failed to get contact collection: {:?}", e).into(), let collection = db_clone_list_contacts
Position::NONE .collection::<Contact>()
)))?; .map_err(|e| {
let contacts = collection.get_all() Box::new(EvalAltResult::ErrorRuntime(
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( format!("Failed to get contact collection: {:?}", e).into(),
format!("Failed to get all contacts: {:?}", e).into(), Position::NONE,
Position::NONE ))
)))?; })?;
let mut array = Array::new(); let contacts = collection.get_all().map_err(|e| {
for contact in contacts { Box::new(EvalAltResult::ErrorRuntime(
array.push(Dynamic::from(contact)); format!("Failed to get all contacts: {:?}", e).into(),
} Position::NONE,
Ok(Dynamic::from(array)) ))
}); })?;
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 // Add list_events function to get all events
let db_clone_list_groups = db.clone(); let db_clone_list_groups = db.clone();
db_module.set_native_fn("list_groups", move || -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let collection = db_clone_list_groups.collection::<Group>() "list_groups",
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( move || -> Result<Dynamic, Box<EvalAltResult>> {
format!("Failed to get group collection: {:?}", e).into(), let collection = db_clone_list_groups.collection::<Group>().map_err(|e| {
Position::NONE Box::new(EvalAltResult::ErrorRuntime(
)))?; format!("Failed to get group collection: {:?}", e).into(),
let groups = collection.get_all() Position::NONE,
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( ))
format!("Failed to get all groups: {:?}", e).into(), })?;
Position::NONE let groups = collection.get_all().map_err(|e| {
)))?; Box::new(EvalAltResult::ErrorRuntime(
let mut array = Array::new(); format!("Failed to get all groups: {:?}", e).into(),
for group in groups { Position::NONE,
array.push(Dynamic::from(group)); ))
} })?;
Ok(Dynamic::from(array)) let mut array = Array::new();
}); for group in groups {
array.push(Dynamic::from(group));
}
Ok(Dynamic::from(array))
},
);
// Register the database module globally // Register the database module globally
engine.register_global_module(db_module.into()); engine.register_global_module(db_module.into());

View File

@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
pub struct Comment { pub struct Comment {
pub base_data: BaseModelData, // Provides id, created_at, updated_at pub base_data: BaseModelData, // Provides id, created_at, updated_at
#[index] #[index]
pub user_id: u32, // Maps to commenter_id pub user_id: u32, // Maps to commenter_id
pub content: String, // Maps to text pub content: String, // Maps to text
pub parent_comment_id: Option<u32>, // For threaded comments pub parent_comment_id: Option<u32>, // For threaded comments
} }

View File

@ -4,4 +4,3 @@ pub mod model;
// Re-export key types for convenience // Re-export key types for convenience
pub use comment::Comment; pub use comment::Comment;

View File

@ -0,0 +1 @@

View File

@ -1,9 +1,9 @@
// heromodels/src/models/finance/account.rs // heromodels/src/models/finance/account.rs
use serde::{Deserialize, Serialize};
use rhai::{CustomType, TypeBuilder};
use heromodels_derive::model;
use heromodels_core::BaseModelData; use heromodels_core::BaseModelData;
use heromodels_derive::model;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
use super::asset::Asset; use super::asset::Asset;
@ -18,7 +18,7 @@ pub struct Account {
pub ledger: String, // describes the ledger/blockchain where the account is located pub ledger: String, // describes the ledger/blockchain where the account is located
pub address: String, // address of the account on the blockchain pub address: String, // address of the account on the blockchain
pub pubkey: String, // public key pub pubkey: String, // public key
pub assets: Vec<u32>, // list of assets in this account pub assets: Vec<u32>, // list of assets in this account
} }
impl Account { impl Account {
@ -87,6 +87,6 @@ impl Account {
/// Find an asset by name /// Find an asset by name
pub fn find_asset_by_name(&self, _name: &str) -> Option<&Asset> { pub fn find_asset_by_name(&self, _name: &str) -> Option<&Asset> {
// TODO: implement // TODO: implement
return None return None;
} }
} }

View File

@ -1,9 +1,9 @@
// heromodels/src/models/finance/asset.rs // heromodels/src/models/finance/asset.rs
use serde::{Deserialize, Serialize};
use rhai::{CustomType, TypeBuilder};
use heromodels_derive::model;
use heromodels_core::BaseModelData; use heromodels_core::BaseModelData;
use heromodels_derive::model;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
/// AssetType defines the type of blockchain asset /// AssetType defines the type of blockchain asset
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]

View File

@ -1,10 +1,10 @@
// heromodels/src/models/finance/marketplace.rs // heromodels/src/models/finance/marketplace.rs
use serde::{Deserialize, Serialize};
use rhai::{CustomType, TypeBuilder};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use heromodels_core::BaseModelData; use heromodels_core::BaseModelData;
use heromodels_derive::model; use heromodels_derive::model;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
use super::asset::AssetType; use super::asset::AssetType;
@ -53,8 +53,7 @@ impl Default for BidStatus {
} }
/// Bid represents a bid on an auction listing /// Bid represents a bid on an auction listing
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)] #[derive(Debug, Clone, Serialize, Deserialize, CustomType, Default)]
#[derive(Default)]
pub struct Bid { pub struct Bid {
pub listing_id: String, // ID of the listing this bid belongs to pub listing_id: String, // ID of the listing this bid belongs to
pub bidder_id: u32, // ID of the user who placed the bid pub bidder_id: u32, // ID of the user who placed the bid
@ -272,7 +271,7 @@ impl Listing {
} }
self.status = ListingStatus::Sold; self.status = ListingStatus::Sold;
if self.sold_at.is_none() { if self.sold_at.is_none() {
self.sold_at = Some(Utc::now()); self.sold_at = Some(Utc::now());
} }
@ -281,7 +280,7 @@ impl Listing {
if self.listing_type == ListingType::Auction { if self.listing_type == ListingType::Auction {
let buyer_id_str = self.buyer_id.as_ref().unwrap().to_string(); let buyer_id_str = self.buyer_id.as_ref().unwrap().to_string();
let sale_price_val = self.sale_price.unwrap(); let sale_price_val = self.sale_price.unwrap();
for bid in &mut self.bids { for bid in &mut self.bids {
if bid.bidder_id.to_string() == buyer_id_str && bid.amount == sale_price_val { if bid.bidder_id.to_string() == buyer_id_str && bid.amount == sale_price_val {
bid.status = BidStatus::Accepted; bid.status = BidStatus::Accepted;

View File

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

View File

@ -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 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::account::Account;
use super::asset::{Asset, AssetType}; 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::hero::OurDB;
use crate::db::{Collection, Db}; use crate::db::{Collection, Db};
@ -18,12 +18,12 @@ type RhaiBid = Bid;
// Helper to convert i64 from Rhai to u32 for IDs // Helper to convert i64 from Rhai to u32 for IDs
fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> { fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_| u32::try_from(id_i64).map_err(|_| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert ID '{}' to u32", id_i64).into(), format!("Failed to convert ID '{}' to u32", id_i64).into(),
Position::NONE Position::NONE,
)) ))
) })
} }
// Helper functions for enum conversions // Helper functions for enum conversions
@ -45,8 +45,12 @@ fn string_to_asset_type(s: &str) -> Result<AssetType, Box<EvalAltResult>> {
"Erc721" => Ok(AssetType::Erc721), "Erc721" => Ok(AssetType::Erc721),
"Erc1155" => Ok(AssetType::Erc1155), "Erc1155" => Ok(AssetType::Erc1155),
_ => Err(Box::new(EvalAltResult::ErrorRuntime( _ => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid asset type: '{}'. Expected one of: Native, Erc20, Erc721, Erc1155", s).into(), format!(
Position::NONE "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<ListingStatus, Box<EvalAltResult>
"Cancelled" => Ok(ListingStatus::Cancelled), "Cancelled" => Ok(ListingStatus::Cancelled),
"Expired" => Ok(ListingStatus::Expired), "Expired" => Ok(ListingStatus::Expired),
_ => Err(Box::new(EvalAltResult::ErrorRuntime( _ => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid listing status: '{}'. Expected one of: Active, Sold, Cancelled, Expired", s).into(), format!(
Position::NONE "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<ListingType, Box<EvalAltResult>> {
"Auction" => Ok(ListingType::Auction), "Auction" => Ok(ListingType::Auction),
"Exchange" => Ok(ListingType::Exchange), "Exchange" => Ok(ListingType::Exchange),
_ => Err(Box::new(EvalAltResult::ErrorRuntime( _ => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid listing type: '{}'. Expected one of: FixedPrice, Auction, Exchange", s).into(), format!(
Position::NONE "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<BidStatus, Box<EvalAltResult>> {
"Rejected" => Ok(BidStatus::Rejected), "Rejected" => Ok(BidStatus::Rejected),
"Cancelled" => Ok(BidStatus::Cancelled), "Cancelled" => Ok(BidStatus::Cancelled),
_ => Err(Box::new(EvalAltResult::ErrorRuntime( _ => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid bid status: '{}'. Expected one of: Active, Accepted, Rejected, Cancelled", s).into(), format!(
Position::NONE "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 // Setters using builder pattern with proper mutability handling
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn name(account: &mut RhaiAccount, name: String) -> Result<RhaiAccount, Box<EvalAltResult>> { pub fn name(
account: &mut RhaiAccount,
name: String,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let mut acc = mem::take(account); let mut acc = mem::take(account);
acc = acc.name(name); acc = acc.name(name);
*account = acc; *account = acc;
@ -189,7 +208,10 @@ mod account_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn user_id(account: &mut RhaiAccount, user_id: INT) -> Result<RhaiAccount, Box<EvalAltResult>> { pub fn user_id(
account: &mut RhaiAccount,
user_id: INT,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let user_id = id_from_i64_to_u32(user_id)?; let user_id = id_from_i64_to_u32(user_id)?;
let mut acc = mem::take(account); let mut acc = mem::take(account);
acc = acc.user_id(user_id); acc = acc.user_id(user_id);
@ -198,7 +220,10 @@ mod account_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn description(account: &mut RhaiAccount, description: String) -> Result<RhaiAccount, Box<EvalAltResult>> { pub fn description(
account: &mut RhaiAccount,
description: String,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let mut acc = mem::take(account); let mut acc = mem::take(account);
acc = acc.description(description); acc = acc.description(description);
*account = acc; *account = acc;
@ -206,7 +231,10 @@ mod account_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn ledger(account: &mut RhaiAccount, ledger: String) -> Result<RhaiAccount, Box<EvalAltResult>> { pub fn ledger(
account: &mut RhaiAccount,
ledger: String,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let mut acc = mem::take(account); let mut acc = mem::take(account);
acc = acc.ledger(ledger); acc = acc.ledger(ledger);
*account = acc; *account = acc;
@ -214,7 +242,10 @@ mod account_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn address(account: &mut RhaiAccount, address: String) -> Result<RhaiAccount, Box<EvalAltResult>> { pub fn address(
account: &mut RhaiAccount,
address: String,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let mut acc = mem::take(account); let mut acc = mem::take(account);
acc = acc.address(address); acc = acc.address(address);
*account = acc; *account = acc;
@ -222,7 +253,10 @@ mod account_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn pubkey(account: &mut RhaiAccount, pubkey: String) -> Result<RhaiAccount, Box<EvalAltResult>> { pub fn pubkey(
account: &mut RhaiAccount,
pubkey: String,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let mut acc = mem::take(account); let mut acc = mem::take(account);
acc = acc.pubkey(pubkey); acc = acc.pubkey(pubkey);
*account = acc; *account = acc;
@ -230,7 +264,10 @@ mod account_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn add_asset(account: &mut RhaiAccount, asset_id: INT) -> Result<RhaiAccount, Box<EvalAltResult>> { pub fn add_asset(
account: &mut RhaiAccount,
asset_id: INT,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let asset_id = id_from_i64_to_u32(asset_id)?; let asset_id = id_from_i64_to_u32(asset_id)?;
let mut acc = mem::take(account); let mut acc = mem::take(account);
acc = acc.add_asset(asset_id); acc = acc.add_asset(asset_id);
@ -301,7 +338,10 @@ mod asset_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn description(asset: &mut RhaiAsset, description: String) -> Result<RhaiAsset, Box<EvalAltResult>> { pub fn description(
asset: &mut RhaiAsset,
description: String,
) -> Result<RhaiAsset, Box<EvalAltResult>> {
let mut ast = mem::take(asset); let mut ast = mem::take(asset);
ast = ast.description(description); ast = ast.description(description);
*asset = ast; *asset = ast;
@ -317,7 +357,10 @@ mod asset_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn address(asset: &mut RhaiAsset, address: String) -> Result<RhaiAsset, Box<EvalAltResult>> { pub fn address(
asset: &mut RhaiAsset,
address: String,
) -> Result<RhaiAsset, Box<EvalAltResult>> {
let mut ast = mem::take(asset); let mut ast = mem::take(asset);
ast = ast.address(address); ast = ast.address(address);
*asset = ast; *asset = ast;
@ -325,7 +368,10 @@ mod asset_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn asset_type(asset: &mut RhaiAsset, asset_type_str: String) -> Result<RhaiAsset, Box<EvalAltResult>> { pub fn asset_type(
asset: &mut RhaiAsset,
asset_type_str: String,
) -> Result<RhaiAsset, Box<EvalAltResult>> {
let asset_type_enum = string_to_asset_type(&asset_type_str)?; let asset_type_enum = string_to_asset_type(&asset_type_str)?;
let mut ast = mem::take(asset); let mut ast = mem::take(asset);
ast = ast.asset_type(asset_type_enum); ast = ast.asset_type(asset_type_enum);
@ -338,7 +384,7 @@ mod asset_module {
if decimals < 0 || decimals > 255 { if decimals < 0 || decimals > 255 {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Decimals value must be between 0 and 255, got {}", decimals).into(), format!("Decimals value must be between 0 and 255, got {}", decimals).into(),
Position::NONE Position::NONE,
))); )));
} }
let mut ast = mem::take(asset); let mut ast = mem::take(asset);
@ -441,7 +487,10 @@ mod listing_module {
// Setters using builder pattern with proper mutability handling // Setters using builder pattern with proper mutability handling
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn seller_id(listing: &mut RhaiListing, seller_id: INT) -> Result<RhaiListing, Box<EvalAltResult>> { pub fn seller_id(
listing: &mut RhaiListing,
seller_id: INT,
) -> Result<RhaiListing, Box<EvalAltResult>> {
let seller_id = id_from_i64_to_u32(seller_id)?; let seller_id = id_from_i64_to_u32(seller_id)?;
let mut lst = mem::take(listing); let mut lst = mem::take(listing);
lst = lst.seller_id(seller_id); lst = lst.seller_id(seller_id);
@ -450,7 +499,10 @@ mod listing_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn asset_id(listing: &mut RhaiListing, asset_id: INT) -> Result<RhaiListing, Box<EvalAltResult>> { pub fn asset_id(
listing: &mut RhaiListing,
asset_id: INT,
) -> Result<RhaiListing, Box<EvalAltResult>> {
let asset_id = id_from_i64_to_u32(asset_id)?; let asset_id = id_from_i64_to_u32(asset_id)?;
let mut lst = mem::take(listing); let mut lst = mem::take(listing);
lst = lst.asset_id(asset_id); lst = lst.asset_id(asset_id);
@ -467,7 +519,10 @@ mod listing_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn currency(listing: &mut RhaiListing, currency: String) -> Result<RhaiListing, Box<EvalAltResult>> { pub fn currency(
listing: &mut RhaiListing,
currency: String,
) -> Result<RhaiListing, Box<EvalAltResult>> {
let mut lst = mem::take(listing); let mut lst = mem::take(listing);
lst = lst.currency(currency); lst = lst.currency(currency);
*listing = lst; *listing = lst;
@ -475,7 +530,10 @@ mod listing_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn listing_type(listing: &mut RhaiListing, listing_type_str: String) -> Result<RhaiListing, Box<EvalAltResult>> { pub fn listing_type(
listing: &mut RhaiListing,
listing_type_str: String,
) -> Result<RhaiListing, Box<EvalAltResult>> {
let listing_type_enum = string_to_listing_type(&listing_type_str)?; let listing_type_enum = string_to_listing_type(&listing_type_str)?;
let mut lst = mem::take(listing); let mut lst = mem::take(listing);
lst = lst.listing_type(listing_type_enum); lst = lst.listing_type(listing_type_enum);
@ -484,7 +542,10 @@ mod listing_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn title(listing: &mut RhaiListing, title: String) -> Result<RhaiListing, Box<EvalAltResult>> { pub fn title(
listing: &mut RhaiListing,
title: String,
) -> Result<RhaiListing, Box<EvalAltResult>> {
let mut lst = mem::take(listing); let mut lst = mem::take(listing);
lst = lst.title(title); lst = lst.title(title);
*listing = lst; *listing = lst;
@ -492,7 +553,10 @@ mod listing_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn description(listing: &mut RhaiListing, description: String) -> Result<RhaiListing, Box<EvalAltResult>> { pub fn description(
listing: &mut RhaiListing,
description: String,
) -> Result<RhaiListing, Box<EvalAltResult>> {
let mut lst = mem::take(listing); let mut lst = mem::take(listing);
lst = lst.description(description); lst = lst.description(description);
*listing = lst; *listing = lst;
@ -500,7 +564,10 @@ mod listing_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn image_url(listing: &mut RhaiListing, image_url: String) -> Result<RhaiListing, Box<EvalAltResult>> { pub fn image_url(
listing: &mut RhaiListing,
image_url: String,
) -> Result<RhaiListing, Box<EvalAltResult>> {
let mut lst = mem::take(listing); let mut lst = mem::take(listing);
lst = lst.image_url(Some(image_url)); lst = lst.image_url(Some(image_url));
*listing = lst; *listing = lst;
@ -508,15 +575,21 @@ mod listing_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn expires_at(listing: &mut RhaiListing, end_date_ts: INT) -> Result<RhaiListing, Box<EvalAltResult>> { pub fn expires_at(
listing: &mut RhaiListing,
end_date_ts: INT,
) -> Result<RhaiListing, Box<EvalAltResult>> {
use chrono::TimeZone; 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() .single()
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime( .ok_or_else(|| {
format!("Invalid timestamp: {}", end_date_ts).into(), Box::new(EvalAltResult::ErrorRuntime(
Position::NONE format!("Invalid timestamp: {}", end_date_ts).into(),
)))?; Position::NONE,
))
})?;
let mut lst = mem::take(listing); let mut lst = mem::take(listing);
lst = lst.expires_at(Some(end_date)); lst = lst.expires_at(Some(end_date));
*listing = lst; *listing = lst;
@ -524,7 +597,10 @@ mod listing_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn add_tag(listing: &mut RhaiListing, tag: String) -> Result<RhaiListing, Box<EvalAltResult>> { pub fn add_tag(
listing: &mut RhaiListing,
tag: String,
) -> Result<RhaiListing, Box<EvalAltResult>> {
let mut lst = mem::take(listing); let mut lst = mem::take(listing);
lst = lst.add_tag(tag); lst = lst.add_tag(tag);
*listing = lst; *listing = lst;
@ -532,44 +608,50 @@ mod listing_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn add_bid(listing: &mut RhaiListing, bid: RhaiBid) -> Result<RhaiListing, Box<EvalAltResult>> { pub fn add_bid(
listing: &mut RhaiListing,
bid: RhaiBid,
) -> Result<RhaiListing, Box<EvalAltResult>> {
let lst = mem::take(listing); let lst = mem::take(listing);
match lst.clone().add_bid(bid) { match lst.clone().add_bid(bid) {
Ok(updated_lst) => { Ok(updated_lst) => {
*listing = updated_lst; *listing = updated_lst;
Ok(listing.clone()) Ok(listing.clone())
}, }
Err(err) => { Err(err) => {
// Put back the original listing since the add_bid failed // Put back the original listing since the add_bid failed
*listing = lst; *listing = lst;
Err(Box::new(EvalAltResult::ErrorRuntime( Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to add bid: {}", err).into(), format!("Failed to add bid: {}", err).into(),
Position::NONE Position::NONE,
))) )))
} }
} }
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn complete_sale(listing: &mut RhaiListing, buyer_id: INT) -> Result<RhaiListing, Box<EvalAltResult>> { pub fn complete_sale(
listing: &mut RhaiListing,
buyer_id: INT,
) -> Result<RhaiListing, Box<EvalAltResult>> {
let buyer_id = id_from_i64_to_u32(buyer_id)?; let buyer_id = id_from_i64_to_u32(buyer_id)?;
let lst = mem::take(listing); let lst = mem::take(listing);
// First set the buyer_id and sale_price // First set the buyer_id and sale_price
let lst_with_buyer = lst.clone().buyer_id(buyer_id); let lst_with_buyer = lst.clone().buyer_id(buyer_id);
// Now complete the sale // Now complete the sale
match lst_with_buyer.complete_sale() { match lst_with_buyer.complete_sale() {
Ok(updated_lst) => { Ok(updated_lst) => {
*listing = updated_lst; *listing = updated_lst;
Ok(listing.clone()) Ok(listing.clone())
}, }
Err(err) => { Err(err) => {
// Put back the original listing since the complete_sale failed // Put back the original listing since the complete_sale failed
*listing = lst; *listing = lst;
Err(Box::new(EvalAltResult::ErrorRuntime( Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to complete sale: {}", err).into(), format!("Failed to complete sale: {}", err).into(),
Position::NONE Position::NONE,
))) )))
} }
} }
@ -582,13 +664,13 @@ mod listing_module {
Ok(updated_lst) => { Ok(updated_lst) => {
*listing = updated_lst; *listing = updated_lst;
Ok(listing.clone()) Ok(listing.clone())
}, }
Err(err) => { Err(err) => {
// Put back the original listing since the cancel failed // Put back the original listing since the cancel failed
*listing = lst; *listing = lst;
Err(Box::new(EvalAltResult::ErrorRuntime( Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to cancel listing: {}", err).into(), 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 // Setters using builder pattern with proper mutability handling
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn listing_id(bid: &mut RhaiBid, listing_id: String) -> Result<RhaiBid, Box<EvalAltResult>> { pub fn listing_id(
bid: &mut RhaiBid,
listing_id: String,
) -> Result<RhaiBid, Box<EvalAltResult>> {
let mut b = mem::take(bid); let mut b = mem::take(bid);
b = b.listing_id(listing_id); b = b.listing_id(listing_id);
*bid = b; *bid = b;
@ -680,7 +765,10 @@ mod bid_module {
} }
#[rhai_fn(return_raw, global)] #[rhai_fn(return_raw, global)]
pub fn update_status(bid: &mut RhaiBid, status_str: String) -> Result<RhaiBid, Box<EvalAltResult>> { pub fn update_status(
bid: &mut RhaiBid,
status_str: String,
) -> Result<RhaiBid, Box<EvalAltResult>> {
let status = string_to_bid_status(&status_str)?; let status = string_to_bid_status(&status_str)?;
let mut b = mem::take(bid); let mut b = mem::take(bid);
b = b.status(status); b = b.status(status);
@ -706,13 +794,21 @@ pub fn register_finance_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
engine.register_global_module(bid_module.into()); engine.register_global_module(bid_module.into());
// --- Global Helper Functions (Enum conversions) --- // --- 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("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("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("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); engine.register_fn("bid_status_to_str", self::bid_status_to_string);
// --- Database interaction functions (registered in a separate db_module) --- // --- Database interaction functions (registered in a separate db_module) ---
@ -720,68 +816,158 @@ pub fn register_finance_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// Account DB functions // Account DB functions
let db_set_account = Arc::clone(&db); let db_set_account = Arc::clone(&db);
db_module.set_native_fn("set_account", move |account: Account| -> Result<INT, Box<EvalAltResult>> { db_module.set_native_fn(
let collection = db_set_account.collection::<Account>().map_err(|e| "set_account",
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account collection: {:?}", e).into(), Position::NONE)) )?; move |account: Account| -> Result<INT, Box<EvalAltResult>> {
collection.set(&account) let collection = db_set_account.collection::<Account>().map_err(|e| {
.map(|(id, _)| id as INT) Box::new(EvalAltResult::ErrorRuntime(
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Account: {:?}", e).into(), Position::NONE))) 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); let db_get_account = Arc::clone(&db);
db_module.set_native_fn("get_account_by_id", move |id_rhai: INT| -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_rhai)?; "get_account_by_id",
let collection = db_get_account.collection::<Account>().map_err(|e| move |id_rhai: INT| -> Result<Dynamic, Box<EvalAltResult>> {
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account collection: {:?}", e).into(), Position::NONE)) )?; let id_u32 = id_from_i64_to_u32(id_rhai)?;
collection.get_by_id(id_u32) let collection = db_get_account.collection::<Account>().map_err(|e| {
.map(|opt_account| opt_account.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) Box::new(EvalAltResult::ErrorRuntime(
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) 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 // Asset DB functions
let db_set_asset = Arc::clone(&db); let db_set_asset = Arc::clone(&db);
db_module.set_native_fn("set_asset", move |asset: Asset| -> Result<INT, Box<EvalAltResult>> { db_module.set_native_fn(
let collection = db_set_asset.collection::<Asset>().map_err(|e| "set_asset",
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset collection: {:?}", e).into(), Position::NONE)) )?; move |asset: Asset| -> Result<INT, Box<EvalAltResult>> {
collection.set(&asset) let collection = db_set_asset.collection::<Asset>().map_err(|e| {
.map(|(id, _)| id as INT) Box::new(EvalAltResult::ErrorRuntime(
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Asset: {:?}", e).into(), Position::NONE))) 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); let db_get_asset = Arc::clone(&db);
db_module.set_native_fn("get_asset_by_id", move |id_rhai: INT| -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_rhai)?; "get_asset_by_id",
let collection = db_get_asset.collection::<Asset>().map_err(|e| move |id_rhai: INT| -> Result<Dynamic, Box<EvalAltResult>> {
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset collection: {:?}", e).into(), Position::NONE)) )?; let id_u32 = id_from_i64_to_u32(id_rhai)?;
collection.get_by_id(id_u32) let collection = db_get_asset.collection::<Asset>().map_err(|e| {
.map(|opt_asset| opt_asset.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) Box::new(EvalAltResult::ErrorRuntime(
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) 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 // Listing DB functions
let db_set_listing = Arc::clone(&db); let db_set_listing = Arc::clone(&db);
db_module.set_native_fn("set_listing", move |listing: Listing| -> Result<INT, Box<EvalAltResult>> { db_module.set_native_fn(
let collection = db_set_listing.collection::<Listing>().map_err(|e| "set_listing",
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing collection: {:?}", e).into(), Position::NONE)) )?; move |listing: Listing| -> Result<INT, Box<EvalAltResult>> {
collection.set(&listing) let collection = db_set_listing.collection::<Listing>().map_err(|e| {
.map(|(id, _)| id as INT) Box::new(EvalAltResult::ErrorRuntime(
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Listing: {:?}", e).into(), Position::NONE))) 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); let db_get_listing = Arc::clone(&db);
db_module.set_native_fn("get_listing_by_id", move |id_rhai: INT| -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_rhai)?; "get_listing_by_id",
let collection = db_get_listing.collection::<Listing>().map_err(|e| move |id_rhai: INT| -> Result<Dynamic, Box<EvalAltResult>> {
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing collection: {:?}", e).into(), Position::NONE)) )?; let id_u32 = id_from_i64_to_u32(id_rhai)?;
collection.get_by_id(id_u32) let collection = db_get_listing.collection::<Listing>().map_err(|e| {
.map(|opt_listing| opt_listing.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) Box::new(EvalAltResult::ErrorRuntime(
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) 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()); engine.register_global_module(db_module.into());
// Global timestamp function for scripts to get current time // Global timestamp function for scripts to get current time
engine.register_fn("timestamp", || Utc::now().timestamp()); engine.register_fn("timestamp", || Utc::now().timestamp());
println!("Successfully registered finance Rhai module."); println!("Successfully registered finance Rhai module.");
} }

View File

@ -1,7 +1,7 @@
use super::flow_step::FlowStep;
use heromodels_core::BaseModelData; use heromodels_core::BaseModelData;
use heromodels_derive::model; use heromodels_derive::model;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::flow_step::FlowStep;
/// Represents a signing flow. /// Represents a signing flow.
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)] #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)]
@ -33,7 +33,7 @@ impl Flow {
Self { Self {
base_data: BaseModelData::new(), base_data: BaseModelData::new(),
flow_uuid: flow_uuid.to_string(), 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 status: String::from("Pending"), // Default status, to be set by builder
steps: Vec::new(), steps: Vec::new(),
} }

View File

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

View File

@ -1,7 +1,7 @@
use rhai::plugin::*; use rhai::plugin::*;
use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position};
use std::sync::Arc;
use std::mem; use std::mem;
use std::sync::Arc;
use super::flow::Flow; use super::flow::Flow;
use super::flow_step::FlowStep; use super::flow_step::FlowStep;
@ -9,18 +9,18 @@ use super::signature_requirement::SignatureRequirement;
type RhaiFlow = Flow; type RhaiFlow = Flow;
type RhaiFlowStep = FlowStep; type RhaiFlowStep = FlowStep;
type RhaiSignatureRequirement = SignatureRequirement; type RhaiSignatureRequirement = SignatureRequirement;
use crate::db::hero::OurDB;
use crate::db::Collection; use crate::db::Collection;
use crate::db::Db; use crate::db::Db;
use crate::db::hero::OurDB;
// Helper to convert i64 from Rhai to u32 for IDs // Helper to convert i64 from Rhai to u32 for IDs
fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> { fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_| u32::try_from(id_i64).map_err(|_| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert ID '{}' to u32", id_i64).into(), format!("Failed to convert ID '{}' to u32", id_i64).into(),
Position::NONE Position::NONE,
)) ))
) })
} }
#[export_module] #[export_module]
@ -30,7 +30,7 @@ mod rhai_flow_module {
pub fn new_flow(flow_uuid: String) -> RhaiFlow { pub fn new_flow(flow_uuid: String) -> RhaiFlow {
Flow::new(flow_uuid) Flow::new(flow_uuid)
} }
/// Sets the flow name /// Sets the flow name
#[rhai_fn(name = "name", return_raw, global, pure)] #[rhai_fn(name = "name", return_raw, global, pure)]
pub fn flow_name(flow: &mut RhaiFlow, name: String) -> Result<RhaiFlow, Box<EvalAltResult>> { pub fn flow_name(flow: &mut RhaiFlow, name: String) -> Result<RhaiFlow, Box<EvalAltResult>> {
@ -38,41 +38,63 @@ mod rhai_flow_module {
*flow = owned_flow.name(name); *flow = owned_flow.name(name);
Ok(flow.clone()) Ok(flow.clone())
} }
/// Sets the flow status /// Sets the flow status
#[rhai_fn(name = "status", return_raw, global, pure)] #[rhai_fn(name = "status", return_raw, global, pure)]
pub fn flow_status(flow: &mut RhaiFlow, status: String) -> Result<RhaiFlow, Box<EvalAltResult>> { pub fn flow_status(
flow: &mut RhaiFlow,
status: String,
) -> Result<RhaiFlow, Box<EvalAltResult>> {
let owned_flow = mem::replace(flow, Flow::new("")); // Dummy for replacement let owned_flow = mem::replace(flow, Flow::new("")); // Dummy for replacement
*flow = owned_flow.status(status); *flow = owned_flow.status(status);
Ok(flow.clone()) Ok(flow.clone())
} }
/// Adds a step to the flow /// Adds a step to the flow
#[rhai_fn(name = "add_step", return_raw, global, pure)] #[rhai_fn(name = "add_step", return_raw, global, pure)]
pub fn flow_add_step(flow: &mut RhaiFlow, step: RhaiFlowStep) -> Result<RhaiFlow, Box<EvalAltResult>> { pub fn flow_add_step(
flow: &mut RhaiFlow,
step: RhaiFlowStep,
) -> Result<RhaiFlow, Box<EvalAltResult>> {
let owned_flow = mem::replace(flow, Flow::new("")); // Dummy for replacement let owned_flow = mem::replace(flow, Flow::new("")); // Dummy for replacement
*flow = owned_flow.add_step(step); *flow = owned_flow.add_step(step);
Ok(flow.clone()) Ok(flow.clone())
} }
// Flow Getters // Flow Getters
#[rhai_fn(get = "id", pure)] #[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)] #[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)] #[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)] #[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)] #[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)] #[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)] #[rhai_fn(get = "steps", pure)]
pub fn get_steps(flow: &mut RhaiFlow) -> Array { pub fn get_steps(flow: &mut RhaiFlow) -> Array {
flow.steps.iter().cloned().map(Dynamic::from).collect::<Array>() flow.steps
.iter()
.cloned()
.map(Dynamic::from)
.collect::<Array>()
} }
// --- FlowStep Functions --- // --- FlowStep Functions ---
#[rhai_fn(global)] #[rhai_fn(global)]
pub fn new_flow_step(step_order_i64: i64) -> Dynamic { pub fn new_flow_step(step_order_i64: i64) -> Dynamic {
@ -81,34 +103,46 @@ mod rhai_flow_module {
let mut flow_step = FlowStep::default(); let mut flow_step = FlowStep::default();
flow_step.step_order = step_order; flow_step.step_order = step_order;
Dynamic::from(flow_step) Dynamic::from(flow_step)
}, }
Err(err) => Dynamic::from(err.to_string()) Err(err) => Dynamic::from(err.to_string()),
} }
} }
/// Sets the flow step description /// Sets the flow step description
#[rhai_fn(name = "description", return_raw, global, pure)] #[rhai_fn(name = "description", return_raw, global, pure)]
pub fn flow_step_description(step: &mut RhaiFlowStep, description: String) -> Result<RhaiFlowStep, Box<EvalAltResult>> { pub fn flow_step_description(
step: &mut RhaiFlowStep,
description: String,
) -> Result<RhaiFlowStep, Box<EvalAltResult>> {
let owned_step = mem::replace(step, FlowStep::default()); // Use Default trait let owned_step = mem::replace(step, FlowStep::default()); // Use Default trait
*step = owned_step.description(description); *step = owned_step.description(description);
Ok(step.clone()) Ok(step.clone())
} }
/// Sets the flow step status /// Sets the flow step status
#[rhai_fn(name = "status", return_raw, global, pure)] #[rhai_fn(name = "status", return_raw, global, pure)]
pub fn flow_step_status(step: &mut RhaiFlowStep, status: String) -> Result<RhaiFlowStep, Box<EvalAltResult>> { pub fn flow_step_status(
step: &mut RhaiFlowStep,
status: String,
) -> Result<RhaiFlowStep, Box<EvalAltResult>> {
let owned_step = mem::replace(step, FlowStep::default()); // Use Default trait let owned_step = mem::replace(step, FlowStep::default()); // Use Default trait
*step = owned_step.status(status); *step = owned_step.status(status);
Ok(step.clone()) Ok(step.clone())
} }
// FlowStep Getters // FlowStep Getters
#[rhai_fn(get = "id", pure)] #[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)] #[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)] #[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)] #[rhai_fn(get = "description", pure)]
pub fn get_step_description(step: &mut RhaiFlowStep) -> Dynamic { pub fn get_step_description(step: &mut RhaiFlowStep) -> Dynamic {
match &step.description { match &step.description {
@ -117,14 +151,22 @@ mod rhai_flow_module {
} }
} }
#[rhai_fn(get = "step_order", pure)] #[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)] #[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 --- // --- SignatureRequirement Functions ---
/// Create a new signature requirement /// Create a new signature requirement
#[rhai_fn(global)] #[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) { match id_from_i64_to_u32(flow_step_id_i64) {
Ok(flow_step_id) => { Ok(flow_step_id) => {
let mut signature_requirement = SignatureRequirement::default(); let mut signature_requirement = SignatureRequirement::default();
@ -132,48 +174,69 @@ mod rhai_flow_module {
signature_requirement.public_key = public_key; signature_requirement.public_key = public_key;
signature_requirement.message = message; signature_requirement.message = message;
Dynamic::from(signature_requirement) Dynamic::from(signature_requirement)
}, }
Err(err) => Dynamic::from(err.to_string()) Err(err) => Dynamic::from(err.to_string()),
} }
} }
/// Sets the signed_by field /// Sets the signed_by field
#[rhai_fn(name = "signed_by", return_raw, global, pure)] #[rhai_fn(name = "signed_by", return_raw, global, pure)]
pub fn signature_requirement_signed_by(sr: &mut RhaiSignatureRequirement, signed_by: String) -> Result<RhaiSignatureRequirement, Box<EvalAltResult>> { pub fn signature_requirement_signed_by(
sr: &mut RhaiSignatureRequirement,
signed_by: String,
) -> Result<RhaiSignatureRequirement, Box<EvalAltResult>> {
let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait
*sr = owned_sr.signed_by(signed_by); *sr = owned_sr.signed_by(signed_by);
Ok(sr.clone()) Ok(sr.clone())
} }
/// Sets the signature field /// Sets the signature field
#[rhai_fn(name = "signature", return_raw, global, pure)] #[rhai_fn(name = "signature", return_raw, global, pure)]
pub fn signature_requirement_signature(sr: &mut RhaiSignatureRequirement, signature: String) -> Result<RhaiSignatureRequirement, Box<EvalAltResult>> { pub fn signature_requirement_signature(
sr: &mut RhaiSignatureRequirement,
signature: String,
) -> Result<RhaiSignatureRequirement, Box<EvalAltResult>> {
let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait
*sr = owned_sr.signature(signature); *sr = owned_sr.signature(signature);
Ok(sr.clone()) Ok(sr.clone())
} }
/// Sets the status field /// Sets the status field
#[rhai_fn(name = "status", return_raw, global, pure)] #[rhai_fn(name = "status", return_raw, global, pure)]
pub fn signature_requirement_status(sr: &mut RhaiSignatureRequirement, status: String) -> Result<RhaiSignatureRequirement, Box<EvalAltResult>> { pub fn signature_requirement_status(
sr: &mut RhaiSignatureRequirement,
status: String,
) -> Result<RhaiSignatureRequirement, Box<EvalAltResult>> {
let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait
*sr = owned_sr.status(status); *sr = owned_sr.status(status);
Ok(sr.clone()) Ok(sr.clone())
} }
// SignatureRequirement Getters // SignatureRequirement Getters
#[rhai_fn(get = "id", pure)] #[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)] #[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)] #[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)] #[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)] #[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)] #[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)] #[rhai_fn(get = "signed_by", pure)]
pub fn get_sr_signed_by(sr: &mut RhaiSignatureRequirement) -> Dynamic { pub fn get_sr_signed_by(sr: &mut RhaiSignatureRequirement) -> Dynamic {
match &sr.signed_by { match &sr.signed_by {
@ -189,185 +252,284 @@ mod rhai_flow_module {
} }
} }
#[rhai_fn(get = "status", pure)] #[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 /// Register the flow module with the Rhai engine
pub fn register_flow_rhai_module(engine: &mut Engine, db: Arc<OurDB>) { pub fn register_flow_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// Create a module for database functions // Create a module for database functions
let mut db_module = Module::new(); let mut db_module = Module::new();
// Flow database functions // Flow database functions
let db_clone = Arc::clone(&db); let db_clone = Arc::clone(&db);
db_module.set_native_fn("save_flow", move |flow: Flow| -> Result<Flow, Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "save_flow",
let result = db_clone.set(&flow) move |flow: Flow| -> Result<Flow, Box<EvalAltResult>> {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_flow: {:?}", e).into(), Position::NONE)))?; // Use the Collection trait method directly
let result = db_clone.set(&flow).map_err(|e| {
// Return the updated flow with the correct ID Box::new(EvalAltResult::ErrorRuntime(
Ok(result.1) 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); let db_clone = Arc::clone(&db);
db_module.set_native_fn("get_flow_by_id", move |id_i64: INT| -> Result<Flow, Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_i64)?; "get_flow_by_id",
// Use the Collection trait method directly move |id_i64: INT| -> Result<Flow, Box<EvalAltResult>> {
db_clone.get_by_id(id_u32) let id_u32 = id_from_i64_to_u32(id_i64)?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_flow_by_id: {:?}", e).into(), Position::NONE)))? // Use the Collection trait method directly
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Flow with ID {} not found", id_u32).into(), Position::NONE))) 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); let db_clone = Arc::clone(&db);
db_module.set_native_fn("delete_flow", move |id_i64: INT| -> Result<(), Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_i64)?; "delete_flow",
// Use the Collection trait method directly move |id_i64: INT| -> Result<(), Box<EvalAltResult>> {
let collection = db_clone.collection::<Flow>() let id_u32 = id_from_i64_to_u32(id_i64)?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( // Use the Collection trait method directly
format!("Failed to get flow collection: {:?}", e).into(), let collection = db_clone.collection::<Flow>().map_err(|e| {
Position::NONE Box::new(EvalAltResult::ErrorRuntime(
)))?; format!("Failed to get flow collection: {:?}", e).into(),
collection.delete_by_id(id_u32) Position::NONE,
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( ))
format!("Failed to delete Flow (ID: {}): {:?}", id_u32, 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); let db_clone = Arc::clone(&db);
db_module.set_native_fn("list_flows", move || -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let collection = db_clone.collection::<Flow>() "list_flows",
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( move || -> Result<Dynamic, Box<EvalAltResult>> {
format!("Failed to get flow collection: {:?}", e).into(), let collection = db_clone.collection::<Flow>().map_err(|e| {
Position::NONE Box::new(EvalAltResult::ErrorRuntime(
)))?; format!("Failed to get flow collection: {:?}", e).into(),
let flows = collection.get_all() Position::NONE,
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( ))
format!("Failed to get all flows: {:?}", e).into(), })?;
Position::NONE let flows = collection.get_all().map_err(|e| {
)))?; Box::new(EvalAltResult::ErrorRuntime(
let mut array = Array::new(); format!("Failed to get all flows: {:?}", e).into(),
for flow in flows { Position::NONE,
array.push(Dynamic::from(flow)); ))
} })?;
Ok(Dynamic::from(array)) let mut array = Array::new();
}); for flow in flows {
array.push(Dynamic::from(flow));
}
Ok(Dynamic::from(array))
},
);
// FlowStep database functions // FlowStep database functions
let db_clone = Arc::clone(&db); let db_clone = Arc::clone(&db);
db_module.set_native_fn("save_flow_step", move |step: FlowStep| -> Result<FlowStep, Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "save_flow_step",
let result = db_clone.set(&step) move |step: FlowStep| -> Result<FlowStep, Box<EvalAltResult>> {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_flow_step: {:?}", e).into(), Position::NONE)))?; // Use the Collection trait method directly
let result = db_clone.set(&step).map_err(|e| {
// Return the updated flow step with the correct ID Box::new(EvalAltResult::ErrorRuntime(
Ok(result.1) 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); let db_clone = Arc::clone(&db);
db_module.set_native_fn("get_flow_step_by_id", move |id_i64: INT| -> Result<FlowStep, Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_i64)?; "get_flow_step_by_id",
// Use the Collection trait method directly move |id_i64: INT| -> Result<FlowStep, Box<EvalAltResult>> {
db_clone.get_by_id(id_u32) let id_u32 = id_from_i64_to_u32(id_i64)?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_flow_step_by_id: {:?}", e).into(), Position::NONE)))? // Use the Collection trait method directly
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("FlowStep with ID {} not found", id_u32).into(), Position::NONE))) 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); let db_clone = Arc::clone(&db);
db_module.set_native_fn("delete_flow_step", move |id_i64: INT| -> Result<(), Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_i64)?; "delete_flow_step",
// Use the Collection trait method directly move |id_i64: INT| -> Result<(), Box<EvalAltResult>> {
let collection = db_clone.collection::<FlowStep>() let id_u32 = id_from_i64_to_u32(id_i64)?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( // Use the Collection trait method directly
format!("Failed to get flow step collection: {:?}", e).into(), let collection = db_clone.collection::<FlowStep>().map_err(|e| {
Position::NONE Box::new(EvalAltResult::ErrorRuntime(
)))?; format!("Failed to get flow step collection: {:?}", e).into(),
collection.delete_by_id(id_u32) Position::NONE,
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( ))
format!("Failed to delete FlowStep (ID: {}): {:?}", id_u32, 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); let db_clone = Arc::clone(&db);
db_module.set_native_fn("list_flow_steps", move || -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let collection = db_clone.collection::<FlowStep>() "list_flow_steps",
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( move || -> Result<Dynamic, Box<EvalAltResult>> {
format!("Failed to get flow step collection: {:?}", e).into(), let collection = db_clone.collection::<FlowStep>().map_err(|e| {
Position::NONE Box::new(EvalAltResult::ErrorRuntime(
)))?; format!("Failed to get flow step collection: {:?}", e).into(),
let steps = collection.get_all() Position::NONE,
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( ))
format!("Failed to get all flow steps: {:?}", e).into(), })?;
Position::NONE let steps = collection.get_all().map_err(|e| {
)))?; Box::new(EvalAltResult::ErrorRuntime(
let mut array = Array::new(); format!("Failed to get all flow steps: {:?}", e).into(),
for step in steps { Position::NONE,
array.push(Dynamic::from(step)); ))
} })?;
Ok(Dynamic::from(array)) let mut array = Array::new();
}); for step in steps {
array.push(Dynamic::from(step));
}
Ok(Dynamic::from(array))
},
);
// SignatureRequirement database functions // SignatureRequirement database functions
let db_clone = Arc::clone(&db); let db_clone = Arc::clone(&db);
db_module.set_native_fn("save_signature_requirement", move |sr: SignatureRequirement| -> Result<SignatureRequirement, Box<EvalAltResult>> { db_module.set_native_fn(
// Use the Collection trait method directly "save_signature_requirement",
let result = db_clone.set(&sr) move |sr: SignatureRequirement| -> Result<SignatureRequirement, Box<EvalAltResult>> {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_signature_requirement: {:?}", e).into(), Position::NONE)))?; // Use the Collection trait method directly
let result = db_clone.set(&sr).map_err(|e| {
// Return the updated signature requirement with the correct ID Box::new(EvalAltResult::ErrorRuntime(
Ok(result.1) 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); let db_clone = Arc::clone(&db);
db_module.set_native_fn("get_signature_requirement_by_id", move |id_i64: INT| -> Result<SignatureRequirement, Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_i64)?; "get_signature_requirement_by_id",
// Use the Collection trait method directly move |id_i64: INT| -> Result<SignatureRequirement, Box<EvalAltResult>> {
db_clone.get_by_id(id_u32) let id_u32 = id_from_i64_to_u32(id_i64)?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_signature_requirement_by_id: {:?}", e).into(), Position::NONE)))? // Use the Collection trait method directly
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("SignatureRequirement with ID {} not found", id_u32).into(), Position::NONE))) 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); let db_clone = Arc::clone(&db);
db_module.set_native_fn("delete_signature_requirement", move |id_i64: INT| -> Result<(), Box<EvalAltResult>> { db_module.set_native_fn(
let id_u32 = id_from_i64_to_u32(id_i64)?; "delete_signature_requirement",
// Use the Collection trait method directly move |id_i64: INT| -> Result<(), Box<EvalAltResult>> {
let collection = db_clone.collection::<SignatureRequirement>() let id_u32 = id_from_i64_to_u32(id_i64)?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( // Use the Collection trait method directly
format!("Failed to get signature requirement collection: {:?}", e).into(), let collection = db_clone.collection::<SignatureRequirement>().map_err(|e| {
Position::NONE Box::new(EvalAltResult::ErrorRuntime(
)))?; format!("Failed to get signature requirement collection: {:?}", e).into(),
collection.delete_by_id(id_u32) Position::NONE,
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( ))
format!("Failed to delete SignatureRequirement (ID: {}): {:?}", id_u32, 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); let db_clone = Arc::clone(&db);
db_module.set_native_fn("list_signature_requirements", move || -> Result<Dynamic, Box<EvalAltResult>> { db_module.set_native_fn(
let collection = db_clone.collection::<SignatureRequirement>() "list_signature_requirements",
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( move || -> Result<Dynamic, Box<EvalAltResult>> {
format!("Failed to get signature requirement collection: {:?}", e).into(), let collection = db_clone.collection::<SignatureRequirement>().map_err(|e| {
Position::NONE Box::new(EvalAltResult::ErrorRuntime(
)))?; format!("Failed to get signature requirement collection: {:?}", e).into(),
let srs = collection.get_all() Position::NONE,
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( ))
format!("Failed to get all signature requirements: {:?}", e).into(), })?;
Position::NONE let srs = collection.get_all().map_err(|e| {
)))?; Box::new(EvalAltResult::ErrorRuntime(
let mut array = Array::new(); format!("Failed to get all signature requirements: {:?}", e).into(),
for sr in srs { Position::NONE,
array.push(Dynamic::from(sr)); ))
} })?;
Ok(Dynamic::from(array)) let mut array = Array::new();
}); for sr in srs {
array.push(Dynamic::from(sr));
}
Ok(Dynamic::from(array))
},
);
// Register the database module globally // Register the database module globally
engine.register_static_module("db", db_module.into()); engine.register_static_module("db", db_module.into());
// Register the flow module using exported_module! macro // Register the flow module using exported_module! macro
let module = exported_module!(rhai_flow_module); let module = exported_module!(rhai_flow_module);
engine.register_global_module(module.into()); engine.register_global_module(module.into());
println!("Flow Rhai module registered."); println!("Flow Rhai module registered.");
} }

View File

@ -32,7 +32,12 @@ pub struct SignatureRequirement {
impl SignatureRequirement { impl SignatureRequirement {
/// Create a new signature requirement. /// 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 { Self {
base_data: BaseModelData::new(), base_data: BaseModelData::new(),
flow_step_id, flow_step_id,

View File

@ -4,5 +4,5 @@ pub mod proposal;
pub mod attached_file; pub mod attached_file;
pub use self::proposal::{Proposal, Ballot, VoteOption, ProposalStatus, VoteEventStatus}; pub use self::attached_file::AttachedFile;
pub use self::attached_file::AttachedFile; pub use self::proposal::{Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption};

View File

@ -5,9 +5,9 @@ use heromodels_derive::model; // For #[model]
use rhai::{CustomType, TypeBuilder}; use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use heromodels_core::BaseModelData;
use crate::models::core::Comment;
use super::AttachedFile; use super::AttachedFile;
use crate::models::core::Comment;
use heromodels_core::BaseModelData;
// --- Enums --- // --- Enums ---
@ -72,9 +72,9 @@ impl VoteOption {
#[model] // Has base.Base in V spec #[model] // Has base.Base in V spec
pub struct Ballot { pub struct Ballot {
pub base_data: BaseModelData, pub base_data: BaseModelData,
pub user_id: u32, // The ID of the user who cast this ballot 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 vote_option_id: u8, // The 'id' of the VoteOption chosen
pub shares_count: i64, // Number of shares/tokens/voting power pub shares_count: i64, // Number of shares/tokens/voting power
pub comment: Option<String>, // Optional comment from the voter pub comment: Option<String>, // Optional comment from the voter
} }
@ -281,7 +281,7 @@ impl Proposal {
eprintln!("Voting is not open for proposal '{}'", self.title); eprintln!("Voting is not open for proposal '{}'", self.title);
return self; return self;
} }
// Check if the option exists // Check if the option exists
if !self.options.iter().any(|opt| opt.id == chosen_option_id) { if !self.options.iter().any(|opt| opt.id == chosen_option_id) {
eprintln!( eprintln!(
@ -290,7 +290,7 @@ impl Proposal {
); );
return self; return self;
} }
// Check eligibility for private proposals // Check eligibility for private proposals
if let Some(group) = &self.private_group { if let Some(group) = &self.private_group {
if !group.contains(&user_id) { if !group.contains(&user_id) {
@ -301,14 +301,14 @@ impl Proposal {
return self; return self;
} }
} }
// Create a new ballot with the comment // Create a new ballot with the comment
let mut new_ballot = Ballot::new(ballot_id, user_id, chosen_option_id, shares); let mut new_ballot = Ballot::new(ballot_id, user_id, chosen_option_id, shares);
new_ballot.comment = Some(comment.to_string()); new_ballot.comment = Some(comment.to_string());
// Add the ballot to the proposal // Add the ballot to the proposal
self.ballots.push(new_ballot); self.ballots.push(new_ballot);
// Update the vote count for the chosen option // Update the vote count for the chosen option
if let Some(option) = self if let Some(option) = self
.options .options
@ -317,7 +317,7 @@ impl Proposal {
{ {
option.count += shares; option.count += shares;
} }
self self
} }
} }

View File

@ -1,7 +1,7 @@
use heromodels_core::BaseModelData; use heromodels_core::BaseModelData;
use heromodels_derive::model; use heromodels_derive::model;
use std::fmt;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt;
// --- Enums --- // --- Enums ---
@ -109,7 +109,7 @@ impl ContractSigner {
self.signed_at = Some(signed_at); self.signed_at = Some(signed_at);
self self
} }
pub fn clear_signed_at(mut self) -> Self { pub fn clear_signed_at(mut self) -> Self {
self.signed_at = None; self.signed_at = None;
self self
@ -139,21 +139,21 @@ pub struct Contract {
pub title: String, pub title: String,
pub description: String, pub description: String,
#[index] #[index]
pub contract_type: String, pub contract_type: String,
#[index] #[index]
pub status: crate::models::ContractStatus, // Use re-exported path for #[model] macro pub status: crate::models::ContractStatus, // Use re-exported path for #[model] macro
pub created_by: String, pub created_by: String,
pub terms_and_conditions: String, pub terms_and_conditions: String,
pub start_date: Option<u64>, pub start_date: Option<u64>,
pub end_date: Option<u64>, pub end_date: Option<u64>,
pub renewal_period_days: Option<i32>, pub renewal_period_days: Option<i32>,
pub next_renewal_date: Option<u64>, pub next_renewal_date: Option<u64>,
pub signers: Vec<ContractSigner>, pub signers: Vec<ContractSigner>,
pub revisions: Vec<ContractRevision>, pub revisions: Vec<ContractRevision>,
pub current_version: u32, pub current_version: u32,
@ -217,7 +217,7 @@ impl Contract {
self.start_date = Some(start_date); self.start_date = Some(start_date);
self self
} }
pub fn clear_start_date(mut self) -> Self { pub fn clear_start_date(mut self) -> Self {
self.start_date = None; self.start_date = None;
self self
@ -257,7 +257,7 @@ impl Contract {
self.signers.push(signer); self.signers.push(signer);
self self
} }
pub fn signers(mut self, signers: Vec<ContractSigner>) -> Self { pub fn signers(mut self, signers: Vec<ContractSigner>) -> Self {
self.signers = signers; self.signers = signers;
self self
@ -272,7 +272,7 @@ impl Contract {
self.revisions = revisions; self.revisions = revisions;
self self
} }
pub fn current_version(mut self, version: u32) -> Self { pub fn current_version(mut self, version: u32) -> Self {
self.current_version = version; self.current_version = version;
self self
@ -287,7 +287,7 @@ impl Contract {
self.last_signed_date = None; self.last_signed_date = None;
self self
} }
// Example methods for state changes // Example methods for state changes
pub fn set_status(&mut self, status: crate::models::ContractStatus) { pub fn set_status(&mut self, status: crate::models::ContractStatus) {
self.status = status; self.status = status;

View File

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

View File

@ -1,7 +1,7 @@
use heromodels_core::BaseModelData; use heromodels_core::BaseModelData;
use heromodels_derive::model; use heromodels_derive::model;
use serde::{Deserialize, Serialize};
use rhai::{CustomType, TypeBuilder}; use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
/// Represents a collection of library items. /// Represents a collection of library items.
#[model] #[model]

File diff suppressed because it is too large Load Diff

View File

@ -3,41 +3,41 @@ pub mod core;
pub mod userexample; pub mod userexample;
// pub mod productexample; // Temporarily remove as files are missing // pub mod productexample; // Temporarily remove as files are missing
pub mod access; 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 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; pub mod projects;
// Re-export key types for convenience // Re-export key types for convenience
pub use core::Comment; pub use core::Comment;
pub use userexample::User; pub use userexample::User;
// pub use productexample::Product; // Temporarily remove // 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 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::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")] #[cfg(feature = "rhai")]
pub use calendar::register_calendar_rhai_module; pub use calendar::register_calendar_rhai_module;
#[cfg(feature = "rhai")] #[cfg(feature = "rhai")]
pub use circle::register_circle_rhai_module; pub use circle::register_circle_rhai_module;
pub use flow::register_flow_rhai_module;
pub use legal::register_legal_rhai_module; pub use legal::register_legal_rhai_module;
#[cfg(feature = "rhai")] #[cfg(feature = "rhai")]
pub use biz::register_biz_rhai_module; pub use library::register_library_rhai_module;
#[cfg(feature = "rhai")] #[cfg(feature = "rhai")]
pub use projects::register_projects_rhai_module; pub use projects::register_projects_rhai_module;
#[cfg(feature = "rhai")]
pub use library::register_library_rhai_module;

View File

@ -1,8 +1,8 @@
// heromodels/src/models/projects/base.rs // heromodels/src/models/projects/base.rs
use serde::{Deserialize, Serialize}; use heromodels_core::{BaseModelData, BaseModelDataOps, Model};
use heromodels_core::{BaseModelData, Model, BaseModelDataOps};
#[cfg(feature = "rhai")] #[cfg(feature = "rhai")]
use rhai::{CustomType, TypeBuilder}; use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
use strum_macros::Display; // Made unconditional as Display derive is used on non-rhai-gated enums use strum_macros::Display; // Made unconditional as Display derive is used on non-rhai-gated enums
// --- Enums --- // --- Enums ---
@ -50,7 +50,7 @@ pub enum ItemType {
impl Default for ItemType { impl Default for ItemType {
fn default() -> Self { fn default() -> Self {
ItemType::Task ItemType::Task
} }
} }
// --- Structs --- // --- Structs ---
@ -178,7 +178,6 @@ impl Project {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "rhai", derive(CustomType))] #[cfg_attr(feature = "rhai", derive(CustomType))]
pub struct Label { pub struct Label {
@ -226,4 +225,3 @@ impl Label {
self self
} }
} }

View File

@ -3,8 +3,8 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use heromodels_core::BaseModelData; use heromodels_core::BaseModelData;
use heromodels_derive::model; use heromodels_derive::model;
use serde::{Deserialize, Serialize};
use rhai::{CustomType, TypeBuilder}; use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
use super::base::Status as ProjectStatus; // Using the generic project status for now use super::base::Status as ProjectStatus; // Using the generic project status for now
@ -15,17 +15,17 @@ pub struct Epic {
pub name: String, pub name: String,
pub description: Option<String>, pub description: Option<String>,
pub status: ProjectStatus, // Or a new EpicStatus enum if needed pub status: ProjectStatus, // Or a new EpicStatus enum if needed
pub project_id: Option<u32>, // Link to a general project/board pub project_id: Option<u32>, // Link to a general project/board
pub start_date: Option<DateTime<Utc>>, pub start_date: Option<DateTime<Utc>>,
pub due_date: Option<DateTime<Utc>>, pub due_date: Option<DateTime<Utc>>,
pub tags: Vec<String>, pub tags: Vec<String>,
// Explicitly list task IDs belonging to this epic // Explicitly list task IDs belonging to this epic
// This helps in querying and avoids relying solely on tasks pointing to the epic. // This helps in querying and avoids relying solely on tasks pointing to the epic.
pub child_task_ids: Vec<u32>, pub child_task_ids: Vec<u32>,
} }
impl Epic { impl Epic {

View File

@ -1,11 +1,11 @@
// heromodels/src/models/projects/mod.rs // heromodels/src/models/projects/mod.rs
pub mod base; pub mod base;
pub mod task_enums;
pub mod task;
pub mod epic; pub mod epic;
pub mod sprint_enums;
pub mod sprint; pub mod sprint;
pub mod sprint_enums;
pub mod task;
pub mod task_enums;
// pub mod epic; // pub mod epic;
// pub mod issue; // pub mod issue;
// pub mod kanban; // pub mod kanban;
@ -13,11 +13,11 @@ pub mod sprint;
// pub mod story; // pub mod story;
pub use base::*; pub use base::*;
pub use task_enums::*;
pub use task::*;
pub use epic::*; pub use epic::*;
pub use sprint_enums::*;
pub use sprint::*; pub use sprint::*;
pub use sprint_enums::*;
pub use task::*;
pub use task_enums::*;
// pub use epic::*; // pub use epic::*;
// pub use issue::*; // pub use issue::*;
// pub use kanban::*; // pub use kanban::*;
@ -25,7 +25,7 @@ pub use sprint::*;
// pub use story::*; // pub use story::*;
#[cfg(feature = "rhai")] #[cfg(feature = "rhai")]
pub mod rhai; pub mod rhai;
#[cfg(feature = "rhai")] #[cfg(feature = "rhai")]
pub use rhai::register_projects_rhai_module; pub use rhai::register_projects_rhai_module;

View File

@ -1,14 +1,13 @@
// heromodels/src/models/projects/rhai.rs // heromodels/src/models/projects/rhai.rs
use rhai::{Engine, EvalAltResult, Dynamic, Position};
use std::sync::Arc;
use crate::db::hero::OurDB; use crate::db::hero::OurDB;
use heromodels_core::{Model, BaseModelDataOps}; use crate::db::{Collection, Db};
use crate::db::{Db, Collection}; use heromodels_core::{BaseModelDataOps, Model};
use rhai::{Dynamic, Engine, EvalAltResult, Position};
use std::sync::Arc;
// Import models from the projects::base module // 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) // Helper function for ID conversion (if needed, similar to other rhai.rs files)
fn id_from_i64(val: i64) -> Result<u32, Box<EvalAltResult>> { fn id_from_i64(val: i64) -> Result<u32, Box<EvalAltResult>> {
@ -78,172 +77,325 @@ pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
}); });
// Multi-argument constructor (renamed) // Multi-argument constructor (renamed)
engine.register_fn("new_project_with_details", |id_i64: i64, name: String, description: String, owner_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { engine.register_fn(
Ok(Project::new(id_from_i64(id_i64)?, name, description, id_from_i64(owner_id_i64)?)) "new_project_with_details",
}); |id_i64: i64,
name: String,
description: String,
owner_id_i64: i64|
-> Result<Project, Box<EvalAltResult>> {
Ok(Project::new(
id_from_i64(id_i64)?,
name,
description,
id_from_i64(owner_id_i64)?,
))
},
);
// Getters for Project // Getters for Project
engine.register_get("id", |p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.get_id() as i64) }); engine.register_get("id", |p: &mut Project| -> Result<i64, Box<EvalAltResult>> {
engine.register_get("name", |p: &mut Project| -> Result<String, Box<EvalAltResult>> { Ok(p.name.clone()) }); Ok(p.get_id() as i64)
engine.register_get("description", |p: &mut Project| -> Result<String, Box<EvalAltResult>> { Ok(p.description.clone()) });
engine.register_get("owner_id", |p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.owner_id as i64) });
engine.register_get("member_ids", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.member_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect())
}); });
engine.register_get("board_ids", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> { engine.register_get(
Ok(p.board_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect()) "name",
}); |p: &mut Project| -> Result<String, Box<EvalAltResult>> { Ok(p.name.clone()) },
engine.register_get("sprint_ids", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> { );
Ok(p.sprint_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect()) engine.register_get(
}); "description",
engine.register_get("epic_ids", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> { |p: &mut Project| -> Result<String, Box<EvalAltResult>> { Ok(p.description.clone()) },
Ok(p.epic_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect()) );
}); engine.register_get(
engine.register_get("tags", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> { "owner_id",
Ok(p.tags.iter().map(|tag| rhai::Dynamic::from(tag.clone())).collect()) |p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.owner_id as i64) },
}); );
engine.register_get("created_at", |p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.base_data.created_at) }); engine.register_get(
engine.register_get("modified_at", |p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.base_data.modified_at) }); "member_ids",
engine.register_get("comments", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> { |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.base_data.comments.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect()) Ok(p.member_ids
}); .iter()
engine.register_get("status", |p: &mut Project| -> Result<Status, Box<EvalAltResult>> { Ok(p.status.clone()) }); .map(|&id| rhai::Dynamic::from(id as i64))
engine.register_get("priority", |p: &mut Project| -> Result<Priority, Box<EvalAltResult>> { Ok(p.priority.clone()) }); .collect())
engine.register_get("item_type", |p: &mut Project| -> Result<ItemType, Box<EvalAltResult>> { Ok(p.item_type.clone()) }); },
);
engine.register_get(
"board_ids",
|p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.board_ids
.iter()
.map(|&id| rhai::Dynamic::from(id as i64))
.collect())
},
);
engine.register_get(
"sprint_ids",
|p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.sprint_ids
.iter()
.map(|&id| rhai::Dynamic::from(id as i64))
.collect())
},
);
engine.register_get(
"epic_ids",
|p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.epic_ids
.iter()
.map(|&id| rhai::Dynamic::from(id as i64))
.collect())
},
);
engine.register_get(
"tags",
|p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.tags
.iter()
.map(|tag| rhai::Dynamic::from(tag.clone()))
.collect())
},
);
engine.register_get(
"created_at",
|p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.base_data.created_at) },
);
engine.register_get(
"modified_at",
|p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.base_data.modified_at) },
);
engine.register_get(
"comments",
|p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.base_data
.comments
.iter()
.map(|&id| rhai::Dynamic::from(id as i64))
.collect())
},
);
engine.register_get(
"status",
|p: &mut Project| -> Result<Status, Box<EvalAltResult>> { Ok(p.status.clone()) },
);
engine.register_get(
"priority",
|p: &mut Project| -> Result<Priority, Box<EvalAltResult>> { Ok(p.priority.clone()) },
);
engine.register_get(
"item_type",
|p: &mut Project| -> Result<ItemType, Box<EvalAltResult>> { Ok(p.item_type.clone()) },
);
// Builder methods for Project // Builder methods for Project
// let db_clone = db.clone(); // This was unused // let db_clone = db.clone(); // This was unused
engine.register_fn("name", |p: Project, name: String| -> Result<Project, Box<EvalAltResult>> { Ok(p.name(name)) }); engine.register_fn(
engine.register_fn("description", |p: Project, description: String| -> Result<Project, Box<EvalAltResult>> { Ok(p.description(description)) }); "name",
engine.register_fn("owner_id", |p: Project, owner_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.owner_id(id_from_i64(owner_id_i64)?)) }); |p: Project, name: String| -> Result<Project, Box<EvalAltResult>> { Ok(p.name(name)) },
engine.register_fn("add_member_id", |p: Project, member_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_member_id(id_from_i64(member_id_i64)?)) }); );
engine.register_fn("member_ids", |p: Project, member_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> { engine.register_fn(
let ids = member_ids_i64 "description",
.into_iter() |p: Project, description: String| -> Result<Project, Box<EvalAltResult>> {
.map(|id_dyn: Dynamic| { Ok(p.description(description))
let val_i64 = id_dyn.clone().try_cast::<i64>().ok_or_else(|| { },
Box::new(EvalAltResult::ErrorMismatchDataType( );
"Expected integer for ID".to_string(), engine.register_fn(
id_dyn.type_name().to_string(), "owner_id",
Position::NONE, |p: Project, owner_id_i64: i64| -> Result<Project, Box<EvalAltResult>> {
)) Ok(p.owner_id(id_from_i64(owner_id_i64)?))
})?; },
id_from_i64(val_i64) );
}) engine.register_fn(
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?; "add_member_id",
Ok(p.member_ids(ids)) |p: Project, member_id_i64: i64| -> Result<Project, Box<EvalAltResult>> {
}); Ok(p.add_member_id(id_from_i64(member_id_i64)?))
engine.register_fn("add_board_id", |p: Project, board_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_board_id(id_from_i64(board_id_i64)?)) }); },
engine.register_fn("board_ids", |p: Project, board_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> { );
let ids = board_ids_i64 engine.register_fn(
.into_iter() "member_ids",
.map(|id_dyn: Dynamic| { |p: Project, member_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let val_i64 = id_dyn.clone().try_cast::<i64>().ok_or_else(|| { let ids = member_ids_i64
Box::new(EvalAltResult::ErrorMismatchDataType( .into_iter()
"Expected integer for ID".to_string(), .map(|id_dyn: Dynamic| {
id_dyn.type_name().to_string(), let val_i64 = id_dyn.clone().try_cast::<i64>().ok_or_else(|| {
Position::NONE, Box::new(EvalAltResult::ErrorMismatchDataType(
)) "Expected integer for ID".to_string(),
})?; id_dyn.type_name().to_string(),
id_from_i64(val_i64) Position::NONE,
}) ))
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?; })?;
Ok(p.board_ids(ids)) id_from_i64(val_i64)
});
engine.register_fn("add_sprint_id", |p: Project, sprint_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_sprint_id(id_from_i64(sprint_id_i64)?)) });
engine.register_fn("sprint_ids", |p: Project, sprint_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = sprint_ids_i64
.into_iter()
.map(|id_dyn: Dynamic| {
let val_i64 = id_dyn.clone().try_cast::<i64>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected integer for ID".to_string(),
id_dyn.type_name().to_string(),
Position::NONE,
))
})?;
id_from_i64(val_i64)
})
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.sprint_ids(ids))
});
engine.register_fn("add_epic_id", |p: Project, epic_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_epic_id(id_from_i64(epic_id_i64)?)) });
engine.register_fn("epic_ids", |p: Project, epic_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = epic_ids_i64
.into_iter()
.map(|id_dyn: Dynamic| {
let val_i64 = id_dyn.clone().try_cast::<i64>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected integer for ID".to_string(),
id_dyn.type_name().to_string(),
Position::NONE,
))
})?;
id_from_i64(val_i64)
})
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.epic_ids(ids))
});
engine.register_fn("add_tag", |p: Project, tag: String| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_tag(tag)) });
engine.register_fn("tags", |p: Project, tags_dyn: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let tags_vec = tags_dyn
.into_iter()
.map(|tag_dyn: Dynamic| {
tag_dyn.clone().into_string().map_err(|_err| { // _err is Rhai's internal error, we create a new one
Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected string for tag".to_string(),
tag_dyn.type_name().to_string(),
Position::NONE,
))
}) })
}) .collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
.collect::<Result<Vec<String>, Box<EvalAltResult>>>()?; Ok(p.member_ids(ids))
Ok(p.tags(tags_vec)) },
}); );
engine.register_fn(
"add_board_id",
|p: Project, board_id_i64: i64| -> Result<Project, Box<EvalAltResult>> {
Ok(p.add_board_id(id_from_i64(board_id_i64)?))
},
);
engine.register_fn(
"board_ids",
|p: Project, board_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = board_ids_i64
.into_iter()
.map(|id_dyn: Dynamic| {
let val_i64 = id_dyn.clone().try_cast::<i64>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected integer for ID".to_string(),
id_dyn.type_name().to_string(),
Position::NONE,
))
})?;
id_from_i64(val_i64)
})
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.board_ids(ids))
},
);
engine.register_fn(
"add_sprint_id",
|p: Project, sprint_id_i64: i64| -> Result<Project, Box<EvalAltResult>> {
Ok(p.add_sprint_id(id_from_i64(sprint_id_i64)?))
},
);
engine.register_fn(
"sprint_ids",
|p: Project, sprint_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = sprint_ids_i64
.into_iter()
.map(|id_dyn: Dynamic| {
let val_i64 = id_dyn.clone().try_cast::<i64>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected integer for ID".to_string(),
id_dyn.type_name().to_string(),
Position::NONE,
))
})?;
id_from_i64(val_i64)
})
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.sprint_ids(ids))
},
);
engine.register_fn(
"add_epic_id",
|p: Project, epic_id_i64: i64| -> Result<Project, Box<EvalAltResult>> {
Ok(p.add_epic_id(id_from_i64(epic_id_i64)?))
},
);
engine.register_fn(
"epic_ids",
|p: Project, epic_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = epic_ids_i64
.into_iter()
.map(|id_dyn: Dynamic| {
let val_i64 = id_dyn.clone().try_cast::<i64>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected integer for ID".to_string(),
id_dyn.type_name().to_string(),
Position::NONE,
))
})?;
id_from_i64(val_i64)
})
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.epic_ids(ids))
},
);
engine.register_fn(
"add_tag",
|p: Project, tag: String| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_tag(tag)) },
);
engine.register_fn(
"tags",
|p: Project, tags_dyn: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let tags_vec = tags_dyn
.into_iter()
.map(|tag_dyn: Dynamic| {
tag_dyn.clone().into_string().map_err(|_err| {
// _err is Rhai's internal error, we create a new one
Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected string for tag".to_string(),
tag_dyn.type_name().to_string(),
Position::NONE,
))
})
})
.collect::<Result<Vec<String>, Box<EvalAltResult>>>()?;
Ok(p.tags(tags_vec))
},
);
engine.register_fn("status", |p: Project, status: Status| -> Result<Project, Box<EvalAltResult>> { Ok(p.status(status)) }); engine.register_fn(
engine.register_fn("priority", |p: Project, priority: Priority| -> Result<Project, Box<EvalAltResult>> { Ok(p.priority(priority)) }); "status",
engine.register_fn("item_type", |p: Project, item_type: ItemType| -> Result<Project, Box<EvalAltResult>> { Ok(p.item_type(item_type)) }); |p: Project, status: Status| -> Result<Project, Box<EvalAltResult>> {
Ok(p.status(status))
},
);
engine.register_fn(
"priority",
|p: Project, priority: Priority| -> Result<Project, Box<EvalAltResult>> {
Ok(p.priority(priority))
},
);
engine.register_fn(
"item_type",
|p: Project, item_type: ItemType| -> Result<Project, Box<EvalAltResult>> {
Ok(p.item_type(item_type))
},
);
// Base ModelData builders // Base ModelData builders
engine.register_fn("add_base_comment", |p: Project, comment_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_base_comment(id_from_i64(comment_id_i64)?)) }); engine.register_fn(
"add_base_comment",
|p: Project, comment_id_i64: i64| -> Result<Project, Box<EvalAltResult>> {
Ok(p.add_base_comment(id_from_i64(comment_id_i64)?))
},
);
// --- Database Interaction Functions --- // --- Database Interaction Functions ---
let db_clone_set = db.clone(); let db_clone_set = db.clone();
engine.register_fn("set_project", move |project: Project| -> Result<(), Box<EvalAltResult>> { engine.register_fn(
let collection = db_clone_set.collection::<Project>().map_err(|e| { "set_project",
Box::new(EvalAltResult::ErrorSystem( move |project: Project| -> Result<(), Box<EvalAltResult>> {
"Failed to access project collection".to_string(), let collection = db_clone_set.collection::<Project>().map_err(|e| {
format!("DB operation failed: {:?}", e).into(), Box::new(EvalAltResult::ErrorSystem(
)) "Failed to access project collection".to_string(),
})?; format!("DB operation failed: {:?}", e).into(),
))
})?;
collection.set(&project).map(|_| ()).map_err(|e| { collection.set(&project).map(|_| ()).map_err(|e| {
Box::new(EvalAltResult::ErrorSystem( Box::new(EvalAltResult::ErrorSystem(
"Failed to save project".to_string(), "Failed to save project".to_string(),
format!("DB operation failed: {:?}", e).into(), format!("DB operation failed: {:?}", e).into(),
)) ))
}) })
}); },
);
let db_clone_get = db.clone(); let db_clone_get = db.clone();
engine.register_fn("get_project_by_id", move |id_i64: i64| -> Result<Dynamic, Box<EvalAltResult>> { engine.register_fn(
let id = id_from_i64(id_i64)?; "get_project_by_id",
let collection = db_clone_get.collection::<Project>().map_err(|e| { move |id_i64: i64| -> Result<Dynamic, Box<EvalAltResult>> {
Box::new(EvalAltResult::ErrorSystem( let id = id_from_i64(id_i64)?;
"Failed to access project collection".to_string(), let collection = db_clone_get.collection::<Project>().map_err(|e| {
format!("DB operation failed: {:?}", e).into(), Box::new(EvalAltResult::ErrorSystem(
)) "Failed to access project collection".to_string(),
})?; format!("DB operation failed: {:?}", e).into(),
))
})?;
match collection.get_by_id(id) { match collection.get_by_id(id) {
Ok(Some(project)) => Ok(Dynamic::from(project)), Ok(Some(project)) => Ok(Dynamic::from(project)),
Ok(None) => Ok(Dynamic::UNIT), // Represents '()' in Rhai Ok(None) => Ok(Dynamic::UNIT), // Represents '()' in Rhai
Err(e) => Err(Box::new(EvalAltResult::ErrorSystem( Err(e) => Err(Box::new(EvalAltResult::ErrorSystem(
"Failed to retrieve project by ID".to_string(), "Failed to retrieve project by ID".to_string(),
format!("DB operation failed: {:?}", e).into(), format!("DB operation failed: {:?}", e).into(),
))), ))),
} }
}); },
);
// TODO: Register Rhai bindings for the `Label` model if needed, or remove unused import. // TODO: Register Rhai bindings for the `Label` model if needed, or remove unused import.
// Register Label type and its methods/getters // Register Label type and its methods/getters

View File

@ -3,8 +3,8 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use heromodels_core::BaseModelData; use heromodels_core::BaseModelData;
use heromodels_derive::model; use heromodels_derive::model;
use serde::{Deserialize, Serialize};
use rhai::{CustomType, TypeBuilder}; use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
use super::sprint_enums::SprintStatus; // Import our new enum use super::sprint_enums::SprintStatus; // Import our new enum
@ -16,14 +16,14 @@ pub struct Sprint {
pub description: Option<String>, pub description: Option<String>,
pub status: SprintStatus, pub status: SprintStatus,
pub goal: Option<String>, // Sprint goal pub goal: Option<String>, // Sprint goal
pub project_id: Option<u32>, // Link to a general project/board pub project_id: Option<u32>, // Link to a general project/board
pub start_date: Option<DateTime<Utc>>, pub start_date: Option<DateTime<Utc>>,
pub end_date: Option<DateTime<Utc>>, // Changed from due_date for sprints pub end_date: Option<DateTime<Utc>>, // Changed from due_date for sprints
// Explicitly list task IDs belonging to this sprint // Explicitly list task IDs belonging to this sprint
pub task_ids: Vec<u32>, pub task_ids: Vec<u32>,
} }
impl Sprint { impl Sprint {

View File

@ -3,10 +3,10 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use heromodels_core::BaseModelData; use heromodels_core::BaseModelData;
use heromodels_derive::model; use heromodels_derive::model;
use serde::{Deserialize, Serialize}; use rhai::{CustomType, TypeBuilder};
use rhai::{CustomType, TypeBuilder}; // Assuming rhai might be used 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)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
#[model] // This will provide id, created_at, updated_at via base_data #[model] // This will provide id, created_at, updated_at via base_data
@ -16,10 +16,10 @@ pub struct Task {
pub description: Option<String>, pub description: Option<String>,
pub status: TaskStatus, pub status: TaskStatus,
pub priority: TaskPriority, pub priority: TaskPriority,
pub assignee_id: Option<u32>, // User ID pub assignee_id: Option<u32>, // User ID
pub reporter_id: Option<u32>, // User ID pub reporter_id: Option<u32>, // User ID
pub parent_task_id: Option<u32>, // For subtasks pub parent_task_id: Option<u32>, // For subtasks
pub epic_id: Option<u32>, pub epic_id: Option<u32>,
pub sprint_id: Option<u32>, pub sprint_id: Option<u32>,

View File

@ -2,4 +2,4 @@
pub mod user; pub mod user;
// Re-export User for convenience // Re-export User for convenience
pub use user::User; pub use user::User;

View File

@ -15,9 +15,9 @@ rand = "0.8.5"
criterion = "0.5.1" criterion = "0.5.1"
tempfile = "3.8.0" tempfile = "3.8.0"
[[bench]] # [[bench]]
name = "ourdb_benchmarks" # name = "ourdb_benchmarks"
harness = false # harness = false
[[example]] [[example]]
name = "basic_usage" name = "basic_usage"

View File

@ -6,18 +6,18 @@ fn main() -> Result<(), ourdb::Error> {
// Create a temporary directory for the database // Create a temporary directory for the database
let db_path = std::env::temp_dir().join("ourdb_advanced_example"); let db_path = std::env::temp_dir().join("ourdb_advanced_example");
std::fs::create_dir_all(&db_path)?; std::fs::create_dir_all(&db_path)?;
println!("Creating database at: {}", db_path.display()); println!("Creating database at: {}", db_path.display());
// Demonstrate key-value mode (non-incremental) // Demonstrate key-value mode (non-incremental)
key_value_mode_example(&db_path)?; key_value_mode_example(&db_path)?;
// Demonstrate incremental mode // Demonstrate incremental mode
incremental_mode_example(&db_path)?; incremental_mode_example(&db_path)?;
// Demonstrate performance benchmarking // Demonstrate performance benchmarking
performance_benchmark(&db_path)?; performance_benchmark(&db_path)?;
// Clean up (optional) // Clean up (optional)
if std::env::var("KEEP_DB").is_err() { if std::env::var("KEEP_DB").is_err() {
std::fs::remove_dir_all(&db_path)?; std::fs::remove_dir_all(&db_path)?;
@ -25,16 +25,16 @@ fn main() -> Result<(), ourdb::Error> {
} else { } else {
println!("Database kept at: {}", db_path.display()); println!("Database kept at: {}", db_path.display());
} }
Ok(()) Ok(())
} }
fn key_value_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> { fn key_value_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> {
println!("\n=== Key-Value Mode Example ==="); println!("\n=== Key-Value Mode Example ===");
let db_path = base_path.join("key_value"); let db_path = base_path.join("key_value");
std::fs::create_dir_all(&db_path)?; std::fs::create_dir_all(&db_path)?;
// Create a new database with key-value mode (non-incremental) // Create a new database with key-value mode (non-incremental)
let config = OurDBConfig { let config = OurDBConfig {
path: db_path, 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 keysize: Some(2), // Small key size for demonstration
reset: None, // Don't reset existing database reset: None, // Don't reset existing database
}; };
let mut db = OurDB::new(config)?; let mut db = OurDB::new(config)?;
// In key-value mode, we must provide IDs explicitly // In key-value mode, we must provide IDs explicitly
let custom_ids = [100, 200, 300, 400, 500]; let custom_ids = [100, 200, 300, 400, 500];
// Store data with custom IDs // Store data with custom IDs
for (i, &id) in custom_ids.iter().enumerate() { for (i, &id) in custom_ids.iter().enumerate() {
let data = format!("Record with custom ID {}", id); let data = format!("Record with custom ID {}", id);
db.set(OurDBSetArgs { id: Some(id), data: data.as_bytes() })?; db.set(OurDBSetArgs {
println!("Stored record {} with custom ID: {}", i+1, id); id: Some(id),
data: data.as_bytes(),
})?;
println!("Stored record {} with custom ID: {}", i + 1, id);
} }
// Retrieve data by custom IDs // Retrieve data by custom IDs
for &id in &custom_ids { for &id in &custom_ids {
let retrieved = db.get(id)?; 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 // Update and track history
let id_to_update = custom_ids[2]; // ID 300 let id_to_update = custom_ids[2]; // ID 300
for i in 1..=3 { for i in 1..=3 {
let updated_data = format!("Updated record {} (version {})", id_to_update, i); 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); println!("Updated ID {} (version {})", id_to_update, i);
} }
// Get history for the updated record // Get history for the updated record
let history = db.get_history(id_to_update, 5)?; let history = db.get_history(id_to_update, 5)?;
println!("History for ID {} (most recent first):", id_to_update); println!("History for ID {} (most recent first):", id_to_update);
for (i, entry) in history.iter().enumerate() { for (i, entry) in history.iter().enumerate() {
println!(" Version {}: {}", i, String::from_utf8_lossy(entry)); println!(" Version {}: {}", i, String::from_utf8_lossy(entry));
} }
db.close()?; db.close()?;
println!("Key-value mode example completed"); println!("Key-value mode example completed");
Ok(()) Ok(())
} }
fn incremental_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> { fn incremental_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> {
println!("\n=== Incremental Mode Example ==="); println!("\n=== Incremental Mode Example ===");
let db_path = base_path.join("incremental"); let db_path = base_path.join("incremental");
std::fs::create_dir_all(&db_path)?; std::fs::create_dir_all(&db_path)?;
// Create a new database with incremental mode // Create a new database with incremental mode
let config = OurDBConfig { let config = OurDBConfig {
path: db_path, path: db_path,
@ -97,42 +107,49 @@ fn incremental_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> {
keysize: Some(3), // 3-byte keys keysize: Some(3), // 3-byte keys
reset: None, // Don't reset existing database reset: None, // Don't reset existing database
}; };
let mut db = OurDB::new(config)?; let mut db = OurDB::new(config)?;
// In incremental mode, IDs are auto-generated // In incremental mode, IDs are auto-generated
let mut assigned_ids = Vec::new(); let mut assigned_ids = Vec::new();
// Store multiple records and collect assigned IDs // Store multiple records and collect assigned IDs
for i in 1..=5 { for i in 1..=5 {
let data = format!("Auto-increment record {}", i); 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); assigned_ids.push(id);
println!("Stored record {} with auto-assigned ID: {}", i, id); println!("Stored record {} with auto-assigned ID: {}", i, id);
} }
// Check next ID // Check next ID
let next_id = db.get_next_id()?; let next_id = db.get_next_id()?;
println!("Next ID to be assigned: {}", next_id); println!("Next ID to be assigned: {}", next_id);
// Retrieve all records // Retrieve all records
for &id in &assigned_ids { for &id in &assigned_ids {
let retrieved = db.get(id)?; 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()?; db.close()?;
println!("Incremental mode example completed"); println!("Incremental mode example completed");
Ok(()) Ok(())
} }
fn performance_benchmark(base_path: &PathBuf) -> Result<(), ourdb::Error> { fn performance_benchmark(base_path: &PathBuf) -> Result<(), ourdb::Error> {
println!("\n=== Performance Benchmark ==="); println!("\n=== Performance Benchmark ===");
let db_path = base_path.join("benchmark"); let db_path = base_path.join("benchmark");
std::fs::create_dir_all(&db_path)?; std::fs::create_dir_all(&db_path)?;
// Create a new database // Create a new database
let config = OurDBConfig { let config = OurDBConfig {
path: db_path, path: db_path,
@ -141,62 +158,74 @@ fn performance_benchmark(base_path: &PathBuf) -> Result<(), ourdb::Error> {
keysize: Some(4), // 4-byte keys keysize: Some(4), // 4-byte keys
reset: None, // Don't reset existing database reset: None, // Don't reset existing database
}; };
let mut db = OurDB::new(config)?; let mut db = OurDB::new(config)?;
// Number of operations for the benchmark // Number of operations for the benchmark
let num_operations = 1000; let num_operations = 1000;
let data_size = 100; // bytes per record let data_size = 100; // bytes per record
// Prepare test data // Prepare test data
let test_data = vec![b'A'; data_size]; let test_data = vec![b'A'; data_size];
// Benchmark write operations // Benchmark write operations
println!("Benchmarking {} write operations...", num_operations); println!("Benchmarking {} write operations...", num_operations);
let start = Instant::now(); let start = Instant::now();
let mut ids = Vec::with_capacity(num_operations); let mut ids = Vec::with_capacity(num_operations);
for _ in 0..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); ids.push(id);
} }
let write_duration = start.elapsed(); let write_duration = start.elapsed();
let writes_per_second = num_operations as f64 / write_duration.as_secs_f64(); let writes_per_second = num_operations as f64 / write_duration.as_secs_f64();
println!("Write performance: {:.2} ops/sec ({:.2} ms/op)", println!(
writes_per_second, "Write performance: {:.2} ops/sec ({:.2} ms/op)",
write_duration.as_secs_f64() * 1000.0 / num_operations as f64); writes_per_second,
write_duration.as_secs_f64() * 1000.0 / num_operations as f64
);
// Benchmark read operations // Benchmark read operations
println!("Benchmarking {} read operations...", num_operations); println!("Benchmarking {} read operations...", num_operations);
let start = Instant::now(); let start = Instant::now();
for &id in &ids { for &id in &ids {
let _ = db.get(id)?; let _ = db.get(id)?;
} }
let read_duration = start.elapsed(); let read_duration = start.elapsed();
let reads_per_second = num_operations as f64 / read_duration.as_secs_f64(); let reads_per_second = num_operations as f64 / read_duration.as_secs_f64();
println!("Read performance: {:.2} ops/sec ({:.2} ms/op)", println!(
reads_per_second, "Read performance: {:.2} ops/sec ({:.2} ms/op)",
read_duration.as_secs_f64() * 1000.0 / num_operations as f64); reads_per_second,
read_duration.as_secs_f64() * 1000.0 / num_operations as f64
);
// Benchmark update operations // Benchmark update operations
println!("Benchmarking {} update operations...", num_operations); println!("Benchmarking {} update operations...", num_operations);
let start = Instant::now(); let start = Instant::now();
for &id in &ids { 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 update_duration = start.elapsed();
let updates_per_second = num_operations as f64 / update_duration.as_secs_f64(); let updates_per_second = num_operations as f64 / update_duration.as_secs_f64();
println!("Update performance: {:.2} ops/sec ({:.2} ms/op)", println!(
updates_per_second, "Update performance: {:.2} ops/sec ({:.2} ms/op)",
update_duration.as_secs_f64() * 1000.0 / num_operations as f64); updates_per_second,
update_duration.as_secs_f64() * 1000.0 / num_operations as f64
);
db.close()?; db.close()?;
println!("Performance benchmark completed"); println!("Performance benchmark completed");
Ok(()) Ok(())
} }

View File

@ -4,9 +4,9 @@ fn main() -> Result<(), ourdb::Error> {
// Create a temporary directory for the database // Create a temporary directory for the database
let db_path = std::env::temp_dir().join("ourdb_example"); let db_path = std::env::temp_dir().join("ourdb_example");
std::fs::create_dir_all(&db_path)?; std::fs::create_dir_all(&db_path)?;
println!("Creating database at: {}", db_path.display()); println!("Creating database at: {}", db_path.display());
// Create a new database with incremental mode enabled // Create a new database with incremental mode enabled
let config = OurDBConfig { let config = OurDBConfig {
path: db_path.clone(), path: db_path.clone(),
@ -15,51 +15,68 @@ fn main() -> Result<(), ourdb::Error> {
keysize: None, // Use default (4 bytes) keysize: None, // Use default (4 bytes)
reset: None, // Don't reset existing database reset: None, // Don't reset existing database
}; };
let mut db = OurDB::new(config)?; let mut db = OurDB::new(config)?;
// Store some data with auto-generated IDs // Store some data with auto-generated IDs
let data1 = b"First record"; 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); println!("Stored first record with ID: {}", id1);
let data2 = b"Second record"; 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); println!("Stored second record with ID: {}", id2);
// Retrieve and print the data // Retrieve and print the data
let retrieved1 = db.get(id1)?; 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)?; 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 // Update a record to demonstrate history tracking
let updated_data = b"Updated first record"; 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); println!("Updated record with ID: {}", id1);
// Get history for the updated record // Get history for the updated record
let history = db.get_history(id1, 2)?; let history = db.get_history(id1, 2)?;
println!("History for ID {}:", id1); println!("History for ID {}:", id1);
for (i, entry) in history.iter().enumerate() { for (i, entry) in history.iter().enumerate() {
println!(" Version {}: {}", i, String::from_utf8_lossy(entry)); println!(" Version {}: {}", i, String::from_utf8_lossy(entry));
} }
// Delete a record // Delete a record
db.delete(id2)?; db.delete(id2)?;
println!("Deleted record with ID: {}", id2); println!("Deleted record with ID: {}", id2);
// Verify deletion // Verify deletion
match db.get(id2) { match db.get(id2) {
Ok(_) => println!("Record still exists (unexpected)"), Ok(_) => println!("Record still exists (unexpected)"),
Err(e) => println!("Verified deletion: {}", e), Err(e) => println!("Verified deletion: {}", e),
} }
// Close the database // Close the database
db.close()?; db.close()?;
println!("Database closed successfully"); println!("Database closed successfully");
// Clean up (optional) // Clean up (optional)
if std::env::var("KEEP_DB").is_err() { if std::env::var("KEEP_DB").is_err() {
std::fs::remove_dir_all(&db_path)?; std::fs::remove_dir_all(&db_path)?;
@ -67,6 +84,6 @@ fn main() -> Result<(), ourdb::Error> {
} else { } else {
println!("Database kept at: {}", db_path.display()); println!("Database kept at: {}", db_path.display());
} }
Ok(()) Ok(())
} }

View File

@ -4,12 +4,12 @@ use std::time::Instant;
fn main() -> Result<(), ourdb::Error> { fn main() -> Result<(), ourdb::Error> {
// Parse command-line arguments // Parse command-line arguments
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
// Default values // Default values
let mut incremental_mode = true; let mut incremental_mode = true;
let mut keysize: u8 = 4; let mut keysize: u8 = 4;
let mut num_operations = 10000; let mut num_operations = 10000;
// Parse arguments // Parse arguments
for i in 1..args.len() { for i in 1..args.len() {
if args[i] == "--no-incremental" { if args[i] == "--no-incremental" {
@ -20,13 +20,13 @@ fn main() -> Result<(), ourdb::Error> {
num_operations = args[i + 1].parse().unwrap_or(10000); num_operations = args[i + 1].parse().unwrap_or(10000);
} }
} }
// Create a temporary directory for the database // Create a temporary directory for the database
let db_path = std::env::temp_dir().join("ourdb_benchmark"); let db_path = std::env::temp_dir().join("ourdb_benchmark");
std::fs::create_dir_all(&db_path)?; std::fs::create_dir_all(&db_path)?;
println!("Database path: {}", db_path.display()); println!("Database path: {}", db_path.display());
// Create a new database // Create a new database
let config = OurDBConfig { let config = OurDBConfig {
path: db_path.clone(), path: db_path.clone(),
@ -35,73 +35,90 @@ fn main() -> Result<(), ourdb::Error> {
keysize: Some(keysize), keysize: Some(keysize),
reset: Some(true), // Reset the database for benchmarking reset: Some(true), // Reset the database for benchmarking
}; };
let mut db = OurDB::new(config)?; let mut db = OurDB::new(config)?;
// Prepare test data (100 bytes per record) // Prepare test data (100 bytes per record)
let test_data = vec![b'A'; 100]; let test_data = vec![b'A'; 100];
// Benchmark write operations // Benchmark write operations
println!("Benchmarking {} write operations (incremental: {}, keysize: {})...", println!(
num_operations, incremental_mode, keysize); "Benchmarking {} write operations (incremental: {}, keysize: {})...",
num_operations, incremental_mode, keysize
);
let start = Instant::now(); let start = Instant::now();
let mut ids = Vec::with_capacity(num_operations); let mut ids = Vec::with_capacity(num_operations);
for _ in 0..num_operations { for _ in 0..num_operations {
let id = if incremental_mode { let id = if incremental_mode {
db.set(OurDBSetArgs { id: None, data: &test_data })? db.set(OurDBSetArgs {
id: None,
data: &test_data,
})?
} else { } else {
// In non-incremental mode, we need to provide IDs // In non-incremental mode, we need to provide IDs
let id = ids.len() as u32 + 1; 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 id
}; };
ids.push(id); ids.push(id);
} }
let write_duration = start.elapsed(); let write_duration = start.elapsed();
let writes_per_second = num_operations as f64 / write_duration.as_secs_f64(); let writes_per_second = num_operations as f64 / write_duration.as_secs_f64();
println!("Write performance: {:.2} ops/sec ({:.2} ms/op)", println!(
writes_per_second, "Write performance: {:.2} ops/sec ({:.2} ms/op)",
write_duration.as_secs_f64() * 1000.0 / num_operations as f64); writes_per_second,
write_duration.as_secs_f64() * 1000.0 / num_operations as f64
);
// Benchmark read operations // Benchmark read operations
println!("Benchmarking {} read operations...", num_operations); println!("Benchmarking {} read operations...", num_operations);
let start = Instant::now(); let start = Instant::now();
for &id in &ids { for &id in &ids {
let _ = db.get(id)?; let _ = db.get(id)?;
} }
let read_duration = start.elapsed(); let read_duration = start.elapsed();
let reads_per_second = num_operations as f64 / read_duration.as_secs_f64(); let reads_per_second = num_operations as f64 / read_duration.as_secs_f64();
println!("Read performance: {:.2} ops/sec ({:.2} ms/op)", println!(
reads_per_second, "Read performance: {:.2} ops/sec ({:.2} ms/op)",
read_duration.as_secs_f64() * 1000.0 / num_operations as f64); reads_per_second,
read_duration.as_secs_f64() * 1000.0 / num_operations as f64
);
// Benchmark update operations // Benchmark update operations
println!("Benchmarking {} update operations...", num_operations); println!("Benchmarking {} update operations...", num_operations);
let start = Instant::now(); let start = Instant::now();
for &id in &ids { 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 update_duration = start.elapsed();
let updates_per_second = num_operations as f64 / update_duration.as_secs_f64(); let updates_per_second = num_operations as f64 / update_duration.as_secs_f64();
println!("Update performance: {:.2} ops/sec ({:.2} ms/op)", println!(
updates_per_second, "Update performance: {:.2} ops/sec ({:.2} ms/op)",
update_duration.as_secs_f64() * 1000.0 / num_operations as f64); updates_per_second,
update_duration.as_secs_f64() * 1000.0 / num_operations as f64
);
// Clean up // Clean up
db.close()?; db.close()?;
std::fs::remove_dir_all(&db_path)?; std::fs::remove_dir_all(&db_path)?;
Ok(()) Ok(())
} }

View File

@ -13,9 +13,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.as_secs(); .as_secs();
let db_path = temp_dir().join(format!("ourdb_example_{}", timestamp)); let db_path = temp_dir().join(format!("ourdb_example_{}", timestamp));
std::fs::create_dir_all(&db_path)?; std::fs::create_dir_all(&db_path)?;
println!("Creating database at: {}", db_path.display()); println!("Creating database at: {}", db_path.display());
// Create a new OurDB instance // Create a new OurDB instance
let config = OurDBConfig { let config = OurDBConfig {
path: db_path.clone(), path: db_path.clone(),
@ -24,51 +24,60 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
keysize: None, keysize: None,
reset: Some(false), reset: Some(false),
}; };
let mut db = OurDB::new(config)?; let mut db = OurDB::new(config)?;
println!("Database created successfully"); println!("Database created successfully");
// Store some data // Store some data
let test_data = b"Hello, OurDB!"; 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); println!("\nStored data with ID: {}", id);
// Retrieve the data // Retrieve the data
let retrieved = db.get(id)?; let retrieved = db.get(id)?;
println!("Retrieved data: {}", String::from_utf8_lossy(&retrieved)); println!("Retrieved data: {}", String::from_utf8_lossy(&retrieved));
// Update the data // Update the data
let updated_data = b"Updated data in OurDB!"; 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); println!("\nUpdated data with ID: {}", id);
// Retrieve the updated data // Retrieve the updated data
let retrieved = db.get(id)?; 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 // Get history
let history = db.get_history(id, 2)?; let history = db.get_history(id, 2)?;
println!("\nHistory for ID {}:", id); println!("\nHistory for ID {}:", id);
for (i, data) in history.iter().enumerate() { for (i, data) in history.iter().enumerate() {
println!(" Version {}: {}", i + 1, String::from_utf8_lossy(data)); println!(" Version {}: {}", i + 1, String::from_utf8_lossy(data));
} }
// Delete the data // Delete the data
db.delete(id)?; db.delete(id)?;
println!("\nDeleted data with ID: {}", id); println!("\nDeleted data with ID: {}", id);
// Try to retrieve the deleted data (should fail) // Try to retrieve the deleted data (should fail)
match db.get(id) { match db.get(id) {
Ok(_) => println!("Data still exists (unexpected)"), Ok(_) => println!("Data still exists (unexpected)"),
Err(e) => println!("Verified deletion: {}", e), Err(e) => println!("Verified deletion: {}", e),
} }
println!("\nExample completed successfully!"); println!("\nExample completed successfully!");
// Clean up // Clean up
db.close()?; db.close()?;
std::fs::remove_dir_all(&db_path)?; std::fs::remove_dir_all(&db_path)?;
println!("Cleaned up database directory"); println!("Cleaned up database directory");
Ok(()) Ok(())
} }

View File

@ -1,7 +1,6 @@
use std::fs::{self, File, OpenOptions}; use std::fs::{self, File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write}; use std::io::{Read, Seek, SeekFrom, Write};
use crc32fast::Hasher; use crc32fast::Hasher;
use crate::error::Error; use crate::error::Error;
@ -27,11 +26,8 @@ impl OurDB {
} }
// Open the file fresh // Open the file fresh
let file = OpenOptions::new() let file = OpenOptions::new().read(true).write(true).open(&path)?;
.read(true)
.write(true)
.open(&path)?;
self.file = Some(file); self.file = Some(file);
self.file_nr = file_nr; 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> { 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 new_file_path = self.path.join(format!("{}.db", file_nr));
let mut file = File::create(&new_file_path)?; let mut file = File::create(&new_file_path)?;
// Write a single byte to make all positions start from 1 // Write a single byte to make all positions start from 1
file.write_all(&[0u8])?; file.write_all(&[0u8])?;
Ok(()) Ok(())
} }
@ -54,17 +50,17 @@ impl OurDB {
// For keysize 2, 3, or 4, we can only use file_nr 0 // For keysize 2, 3, or 4, we can only use file_nr 0
if self.lookup.keysize() <= 4 { if self.lookup.keysize() <= 4 {
let path = self.path.join("0.db"); let path = self.path.join("0.db");
if !path.exists() { if !path.exists() {
self.create_new_db_file(0)?; self.create_new_db_file(0)?;
} }
return Ok(0); return Ok(0);
} }
// For keysize 6, we can use multiple files // For keysize 6, we can use multiple files
let path = self.path.join(format!("{}.db", self.last_used_file_nr)); let path = self.path.join(format!("{}.db", self.last_used_file_nr));
if !path.exists() { if !path.exists() {
self.create_new_db_file(self.last_used_file_nr)?; self.create_new_db_file(self.last_used_file_nr)?;
return Ok(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 /// 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) // Validate data size - maximum is u16::MAX (65535 bytes or ~64KB)
if data.len() > u16::MAX as usize { if data.len() > u16::MAX as usize {
return Err(Error::InvalidOperation( return Err(Error::InvalidOperation(format!(
format!("Data size exceeds maximum allowed size of {} bytes", u16::MAX) "Data size exceeds maximum allowed size of {} bytes",
)); u16::MAX
)));
} }
// Get file number to use // Get file number to use
let file_nr = self.get_file_nr()?; let file_nr = self.get_file_nr()?;
// Select the file // Select the file
self.db_file_select(file_nr)?; self.db_file_select(file_nr)?;
// Get current file position for lookup // 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))?; file.seek(SeekFrom::End(0))?;
let position = file.stream_position()? as u32; let position = file.stream_position()? as u32;
// Create new location // Create new location
let new_location = Location { let new_location = Location { file_nr, position };
file_nr,
position,
};
// Calculate CRC of data // Calculate CRC of data
let crc = calculate_crc(data); let crc = calculate_crc(data);
@ -144,13 +146,19 @@ impl OurDB {
/// Retrieves data at the specified location /// Retrieves data at the specified location
pub(crate) fn get_(&mut self, location: Location) -> Result<Vec<u8>, Error> { pub(crate) fn get_(&mut self, location: Location) -> Result<Vec<u8>, Error> {
if location.position == 0 { 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 // Select the file
self.db_file_select(location.file_nr)?; 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 // Read header
file.seek(SeekFrom::Start(location.position as u64))?; 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); let size = u16::from(header[0]) | (u16::from(header[1]) << 8);
// Parse CRC (4 bytes) // Parse CRC (4 bytes)
let stored_crc = u32::from(header[2]) let stored_crc = u32::from(header[2])
| (u32::from(header[3]) << 8) | (u32::from(header[3]) << 8)
| (u32::from(header[4]) << 16) | (u32::from(header[4]) << 16)
| (u32::from(header[5]) << 24); | (u32::from(header[5]) << 24);
// Read data // Read data
let mut data = vec![0u8; size as usize]; let mut data = vec![0u8; size as usize];
@ -173,7 +181,9 @@ impl OurDB {
// Verify CRC // Verify CRC
let calculated_crc = calculate_crc(&data); let calculated_crc = calculate_crc(&data);
if calculated_crc != stored_crc { 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) Ok(data)
@ -188,7 +198,10 @@ impl OurDB {
// Select the file // Select the file
self.db_file_select(location.file_nr)?; 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) // Skip size and CRC (6 bytes)
file.seek(SeekFrom::Start(location.position as u64 + 6))?; file.seek(SeekFrom::Start(location.position as u64 + 6))?;
@ -210,7 +223,10 @@ impl OurDB {
// Select the file // Select the file
self.db_file_select(location.file_nr)?; 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 // Read size first
file.seek(SeekFrom::Start(location.position as u64))?; file.seek(SeekFrom::Start(location.position as u64))?;
@ -240,7 +256,7 @@ impl OurDB {
for entry in fs::read_dir(&self.path)? { for entry in fs::read_dir(&self.path)? {
let entry = entry?; let entry = entry?;
let path = entry.path(); let path = entry.path();
if path.is_file() && path.extension().map_or(false, |ext| ext == "db") { if path.is_file() && path.extension().map_or(false, |ext| ext == "db") {
if let Some(stem) = path.file_stem() { if let Some(stem) = path.file_stem() {
if let Ok(file_nr) = stem.to_string_lossy().parse::<u16>() { if let Ok(file_nr) = stem.to_string_lossy().parse::<u16>() {
@ -254,42 +270,42 @@ impl OurDB {
for file_nr in file_numbers { for file_nr in file_numbers {
let src_path = self.path.join(format!("{}.db", file_nr)); let src_path = self.path.join(format!("{}.db", file_nr));
let temp_file_path = temp_path.join(format!("{}.db", file_nr)); let temp_file_path = temp_path.join(format!("{}.db", file_nr));
// Create new file // Create new file
let mut temp_file = File::create(&temp_file_path)?; let mut temp_file = File::create(&temp_file_path)?;
temp_file.write_all(&[0u8])?; // Initialize with a byte temp_file.write_all(&[0u8])?; // Initialize with a byte
// Open source file // Open source file
let mut src_file = File::open(&src_path)?; let mut src_file = File::open(&src_path)?;
// Read and process records // Read and process records
let mut buffer = vec![0u8; 1024]; // Read in chunks let mut buffer = vec![0u8; 1024]; // Read in chunks
let mut _position = 0; let mut _position = 0;
while let Ok(bytes_read) = src_file.read(&mut buffer) { while let Ok(bytes_read) = src_file.read(&mut buffer) {
if bytes_read == 0 { if bytes_read == 0 {
break; break;
} }
// Process the chunk // Process the chunk
// This is a simplified version - in a real implementation, // This is a simplified version - in a real implementation,
// you would need to handle records that span chunk boundaries // you would need to handle records that span chunk boundaries
_position += bytes_read; _position += bytes_read;
} }
// TODO: Implement proper record copying and position updating // TODO: Implement proper record copying and position updating
// This would involve: // This would involve:
// 1. Reading each record from the source file // 1. Reading each record from the source file
// 2. If not deleted (all zeros), copy to temp file // 2. If not deleted (all zeros), copy to temp file
// 3. Update lookup table with new positions // 3. Update lookup table with new positions
} }
// TODO: Replace original files with temp files // TODO: Replace original files with temp files
// Clean up // Clean up
fs::remove_dir_all(&temp_path)?; fs::remove_dir_all(&temp_path)?;
Ok(()) Ok(())
} }
} }
@ -304,7 +320,7 @@ fn calculate_crc(data: &[u8]) -> u32 {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::path::PathBuf; use std::path::PathBuf;
use crate::{OurDB, OurDBConfig, OurDBSetArgs}; use crate::{OurDB, OurDBConfig, OurDBSetArgs};
use std::env::temp_dir; use std::env::temp_dir;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
@ -320,26 +336,30 @@ mod tests {
#[test] #[test]
fn test_backend_operations() { fn test_backend_operations() {
let temp_dir = get_temp_dir(); let temp_dir = get_temp_dir();
let config = OurDBConfig { let config = OurDBConfig {
path: temp_dir.clone(), path: temp_dir.clone(),
incremental_mode: false, incremental_mode: false,
file_size: None, file_size: None,
keysize: None, keysize: None,
reset: None, // Don't reset existing database reset: None, // Don't reset existing database
}; };
let mut db = OurDB::new(config).unwrap(); let mut db = OurDB::new(config).unwrap();
// Test set and get // Test set and get
let test_data = b"Test data for backend operations"; let test_data = b"Test data for backend operations";
let id = 1; 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(); let retrieved = db.get(id).unwrap();
assert_eq!(retrieved, test_data); assert_eq!(retrieved, test_data);
// Clean up // Clean up
db.destroy().unwrap(); db.destroy().unwrap();
} }

View File

@ -6,23 +6,23 @@ pub enum Error {
/// IO errors from file operations /// IO errors from file operations
#[error("IO error: {0}")] #[error("IO error: {0}")]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
/// Data corruption errors /// Data corruption errors
#[error("Data corruption: {0}")] #[error("Data corruption: {0}")]
DataCorruption(String), DataCorruption(String),
/// Invalid operation errors /// Invalid operation errors
#[error("Invalid operation: {0}")] #[error("Invalid operation: {0}")]
InvalidOperation(String), InvalidOperation(String),
/// Lookup table errors /// Lookup table errors
#[error("Lookup error: {0}")] #[error("Lookup error: {0}")]
LookupError(String), LookupError(String),
/// Record not found errors /// Record not found errors
#[error("Record not found: {0}")] #[error("Record not found: {0}")]
NotFound(String), NotFound(String),
/// Other errors /// Other errors
#[error("Error: {0}")] #[error("Error: {0}")]
Other(String), Other(String),

View File

@ -1,7 +1,7 @@
mod backend;
mod error; mod error;
mod location; mod location;
mod lookup; mod lookup;
mod backend;
pub use error::Error; pub use error::Error;
pub use location::Location; pub use location::Location;
@ -62,7 +62,7 @@ impl OurDB {
if config.reset.unwrap_or(false) && config.path.exists() { if config.reset.unwrap_or(false) && config.path.exists() {
std::fs::remove_dir_all(&config.path)?; std::fs::remove_dir_all(&config.path)?;
} }
// Create directory if it doesn't exist // Create directory if it doesn't exist
std::fs::create_dir_all(&config.path)?; std::fs::create_dir_all(&config.path)?;
@ -96,11 +96,11 @@ impl OurDB {
} }
/// Sets a value in the database /// Sets a value in the database
/// ///
/// In incremental mode: /// In incremental mode:
/// - If ID is provided, it updates an existing record /// - If ID is provided, it updates an existing record
/// - If ID is not provided, it creates a new record with auto-generated ID /// - If ID is not provided, it creates a new record with auto-generated ID
/// ///
/// In key-value mode: /// In key-value mode:
/// - ID must be provided /// - ID must be provided
pub fn set(&mut self, args: OurDBSetArgs) -> Result<u32, Error> { pub fn set(&mut self, args: OurDBSetArgs) -> Result<u32, Error> {
@ -110,7 +110,7 @@ impl OurDB {
let location = self.lookup.get(id)?; let location = self.lookup.get(id)?;
if location.position == 0 { if location.position == 0 {
return Err(Error::InvalidOperation( 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 { } else {
// Using key-value mode // Using key-value mode
let id = args.id.ok_or_else(|| Error::InvalidOperation( let id = args.id.ok_or_else(|| {
"ID must be provided when incremental is disabled".to_string() Error::InvalidOperation(
))?; "ID must be provided when incremental is disabled".to_string(),
)
})?;
let location = self.lookup.get(id)?; let location = self.lookup.get(id)?;
self.set_(id, location, args.data)?; self.set_(id, location, args.data)?;
Ok(id) Ok(id)
@ -141,7 +143,7 @@ impl OurDB {
} }
/// Retrieves a list of previous values for the specified key /// Retrieves a list of previous values for the specified key
/// ///
/// The depth parameter controls how many historical values to retrieve (maximum) /// The depth parameter controls how many historical values to retrieve (maximum)
pub fn get_history(&mut self, id: u32, depth: u8) -> Result<Vec<Vec<u8>>, Error> { pub fn get_history(&mut self, id: u32, depth: u8) -> Result<Vec<Vec<u8>>, Error> {
let mut result = Vec::new(); let mut result = Vec::new();
@ -179,7 +181,9 @@ impl OurDB {
/// Returns the next ID which will be used when storing in incremental mode /// Returns the next ID which will be used when storing in incremental mode
pub fn get_next_id(&mut self) -> Result<u32, Error> { pub fn get_next_id(&mut self) -> Result<u32, Error> {
if !self.incremental_mode { 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() self.lookup.get_next_id()
} }
@ -212,7 +216,8 @@ impl OurDB {
} }
fn save(&mut self) -> Result<(), Error> { 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(()) Ok(())
} }
@ -238,41 +243,50 @@ mod tests {
#[test] #[test]
fn test_basic_operations() { fn test_basic_operations() {
let temp_dir = get_temp_dir(); let temp_dir = get_temp_dir();
let config = OurDBConfig { let config = OurDBConfig {
path: temp_dir.clone(), path: temp_dir.clone(),
incremental_mode: true, incremental_mode: true,
file_size: None, file_size: None,
keysize: None, keysize: None,
reset: None, // Don't reset existing database reset: None, // Don't reset existing database
}; };
let mut db = OurDB::new(config).unwrap(); let mut db = OurDB::new(config).unwrap();
// Test set and get // Test set and get
let test_data = b"Hello, OurDB!"; 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(); let retrieved = db.get(id).unwrap();
assert_eq!(retrieved, test_data); assert_eq!(retrieved, test_data);
// Test update // Test update
let updated_data = b"Updated data"; 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(); let retrieved = db.get(id).unwrap();
assert_eq!(retrieved, updated_data); assert_eq!(retrieved, updated_data);
// Test history // Test history
let history = db.get_history(id, 2).unwrap(); let history = db.get_history(id, 2).unwrap();
assert_eq!(history.len(), 2); assert_eq!(history.len(), 2);
assert_eq!(history[0], updated_data); assert_eq!(history[0], updated_data);
assert_eq!(history[1], test_data); assert_eq!(history[1], test_data);
// Test delete // Test delete
db.delete(id).unwrap(); db.delete(id).unwrap();
assert!(db.get(id).is_err()); assert!(db.get(id).is_err());
// Clean up // Clean up
db.destroy().unwrap(); db.destroy().unwrap();
} }

View File

@ -1,7 +1,7 @@
use crate::error::Error; use crate::error::Error;
/// Location represents a physical position in a database file /// Location represents a physical position in a database file
/// ///
/// It consists of a file number and a position within that file. /// It consists of a file number and a position within that file.
/// This allows OurDB to span multiple files for large datasets. /// This allows OurDB to span multiple files for large datasets.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
@ -14,7 +14,7 @@ pub struct Location {
impl Location { impl Location {
/// Creates a new Location from bytes based on keysize /// Creates a new Location from bytes based on keysize
/// ///
/// - keysize = 2: Only position (2 bytes), file_nr = 0 /// - keysize = 2: Only position (2 bytes), file_nr = 0
/// - keysize = 3: Only position (3 bytes), file_nr = 0 /// - keysize = 3: Only position (3 bytes), file_nr = 0
/// - keysize = 4: Only position (4 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<Self, Error> { pub fn from_bytes(bytes: &[u8], keysize: u8) -> Result<Self, Error> {
// Validate keysize // Validate keysize
if ![2, 3, 4, 6].contains(&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 // Create padded bytes
let mut padded = vec![0u8; keysize as usize]; let mut padded = vec![0u8; keysize as usize];
if bytes.len() > 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(); let start_idx = keysize as usize - bytes.len();
@ -49,34 +54,39 @@ impl Location {
// Verify limits // Verify limits
if location.position > 0xFFFF { if location.position > 0xFFFF {
return Err(Error::InvalidOperation( 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 => { 3 => {
// Only position, 3 bytes big endian // 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; location.file_nr = 0;
// Verify limits // Verify limits
if location.position > 0xFFFFFF { if location.position > 0xFFFFFF {
return Err(Error::InvalidOperation( 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 => { 4 => {
// Only position, 4 bytes big endian // Only position, 4 bytes big endian
location.position = u32::from(padded[0]) << 24 | u32::from(padded[1]) << 16 location.position = u32::from(padded[0]) << 24
| u32::from(padded[2]) << 8 | u32::from(padded[3]); | u32::from(padded[1]) << 16
| u32::from(padded[2]) << 8
| u32::from(padded[3]);
location.file_nr = 0; location.file_nr = 0;
}, }
6 => { 6 => {
// 2 bytes file_nr + 4 bytes position, all big endian // 2 bytes file_nr + 4 bytes position, all big endian
location.file_nr = u16::from(padded[0]) << 8 | u16::from(padded[1]); location.file_nr = u16::from(padded[0]) << 8 | u16::from(padded[1]);
location.position = u32::from(padded[2]) << 24 | u32::from(padded[3]) << 16 location.position = u32::from(padded[2]) << 24
| u32::from(padded[4]) << 8 | u32::from(padded[5]); | u32::from(padded[3]) << 16
}, | u32::from(padded[4]) << 8
| u32::from(padded[5]);
}
_ => unreachable!(), _ => unreachable!(),
} }
@ -84,26 +94,26 @@ impl Location {
} }
/// Converts the location to bytes (always 6 bytes) /// Converts the location to bytes (always 6 bytes)
/// ///
/// Format: [file_nr (2 bytes)][position (4 bytes)] /// Format: [file_nr (2 bytes)][position (4 bytes)]
pub fn to_bytes(&self) -> Vec<u8> { pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(6); let mut bytes = Vec::with_capacity(6);
// Put file_nr first (2 bytes) // Put file_nr first (2 bytes)
bytes.push((self.file_nr >> 8) as u8); bytes.push((self.file_nr >> 8) as u8);
bytes.push(self.file_nr as u8); bytes.push(self.file_nr as u8);
// Put position next (4 bytes) // Put position next (4 bytes)
bytes.push((self.position >> 24) as u8); bytes.push((self.position >> 24) as u8);
bytes.push((self.position >> 16) as u8); bytes.push((self.position >> 16) as u8);
bytes.push((self.position >> 8) as u8); bytes.push((self.position >> 8) as u8);
bytes.push(self.position as u8); bytes.push(self.position as u8);
bytes bytes
} }
/// Converts the location to a u64 value /// Converts the location to a u64 value
/// ///
/// The file_nr is stored in the most significant bits /// The file_nr is stored in the most significant bits
pub fn to_u64(&self) -> u64 { pub fn to_u64(&self) -> u64 {
(u64::from(self.file_nr) << 32) | u64::from(self.position) (u64::from(self.file_nr) << 32) | u64::from(self.position)

View File

@ -16,7 +16,7 @@ pub struct LookupConfig {
/// - 2: For databases with < 65,536 records (single file) /// - 2: For databases with < 65,536 records (single file)
/// - 3: For databases with < 16,777,216 records (single file) /// - 3: For databases with < 16,777,216 records (single file)
/// - 4: For databases with < 4,294,967,296 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, pub keysize: u8,
/// Path for disk-based lookup /// Path for disk-based lookup
pub lookuppath: String, pub lookuppath: String,
@ -46,7 +46,10 @@ impl LookupTable {
pub fn new(config: LookupConfig) -> Result<Self, Error> { pub fn new(config: LookupConfig) -> Result<Self, Error> {
// Verify keysize is valid // Verify keysize is valid
if ![2, 3, 4, 6].contains(&config.keysize) { 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 { let incremental = if config.incremental_mode {
@ -90,7 +93,7 @@ impl LookupTable {
if !self.lookuppath.is_empty() { if !self.lookuppath.is_empty() {
// Disk-based lookup // Disk-based lookup
let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME);
// Check file size first // Check file size first
let file_size = fs::metadata(&data_path)?.len(); let file_size = fs::metadata(&data_path)?.len();
let start_pos = id as u64 * entry_size as u64; 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 { if start_pos + entry_size as u64 > file_size {
return Err(Error::LookupError(format!( return Err(Error::LookupError(format!(
"Invalid read for get in lut: {}: {} would exceed file size {}", "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 mut data = vec![0u8; entry_size];
let bytes_read = file.read(&mut data)?; let bytes_read = file.read(&mut data)?;
if bytes_read < entry_size { if bytes_read < entry_size {
return Err(Error::LookupError(format!( return Err(Error::LookupError(format!(
"Incomplete read: expected {} bytes but got {}", "Incomplete read: expected {} bytes but got {}",
entry_size, bytes_read entry_size, bytes_read
))); )));
} }
return Location::from_bytes(&data, self.keysize); return Location::from_bytes(&data, self.keysize);
} }
@ -126,7 +131,7 @@ impl LookupTable {
let start = (id * self.keysize as u32) as usize; let start = (id * self.keysize as u32) as usize;
let end = start + entry_size; let end = start + entry_size;
Location::from_bytes(&self.data[start..end], self.keysize) Location::from_bytes(&self.data[start..end], self.keysize)
} }
@ -142,7 +147,7 @@ impl LookupTable {
if id > incremental { if id > incremental {
return Err(Error::InvalidOperation( 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 { let location_bytes = match self.keysize {
2 => { 2 => {
if location.file_nr != 0 { 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 { if location.position > 0xFFFF {
return Err(Error::InvalidOperation( 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] vec![(location.position >> 8) as u8, location.position as u8]
}, }
3 => { 3 => {
if location.file_nr != 0 { 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 { if location.position > 0xFFFFFF {
return Err(Error::InvalidOperation( 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![ vec![
(location.position >> 16) as u8, (location.position >> 16) as u8,
(location.position >> 8) as u8, (location.position >> 8) as u8,
location.position as u8 location.position as u8,
] ]
}, }
4 => { 4 => {
if location.file_nr != 0 { 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![ vec![
(location.position >> 24) as u8, (location.position >> 24) as u8,
(location.position >> 16) as u8, (location.position >> 16) as u8,
(location.position >> 8) as u8, (location.position >> 8) as u8,
location.position as u8 location.position as u8,
] ]
}, }
6 => { 6 => {
// Full location with file_nr and position // Full location with file_nr and position
location.to_bytes() 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() { if !self.lookuppath.is_empty() {
// Disk-based lookup // Disk-based lookup
let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME);
let mut file = OpenOptions::new().write(true).open(data_path)?; let mut file = OpenOptions::new().write(true).open(data_path)?;
let start_pos = id as u64 * entry_size as u64; let start_pos = id as u64 * entry_size as u64;
file.seek(SeekFrom::Start(start_pos))?; file.seek(SeekFrom::Start(start_pos))?;
file.write_all(&location_bytes)?; file.write_all(&location_bytes)?;
@ -207,7 +223,7 @@ impl LookupTable {
if start + entry_size > self.data.len() { if start + entry_size > self.data.len() {
return Err(Error::LookupError("Index out of bounds".to_string())); return Err(Error::LookupError("Index out of bounds".to_string()));
} }
for (i, &byte) in location_bytes.iter().enumerate() { for (i, &byte) in location_bytes.iter().enumerate() {
self.data[start + i] = byte; self.data[start + i] = byte;
} }
@ -224,9 +240,9 @@ impl LookupTable {
/// Gets the next available ID in incremental mode /// Gets the next available ID in incremental mode
pub fn get_next_id(&self) -> Result<u32, Error> { pub fn get_next_id(&self) -> Result<u32, Error> {
let incremental = self.incremental.ok_or_else(|| let incremental = self.incremental.ok_or_else(|| {
Error::InvalidOperation("Lookup table not in incremental mode".to_string()) Error::InvalidOperation("Lookup table not in incremental mode".to_string())
)?; })?;
let table_size = if !self.lookuppath.is_empty() { let table_size = if !self.lookuppath.is_empty() {
let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME);
@ -244,9 +260,9 @@ impl LookupTable {
/// Increments the index in incremental mode /// Increments the index in incremental mode
pub fn increment_index(&mut self) -> Result<(), Error> { 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()) Error::InvalidOperation("Lookup table not in incremental mode".to_string())
)?; })?;
incremental += 1; incremental += 1;
self.incremental = Some(incremental); self.incremental = Some(incremental);
@ -299,10 +315,10 @@ impl LookupTable {
for id in 0..max_entries { for id in 0..max_entries {
file.seek(SeekFrom::Start(id * entry_size as u64))?; file.seek(SeekFrom::Start(id * entry_size as u64))?;
let mut buffer = vec![0u8; entry_size]; let mut buffer = vec![0u8; entry_size];
let bytes_read = file.read(&mut buffer)?; let bytes_read = file.read(&mut buffer)?;
if bytes_read < entry_size { if bytes_read < entry_size {
break; break;
} }
@ -317,11 +333,11 @@ impl LookupTable {
} else { } else {
// For memory-based lookup // For memory-based lookup
let max_entries = self.data.len() / entry_size; let max_entries = self.data.len() / entry_size;
for id in 0..max_entries { for id in 0..max_entries {
let start = id * entry_size; let start = id * entry_size;
let entry = &self.data[start..start + entry_size]; let entry = &self.data[start..start + entry_size];
// Check if entry is non-zero // Check if entry is non-zero
if entry.iter().any(|&b| b != 0) { if entry.iter().any(|&b| b != 0) {
// Write ID (4 bytes) + entry // Write ID (4 bytes) + entry
@ -344,7 +360,7 @@ impl LookupTable {
if data.len() % record_size != 0 { if data.len() % record_size != 0 {
return Err(Error::DataCorruption( 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 // Extract entry
let entry = &data[chunk_start + 4..chunk_start + record_size]; let entry = &data[chunk_start + 4..chunk_start + record_size];
// Create location from entry // Create location from entry
let location = Location::from_bytes(entry, self.keysize)?; let location = Location::from_bytes(entry, self.keysize)?;
// Set the entry // Set the entry
self.set(id, location)?; self.set(id, location)?;
} }
@ -380,13 +396,13 @@ impl LookupTable {
let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME);
let mut file = File::open(&data_path)?; let mut file = File::open(&data_path)?;
let file_size = fs::metadata(&data_path)?.len(); let file_size = fs::metadata(&data_path)?.len();
let mut buffer = vec![0u8; entry_size]; let mut buffer = vec![0u8; entry_size];
let mut pos = 0u32; let mut pos = 0u32;
while (pos as u64 * entry_size as u64) < file_size { while (pos as u64 * entry_size as u64) < file_size {
file.seek(SeekFrom::Start(pos as u64 * entry_size as u64))?; file.seek(SeekFrom::Start(pos as u64 * entry_size as u64))?;
let bytes_read = file.read(&mut buffer)?; let bytes_read = file.read(&mut buffer)?;
if bytes_read == 0 || bytes_read < entry_size { if bytes_read == 0 || bytes_read < entry_size {
break; break;
@ -396,7 +412,7 @@ impl LookupTable {
if location.position != 0 || location.file_nr != 0 { if location.position != 0 || location.file_nr != 0 {
last_id = pos; last_id = pos;
} }
pos += 1; pos += 1;
} }
} else { } else {
@ -422,7 +438,7 @@ fn get_incremental_info(config: &LookupConfig) -> Result<u32, Error> {
if !config.lookuppath.is_empty() { if !config.lookuppath.is_empty() {
let inc_path = Path::new(&config.lookuppath).join(INCREMENTAL_FILE_NAME); let inc_path = Path::new(&config.lookuppath).join(INCREMENTAL_FILE_NAME);
if !inc_path.exists() { if !inc_path.exists() {
// Create a separate file for storing the incremental value // Create a separate file for storing the incremental value
fs::write(&inc_path, "1")?; fs::write(&inc_path, "1")?;
@ -437,7 +453,7 @@ fn get_incremental_info(config: &LookupConfig) -> Result<u32, Error> {
1 1
} }
}; };
Ok(incremental) Ok(incremental)
} else { } else {
// For memory-based lookup, start with 1 // For memory-based lookup, start with 1
@ -447,9 +463,9 @@ fn get_incremental_info(config: &LookupConfig) -> Result<u32, Error> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::path::PathBuf;
use super::*; use super::*;
use std::env::temp_dir; use std::env::temp_dir;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
fn get_temp_dir() -> PathBuf { fn get_temp_dir() -> PathBuf {
@ -468,25 +484,25 @@ mod tests {
lookuppath: String::new(), lookuppath: String::new(),
incremental_mode: true, incremental_mode: true,
}; };
let mut lookup = LookupTable::new(config).unwrap(); let mut lookup = LookupTable::new(config).unwrap();
// Test set and get // Test set and get
let location = Location { let location = Location {
file_nr: 0, file_nr: 0,
position: 12345, position: 12345,
}; };
lookup.set(1, location).unwrap(); lookup.set(1, location).unwrap();
let retrieved = lookup.get(1).unwrap(); let retrieved = lookup.get(1).unwrap();
assert_eq!(retrieved.file_nr, location.file_nr); assert_eq!(retrieved.file_nr, location.file_nr);
assert_eq!(retrieved.position, location.position); assert_eq!(retrieved.position, location.position);
// Test incremental mode // Test incremental mode
let next_id = lookup.get_next_id().unwrap(); let next_id = lookup.get_next_id().unwrap();
assert_eq!(next_id, 2); assert_eq!(next_id, 2);
lookup.increment_index().unwrap(); lookup.increment_index().unwrap();
let next_id = lookup.get_next_id().unwrap(); let next_id = lookup.get_next_id().unwrap();
assert_eq!(next_id, 3); assert_eq!(next_id, 3);
@ -496,28 +512,28 @@ mod tests {
fn test_disk_lookup() { fn test_disk_lookup() {
let temp_dir = get_temp_dir(); let temp_dir = get_temp_dir();
fs::create_dir_all(&temp_dir).unwrap(); fs::create_dir_all(&temp_dir).unwrap();
let config = LookupConfig { let config = LookupConfig {
size: 1000, size: 1000,
keysize: 4, keysize: 4,
lookuppath: temp_dir.to_string_lossy().to_string(), lookuppath: temp_dir.to_string_lossy().to_string(),
incremental_mode: true, incremental_mode: true,
}; };
let mut lookup = LookupTable::new(config).unwrap(); let mut lookup = LookupTable::new(config).unwrap();
// Test set and get // Test set and get
let location = Location { let location = Location {
file_nr: 0, file_nr: 0,
position: 12345, position: 12345,
}; };
lookup.set(1, location).unwrap(); lookup.set(1, location).unwrap();
let retrieved = lookup.get(1).unwrap(); let retrieved = lookup.get(1).unwrap();
assert_eq!(retrieved.file_nr, location.file_nr); assert_eq!(retrieved.file_nr, location.file_nr);
assert_eq!(retrieved.position, location.position); assert_eq!(retrieved.position, location.position);
// Clean up // Clean up
fs::remove_dir_all(temp_dir).unwrap(); fs::remove_dir_all(temp_dir).unwrap();
} }

View File

@ -1,9 +1,9 @@
use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; use ourdb::{OurDB, OurDBConfig, OurDBSetArgs};
use rand;
use std::env::temp_dir; use std::env::temp_dir;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use rand;
// Helper function to create a unique temporary directory for tests // Helper function to create a unique temporary directory for tests
fn get_temp_dir() -> PathBuf { fn get_temp_dir() -> PathBuf {
@ -13,56 +13,64 @@ fn get_temp_dir() -> PathBuf {
.as_nanos(); .as_nanos();
let random_part = rand::random::<u32>(); let random_part = rand::random::<u32>();
let dir = temp_dir().join(format!("ourdb_test_{}_{}", timestamp, random_part)); let dir = temp_dir().join(format!("ourdb_test_{}_{}", timestamp, random_part));
// Ensure the directory exists and is empty // Ensure the directory exists and is empty
if dir.exists() { if dir.exists() {
std::fs::remove_dir_all(&dir).unwrap(); std::fs::remove_dir_all(&dir).unwrap();
} }
std::fs::create_dir_all(&dir).unwrap(); std::fs::create_dir_all(&dir).unwrap();
dir dir
} }
#[test] #[test]
fn test_basic_operations() { fn test_basic_operations() {
let temp_dir = get_temp_dir(); let temp_dir = get_temp_dir();
// Create a new database with incremental mode // Create a new database with incremental mode
let config = OurDBConfig { let config = OurDBConfig {
path: temp_dir.clone(), path: temp_dir.clone(),
incremental_mode: true, incremental_mode: true,
file_size: None, file_size: None,
keysize: None, keysize: None,
reset: None reset: None,
}; };
let mut db = OurDB::new(config).unwrap(); let mut db = OurDB::new(config).unwrap();
// Test set and get // Test set and get
let test_data = b"Hello, OurDB!"; 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(); let retrieved = db.get(id).unwrap();
assert_eq!(retrieved, test_data); assert_eq!(retrieved, test_data);
// Test update // Test update
let updated_data = b"Updated data"; 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(); let retrieved = db.get(id).unwrap();
assert_eq!(retrieved, updated_data); assert_eq!(retrieved, updated_data);
// Test history // Test history
let history = db.get_history(id, 2).unwrap(); let history = db.get_history(id, 2).unwrap();
assert_eq!(history.len(), 2); assert_eq!(history.len(), 2);
assert_eq!(history[0], updated_data); assert_eq!(history[0], updated_data);
assert_eq!(history[1], test_data); assert_eq!(history[1], test_data);
// Test delete // Test delete
db.delete(id).unwrap(); db.delete(id).unwrap();
assert!(db.get(id).is_err()); assert!(db.get(id).is_err());
// Clean up // Clean up
db.destroy().unwrap(); db.destroy().unwrap();
} }
@ -70,30 +78,33 @@ fn test_basic_operations() {
#[test] #[test]
fn test_key_value_mode() { fn test_key_value_mode() {
let temp_dir = get_temp_dir(); let temp_dir = get_temp_dir();
// Create a new database with key-value mode // Create a new database with key-value mode
let config = OurDBConfig { let config = OurDBConfig {
path: temp_dir.clone(), path: temp_dir.clone(),
incremental_mode: false, incremental_mode: false,
file_size: None, file_size: None,
keysize: None, keysize: None,
reset: None reset: None,
}; };
let mut db = OurDB::new(config).unwrap(); let mut db = OurDB::new(config).unwrap();
// Test set with explicit ID // Test set with explicit ID
let test_data = b"Key-value data"; let test_data = b"Key-value data";
let id = 42; 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(); let retrieved = db.get(id).unwrap();
assert_eq!(retrieved, test_data); assert_eq!(retrieved, test_data);
// Verify next_id fails in key-value mode // Verify next_id fails in key-value mode
assert!(db.get_next_id().is_err()); assert!(db.get_next_id().is_err());
// Clean up // Clean up
db.destroy().unwrap(); db.destroy().unwrap();
} }
@ -101,33 +112,42 @@ fn test_key_value_mode() {
#[test] #[test]
fn test_incremental_mode() { fn test_incremental_mode() {
let temp_dir = get_temp_dir(); let temp_dir = get_temp_dir();
// Create a new database with incremental mode // Create a new database with incremental mode
let config = OurDBConfig { let config = OurDBConfig {
path: temp_dir.clone(), path: temp_dir.clone(),
incremental_mode: true, incremental_mode: true,
file_size: None, file_size: None,
keysize: None, keysize: None,
reset: None reset: None,
}; };
let mut db = OurDB::new(config).unwrap(); let mut db = OurDB::new(config).unwrap();
// Test auto-increment IDs // Test auto-increment IDs
let data1 = b"First record"; 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 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 // IDs should be sequential
assert_eq!(id2, id1 + 1); assert_eq!(id2, id1 + 1);
// Verify get_next_id works // Verify get_next_id works
let next_id = db.get_next_id().unwrap(); let next_id = db.get_next_id().unwrap();
assert_eq!(next_id, id2 + 1); assert_eq!(next_id, id2 + 1);
// Clean up // Clean up
db.destroy().unwrap(); db.destroy().unwrap();
} }
@ -135,8 +155,7 @@ fn test_incremental_mode() {
#[test] #[test]
fn test_persistence() { fn test_persistence() {
let temp_dir = get_temp_dir(); let temp_dir = get_temp_dir();
// Create data in a new database // Create data in a new database
{ {
let config = OurDBConfig { let config = OurDBConfig {
@ -144,21 +163,26 @@ fn test_persistence() {
incremental_mode: true, incremental_mode: true,
file_size: None, file_size: None,
keysize: None, keysize: None,
reset: None reset: None,
}; };
let mut db = OurDB::new(config).unwrap(); let mut db = OurDB::new(config).unwrap();
let test_data = b"Persistent data"; 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 // Explicitly close the database
db.close().unwrap(); db.close().unwrap();
// ID should be 1 in a new database // ID should be 1 in a new database
assert_eq!(id, 1); assert_eq!(id, 1);
} }
// Reopen the database and verify data persists // Reopen the database and verify data persists
{ {
let config = OurDBConfig { let config = OurDBConfig {
@ -166,19 +190,19 @@ fn test_persistence() {
incremental_mode: true, incremental_mode: true,
file_size: None, file_size: None,
keysize: None, keysize: None,
reset: None reset: None,
}; };
let mut db = OurDB::new(config).unwrap(); let mut db = OurDB::new(config).unwrap();
// Verify data is still there // Verify data is still there
let retrieved = db.get(1).unwrap(); let retrieved = db.get(1).unwrap();
assert_eq!(retrieved, b"Persistent data"); assert_eq!(retrieved, b"Persistent data");
// Verify incremental counter persisted // Verify incremental counter persisted
let next_id = db.get_next_id().unwrap(); let next_id = db.get_next_id().unwrap();
assert_eq!(next_id, 2); assert_eq!(next_id, 2);
// Clean up // Clean up
db.destroy().unwrap(); db.destroy().unwrap();
} }
@ -188,28 +212,33 @@ fn test_persistence() {
fn test_different_keysizes() { fn test_different_keysizes() {
for keysize in [2, 3, 4, 6].iter() { for keysize in [2, 3, 4, 6].iter() {
let temp_dir = get_temp_dir(); let temp_dir = get_temp_dir();
// Ensure the directory exists // Ensure the directory exists
std::fs::create_dir_all(&temp_dir).unwrap(); std::fs::create_dir_all(&temp_dir).unwrap();
// Create a new database with specified keysize // Create a new database with specified keysize
let config = OurDBConfig { let config = OurDBConfig {
path: temp_dir.clone(), path: temp_dir.clone(),
incremental_mode: true, incremental_mode: true,
file_size: None, file_size: None,
keysize: Some(*keysize), keysize: Some(*keysize),
reset: None reset: None,
}; };
let mut db = OurDB::new(config).unwrap(); let mut db = OurDB::new(config).unwrap();
// Test basic operations // Test basic operations
let test_data = b"Keysize test data"; 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(); let retrieved = db.get(id).unwrap();
assert_eq!(retrieved, test_data); assert_eq!(retrieved, test_data);
// Clean up // Clean up
db.destroy().unwrap(); db.destroy().unwrap();
} }
@ -218,28 +247,33 @@ fn test_different_keysizes() {
#[test] #[test]
fn test_large_data() { fn test_large_data() {
let temp_dir = get_temp_dir(); let temp_dir = get_temp_dir();
// Create a new database // Create a new database
let config = OurDBConfig { let config = OurDBConfig {
path: temp_dir.clone(), path: temp_dir.clone(),
incremental_mode: true, incremental_mode: true,
file_size: None, file_size: None,
keysize: None, keysize: None,
reset: None reset: None,
}; };
let mut db = OurDB::new(config).unwrap(); let mut db = OurDB::new(config).unwrap();
// Create a large data set (60KB - within the 64KB limit) // Create a large data set (60KB - within the 64KB limit)
let large_data = vec![b'X'; 60 * 1024]; let large_data = vec![b'X'; 60 * 1024];
// Store and retrieve large data // 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(); let retrieved = db.get(id).unwrap();
assert_eq!(retrieved.len(), large_data.len()); assert_eq!(retrieved.len(), large_data.len());
assert_eq!(retrieved, large_data); assert_eq!(retrieved, large_data);
// Clean up // Clean up
db.destroy().unwrap(); db.destroy().unwrap();
} }
@ -247,27 +281,33 @@ fn test_large_data() {
#[test] #[test]
fn test_exceed_size_limit() { fn test_exceed_size_limit() {
let temp_dir = get_temp_dir(); let temp_dir = get_temp_dir();
// Create a new database // Create a new database
let config = OurDBConfig { let config = OurDBConfig {
path: temp_dir.clone(), path: temp_dir.clone(),
incremental_mode: true, incremental_mode: true,
file_size: None, file_size: None,
keysize: None, keysize: None,
reset: None reset: None,
}; };
let mut db = OurDB::new(config).unwrap(); let mut db = OurDB::new(config).unwrap();
// Create data larger than the 64KB limit (70KB) // Create data larger than the 64KB limit (70KB)
let oversized_data = vec![b'X'; 70 * 1024]; let oversized_data = vec![b'X'; 70 * 1024];
// Attempt to store data that exceeds the size limit // 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 // 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 // Clean up
db.destroy().unwrap(); db.destroy().unwrap();
} }
@ -275,46 +315,55 @@ fn test_exceed_size_limit() {
#[test] #[test]
fn test_multiple_files() { fn test_multiple_files() {
let temp_dir = get_temp_dir(); let temp_dir = get_temp_dir();
// Create a new database with small file size to force multiple files // Create a new database with small file size to force multiple files
let config = OurDBConfig { let config = OurDBConfig {
path: temp_dir.clone(), path: temp_dir.clone(),
incremental_mode: true, incremental_mode: true,
file_size: Some(1024), // Very small file size (1KB) file_size: Some(1024), // Very small file size (1KB)
keysize: Some(6), // 6-byte keysize for multiple files keysize: Some(6), // 6-byte keysize for multiple files
reset: None reset: None,
}; };
let mut db = OurDB::new(config).unwrap(); let mut db = OurDB::new(config).unwrap();
// Store enough data to span multiple files // Store enough data to span multiple files
let data_size = 500; // bytes per record let data_size = 500; // bytes per record
let test_data = vec![b'A'; data_size]; let test_data = vec![b'A'; data_size];
let mut ids = Vec::new(); let mut ids = Vec::new();
for _ in 0..10 { 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); ids.push(id);
} }
// Verify all data can be retrieved // Verify all data can be retrieved
for &id in &ids { for &id in &ids {
let retrieved = db.get(id).unwrap(); let retrieved = db.get(id).unwrap();
assert_eq!(retrieved.len(), data_size); assert_eq!(retrieved.len(), data_size);
} }
// Verify multiple files were created // 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_map(Result::ok)
.filter(|entry| { .filter(|entry| {
let path = entry.path(); let path = entry.path();
path.is_file() && path.extension().map_or(false, |ext| ext == "db") path.is_file() && path.extension().map_or(false, |ext| ext == "db")
}) })
.count(); .count();
assert!(files > 1, "Expected multiple database files, found {}", files); assert!(
files > 1,
"Expected multiple database files, found {}",
files
);
// Clean up // Clean up
db.destroy().unwrap(); db.destroy().unwrap();
} }

View File

@ -1,23 +1,23 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::{quote, format_ident}; use quote::{format_ident, quote};
use syn::{parse_macro_input, ItemFn, FnArg, Pat, PatType, ReturnType, parse_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. /// 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 /// 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. /// that calls the original function through the Rhai engine.
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// #[rhai] /// #[rhai]
/// fn hello(name: String) -> String { /// fn hello(name: String) -> String {
/// format!("Hello, {}!", name) /// format!("Hello, {}!", name)
/// } /// }
/// ``` /// ```
/// ///
/// This will generate: /// This will generate:
/// ///
/// ```rust /// ```rust
/// fn hello_rhai_client(engine: &rhai::Engine, name: String) -> String { /// fn hello_rhai_client(engine: &rhai::Engine, name: String) -> String {
/// let script = format!("hello(\"{}\")", name.replace("\"", "\\\"")); /// 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, /// Note: The macro handles type conversions between Rust and Rhai types,
/// particularly for integer types (Rhai uses i64 internally). /// particularly for integer types (Rhai uses i64 internally).
#[proc_macro_attribute] #[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 input_fn = parse_macro_input!(item as ItemFn);
let fn_name = &input_fn.sig.ident; let fn_name = &input_fn.sig.ident;
let fn_name_str = fn_name.to_string(); let fn_name_str = fn_name.to_string();
// Create the client function name (original + _rhai_client) // Create the client function name (original + _rhai_client)
let client_fn_name = format_ident!("{}_rhai_client", fn_name); let client_fn_name = format_ident!("{}_rhai_client", fn_name);
// Extract function parameters // Extract function parameters
let mut param_names = Vec::new(); let mut param_names = Vec::new();
let mut param_types = Vec::new(); let mut param_types = Vec::new();
let mut param_declarations = Vec::new(); let mut param_declarations = Vec::new();
for arg in &input_fn.sig.inputs { for arg in &input_fn.sig.inputs {
match arg { match arg {
FnArg::Typed(PatType { pat, ty, .. }) => { FnArg::Typed(PatType { pat, ty, .. }) => {
@ -61,48 +61,55 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream {
} }
} }
} }
// Determine return type // Determine return type
let return_type = match &input_fn.sig.output { let return_type = match &input_fn.sig.output {
ReturnType::Default => parse_quote!(()), ReturnType::Default => parse_quote!(()),
ReturnType::Type(_, ty) => ty.clone(), ReturnType::Type(_, ty) => ty.clone(),
}; };
// Generate parameter formatting for the Rhai script // Generate parameter formatting for the Rhai script
let param_format_strings = param_names.iter().zip(param_types.iter()).map(|(name, ty)| { let param_format_strings = param_names
let type_str = quote! { #ty }.to_string(); .iter()
.zip(param_types.iter())
// Handle different parameter types .map(|(name, ty)| {
if type_str.contains("String") { let type_str = quote! { #ty }.to_string();
quote! {
format!("\"{}\"" , #name.replace("\"", "\\\"")) // 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 // Determine if the return type needs conversion
let return_type_str = quote! { #return_type }.to_string(); let return_type_str = quote! { #return_type }.to_string();
// Generate the client function with appropriate type conversions // Generate the client function with appropriate type conversions
let client_fn = if return_type_str.contains("i32") || return_type_str.contains("u32") { let client_fn = if return_type_str.contains("i32") || return_type_str.contains("u32") {
// For integer return types that need conversion // For integer return types that need conversion
@ -113,7 +120,7 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream {
#fn_name_str, #fn_name_str,
&[#(#param_format_strings),*].join(", ") &[#(#param_format_strings),*].join(", ")
); );
match engine.eval::<i64>(&script) { match engine.eval::<i64>(&script) {
Ok(result) => result as #return_type, Ok(result) => result as #return_type,
Err(err) => { Err(err) => {
@ -132,7 +139,7 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream {
#fn_name_str, #fn_name_str,
&[#(#param_format_strings),*].join(", ") &[#(#param_format_strings),*].join(", ")
); );
match engine.eval::<#return_type>(&script) { match engine.eval::<#return_type>(&script) {
Ok(result) => result, Ok(result) => result,
Err(err) => { Err(err) => {
@ -151,7 +158,7 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream {
#fn_name_str, #fn_name_str,
&[#(#param_format_strings),*].join(", ") &[#(#param_format_strings),*].join(", ")
); );
match engine.eval::<#return_type>(&script) { match engine.eval::<#return_type>(&script) {
Ok(result) => result, Ok(result) => result,
Err(err) => { Err(err) => {
@ -170,7 +177,7 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream {
#fn_name_str, #fn_name_str,
&[#(#param_format_strings),*].join(", ") &[#(#param_format_strings),*].join(", ")
); );
match engine.eval::<#return_type>(&script) { match engine.eval::<#return_type>(&script) {
Ok(result) => result, Ok(result) => result,
Err(err) => { Err(err) => {
@ -181,19 +188,19 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream {
} }
} }
}; };
// Combine the original function and the generated client function // Combine the original function and the generated client function
let output = quote! { let output = quote! {
#input_fn #input_fn
#client_fn #client_fn
}; };
output.into() output.into()
} }
/// A more advanced version of the rhai macro that handles different parameter types better. /// 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. /// This version properly escapes strings and handles different parameter types more accurately.
/// It's recommended to use this version for more complex functions. /// It's recommended to use this version for more complex functions.
#[proc_macro_attribute] #[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 input_fn = parse_macro_input!(item as ItemFn);
let fn_name = &input_fn.sig.ident; let fn_name = &input_fn.sig.ident;
let fn_name_str = fn_name.to_string(); let fn_name_str = fn_name.to_string();
// Create the client function name (original + _rhai_client) // Create the client function name (original + _rhai_client)
let client_fn_name = format_ident!("{}_rhai_client", fn_name); let client_fn_name = format_ident!("{}_rhai_client", fn_name);
// Extract function parameters // Extract function parameters
let mut param_names = Vec::new(); let mut param_names = Vec::new();
let mut param_types = Vec::new(); let mut param_types = Vec::new();
let mut param_declarations = Vec::new(); let mut param_declarations = Vec::new();
for arg in &input_fn.sig.inputs { for arg in &input_fn.sig.inputs {
match arg { match arg {
FnArg::Typed(PatType { pat, ty, .. }) => { FnArg::Typed(PatType { pat, ty, .. }) => {
@ -227,48 +234,53 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream {
} }
} }
} }
// Determine return type // Determine return type
let return_type = match &input_fn.sig.output { let return_type = match &input_fn.sig.output {
ReturnType::Default => parse_quote!(()), ReturnType::Default => parse_quote!(()),
ReturnType::Type(_, ty) => ty.clone(), ReturnType::Type(_, ty) => ty.clone(),
}; };
// Generate parameter formatting for the Rhai script // Generate parameter formatting for the Rhai script
let param_format_expressions = param_names.iter().zip(param_types.iter()).map(|(name, ty)| { let param_format_expressions = param_names
let type_str = quote! { #ty }.to_string(); .iter()
.zip(param_types.iter())
// Handle different parameter types .map(|(name, ty)| {
if type_str.contains("String") { let type_str = quote! { #ty }.to_string();
quote! {
format!("\"{}\"", #name.replace("\"", "\\\"")) // 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! { .collect::<Vec<_>>();
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::<Vec<_>>();
// Determine if the return type needs conversion // Determine if the return type needs conversion
let return_type_str = quote! { #return_type }.to_string(); 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 // Generate the client function with appropriate type conversions
let client_fn = if needs_return_conversion { let client_fn = if needs_return_conversion {
quote! { quote! {
@ -278,7 +290,7 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream {
#fn_name_str, #fn_name_str,
&[#(#param_format_expressions),*].join(", ") &[#(#param_format_expressions),*].join(", ")
); );
match engine.eval::<i64>(&script) { match engine.eval::<i64>(&script) {
Ok(result) => result as #return_type, Ok(result) => result as #return_type,
Err(err) => { Err(err) => {
@ -296,7 +308,7 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream {
#fn_name_str, #fn_name_str,
&[#(#param_format_expressions),*].join(", ") &[#(#param_format_expressions),*].join(", ")
); );
match engine.eval::<#return_type>(&script) { match engine.eval::<#return_type>(&script) {
Ok(result) => result, Ok(result) => result,
Err(err) => { Err(err) => {
@ -307,19 +319,19 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream {
} }
} }
}; };
// Combine the original function and the generated client function // Combine the original function and the generated client function
let output = quote! { let output = quote! {
#input_fn #input_fn
#client_fn #client_fn
}; };
output.into() output.into()
} }
/// Macro that generates a module with Rhai client functions for all functions in scope. /// 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 /// This macro should be used at the module level to generate Rhai client functions for all
/// functions marked with the #[rhai] attribute. /// functions marked with the #[rhai] attribute.
#[proc_macro] #[proc_macro]
@ -329,20 +341,20 @@ pub fn generate_rhai_module(_item: TokenStream) -> TokenStream {
// client functions for all of them. // client functions for all of them.
// //
// For simplicity, we'll just return a placeholder implementation // For simplicity, we'll just return a placeholder implementation
let output = quote! { let output = quote! {
/// Register all functions marked with #[rhai] in this module with the Rhai engine. /// Register all functions marked with #[rhai] in this module with the Rhai engine.
/// ///
/// This function handles type conversions between Rust and Rhai types automatically. /// This function handles type conversions between Rust and Rhai types automatically.
/// For example, it converts between Rust's i32 and Rhai's i64 types. /// For example, it converts between Rust's i32 and Rhai's i64 types.
pub fn register_rhai_functions(engine: &mut rhai::Engine) { pub fn register_rhai_functions(engine: &mut rhai::Engine) {
// This would be generated based on the functions in the module // This would be generated based on the functions in the module
println!("Registering Rhai functions..."); println!("Registering Rhai functions...");
// In a real implementation, this would iterate through all functions // In a real implementation, this would iterate through all functions
// marked with #[rhai] and register them with the engine. // marked with #[rhai] and register them with the engine.
} }
}; };
output.into() output.into()
} }

View File

@ -1,16 +1,16 @@
use tst::TST;
use std::time::Instant; use std::time::Instant;
use tst::TST;
fn main() -> Result<(), tst::Error> { fn main() -> Result<(), tst::Error> {
// Create a temporary directory for the database // Create a temporary directory for the database
let db_path = std::env::temp_dir().join("tst_example"); let db_path = std::env::temp_dir().join("tst_example");
std::fs::create_dir_all(&db_path)?; std::fs::create_dir_all(&db_path)?;
println!("Creating ternary search tree at: {}", db_path.display()); println!("Creating ternary search tree at: {}", db_path.display());
// Create a new TST // Create a new TST
let mut tree = TST::new(db_path.to_str().unwrap(), true)?; let mut tree = TST::new(db_path.to_str().unwrap(), true)?;
// Store some data // Store some data
println!("Inserting data..."); println!("Inserting data...");
tree.set("hello", b"world".to_vec())?; 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("apple", b"fruit".to_vec())?;
tree.set("application", b"software".to_vec())?; tree.set("application", b"software".to_vec())?;
tree.set("banana", b"yellow".to_vec())?; tree.set("banana", b"yellow".to_vec())?;
// Retrieve and print the data // Retrieve and print the data
let value = tree.get("hello")?; let value = tree.get("hello")?;
println!("hello: {}", String::from_utf8_lossy(&value)); println!("hello: {}", String::from_utf8_lossy(&value));
// List keys with prefix // List keys with prefix
println!("\nListing keys with prefix 'hel':"); println!("\nListing keys with prefix 'hel':");
let start = Instant::now(); let start = Instant::now();
let keys = tree.list("hel")?; let keys = tree.list("hel")?;
let duration = start.elapsed(); let duration = start.elapsed();
for key in &keys { for key in &keys {
println!(" {}", key); println!(" {}", key);
} }
println!("Found {} keys in {:?}", keys.len(), duration); println!("Found {} keys in {:?}", keys.len(), duration);
// Get all values with prefix // Get all values with prefix
println!("\nGetting all values with prefix 'app':"); println!("\nGetting all values with prefix 'app':");
let start = Instant::now(); let start = Instant::now();
let values = tree.getall("app")?; let values = tree.getall("app")?;
let duration = start.elapsed(); let duration = start.elapsed();
for (i, value) in values.iter().enumerate() { for (i, value) in values.iter().enumerate() {
println!(" Value {}: {}", i + 1, String::from_utf8_lossy(value)); println!(" Value {}: {}", i + 1, String::from_utf8_lossy(value));
} }
println!("Found {} values in {:?}", values.len(), duration); println!("Found {} values in {:?}", values.len(), duration);
// Delete a key // Delete a key
println!("\nDeleting 'help'..."); println!("\nDeleting 'help'...");
tree.delete("help")?; tree.delete("help")?;
// Verify deletion // Verify deletion
println!("Listing keys with prefix 'hel' after deletion:"); println!("Listing keys with prefix 'hel' after deletion:");
let keys_after = tree.list("hel")?; let keys_after = tree.list("hel")?;
for key in &keys_after { for key in &keys_after {
println!(" {}", key); println!(" {}", key);
} }
// Try to get a deleted key // Try to get a deleted key
match tree.get("help") { match tree.get("help") {
Ok(_) => println!("Unexpectedly found 'help' after deletion!"), Ok(_) => println!("Unexpectedly found 'help' after deletion!"),
Err(e) => println!("As expected, 'help' was not found: {}", e), Err(e) => println!("As expected, 'help' was not found: {}", e),
} }
// Clean up (optional) // Clean up (optional)
if std::env::var("KEEP_DB").is_err() { if std::env::var("KEEP_DB").is_err() {
std::fs::remove_dir_all(&db_path)?; std::fs::remove_dir_all(&db_path)?;
@ -70,6 +70,6 @@ fn main() -> Result<(), tst::Error> {
} else { } else {
println!("\nDatabase kept at: {}", db_path.display()); println!("\nDatabase kept at: {}", db_path.display());
} }
Ok(()) Ok(())
} }

View File

@ -1,20 +1,20 @@
use tst::TST;
use std::time::{Duration, Instant};
use std::io::{self, Write}; use std::io::{self, Write};
use std::time::{Duration, Instant};
use tst::TST;
// Function to generate a test value of specified size // Function to generate a test value of specified size
fn generate_test_value(index: usize, size: usize) -> Vec<u8> { fn generate_test_value(index: usize, size: usize) -> Vec<u8> {
let base_value = format!("val{:08}", index); let base_value = format!("val{:08}", index);
let mut value = Vec::with_capacity(size); let mut value = Vec::with_capacity(size);
// Fill with repeating pattern to reach desired size // Fill with repeating pattern to reach desired size
while value.len() < size { while value.len() < size {
value.extend_from_slice(base_value.as_bytes()); value.extend_from_slice(base_value.as_bytes());
} }
// Truncate to exact size // Truncate to exact size
value.truncate(size); value.truncate(size);
value value
} }
@ -28,39 +28,39 @@ const PERFORMANCE_SAMPLE_SIZE: usize = 100;
fn main() -> Result<(), tst::Error> { fn main() -> Result<(), tst::Error> {
// Create a temporary directory for the database // Create a temporary directory for the database
let db_path = std::env::temp_dir().join("tst_performance_test"); let db_path = std::env::temp_dir().join("tst_performance_test");
// Completely remove and recreate the directory to ensure a clean start // Completely remove and recreate the directory to ensure a clean start
if db_path.exists() { if db_path.exists() {
std::fs::remove_dir_all(&db_path)?; std::fs::remove_dir_all(&db_path)?;
} }
std::fs::create_dir_all(&db_path)?; std::fs::create_dir_all(&db_path)?;
println!("Creating ternary search tree at: {}", db_path.display()); println!("Creating ternary search tree at: {}", db_path.display());
println!("Will insert {} records and show progress...", TOTAL_RECORDS); println!("Will insert {} records and show progress...", TOTAL_RECORDS);
// Create a new TST // Create a new TST
let mut tree = TST::new(db_path.to_str().unwrap(), true)?; let mut tree = TST::new(db_path.to_str().unwrap(), true)?;
// Track overall time // Track overall time
let start_time = Instant::now(); let start_time = Instant::now();
// Track performance metrics // Track performance metrics
let mut insertion_times = Vec::with_capacity(TOTAL_RECORDS / PROGRESS_INTERVAL); let mut insertion_times = Vec::with_capacity(TOTAL_RECORDS / PROGRESS_INTERVAL);
let mut last_batch_time = Instant::now(); let mut last_batch_time = Instant::now();
let mut last_batch_records = 0; let mut last_batch_records = 0;
// Insert records and track progress // Insert records and track progress
for i in 0..TOTAL_RECORDS { for i in 0..TOTAL_RECORDS {
let key = format!("key:{:08}", i); let key = format!("key:{:08}", i);
// Generate a 100-byte value // Generate a 100-byte value
let value = generate_test_value(i, 100); let value = generate_test_value(i, 100);
// Time the insertion of every Nth record for performance sampling // Time the insertion of every Nth record for performance sampling
if i % PERFORMANCE_SAMPLE_SIZE == 0 { if i % PERFORMANCE_SAMPLE_SIZE == 0 {
let insert_start = Instant::now(); let insert_start = Instant::now();
tree.set(&key, value)?; tree.set(&key, value)?;
let insert_duration = insert_start.elapsed(); let insert_duration = insert_start.elapsed();
// Only print detailed timing for specific samples to avoid flooding output // Only print detailed timing for specific samples to avoid flooding output
if i % (PERFORMANCE_SAMPLE_SIZE * 10) == 0 { if i % (PERFORMANCE_SAMPLE_SIZE * 10) == 0 {
println!("Record {}: Insertion took {:?}", i, insert_duration); println!("Record {}: Insertion took {:?}", i, insert_duration);
@ -68,76 +68,93 @@ fn main() -> Result<(), tst::Error> {
} else { } else {
tree.set(&key, value)?; tree.set(&key, value)?;
} }
// Show progress at intervals // Show progress at intervals
if (i + 1) % PROGRESS_INTERVAL == 0 || i == TOTAL_RECORDS - 1 { if (i + 1) % PROGRESS_INTERVAL == 0 || i == TOTAL_RECORDS - 1 {
let records_in_batch = i + 1 - last_batch_records; let records_in_batch = i + 1 - last_batch_records;
let batch_duration = last_batch_time.elapsed(); let batch_duration = last_batch_time.elapsed();
let records_per_second = records_in_batch as f64 / batch_duration.as_secs_f64(); let records_per_second = records_in_batch as f64 / batch_duration.as_secs_f64();
insertion_times.push((i + 1, batch_duration)); insertion_times.push((i + 1, batch_duration));
print!("\rProgress: {}/{} records ({:.2}%) - {:.2} records/sec", print!(
i + 1, TOTAL_RECORDS, "\rProgress: {}/{} records ({:.2}%) - {:.2} records/sec",
(i + 1) as f64 / TOTAL_RECORDS as f64 * 100.0, i + 1,
records_per_second); TOTAL_RECORDS,
(i + 1) as f64 / TOTAL_RECORDS as f64 * 100.0,
records_per_second
);
io::stdout().flush().unwrap(); io::stdout().flush().unwrap();
last_batch_time = Instant::now(); last_batch_time = Instant::now();
last_batch_records = i + 1; last_batch_records = i + 1;
} }
} }
let total_duration = start_time.elapsed(); let total_duration = start_time.elapsed();
println!("\n\nPerformance Summary:"); println!("\n\nPerformance Summary:");
println!("Total time to insert {} records: {:?}", TOTAL_RECORDS, total_duration); println!(
println!("Average insertion rate: {:.2} records/second", "Total time to insert {} records: {:?}",
TOTAL_RECORDS as f64 / total_duration.as_secs_f64()); TOTAL_RECORDS, total_duration
);
println!(
"Average insertion rate: {:.2} records/second",
TOTAL_RECORDS as f64 / total_duration.as_secs_f64()
);
// Show performance trend // Show performance trend
println!("\nPerformance Trend (records inserted vs. time per batch):"); println!("\nPerformance Trend (records inserted vs. time per batch):");
for (i, (record_count, duration)) in insertion_times.iter().enumerate() { 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 if i % 10 == 0 || i == insertion_times.len() - 1 {
println!(" After {} records: {:?} for {} records ({:.2} records/sec)", // Only show every 10th point to avoid too much output
record_count, println!(
duration, " After {} records: {:?} for {} records ({:.2} records/sec)",
PROGRESS_INTERVAL, record_count,
PROGRESS_INTERVAL as f64 / duration.as_secs_f64()); duration,
PROGRESS_INTERVAL,
PROGRESS_INTERVAL as f64 / duration.as_secs_f64()
);
} }
} }
// Test access performance with distributed samples // Test access performance with distributed samples
println!("\nTesting access performance with distributed samples..."); println!("\nTesting access performance with distributed samples...");
let mut total_get_time = Duration::new(0, 0); let mut total_get_time = Duration::new(0, 0);
let num_samples = 1000; let num_samples = 1000;
// Use a simple distribution pattern instead of random // Use a simple distribution pattern instead of random
for i in 0..num_samples { for i in 0..num_samples {
// Distribute samples across the entire range // Distribute samples across the entire range
let sample_id = (i * (TOTAL_RECORDS / num_samples)) % TOTAL_RECORDS; let sample_id = (i * (TOTAL_RECORDS / num_samples)) % TOTAL_RECORDS;
let key = format!("key:{:08}", sample_id); let key = format!("key:{:08}", sample_id);
let get_start = Instant::now(); let get_start = Instant::now();
let _ = tree.get(&key)?; let _ = tree.get(&key)?;
total_get_time += get_start.elapsed(); total_get_time += get_start.elapsed();
} }
println!("Average time to retrieve a record: {:?}", println!(
total_get_time / num_samples as u32); "Average time to retrieve a record: {:?}",
total_get_time / num_samples as u32
);
// Test prefix search performance // Test prefix search performance
println!("\nTesting prefix search performance..."); println!("\nTesting prefix search performance...");
let prefixes = ["key:0", "key:1", "key:5", "key:9"]; let prefixes = ["key:0", "key:1", "key:5", "key:9"];
for prefix in &prefixes { for prefix in &prefixes {
let list_start = Instant::now(); let list_start = Instant::now();
let keys = tree.list(prefix)?; let keys = tree.list(prefix)?;
let list_duration = list_start.elapsed(); let list_duration = list_start.elapsed();
println!("Found {} keys with prefix '{}' in {:?}", println!(
keys.len(), prefix, list_duration); "Found {} keys with prefix '{}' in {:?}",
keys.len(),
prefix,
list_duration
);
} }
// Clean up (optional) // Clean up (optional)
if std::env::var("KEEP_DB").is_err() { if std::env::var("KEEP_DB").is_err() {
std::fs::remove_dir_all(&db_path)?; std::fs::remove_dir_all(&db_path)?;
@ -145,6 +162,6 @@ fn main() -> Result<(), tst::Error> {
} else { } else {
println!("\nDatabase kept at: {}", db_path.display()); println!("\nDatabase kept at: {}", db_path.display());
} }
Ok(()) Ok(())
} }

View File

@ -1,82 +1,137 @@
use tst::TST;
use std::time::Instant; use std::time::Instant;
use tst::TST;
fn main() -> Result<(), tst::Error> { fn main() -> Result<(), tst::Error> {
// Create a temporary directory for the database // Create a temporary directory for the database
let db_path = std::env::temp_dir().join("tst_prefix_example"); let db_path = std::env::temp_dir().join("tst_prefix_example");
std::fs::create_dir_all(&db_path)?; std::fs::create_dir_all(&db_path)?;
println!("Creating ternary search tree at: {}", db_path.display()); println!("Creating ternary search tree at: {}", db_path.display());
// Create a new TST // Create a new TST
let mut tree = TST::new(db_path.to_str().unwrap(), true)?; let mut tree = TST::new(db_path.to_str().unwrap(), true)?;
// Insert a variety of keys with different prefixes // Insert a variety of keys with different prefixes
println!("Inserting data with various prefixes..."); println!("Inserting data with various prefixes...");
// Names // Names
let names = [ let names = [
"Alice", "Alexander", "Amanda", "Andrew", "Amy", "Alice",
"Bob", "Barbara", "Benjamin", "Brenda", "Brian", "Alexander",
"Charlie", "Catherine", "Christopher", "Cynthia", "Carl", "Amanda",
"David", "Diana", "Daniel", "Deborah", "Donald", "Andrew",
"Edward", "Elizabeth", "Eric", "Emily", "Ethan" "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() { for (i, name) in names.iter().enumerate() {
let value = format!("person-{}", i).into_bytes(); let value = format!("person-{}", i).into_bytes();
tree.set(name, value)?; tree.set(name, value)?;
} }
// Cities // Cities
let cities = [ let cities = [
"New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "New York",
"Philadelphia", "San Antonio", "San Diego", "Dallas", "San Jose", "Los Angeles",
"Austin", "Jacksonville", "Fort Worth", "Columbus", "San Francisco", "Chicago",
"Charlotte", "Indianapolis", "Seattle", "Denver", "Washington" "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() { for (i, city) in cities.iter().enumerate() {
let value = format!("city-{}", i).into_bytes(); let value = format!("city-{}", i).into_bytes();
tree.set(city, value)?; tree.set(city, value)?;
} }
// Countries // Countries
let countries = [ let countries = [
"United States", "Canada", "Mexico", "Brazil", "Argentina", "United States",
"United Kingdom", "France", "Germany", "Italy", "Spain", "Canada",
"China", "Japan", "India", "Australia", "Russia" "Mexico",
"Brazil",
"Argentina",
"United Kingdom",
"France",
"Germany",
"Italy",
"Spain",
"China",
"Japan",
"India",
"Australia",
"Russia",
]; ];
for (i, country) in countries.iter().enumerate() { for (i, country) in countries.iter().enumerate() {
let value = format!("country-{}", i).into_bytes(); let value = format!("country-{}", i).into_bytes();
tree.set(country, value)?; 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 operations
test_prefix(&mut tree, "A")?; test_prefix(&mut tree, "A")?;
test_prefix(&mut tree, "B")?; test_prefix(&mut tree, "B")?;
test_prefix(&mut tree, "C")?; test_prefix(&mut tree, "C")?;
test_prefix(&mut tree, "San")?; test_prefix(&mut tree, "San")?;
test_prefix(&mut tree, "United")?; test_prefix(&mut tree, "United")?;
// Test non-existent prefix // Test non-existent prefix
test_prefix(&mut tree, "Z")?; test_prefix(&mut tree, "Z")?;
// Test empty prefix (should return all keys) // Test empty prefix (should return all keys)
println!("\nTesting empty prefix (should return all keys):"); println!("\nTesting empty prefix (should return all keys):");
let start = Instant::now(); let start = Instant::now();
let all_keys = tree.list("")?; let all_keys = tree.list("")?;
let duration = start.elapsed(); 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):"); println!("First 5 keys (alphabetically):");
for key in all_keys.iter().take(5) { for key in all_keys.iter().take(5) {
println!(" {}", key); println!(" {}", key);
} }
// Clean up (optional) // Clean up (optional)
if std::env::var("KEEP_DB").is_err() { if std::env::var("KEEP_DB").is_err() {
std::fs::remove_dir_all(&db_path)?; std::fs::remove_dir_all(&db_path)?;
@ -84,39 +139,46 @@ fn main() -> Result<(), tst::Error> {
} else { } else {
println!("\nDatabase kept at: {}", db_path.display()); println!("\nDatabase kept at: {}", db_path.display());
} }
Ok(()) Ok(())
} }
fn test_prefix(tree: &mut TST, prefix: &str) -> Result<(), tst::Error> { fn test_prefix(tree: &mut TST, prefix: &str) -> Result<(), tst::Error> {
println!("\nTesting prefix '{}':", prefix); println!("\nTesting prefix '{}':", prefix);
// Test list operation // Test list operation
let start = Instant::now(); let start = Instant::now();
let keys = tree.list(prefix)?; let keys = tree.list(prefix)?;
let list_duration = start.elapsed(); 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() { if !keys.is_empty() {
println!("Keys:"); println!("Keys:");
for key in &keys { for key in &keys {
println!(" {}", key); println!(" {}", key);
} }
// Test getall operation // Test getall operation
let start = Instant::now(); let start = Instant::now();
let values = tree.getall(prefix)?; let values = tree.getall(prefix)?;
let getall_duration = start.elapsed(); let getall_duration = start.elapsed();
println!("Retrieved {} values in {:?}", values.len(), getall_duration); println!("Retrieved {} values in {:?}", values.len(), getall_duration);
println!("First value: {}", println!(
if !values.is_empty() { "First value: {}",
String::from_utf8_lossy(&values[0]) if !values.is_empty() {
} else { String::from_utf8_lossy(&values[0])
"None".into() } else {
}); "None".into()
}
);
} }
Ok(()) Ok(())
} }

View File

@ -1,7 +1,7 @@
//! Error types for the TST module. //! Error types for the TST module.
use thiserror::Error;
use std::io; use std::io;
use thiserror::Error;
/// Error type for TST operations. /// Error type for TST operations.
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -9,28 +9,28 @@ pub enum Error {
/// Error from OurDB operations. /// Error from OurDB operations.
#[error("OurDB error: {0}")] #[error("OurDB error: {0}")]
OurDB(#[from] ourdb::Error), OurDB(#[from] ourdb::Error),
/// Error when a key is not found. /// Error when a key is not found.
#[error("Key not found: {0}")] #[error("Key not found: {0}")]
KeyNotFound(String), KeyNotFound(String),
/// Error when a prefix is not found. /// Error when a prefix is not found.
#[error("Prefix not found: {0}")] #[error("Prefix not found: {0}")]
PrefixNotFound(String), PrefixNotFound(String),
/// Error during serialization. /// Error during serialization.
#[error("Serialization error: {0}")] #[error("Serialization error: {0}")]
Serialization(String), Serialization(String),
/// Error during deserialization. /// Error during deserialization.
#[error("Deserialization error: {0}")] #[error("Deserialization error: {0}")]
Deserialization(String), Deserialization(String),
/// Error for invalid operations. /// Error for invalid operations.
#[error("Invalid operation: {0}")] #[error("Invalid operation: {0}")]
InvalidOperation(String), InvalidOperation(String),
/// IO error. /// IO error.
#[error("IO error: {0}")] #[error("IO error: {0}")]
IO(#[from] io::Error), IO(#[from] io::Error),
} }

View File

@ -18,7 +18,7 @@ use ourdb::OurDB;
pub struct TST { pub struct TST {
/// Database for persistent storage /// Database for persistent storage
db: OurDB, db: OurDB,
/// Database ID of the root node /// Database ID of the root node
root_id: Option<u32>, root_id: Option<u32>,
} }
@ -119,4 +119,4 @@ impl TST {
pub fn getall(&mut self, prefix: &str) -> Result<Vec<Vec<u8>>, Error> { pub fn getall(&mut self, prefix: &str) -> Result<Vec<Vec<u8>>, Error> {
operations::getall(self, prefix) operations::getall(self, prefix)
} }
} }

View File

@ -5,19 +5,19 @@
pub struct TSTNode { pub struct TSTNode {
/// The character stored at this node. /// The character stored at this node.
pub character: char, pub character: char,
/// Value stored at this node (empty if not end of key). /// Value stored at this node (empty if not end of key).
pub value: Vec<u8>, pub value: Vec<u8>,
/// Whether this node represents the end of a key. /// Whether this node represents the end of a key.
pub is_end_of_key: bool, pub is_end_of_key: bool,
/// Reference to the left child node (for characters < current character). /// Reference to the left child node (for characters < current character).
pub left_id: Option<u32>, pub left_id: Option<u32>,
/// Reference to the middle child node (for next character in key). /// Reference to the middle child node (for next character in key).
pub middle_id: Option<u32>, pub middle_id: Option<u32>,
/// Reference to the right child node (for characters > current character). /// Reference to the right child node (for characters > current character).
pub right_id: Option<u32>, pub right_id: Option<u32>,
} }
@ -34,7 +34,7 @@ impl TSTNode {
right_id: None, right_id: None,
} }
} }
/// Creates a new root node. /// Creates a new root node.
pub fn new_root() -> Self { pub fn new_root() -> Self {
Self { Self {
@ -46,4 +46,4 @@ impl TSTNode {
right_id: None, right_id: None,
} }
} }
} }

View File

@ -9,19 +9,19 @@ use std::path::PathBuf;
/// Creates a new TST with the specified database path. /// Creates a new TST with the specified database path.
pub fn new_tst(path: &str, reset: bool) -> Result<TST, Error> { pub fn new_tst(path: &str, reset: bool) -> Result<TST, Error> {
let path_buf = PathBuf::from(path); let path_buf = PathBuf::from(path);
// Create the configuration for OurDB with reset parameter // Create the configuration for OurDB with reset parameter
let config = OurDBConfig { let config = OurDBConfig {
path: path_buf.clone(), path: path_buf.clone(),
incremental_mode: true, incremental_mode: true,
file_size: Some(1024 * 1024), // 1MB file size for better performance with large datasets file_size: Some(1024 * 1024), // 1MB file size for better performance with large datasets
keysize: Some(4), // Use keysize=4 (default) keysize: Some(4), // Use keysize=4 (default)
reset: Some(reset), // Use the reset parameter reset: Some(reset), // Use the reset parameter
}; };
// Create a new OurDB instance (it will handle reset internally) // Create a new OurDB instance (it will handle reset internally)
let mut db = OurDB::new(config)?; let mut db = OurDB::new(config)?;
let root_id = if db.get_next_id()? == 1 || reset { let root_id = if db.get_next_id()? == 1 || reset {
// Create a new root node // Create a new root node
let root = TSTNode::new_root(); let root = TSTNode::new_root();
@ -29,17 +29,14 @@ pub fn new_tst(path: &str, reset: bool) -> Result<TST, Error> {
id: None, id: None,
data: &root.serialize(), data: &root.serialize(),
})?; })?;
Some(root_id) Some(root_id)
} else { } else {
// Use existing root node // Use existing root node
Some(1) // Root node always has ID 1 Some(1) // Root node always has ID 1
}; };
Ok(TST { Ok(TST { db, root_id })
db,
root_id,
})
} }
/// Sets a key-value pair in the tree. /// Sets a key-value pair in the tree.
@ -47,45 +44,51 @@ pub fn set(tree: &mut TST, key: &str, value: Vec<u8>) -> Result<(), Error> {
if key.is_empty() { if key.is_empty() {
return Err(Error::InvalidOperation("Empty key not allowed".to_string())); return Err(Error::InvalidOperation("Empty key not allowed".to_string()));
} }
let root_id = match tree.root_id { let root_id = match tree.root_id {
Some(id) => id, Some(id) => id,
None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), None => return Err(Error::InvalidOperation("Tree not initialized".to_string())),
}; };
let chars: Vec<char> = key.chars().collect(); let chars: Vec<char> = key.chars().collect();
set_recursive(tree, root_id, &chars, 0, value)?; set_recursive(tree, root_id, &chars, 0, value)?;
Ok(()) Ok(())
} }
/// Recursive helper function for setting a key-value pair. /// Recursive helper function for setting a key-value pair.
fn set_recursive(tree: &mut TST, node_id: u32, chars: &[char], pos: usize, value: Vec<u8>) -> Result<u32, Error> { fn set_recursive(
tree: &mut TST,
node_id: u32,
chars: &[char],
pos: usize,
value: Vec<u8>,
) -> Result<u32, Error> {
let mut node = tree.get_node(node_id)?; let mut node = tree.get_node(node_id)?;
if pos >= chars.len() { if pos >= chars.len() {
// We've reached the end of the key // We've reached the end of the key
node.is_end_of_key = true; node.is_end_of_key = true;
node.value = value; node.value = value;
return tree.save_node(Some(node_id), &node); return tree.save_node(Some(node_id), &node);
} }
let current_char = chars[pos]; let current_char = chars[pos];
if node.character == '\0' { if node.character == '\0' {
// Root node or empty node, set the character // Root node or empty node, set the character
node.character = current_char; node.character = current_char;
let node_id = tree.save_node(Some(node_id), &node)?; let node_id = tree.save_node(Some(node_id), &node)?;
// Continue with the next character // Continue with the next character
if pos + 1 < chars.len() { if pos + 1 < chars.len() {
let new_node = TSTNode::new(chars[pos + 1], Vec::new(), false); let new_node = TSTNode::new(chars[pos + 1], Vec::new(), false);
let new_id = tree.save_node(None, &new_node)?; let new_id = tree.save_node(None, &new_node)?;
let mut updated_node = tree.get_node(node_id)?; let mut updated_node = tree.get_node(node_id)?;
updated_node.middle_id = Some(new_id); updated_node.middle_id = Some(new_id);
tree.save_node(Some(node_id), &updated_node)?; tree.save_node(Some(node_id), &updated_node)?;
return set_recursive(tree, new_id, chars, pos + 1, value); return set_recursive(tree, new_id, chars, pos + 1, value);
} else { } else {
// This is the last character // 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); return tree.save_node(Some(node_id), &updated_node);
} }
} }
if current_char < node.character { if current_char < node.character {
// Go left // Go left
if let Some(left_id) = node.left_id { 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 // Create new left node
let new_node = TSTNode::new(current_char, Vec::new(), false); let new_node = TSTNode::new(current_char, Vec::new(), false);
let new_id = tree.save_node(None, &new_node)?; let new_id = tree.save_node(None, &new_node)?;
// Update current node // Update current node
node.left_id = Some(new_id); node.left_id = Some(new_id);
tree.save_node(Some(node_id), &node)?; tree.save_node(Some(node_id), &node)?;
return set_recursive(tree, new_id, chars, pos, value); return set_recursive(tree, new_id, chars, pos, value);
} }
} else if current_char > node.character { } 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 // Create new right node
let new_node = TSTNode::new(current_char, Vec::new(), false); let new_node = TSTNode::new(current_char, Vec::new(), false);
let new_id = tree.save_node(None, &new_node)?; let new_id = tree.save_node(None, &new_node)?;
// Update current node // Update current node
node.right_id = Some(new_id); node.right_id = Some(new_id);
tree.save_node(Some(node_id), &node)?; tree.save_node(Some(node_id), &node)?;
return set_recursive(tree, new_id, chars, pos, value); return set_recursive(tree, new_id, chars, pos, value);
} }
} else { } else {
@ -134,18 +137,18 @@ fn set_recursive(tree: &mut TST, node_id: u32, chars: &[char], pos: usize, value
node.value = value; node.value = value;
return tree.save_node(Some(node_id), &node); return tree.save_node(Some(node_id), &node);
} }
if let Some(middle_id) = node.middle_id { if let Some(middle_id) = node.middle_id {
return set_recursive(tree, middle_id, chars, pos + 1, value); return set_recursive(tree, middle_id, chars, pos + 1, value);
} else { } else {
// Create new middle node // Create new middle node
let new_node = TSTNode::new(chars[pos + 1], Vec::new(), false); let new_node = TSTNode::new(chars[pos + 1], Vec::new(), false);
let new_id = tree.save_node(None, &new_node)?; let new_id = tree.save_node(None, &new_node)?;
// Update current node // Update current node
node.middle_id = Some(new_id); node.middle_id = Some(new_id);
tree.save_node(Some(node_id), &node)?; tree.save_node(Some(node_id), &node)?;
return set_recursive(tree, new_id, chars, pos + 1, value); return set_recursive(tree, new_id, chars, pos + 1, value);
} }
} }
@ -156,15 +159,15 @@ pub fn get(tree: &mut TST, key: &str) -> Result<Vec<u8>, Error> {
if key.is_empty() { if key.is_empty() {
return Err(Error::InvalidOperation("Empty key not allowed".to_string())); return Err(Error::InvalidOperation("Empty key not allowed".to_string()));
} }
let root_id = match tree.root_id { let root_id = match tree.root_id {
Some(id) => id, Some(id) => id,
None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), None => return Err(Error::InvalidOperation("Tree not initialized".to_string())),
}; };
let chars: Vec<char> = key.chars().collect(); let chars: Vec<char> = key.chars().collect();
let node_id = find_node(tree, root_id, &chars, 0)?; let node_id = find_node(tree, root_id, &chars, 0)?;
let node = tree.get_node(node_id)?; let node = tree.get_node(node_id)?;
if node.is_end_of_key { if node.is_end_of_key {
Ok(node.value.clone()) Ok(node.value.clone())
@ -176,13 +179,13 @@ pub fn get(tree: &mut TST, key: &str) -> Result<Vec<u8>, Error> {
/// Finds a node by key. /// Finds a node by key.
fn find_node(tree: &mut TST, node_id: u32, chars: &[char], pos: usize) -> Result<u32, Error> { fn find_node(tree: &mut TST, node_id: u32, chars: &[char], pos: usize) -> Result<u32, Error> {
let node = tree.get_node(node_id)?; let node = tree.get_node(node_id)?;
if pos >= chars.len() { if pos >= chars.len() {
return Ok(node_id); return Ok(node_id);
} }
let current_char = chars[pos]; let current_char = chars[pos];
if current_char < node.character { if current_char < node.character {
// Go left // Go left
if let Some(left_id) = node.left_id { 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() { if key.is_empty() {
return Err(Error::InvalidOperation("Empty key not allowed".to_string())); return Err(Error::InvalidOperation("Empty key not allowed".to_string()));
} }
let root_id = match tree.root_id { let root_id = match tree.root_id {
Some(id) => id, Some(id) => id,
None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), None => return Err(Error::InvalidOperation("Tree not initialized".to_string())),
}; };
let chars: Vec<char> = key.chars().collect(); let chars: Vec<char> = key.chars().collect();
let node_id = find_node(tree, root_id, &chars, 0)?; let node_id = find_node(tree, root_id, &chars, 0)?;
let mut node = tree.get_node(node_id)?; let mut node = tree.get_node(node_id)?;
if !node.is_end_of_key { if !node.is_end_of_key {
return Err(Error::KeyNotFound(key.to_string())); return Err(Error::KeyNotFound(key.to_string()));
} }
// If the node has a middle child, just mark it as not end of key // 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() { if node.middle_id.is_some() || node.left_id.is_some() || node.right_id.is_some() {
node.is_end_of_key = false; 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)?; tree.save_node(Some(node_id), &node)?;
return Ok(()); return Ok(());
} }
// Otherwise, we need to remove the node and update its parent // Otherwise, we need to remove the node and update its parent
// This is more complex and would require tracking the path to the node // 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 // For simplicity, we'll just mark it as not end of key for now
node.is_end_of_key = false; node.is_end_of_key = false;
node.value = Vec::new(); node.value = Vec::new();
tree.save_node(Some(node_id), &node)?; tree.save_node(Some(node_id), &node)?;
Ok(()) Ok(())
} }
@ -255,46 +258,51 @@ pub fn list(tree: &mut TST, prefix: &str) -> Result<Vec<String>, Error> {
Some(id) => id, Some(id) => id,
None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), None => return Err(Error::InvalidOperation("Tree not initialized".to_string())),
}; };
let mut result = Vec::new(); let mut result = Vec::new();
// Handle empty prefix case - will return all keys // Handle empty prefix case - will return all keys
if prefix.is_empty() { if prefix.is_empty() {
collect_all_keys(tree, root_id, String::new(), &mut result)?; collect_all_keys(tree, root_id, String::new(), &mut result)?;
return Ok(result); return Ok(result);
} }
// Find the node corresponding to the prefix // Find the node corresponding to the prefix
let chars: Vec<char> = prefix.chars().collect(); let chars: Vec<char> = prefix.chars().collect();
let node_id = match find_prefix_node(tree, root_id, &chars, 0) { let node_id = match find_prefix_node(tree, root_id, &chars, 0) {
Ok(id) => id, Ok(id) => id,
Err(_) => return Ok(Vec::new()), // Prefix not found, return empty list Err(_) => return Ok(Vec::new()), // Prefix not found, return empty list
}; };
// For empty prefix, we start with an empty string // For empty prefix, we start with an empty string
// For non-empty prefix, we start with the prefix minus the last character // For non-empty prefix, we start with the prefix minus the last character
// (since the last character is in the node we found) // (since the last character is in the node we found)
let prefix_base = if chars.len() > 1 { let prefix_base = if chars.len() > 1 {
chars[0..chars.len()-1].iter().collect() chars[0..chars.len() - 1].iter().collect()
} else { } else {
String::new() String::new()
}; };
// Collect all keys from the subtree // Collect all keys from the subtree
collect_keys_with_prefix(tree, node_id, prefix_base, &mut result)?; collect_keys_with_prefix(tree, node_id, prefix_base, &mut result)?;
Ok(result) Ok(result)
} }
/// Finds the node corresponding to a prefix. /// Finds the node corresponding to a prefix.
fn find_prefix_node(tree: &mut TST, node_id: u32, chars: &[char], pos: usize) -> Result<u32, Error> { fn find_prefix_node(
tree: &mut TST,
node_id: u32,
chars: &[char],
pos: usize,
) -> Result<u32, Error> {
if pos >= chars.len() { if pos >= chars.len() {
return Ok(node_id); return Ok(node_id);
} }
let node = tree.get_node(node_id)?; let node = tree.get_node(node_id)?;
let current_char = chars[pos]; let current_char = chars[pos];
if current_char < node.character { if current_char < node.character {
// Go left // Go left
if let Some(left_id) = node.left_id { if let Some(left_id) = node.left_id {
@ -331,32 +339,32 @@ fn collect_keys_with_prefix(
result: &mut Vec<String>, result: &mut Vec<String>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let node = tree.get_node(node_id)?; let node = tree.get_node(node_id)?;
let mut new_path = current_path.clone(); let mut new_path = current_path.clone();
// For non-root nodes, add the character to the path // For non-root nodes, add the character to the path
if node.character != '\0' { if node.character != '\0' {
new_path.push(node.character); new_path.push(node.character);
} }
// If this node is an end of key, add it to the result // If this node is an end of key, add it to the result
if node.is_end_of_key { if node.is_end_of_key {
result.push(new_path.clone()); result.push(new_path.clone());
} }
// Recursively collect keys from all children // Recursively collect keys from all children
if let Some(left_id) = node.left_id { if let Some(left_id) = node.left_id {
collect_keys_with_prefix(tree, left_id, current_path.clone(), result)?; collect_keys_with_prefix(tree, left_id, current_path.clone(), result)?;
} }
if let Some(middle_id) = node.middle_id { if let Some(middle_id) = node.middle_id {
collect_keys_with_prefix(tree, middle_id, new_path.clone(), result)?; collect_keys_with_prefix(tree, middle_id, new_path.clone(), result)?;
} }
if let Some(right_id) = node.right_id { if let Some(right_id) = node.right_id {
collect_keys_with_prefix(tree, right_id, current_path.clone(), result)?; collect_keys_with_prefix(tree, right_id, current_path.clone(), result)?;
} }
Ok(()) Ok(())
} }
@ -368,32 +376,32 @@ fn collect_all_keys(
result: &mut Vec<String>, result: &mut Vec<String>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let node = tree.get_node(node_id)?; let node = tree.get_node(node_id)?;
let mut new_path = current_path.clone(); let mut new_path = current_path.clone();
// Skip adding the character for the root node // Skip adding the character for the root node
if node.character != '\0' { if node.character != '\0' {
new_path.push(node.character); new_path.push(node.character);
} }
// If this node is an end of key, add it to the result // If this node is an end of key, add it to the result
if node.is_end_of_key { if node.is_end_of_key {
result.push(new_path.clone()); result.push(new_path.clone());
} }
// Recursively collect keys from all children // Recursively collect keys from all children
if let Some(left_id) = node.left_id { if let Some(left_id) = node.left_id {
collect_all_keys(tree, left_id, current_path.clone(), result)?; collect_all_keys(tree, left_id, current_path.clone(), result)?;
} }
if let Some(middle_id) = node.middle_id { if let Some(middle_id) = node.middle_id {
collect_all_keys(tree, middle_id, new_path.clone(), result)?; collect_all_keys(tree, middle_id, new_path.clone(), result)?;
} }
if let Some(right_id) = node.right_id { if let Some(right_id) = node.right_id {
collect_all_keys(tree, right_id, current_path.clone(), result)?; collect_all_keys(tree, right_id, current_path.clone(), result)?;
} }
Ok(()) Ok(())
} }
@ -401,23 +409,23 @@ fn collect_all_keys(
pub fn getall(tree: &mut TST, prefix: &str) -> Result<Vec<Vec<u8>>, Error> { pub fn getall(tree: &mut TST, prefix: &str) -> Result<Vec<Vec<u8>>, Error> {
// Get all matching keys // Get all matching keys
let keys = list(tree, prefix)?; let keys = list(tree, prefix)?;
// Get values for each key // Get values for each key
let mut values = Vec::new(); let mut values = Vec::new();
let mut errors = Vec::new(); let mut errors = Vec::new();
for key in keys { for key in keys {
match get(tree, &key) { match get(tree, &key) {
Ok(value) => values.push(value), 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 we couldn't get any values but had keys, return the first error
if values.is_empty() && !errors.is_empty() { if values.is_empty() && !errors.is_empty() {
return Err(Error::InvalidOperation(errors.join("; "))); return Err(Error::InvalidOperation(errors.join("; ")));
} }
Ok(values) Ok(values)
} }
@ -442,4 +450,4 @@ impl TST {
Err(err) => Err(Error::OurDB(err)), Err(err) => Err(Error::OurDB(err)),
} }
} }
} }

View File

@ -10,17 +10,17 @@ impl TSTNode {
/// Serializes a node to bytes for storage. /// Serializes a node to bytes for storage.
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
let mut buffer = Vec::new(); let mut buffer = Vec::new();
// Version // Version
buffer.push(VERSION); buffer.push(VERSION);
// Character (as UTF-32) // Character (as UTF-32)
let char_bytes = (self.character as u32).to_le_bytes(); let char_bytes = (self.character as u32).to_le_bytes();
buffer.extend_from_slice(&char_bytes); buffer.extend_from_slice(&char_bytes);
// Is end of key // Is end of key
buffer.push(if self.is_end_of_key { 1 } else { 0 }); buffer.push(if self.is_end_of_key { 1 } else { 0 });
// Value (only if is_end_of_key) // Value (only if is_end_of_key)
if self.is_end_of_key { if self.is_end_of_key {
let value_len = (self.value.len() as u32).to_le_bytes(); let value_len = (self.value.len() as u32).to_le_bytes();
@ -30,88 +30,100 @@ impl TSTNode {
// Zero length // Zero length
buffer.extend_from_slice(&[0, 0, 0, 0]); buffer.extend_from_slice(&[0, 0, 0, 0]);
} }
// Child pointers // Child pointers
let left_id = self.left_id.unwrap_or(0).to_le_bytes(); let left_id = self.left_id.unwrap_or(0).to_le_bytes();
buffer.extend_from_slice(&left_id); buffer.extend_from_slice(&left_id);
let middle_id = self.middle_id.unwrap_or(0).to_le_bytes(); let middle_id = self.middle_id.unwrap_or(0).to_le_bytes();
buffer.extend_from_slice(&middle_id); buffer.extend_from_slice(&middle_id);
let right_id = self.right_id.unwrap_or(0).to_le_bytes(); let right_id = self.right_id.unwrap_or(0).to_le_bytes();
buffer.extend_from_slice(&right_id); buffer.extend_from_slice(&right_id);
buffer buffer
} }
/// Deserializes bytes to a node. /// Deserializes bytes to a node.
pub fn deserialize(data: &[u8]) -> Result<Self, Error> { pub fn deserialize(data: &[u8]) -> Result<Self, Error> {
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())); return Err(Error::Deserialization("Data too short".to_string()));
} }
let mut pos = 0; let mut pos = 0;
// Version // Version
let version = data[pos]; let version = data[pos];
pos += 1; pos += 1;
if version != VERSION { if version != VERSION {
return Err(Error::Deserialization(format!("Unsupported version: {}", version))); return Err(Error::Deserialization(format!(
"Unsupported version: {}",
version
)));
} }
// Character // 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 char_code = u32::from_le_bytes(char_bytes);
let character = char::from_u32(char_code) let character = char::from_u32(char_code)
.ok_or_else(|| Error::Deserialization("Invalid character".to_string()))?; .ok_or_else(|| Error::Deserialization("Invalid character".to_string()))?;
pos += 4; pos += 4;
// Is end of key // Is end of key
let is_end_of_key = data[pos] != 0; let is_end_of_key = data[pos] != 0;
pos += 1; pos += 1;
// Value length // 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; let value_len = u32::from_le_bytes(value_len_bytes) as usize;
pos += 4; pos += 4;
// Value // Value
let value = if value_len > 0 { let value = if value_len > 0 {
if pos + value_len > data.len() { 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 { } else {
Vec::new() Vec::new()
}; };
pos += value_len; pos += value_len;
// Child pointers // Child pointers
if pos + 12 > data.len() { 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); let left_id = u32::from_le_bytes(left_id_bytes);
pos += 4; 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); let middle_id = u32::from_le_bytes(middle_id_bytes);
pos += 4; 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); let right_id = u32::from_le_bytes(right_id_bytes);
Ok(TSTNode { Ok(TSTNode {
character, character,
value, value,
is_end_of_key, is_end_of_key,
left_id: if left_id == 0 { None } else { Some(left_id) }, 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) }, right_id: if right_id == 0 { None } else { Some(right_id) },
}) })
} }
} }
// Function removed as it was unused // Function removed as it was unused

View File

@ -1,24 +1,24 @@
use tst::TST;
use std::env::temp_dir; use std::env::temp_dir;
use std::fs; use std::fs;
use std::time::SystemTime; use std::time::SystemTime;
use tst::TST;
fn get_test_db_path() -> String { fn get_test_db_path() -> String {
let timestamp = SystemTime::now() let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH) .duration_since(SystemTime::UNIX_EPOCH)
.unwrap() .unwrap()
.as_nanos(); .as_nanos();
let path = temp_dir().join(format!("tst_test_{}", timestamp)); let path = temp_dir().join(format!("tst_test_{}", timestamp));
// If the path exists, remove it first // If the path exists, remove it first
if path.exists() { if path.exists() {
let _ = fs::remove_dir_all(&path); let _ = fs::remove_dir_all(&path);
} }
// Create the directory // Create the directory
fs::create_dir_all(&path).unwrap(); fs::create_dir_all(&path).unwrap();
path.to_string_lossy().to_string() path.to_string_lossy().to_string()
} }
@ -30,44 +30,44 @@ fn cleanup_test_db(path: &str) {
#[test] #[test]
fn test_create_tst() { fn test_create_tst() {
let path = get_test_db_path(); let path = get_test_db_path();
let result = TST::new(&path, true); let result = TST::new(&path, true);
match &result { match &result {
Ok(_) => (), Ok(_) => (),
Err(e) => println!("Error creating TST: {:?}", e), Err(e) => println!("Error creating TST: {:?}", e),
} }
assert!(result.is_ok()); assert!(result.is_ok());
if let Ok(mut tst) = result { if let Ok(mut tst) = result {
// Make sure we can perform a basic operation // Make sure we can perform a basic operation
let set_result = tst.set("test_key", b"test_value".to_vec()); let set_result = tst.set("test_key", b"test_value".to_vec());
assert!(set_result.is_ok()); assert!(set_result.is_ok());
} }
cleanup_test_db(&path); cleanup_test_db(&path);
} }
#[test] #[test]
fn test_set_and_get() { fn test_set_and_get() {
let path = get_test_db_path(); let path = get_test_db_path();
// Create a new TST with reset=true to ensure a clean state // Create a new TST with reset=true to ensure a clean state
let result = TST::new(&path, true); let result = TST::new(&path, true);
assert!(result.is_ok()); assert!(result.is_ok());
let mut tree = result.unwrap(); let mut tree = result.unwrap();
// Test setting and getting a key // Test setting and getting a key
let key = "test_key"; let key = "test_key";
let value = b"test_value".to_vec(); let value = b"test_value".to_vec();
let set_result = tree.set(key, value.clone()); let set_result = tree.set(key, value.clone());
assert!(set_result.is_ok()); assert!(set_result.is_ok());
let get_result = tree.get(key); let get_result = tree.get(key);
assert!(get_result.is_ok()); assert!(get_result.is_ok());
assert_eq!(get_result.unwrap(), value); assert_eq!(get_result.unwrap(), value);
// Make sure to clean up properly // Make sure to clean up properly
cleanup_test_db(&path); cleanup_test_db(&path);
} }
@ -75,45 +75,45 @@ fn test_set_and_get() {
#[test] #[test]
fn test_get_nonexistent_key() { fn test_get_nonexistent_key() {
let path = get_test_db_path(); let path = get_test_db_path();
let mut tree = TST::new(&path, true).unwrap(); let mut tree = TST::new(&path, true).unwrap();
// Test getting a key that doesn't exist // Test getting a key that doesn't exist
let get_result = tree.get("nonexistent_key"); let get_result = tree.get("nonexistent_key");
assert!(get_result.is_err()); assert!(get_result.is_err());
cleanup_test_db(&path); cleanup_test_db(&path);
} }
#[test] #[test]
fn test_delete() { fn test_delete() {
let path = get_test_db_path(); let path = get_test_db_path();
// Create a new TST with reset=true to ensure a clean state // Create a new TST with reset=true to ensure a clean state
let result = TST::new(&path, true); let result = TST::new(&path, true);
assert!(result.is_ok()); assert!(result.is_ok());
let mut tree = result.unwrap(); let mut tree = result.unwrap();
// Set a key // Set a key
let key = "delete_test"; let key = "delete_test";
let value = b"to_be_deleted".to_vec(); let value = b"to_be_deleted".to_vec();
let set_result = tree.set(key, value); let set_result = tree.set(key, value);
assert!(set_result.is_ok()); assert!(set_result.is_ok());
// Verify it exists // Verify it exists
let get_result = tree.get(key); let get_result = tree.get(key);
assert!(get_result.is_ok()); assert!(get_result.is_ok());
// Delete it // Delete it
let delete_result = tree.delete(key); let delete_result = tree.delete(key);
assert!(delete_result.is_ok()); assert!(delete_result.is_ok());
// Verify it's gone // Verify it's gone
let get_after_delete = tree.get(key); let get_after_delete = tree.get(key);
assert!(get_after_delete.is_err()); assert!(get_after_delete.is_err());
// Make sure to clean up properly // Make sure to clean up properly
cleanup_test_db(&path); cleanup_test_db(&path);
} }
@ -121,28 +121,28 @@ fn test_delete() {
#[test] #[test]
fn test_multiple_keys() { fn test_multiple_keys() {
let path = get_test_db_path(); let path = get_test_db_path();
// Create a new TST with reset=true to ensure a clean state // Create a new TST with reset=true to ensure a clean state
let result = TST::new(&path, true); let result = TST::new(&path, true);
assert!(result.is_ok()); assert!(result.is_ok());
let mut tree = result.unwrap(); let mut tree = result.unwrap();
// Insert multiple keys - use fewer keys to avoid filling the lookup table // Insert multiple keys - use fewer keys to avoid filling the lookup table
let keys = ["apple", "banana", "cherry"]; let keys = ["apple", "banana", "cherry"];
for (i, key) in keys.iter().enumerate() { for (i, key) in keys.iter().enumerate() {
let value = format!("value_{}", i).into_bytes(); let value = format!("value_{}", i).into_bytes();
let set_result = tree.set(key, value); let set_result = tree.set(key, value);
// Print error if set fails // Print error if set fails
if set_result.is_err() { if set_result.is_err() {
println!("Error setting key '{}': {:?}", key, set_result); println!("Error setting key '{}': {:?}", key, set_result);
} }
assert!(set_result.is_ok()); assert!(set_result.is_ok());
} }
// Verify all keys exist // Verify all keys exist
for (i, key) in keys.iter().enumerate() { for (i, key) in keys.iter().enumerate() {
let expected_value = format!("value_{}", i).into_bytes(); let expected_value = format!("value_{}", i).into_bytes();
@ -150,7 +150,7 @@ fn test_multiple_keys() {
assert!(get_result.is_ok()); assert!(get_result.is_ok());
assert_eq!(get_result.unwrap(), expected_value); assert_eq!(get_result.unwrap(), expected_value);
} }
// Make sure to clean up properly // Make sure to clean up properly
cleanup_test_db(&path); cleanup_test_db(&path);
} }
@ -158,56 +158,53 @@ fn test_multiple_keys() {
#[test] #[test]
fn test_list_prefix() { fn test_list_prefix() {
let path = get_test_db_path(); let path = get_test_db_path();
// Create a new TST with reset=true to ensure a clean state // Create a new TST with reset=true to ensure a clean state
let result = TST::new(&path, true); let result = TST::new(&path, true);
assert!(result.is_ok()); assert!(result.is_ok());
let mut tree = result.unwrap(); let mut tree = result.unwrap();
// Insert keys with common prefixes - use fewer keys to avoid filling the lookup table // Insert keys with common prefixes - use fewer keys to avoid filling the lookup table
let keys = [ let keys = ["apple", "application", "append", "banana", "bandana"];
"apple", "application", "append",
"banana", "bandana"
];
for key in &keys { for key in &keys {
let set_result = tree.set(key, key.as_bytes().to_vec()); let set_result = tree.set(key, key.as_bytes().to_vec());
assert!(set_result.is_ok()); assert!(set_result.is_ok());
} }
// Test prefix "app" // Test prefix "app"
let list_result = tree.list("app"); let list_result = tree.list("app");
assert!(list_result.is_ok()); assert!(list_result.is_ok());
let app_keys = list_result.unwrap(); let app_keys = list_result.unwrap();
// Print the keys for debugging // Print the keys for debugging
println!("Keys with prefix 'app':"); println!("Keys with prefix 'app':");
for key in &app_keys { for key in &app_keys {
println!(" {}", key); println!(" {}", key);
} }
// Check that each key is present // Check that each key is present
assert!(app_keys.contains(&"apple".to_string())); assert!(app_keys.contains(&"apple".to_string()));
assert!(app_keys.contains(&"application".to_string())); assert!(app_keys.contains(&"application".to_string()));
assert!(app_keys.contains(&"append".to_string())); assert!(app_keys.contains(&"append".to_string()));
// Test prefix "ban" // Test prefix "ban"
let list_result = tree.list("ban"); let list_result = tree.list("ban");
assert!(list_result.is_ok()); assert!(list_result.is_ok());
let ban_keys = list_result.unwrap(); let ban_keys = list_result.unwrap();
assert!(ban_keys.contains(&"banana".to_string())); assert!(ban_keys.contains(&"banana".to_string()));
assert!(ban_keys.contains(&"bandana".to_string())); assert!(ban_keys.contains(&"bandana".to_string()));
// Test non-existent prefix // Test non-existent prefix
let list_result = tree.list("z"); let list_result = tree.list("z");
assert!(list_result.is_ok()); assert!(list_result.is_ok());
let z_keys = list_result.unwrap(); let z_keys = list_result.unwrap();
assert_eq!(z_keys.len(), 0); assert_eq!(z_keys.len(), 0);
// Make sure to clean up properly // Make sure to clean up properly
cleanup_test_db(&path); cleanup_test_db(&path);
} }
@ -215,46 +212,44 @@ fn test_list_prefix() {
#[test] #[test]
fn test_getall_prefix() { fn test_getall_prefix() {
let path = get_test_db_path(); let path = get_test_db_path();
// Create a new TST with reset=true to ensure a clean state // Create a new TST with reset=true to ensure a clean state
let result = TST::new(&path, true); let result = TST::new(&path, true);
assert!(result.is_ok()); assert!(result.is_ok());
let mut tree = result.unwrap(); let mut tree = result.unwrap();
// Insert keys with common prefixes - use fewer keys to avoid filling the lookup table // Insert keys with common prefixes - use fewer keys to avoid filling the lookup table
let keys = [ let keys = ["apple", "application", "append"];
"apple", "application", "append"
];
for key in &keys { for key in &keys {
let set_result = tree.set(key, key.as_bytes().to_vec()); let set_result = tree.set(key, key.as_bytes().to_vec());
assert!(set_result.is_ok()); assert!(set_result.is_ok());
} }
// Test getall with prefix "app" // Test getall with prefix "app"
let getall_result = tree.getall("app"); let getall_result = tree.getall("app");
assert!(getall_result.is_ok()); assert!(getall_result.is_ok());
let app_values = getall_result.unwrap(); let app_values = getall_result.unwrap();
// Convert values to strings for easier comparison // Convert values to strings for easier comparison
let app_value_strings: Vec<String> = app_values let app_value_strings: Vec<String> = app_values
.iter() .iter()
.map(|v| String::from_utf8_lossy(v).to_string()) .map(|v| String::from_utf8_lossy(v).to_string())
.collect(); .collect();
// Print the values for debugging // Print the values for debugging
println!("Values with prefix 'app':"); println!("Values with prefix 'app':");
for value in &app_value_strings { for value in &app_value_strings {
println!(" {}", value); println!(" {}", value);
} }
// Check that each value is present // Check that each value is present
assert!(app_value_strings.contains(&"apple".to_string())); assert!(app_value_strings.contains(&"apple".to_string()));
assert!(app_value_strings.contains(&"application".to_string())); assert!(app_value_strings.contains(&"application".to_string()));
assert!(app_value_strings.contains(&"append".to_string())); assert!(app_value_strings.contains(&"append".to_string()));
// Make sure to clean up properly // Make sure to clean up properly
cleanup_test_db(&path); cleanup_test_db(&path);
} }
@ -262,38 +257,38 @@ fn test_getall_prefix() {
#[test] #[test]
fn test_empty_prefix() { fn test_empty_prefix() {
let path = get_test_db_path(); let path = get_test_db_path();
// Create a new TST with reset=true to ensure a clean state // Create a new TST with reset=true to ensure a clean state
let result = TST::new(&path, true); let result = TST::new(&path, true);
assert!(result.is_ok()); assert!(result.is_ok());
let mut tree = result.unwrap(); let mut tree = result.unwrap();
// Insert some keys // Insert some keys
let keys = ["apple", "banana", "cherry"]; let keys = ["apple", "banana", "cherry"];
for key in &keys { for key in &keys {
let set_result = tree.set(key, key.as_bytes().to_vec()); let set_result = tree.set(key, key.as_bytes().to_vec());
assert!(set_result.is_ok()); assert!(set_result.is_ok());
} }
// Test list with empty prefix (should return all keys) // Test list with empty prefix (should return all keys)
let list_result = tree.list(""); let list_result = tree.list("");
assert!(list_result.is_ok()); assert!(list_result.is_ok());
let all_keys = list_result.unwrap(); let all_keys = list_result.unwrap();
// Print the keys for debugging // Print the keys for debugging
println!("Keys with empty prefix:"); println!("Keys with empty prefix:");
for key in &all_keys { for key in &all_keys {
println!(" {}", key); println!(" {}", key);
} }
// Check that each key is present // Check that each key is present
for key in &keys { for key in &keys {
assert!(all_keys.contains(&key.to_string())); assert!(all_keys.contains(&key.to_string()));
} }
// Make sure to clean up properly // Make sure to clean up properly
cleanup_test_db(&path); cleanup_test_db(&path);
} }

View File

@ -1,24 +1,24 @@
use tst::TST;
use std::env::temp_dir; use std::env::temp_dir;
use std::fs; use std::fs;
use std::time::SystemTime; use std::time::SystemTime;
use tst::TST;
fn get_test_db_path() -> String { fn get_test_db_path() -> String {
let timestamp = SystemTime::now() let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH) .duration_since(SystemTime::UNIX_EPOCH)
.unwrap() .unwrap()
.as_nanos(); .as_nanos();
let path = temp_dir().join(format!("tst_prefix_test_{}", timestamp)); let path = temp_dir().join(format!("tst_prefix_test_{}", timestamp));
// If the path exists, remove it first // If the path exists, remove it first
if path.exists() { if path.exists() {
let _ = fs::remove_dir_all(&path); let _ = fs::remove_dir_all(&path);
} }
// Create the directory // Create the directory
fs::create_dir_all(&path).unwrap(); fs::create_dir_all(&path).unwrap();
path.to_string_lossy().to_string() path.to_string_lossy().to_string()
} }
@ -30,9 +30,9 @@ fn cleanup_test_db(path: &str) {
#[test] #[test]
fn test_prefix_with_common_prefixes() { fn test_prefix_with_common_prefixes() {
let path = get_test_db_path(); let path = get_test_db_path();
let mut tree = TST::new(&path, true).unwrap(); let mut tree = TST::new(&path, true).unwrap();
// Insert keys with common prefixes // Insert keys with common prefixes
let test_data = [ let test_data = [
("test", b"value1".to_vec()), ("test", b"value1".to_vec()),
@ -41,34 +41,34 @@ fn test_prefix_with_common_prefixes() {
("tests", b"value4".to_vec()), ("tests", b"value4".to_vec()),
("tester", b"value5".to_vec()), ("tester", b"value5".to_vec()),
]; ];
for (key, value) in &test_data { for (key, value) in &test_data {
tree.set(key, value.clone()).unwrap(); tree.set(key, value.clone()).unwrap();
} }
// Test prefix "test" // Test prefix "test"
let keys = tree.list("test").unwrap(); let keys = tree.list("test").unwrap();
assert_eq!(keys.len(), 5); assert_eq!(keys.len(), 5);
for (key, _) in &test_data { for (key, _) in &test_data {
assert!(keys.contains(&key.to_string())); assert!(keys.contains(&key.to_string()));
} }
// Test prefix "teste" // Test prefix "teste"
let keys = tree.list("teste").unwrap(); let keys = tree.list("teste").unwrap();
assert_eq!(keys.len(), 2); assert_eq!(keys.len(), 2);
assert!(keys.contains(&"tested".to_string())); assert!(keys.contains(&"tested".to_string()));
assert!(keys.contains(&"tester".to_string())); assert!(keys.contains(&"tester".to_string()));
cleanup_test_db(&path); cleanup_test_db(&path);
} }
#[test] #[test]
fn test_prefix_with_different_prefixes() { fn test_prefix_with_different_prefixes() {
let path = get_test_db_path(); let path = get_test_db_path();
let mut tree = TST::new(&path, true).unwrap(); let mut tree = TST::new(&path, true).unwrap();
// Insert keys with different prefixes // Insert keys with different prefixes
let test_data = [ let test_data = [
("apple", b"fruit1".to_vec()), ("apple", b"fruit1".to_vec()),
@ -77,64 +77,64 @@ fn test_prefix_with_different_prefixes() {
("date", b"fruit4".to_vec()), ("date", b"fruit4".to_vec()),
("elderberry", b"fruit5".to_vec()), ("elderberry", b"fruit5".to_vec()),
]; ];
for (key, value) in &test_data { for (key, value) in &test_data {
tree.set(key, value.clone()).unwrap(); tree.set(key, value.clone()).unwrap();
} }
// Test each prefix // Test each prefix
for (key, _) in &test_data { for (key, _) in &test_data {
let prefix = &key[0..1]; // First character let prefix = &key[0..1]; // First character
let keys = tree.list(prefix).unwrap(); let keys = tree.list(prefix).unwrap();
assert!(keys.contains(&key.to_string())); assert!(keys.contains(&key.to_string()));
} }
// Test non-existent prefix // Test non-existent prefix
let keys = tree.list("z").unwrap(); let keys = tree.list("z").unwrap();
assert_eq!(keys.len(), 0); assert_eq!(keys.len(), 0);
cleanup_test_db(&path); cleanup_test_db(&path);
} }
#[test] #[test]
fn test_prefix_with_empty_string() { fn test_prefix_with_empty_string() {
let path = get_test_db_path(); let path = get_test_db_path();
// Create a new TST with reset=true to ensure a clean state // Create a new TST with reset=true to ensure a clean state
let result = TST::new(&path, true); let result = TST::new(&path, true);
assert!(result.is_ok()); assert!(result.is_ok());
let mut tree = result.unwrap(); let mut tree = result.unwrap();
// Insert some keys // Insert some keys
let test_data = [ let test_data = [
("apple", b"fruit1".to_vec()), ("apple", b"fruit1".to_vec()),
("banana", b"fruit2".to_vec()), ("banana", b"fruit2".to_vec()),
("cherry", b"fruit3".to_vec()), ("cherry", b"fruit3".to_vec()),
]; ];
for (key, value) in &test_data { for (key, value) in &test_data {
let set_result = tree.set(key, value.clone()); let set_result = tree.set(key, value.clone());
assert!(set_result.is_ok()); assert!(set_result.is_ok());
} }
// Test empty prefix (should return all keys) // Test empty prefix (should return all keys)
let list_result = tree.list(""); let list_result = tree.list("");
assert!(list_result.is_ok()); assert!(list_result.is_ok());
let keys = list_result.unwrap(); let keys = list_result.unwrap();
// Print the keys for debugging // Print the keys for debugging
println!("Keys with empty prefix:"); println!("Keys with empty prefix:");
for key in &keys { for key in &keys {
println!(" {}", key); println!(" {}", key);
} }
// Check that each key is present // Check that each key is present
for (key, _) in &test_data { for (key, _) in &test_data {
assert!(keys.contains(&key.to_string())); assert!(keys.contains(&key.to_string()));
} }
// Make sure to clean up properly // Make sure to clean up properly
cleanup_test_db(&path); cleanup_test_db(&path);
} }
@ -142,9 +142,9 @@ fn test_prefix_with_empty_string() {
#[test] #[test]
fn test_getall_with_prefix() { fn test_getall_with_prefix() {
let path = get_test_db_path(); let path = get_test_db_path();
let mut tree = TST::new(&path, true).unwrap(); let mut tree = TST::new(&path, true).unwrap();
// Insert keys with common prefixes // Insert keys with common prefixes
let test_data = [ let test_data = [
("test", b"value1".to_vec()), ("test", b"value1".to_vec()),
@ -153,28 +153,28 @@ fn test_getall_with_prefix() {
("tests", b"value4".to_vec()), ("tests", b"value4".to_vec()),
("tester", b"value5".to_vec()), ("tester", b"value5".to_vec()),
]; ];
for (key, value) in &test_data { for (key, value) in &test_data {
tree.set(key, value.clone()).unwrap(); tree.set(key, value.clone()).unwrap();
} }
// Test getall with prefix "test" // Test getall with prefix "test"
let values = tree.getall("test").unwrap(); let values = tree.getall("test").unwrap();
assert_eq!(values.len(), 5); assert_eq!(values.len(), 5);
for (_, value) in &test_data { for (_, value) in &test_data {
assert!(values.contains(value)); assert!(values.contains(value));
} }
cleanup_test_db(&path); cleanup_test_db(&path);
} }
#[test] #[test]
fn test_prefix_with_unicode_characters() { fn test_prefix_with_unicode_characters() {
let path = get_test_db_path(); let path = get_test_db_path();
let mut tree = TST::new(&path, true).unwrap(); let mut tree = TST::new(&path, true).unwrap();
// Insert keys with Unicode characters // Insert keys with Unicode characters
let test_data = [ let test_data = [
("café", b"coffee".to_vec()), ("café", b"coffee".to_vec()),
@ -182,77 +182,86 @@ fn test_prefix_with_unicode_characters() {
("caffè", b"italian coffee".to_vec()), ("caffè", b"italian coffee".to_vec()),
("café au lait", b"coffee with milk".to_vec()), ("café au lait", b"coffee with milk".to_vec()),
]; ];
for (key, value) in &test_data { for (key, value) in &test_data {
tree.set(key, value.clone()).unwrap(); tree.set(key, value.clone()).unwrap();
} }
// Test prefix "café" // Test prefix "café"
let keys = tree.list("café").unwrap(); let keys = tree.list("café").unwrap();
// Print the keys for debugging // Print the keys for debugging
println!("Keys with prefix 'café':"); println!("Keys with prefix 'café':");
for key in &keys { for key in &keys {
println!(" {}", key); println!(" {}", key);
} }
// Check that the keys we expect are present // Check that the keys we expect are present
assert!(keys.contains(&"café".to_string())); assert!(keys.contains(&"café".to_string()));
assert!(keys.contains(&"café au lait".to_string())); assert!(keys.contains(&"café au lait".to_string()));
// We don't assert on the exact count because Unicode handling can vary // We don't assert on the exact count because Unicode handling can vary
// Test prefix "caf" // Test prefix "caf"
let keys = tree.list("caf").unwrap(); let keys = tree.list("caf").unwrap();
// Print the keys for debugging // Print the keys for debugging
println!("Keys with prefix 'caf':"); println!("Keys with prefix 'caf':");
for key in &keys { for key in &keys {
println!(" {}", key); println!(" {}", key);
} }
// Check that each key is present individually // Check that each key is present individually
// Due to Unicode handling, we need to be careful with exact matching // Due to Unicode handling, we need to be careful with exact matching
// The important thing is that we can find the keys we need // 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 // Check that we have at least the café and café au lait keys
assert!(keys.contains(&"café".to_string())); assert!(keys.contains(&"café".to_string()));
assert!(keys.contains(&"café au lait".to_string())); assert!(keys.contains(&"café au lait".to_string()));
// We don't assert on the exact count because Unicode handling can vary // We don't assert on the exact count because Unicode handling can vary
cleanup_test_db(&path); cleanup_test_db(&path);
} }
#[test] #[test]
fn test_prefix_with_long_keys() { fn test_prefix_with_long_keys() {
let path = get_test_db_path(); let path = get_test_db_path();
let mut tree = TST::new(&path, true).unwrap(); let mut tree = TST::new(&path, true).unwrap();
// Insert long keys // Insert long keys
let test_data = [ 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_1",
("this_is_a_very_long_key_for_testing_purposes_3", b"value3".to_vec()), 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()), ("this_is_another_long_key_for_testing", b"value4".to_vec()),
]; ];
for (key, value) in &test_data { for (key, value) in &test_data {
tree.set(key, value.clone()).unwrap(); tree.set(key, value.clone()).unwrap();
} }
// Test prefix "this_is_a_very" // Test prefix "this_is_a_very"
let keys = tree.list("this_is_a_very").unwrap(); let keys = tree.list("this_is_a_very").unwrap();
assert_eq!(keys.len(), 3); assert_eq!(keys.len(), 3);
// Test prefix "this_is" // Test prefix "this_is"
let keys = tree.list("this_is").unwrap(); let keys = tree.list("this_is").unwrap();
assert_eq!(keys.len(), 4); assert_eq!(keys.len(), 4);
for (key, _) in &test_data { for (key, _) in &test_data {
assert!(keys.contains(&key.to_string())); assert!(keys.contains(&key.to_string()));
} }
cleanup_test_db(&path); cleanup_test_db(&path);
} }