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)]

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");
@ -91,42 +107,76 @@ fn main() {
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,11 +1,10 @@
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
@ -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(
"set_description",
|calendar: &mut Calendar, desc: String| {
calendar.description = Some(desc); 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(
"get_calendar_by_id",
|_db: Arc<OurDB>, id: i64| -> Calendar {
// In a real implementation, this would retrieve the calendar from the database // In a real implementation, this would retrieve the calendar from the database
Calendar::new(Some(id as u32), "Retrieved Calendar") 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

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");
@ -18,10 +20,13 @@ fn main() {
"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);
@ -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());
@ -113,9 +123,18 @@ fn main() {
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,10 +145,14 @@ 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),
} }
@ -145,13 +168,26 @@ fn main() {
"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
@ -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,20 +262,26 @@ 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),
} }
@ -256,9 +301,18 @@ fn main() {
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 ---
@ -319,7 +373,10 @@ fn main() {
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
@ -42,7 +42,7 @@ fn main() -> Result<(), Box<EvalAltResult>> {
&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
@ -101,7 +108,12 @@ 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!("===========================");
@ -25,7 +25,10 @@ fn main() {
"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());
@ -38,7 +41,10 @@ fn main() {
"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(
@ -48,7 +54,10 @@ fn main() {
"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 ---
@ -59,7 +68,10 @@ fn main() {
"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(
@ -69,7 +81,10 @@ fn main() {
"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,7 +159,10 @@ 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 ---
@ -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,8 +20,13 @@ 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);

View File

@ -108,7 +108,7 @@ fn main() {
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
@ -117,7 +117,7 @@ fn main() {
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:");
@ -225,7 +225,7 @@ fn main() {
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
@ -234,7 +234,7 @@ fn main() {
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
@ -243,7 +243,7 @@ fn main() {
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.");

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,
),
] ]
} }
@ -172,19 +211,37 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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(
"create_proposal",
|id: i64, creator_id: String, title: String, description: String| {
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,
)
},
);
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);
@ -203,12 +260,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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(
"get_proposal_by_id",
|_db: Arc<OurDB>, id: i64| -> Proposal {
// In a real implementation, this would retrieve the proposal from the database // In a real implementation, this would retrieve the proposal from the database
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, "Retrieved Creator", "Retrieved Proposal", "Retrieved Description", start_date, end_date) 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 {
@ -217,7 +284,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}); });
// 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);
@ -242,33 +312,41 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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(
1,
"user_creator_123".to_string(), "user_creator_123".to_string(),
"Community Fund Allocation for Q3".to_string(), "Community Fund Allocation for Q3".to_string(),
"Proposal to allocate funds for community projects in the third quarter.".to_string()); "Proposal to allocate funds for community projects in the third quarter.".to_string(),
);
println!("Created Proposal: '{}' (ID: {})", println!(
"Created Proposal: '{}' (ID: {})",
get_title(&proposal), get_title(&proposal),
get_id(&proposal)); get_id(&proposal)
println!("Status: {}, Vote Status: {}", );
println!(
"Status: {}, Vote Status: {}",
get_status(&proposal), get_status(&proposal),
get_vote_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
@ -278,48 +356,52 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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: {}",
i,
get_ballot_user_id(&ballot),
get_ballot_option_id(&ballot), get_ballot_option_id(&ballot),
get_ballot_shares(&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:");
@ -330,9 +412,12 @@ 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));
@ -340,9 +425,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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!(
"Proposal ID: {}, Title: '{}'",
get_id(&proposal), get_id(&proposal),
get_title(&proposal)); get_title(&proposal)
);
} }
// Delete a proposal // Delete a proposal
@ -351,13 +438,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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(
&engine,
2,
"rhai_user".to_string(), "rhai_user".to_string(),
"Rhai Proposal".to_string(), "Rhai Proposal".to_string(),
"This proposal was created using a Rhai client function".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.");

View File

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

View File

@ -22,8 +22,13 @@ 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);

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

@ -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]
@ -30,7 +30,10 @@ mod rhai_access_module {
/// 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();
@ -41,7 +44,10 @@ 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();
@ -52,7 +58,10 @@ mod rhai_access_module {
} }
#[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();
@ -63,7 +72,10 @@ 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();
@ -74,7 +86,10 @@ 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();
@ -86,28 +101,44 @@ 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>) {
@ -119,56 +150,86 @@ pub fn register_access_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
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(
"save_access",
move |access: Access| -> Result<Access, Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone_set_access.set(&access) let result = db_clone_set_access.set(&access).map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_access: {}", e).into(), Position::NONE)))?; Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error set_access: {}", e).into(),
Position::NONE,
))
})?;
// Return the updated access with the correct ID // Return the updated access with the correct ID
Ok(result.1) 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(
"delete_access",
move |access: Access| -> Result<(), Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone_delete_access.collection::<Access>() let result = db_clone_delete_access
.collection::<Access>()
.expect("can open access collection") .expect("can open access collection")
.delete_by_id(access.base_data.id) .delete_by_id(access.base_data.id)
.expect("can delete event"); .expect("can delete event");
// Return the updated event with the correct ID // Return the updated event with the correct ID
Ok(result) 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(
"get_access_by_id",
move |id_i64: INT| -> Result<Access, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?; let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly // Use the Collection trait method directly
db_clone_get_access.get_by_id(id_u32) db_clone_get_access
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_access_by_id: {}", e).into(), Position::NONE)))? .get_by_id(id_u32)
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Access with ID {} not found", id_u32).into(), Position::NONE))) .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>> {
let collection = db_clone_list_accesss.collection::<Access>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get access collection: {:?}", e).into(), format!("Failed to get access collection: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
let accesss = collection.get_all() })?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( let accesss = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all accesss: {:?}", e).into(), format!("Failed to get all accesss: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
})?;
let mut array = Array::new(); let mut array = Array::new();
for access in accesss { for access in accesss {
array.push(Dynamic::from(access)); array.push(Dynamic::from(access));
} }
Ok(Dynamic::from(array)) 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 ---

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)]

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]
@ -39,42 +39,60 @@ mod rhai_biz_module {
// 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())
@ -134,14 +152,20 @@ mod rhai_biz_module {
// 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);
@ -149,14 +173,19 @@ mod rhai_biz_module {
} }
#[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())
@ -196,21 +225,30 @@ mod rhai_biz_module {
// 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())
@ -240,77 +278,110 @@ mod rhai_biz_module {
// 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())
@ -384,7 +455,12 @@ mod rhai_biz_module {
#[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 ---
@ -395,28 +471,39 @@ mod rhai_biz_module {
// 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);
@ -451,28 +538,39 @@ mod rhai_biz_module {
} }
#[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())
@ -511,7 +609,11 @@ mod rhai_biz_module {
#[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()
} }
} }
@ -527,99 +629,163 @@ pub fn register_biz_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// 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>> {
let company_collection_set = db_for_set_company
.collection::<Company>()
.expect("Failed to get company collection for set in closure");
company_collection_set
.set(&company)
.map(|(id_val, _)| id_val as INT) .map(|(id_val, _)| id_val as INT)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to save company: {:?}", e).into(), format!("Failed to save company: {:?}", e).into(),
Position::NONE 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",
move |id: INT| -> Result<Dynamic, Box<EvalAltResult>> {
let company_collection_get = db_for_get_company
.collection::<Company>()
.expect("Failed to get company collection for get in closure");
let id_u32 = id_from_i64_to_u32(id)?; let id_u32 = id_from_i64_to_u32(id)?;
company_collection_get.get_by_id(id_u32) company_collection_get
.get_by_id(id_u32)
.map(Dynamic::from) .map(Dynamic::from)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get company with id {}: {:?}", id, e).into(), format!("Failed to get company with id {}: {:?}", id, e).into(),
Position::NONE 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>> {
let shareholder_collection_set = db_for_set_shareholder
.collection::<Shareholder>()
.expect("Failed to get shareholder collection for set in closure");
shareholder_collection_set
.set(&shareholder)
.map(|(id_val, _)| id_val as INT) .map(|(id_val, _)| id_val as INT)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to save shareholder: {:?}", e).into(), format!("Failed to save shareholder: {:?}", e).into(),
Position::NONE 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",
move |id: INT| -> Result<Dynamic, Box<EvalAltResult>> {
let shareholder_collection_get = db_for_get_shareholder
.collection::<Shareholder>()
.expect("Failed to get shareholder collection for get in closure");
let id_u32 = id_from_i64_to_u32(id)?; let id_u32 = id_from_i64_to_u32(id)?;
shareholder_collection_get.get_by_id(id_u32) shareholder_collection_get
.get_by_id(id_u32)
.map(Dynamic::from) .map(Dynamic::from)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get shareholder with id {}: {:?}", id, e).into(), format!("Failed to get shareholder with id {}: {:?}", id, e).into(),
Position::NONE 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>> {
let product_collection_set = db_for_set_product
.collection::<Product>()
.expect("Failed to get product collection for set in closure");
product_collection_set
.set(&product)
.map(|(id_val, _)| id_val as INT) .map(|(id_val, _)| id_val as INT)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to save product: {:?}", e).into(), format!("Failed to save product: {:?}", e).into(),
Position::NONE 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",
move |id: INT| -> Result<Dynamic, Box<EvalAltResult>> {
let product_collection_get = db_for_get_product
.collection::<Product>()
.expect("Failed to get product collection for get in closure");
let id_u32 = id_from_i64_to_u32(id)?; let id_u32 = id_from_i64_to_u32(id)?;
product_collection_get.get_by_id(id_u32) product_collection_get
.get_by_id(id_u32)
.map(Dynamic::from) .map(Dynamic::from)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get product with id {}: {:?}", id, e).into(), format!("Failed to get product with id {}: {:?}", id, e).into(),
Position::NONE 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>> {
let sale_collection_set = db_for_set_sale
.collection::<Sale>()
.expect("Failed to get sale collection for set in closure");
sale_collection_set
.set(&sale)
.map(|(id_val, _)| id_val as INT) .map(|(id_val, _)| id_val as INT)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to save sale: {:?}", e).into(), format!("Failed to save sale: {:?}", e).into(),
Position::NONE 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",
move |id: INT| -> Result<Dynamic, Box<EvalAltResult>> {
let sale_collection_get = db_for_get_sale
.collection::<Sale>()
.expect("Failed to get sale collection for get in closure");
let id_u32 = id_from_i64_to_u32(id)?; let id_u32 = id_from_i64_to_u32(id)?;
sale_collection_get.get_by_id(id_u32) sale_collection_get
.get_by_id(id_u32)
.map(Dynamic::from) .map(Dynamic::from)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( .map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get sale with id {}: {:?}", id, e).into(), format!("Failed to get sale with id {}: {:?}", id, e).into(),
Position::NONE 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());

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 {

View File

@ -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]
@ -31,7 +31,10 @@ mod rhai_calendar_module {
/// 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())
@ -39,7 +42,10 @@ mod rhai_calendar_module {
/// 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())
@ -47,7 +53,10 @@ mod rhai_calendar_module {
/// 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,12 +76,16 @@ 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,
))); )));
} }
@ -81,7 +97,11 @@ mod rhai_calendar_module {
/// 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(),
@ -96,24 +116,42 @@ 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")]
@ -123,7 +161,10 @@ mod rhai_calendar_module {
/// 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);
@ -133,7 +174,10 @@ mod rhai_calendar_module {
/// 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(),
@ -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)]
@ -162,7 +210,10 @@ mod rhai_calendar_module {
/// 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, "");
@ -174,7 +225,10 @@ mod rhai_calendar_module {
/// 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, "");
@ -186,7 +240,10 @@ 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, "");
@ -197,7 +254,10 @@ 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, "");
@ -209,26 +269,37 @@ 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>) {
@ -241,108 +312,170 @@ pub fn register_calendar_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// 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(
"save_event",
move |event: Event| -> Result<Event, Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone_set_event.set(&event) let result = db_clone_set_event.set(&event).map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_event: {}", e).into(), Position::NONE)))?; Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error set_event: {}", e).into(),
Position::NONE,
))
})?;
// Return the updated event with the correct ID // Return the updated event with the correct ID
Ok(result.1) 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(
"delete_event",
move |event: Event| -> Result<(), Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone_delete_event.collection::<Event>() let result = db_clone_delete_event
.collection::<Event>()
.expect("can open event collection") .expect("can open event collection")
.delete_by_id(event.base_data.id) .delete_by_id(event.base_data.id)
.expect("can delete event"); .expect("can delete event");
// Return the updated event with the correct ID // Return the updated event with the correct ID
Ok(result) 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(
"get_event_by_id",
move |id_i64: INT| -> Result<Event, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?; let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly // Use the Collection trait method directly
db_clone_get_event.get_by_id(id_u32) db_clone_get_event
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_event_by_id: {}", e).into(), Position::NONE)))? .get_by_id(id_u32)
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Event with ID {} not found", id_u32).into(), Position::NONE))) .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(
"save_calendar",
move |calendar: Calendar| -> Result<Calendar, Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone_set_calendar.set(&calendar) let result = db_clone_set_calendar.set(&calendar).map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_calendar: {}", e).into(), Position::NONE)))?; Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error set_calendar: {}", e).into(),
Position::NONE,
))
})?;
// Return the updated calendar with the correct ID // Return the updated calendar with the correct ID
Ok(result.1) 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(
"delete_calendar",
move |calendar: Calendar| -> Result<(), Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone_delete_calendar.collection::<Calendar>() let result = db_clone_delete_calendar
.collection::<Calendar>()
.expect("can open calendar collection") .expect("can open calendar collection")
.delete_by_id(calendar.base_data.id) .delete_by_id(calendar.base_data.id)
.expect("can delete event"); .expect("can delete event");
// Return the updated event with the correct ID // Return the updated event with the correct ID
Ok(result) 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(
"get_calendar_by_id",
move |id_i64: INT| -> Result<Calendar, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?; let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly // Use the Collection trait method directly
db_clone_get_calendar.get_by_id(id_u32) db_clone_get_calendar
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_calendar_by_id: {}", e).into(), Position::NONE)))? .get_by_id(id_u32)
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Calendar with ID {} not found", id_u32).into(), Position::NONE))) .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>> {
let collection = db_clone_list_calendars
.collection::<Calendar>()
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get calendar collection: {:?}", e).into(), format!("Failed to get calendar collection: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
let calendars = collection.get_all() })?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( let calendars = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all calendars: {:?}", e).into(), format!("Failed to get all calendars: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
})?;
let mut array = Array::new(); let mut array = Array::new();
for calendar in calendars { for calendar in calendars {
array.push(Dynamic::from(calendar)); array.push(Dynamic::from(calendar));
} }
Ok(Dynamic::from(array)) 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>> {
let collection = db_clone_list_events.collection::<Event>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get event collection: {:?}", e).into(), format!("Failed to get event collection: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
let events = collection.get_all() })?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( let events = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all events: {:?}", e).into(), format!("Failed to get all events: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
})?;
let mut array = Array::new(); let mut array = Array::new();
for event in events { for event in events {
array.push(Dynamic::from(event)); array.push(Dynamic::from(event));
} }
Ok(Dynamic::from(array)) 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]
@ -49,7 +49,10 @@ mod rhai_circle_module {
/// 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,7 +60,10 @@ 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())
@ -65,7 +71,10 @@ mod rhai_circle_module {
/// 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,7 +93,10 @@ 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())
@ -89,33 +104,66 @@ mod rhai_circle_module {
/// 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>) {
@ -128,71 +176,111 @@ pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// 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(
"save_circle",
move |circle: Circle| -> Result<Circle, Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone_set_circle.set(&circle) let result = db_clone_set_circle.set(&circle).map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_circle: {}", e).into(), Position::NONE)))?; Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error set_circle: {}", e).into(),
Position::NONE,
))
})?;
// Return the updated circle with the correct ID // Return the updated circle with the correct ID
Ok(result.1) 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(
"delete_circle",
move |circle: Circle| -> Result<(), Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone_delete_circle.collection::<Circle>() let result = db_clone_delete_circle
.collection::<Circle>()
.expect("can open circle collection") .expect("can open circle collection")
.delete_by_id(circle.base_data.id) .delete_by_id(circle.base_data.id)
.expect("can delete circle"); .expect("can delete circle");
// Return the updated circle with the correct ID // Return the updated circle with the correct ID
Ok(result) Ok(result)
}); },
);
let db_clone_get_circle = db.clone(); let db_clone_get_circle = db.clone();
db_module.set_native_fn("get_circle", move || -> Result<Circle, Box<EvalAltResult>> { db_module.set_native_fn(
"get_circle",
move || -> Result<Circle, Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let all_circles: Vec<Circle> = db_clone_get_circle.get_all() let all_circles: Vec<Circle> = db_clone_get_circle.get_all().map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_circle: {}", e).into(), Position::NONE)))?; Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error get_circle: {}", e).into(),
Position::NONE,
))
})?;
if let Some(first_circle) = all_circles.first() { if let Some(first_circle) = all_circles.first() {
Ok(first_circle.clone()) Ok(first_circle.clone())
} else { } else {
Err(Box::new(EvalAltResult::ErrorRuntime("Circle not found".into(), Position::NONE))) 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(
"get_circle_by_id",
move |id_i64: INT| -> Result<Circle, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?; let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly // Use the Collection trait method directly
db_clone_get_circle_by_id.get_by_id(id_u32) db_clone_get_circle_by_id
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_circle_by_id: {}", e).into(), Position::NONE)))? .get_by_id(id_u32)
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Circle with ID {} not found", id_u32).into(), Position::NONE))) .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>> {
let collection = db_clone_list_circles.collection::<Circle>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get circle collection: {:?}", e).into(), format!("Failed to get circle collection: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
let circles = collection.get_all() })?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( let circles = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all circles: {:?}", e).into(), format!("Failed to get all circles: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
})?;
let mut array = Array::new(); let mut array = Array::new();
for circle in circles { for circle in circles {
array.push(Dynamic::from(circle)); array.push(Dynamic::from(circle));
} }
Ok(Dynamic::from(array)) 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,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]
@ -30,7 +30,10 @@ mod rhai_contact_module {
/// 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())
@ -38,7 +41,10 @@ mod rhai_contact_module {
/// 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)] #[rhai_fn(get = "name", pure)]
pub fn get_group_name(group: &mut RhaiGroup) -> String { group.name.clone() } pub fn get_group_name(group: &mut RhaiGroup) -> String {
group.name.clone()
}
#[rhai_fn(get = "description", pure)] #[rhai_fn(get = "description", pure)]
pub fn get_group_description(group: &mut RhaiGroup) -> Option<String> { group.description.clone() } 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)]
@ -79,7 +104,10 @@ mod rhai_contact_module {
/// 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();
@ -91,7 +119,10 @@ mod rhai_contact_module {
/// 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();
@ -103,16 +134,24 @@ 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>) {
@ -125,108 +164,170 @@ pub fn register_contact_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// 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(
"save_group",
move |group: Group| -> Result<Group, Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone_set_group.set(&group) let result = db_clone_set_group.set(&group).map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_group: {}", e).into(), Position::NONE)))?; Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error set_group: {}", e).into(),
Position::NONE,
))
})?;
// Return the updated event with the correct ID // Return the updated event with the correct ID
Ok(result.1) 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(
"delete_group",
move |group: Group| -> Result<(), Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone_delete_group.collection::<Group>() let result = db_clone_delete_group
.collection::<Group>()
.expect("can open group collection") .expect("can open group collection")
.delete_by_id(group.base_data.id) .delete_by_id(group.base_data.id)
.expect("can delete group"); .expect("can delete group");
// Return the updated event with the correct ID // Return the updated event with the correct ID
Ok(result) 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(
"get_group_by_id",
move |id_i64: INT| -> Result<Group, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?; let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly // Use the Collection trait method directly
db_clone_get_group.get_by_id(id_u32) db_clone_get_group
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_event_by_id: {}", e).into(), Position::NONE)))? .get_by_id(id_u32)
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Event with ID {} not found", id_u32).into(), Position::NONE))) .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(
"save_contact",
move |contact: Contact| -> Result<Contact, Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone_set_contact.set(&contact) let result = db_clone_set_contact.set(&contact).map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_contact: {}", e).into(), Position::NONE)))?; Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error set_contact: {}", e).into(),
Position::NONE,
))
})?;
// Return the updated contact with the correct ID // Return the updated contact with the correct ID
Ok(result.1) 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(
"delete_contact",
move |contact: Contact| -> Result<(), Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone_delete_contact.collection::<Contact>() let result = db_clone_delete_contact
.collection::<Contact>()
.expect("can open contact collection") .expect("can open contact collection")
.delete_by_id(contact.base_data.id) .delete_by_id(contact.base_data.id)
.expect("can delete event"); .expect("can delete event");
// Return the updated event with the correct ID // Return the updated event with the correct ID
Ok(result) 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(
"get_contact_by_id",
move |id_i64: INT| -> Result<Contact, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?; let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly // Use the Collection trait method directly
db_clone_get_contact.get_by_id(id_u32) db_clone_get_contact
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_contact_by_id: {}", e).into(), Position::NONE)))? .get_by_id(id_u32)
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Contact with ID {} not found", id_u32).into(), Position::NONE))) .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>> {
let collection = db_clone_list_contacts
.collection::<Contact>()
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get contact collection: {:?}", e).into(), format!("Failed to get contact collection: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
let contacts = collection.get_all() })?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( let contacts = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all contacts: {:?}", e).into(), format!("Failed to get all contacts: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
})?;
let mut array = Array::new(); let mut array = Array::new();
for contact in contacts { for contact in contacts {
array.push(Dynamic::from(contact)); array.push(Dynamic::from(contact));
} }
Ok(Dynamic::from(array)) 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>> {
let collection = db_clone_list_groups.collection::<Group>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get group collection: {:?}", e).into(), format!("Failed to get group collection: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
let groups = collection.get_all() })?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( let groups = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all groups: {:?}", e).into(), format!("Failed to get all groups: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
})?;
let mut array = Array::new(); let mut array = Array::new();
for group in groups { for group in groups {
array.push(Dynamic::from(group)); array.push(Dynamic::from(group));
} }
Ok(Dynamic::from(array)) 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

@ -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;
@ -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

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,14 +575,20 @@ 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(|| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid timestamp: {}", end_date_ts).into(), format!("Invalid timestamp: {}", end_date_ts).into(),
Position::NONE 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));
@ -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,26 +608,32 @@ 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);
@ -563,13 +645,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 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,63 +816,153 @@ 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| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get Account collection: {:?}", e).into(),
Position::NONE,
))
})?;
collection
.set(&account)
.map(|(id, _)| id as INT) .map(|(id, _)| id as INT)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Account: {:?}", e).into(), Position::NONE))) .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(
"get_account_by_id",
move |id_rhai: INT| -> Result<Dynamic, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_rhai)?; let id_u32 = id_from_i64_to_u32(id_rhai)?;
let collection = db_get_account.collection::<Account>().map_err(|e| let collection = db_get_account.collection::<Account>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account collection: {:?}", e).into(), Position::NONE)) )?; Box::new(EvalAltResult::ErrorRuntime(
collection.get_by_id(id_u32) format!("Failed to get Account collection: {:?}", e).into(),
.map(|opt_account| opt_account.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) Position::NONE,
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account with ID {}: {:?}", id_rhai, 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| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get Asset collection: {:?}", e).into(),
Position::NONE,
))
})?;
collection
.set(&asset)
.map(|(id, _)| id as INT) .map(|(id, _)| id as INT)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Asset: {:?}", e).into(), Position::NONE))) .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(
"get_asset_by_id",
move |id_rhai: INT| -> Result<Dynamic, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_rhai)?; let id_u32 = id_from_i64_to_u32(id_rhai)?;
let collection = db_get_asset.collection::<Asset>().map_err(|e| let collection = db_get_asset.collection::<Asset>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset collection: {:?}", e).into(), Position::NONE)) )?; Box::new(EvalAltResult::ErrorRuntime(
collection.get_by_id(id_u32) format!("Failed to get Asset collection: {:?}", e).into(),
.map(|opt_asset| opt_asset.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) Position::NONE,
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset with ID {}: {:?}", id_rhai, 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| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get Listing collection: {:?}", e).into(),
Position::NONE,
))
})?;
collection
.set(&listing)
.map(|(id, _)| id as INT) .map(|(id, _)| id as INT)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Listing: {:?}", e).into(), Position::NONE))) .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(
"get_listing_by_id",
move |id_rhai: INT| -> Result<Dynamic, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_rhai)?; let id_u32 = id_from_i64_to_u32(id_rhai)?;
let collection = db_get_listing.collection::<Listing>().map_err(|e| let collection = db_get_listing.collection::<Listing>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing collection: {:?}", e).into(), Position::NONE)) )?; Box::new(EvalAltResult::ErrorRuntime(
collection.get_by_id(id_u32) format!("Failed to get Listing collection: {:?}", e).into(),
.map(|opt_listing| opt_listing.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) Position::NONE,
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing with ID {}: {:?}", id_rhai, 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());

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)]

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]
@ -41,7 +41,10 @@ mod rhai_flow_module {
/// 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())
@ -49,7 +52,10 @@ mod rhai_flow_module {
/// 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())
@ -57,20 +63,36 @@ mod rhai_flow_module {
// 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 ---
@ -81,14 +103,17 @@ 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())
@ -96,7 +121,10 @@ mod rhai_flow_module {
/// 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())
@ -104,11 +132,17 @@ mod rhai_flow_module {
// 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,14 +174,17 @@ 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())
@ -147,7 +192,10 @@ mod rhai_flow_module {
/// 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())
@ -155,7 +203,10 @@ mod rhai_flow_module {
/// 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())
@ -163,17 +214,29 @@ mod rhai_flow_module {
// 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,7 +252,9 @@ 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
@ -199,168 +264,265 @@ pub fn register_flow_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// 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(
"save_flow",
move |flow: Flow| -> Result<Flow, Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone.set(&flow) let result = db_clone.set(&flow).map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_flow: {:?}", e).into(), Position::NONE)))?; Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error save_flow: {:?}", e).into(),
Position::NONE,
))
})?;
// Return the updated flow with the correct ID // Return the updated flow with the correct ID
Ok(result.1) 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(
"get_flow_by_id",
move |id_i64: INT| -> Result<Flow, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?; let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly // Use the Collection trait method directly
db_clone.get_by_id(id_u32) db_clone
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_flow_by_id: {:?}", e).into(), Position::NONE)))? .get_by_id(id_u32)
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Flow with ID {} not found", id_u32).into(), Position::NONE))) .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(
"delete_flow",
move |id_i64: INT| -> Result<(), Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?; let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly // Use the Collection trait method directly
let collection = db_clone.collection::<Flow>() let collection = db_clone.collection::<Flow>().map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get flow collection: {:?}", e).into(), format!("Failed to get flow collection: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
collection.delete_by_id(id_u32) })?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( collection.delete_by_id(id_u32).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to delete Flow (ID: {}): {:?}", id_u32, e).into(), format!("Failed to delete Flow (ID: {}): {:?}", id_u32, e).into(),
Position::NONE 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>> {
let collection = db_clone.collection::<Flow>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get flow collection: {:?}", e).into(), format!("Failed to get flow collection: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
let flows = collection.get_all() })?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( let flows = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all flows: {:?}", e).into(), format!("Failed to get all flows: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
})?;
let mut array = Array::new(); let mut array = Array::new();
for flow in flows { for flow in flows {
array.push(Dynamic::from(flow)); array.push(Dynamic::from(flow));
} }
Ok(Dynamic::from(array)) 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(
"save_flow_step",
move |step: FlowStep| -> Result<FlowStep, Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone.set(&step) let result = db_clone.set(&step).map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_flow_step: {:?}", e).into(), Position::NONE)))?; Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error save_flow_step: {:?}", e).into(),
Position::NONE,
))
})?;
// Return the updated flow step with the correct ID // Return the updated flow step with the correct ID
Ok(result.1) 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(
"get_flow_step_by_id",
move |id_i64: INT| -> Result<FlowStep, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?; let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly // Use the Collection trait method directly
db_clone.get_by_id(id_u32) db_clone
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_flow_step_by_id: {:?}", e).into(), Position::NONE)))? .get_by_id(id_u32)
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("FlowStep with ID {} not found", id_u32).into(), Position::NONE))) .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(
"delete_flow_step",
move |id_i64: INT| -> Result<(), Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?; let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly // Use the Collection trait method directly
let collection = db_clone.collection::<FlowStep>() let collection = db_clone.collection::<FlowStep>().map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get flow step collection: {:?}", e).into(), format!("Failed to get flow step collection: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
collection.delete_by_id(id_u32) })?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( collection.delete_by_id(id_u32).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to delete FlowStep (ID: {}): {:?}", id_u32, e).into(), format!("Failed to delete FlowStep (ID: {}): {:?}", id_u32, e).into(),
Position::NONE 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>> {
let collection = db_clone.collection::<FlowStep>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get flow step collection: {:?}", e).into(), format!("Failed to get flow step collection: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
let steps = collection.get_all() })?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( let steps = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all flow steps: {:?}", e).into(), format!("Failed to get all flow steps: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
})?;
let mut array = Array::new(); let mut array = Array::new();
for step in steps { for step in steps {
array.push(Dynamic::from(step)); array.push(Dynamic::from(step));
} }
Ok(Dynamic::from(array)) 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(
"save_signature_requirement",
move |sr: SignatureRequirement| -> Result<SignatureRequirement, Box<EvalAltResult>> {
// Use the Collection trait method directly // Use the Collection trait method directly
let result = db_clone.set(&sr) let result = db_clone.set(&sr).map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_signature_requirement: {:?}", e).into(), Position::NONE)))?; Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error save_signature_requirement: {:?}", e).into(),
Position::NONE,
))
})?;
// Return the updated signature requirement with the correct ID // Return the updated signature requirement with the correct ID
Ok(result.1) 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(
"get_signature_requirement_by_id",
move |id_i64: INT| -> Result<SignatureRequirement, Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?; let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly // Use the Collection trait method directly
db_clone.get_by_id(id_u32) db_clone
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_signature_requirement_by_id: {:?}", e).into(), Position::NONE)))? .get_by_id(id_u32)
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("SignatureRequirement with ID {} not found", id_u32).into(), Position::NONE))) .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(
"delete_signature_requirement",
move |id_i64: INT| -> Result<(), Box<EvalAltResult>> {
let id_u32 = id_from_i64_to_u32(id_i64)?; let id_u32 = id_from_i64_to_u32(id_i64)?;
// Use the Collection trait method directly // Use the Collection trait method directly
let collection = db_clone.collection::<SignatureRequirement>() let collection = db_clone.collection::<SignatureRequirement>().map_err(|e| {
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get signature requirement collection: {:?}", e).into(), format!("Failed to get signature requirement collection: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
collection.delete_by_id(id_u32) })?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( collection.delete_by_id(id_u32).map_err(|e| {
format!("Failed to delete SignatureRequirement (ID: {}): {:?}", id_u32, e).into(), Box::new(EvalAltResult::ErrorRuntime(
Position::NONE 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>> {
let collection = db_clone.collection::<SignatureRequirement>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get signature requirement collection: {:?}", e).into(), format!("Failed to get signature requirement collection: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
let srs = collection.get_all() })?;
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime( let srs = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all signature requirements: {:?}", e).into(), format!("Failed to get all signature requirements: {:?}", e).into(),
Position::NONE Position::NONE,
)))?; ))
})?;
let mut array = Array::new(); let mut array = Array::new();
for sr in srs { for sr in srs {
array.push(Dynamic::from(sr)); array.push(Dynamic::from(sr));
} }
Ok(Dynamic::from(array)) 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());

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 ---

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 ---

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,
)) ))
@ -77,23 +86,69 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
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 { engine.register_fn(
"comments",
|mut revision: ContractRevision, comments: String| -> ContractRevision {
revision.comments = Some(comments); revision.comments = Some(comments);
revision 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(
engine.register_get("created_at", |revision: &mut ContractRevision| -> Result<i64, Box<EvalAltResult>> { Ok(revision.created_at as i64) }); "version",
engine.register_get("created_by", |revision: &mut ContractRevision| -> Result<String, Box<EvalAltResult>> { Ok(revision.created_by.clone()) }); |revision: &mut ContractRevision| -> Result<i64, Box<EvalAltResult>> {
engine.register_get("comments", |revision: &mut ContractRevision| -> Result<Dynamic, Box<EvalAltResult>> { Ok(revision.version as i64)
Ok(revision.comments.clone().map_or(Dynamic::UNIT, Dynamic::from)) },
}); );
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");
@ -101,165 +156,458 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
"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(
engine.register_fn("signed_at", |context: NativeCallContext, signer: ContractSigner, signed_at_i64: i64| -> Result<ContractSigner, Box<EvalAltResult>> { "status",
let signed_at_u64 = i64_to_u64(signed_at_i64, context.position(), "signed_at", "ContractSigner.signed_at")?; |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)) 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(
engine.register_fn("clear_comments", |signer: ContractSigner| -> ContractSigner { signer.clear_comments() }); "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",
|signer: &mut ContractSigner| -> Result<String, Box<EvalAltResult>> {
Ok(signer.name.clone())
},
);
engine.register_get(
"email",
|signer: &mut ContractSigner| -> Result<String, Box<EvalAltResult>> {
Ok(signer.email.clone())
},
);
engine.register_get(
"status",
|signer: &mut ContractSigner| -> Result<SignerStatus, Box<EvalAltResult>> {
Ok(signer.status.clone())
},
);
engine.register_get(
"signed_at_ts",
|signer: &mut ContractSigner| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(signer
.signed_at
.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
},
);
engine.register_get(
"comments",
|signer: &mut ContractSigner| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(signer.comments.clone().map_or(Dynamic::UNIT, Dynamic::from)) 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))) 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(
engine.register_fn("created_by", |contract: Contract, created_by: String| -> Contract { contract.created_by(created_by) }); "description",
engine.register_fn("terms_and_conditions", |contract: Contract, terms: String| -> Contract { contract.terms_and_conditions(terms) }); |contract: Contract, description: String| -> Contract { contract.description(description) },
);
engine.register_fn(
"contract_type",
|contract: Contract, contract_type: String| -> Contract {
contract.contract_type(contract_type)
},
);
engine.register_fn(
"status",
|contract: Contract, status: ContractStatus| -> Contract { contract.status(status) },
);
engine.register_fn(
"created_by",
|contract: Contract, created_by: String| -> Contract { contract.created_by(created_by) },
);
engine.register_fn(
"terms_and_conditions",
|contract: Contract, terms: String| -> Contract { contract.terms_and_conditions(terms) },
);
engine.register_fn("start_date", |context: NativeCallContext, contract: Contract, start_date_i64: i64| -> Result<Contract, Box<EvalAltResult>> { engine.register_fn(
let start_date_u64 = i64_to_u64(start_date_i64, context.position(), "start_date", "Contract.start_date")?; "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.call_position(),
"start_date",
"Contract.start_date",
)?;
Ok(contract.start_date(start_date_u64)) Ok(contract.start_date(start_date_u64))
},
);
engine.register_fn("clear_start_date", |contract: Contract| -> Contract {
contract.clear_start_date()
}); });
engine.register_fn("clear_start_date", |contract: Contract| -> Contract { contract.clear_start_date() });
engine.register_fn("end_date", |context: NativeCallContext, contract: Contract, end_date_i64: i64| -> Result<Contract, Box<EvalAltResult>> { engine.register_fn(
let end_date_u64 = i64_to_u64(end_date_i64, context.position(), "end_date", "Contract.end_date")?; "end_date",
|context: NativeCallContext,
contract: Contract,
end_date_i64: i64|
-> Result<Contract, Box<EvalAltResult>> {
let end_date_u64 = i64_to_u64(
end_date_i64,
context.call_position(),
"end_date",
"Contract.end_date",
)?;
Ok(contract.end_date(end_date_u64)) Ok(contract.end_date(end_date_u64))
},
);
engine.register_fn("clear_end_date", |contract: Contract| -> Contract {
contract.clear_end_date()
}); });
engine.register_fn("clear_end_date", |contract: Contract| -> Contract { contract.clear_end_date() });
engine.register_fn("renewal_period_days", |context: NativeCallContext, contract: Contract, days_i64: i64| -> Result<Contract, Box<EvalAltResult>> { engine.register_fn(
let days_i32 = i64_to_i32(days_i64, context.position(), "renewal_period_days", "Contract.renewal_period_days")?; "renewal_period_days",
|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)) 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(
"clear_renewal_period_days",
|contract: Contract| -> Contract { contract.clear_renewal_period_days() },
);
engine.register_fn("next_renewal_date", |context: NativeCallContext, contract: Contract, date_i64: i64| -> Result<Contract, Box<EvalAltResult>> { engine.register_fn(
let date_u64 = i64_to_u64(date_i64, context.position(), "next_renewal_date", "Contract.next_renewal_date")?; "next_renewal_date",
|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)) 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(
"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(
engine.register_fn("signers", |contract: Contract, signers_array: Array| -> Contract { "add_signer",
let signers_vec = signers_array.into_iter().filter_map(|s| s.try_cast::<ContractSigner>()).collect(); |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) 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 { "add_revision",
let revisions_vec = revisions_array.into_iter().filter_map(|r| r.try_cast::<ContractRevision>()).collect(); |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) contract.revisions(revisions_vec)
}); },
);
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")?; "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)) Ok(contract.current_version(version_u32))
}); },
);
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")?; "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)) 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(
"updated_at_ts",
|contract: &mut Contract| -> Result<i64, Box<EvalAltResult>> {
Ok(contract.base_data.modified_at as i64)
},
);
engine.register_get(
"contract_id",
|contract: &mut Contract| -> Result<String, Box<EvalAltResult>> {
Ok(contract.contract_id.clone())
},
);
engine.register_get(
"title",
|contract: &mut Contract| -> Result<String, Box<EvalAltResult>> {
Ok(contract.title.clone())
},
);
engine.register_get(
"description",
|contract: &mut Contract| -> Result<String, Box<EvalAltResult>> {
Ok(contract.description.clone())
},
);
engine.register_get(
"contract_type",
|contract: &mut Contract| -> Result<String, Box<EvalAltResult>> {
Ok(contract.contract_type.clone())
},
);
engine.register_get(
"status",
|contract: &mut Contract| -> Result<ContractStatus, Box<EvalAltResult>> {
Ok(contract.status.clone())
},
);
engine.register_get(
"created_by",
|contract: &mut Contract| -> Result<String, Box<EvalAltResult>> {
Ok(contract.created_by.clone())
},
);
engine.register_get(
"terms_and_conditions",
|contract: &mut Contract| -> Result<String, Box<EvalAltResult>> {
Ok(contract.terms_and_conditions.clone())
},
);
engine.register_get("start_date", |contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> { engine.register_get(
Ok(contract.start_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) "start_date",
}); |contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> {
engine.register_get("end_date", |contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> { Ok(contract
Ok(contract.end_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) .start_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(
engine.register_get("next_renewal_date", |contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> { "end_date",
Ok(contract.next_renewal_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) |contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> {
}); Ok(contract
engine.register_get("last_signed_date", |contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> { .end_date
Ok(contract.last_signed_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
}); },
);
engine.register_get(
"renewal_period_days",
|contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(contract
.renewal_period_days
.map_or(Dynamic::UNIT, |days| Dynamic::from(days as i64)))
},
);
engine.register_get(
"next_renewal_date",
|contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(contract
.next_renewal_date
.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
},
);
engine.register_get(
"last_signed_date",
|contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(contract
.last_signed_date
.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
},
);
engine.register_get("current_version", |contract: &mut Contract| -> Result<i64, Box<EvalAltResult>> { Ok(contract.current_version as i64) }); engine.register_get(
"current_version",
|contract: &mut Contract| -> Result<i64, Box<EvalAltResult>> {
Ok(contract.current_version as i64)
},
);
engine.register_get("signers", |contract: &mut Contract| -> Result<Array, Box<EvalAltResult>> { engine.register_get(
let rhai_array = contract.signers.iter().cloned().map(Dynamic::from).collect::<Array>(); "signers",
|contract: &mut Contract| -> Result<Array, Box<EvalAltResult>> {
let rhai_array = contract
.signers
.iter()
.cloned()
.map(Dynamic::from)
.collect::<Array>();
Ok(rhai_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>(); 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) Ok(rhai_array)
}); },
);
// Method set_status // Method set_status
engine.register_fn("set_contract_status", |contract: &mut Contract, status: ContractStatus| { engine.register_fn(
"set_contract_status",
|contract: &mut Contract, status: ContractStatus| {
contract.set_status(status); 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)
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Error getting Contract (ID: {}): {}", id_u32, e).into(), format!("Error getting Contract (ID: {}): {}", id_u32, e).into(),
Position::NONE, Position::NONE,
)))? ))
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime( })?
.ok_or_else(|| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Contract with ID {} not found", id_u32).into(), format!("Contract with ID {} not found", id_u32).into(),
Position::NONE, 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 ---
@ -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

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::*;

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,46 +77,141 @@ 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(
"description",
|p: Project, description: String| -> Result<Project, Box<EvalAltResult>> {
Ok(p.description(description))
},
);
engine.register_fn(
"owner_id",
|p: Project, owner_id_i64: i64| -> Result<Project, Box<EvalAltResult>> {
Ok(p.owner_id(id_from_i64(owner_id_i64)?))
},
);
engine.register_fn(
"add_member_id",
|p: Project, member_id_i64: i64| -> Result<Project, Box<EvalAltResult>> {
Ok(p.add_member_id(id_from_i64(member_id_i64)?))
},
);
engine.register_fn(
"member_ids",
|p: Project, member_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = member_ids_i64 let ids = member_ids_i64
.into_iter() .into_iter()
.map(|id_dyn: Dynamic| { .map(|id_dyn: Dynamic| {
@ -132,9 +226,17 @@ pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
}) })
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?; .collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.member_ids(ids)) Ok(p.member_ids(ids))
}); },
engine.register_fn("add_board_id", |p: Project, board_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_board_id(id_from_i64(board_id_i64)?)) }); );
engine.register_fn("board_ids", |p: Project, board_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> { 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 let ids = board_ids_i64
.into_iter() .into_iter()
.map(|id_dyn: Dynamic| { .map(|id_dyn: Dynamic| {
@ -149,9 +251,17 @@ pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
}) })
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?; .collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.board_ids(ids)) 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>> { 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 let ids = sprint_ids_i64
.into_iter() .into_iter()
.map(|id_dyn: Dynamic| { .map(|id_dyn: Dynamic| {
@ -166,9 +276,17 @@ pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
}) })
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?; .collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.sprint_ids(ids)) 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>> { 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 let ids = epic_ids_i64
.into_iter() .into_iter()
.map(|id_dyn: Dynamic| { .map(|id_dyn: Dynamic| {
@ -183,13 +301,20 @@ pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
}) })
.collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?; .collect::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.epic_ids(ids)) 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>> { 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 let tags_vec = tags_dyn
.into_iter() .into_iter()
.map(|tag_dyn: Dynamic| { .map(|tag_dyn: Dynamic| {
tag_dyn.clone().into_string().map_err(|_err| { // _err is Rhai's internal error, we create a new one tag_dyn.clone().into_string().map_err(|_err| {
// _err is Rhai's internal error, we create a new one
Box::new(EvalAltResult::ErrorMismatchDataType( Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected string for tag".to_string(), "Expected string for tag".to_string(),
tag_dyn.type_name().to_string(), tag_dyn.type_name().to_string(),
@ -199,17 +324,40 @@ pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
}) })
.collect::<Result<Vec<String>, Box<EvalAltResult>>>()?; .collect::<Result<Vec<String>, Box<EvalAltResult>>>()?;
Ok(p.tags(tags_vec)) 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(
"set_project",
move |project: Project| -> Result<(), Box<EvalAltResult>> {
let collection = db_clone_set.collection::<Project>().map_err(|e| { let collection = db_clone_set.collection::<Project>().map_err(|e| {
Box::new(EvalAltResult::ErrorSystem( Box::new(EvalAltResult::ErrorSystem(
"Failed to access project collection".to_string(), "Failed to access project collection".to_string(),
@ -223,10 +371,13 @@ pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
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(
"get_project_by_id",
move |id_i64: i64| -> Result<Dynamic, Box<EvalAltResult>> {
let id = id_from_i64(id_i64)?; let id = id_from_i64(id_i64)?;
let collection = db_clone_get.collection::<Project>().map_err(|e| { let collection = db_clone_get.collection::<Project>().map_err(|e| {
Box::new(EvalAltResult::ErrorSystem( Box::new(EvalAltResult::ErrorSystem(
@ -243,7 +394,8 @@ pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
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

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

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

@ -52,21 +52,31 @@ fn key_value_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> {
// 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 {
id: Some(id),
data: data.as_bytes(),
})?;
println!("Stored record {} with custom ID: {}", i + 1, id); 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);
} }
@ -106,7 +116,10 @@ fn incremental_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> {
// 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);
} }
@ -118,7 +131,11 @@ fn incremental_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> {
// 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()?;
@ -157,15 +174,20 @@ fn performance_benchmark(base_path: &PathBuf) -> Result<(), ourdb::Error> {
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!(
"Write performance: {:.2} ops/sec ({:.2} ms/op)",
writes_per_second, writes_per_second,
write_duration.as_secs_f64() * 1000.0 / num_operations as f64); 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);
@ -177,23 +199,30 @@ fn performance_benchmark(base_path: &PathBuf) -> Result<(), ourdb::Error> {
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!(
"Read performance: {:.2} ops/sec ({:.2} ms/op)",
reads_per_second, reads_per_second,
read_duration.as_secs_f64() * 1000.0 / num_operations as f64); 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!(
"Update performance: {:.2} ops/sec ({:.2} ms/op)",
updates_per_second, updates_per_second,
update_duration.as_secs_f64() * 1000.0 / num_operations as f64); update_duration.as_secs_f64() * 1000.0 / num_operations as f64
);
db.close()?; db.close()?;
println!("Performance benchmark completed"); println!("Performance benchmark completed");

View File

@ -20,23 +20,40 @@ fn main() -> Result<(), ourdb::Error> {
// 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

View File

@ -42,19 +42,27 @@ fn main() -> Result<(), ourdb::Error> {
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);
@ -63,9 +71,11 @@ fn main() -> Result<(), ourdb::Error> {
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!(
"Write performance: {:.2} ops/sec ({:.2} ms/op)",
writes_per_second, writes_per_second,
write_duration.as_secs_f64() * 1000.0 / num_operations as f64); 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);
@ -79,9 +89,11 @@ fn main() -> Result<(), ourdb::Error> {
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!(
"Read performance: {:.2} ops/sec ({:.2} ms/op)",
reads_per_second, reads_per_second,
read_duration.as_secs_f64() * 1000.0 / num_operations as f64); 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);
@ -89,15 +101,20 @@ fn main() -> Result<(), ourdb::Error> {
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!(
"Update performance: {:.2} ops/sec ({:.2} ms/op)",
updates_per_second, updates_per_second,
update_duration.as_secs_f64() * 1000.0 / num_operations as f64); update_duration.as_secs_f64() * 1000.0 / num_operations as f64
);
// Clean up // Clean up
db.close()?; db.close()?;

View File

@ -30,7 +30,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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
@ -39,12 +42,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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)?;

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,10 +26,7 @@ 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;
@ -80,12 +76,18 @@ 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
@ -95,15 +97,15 @@ impl OurDB {
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))?;
@ -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))?;
@ -335,7 +351,11 @@ mod tests {
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);

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;
@ -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,9 +124,11 @@ 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)?;
@ -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(())
} }
@ -251,14 +256,23 @@ mod tests {
// 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);

View File

@ -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!(),
} }

View File

@ -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 {
@ -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
))); )));
} }
@ -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,46 +156,57 @@ 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() {
@ -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);
@ -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(),
)); ));
} }
@ -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 {

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 {
@ -33,22 +33,30 @@ fn test_basic_operations() {
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);
@ -71,14 +79,13 @@ fn test_basic_operations() {
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();
@ -86,7 +93,11 @@ fn test_key_value_mode() {
// 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);
@ -108,18 +119,27 @@ fn test_incremental_mode() {
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);
@ -136,7 +156,6 @@ fn test_incremental_mode() {
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,13 +163,18 @@ 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();
@ -166,7 +190,7 @@ 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();
@ -198,14 +222,19 @@ fn test_different_keysizes() {
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);
@ -225,7 +254,7 @@ fn test_large_data() {
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();
@ -234,7 +263,12 @@ fn test_large_data() {
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());
@ -254,7 +288,7 @@ fn test_exceed_size_limit() {
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();
@ -263,10 +297,16 @@ fn test_exceed_size_limit() {
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();
@ -276,14 +316,13 @@ fn test_exceed_size_limit() {
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();
@ -294,7 +333,12 @@ fn test_multiple_files() {
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);
} }
@ -305,7 +349,8 @@ fn test_multiple_files() {
} }
// 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();
@ -313,7 +358,11 @@ fn test_multiple_files() {
}) })
.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,6 +1,6 @@
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.
/// ///
@ -69,7 +69,10 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream {
}; };
// 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
.iter()
.zip(param_types.iter())
.map(|(name, ty)| {
let type_str = quote! { #ty }.to_string(); let type_str = quote! { #ty }.to_string();
// Handle different parameter types // Handle different parameter types
@ -86,7 +89,11 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream {
quote! { quote! {
format!("{}", #name as i64) format!("{}", #name as i64)
} }
} else if type_str.contains("i64") || type_str.contains("u64") || type_str.contains("f32") || type_str.contains("f64") { } else if type_str.contains("i64")
|| type_str.contains("u64")
|| type_str.contains("f32")
|| type_str.contains("f64")
{
// Other numeric types // Other numeric types
quote! { quote! {
format!("{}", #name) format!("{}", #name)
@ -235,7 +242,10 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream {
}; };
// 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
.iter()
.zip(param_types.iter())
.map(|(name, ty)| {
let type_str = quote! { #ty }.to_string(); let type_str = quote! { #ty }.to_string();
// Handle different parameter types // Handle different parameter types
@ -263,11 +273,13 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream {
format!("{:?}", #name) format!("{:?}", #name)
} }
} }
}).collect::<Vec<_>>(); })
.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 {

View File

@ -1,5 +1,5 @@
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

View File

@ -1,6 +1,6 @@
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> {
@ -77,10 +77,13 @@ fn main() -> Result<(), tst::Error> {
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,
TOTAL_RECORDS,
(i + 1) as f64 / TOTAL_RECORDS as f64 * 100.0, (i + 1) as f64 / TOTAL_RECORDS as f64 * 100.0,
records_per_second); records_per_second
);
io::stdout().flush().unwrap(); io::stdout().flush().unwrap();
last_batch_time = Instant::now(); last_batch_time = Instant::now();
@ -90,19 +93,27 @@ fn main() -> Result<(), tst::Error> {
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
println!(
" After {} records: {:?} for {} records ({:.2} records/sec)",
record_count, record_count,
duration, duration,
PROGRESS_INTERVAL, PROGRESS_INTERVAL,
PROGRESS_INTERVAL as f64 / duration.as_secs_f64()); PROGRESS_INTERVAL as f64 / duration.as_secs_f64()
);
} }
} }
@ -122,8 +133,10 @@ fn main() -> Result<(), tst::Error> {
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...");
@ -134,8 +147,12 @@ fn main() -> Result<(), tst::Error> {
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)

View File

@ -1,5 +1,5 @@
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
@ -16,11 +16,31 @@ fn main() -> Result<(), tst::Error> {
// 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() {
@ -30,10 +50,26 @@ fn main() -> Result<(), tst::Error> {
// 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() {
@ -43,9 +79,21 @@ fn main() -> Result<(), tst::Error> {
// 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() {
@ -53,7 +101,10 @@ fn main() -> Result<(), tst::Error> {
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")?;
@ -71,7 +122,11 @@ fn main() -> Result<(), tst::Error> {
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);
@ -96,7 +151,12 @@ fn test_prefix(tree: &mut TST, prefix: &str) -> Result<(), tst::Error> {
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:");
@ -110,12 +170,14 @@ fn test_prefix(tree: &mut TST, prefix: &str) -> Result<(), tst::Error> {
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!(
"First value: {}",
if !values.is_empty() { if !values.is_empty() {
String::from_utf8_lossy(&values[0]) String::from_utf8_lossy(&values[0])
} else { } else {
"None".into() "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)]

View File

@ -36,10 +36,7 @@ pub fn new_tst(path: &str, reset: bool) -> Result<TST, Error> {
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.
@ -60,7 +57,13 @@ pub fn set(tree: &mut TST, key: &str, value: Vec<u8>) -> Result<(), Error> {
} }
/// 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() {
@ -287,7 +290,12 @@ pub fn list(tree: &mut TST, prefix: &str) -> Result<Vec<String>, Error> {
} }
/// 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);
} }
@ -409,7 +417,7 @@ pub fn getall(tree: &mut TST, prefix: &str) -> Result<Vec<Vec<u8>>, Error> {
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)),
} }
} }

View File

@ -46,7 +46,8 @@ impl TSTNode {
/// 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()));
} }
@ -57,7 +58,10 @@ impl TSTNode {
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
@ -79,7 +83,9 @@ impl TSTNode {
// 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 {
@ -89,7 +95,9 @@ impl TSTNode {
// 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]];
@ -108,7 +116,11 @@ impl TSTNode {
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) },
}) })
} }

View File

@ -1,7 +1,7 @@
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()
@ -166,10 +166,7 @@ fn test_list_prefix() {
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());
@ -223,9 +220,7 @@ fn test_getall_prefix() {
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());

View File

@ -1,7 +1,7 @@
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()
@ -232,9 +232,18 @@ fn test_prefix_with_long_keys() {
// 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()),
]; ];