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 serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
// Define the necessary structs and traits for testing
#[derive(Debug, Clone, Serialize, Deserialize)]

View File

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

View File

@ -68,10 +68,26 @@ fn main() {
.build();
// Save all users to database and get their assigned IDs and updated models
let (user1_id, db_user1) = db.collection().expect("can open user collection").set(&user1).expect("can set user");
let (user2_id, db_user2) = db.collection().expect("can open user collection").set(&user2).expect("can set user");
let (user3_id, db_user3) = db.collection().expect("can open user collection").set(&user3).expect("can set user");
let (user4_id, db_user4) = db.collection().expect("can open user collection").set(&user4).expect("can set user");
let (user1_id, db_user1) = db
.collection()
.expect("can open user collection")
.set(&user1)
.expect("can set user");
let (user2_id, db_user2) = db
.collection()
.expect("can open user collection")
.set(&user2)
.expect("can set user");
let (user3_id, db_user3) = db
.collection()
.expect("can open user collection")
.set(&user3)
.expect("can set user");
let (user4_id, db_user4) = db
.collection()
.expect("can open user collection")
.set(&user4)
.expect("can set user");
println!("User 1 assigned ID: {}", user1_id);
println!("User 2 assigned ID: {}", user2_id);
@ -170,7 +186,8 @@ fn main() {
.build();
// Save the comment and get its assigned ID and updated model
let (comment_id, db_comment) = db.collection()
let (comment_id, db_comment) = db
.collection()
.expect("can open comment collection")
.set(&comment)
.expect("can set comment");
@ -186,7 +203,8 @@ fn main() {
updated_user.base_data.add_comment(db_comment.get_id());
// Save the updated user and get the new version
let (_, user_with_comment) = db.collection::<User>()
let (_, user_with_comment) = db
.collection::<User>()
.expect("can open user collection")
.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::models::biz::register_biz_rhai_module; // Corrected path
use rhai::{Engine, EvalAltResult, Scope};
use std::fs;
use std::sync::Arc;
fn main() -> Result<(), Box<EvalAltResult>> {
println!("Executing Rhai script: examples/biz_rhai/biz.rhai");
@ -20,8 +20,12 @@ fn main() -> Result<(), Box<EvalAltResult>> {
// Read the Rhai script from file
let script_path = "examples/biz_rhai/biz.rhai";
let script_content = fs::read_to_string(script_path)
.map_err(|e| Box::new(EvalAltResult::ErrorSystem(format!("Cannot read script file: {}", script_path), e.into())))?;
let script_content = fs::read_to_string(script_path).map_err(|e| {
Box::new(EvalAltResult::ErrorSystem(
format!("Cannot read script file: {}", script_path),
e.into(),
))
})?;
// Create a new scope
let mut scope = Scope::new();

View File

@ -1,6 +1,6 @@
use chrono::{Duration, Utc};
use heromodels::db::{Collection, Db};
use heromodels::models::calendar::{Attendee, AttendanceStatus, Calendar, Event};
use heromodels::models::calendar::{AttendanceStatus, Attendee, Calendar, Event};
use heromodels_core::Model;
fn main() {
@ -12,10 +12,8 @@ fn main() {
println!("====================================");
// --- Create Attendees ---
let attendee1 = Attendee::new("user_123".to_string())
.status(AttendanceStatus::Accepted);
let attendee2 = Attendee::new("user_456".to_string())
.status(AttendanceStatus::Tentative);
let attendee1 = Attendee::new("user_123".to_string()).status(AttendanceStatus::Accepted);
let attendee2 = Attendee::new("user_456".to_string()).status(AttendanceStatus::Tentative);
let attendee3 = Attendee::new("user_789".to_string()); // Default NoResponse
// --- Create Events ---
@ -45,7 +43,7 @@ fn main() {
"event_gamma".to_string(),
"Client Call",
now + Duration::days(2),
now + Duration::days(2) + Duration::seconds(3600)
now + Duration::days(2) + Duration::seconds(3600),
);
// --- Create Calendars ---
@ -58,25 +56,43 @@ fn main() {
.add_event(event2.clone());
// Create a calendar with auto-generated ID (explicit IDs are no longer supported)
let calendar2 = Calendar::new(None, "Personal Calendar")
.add_event(event3_for_calendar2.clone());
let calendar2 =
Calendar::new(None, "Personal Calendar").add_event(event3_for_calendar2.clone());
// --- Store Calendars in DB ---
let cal_collection = db.collection::<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 (_, calendar2) = cal_collection.set(&calendar2).expect("can set calendar2");
println!("Created calendar1 (ID: {}): Name - '{}'", calendar1.get_id(), calendar1.name);
println!("Created calendar2 (ID: {}): Name - '{}'", calendar2.get_id(), calendar2.name);
println!(
"Created calendar1 (ID: {}): Name - '{}'",
calendar1.get_id(),
calendar1.name
);
println!(
"Created calendar2 (ID: {}): Name - '{}'",
calendar2.get_id(),
calendar2.name
);
// --- Retrieve a Calendar by ID ---
let stored_calendar1_opt = cal_collection.get_by_id(calendar1.get_id()).expect("can try to load calendar1");
assert!(stored_calendar1_opt.is_some(), "Calendar1 should be found in DB");
let stored_calendar1_opt = cal_collection
.get_by_id(calendar1.get_id())
.expect("can try to load calendar1");
assert!(
stored_calendar1_opt.is_some(),
"Calendar1 should be found in DB"
);
let mut stored_calendar1 = stored_calendar1_opt.unwrap();
println!("\nRetrieved calendar1 from DB: Name - '{}', Events count: {}", stored_calendar1.name, stored_calendar1.events.len());
println!(
"\nRetrieved calendar1 from DB: Name - '{}', Events count: {}",
stored_calendar1.name,
stored_calendar1.events.len()
);
assert_eq!(stored_calendar1.name, "Work Calendar");
assert_eq!(stored_calendar1.events.len(), 2);
assert_eq!(stored_calendar1.events[0].title, "Team Meeting");
@ -91,42 +107,76 @@ fn main() {
event_to_update.reschedule(new_start_time, new_end_time)
});
let rescheduled_event = stored_calendar1.events.iter().find(|e| e.id == event_id_to_reschedule)
let rescheduled_event = stored_calendar1
.events
.iter()
.find(|e| e.id == event_id_to_reschedule)
.expect("Rescheduled event should exist");
assert_eq!(rescheduled_event.start_time, new_start_time);
assert_eq!(rescheduled_event.end_time, new_end_time);
println!("Event '{}' rescheduled in stored_calendar1.", rescheduled_event.title);
println!(
"Event '{}' rescheduled in stored_calendar1.",
rescheduled_event.title
);
// --- Store the modified calendar ---
let (_, mut stored_calendar1) = cal_collection.set(&stored_calendar1).expect("can set modified calendar1");
let re_retrieved_calendar1_opt = cal_collection.get_by_id(calendar1.get_id()).expect("can try to load modified calendar1");
let (_, mut stored_calendar1) = cal_collection
.set(&stored_calendar1)
.expect("can set modified calendar1");
let re_retrieved_calendar1_opt = cal_collection
.get_by_id(calendar1.get_id())
.expect("can try to load modified calendar1");
let re_retrieved_calendar1 = re_retrieved_calendar1_opt.unwrap();
let re_retrieved_event = re_retrieved_calendar1.events.iter().find(|e| e.id == event_id_to_reschedule)
let re_retrieved_event = re_retrieved_calendar1
.events
.iter()
.find(|e| e.id == event_id_to_reschedule)
.expect("Rescheduled event should exist in re-retrieved calendar");
assert_eq!(re_retrieved_event.start_time, new_start_time, "Reschedule not persisted correctly");
assert_eq!(
re_retrieved_event.start_time, new_start_time,
"Reschedule not persisted correctly"
);
println!("\nModified and re-saved calendar1. Rescheduled event start time: {}", re_retrieved_event.start_time);
println!(
"\nModified and re-saved calendar1. Rescheduled event start time: {}",
re_retrieved_event.start_time
);
// --- Add a new event to an existing calendar ---
let event4_new = Event::new(
"event_delta".to_string(),
"1-on-1",
now + Duration::days(3),
now + Duration::days(3) + Duration::seconds(1800) // 30 minutes
now + Duration::days(3) + Duration::seconds(1800), // 30 minutes
);
stored_calendar1 = stored_calendar1.add_event(event4_new);
assert_eq!(stored_calendar1.events.len(), 3);
let (_, stored_calendar1) = cal_collection.set(&stored_calendar1).expect("can set calendar1 after adding new event");
println!("Added new event '1-on-1' to stored_calendar1. Total events: {}", stored_calendar1.events.len());
let (_, stored_calendar1) = cal_collection
.set(&stored_calendar1)
.expect("can set calendar1 after adding new event");
println!(
"Added new event '1-on-1' to stored_calendar1. Total events: {}",
stored_calendar1.events.len()
);
// --- Delete a Calendar ---
cal_collection.delete_by_id(calendar2.get_id()).expect("can delete calendar2");
let deleted_calendar2_opt = cal_collection.get_by_id(calendar2.get_id()).expect("can try to load deleted calendar2");
assert!(deleted_calendar2_opt.is_none(), "Calendar2 should be deleted from DB");
cal_collection
.delete_by_id(calendar2.get_id())
.expect("can delete calendar2");
let deleted_calendar2_opt = cal_collection
.get_by_id(calendar2.get_id())
.expect("can try to load deleted calendar2");
assert!(
deleted_calendar2_opt.is_none(),
"Calendar2 should be deleted from DB"
);
println!("\nDeleted calendar2 (ID: {}) from DB.", calendar2.get_id());
println!("Calendar model DB Prefix: {}", Calendar::db_prefix());
println!("\nExample finished. DB stored at {}", db_path);
println!("To clean up, you can manually delete the directory: {}", db_path);
println!(
"To clean up, you can manually delete the directory: {}",
db_path
);
}

View File

@ -1,11 +1,10 @@
use heromodels::db::hero::OurDB;
use heromodels::models::calendar::{Attendee, AttendanceStatus, Calendar, Event};
use heromodels::models::calendar::rhai::register_rhai_engine_functions;
use heromodels::models::calendar::{AttendanceStatus, Attendee, Calendar, Event};
use rhai::Engine;
use rhai_wrapper::wrap_vec_return;
use std::sync::Arc;
use std::{fs, path::Path};
use rhai_wrapper::wrap_vec_return;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize Rhai engine
@ -29,9 +28,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
});
// 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);
});
},
);
// Register getter methods for Calendar properties
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);
});
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
Calendar::new(Some(id as u32), "Retrieved Calendar")
});
},
);
// Register a function to check if a calendar exists
engine.register_fn("calendar_exists", |_db: Arc<OurDB>, id: i64| -> bool {
@ -63,11 +68,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Define the function separately to use with the wrap_vec_return macro
fn get_all_calendars(_db: Arc<OurDB>) -> Vec<Calendar> {
// In a real implementation, this would retrieve all calendars from the database
vec![Calendar::new(Some(1), "Calendar 1"), Calendar::new(Some(2), "Calendar 2")]
vec![
Calendar::new(Some(1), "Calendar 1"),
Calendar::new(Some(2), "Calendar 2"),
]
}
// Register the function with the wrap_vec_return macro
engine.register_fn("get_all_calendars", wrap_vec_return!(get_all_calendars, Arc<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| {
// 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());
// 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");
println!("\nAfter saving - CustomUser ID: {}", saved_user.get_id());
println!("After saving - CustomUser DB Keys: {:?}", saved_user.db_keys());
println!(
"After saving - CustomUser DB Keys: {:?}",
saved_user.db_keys()
);
println!("Returned ID: {}", user_id);
// Verify that the ID was auto-generated
@ -53,5 +58,8 @@ fn main() {
assert_ne!(saved_user.get_id(), 0);
println!("\nExample finished. DB stored at {}", db_path);
println!("To clean up, you can manually delete the directory: {}", db_path);
println!(
"To clean up, you can manually delete the directory: {}",
db_path
);
}

View File

@ -1,8 +1,10 @@
// heromodels/examples/finance_example/main.rs
use chrono::{Utc, Duration};
use chrono::{Duration, Utc};
use heromodels::models::finance::marketplace::{
Bid, BidStatus, Listing, ListingStatus, ListingType,
};
use heromodels::models::finance::{Account, Asset, AssetType};
use heromodels::models::finance::marketplace::{Listing, ListingType, ListingStatus, Bid, BidStatus};
fn main() {
println!("Finance Models Example\n");
@ -18,10 +20,13 @@ fn main() {
"My primary Ethereum wallet", // description
"ethereum", // ledger
"0x1234567890abcdef1234567890abcdef12345678", // address
"0xpubkey123456789" // pubkey
"0xpubkey123456789", // pubkey
);
println!("Created Account: '{}' (ID: {})", account.name, account.base_data.id);
println!(
"Created Account: '{}' (ID: {})",
account.name, account.base_data.id
);
println!("Owner: User {}", account.user_id);
println!("Blockchain: {}", account.ledger);
println!("Address: {}", account.address);
@ -67,7 +72,12 @@ fn main() {
println!("Added Assets to Account:");
for asset in &account.assets {
println!("- {} ({:?}): {} units", asset.name, asset.asset_type, asset.formatted_amount());
println!(
"- {} ({:?}): {} units",
asset.name,
asset.asset_type,
asset.formatted_amount()
);
}
println!("\nTotal Account Value (raw sum): {}", account.total_value());
@ -113,9 +123,18 @@ fn main() {
Some("https://example.com/usdc.png"), // image_url
);
println!("Created Fixed Price Listing: '{}' (ID: {})", fixed_price_listing.title, fixed_price_listing.base_data.id);
println!("Price: {} {}", fixed_price_listing.price, fixed_price_listing.currency);
println!("Type: {:?}, Status: {:?}", fixed_price_listing.listing_type, fixed_price_listing.status);
println!(
"Created Fixed Price Listing: '{}' (ID: {})",
fixed_price_listing.title, fixed_price_listing.base_data.id
);
println!(
"Price: {} {}",
fixed_price_listing.price, fixed_price_listing.currency
);
println!(
"Type: {:?}, Status: {:?}",
fixed_price_listing.listing_type, fixed_price_listing.status
);
println!("Expires: {}", fixed_price_listing.expires_at.unwrap());
println!("");
@ -126,10 +145,14 @@ fn main() {
println!("Fixed Price Sale Completed:");
println!("Status: {:?}", fixed_price_listing.status);
println!("Buyer: {}", fixed_price_listing.buyer_id.unwrap());
println!("Sale Price: {} {}", fixed_price_listing.sale_price.unwrap(), fixed_price_listing.currency);
println!(
"Sale Price: {} {}",
fixed_price_listing.sale_price.unwrap(),
fixed_price_listing.currency
);
println!("Sold At: {}", fixed_price_listing.sold_at.unwrap());
println!("");
},
}
Err(e) => println!("Error completing sale: {}", e),
}
@ -145,13 +168,26 @@ fn main() {
"ETH", // currency
ListingType::Auction, // listing_type
Some(Utc::now() + Duration::days(3)), // expires_at (3 days from now)
vec!["nft".to_string(), "collectible".to_string(), "cryptopunk".to_string()], // tags
vec![
"nft".to_string(),
"collectible".to_string(),
"cryptopunk".to_string(),
], // tags
Some("https://example.com/cryptopunk1234.png"), // image_url
);
println!("Created Auction Listing: '{}' (ID: {})", auction_listing.title, auction_listing.base_data.id);
println!("Starting Price: {} {}", auction_listing.price, auction_listing.currency);
println!("Type: {:?}, Status: {:?}", auction_listing.listing_type, auction_listing.status);
println!(
"Created Auction Listing: '{}' (ID: {})",
auction_listing.title, auction_listing.base_data.id
);
println!(
"Starting Price: {} {}",
auction_listing.price, auction_listing.currency
);
println!(
"Type: {:?}, Status: {:?}",
auction_listing.listing_type, auction_listing.status
);
println!("");
// Create some bids
@ -184,7 +220,7 @@ fn main() {
Ok(updated_listing) => {
auction_listing = updated_listing;
println!("- Bid added: 11.0 ETH from User 2001");
},
}
Err(e) => println!("Error adding bid: {}", e),
}
@ -192,7 +228,7 @@ fn main() {
Ok(updated_listing) => {
auction_listing = updated_listing;
println!("- Bid added: 12.5 ETH from User 2002");
},
}
Err(e) => println!("Error adding bid: {}", e),
}
@ -200,18 +236,21 @@ fn main() {
Ok(updated_listing) => {
auction_listing = updated_listing;
println!("- Bid added: 15.0 ETH from User 2003");
},
}
Err(e) => println!("Error adding bid: {}", e),
}
println!("\nCurrent Auction Status:");
println!("Current Price: {} {}", auction_listing.price, auction_listing.currency);
println!(
"Current Price: {} {}",
auction_listing.price, auction_listing.currency
);
if let Some(highest_bid) = auction_listing.highest_bid() {
println!("Highest Bid: {} {} from User {}",
highest_bid.amount,
highest_bid.currency,
highest_bid.bidder_id);
println!(
"Highest Bid: {} {} from User {}",
highest_bid.amount, highest_bid.currency, highest_bid.bidder_id
);
}
println!("Total Bids: {}", auction_listing.bids.len());
@ -223,20 +262,26 @@ fn main() {
auction_listing = updated_listing;
println!("Auction Completed:");
println!("Status: {:?}", auction_listing.status);
println!("Winner: User {}", auction_listing.buyer_id.as_ref().unwrap());
println!("Winning Bid: {} {}", auction_listing.sale_price.as_ref().unwrap(), auction_listing.currency);
println!(
"Winner: User {}",
auction_listing.buyer_id.as_ref().unwrap()
);
println!(
"Winning Bid: {} {}",
auction_listing.sale_price.as_ref().unwrap(),
auction_listing.currency
);
println!("");
println!("Final Bid Statuses:");
for bid in &auction_listing.bids {
println!("- User {}: {} {} (Status: {:?})",
bid.bidder_id,
bid.amount,
bid.currency,
bid.status);
println!(
"- User {}: {} {} (Status: {:?})",
bid.bidder_id, bid.amount, bid.currency, bid.status
);
}
println!("");
},
}
Err(e) => println!("Error completing auction: {}", e),
}
@ -256,9 +301,18 @@ fn main() {
None::<String>, // image_url
);
println!("Created Exchange Listing: '{}' (ID: {})", exchange_listing.title, exchange_listing.base_data.id);
println!("Offering: Asset {} ({:?})", exchange_listing.asset_id, exchange_listing.asset_type);
println!("Wanted: {} {}", exchange_listing.price, exchange_listing.currency);
println!(
"Created Exchange Listing: '{}' (ID: {})",
exchange_listing.title, exchange_listing.base_data.id
);
println!(
"Offering: Asset {} ({:?})",
exchange_listing.asset_id, exchange_listing.asset_type
);
println!(
"Wanted: {} {}",
exchange_listing.price, exchange_listing.currency
);
println!("");
// --- PART 3: DEMONSTRATING EDGE CASES ---
@ -319,7 +373,10 @@ fn main() {
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);
// Check expiration

View File

@ -1,12 +1,12 @@
use rhai::{Engine, Scope, EvalAltResult};
use std::sync::{Arc, Mutex};
use rhai::{Engine, EvalAltResult, Scope};
use std::collections::HashMap;
use std::fs;
use std::sync::{Arc, Mutex};
// Import the models and the registration function
use heromodels::models::finance::account::Account;
use heromodels::models::finance::asset::{Asset};
use heromodels::models::finance::marketplace::{Listing};
use heromodels::models::finance::asset::Asset;
use heromodels::models::finance::marketplace::Listing;
use heromodels::models::finance::rhai::register_rhai_engine_functions;
// Define a simple in-memory mock database for the example
@ -42,7 +42,7 @@ fn main() -> Result<(), Box<EvalAltResult>> {
&mut engine,
Arc::clone(&mock_db.accounts),
Arc::clone(&mock_db.assets),
Arc::clone(&mock_db.listings)
Arc::clone(&mock_db.listings),
);
println!("Rhai functions registered.");
@ -77,8 +77,13 @@ fn main() -> Result<(), Box<EvalAltResult>> {
println!("No accounts in mock DB.");
}
for (id, account) in final_accounts.iter() {
println!("Account ID: {}, Name: '{}', User ID: {}, Assets: {}",
id, account.name, account.user_id, account.assets.len());
println!(
"Account ID: {}, Name: '{}', User ID: {}, Assets: {}",
id,
account.name,
account.user_id,
account.assets.len()
);
}
// Print final state of Assets
@ -88,8 +93,10 @@ fn main() -> Result<(), Box<EvalAltResult>> {
println!("No assets in mock DB.");
}
for (id, asset) in final_assets.iter() {
println!("Asset ID: {}, Name: '{}', Amount: {}, Type: {:?}",
id, asset.name, asset.amount, asset.asset_type);
println!(
"Asset ID: {}, Name: '{}', Amount: {}, Type: {:?}",
id, asset.name, asset.amount, asset.asset_type
);
}
// Print final state of Listings
@ -101,7 +108,12 @@ fn main() -> Result<(), Box<EvalAltResult>> {
for (id, listing) in final_listings.iter() {
println!(
"Listing ID: {}, Title: '{}', Type: {:?}, Status: {:?}, Price: {}, Bids: {}",
id, listing.title, listing.listing_type, listing.status, listing.price, listing.bids.len()
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() {
// Create a new DB instance in /tmp/ourdb_flowbroker, and reset before every run
let db = heromodels::db::hero::OurDB::new("/tmp/ourdb_flowbroker", true)
.expect("Can create DB");
let db =
heromodels::db::hero::OurDB::new("/tmp/ourdb_flowbroker", true).expect("Can create DB");
println!("Hero Models - Flow Example");
println!("===========================");
@ -25,7 +25,10 @@ fn main() {
"Document Approval Flow", // name
"Pending", // status
);
db.collection().expect("can open flow collection").set(&flow1).expect("can set flow1");
db.collection()
.expect("can open flow collection")
.set(&flow1)
.expect("can set flow1");
println!("Created Flow: {:?}", flow1);
println!("Flow ID: {}", flow1.get_id());
println!("Flow DB Prefix: {}", Flow::db_prefix());
@ -38,7 +41,10 @@ fn main() {
"Pending", // status
)
.description("Initial review by manager");
db.collection().expect("can open flow_step collection").set(&step1_flow1).expect("can set step1_flow1");
db.collection()
.expect("can open flow_step collection")
.set(&step1_flow1)
.expect("can set step1_flow1");
println!("Created FlowStep: {:?}", step1_flow1);
let step2_flow1 = FlowStep::new(
@ -48,7 +54,10 @@ fn main() {
"Pending", // status
)
.description("Legal team sign-off");
db.collection().expect("can open flow_step collection").set(&step2_flow1).expect("can set step2_flow1");
db.collection()
.expect("can open flow_step collection")
.set(&step2_flow1)
.expect("can set step2_flow1");
println!("Created FlowStep: {:?}", step2_flow1);
// --- Create SignatureRequirements for step2_flow1 ---
@ -59,7 +68,10 @@ fn main() {
"I approve this document for legal compliance.", // message
"Pending", // status
);
db.collection().expect("can open sig_req collection").set(&sig_req1_step2).expect("can set sig_req1_step2");
db.collection()
.expect("can open sig_req collection")
.set(&sig_req1_step2)
.expect("can set sig_req1_step2");
println!("Created SignatureRequirement: {:?}", sig_req1_step2);
let sig_req2_step2 = SignatureRequirement::new(
@ -69,7 +81,10 @@ fn main() {
"I, as General Counsel, approve this document.", // message
"Pending", // status
);
db.collection().expect("can open sig_req collection").set(&sig_req2_step2).expect("can set sig_req2_step2");
db.collection()
.expect("can open sig_req collection")
.set(&sig_req2_step2)
.expect("can set sig_req2_step2");
println!("Created SignatureRequirement: {:?}", sig_req2_step2);
// --- Retrieve and Verify ---
@ -101,9 +116,18 @@ fn main() {
.get::<flow_step_flow_id_idx, _>(&retrieved_flow.get_id())
.expect("can load steps for flow1");
assert_eq!(steps_for_flow1.len(), 2);
println!("Retrieved {} FlowSteps for Flow ID {}:", steps_for_flow1.len(), retrieved_flow.get_id());
println!(
"Retrieved {} FlowSteps for Flow ID {}:",
steps_for_flow1.len(),
retrieved_flow.get_id()
);
for step in &steps_for_flow1 {
println!(" - Step ID: {}, Order: {}, Desc: {:?}", step.get_id(), step.step_order, step.description);
println!(
" - Step ID: {}, Order: {}, Desc: {:?}",
step.get_id(),
step.step_order,
step.description
);
}
// --- Update a SignatureRequirement (simulate signing) ---
@ -114,12 +138,18 @@ fn main() {
.expect("can load sig_req1")
.unwrap();
println!("\nUpdating SignatureRequirement ID: {}", retrieved_sig_req1.get_id());
println!(
"\nUpdating SignatureRequirement ID: {}",
retrieved_sig_req1.get_id()
);
retrieved_sig_req1.status = "Signed".to_string();
retrieved_sig_req1.signed_by = Some("pubkey_legal_team_lead_hex_actual_signer".to_string());
retrieved_sig_req1.signature = Some("mock_signature_base64_encoded".to_string());
db.collection().expect("can open sig_req collection").set(&retrieved_sig_req1).expect("can update sig_req1");
db.collection()
.expect("can open sig_req collection")
.set(&retrieved_sig_req1)
.expect("can update sig_req1");
let updated_sig_req1 = db
.collection::<SignatureRequirement>()
@ -129,7 +159,10 @@ fn main() {
.unwrap();
assert_eq!(updated_sig_req1.status, "Signed");
assert_eq!(updated_sig_req1.signature.as_deref(), Some("mock_signature_base64_encoded"));
assert_eq!(
updated_sig_req1.signature.as_deref(),
Some("mock_signature_base64_encoded")
);
println!("Updated SignatureRequirement: {:?}", updated_sig_req1);
// --- Delete a FlowStep ---
@ -157,7 +190,11 @@ fn main() {
.expect("can load remaining steps for flow1");
assert_eq!(remaining_steps_for_flow1.len(), 1);
assert_eq!(remaining_steps_for_flow1[0].get_id(), step2_flow1.get_id());
println!("Remaining FlowSteps for Flow ID {}: count = {}", retrieved_flow.get_id(), remaining_steps_for_flow1.len());
println!(
"Remaining FlowSteps for Flow ID {}: count = {}",
retrieved_flow.get_id(),
remaining_steps_for_flow1.len()
);
println!("\nFlow example finished successfully!");
}

View File

@ -20,8 +20,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let script_path = Path::new(script_path_str);
if !script_path.exists() {
eprintln!("Error: Rhai script not found at {}", script_path_str);
eprintln!("Please ensure the script 'flow.rhai' exists in the 'examples/flow_rhai/' directory.");
return Err(Box::new(std::io::Error::new(std::io::ErrorKind::NotFound, format!("Rhai script not found: {}", script_path_str))));
eprintln!(
"Please ensure the script 'flow.rhai' exists in the 'examples/flow_rhai/' directory."
);
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Rhai script not found: {}", script_path_str),
)));
}
println!("Executing Rhai script: {}", script_path_str);

View File

@ -108,7 +108,7 @@ fn main() {
7, // user_id
1, // chosen_option_id (Approve Allocation)
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
@ -117,7 +117,7 @@ fn main() {
8, // user_id
2, // chosen_option_id (Reject Allocation)
60, // shares
"I have concerns about the allocation priorities."
"I have concerns about the allocation priorities.",
);
println!("\nBallots with Comments:");
@ -225,7 +225,7 @@ fn main() {
20, // user_id (eligible)
1, // chosen_option_id
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
@ -234,7 +234,7 @@ fn main() {
30, // user_id (eligible)
2, // chosen_option_id
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
@ -243,7 +243,7 @@ fn main() {
40, // user_id (ineligible)
1, // chosen_option_id
50, // shares
"This restructuring seems unnecessary."
"This restructuring seems unnecessary.",
);
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::models::governance::{Proposal, ProposalStatus, VoteEventStatus, VoteOption, Ballot};
use heromodels::models::governance::{
Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption,
};
use rhai::Engine;
use rhai_client_macros::rhai;
use rhai_wrapper::wrap_vec_return;
use std::sync::Arc;
use chrono::{Utc, Duration};
use rhai_client_macros::rhai;
// Define the functions we want to expose to Rhai
// We'll only use the #[rhai] attribute on functions with simple types
@ -13,7 +15,14 @@ use rhai_client_macros::rhai;
fn create_proposal(id: i64, creator_id: String, title: String, description: String) -> Proposal {
let start_date = Utc::now();
let end_date = start_date + Duration::days(14);
Proposal::new(id as u32, creator_id, title, description, start_date, end_date)
Proposal::new(
id as u32,
creator_id,
title,
description,
start_date,
end_date,
)
}
// Getter functions for Proposal properties
@ -46,7 +55,13 @@ fn add_option_to_proposal(proposal: Proposal, option_id: i64, option_text: Strin
proposal.add_option(option_id as u8, option_text)
}
fn cast_vote_on_proposal(proposal: Proposal, ballot_id: i64, user_id: i64, option_id: i64, shares: i64) -> Proposal {
fn cast_vote_on_proposal(
proposal: Proposal,
ballot_id: i64,
user_id: i64,
option_id: i64,
shares: i64,
) -> Proposal {
proposal.cast_vote(ballot_id as u32, user_id as u32, option_id as u8, shares)
}
@ -119,14 +134,24 @@ fn get_ballot_shares(ballot: &Ballot) -> i64 {
// Simple functions that we can use with the #[rhai] attribute
#[rhai]
fn create_proposal_wrapper(id: i64, creator_id: String, title: String, description: String) -> String {
fn create_proposal_wrapper(
id: i64,
creator_id: String,
title: String,
description: String,
) -> String {
let proposal = create_proposal(id, creator_id, title, description);
format!("Created proposal with ID: {}", proposal.base_data.id)
}
#[rhai]
fn add_option_wrapper(id: i64, option_id: i64, option_text: String) -> String {
let proposal = create_proposal(id, "user".to_string(), "title".to_string(), "description".to_string());
let proposal = create_proposal(
id,
"user".to_string(),
"title".to_string(),
"description".to_string(),
);
let updated = add_option_to_proposal(proposal, option_id, option_text.clone());
format!("Added option '{}' to proposal {}", option_text, id)
}
@ -141,8 +166,22 @@ fn get_all_proposals(_db: Arc<OurDB>) -> Vec<Proposal> {
let start_date = Utc::now();
let end_date = start_date + Duration::days(14);
vec![
Proposal::new(1, "Creator 1", "Proposal 1", "Description 1", start_date, end_date),
Proposal::new(2, "Creator 2", "Proposal 2", "Description 2", start_date, end_date)
Proposal::new(
1,
"Creator 1",
"Proposal 1",
"Description 1",
start_date,
end_date,
),
Proposal::new(
2,
"Creator 2",
"Proposal 2",
"Description 2",
start_date,
end_date,
),
]
}
@ -172,19 +211,37 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
engine.register_fn("get_db", move || db_for_get_db.clone());
// Register builder functions for Proposal and related types
engine.register_fn("create_proposal", |id: i64, creator_id: String, title: String, description: String| {
engine.register_fn(
"create_proposal",
|id: i64, creator_id: String, title: String, description: String| {
let start_date = Utc::now();
let end_date = start_date + Duration::days(14);
Proposal::new(id as u32, creator_id, title, description, start_date, end_date)
});
Proposal::new(
id as u32,
creator_id,
title,
description,
start_date,
end_date,
)
},
);
engine.register_fn("create_vote_option", |id: i64, text: String| {
VoteOption::new(id as u8, text)
});
engine.register_fn("create_ballot", |id: i64, user_id: i64, vote_option_id: i64, shares_count: i64| {
Ballot::new(id as u32, user_id as u32, vote_option_id as u8, shares_count)
});
engine.register_fn(
"create_ballot",
|id: i64, user_id: i64, vote_option_id: i64, shares_count: i64| {
Ballot::new(
id as u32,
user_id as u32,
vote_option_id as u8,
shares_count,
)
},
);
// Register getter and setter methods for Proposal properties
engine.register_fn("get_title", get_title);
@ -203,12 +260,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Register functions for database operations
engine.register_fn("save_proposal", save_proposal);
engine.register_fn("get_proposal_by_id", |_db: Arc<OurDB>, id: i64| -> Proposal {
engine.register_fn(
"get_proposal_by_id",
|_db: Arc<OurDB>, id: i64| -> Proposal {
// In a real implementation, this would retrieve the proposal from the database
let start_date = Utc::now();
let end_date = start_date + Duration::days(14);
Proposal::new(id as u32, "Retrieved Creator", "Retrieved Proposal", "Retrieved Description", start_date, end_date)
});
Proposal::new(
id as u32,
"Retrieved Creator",
"Retrieved Proposal",
"Retrieved Description",
start_date,
end_date,
)
},
);
// Register a function to check if a proposal exists
engine.register_fn("proposal_exists", |_db: Arc<OurDB>, id: i64| -> bool {
@ -217,7 +284,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
});
// 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);
@ -242,33 +312,41 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Use the database instance
// Create a new proposal
let proposal = create_proposal(1,
let proposal = create_proposal(
1,
"user_creator_123".to_string(),
"Community Fund Allocation for Q3".to_string(),
"Proposal to allocate funds for community projects in the third quarter.".to_string());
"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_id(&proposal));
println!("Status: {}, Vote Status: {}",
get_id(&proposal)
);
println!(
"Status: {}, Vote Status: {}",
get_status(&proposal),
get_vote_status(&proposal));
get_vote_status(&proposal)
);
// Add vote options
let mut proposal_with_options = add_option_to_proposal(
proposal, 1, "Approve Allocation".to_string());
proposal_with_options = add_option_to_proposal(
proposal_with_options, 2, "Reject Allocation".to_string());
proposal_with_options = add_option_to_proposal(
proposal_with_options, 3, "Abstain".to_string());
let mut proposal_with_options =
add_option_to_proposal(proposal, 1, "Approve Allocation".to_string());
proposal_with_options =
add_option_to_proposal(proposal_with_options, 2, "Reject Allocation".to_string());
proposal_with_options = add_option_to_proposal(proposal_with_options, 3, "Abstain".to_string());
println!("\nAdded Vote Options:");
let option_count = get_option_count(&proposal_with_options);
for i in 0..option_count {
let option = get_option_at(&proposal_with_options, i);
println!("- Option ID: {}, Text: '{}', Votes: {}",
i, get_option_text(&option),
get_option_votes(&option));
println!(
"- Option ID: {}, Text: '{}', Votes: {}",
i,
get_option_text(&option),
get_option_votes(&option)
);
}
// Save the proposal to the database
@ -278,48 +356,52 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Simulate casting votes
println!("\nSimulating Votes...");
// User 1 votes for 'Approve Allocation' with 100 shares
let mut proposal_with_votes = cast_vote_on_proposal(
proposal_with_options, 101, 1, 1, 100);
let mut proposal_with_votes = cast_vote_on_proposal(proposal_with_options, 101, 1, 1, 100);
// User 2 votes for 'Reject Allocation' with 50 shares
proposal_with_votes = cast_vote_on_proposal(
proposal_with_votes, 102, 2, 2, 50);
proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 102, 2, 2, 50);
// User 3 votes for 'Approve Allocation' with 75 shares
proposal_with_votes = cast_vote_on_proposal(
proposal_with_votes, 103, 3, 1, 75);
proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 103, 3, 1, 75);
// User 4 abstains with 20 shares
proposal_with_votes = cast_vote_on_proposal(
proposal_with_votes, 104, 4, 3, 20);
proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 104, 4, 3, 20);
println!("\nVote Counts After Simulation:");
let option_count = get_option_count(&proposal_with_votes);
for i in 0..option_count {
let option = get_option_at(&proposal_with_votes, i);
println!("- Option ID: {}, Text: '{}', Votes: {}",
i, get_option_text(&option),
get_option_votes(&option));
println!(
"- Option ID: {}, Text: '{}', Votes: {}",
i,
get_option_text(&option),
get_option_votes(&option)
);
}
println!("\nBallots Cast:");
let ballot_count = get_ballot_count(&proposal_with_votes);
for i in 0..ballot_count {
let ballot = get_ballot_at(&proposal_with_votes, i);
println!("- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}",
i, get_ballot_user_id(&ballot),
println!(
"- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}",
i,
get_ballot_user_id(&ballot),
get_ballot_option_id(&ballot),
get_ballot_shares(&ballot));
get_ballot_shares(&ballot)
);
}
// Change proposal status
let active_proposal = change_proposal_status(
proposal_with_votes, "Active".to_string());
println!("\nChanged Proposal Status to: {}",
get_status(&active_proposal));
let active_proposal = change_proposal_status(proposal_with_votes, "Active".to_string());
println!(
"\nChanged Proposal Status to: {}",
get_status(&active_proposal)
);
// Simulate closing the vote
let closed_proposal = change_vote_event_status(
active_proposal, "Closed".to_string());
println!("Changed Vote Event Status to: {}",
get_vote_status(&closed_proposal));
let closed_proposal = change_vote_event_status(active_proposal, "Closed".to_string());
println!(
"Changed Vote Event Status to: {}",
get_vote_status(&closed_proposal)
);
// Final proposal state
println!("\nFinal Proposal State:");
@ -330,9 +412,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let option_count = get_option_count(&closed_proposal);
for i in 0..option_count {
let option = get_option_at(&closed_proposal, i);
println!(" - {}: {} (Votes: {})",
i, get_option_text(&option),
get_option_votes(&option));
println!(
" - {}: {} (Votes: {})",
i,
get_option_text(&option),
get_option_votes(&option)
);
}
println!("Total Ballots: {}", get_ballot_count(&closed_proposal));
@ -340,9 +425,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let all_proposals = get_all_proposals(db.clone());
println!("\nTotal Proposals in Database: {}", all_proposals.len());
for proposal in all_proposals {
println!("Proposal ID: {}, Title: '{}'",
println!(
"Proposal ID: {}, Title: '{}'",
get_id(&proposal),
get_title(&proposal));
get_title(&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
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 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);
let add_option_result = add_option_wrapper_rhai_client(&engine, 2, 4, "Rhai Option".to_string());
let add_option_result =
add_option_wrapper_rhai_client(&engine, 2, 4, "Rhai Option".to_string());
println!("{}", add_option_result);
println!("\nGovernance Proposal Example Finished.");

View File

@ -97,7 +97,10 @@ fn main() {
println!("Updated At (timestamp): {}", contract.base_data.modified_at); // From BaseModelData
if let Some(first_signer_details) = contract.signers.first() {
println!("\nFirst Signer: {} ({})", first_signer_details.name, first_signer_details.email);
println!(
"\nFirst Signer: {} ({})",
first_signer_details.name, first_signer_details.email
);
println!(" Status: {:?}", first_signer_details.status);
if let Some(signed_time) = first_signer_details.signed_at {
println!(" Signed At: {}", signed_time);

View File

@ -22,8 +22,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
if !script_path.exists() {
eprintln!("Error: Rhai script not found at {}", script_path_str);
eprintln!("Please ensure the script 'legal.rhai' exists in the 'examples/legal_rhai/' directory.");
return Err(Box::new(std::io::Error::new(std::io::ErrorKind::NotFound, format!("Rhai script not found: {}", script_path_str))));
eprintln!(
"Please ensure the script 'legal.rhai' exists in the 'examples/legal_rhai/' directory."
);
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Rhai script not found: {}", script_path_str),
)));
}
println!("Executing Rhai script: {}", script_path_str);

View File

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

View File

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

View File

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

View File

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

View File

@ -57,7 +57,9 @@ where
fn get_all(&self) -> Result<Vec<V>, Error<Self::Error>>;
/// 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

View File

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

View File

@ -3,5 +3,5 @@ pub mod access;
pub mod rhai;
// Re-export contact, Group from the inner contact module (contact.rs) within src/models/contact/mod.rs
pub use self::access::{Access};
pub use self::access::Access;
pub use rhai::register_access_rhai_module;

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 rhai::plugin::*;
use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position};
use std::mem;
use std::sync::Arc;
use super::access::{Access};
use super::access::Access;
type RhaiAccess = Access;
use crate::db::hero::OurDB;
use crate::db::Collection;
use crate::db::hero::OurDB;
// Helper to convert i64 from Rhai to u32 for IDs
fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_|
u32::try_from(id_i64).map_err(|_| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert ID '{}' to u32", id_i64).into(),
Position::NONE
Position::NONE,
))
)
})
}
#[export_module]
@ -30,7 +30,10 @@ mod rhai_access_module {
/// Sets the access name
#[rhai_fn(name = "object_id", return_raw, global, pure)]
pub fn access_object_id(access: &mut RhaiAccess, object_id: u32) -> Result<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
let default_access = Access::new();
@ -41,7 +44,10 @@ mod rhai_access_module {
}
#[rhai_fn(name = "circle_id", return_raw, global, pure)]
pub fn access_circle_id(access: &mut RhaiAccess, circle_id: u32) -> Result<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
let default_access = Access::new();
@ -52,7 +58,10 @@ mod rhai_access_module {
}
#[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
let default_access = Access::new();
@ -63,7 +72,10 @@ mod rhai_access_module {
}
#[rhai_fn(name = "contact_id", return_raw, global, pure)]
pub fn access_contact_id(access: &mut RhaiAccess, contact_id: u32) -> Result<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
let default_access = Access::new();
@ -74,7 +86,10 @@ mod rhai_access_module {
}
#[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
let default_access = Access::new();
@ -86,28 +101,44 @@ mod rhai_access_module {
// Access Getters
#[rhai_fn(get = "id", pure)]
pub fn get_access_id(access: &mut RhaiAccess) -> i64 { access.base_data.id as i64 }
pub fn get_access_id(access: &mut RhaiAccess) -> i64 {
access.base_data.id as i64
}
#[rhai_fn(get = "object_id", pure)]
pub fn get_access_object_id(access: &mut RhaiAccess) -> i64 { access.object_id as i64 }
pub fn get_access_object_id(access: &mut RhaiAccess) -> i64 {
access.object_id as i64
}
#[rhai_fn(get = "circle_id", pure)]
pub fn get_access_circle_id(access: &mut RhaiAccess) -> i64 { access.circle_id as i64 }
pub fn get_access_circle_id(access: &mut RhaiAccess) -> i64 {
access.circle_id as i64
}
#[rhai_fn(get = "group_id", pure)]
pub fn get_access_group_id(access: &mut RhaiAccess) -> i64 { access.group_id as i64 }
pub fn get_access_group_id(access: &mut RhaiAccess) -> i64 {
access.group_id as i64
}
#[rhai_fn(get = "contact_id", pure)]
pub fn get_access_contact_id(access: &mut RhaiAccess) -> i64 { access.contact_id as i64 }
pub fn get_access_contact_id(access: &mut RhaiAccess) -> i64 {
access.contact_id as i64
}
#[rhai_fn(get = "expires_at", pure)]
pub fn get_access_expires_at(access: &mut RhaiAccess) -> i64 { access.expires_at.unwrap_or(0) as i64 }
pub fn get_access_expires_at(access: &mut RhaiAccess) -> i64 {
access.expires_at.unwrap_or(0) as i64
}
#[rhai_fn(get = "created_at", pure)]
pub fn get_access_created_at(access: &mut RhaiAccess) -> i64 { access.base_data.created_at }
pub fn get_access_created_at(access: &mut RhaiAccess) -> i64 {
access.base_data.created_at
}
#[rhai_fn(get = "modified_at", pure)]
pub fn get_access_modified_at(access: &mut RhaiAccess) -> i64 { access.base_data.modified_at }
pub fn get_access_modified_at(access: &mut RhaiAccess) -> i64 {
access.base_data.modified_at
}
}
pub fn register_access_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
@ -119,56 +150,86 @@ pub fn register_access_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
let mut db_module = Module::new();
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
let result = db_clone_set_access.set(&access)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_access: {}", e).into(), Position::NONE)))?;
let result = db_clone_set_access.set(&access).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error set_access: {}", e).into(),
Position::NONE,
))
})?;
// Return the updated access with the correct ID
Ok(result.1)
});
},
);
// Manually register database functions as they need to capture 'db'
let db_clone_delete_access = db.clone();
db_module.set_native_fn("delete_access", move |access: Access| -> Result<(), Box<EvalAltResult>> {
db_module.set_native_fn(
"delete_access",
move |access: Access| -> Result<(), Box<EvalAltResult>> {
// 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")
.delete_by_id(access.base_data.id)
.expect("can delete event");
// Return the updated event with the correct ID
Ok(result)
});
},
);
let db_clone_get_access = db.clone();
db_module.set_native_fn("get_access_by_id", move |id_i64: INT| -> Result<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)?;
// Use the Collection trait method directly
db_clone_get_access.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_access_by_id: {}", e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Access with ID {} not found", id_u32).into(), Position::NONE)))
});
db_clone_get_access
.get_by_id(id_u32)
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error get_access_by_id: {}", e).into(),
Position::NONE,
))
})?
.ok_or_else(|| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Access with ID {} not found", id_u32).into(),
Position::NONE,
))
})
},
);
// Add list_accesss function to get all accesss
let db_clone_list_accesss = db.clone();
db_module.set_native_fn("list_accesss", move || -> Result<Dynamic, Box<EvalAltResult>> {
let collection = db_clone_list_accesss.collection::<Access>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
db_module.set_native_fn(
"list_accesss",
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(),
Position::NONE
)))?;
let accesss = collection.get_all()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
Position::NONE,
))
})?;
let accesss = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all accesss: {:?}", e).into(),
Position::NONE
)))?;
Position::NONE,
))
})?;
let mut array = Array::new();
for access in accesss {
array.push(Dynamic::from(access));
}
Ok(Dynamic::from(array))
});
},
);
// Register the database module globally
engine.register_global_module(db_module.into());

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::{BaseModelData, Index};
use heromodels_derive::model;
use rhai::{CustomType, TypeBuilder}; // For #[derive(CustomType)]
use serde::{Deserialize, Serialize};
// --- Enums ---

View File

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

View File

@ -1,6 +1,6 @@
use serde::{Serialize, Deserialize};
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use serde::{Deserialize, Serialize};
// ProductType represents the type of a product
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]

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::hero::OurDB;
use crate::db::Db;
use crate::db::hero::OurDB;
use rhai::plugin::*;
use rhai::{Dynamic, Engine, EvalAltResult, INT, Module, Position};
use std::mem;
use std::sync::Arc;
use super::company::{Company, CompanyStatus, BusinessType};
use crate::models::biz::shareholder::{Shareholder, ShareholderType};
use crate::models::biz::product::{Product, ProductType, ProductStatus, ProductComponent};
use super::company::{BusinessType, Company, CompanyStatus};
use crate::models::biz::product::{Product, ProductComponent, ProductStatus, ProductType};
use crate::models::biz::sale::{Sale, SaleItem, SaleStatus};
use crate::models::biz::shareholder::{Shareholder, ShareholderType};
use heromodels_core::Model;
type RhaiCompany = Company;
@ -21,12 +21,12 @@ type RhaiSaleItem = SaleItem;
// Helper to convert i64 from Rhai to u32 for IDs
fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_|
u32::try_from(id_i64).map_err(|_| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert ID '{}' to u32", id_i64).into(),
Position::NONE
Position::NONE,
))
)
})
}
#[export_module]
@ -39,42 +39,60 @@ mod rhai_biz_module {
// Company builder methods
#[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);
*company = owned_company.name(name);
Ok(company.clone())
}
#[rhai_fn(name = "fiscal_year_end", return_raw, global, pure)]
pub fn company_fiscal_year_end(company: &mut RhaiCompany, fiscal_year_end: String) -> Result<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);
*company = owned_company.fiscal_year_end(fiscal_year_end);
Ok(company.clone())
}
#[rhai_fn(name = "registration_number", return_raw, global, pure)]
pub fn company_registration_number(company: &mut RhaiCompany, reg_num: String) -> Result<RhaiCompany, Box<EvalAltResult>> {
pub fn company_registration_number(
company: &mut RhaiCompany,
reg_num: String,
) -> Result<RhaiCompany, Box<EvalAltResult>> {
let owned_company = mem::take(company);
*company = owned_company.registration_number(reg_num);
Ok(company.clone())
}
#[rhai_fn(name = "incorporation_date", return_raw, global, pure)]
pub fn company_incorporation_date(company: &mut RhaiCompany, date: i64) -> Result<RhaiCompany, Box<EvalAltResult>> {
pub fn company_incorporation_date(
company: &mut RhaiCompany,
date: i64,
) -> Result<RhaiCompany, Box<EvalAltResult>> {
let owned_company = mem::take(company);
*company = owned_company.incorporation_date(date);
Ok(company.clone())
}
#[rhai_fn(name = "status", return_raw, global, pure)]
pub fn company_status(company: &mut RhaiCompany, status: CompanyStatus) -> Result<RhaiCompany, Box<EvalAltResult>> {
pub fn company_status(
company: &mut RhaiCompany,
status: CompanyStatus,
) -> Result<RhaiCompany, Box<EvalAltResult>> {
let owned_company = mem::take(company);
*company = owned_company.status(status);
Ok(company.clone())
}
#[rhai_fn(name = "business_type", return_raw, global, pure)]
pub fn company_business_type(company: &mut RhaiCompany, business_type: BusinessType) -> Result<RhaiCompany, Box<EvalAltResult>> {
pub fn company_business_type(
company: &mut RhaiCompany,
business_type: BusinessType,
) -> Result<RhaiCompany, Box<EvalAltResult>> {
let owned_company = mem::take(company);
*company = owned_company.business_type(business_type);
Ok(company.clone())
@ -134,14 +152,20 @@ mod rhai_biz_module {
// Shareholder builder methods
#[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);
*shareholder = owned_shareholder.name(name);
Ok(shareholder.clone())
}
#[rhai_fn(name = "company_id", return_raw, global, pure)]
pub fn shareholder_company_id(shareholder: &mut RhaiShareholder, company_id: i64) -> Result<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 owned_shareholder = mem::take(shareholder);
*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)]
pub fn shareholder_share_count(shareholder: &mut RhaiShareholder, share_count: f64) -> Result<RhaiShareholder, Box<EvalAltResult>> {
let owned_shareholder = mem::take(shareholder);
pub fn shareholder_share_count(
shareholder: &mut RhaiShareholder,
share_count: f64,
) -> Result<RhaiShareholder, Box<EvalAltResult>> {
shareholder.shares = share_count;
Ok(shareholder.clone())
}
#[rhai_fn(name = "type_", return_raw, global, pure)]
pub fn shareholder_type(shareholder: &mut RhaiShareholder, type_: ShareholderType) -> Result<RhaiShareholder, Box<EvalAltResult>> {
pub fn shareholder_type(
shareholder: &mut RhaiShareholder,
type_: ShareholderType,
) -> Result<RhaiShareholder, Box<EvalAltResult>> {
let owned_shareholder = mem::take(shareholder);
*shareholder = owned_shareholder.type_(type_);
Ok(shareholder.clone())
@ -196,21 +225,30 @@ mod rhai_biz_module {
// ProductComponent builder methods
#[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);
*component = owned_component.name(name);
Ok(component.clone())
}
#[rhai_fn(name = "description", return_raw, global, pure)]
pub fn product_component_description(component: &mut RhaiProductComponent, description: String) -> Result<RhaiProductComponent, Box<EvalAltResult>> {
pub fn product_component_description(
component: &mut RhaiProductComponent,
description: String,
) -> Result<RhaiProductComponent, Box<EvalAltResult>> {
let owned_component = mem::take(component);
*component = owned_component.description(description);
Ok(component.clone())
}
#[rhai_fn(name = "quantity", return_raw, global, pure)]
pub fn product_component_quantity(component: &mut RhaiProductComponent, quantity: i64) -> Result<RhaiProductComponent, Box<EvalAltResult>> {
pub fn product_component_quantity(
component: &mut RhaiProductComponent,
quantity: i64,
) -> Result<RhaiProductComponent, Box<EvalAltResult>> {
let owned_component = mem::take(component);
*component = owned_component.quantity(quantity as u32);
Ok(component.clone())
@ -240,77 +278,110 @@ mod rhai_biz_module {
// Product builder methods
#[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);
*product = owned_product.name(name);
Ok(product.clone())
}
#[rhai_fn(name = "description", return_raw, global, pure)]
pub fn product_description(product: &mut RhaiProduct, description: String) -> Result<RhaiProduct, Box<EvalAltResult>> {
pub fn product_description(
product: &mut RhaiProduct,
description: String,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product);
*product = owned_product.description(description);
Ok(product.clone())
}
#[rhai_fn(name = "price", return_raw, global, pure)]
pub fn product_price(product: &mut RhaiProduct, price: f64) -> Result<RhaiProduct, Box<EvalAltResult>> {
pub fn product_price(
product: &mut RhaiProduct,
price: f64,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product);
*product = owned_product.price(price);
Ok(product.clone())
}
#[rhai_fn(name = "type_", return_raw, global, pure)]
pub fn product_type(product: &mut RhaiProduct, type_: ProductType) -> Result<RhaiProduct, Box<EvalAltResult>> {
pub fn product_type(
product: &mut RhaiProduct,
type_: ProductType,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product);
*product = owned_product.type_(type_);
Ok(product.clone())
}
#[rhai_fn(name = "category", return_raw, global, pure)]
pub fn product_category(product: &mut RhaiProduct, category: String) -> Result<RhaiProduct, Box<EvalAltResult>> {
pub fn product_category(
product: &mut RhaiProduct,
category: String,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product);
*product = owned_product.category(category);
Ok(product.clone())
}
#[rhai_fn(name = "status", return_raw, global, pure)]
pub fn product_status(product: &mut RhaiProduct, status: ProductStatus) -> Result<RhaiProduct, Box<EvalAltResult>> {
pub fn product_status(
product: &mut RhaiProduct,
status: ProductStatus,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product);
*product = owned_product.status(status);
Ok(product.clone())
}
#[rhai_fn(name = "max_amount", return_raw, global, pure)]
pub fn product_max_amount(product: &mut RhaiProduct, max_amount: i64) -> Result<RhaiProduct, Box<EvalAltResult>> {
pub fn product_max_amount(
product: &mut RhaiProduct,
max_amount: i64,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product);
*product = owned_product.max_amount(max_amount as u16);
Ok(product.clone())
}
#[rhai_fn(name = "purchase_till", return_raw, global, pure)]
pub fn product_purchase_till(product: &mut RhaiProduct, purchase_till: i64) -> Result<RhaiProduct, Box<EvalAltResult>> {
pub fn product_purchase_till(
product: &mut RhaiProduct,
purchase_till: i64,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product);
*product = owned_product.purchase_till(purchase_till);
Ok(product.clone())
}
#[rhai_fn(name = "active_till", return_raw, global, pure)]
pub fn product_active_till(product: &mut RhaiProduct, active_till: i64) -> Result<RhaiProduct, Box<EvalAltResult>> {
pub fn product_active_till(
product: &mut RhaiProduct,
active_till: i64,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product);
*product = owned_product.active_till(active_till);
Ok(product.clone())
}
#[rhai_fn(name = "add_component", return_raw, global, pure)]
pub fn product_add_component(product: &mut RhaiProduct, component: RhaiProductComponent) -> Result<RhaiProduct, Box<EvalAltResult>> {
pub fn product_add_component(
product: &mut RhaiProduct,
component: RhaiProductComponent,
) -> Result<RhaiProduct, Box<EvalAltResult>> {
let owned_product = mem::take(product);
*product = owned_product.add_component(component);
Ok(product.clone())
}
#[rhai_fn(name = "components", return_raw, global, pure)]
pub fn product_components(product: &mut RhaiProduct, components: Vec<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);
*product = owned_product.components(components);
Ok(product.clone())
@ -384,7 +455,12 @@ mod rhai_biz_module {
#[rhai_fn(name = "get_product_comments")]
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 ---
@ -395,28 +471,39 @@ mod rhai_biz_module {
// SaleItem builder methods
#[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);
*item = owned_item.name(name);
Ok(item.clone())
}
#[rhai_fn(name = "price", return_raw, global, pure)]
pub fn sale_item_price(item: &mut RhaiSaleItem, price: f64) -> Result<RhaiSaleItem, Box<EvalAltResult>> {
let owned_item = mem::take(item);
pub fn sale_item_price(
item: &mut RhaiSaleItem,
price: f64,
) -> Result<RhaiSaleItem, Box<EvalAltResult>> {
item.unit_price = price;
Ok(item.clone())
}
#[rhai_fn(name = "quantity", return_raw, global, pure)]
pub fn sale_item_quantity(item: &mut RhaiSaleItem, quantity: i64) -> Result<RhaiSaleItem, Box<EvalAltResult>> {
pub fn sale_item_quantity(
item: &mut RhaiSaleItem,
quantity: i64,
) -> Result<RhaiSaleItem, Box<EvalAltResult>> {
let owned_item = mem::take(item);
*item = owned_item.quantity(quantity.try_into().unwrap());
Ok(item.clone())
}
#[rhai_fn(name = "product_id", return_raw, global, pure)]
pub fn sale_item_product_id(item: &mut RhaiSaleItem, product_id: i64) -> Result<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 owned_item = mem::take(item);
*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)]
pub fn sale_transaction_id(sale: &mut RhaiSale, transaction_id: u32) -> Result<RhaiSale, Box<EvalAltResult>> {
let owned_sale = mem::take(sale);
pub fn sale_transaction_id(
sale: &mut RhaiSale,
transaction_id: u32,
) -> Result<RhaiSale, Box<EvalAltResult>> {
sale.transaction_id = transaction_id;
Ok(sale.clone())
}
#[rhai_fn(name = "status", return_raw, global, pure)]
pub fn sale_status(sale: &mut RhaiSale, status: SaleStatus) -> Result<RhaiSale, Box<EvalAltResult>> {
pub fn sale_status(
sale: &mut RhaiSale,
status: SaleStatus,
) -> Result<RhaiSale, Box<EvalAltResult>> {
let owned_sale = mem::take(sale);
*sale = owned_sale.status(status);
Ok(sale.clone())
}
#[rhai_fn(name = "add_item", return_raw, global, pure)]
pub fn sale_add_item(sale: &mut RhaiSale, item: RhaiSaleItem) -> Result<RhaiSale, Box<EvalAltResult>> {
pub fn sale_add_item(
sale: &mut RhaiSale,
item: RhaiSaleItem,
) -> Result<RhaiSale, Box<EvalAltResult>> {
let owned_sale = mem::take(sale);
*sale = owned_sale.add_item(item);
Ok(sale.clone())
}
#[rhai_fn(name = "items", return_raw, global, pure)]
pub fn sale_items(sale: &mut RhaiSale, items: Vec<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);
*sale = owned_sale.items(items);
Ok(sale.clone())
@ -511,7 +609,11 @@ mod rhai_biz_module {
#[rhai_fn(name = "get_sale_comments")]
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
let db_for_set_company = Arc::clone(&db);
db_module.set_native_fn("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)
db_module.set_native_fn(
"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_err(|e| Box::new(EvalAltResult::ErrorRuntime(
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to save company: {:?}", e).into(),
Position::NONE
)))
});
Position::NONE,
))
})
},
);
let db_for_get_company = Arc::clone(&db);
db_module.set_native_fn("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");
db_module.set_native_fn(
"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)?;
company_collection_get.get_by_id(id_u32)
company_collection_get
.get_by_id(id_u32)
.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(),
Position::NONE
)))
});
Position::NONE,
))
})
},
);
// Add database functions for Shareholder
let db_for_set_shareholder = Arc::clone(&db);
db_module.set_native_fn("set_shareholder", move |shareholder: Shareholder| -> Result<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)
db_module.set_native_fn(
"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_err(|e| Box::new(EvalAltResult::ErrorRuntime(
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to save shareholder: {:?}", e).into(),
Position::NONE
)))
});
Position::NONE,
))
})
},
);
let db_for_get_shareholder = Arc::clone(&db);
db_module.set_native_fn("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");
db_module.set_native_fn(
"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)?;
shareholder_collection_get.get_by_id(id_u32)
shareholder_collection_get
.get_by_id(id_u32)
.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(),
Position::NONE
)))
});
Position::NONE,
))
})
},
);
// Add database functions for Product
let db_for_set_product = Arc::clone(&db);
db_module.set_native_fn("set_product", move |product: Product| -> Result<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)
db_module.set_native_fn(
"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_err(|e| Box::new(EvalAltResult::ErrorRuntime(
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to save product: {:?}", e).into(),
Position::NONE
)))
});
Position::NONE,
))
})
},
);
let db_for_get_product = Arc::clone(&db);
db_module.set_native_fn("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");
db_module.set_native_fn(
"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)?;
product_collection_get.get_by_id(id_u32)
product_collection_get
.get_by_id(id_u32)
.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(),
Position::NONE
)))
});
Position::NONE,
))
})
},
);
// Add database functions for Sale
let db_for_set_sale = Arc::clone(&db);
db_module.set_native_fn("set_sale", move |sale: Sale| -> Result<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)
db_module.set_native_fn(
"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_err(|e| Box::new(EvalAltResult::ErrorRuntime(
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to save sale: {:?}", e).into(),
Position::NONE
)))
});
Position::NONE,
))
})
},
);
let db_for_get_sale = Arc::clone(&db);
db_module.set_native_fn("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");
db_module.set_native_fn(
"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)?;
sale_collection_get.get_by_id(id_u32)
sale_collection_get
.get_by_id(id_u32)
.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(),
Position::NONE
)))
});
Position::NONE,
))
})
},
);
// Register the database module globally
engine.register_global_module(db_module.into());

View File

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

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ShareholderType {

View File

@ -134,7 +134,11 @@ impl Event {
/// Adds an attendee to the event
pub fn add_attendee(mut self, attendee: Attendee) -> Self {
// Prevent duplicate attendees by contact_id
if !self.attendees.iter().any(|a| a.contact_id == attendee.contact_id) {
if !self
.attendees
.iter()
.any(|a| a.contact_id == attendee.contact_id)
{
self.attendees.push(attendee);
}
self
@ -148,18 +152,18 @@ impl Event {
/// Updates the status of an existing attendee
pub fn update_attendee_status(mut self, contact_id: u32, status: AttendanceStatus) -> Self {
if let Some(attendee) = self.attendees.iter_mut().find(|a| a.contact_id == contact_id) {
if let Some(attendee) = self
.attendees
.iter_mut()
.find(|a| a.contact_id == contact_id)
{
attendee.status = status;
}
self
}
/// Reschedules the event to new start and end times
pub fn reschedule(
mut self,
new_start_time: i64,
new_end_time: i64,
) -> Self {
pub fn reschedule(mut self, new_start_time: i64, new_end_time: i64) -> Self {
// Basic validation: end_time should be after start_time
if new_end_time > new_start_time {
self.start_time = new_start_time;
@ -236,7 +240,8 @@ impl Calendar {
/// Removes an event from the calendar by its ID
pub fn remove_event(mut self, event_id_to_remove: i64) -> Self {
self.events.retain(|&event_id_in_vec| event_id_in_vec != event_id_to_remove);
self.events
.retain(|&event_id_in_vec| event_id_in_vec != event_id_to_remove);
self
}
}

View File

@ -3,5 +3,5 @@ pub mod calendar;
pub mod rhai;
// Re-export Calendar, Event, Attendee, and AttendanceStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs
pub use self::calendar::{Calendar, Event, Attendee, AttendanceStatus};
pub use self::calendar::{AttendanceStatus, Attendee, Calendar, Event};
pub use rhai::register_calendar_rhai_module;

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 rhai::plugin::*;
use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position};
use std::mem;
use std::sync::Arc;
use super::calendar::{Event, Attendee, Calendar, AttendanceStatus};
use super::calendar::{AttendanceStatus, Attendee, Calendar, Event};
type RhaiEvent = Event;
type RhaiAttendee = Attendee;
type RhaiCalendar = Calendar;
use crate::db::hero::OurDB;
use crate::db::Collection;
use crate::db::hero::OurDB;
// Helper to convert i64 from Rhai to u32 for IDs
fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_|
u32::try_from(id_i64).map_err(|_| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert ID '{}' to u32", id_i64).into(),
Position::NONE
Position::NONE,
))
)
})
}
#[export_module]
@ -31,7 +31,10 @@ mod rhai_calendar_module {
/// Sets the event title
#[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);
*event = owned_event.title(title);
Ok(event.clone())
@ -39,7 +42,10 @@ mod rhai_calendar_module {
/// Sets the event description
#[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);
*event = owned_event.description(description);
Ok(event.clone())
@ -47,7 +53,10 @@ mod rhai_calendar_module {
/// Sets the event location
#[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);
*event = owned_event.location(location);
Ok(event.clone())
@ -55,7 +64,10 @@ mod rhai_calendar_module {
/// Adds an attendee to the event
#[rhai_fn(name = "add_attendee", return_raw, global, pure)]
pub fn event_add_attendee(event: &mut RhaiEvent, attendee: RhaiAttendee) -> Result<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
let owned_event = mem::take(event);
*event = owned_event.add_attendee(attendee);
@ -64,12 +76,16 @@ mod rhai_calendar_module {
/// Reschedules the event with new start and end times
#[rhai_fn(name = "reschedule", return_raw, global, pure)]
pub fn event_reschedule(event: &mut RhaiEvent, new_start_time: i64, new_end_time: i64) -> Result<RhaiEvent, Box<EvalAltResult>> {
pub fn event_reschedule(
event: &mut RhaiEvent,
new_start_time: i64,
new_end_time: i64,
) -> Result<RhaiEvent, Box<EvalAltResult>> {
// Validate timestamps
if new_end_time <= new_start_time {
return Err(Box::new(EvalAltResult::ErrorRuntime(
"End time must be after start time".into(),
Position::NONE
Position::NONE,
)));
}
@ -81,7 +97,11 @@ mod rhai_calendar_module {
/// Updates an attendee's status in the event
#[rhai_fn(name = "update_attendee_status", return_raw, global, pure)]
pub fn event_update_attendee_status(event: &mut RhaiEvent, contact_id: i64, status_str: String) -> Result<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)
.map_err(|_| Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid attendance status: '{}'. Expected one of: Pending, Accepted, Declined, Tentative", status_str).into(),
@ -96,24 +116,42 @@ mod rhai_calendar_module {
// Event Getters
#[rhai_fn(get = "id", pure)]
pub fn get_event_id(event: &mut RhaiEvent) -> i64 { event.base_data.id as i64 }
pub fn get_event_id(event: &mut RhaiEvent) -> i64 {
event.base_data.id as i64
}
#[rhai_fn(get = "created_at", pure)]
pub fn get_event_created_at(event: &mut RhaiEvent) -> i64 { event.base_data.created_at }
pub fn get_event_created_at(event: &mut RhaiEvent) -> i64 {
event.base_data.created_at
}
#[rhai_fn(get = "modified_at", pure)]
pub fn get_event_modified_at(event: &mut RhaiEvent) -> i64 { event.base_data.modified_at }
pub fn get_event_modified_at(event: &mut RhaiEvent) -> i64 {
event.base_data.modified_at
}
#[rhai_fn(get = "title", pure)]
pub fn get_event_title(event: &mut RhaiEvent) -> String { event.title.clone() }
pub fn get_event_title(event: &mut RhaiEvent) -> String {
event.title.clone()
}
#[rhai_fn(get = "description", pure)]
pub fn get_event_description(event: &mut RhaiEvent) -> Option<String> { event.description.clone() }
pub fn get_event_description(event: &mut RhaiEvent) -> Option<String> {
event.description.clone()
}
#[rhai_fn(get = "start_time", pure)]
pub fn get_event_start_time(event: &mut RhaiEvent) -> i64 { event.start_time }
pub fn get_event_start_time(event: &mut RhaiEvent) -> i64 {
event.start_time
}
#[rhai_fn(get = "end_time", pure)]
pub fn get_event_end_time(event: &mut RhaiEvent) -> i64 { event.end_time }
pub fn get_event_end_time(event: &mut RhaiEvent) -> i64 {
event.end_time
}
#[rhai_fn(get = "location", pure)]
pub fn get_event_location(event: &mut RhaiEvent) -> Option<String> { event.location.clone() }
pub fn get_event_location(event: &mut RhaiEvent) -> Option<String> {
event.location.clone()
}
#[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 ---
#[rhai_fn(name = "new_attendee")]
@ -123,7 +161,10 @@ mod rhai_calendar_module {
/// Sets the contact ID for an attendee
#[rhai_fn(name = "with_contact_id", return_raw, global, pure)]
pub fn attendee_with_contact_id(attendee: &mut RhaiAttendee, contact_id: i64) -> Result<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 owned_attendee = mem::replace(attendee, Attendee::new(0));
*attendee = Attendee::new(new_contact_id);
@ -133,7 +174,10 @@ mod rhai_calendar_module {
/// Sets the status for an attendee
#[rhai_fn(name = "with_status", return_raw, global, pure)]
pub fn attendee_with_status(attendee: &mut RhaiAttendee, status_str: String) -> Result<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)
.map_err(|_| Box::new(EvalAltResult::ErrorRuntime(
format!("Invalid attendance status: '{}'. Expected one of: Accepted, Declined, Tentative, NoResponse", status_str).into(),
@ -149,9 +193,13 @@ mod rhai_calendar_module {
// Attendee Getters
#[rhai_fn(get = "contact_id", pure)]
pub fn get_attendee_contact_id(attendee: &mut RhaiAttendee) -> i64 { attendee.contact_id as i64 }
pub fn get_attendee_contact_id(attendee: &mut RhaiAttendee) -> i64 {
attendee.contact_id as i64
}
#[rhai_fn(get = "status", pure)]
pub fn get_attendee_status(attendee: &mut RhaiAttendee) -> String { attendee.status.to_string() }
pub fn get_attendee_status(attendee: &mut RhaiAttendee) -> String {
attendee.status.to_string()
}
// --- Calendar Functions ---
#[rhai_fn(name = "new_calendar", return_raw)]
@ -162,7 +210,10 @@ mod rhai_calendar_module {
/// Sets the calendar name
#[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
let default_calendar = Calendar::new(None, "");
@ -174,7 +225,10 @@ mod rhai_calendar_module {
/// Sets the calendar description
#[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
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)]
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
let default_calendar = Calendar::new(None, "");
@ -197,7 +254,10 @@ mod rhai_calendar_module {
}
#[rhai_fn(name = "remove_event_from_calendar", return_raw)]
pub fn calendar_remove_event(calendar: &mut RhaiCalendar, event_id: i64) -> Result<(), Box<EvalAltResult>> {
pub fn calendar_remove_event(
calendar: &mut RhaiCalendar,
event_id: i64,
) -> Result<(), Box<EvalAltResult>> {
// Create a default Calendar to replace the taken one
let default_calendar = Calendar::new(None, "");
@ -209,26 +269,37 @@ mod rhai_calendar_module {
// Calendar Getters
#[rhai_fn(get = "id", pure)]
pub fn get_calendar_id(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.id as i64 }
pub fn get_calendar_id(calendar: &mut RhaiCalendar) -> i64 {
calendar.base_data.id as i64
}
#[rhai_fn(get = "name", pure)]
pub fn get_calendar_name(calendar: &mut RhaiCalendar) -> String { calendar.name.clone() }
pub fn get_calendar_name(calendar: &mut RhaiCalendar) -> String {
calendar.name.clone()
}
#[rhai_fn(get = "created_at", pure)]
pub fn get_calendar_created_at(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.created_at }
pub fn get_calendar_created_at(calendar: &mut RhaiCalendar) -> i64 {
calendar.base_data.created_at
}
#[rhai_fn(get = "modified_at", pure)]
pub fn get_calendar_modified_at(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.modified_at }
pub fn get_calendar_modified_at(calendar: &mut RhaiCalendar) -> i64 {
calendar.base_data.modified_at
}
#[rhai_fn(get = "events", pure)]
pub fn get_calendar_events(calendar: &mut RhaiCalendar) -> Vec<i64> { calendar.events.clone() }
pub fn get_calendar_events(calendar: &mut RhaiCalendar) -> Vec<i64> {
calendar.events.clone()
}
#[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
// 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>) {
@ -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'
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
let result = db_clone_set_event.set(&event)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_event: {}", e).into(), Position::NONE)))?;
let result = db_clone_set_event.set(&event).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error set_event: {}", e).into(),
Position::NONE,
))
})?;
// Return the updated event with the correct ID
Ok(result.1)
});
},
);
// Manually register database functions as they need to capture 'db'
let db_clone_delete_event = db.clone();
db_module.set_native_fn("delete_event", move |event: Event| -> Result<(), Box<EvalAltResult>> {
db_module.set_native_fn(
"delete_event",
move |event: Event| -> Result<(), Box<EvalAltResult>> {
// 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")
.delete_by_id(event.base_data.id)
.expect("can delete event");
// Return the updated event with the correct ID
Ok(result)
});
},
);
let db_clone_get_event = db.clone();
db_module.set_native_fn("get_event_by_id", move |id_i64: INT| -> Result<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)?;
// Use the Collection trait method directly
db_clone_get_event.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_event_by_id: {}", e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Event with ID {} not found", id_u32).into(), Position::NONE)))
});
db_clone_get_event
.get_by_id(id_u32)
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error get_event_by_id: {}", e).into(),
Position::NONE,
))
})?
.ok_or_else(|| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Event with ID {} not found", id_u32).into(),
Position::NONE,
))
})
},
);
let db_clone_set_calendar = db.clone();
db_module.set_native_fn("save_calendar", move |calendar: Calendar| -> Result<Calendar, Box<EvalAltResult>> {
db_module.set_native_fn(
"save_calendar",
move |calendar: Calendar| -> Result<Calendar, Box<EvalAltResult>> {
// Use the Collection trait method directly
let result = db_clone_set_calendar.set(&calendar)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_calendar: {}", e).into(), Position::NONE)))?;
let result = db_clone_set_calendar.set(&calendar).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error set_calendar: {}", e).into(),
Position::NONE,
))
})?;
// Return the updated calendar with the correct ID
Ok(result.1)
});
},
);
// Manually register database functions as they need to capture 'db'
let db_clone_delete_calendar = db.clone();
db_module.set_native_fn("delete_calendar", move |calendar: Calendar| -> Result<(), Box<EvalAltResult>> {
db_module.set_native_fn(
"delete_calendar",
move |calendar: Calendar| -> Result<(), Box<EvalAltResult>> {
// 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")
.delete_by_id(calendar.base_data.id)
.expect("can delete event");
// Return the updated event with the correct ID
Ok(result)
});
},
);
let db_clone_get_calendar = db.clone();
db_module.set_native_fn("get_calendar_by_id", move |id_i64: INT| -> Result<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)?;
// Use the Collection trait method directly
db_clone_get_calendar.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_calendar_by_id: {}", e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Calendar with ID {} not found", id_u32).into(), Position::NONE)))
});
db_clone_get_calendar
.get_by_id(id_u32)
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error get_calendar_by_id: {}", e).into(),
Position::NONE,
))
})?
.ok_or_else(|| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Calendar with ID {} not found", id_u32).into(),
Position::NONE,
))
})
},
);
// Add list_calendars function to get all calendars
let db_clone_list_calendars = db.clone();
db_module.set_native_fn("list_calendars", move || -> Result<Dynamic, Box<EvalAltResult>> {
let collection = db_clone_list_calendars.collection::<Calendar>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
db_module.set_native_fn(
"list_calendars",
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(),
Position::NONE
)))?;
let calendars = collection.get_all()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
Position::NONE,
))
})?;
let calendars = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all calendars: {:?}", e).into(),
Position::NONE
)))?;
Position::NONE,
))
})?;
let mut array = Array::new();
for calendar in calendars {
array.push(Dynamic::from(calendar));
}
Ok(Dynamic::from(array))
});
},
);
// Add list_events function to get all events
let db_clone_list_events = db.clone();
db_module.set_native_fn("list_events", move || -> Result<Dynamic, Box<EvalAltResult>> {
let collection = db_clone_list_events.collection::<Event>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
db_module.set_native_fn(
"list_events",
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(),
Position::NONE
)))?;
let events = collection.get_all()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
Position::NONE,
))
})?;
let events = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all events: {:?}", e).into(),
Position::NONE
)))?;
Position::NONE,
))
})?;
let mut array = Array::new();
for event in events {
array.push(Dynamic::from(event));
}
Ok(Dynamic::from(array))
});
},
);
// Register the database module globally
engine.register_global_module(db_module.into());

View File

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

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

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 rhai::plugin::*;
use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position};
use std::mem;
use std::sync::Arc;
use super::contact::{Group, Contact};
use super::contact::{Contact, Group};
type RhaiGroup = Group;
type RhaiContact = Contact;
use crate::db::hero::OurDB;
use crate::db::Collection;
use crate::db::hero::OurDB;
// Helper to convert i64 from Rhai to u32 for IDs
fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_|
u32::try_from(id_i64).map_err(|_| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert ID '{}' to u32", id_i64).into(),
Position::NONE
Position::NONE,
))
)
})
}
#[export_module]
@ -30,7 +30,10 @@ mod rhai_contact_module {
/// Sets the event title
#[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);
*group = owned_group.name(name);
Ok(group.clone())
@ -38,7 +41,10 @@ mod rhai_contact_module {
/// Sets the event description
#[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);
*group = owned_group.description(description);
Ok(group.clone())
@ -46,7 +52,10 @@ mod rhai_contact_module {
/// Adds an attendee to the event
#[rhai_fn(name = "add_contact", return_raw, global, pure)]
pub fn group_add_contact(group: &mut RhaiGroup, contact_id: i64) -> Result<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
let owned_group = mem::take(group);
*group = owned_group.add_contact(contact_id as u32);
@ -54,21 +63,37 @@ mod rhai_contact_module {
}
#[rhai_fn(get = "contacts", pure)]
pub fn get_group_contacts(group: &mut RhaiGroup) -> Vec<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
#[rhai_fn(get = "id", pure)]
pub fn get_group_id(group: &mut RhaiGroup) -> i64 { group.base_data.id as i64 }
pub fn get_group_id(group: &mut RhaiGroup) -> i64 {
group.base_data.id as i64
}
#[rhai_fn(get = "created_at", pure)]
pub fn get_group_created_at(group: &mut RhaiGroup) -> i64 { group.base_data.created_at }
pub fn get_group_created_at(group: &mut RhaiGroup) -> i64 {
group.base_data.created_at
}
#[rhai_fn(get = "modified_at", pure)]
pub fn get_group_modified_at(group: &mut RhaiGroup) -> i64 { group.base_data.modified_at }
pub fn get_group_modified_at(group: &mut RhaiGroup) -> i64 {
group.base_data.modified_at
}
#[rhai_fn(get = "name", pure)]
pub fn get_group_name(group: &mut RhaiGroup) -> String { group.name.clone() }
pub fn get_group_name(group: &mut RhaiGroup) -> String {
group.name.clone()
}
#[rhai_fn(get = "description", pure)]
pub fn get_group_description(group: &mut RhaiGroup) -> Option<String> { group.description.clone() }
pub fn get_group_description(group: &mut RhaiGroup) -> Option<String> {
group.description.clone()
}
// --- Contact Functions ---
#[rhai_fn(name = "new_contact", return_raw)]
@ -79,7 +104,10 @@ mod rhai_contact_module {
/// Sets the contact name
#[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
let default_contact = Contact::new();
@ -91,7 +119,10 @@ mod rhai_contact_module {
/// Sets the contact description
#[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
let default_contact = Contact::new();
@ -103,16 +134,24 @@ mod rhai_contact_module {
// Contact Getters
#[rhai_fn(get = "id", pure)]
pub fn get_contact_id(contact: &mut RhaiContact) -> i64 { contact.base_data.id as i64 }
pub fn get_contact_id(contact: &mut RhaiContact) -> i64 {
contact.base_data.id as i64
}
#[rhai_fn(get = "name", pure)]
pub fn get_contact_name(contact: &mut RhaiContact) -> String { contact.name.clone() }
pub fn get_contact_name(contact: &mut RhaiContact) -> String {
contact.name.clone()
}
#[rhai_fn(get = "created_at", pure)]
pub fn get_contact_created_at(contact: &mut RhaiContact) -> i64 { contact.base_data.created_at }
pub fn get_contact_created_at(contact: &mut RhaiContact) -> i64 {
contact.base_data.created_at
}
#[rhai_fn(get = "modified_at", pure)]
pub fn get_contact_modified_at(contact: &mut RhaiContact) -> i64 { contact.base_data.modified_at }
pub fn get_contact_modified_at(contact: &mut RhaiContact) -> i64 {
contact.base_data.modified_at
}
}
pub fn register_contact_rhai_module(engine: &mut Engine, db: Arc<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'
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
let result = db_clone_set_group.set(&group)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_group: {}", e).into(), Position::NONE)))?;
let result = db_clone_set_group.set(&group).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error set_group: {}", e).into(),
Position::NONE,
))
})?;
// Return the updated event with the correct ID
Ok(result.1)
});
},
);
// Manually register database functions as they need to capture 'db'
let db_clone_delete_group = db.clone();
db_module.set_native_fn("delete_group", move |group: Group| -> Result<(), Box<EvalAltResult>> {
db_module.set_native_fn(
"delete_group",
move |group: Group| -> Result<(), Box<EvalAltResult>> {
// 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")
.delete_by_id(group.base_data.id)
.expect("can delete group");
// Return the updated event with the correct ID
Ok(result)
});
},
);
let db_clone_get_group = db.clone();
db_module.set_native_fn("get_group_by_id", move |id_i64: INT| -> Result<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)?;
// Use the Collection trait method directly
db_clone_get_group.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_event_by_id: {}", e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Event with ID {} not found", id_u32).into(), Position::NONE)))
});
db_clone_get_group
.get_by_id(id_u32)
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error get_event_by_id: {}", e).into(),
Position::NONE,
))
})?
.ok_or_else(|| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Event with ID {} not found", id_u32).into(),
Position::NONE,
))
})
},
);
let db_clone_set_contact = db.clone();
db_module.set_native_fn("save_contact", move |contact: Contact| -> Result<Contact, Box<EvalAltResult>> {
db_module.set_native_fn(
"save_contact",
move |contact: Contact| -> Result<Contact, Box<EvalAltResult>> {
// Use the Collection trait method directly
let result = db_clone_set_contact.set(&contact)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_contact: {}", e).into(), Position::NONE)))?;
let result = db_clone_set_contact.set(&contact).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error set_contact: {}", e).into(),
Position::NONE,
))
})?;
// Return the updated contact with the correct ID
Ok(result.1)
});
},
);
// Manually register database functions as they need to capture 'db'
let db_clone_delete_contact = db.clone();
db_module.set_native_fn("delete_contact", move |contact: Contact| -> Result<(), Box<EvalAltResult>> {
db_module.set_native_fn(
"delete_contact",
move |contact: Contact| -> Result<(), Box<EvalAltResult>> {
// 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")
.delete_by_id(contact.base_data.id)
.expect("can delete event");
// Return the updated event with the correct ID
Ok(result)
});
},
);
let db_clone_get_contact = db.clone();
db_module.set_native_fn("get_contact_by_id", move |id_i64: INT| -> Result<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)?;
// Use the Collection trait method directly
db_clone_get_contact.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_contact_by_id: {}", e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Contact with ID {} not found", id_u32).into(), Position::NONE)))
});
db_clone_get_contact
.get_by_id(id_u32)
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error get_contact_by_id: {}", e).into(),
Position::NONE,
))
})?
.ok_or_else(|| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Contact with ID {} not found", id_u32).into(),
Position::NONE,
))
})
},
);
// Add list_contacts function to get all contacts
let db_clone_list_contacts = db.clone();
db_module.set_native_fn("list_contacts", move || -> Result<Dynamic, Box<EvalAltResult>> {
let collection = db_clone_list_contacts.collection::<Contact>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
db_module.set_native_fn(
"list_contacts",
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(),
Position::NONE
)))?;
let contacts = collection.get_all()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
Position::NONE,
))
})?;
let contacts = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all contacts: {:?}", e).into(),
Position::NONE
)))?;
Position::NONE,
))
})?;
let mut array = Array::new();
for contact in contacts {
array.push(Dynamic::from(contact));
}
Ok(Dynamic::from(array))
});
},
);
// Add list_events function to get all events
let db_clone_list_groups = db.clone();
db_module.set_native_fn("list_groups", move || -> Result<Dynamic, Box<EvalAltResult>> {
let collection = db_clone_list_groups.collection::<Group>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
db_module.set_native_fn(
"list_groups",
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(),
Position::NONE
)))?;
let groups = collection.get_all()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
Position::NONE,
))
})?;
let groups = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all groups: {:?}", e).into(),
Position::NONE
)))?;
Position::NONE,
))
})?;
let mut array = Array::new();
for group in groups {
array.push(Dynamic::from(group));
}
Ok(Dynamic::from(array))
});
},
);
// Register the database module globally
engine.register_global_module(db_module.into());

View File

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

View File

@ -0,0 +1 @@

View File

@ -1,9 +1,9 @@
// heromodels/src/models/finance/account.rs
use serde::{Deserialize, Serialize};
use rhai::{CustomType, TypeBuilder};
use heromodels_derive::model;
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
use super::asset::Asset;
@ -87,6 +87,6 @@ impl Account {
/// Find an asset by name
pub fn find_asset_by_name(&self, _name: &str) -> Option<&Asset> {
// TODO: implement
return None
return None;
}
}

View File

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

View File

@ -1,10 +1,10 @@
// heromodels/src/models/finance/marketplace.rs
use serde::{Deserialize, Serialize};
use rhai::{CustomType, TypeBuilder};
use chrono::{DateTime, Utc};
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
use super::asset::AssetType;
@ -53,8 +53,7 @@ impl Default for BidStatus {
}
/// Bid represents a bid on an auction listing
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
#[derive(Default)]
#[derive(Debug, Clone, Serialize, Deserialize, CustomType, Default)]
pub struct Bid {
pub listing_id: String, // ID of the listing this bid belongs to
pub bidder_id: u32, // ID of the user who placed the bid

View File

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

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

View File

@ -1,7 +1,7 @@
use super::flow_step::FlowStep;
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use serde::{Deserialize, Serialize};
use super::flow_step::FlowStep;
/// Represents a signing flow.
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)]

View File

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

View File

@ -1,7 +1,7 @@
use rhai::plugin::*;
use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array};
use std::sync::Arc;
use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position};
use std::mem;
use std::sync::Arc;
use super::flow::Flow;
use super::flow_step::FlowStep;
@ -9,18 +9,18 @@ use super::signature_requirement::SignatureRequirement;
type RhaiFlow = Flow;
type RhaiFlowStep = FlowStep;
type RhaiSignatureRequirement = SignatureRequirement;
use crate::db::hero::OurDB;
use crate::db::Collection;
use crate::db::Db;
use crate::db::hero::OurDB;
// Helper to convert i64 from Rhai to u32 for IDs
fn id_from_i64_to_u32(id_i64: i64) -> Result<u32, Box<EvalAltResult>> {
u32::try_from(id_i64).map_err(|_|
u32::try_from(id_i64).map_err(|_| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Failed to convert ID '{}' to u32", id_i64).into(),
Position::NONE
Position::NONE,
))
)
})
}
#[export_module]
@ -41,7 +41,10 @@ mod rhai_flow_module {
/// Sets the flow status
#[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
*flow = owned_flow.status(status);
Ok(flow.clone())
@ -49,7 +52,10 @@ mod rhai_flow_module {
/// Adds a step to the flow
#[rhai_fn(name = "add_step", return_raw, global, pure)]
pub fn flow_add_step(flow: &mut RhaiFlow, step: RhaiFlowStep) -> Result<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
*flow = owned_flow.add_step(step);
Ok(flow.clone())
@ -57,20 +63,36 @@ mod rhai_flow_module {
// Flow Getters
#[rhai_fn(get = "id", pure)]
pub fn get_id(flow: &mut RhaiFlow) -> i64 { flow.base_data.id as i64 }
pub fn get_id(flow: &mut RhaiFlow) -> i64 {
flow.base_data.id as i64
}
#[rhai_fn(get = "created_at", pure)]
pub fn get_created_at(flow: &mut RhaiFlow) -> i64 { flow.base_data.created_at }
pub fn get_created_at(flow: &mut RhaiFlow) -> i64 {
flow.base_data.created_at
}
#[rhai_fn(get = "modified_at", pure)]
pub fn get_modified_at(flow: &mut RhaiFlow) -> i64 { flow.base_data.modified_at }
pub fn get_modified_at(flow: &mut RhaiFlow) -> i64 {
flow.base_data.modified_at
}
#[rhai_fn(get = "flow_uuid", pure)]
pub fn get_flow_uuid(flow: &mut RhaiFlow) -> String { flow.flow_uuid.clone() }
pub fn get_flow_uuid(flow: &mut RhaiFlow) -> String {
flow.flow_uuid.clone()
}
#[rhai_fn(get = "name", pure)]
pub fn get_name(flow: &mut RhaiFlow) -> String { flow.name.clone() }
pub fn get_name(flow: &mut RhaiFlow) -> String {
flow.name.clone()
}
#[rhai_fn(get = "status", pure)]
pub fn get_status(flow: &mut RhaiFlow) -> String { flow.status.clone() }
pub fn get_status(flow: &mut RhaiFlow) -> String {
flow.status.clone()
}
#[rhai_fn(get = "steps", pure)]
pub fn get_steps(flow: &mut RhaiFlow) -> Array {
flow.steps.iter().cloned().map(Dynamic::from).collect::<Array>()
flow.steps
.iter()
.cloned()
.map(Dynamic::from)
.collect::<Array>()
}
// --- FlowStep Functions ---
@ -81,14 +103,17 @@ mod rhai_flow_module {
let mut flow_step = FlowStep::default();
flow_step.step_order = step_order;
Dynamic::from(flow_step)
},
Err(err) => Dynamic::from(err.to_string())
}
Err(err) => Dynamic::from(err.to_string()),
}
}
/// Sets the flow step description
#[rhai_fn(name = "description", return_raw, global, pure)]
pub fn flow_step_description(step: &mut RhaiFlowStep, description: String) -> Result<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
*step = owned_step.description(description);
Ok(step.clone())
@ -96,7 +121,10 @@ mod rhai_flow_module {
/// Sets the flow step status
#[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
*step = owned_step.status(status);
Ok(step.clone())
@ -104,11 +132,17 @@ mod rhai_flow_module {
// FlowStep Getters
#[rhai_fn(get = "id", pure)]
pub fn get_step_id(step: &mut RhaiFlowStep) -> i64 { step.base_data.id as i64 }
pub fn get_step_id(step: &mut RhaiFlowStep) -> i64 {
step.base_data.id as i64
}
#[rhai_fn(get = "created_at", pure)]
pub fn get_step_created_at(step: &mut RhaiFlowStep) -> i64 { step.base_data.created_at }
pub fn get_step_created_at(step: &mut RhaiFlowStep) -> i64 {
step.base_data.created_at
}
#[rhai_fn(get = "modified_at", pure)]
pub fn get_step_modified_at(step: &mut RhaiFlowStep) -> i64 { step.base_data.modified_at }
pub fn get_step_modified_at(step: &mut RhaiFlowStep) -> i64 {
step.base_data.modified_at
}
#[rhai_fn(get = "description", pure)]
pub fn get_step_description(step: &mut RhaiFlowStep) -> Dynamic {
match &step.description {
@ -117,14 +151,22 @@ mod rhai_flow_module {
}
}
#[rhai_fn(get = "step_order", pure)]
pub fn get_step_order(step: &mut RhaiFlowStep) -> i64 { step.step_order as i64 }
pub fn get_step_order(step: &mut RhaiFlowStep) -> i64 {
step.step_order as i64
}
#[rhai_fn(get = "status", pure)]
pub fn get_step_status(step: &mut RhaiFlowStep) -> String { step.status.clone() }
pub fn get_step_status(step: &mut RhaiFlowStep) -> String {
step.status.clone()
}
// --- SignatureRequirement Functions ---
/// Create a new signature requirement
#[rhai_fn(global)]
pub fn new_signature_requirement(flow_step_id_i64: i64, public_key: String, message: String) -> Dynamic {
pub fn new_signature_requirement(
flow_step_id_i64: i64,
public_key: String,
message: String,
) -> Dynamic {
match id_from_i64_to_u32(flow_step_id_i64) {
Ok(flow_step_id) => {
let mut signature_requirement = SignatureRequirement::default();
@ -132,14 +174,17 @@ mod rhai_flow_module {
signature_requirement.public_key = public_key;
signature_requirement.message = message;
Dynamic::from(signature_requirement)
},
Err(err) => Dynamic::from(err.to_string())
}
Err(err) => Dynamic::from(err.to_string()),
}
}
/// Sets the signed_by field
#[rhai_fn(name = "signed_by", return_raw, global, pure)]
pub fn signature_requirement_signed_by(sr: &mut RhaiSignatureRequirement, signed_by: String) -> Result<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
*sr = owned_sr.signed_by(signed_by);
Ok(sr.clone())
@ -147,7 +192,10 @@ mod rhai_flow_module {
/// Sets the signature field
#[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
*sr = owned_sr.signature(signature);
Ok(sr.clone())
@ -155,7 +203,10 @@ mod rhai_flow_module {
/// Sets the status field
#[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
*sr = owned_sr.status(status);
Ok(sr.clone())
@ -163,17 +214,29 @@ mod rhai_flow_module {
// SignatureRequirement Getters
#[rhai_fn(get = "id", pure)]
pub fn get_sr_id(sr: &mut RhaiSignatureRequirement) -> i64 { sr.base_data.id as i64 }
pub fn get_sr_id(sr: &mut RhaiSignatureRequirement) -> i64 {
sr.base_data.id as i64
}
#[rhai_fn(get = "created_at", pure)]
pub fn get_sr_created_at(sr: &mut RhaiSignatureRequirement) -> i64 { sr.base_data.created_at }
pub fn get_sr_created_at(sr: &mut RhaiSignatureRequirement) -> i64 {
sr.base_data.created_at
}
#[rhai_fn(get = "modified_at", pure)]
pub fn get_sr_modified_at(sr: &mut RhaiSignatureRequirement) -> i64 { sr.base_data.modified_at }
pub fn get_sr_modified_at(sr: &mut RhaiSignatureRequirement) -> i64 {
sr.base_data.modified_at
}
#[rhai_fn(get = "flow_step_id", pure)]
pub fn get_sr_flow_step_id(sr: &mut RhaiSignatureRequirement) -> i64 { sr.flow_step_id as i64 }
pub fn get_sr_flow_step_id(sr: &mut RhaiSignatureRequirement) -> i64 {
sr.flow_step_id as i64
}
#[rhai_fn(get = "public_key", pure)]
pub fn get_sr_public_key(sr: &mut RhaiSignatureRequirement) -> String { sr.public_key.clone() }
pub fn get_sr_public_key(sr: &mut RhaiSignatureRequirement) -> String {
sr.public_key.clone()
}
#[rhai_fn(get = "message", pure)]
pub fn get_sr_message(sr: &mut RhaiSignatureRequirement) -> String { sr.message.clone() }
pub fn get_sr_message(sr: &mut RhaiSignatureRequirement) -> String {
sr.message.clone()
}
#[rhai_fn(get = "signed_by", pure)]
pub fn get_sr_signed_by(sr: &mut RhaiSignatureRequirement) -> Dynamic {
match &sr.signed_by {
@ -189,7 +252,9 @@ mod rhai_flow_module {
}
}
#[rhai_fn(get = "status", pure)]
pub fn get_sr_status(sr: &mut RhaiSignatureRequirement) -> String { sr.status.clone() }
pub fn get_sr_status(sr: &mut RhaiSignatureRequirement) -> String {
sr.status.clone()
}
}
/// Register the flow module with the Rhai engine
@ -199,168 +264,265 @@ pub fn register_flow_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
// Flow database functions
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
let result = db_clone.set(&flow)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_flow: {:?}", e).into(), Position::NONE)))?;
let result = db_clone.set(&flow).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error save_flow: {:?}", e).into(),
Position::NONE,
))
})?;
// Return the updated flow with the correct ID
Ok(result.1)
});
},
);
let db_clone = Arc::clone(&db);
db_module.set_native_fn("get_flow_by_id", move |id_i64: INT| -> Result<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)?;
// Use the Collection trait method directly
db_clone.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_flow_by_id: {:?}", e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Flow with ID {} not found", id_u32).into(), Position::NONE)))
});
db_clone
.get_by_id(id_u32)
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error get_flow_by_id: {:?}", e).into(),
Position::NONE,
))
})?
.ok_or_else(|| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Flow with ID {} not found", id_u32).into(),
Position::NONE,
))
})
},
);
let db_clone = Arc::clone(&db);
db_module.set_native_fn("delete_flow", move |id_i64: INT| -> Result<(), Box<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)?;
// Use the Collection trait method directly
let collection = db_clone.collection::<Flow>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
let collection = db_clone.collection::<Flow>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get flow collection: {:?}", e).into(),
Position::NONE
)))?;
collection.delete_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
Position::NONE,
))
})?;
collection.delete_by_id(id_u32).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to delete Flow (ID: {}): {:?}", id_u32, e).into(),
Position::NONE
)))
});
Position::NONE,
))
})
},
);
let db_clone = Arc::clone(&db);
db_module.set_native_fn("list_flows", move || -> Result<Dynamic, Box<EvalAltResult>> {
let collection = db_clone.collection::<Flow>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
db_module.set_native_fn(
"list_flows",
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(),
Position::NONE
)))?;
let flows = collection.get_all()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
Position::NONE,
))
})?;
let flows = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all flows: {:?}", e).into(),
Position::NONE
)))?;
Position::NONE,
))
})?;
let mut array = Array::new();
for flow in flows {
array.push(Dynamic::from(flow));
}
Ok(Dynamic::from(array))
});
},
);
// FlowStep database functions
let db_clone = Arc::clone(&db);
db_module.set_native_fn("save_flow_step", move |step: FlowStep| -> Result<FlowStep, Box<EvalAltResult>> {
db_module.set_native_fn(
"save_flow_step",
move |step: FlowStep| -> Result<FlowStep, Box<EvalAltResult>> {
// Use the Collection trait method directly
let result = db_clone.set(&step)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_flow_step: {:?}", e).into(), Position::NONE)))?;
let result = db_clone.set(&step).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error save_flow_step: {:?}", e).into(),
Position::NONE,
))
})?;
// Return the updated flow step with the correct ID
Ok(result.1)
});
},
);
let db_clone = Arc::clone(&db);
db_module.set_native_fn("get_flow_step_by_id", move |id_i64: INT| -> Result<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)?;
// Use the Collection trait method directly
db_clone.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_flow_step_by_id: {:?}", e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("FlowStep with ID {} not found", id_u32).into(), Position::NONE)))
});
db_clone
.get_by_id(id_u32)
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error get_flow_step_by_id: {:?}", e).into(),
Position::NONE,
))
})?
.ok_or_else(|| {
Box::new(EvalAltResult::ErrorRuntime(
format!("FlowStep with ID {} not found", id_u32).into(),
Position::NONE,
))
})
},
);
let db_clone = Arc::clone(&db);
db_module.set_native_fn("delete_flow_step", move |id_i64: INT| -> Result<(), Box<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)?;
// Use the Collection trait method directly
let collection = db_clone.collection::<FlowStep>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
let collection = db_clone.collection::<FlowStep>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get flow step collection: {:?}", e).into(),
Position::NONE
)))?;
collection.delete_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
Position::NONE,
))
})?;
collection.delete_by_id(id_u32).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to delete FlowStep (ID: {}): {:?}", id_u32, e).into(),
Position::NONE
)))
});
Position::NONE,
))
})
},
);
let db_clone = Arc::clone(&db);
db_module.set_native_fn("list_flow_steps", move || -> Result<Dynamic, Box<EvalAltResult>> {
let collection = db_clone.collection::<FlowStep>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
db_module.set_native_fn(
"list_flow_steps",
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(),
Position::NONE
)))?;
let steps = collection.get_all()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
Position::NONE,
))
})?;
let steps = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all flow steps: {:?}", e).into(),
Position::NONE
)))?;
Position::NONE,
))
})?;
let mut array = Array::new();
for step in steps {
array.push(Dynamic::from(step));
}
Ok(Dynamic::from(array))
});
},
);
// SignatureRequirement database functions
let db_clone = Arc::clone(&db);
db_module.set_native_fn("save_signature_requirement", move |sr: SignatureRequirement| -> Result<SignatureRequirement, Box<EvalAltResult>> {
db_module.set_native_fn(
"save_signature_requirement",
move |sr: SignatureRequirement| -> Result<SignatureRequirement, Box<EvalAltResult>> {
// Use the Collection trait method directly
let result = db_clone.set(&sr)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_signature_requirement: {:?}", e).into(), Position::NONE)))?;
let result = db_clone.set(&sr).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error save_signature_requirement: {:?}", e).into(),
Position::NONE,
))
})?;
// Return the updated signature requirement with the correct ID
Ok(result.1)
});
},
);
let db_clone = Arc::clone(&db);
db_module.set_native_fn("get_signature_requirement_by_id", move |id_i64: INT| -> Result<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)?;
// Use the Collection trait method directly
db_clone.get_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_signature_requirement_by_id: {:?}", e).into(), Position::NONE)))?
.ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("SignatureRequirement with ID {} not found", id_u32).into(), Position::NONE)))
});
db_clone
.get_by_id(id_u32)
.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("DB Error get_signature_requirement_by_id: {:?}", e).into(),
Position::NONE,
))
})?
.ok_or_else(|| {
Box::new(EvalAltResult::ErrorRuntime(
format!("SignatureRequirement with ID {} not found", id_u32).into(),
Position::NONE,
))
})
},
);
let db_clone = Arc::clone(&db);
db_module.set_native_fn("delete_signature_requirement", move |id_i64: INT| -> Result<(), Box<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)?;
// Use the Collection trait method directly
let collection = db_clone.collection::<SignatureRequirement>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
let collection = db_clone.collection::<SignatureRequirement>().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get signature requirement collection: {:?}", e).into(),
Position::NONE
)))?;
collection.delete_by_id(id_u32)
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to delete SignatureRequirement (ID: {}): {:?}", id_u32, e).into(),
Position::NONE
)))
});
Position::NONE,
))
})?;
collection.delete_by_id(id_u32).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!(
"Failed to delete SignatureRequirement (ID: {}): {:?}",
id_u32, e
)
.into(),
Position::NONE,
))
})
},
);
let db_clone = Arc::clone(&db);
db_module.set_native_fn("list_signature_requirements", move || -> Result<Dynamic, Box<EvalAltResult>> {
let collection = db_clone.collection::<SignatureRequirement>()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
db_module.set_native_fn(
"list_signature_requirements",
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(),
Position::NONE
)))?;
let srs = collection.get_all()
.map_err(|e| Box::new(EvalAltResult::ErrorRuntime(
Position::NONE,
))
})?;
let srs = collection.get_all().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get all signature requirements: {:?}", e).into(),
Position::NONE
)))?;
Position::NONE,
))
})?;
let mut array = Array::new();
for sr in srs {
array.push(Dynamic::from(sr));
}
Ok(Dynamic::from(array))
});
},
);
// Register the database module globally
engine.register_static_module("db", db_module.into());

View File

@ -32,7 +32,12 @@ pub struct SignatureRequirement {
impl SignatureRequirement {
/// Create a new signature requirement.
pub fn new(_id: u32, flow_step_id: u32, public_key: impl ToString, message: impl ToString) -> Self {
pub fn new(
_id: u32,
flow_step_id: u32,
public_key: impl ToString,
message: impl ToString,
) -> Self {
Self {
base_data: BaseModelData::new(),
flow_step_id,

View File

@ -4,5 +4,5 @@ pub mod proposal;
pub mod attached_file;
pub use self::proposal::{Proposal, Ballot, VoteOption, ProposalStatus, VoteEventStatus};
pub use self::attached_file::AttachedFile;
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 serde::{Deserialize, Serialize};
use heromodels_core::BaseModelData;
use crate::models::core::Comment;
use super::AttachedFile;
use crate::models::core::Comment;
use heromodels_core::BaseModelData;
// --- Enums ---

View File

@ -1,7 +1,7 @@
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use std::fmt;
use serde::{Deserialize, Serialize};
use std::fmt;
// --- Enums ---

View File

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

View File

@ -1,7 +1,7 @@
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use serde::{Deserialize, Serialize};
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
/// Represents a collection of library items.
#[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 productexample; // Temporarily remove as files are missing
pub mod access;
pub mod calendar;
pub mod contact;
pub mod circle;
pub mod governance;
pub mod finance;
pub mod library;
pub mod legal;
pub mod flow;
pub mod biz;
pub mod calendar;
pub mod circle;
pub mod contact;
pub mod finance;
pub mod flow;
pub mod governance;
pub mod legal;
pub mod library;
pub mod projects;
// Re-export key types for convenience
pub use core::Comment;
pub use userexample::User;
// pub use productexample::Product; // Temporarily remove
pub use calendar::{Calendar, Event, Attendee, AttendanceStatus};
pub use circle::{Circle};
pub use governance::{Proposal, ProposalStatus, VoteEventStatus, Ballot, VoteOption, AttachedFile};
pub use finance::{Account, Asset, AssetType};
pub use finance::marketplace::{Listing, ListingStatus, ListingType, Bid, BidStatus};
pub use legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus};
pub use flow::{Flow, FlowStep, SignatureRequirement};
pub use biz::{Sale, SaleItem, SaleStatus};
pub use library::items::{Image, Pdf, Markdown};
pub use calendar::{AttendanceStatus, Attendee, Calendar, Event};
pub use circle::Circle;
pub use finance::marketplace::{Bid, BidStatus, Listing, ListingStatus, ListingType};
pub use finance::{Account, Asset, AssetType};
pub use flow::{Flow, FlowStep, SignatureRequirement};
pub use governance::{AttachedFile, Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption};
pub use legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus};
pub use library::collection::Collection;
pub use library::items::{Image, Markdown, Pdf};
pub use flow::register_flow_rhai_module;
#[cfg(feature = "rhai")]
pub use biz::register_biz_rhai_module;
#[cfg(feature = "rhai")]
pub use calendar::register_calendar_rhai_module;
#[cfg(feature = "rhai")]
pub use circle::register_circle_rhai_module;
pub use flow::register_flow_rhai_module;
pub use legal::register_legal_rhai_module;
#[cfg(feature = "rhai")]
pub use biz::register_biz_rhai_module;
pub use library::register_library_rhai_module;
#[cfg(feature = "rhai")]
pub use projects::register_projects_rhai_module;
#[cfg(feature = "rhai")]
pub use library::register_library_rhai_module;

View File

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

View File

@ -3,8 +3,8 @@
use chrono::{DateTime, Utc};
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use serde::{Deserialize, Serialize};
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
use super::base::Status as ProjectStatus; // Using the generic project status for now

View File

@ -1,11 +1,11 @@
// heromodels/src/models/projects/mod.rs
pub mod base;
pub mod task_enums;
pub mod task;
pub mod epic;
pub mod sprint_enums;
pub mod sprint;
pub mod sprint_enums;
pub mod task;
pub mod task_enums;
// pub mod epic;
// pub mod issue;
// pub mod kanban;
@ -13,11 +13,11 @@ pub mod sprint;
// pub mod story;
pub use base::*;
pub use task_enums::*;
pub use task::*;
pub use epic::*;
pub use sprint_enums::*;
pub use sprint::*;
pub use sprint_enums::*;
pub use task::*;
pub use task_enums::*;
// pub use epic::*;
// pub use issue::*;
// pub use kanban::*;

View File

@ -1,14 +1,13 @@
// heromodels/src/models/projects/rhai.rs
use rhai::{Engine, EvalAltResult, Dynamic, Position};
use std::sync::Arc;
use crate::db::hero::OurDB;
use heromodels_core::{Model, BaseModelDataOps};
use crate::db::{Db, Collection};
use crate::db::{Collection, Db};
use heromodels_core::{BaseModelDataOps, Model};
use rhai::{Dynamic, Engine, EvalAltResult, Position};
use std::sync::Arc;
// Import models from the projects::base module
use super::base::{Project, /* Label, */ Priority, Status, ItemType}; // Label commented out as it's unused for now
use super::base::{ItemType, /* Label, */ Priority, Project, Status}; // Label commented out as it's unused for now
// Helper function for ID conversion (if needed, similar to other rhai.rs files)
fn id_from_i64(val: i64) -> Result<u32, Box<EvalAltResult>> {
@ -78,46 +77,141 @@ pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
});
// 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>> {
Ok(Project::new(id_from_i64(id_i64)?, name, description, id_from_i64(owner_id_i64)?))
});
engine.register_fn(
"new_project_with_details",
|id_i64: i64,
name: String,
description: String,
owner_id_i64: i64|
-> Result<Project, Box<EvalAltResult>> {
Ok(Project::new(
id_from_i64(id_i64)?,
name,
description,
id_from_i64(owner_id_i64)?,
))
},
);
// Getters for Project
engine.register_get("id", |p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.get_id() as i64) });
engine.register_get("name", |p: &mut Project| -> Result<String, Box<EvalAltResult>> { Ok(p.name.clone()) });
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("id", |p: &mut Project| -> Result<i64, Box<EvalAltResult>> {
Ok(p.get_id() as i64)
});
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()) });
engine.register_get(
"name",
|p: &mut Project| -> Result<String, Box<EvalAltResult>> { Ok(p.name.clone()) },
);
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>> {
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
// 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("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>> {
engine.register_fn(
"name",
|p: Project, name: String| -> Result<Project, Box<EvalAltResult>> { Ok(p.name(name)) },
);
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
.into_iter()
.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>>>()?;
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
.into_iter()
.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>>>()?;
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
.into_iter()
.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>>>()?;
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
.into_iter()
.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>>>()?;
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
.into_iter()
.map(|tag_dyn: Dynamic| {
tag_dyn.clone().into_string().map_err(|_err| { // _err is Rhai's internal error, we create a new one
tag_dyn.clone().into_string().map_err(|_err| {
// _err is Rhai's internal error, we create a new one
Box::new(EvalAltResult::ErrorMismatchDataType(
"Expected string for tag".to_string(),
tag_dyn.type_name().to_string(),
@ -199,17 +324,40 @@ pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
})
.collect::<Result<Vec<String>, Box<EvalAltResult>>>()?;
Ok(p.tags(tags_vec))
});
},
);
engine.register_fn("status", |p: Project, status: Status| -> Result<Project, Box<EvalAltResult>> { Ok(p.status(status)) });
engine.register_fn("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)) });
engine.register_fn(
"status",
|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
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 ---
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| {
Box::new(EvalAltResult::ErrorSystem(
"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(),
))
})
});
},
);
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 collection = db_clone_get.collection::<Project>().map_err(|e| {
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(),
))),
}
});
},
);
// TODO: Register Rhai bindings for the `Label` model if needed, or remove unused import.
// Register Label type and its methods/getters

View File

@ -3,8 +3,8 @@
use chrono::{DateTime, Utc};
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use serde::{Deserialize, Serialize};
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
use super::sprint_enums::SprintStatus; // Import our new enum

View File

@ -3,10 +3,10 @@
use chrono::{DateTime, Utc};
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use serde::{Deserialize, Serialize};
use rhai::{CustomType, TypeBuilder}; // Assuming rhai might be used
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize}; // Assuming rhai might be used
use super::task_enums::{TaskStatus, TaskPriority}; // Import our new enums
use super::task_enums::{TaskPriority, TaskStatus}; // Import our new enums
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
#[model] // This will provide id, created_at, updated_at via base_data

View File

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

View File

@ -52,21 +52,31 @@ fn key_value_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> {
// Store data with custom IDs
for (i, &id) in custom_ids.iter().enumerate() {
let data = format!("Record with custom ID {}", id);
db.set(OurDBSetArgs { id: Some(id), data: data.as_bytes() })?;
db.set(OurDBSetArgs {
id: Some(id),
data: data.as_bytes(),
})?;
println!("Stored record {} with custom ID: {}", i + 1, id);
}
// Retrieve data by custom IDs
for &id in &custom_ids {
let retrieved = db.get(id)?;
println!("Retrieved ID {}: {}", id, String::from_utf8_lossy(&retrieved));
println!(
"Retrieved ID {}: {}",
id,
String::from_utf8_lossy(&retrieved)
);
}
// Update and track history
let id_to_update = custom_ids[2]; // ID 300
for i in 1..=3 {
let updated_data = format!("Updated record {} (version {})", id_to_update, i);
db.set(OurDBSetArgs { id: Some(id_to_update), data: updated_data.as_bytes() })?;
db.set(OurDBSetArgs {
id: Some(id_to_update),
data: updated_data.as_bytes(),
})?;
println!("Updated ID {} (version {})", id_to_update, i);
}
@ -106,7 +116,10 @@ fn incremental_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> {
// Store multiple records and collect assigned IDs
for i in 1..=5 {
let data = format!("Auto-increment record {}", i);
let id = db.set(OurDBSetArgs { id: None, data: data.as_bytes() })?;
let id = db.set(OurDBSetArgs {
id: None,
data: data.as_bytes(),
})?;
assigned_ids.push(id);
println!("Stored record {} with auto-assigned ID: {}", i, id);
}
@ -118,7 +131,11 @@ fn incremental_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> {
// Retrieve all records
for &id in &assigned_ids {
let retrieved = db.get(id)?;
println!("Retrieved ID {}: {}", id, String::from_utf8_lossy(&retrieved));
println!(
"Retrieved ID {}: {}",
id,
String::from_utf8_lossy(&retrieved)
);
}
db.close()?;
@ -157,15 +174,20 @@ fn performance_benchmark(base_path: &PathBuf) -> Result<(), ourdb::Error> {
let mut ids = Vec::with_capacity(num_operations);
for _ in 0..num_operations {
let id = db.set(OurDBSetArgs { id: None, data: &test_data })?;
let id = db.set(OurDBSetArgs {
id: None,
data: &test_data,
})?;
ids.push(id);
}
let write_duration = start.elapsed();
let writes_per_second = num_operations as f64 / write_duration.as_secs_f64();
println!("Write performance: {:.2} ops/sec ({:.2} ms/op)",
println!(
"Write performance: {:.2} ops/sec ({:.2} ms/op)",
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
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 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,
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
println!("Benchmarking {} update operations...", num_operations);
let start = Instant::now();
for &id in &ids {
db.set(OurDBSetArgs { id: Some(id), data: &test_data })?;
db.set(OurDBSetArgs {
id: Some(id),
data: &test_data,
})?;
}
let update_duration = start.elapsed();
let updates_per_second = num_operations as f64 / update_duration.as_secs_f64();
println!("Update performance: {:.2} ops/sec ({:.2} ms/op)",
println!(
"Update performance: {:.2} ops/sec ({:.2} ms/op)",
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()?;
println!("Performance benchmark completed");

View File

@ -20,23 +20,40 @@ fn main() -> Result<(), ourdb::Error> {
// Store some data with auto-generated IDs
let data1 = b"First record";
let id1 = db.set(OurDBSetArgs { id: None, data: data1 })?;
let id1 = db.set(OurDBSetArgs {
id: None,
data: data1,
})?;
println!("Stored first record with ID: {}", id1);
let data2 = b"Second record";
let id2 = db.set(OurDBSetArgs { id: None, data: data2 })?;
let id2 = db.set(OurDBSetArgs {
id: None,
data: data2,
})?;
println!("Stored second record with ID: {}", id2);
// Retrieve and print the data
let retrieved1 = db.get(id1)?;
println!("Retrieved ID {}: {}", id1, String::from_utf8_lossy(&retrieved1));
println!(
"Retrieved ID {}: {}",
id1,
String::from_utf8_lossy(&retrieved1)
);
let retrieved2 = db.get(id2)?;
println!("Retrieved ID {}: {}", id2, String::from_utf8_lossy(&retrieved2));
println!(
"Retrieved ID {}: {}",
id2,
String::from_utf8_lossy(&retrieved2)
);
// Update a record to demonstrate history tracking
let updated_data = b"Updated first record";
db.set(OurDBSetArgs { id: Some(id1), data: updated_data })?;
db.set(OurDBSetArgs {
id: Some(id1),
data: updated_data,
})?;
println!("Updated record with ID: {}", id1);
// Get history for the updated record

View File

@ -42,19 +42,27 @@ fn main() -> Result<(), ourdb::Error> {
let test_data = vec![b'A'; 100];
// Benchmark write operations
println!("Benchmarking {} write operations (incremental: {}, keysize: {})...",
num_operations, incremental_mode, keysize);
println!(
"Benchmarking {} write operations (incremental: {}, keysize: {})...",
num_operations, incremental_mode, keysize
);
let start = Instant::now();
let mut ids = Vec::with_capacity(num_operations);
for _ in 0..num_operations {
let id = if incremental_mode {
db.set(OurDBSetArgs { id: None, data: &test_data })?
db.set(OurDBSetArgs {
id: None,
data: &test_data,
})?
} else {
// In non-incremental mode, we need to provide IDs
let id = ids.len() as u32 + 1;
db.set(OurDBSetArgs { id: Some(id), data: &test_data })?;
db.set(OurDBSetArgs {
id: Some(id),
data: &test_data,
})?;
id
};
ids.push(id);
@ -63,9 +71,11 @@ fn main() -> Result<(), ourdb::Error> {
let write_duration = start.elapsed();
let writes_per_second = num_operations as f64 / write_duration.as_secs_f64();
println!("Write performance: {:.2} ops/sec ({:.2} ms/op)",
println!(
"Write performance: {:.2} ops/sec ({:.2} ms/op)",
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
println!("Benchmarking {} read operations...", num_operations);
@ -79,9 +89,11 @@ fn main() -> Result<(), ourdb::Error> {
let read_duration = start.elapsed();
let reads_per_second = num_operations as f64 / read_duration.as_secs_f64();
println!("Read performance: {:.2} ops/sec ({:.2} ms/op)",
println!(
"Read performance: {:.2} ops/sec ({:.2} ms/op)",
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
println!("Benchmarking {} update operations...", num_operations);
@ -89,15 +101,20 @@ fn main() -> Result<(), ourdb::Error> {
let start = Instant::now();
for &id in &ids {
db.set(OurDBSetArgs { id: Some(id), data: &test_data })?;
db.set(OurDBSetArgs {
id: Some(id),
data: &test_data,
})?;
}
let update_duration = start.elapsed();
let updates_per_second = num_operations as f64 / update_duration.as_secs_f64();
println!("Update performance: {:.2} ops/sec ({:.2} ms/op)",
println!(
"Update performance: {:.2} ops/sec ({:.2} ms/op)",
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
db.close()?;

View File

@ -30,7 +30,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Store some data
let test_data = b"Hello, OurDB!";
let id = db.set(OurDBSetArgs { id: None, data: test_data })?;
let id = db.set(OurDBSetArgs {
id: None,
data: test_data,
})?;
println!("\nStored data with ID: {}", id);
// Retrieve the data
@ -39,12 +42,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Update the data
let updated_data = b"Updated data in OurDB!";
db.set(OurDBSetArgs { id: Some(id), data: updated_data })?;
db.set(OurDBSetArgs {
id: Some(id),
data: updated_data,
})?;
println!("\nUpdated data with ID: {}", id);
// Retrieve the updated data
let retrieved = db.get(id)?;
println!("Retrieved updated data: {}", String::from_utf8_lossy(&retrieved));
println!(
"Retrieved updated data: {}",
String::from_utf8_lossy(&retrieved)
);
// Get history
let history = db.get_history(id, 2)?;

View File

@ -1,7 +1,6 @@
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use crc32fast::Hasher;
use crate::error::Error;
@ -27,10 +26,7 @@ impl OurDB {
}
// Open the file fresh
let file = OpenOptions::new()
.read(true)
.write(true)
.open(&path)?;
let file = OpenOptions::new().read(true).write(true).open(&path)?;
self.file = Some(file);
self.file_nr = file_nr;
@ -80,12 +76,18 @@ impl OurDB {
}
/// Stores data at the specified ID with history tracking
pub(crate) fn set_(&mut self, id: u32, old_location: Location, data: &[u8]) -> Result<(), Error> {
pub(crate) fn set_(
&mut self,
id: u32,
old_location: Location,
data: &[u8],
) -> Result<(), Error> {
// Validate data size - maximum is u16::MAX (65535 bytes or ~64KB)
if data.len() > u16::MAX as usize {
return Err(Error::InvalidOperation(
format!("Data size exceeds maximum allowed size of {} bytes", u16::MAX)
));
return Err(Error::InvalidOperation(format!(
"Data size exceeds maximum allowed size of {} bytes",
u16::MAX
)));
}
// Get file number to use
@ -95,15 +97,15 @@ impl OurDB {
self.db_file_select(file_nr)?;
// Get current file position for lookup
let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?;
let file = self
.file
.as_mut()
.ok_or_else(|| Error::Other("No file open".to_string()))?;
file.seek(SeekFrom::End(0))?;
let position = file.stream_position()? as u32;
// Create new location
let new_location = Location {
file_nr,
position,
};
let new_location = Location { file_nr, position };
// Calculate CRC of data
let crc = calculate_crc(data);
@ -144,13 +146,19 @@ impl OurDB {
/// Retrieves data at the specified location
pub(crate) fn get_(&mut self, location: Location) -> Result<Vec<u8>, Error> {
if location.position == 0 {
return Err(Error::NotFound(format!("Record not found, location: {:?}", location)));
return Err(Error::NotFound(format!(
"Record not found, location: {:?}",
location
)));
}
// Select the file
self.db_file_select(location.file_nr)?;
let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?;
let file = self
.file
.as_mut()
.ok_or_else(|| Error::Other("No file open".to_string()))?;
// Read header
file.seek(SeekFrom::Start(location.position as u64))?;
@ -173,7 +181,9 @@ impl OurDB {
// Verify CRC
let calculated_crc = calculate_crc(&data);
if calculated_crc != stored_crc {
return Err(Error::DataCorruption("CRC mismatch: data corruption detected".to_string()));
return Err(Error::DataCorruption(
"CRC mismatch: data corruption detected".to_string(),
));
}
Ok(data)
@ -188,7 +198,10 @@ impl OurDB {
// Select the file
self.db_file_select(location.file_nr)?;
let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?;
let file = self
.file
.as_mut()
.ok_or_else(|| Error::Other("No file open".to_string()))?;
// Skip size and CRC (6 bytes)
file.seek(SeekFrom::Start(location.position as u64 + 6))?;
@ -210,7 +223,10 @@ impl OurDB {
// Select the file
self.db_file_select(location.file_nr)?;
let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?;
let file = self
.file
.as_mut()
.ok_or_else(|| Error::Other("No file open".to_string()))?;
// Read size first
file.seek(SeekFrom::Start(location.position as u64))?;
@ -335,7 +351,11 @@ mod tests {
let test_data = b"Test data for backend operations";
let id = 1;
db.set(OurDBSetArgs { id: Some(id), data: test_data }).unwrap();
db.set(OurDBSetArgs {
id: Some(id),
data: test_data,
})
.unwrap();
let retrieved = db.get(id).unwrap();
assert_eq!(retrieved, test_data);

View File

@ -1,7 +1,7 @@
mod backend;
mod error;
mod location;
mod lookup;
mod backend;
pub use error::Error;
pub use location::Location;
@ -110,7 +110,7 @@ impl OurDB {
let location = self.lookup.get(id)?;
if location.position == 0 {
return Err(Error::InvalidOperation(
"Cannot set ID for insertions when incremental mode is enabled".to_string()
"Cannot set ID for insertions when incremental mode is enabled".to_string(),
));
}
@ -124,9 +124,11 @@ impl OurDB {
}
} else {
// Using key-value mode
let id = args.id.ok_or_else(|| Error::InvalidOperation(
"ID must be provided when incremental is disabled".to_string()
))?;
let id = args.id.ok_or_else(|| {
Error::InvalidOperation(
"ID must be provided when incremental is disabled".to_string(),
)
})?;
let location = self.lookup.get(id)?;
self.set_(id, location, args.data)?;
@ -179,7 +181,9 @@ impl OurDB {
/// Returns the next ID which will be used when storing in incremental mode
pub fn get_next_id(&mut self) -> Result<u32, Error> {
if !self.incremental_mode {
return Err(Error::InvalidOperation("Incremental mode is not enabled".to_string()));
return Err(Error::InvalidOperation(
"Incremental mode is not enabled".to_string(),
));
}
self.lookup.get_next_id()
}
@ -212,7 +216,8 @@ impl OurDB {
}
fn save(&mut self) -> Result<(), Error> {
self.lookup.export_sparse(&self.lookup_dump_path().to_string_lossy())?;
self.lookup
.export_sparse(&self.lookup_dump_path().to_string_lossy())?;
Ok(())
}
@ -251,14 +256,23 @@ mod tests {
// Test set and get
let test_data = b"Hello, OurDB!";
let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap();
let id = db
.set(OurDBSetArgs {
id: None,
data: test_data,
})
.unwrap();
let retrieved = db.get(id).unwrap();
assert_eq!(retrieved, test_data);
// Test update
let updated_data = b"Updated data";
db.set(OurDBSetArgs { id: Some(id), data: updated_data }).unwrap();
db.set(OurDBSetArgs {
id: Some(id),
data: updated_data,
})
.unwrap();
let retrieved = db.get(id).unwrap();
assert_eq!(retrieved, updated_data);

View File

@ -22,13 +22,18 @@ impl Location {
pub fn from_bytes(bytes: &[u8], keysize: u8) -> Result<Self, Error> {
// Validate keysize
if ![2, 3, 4, 6].contains(&keysize) {
return Err(Error::InvalidOperation(format!("Invalid keysize: {}", keysize)));
return Err(Error::InvalidOperation(format!(
"Invalid keysize: {}",
keysize
)));
}
// Create padded bytes
let mut padded = vec![0u8; keysize as usize];
if bytes.len() > keysize as usize {
return Err(Error::InvalidOperation("Input bytes exceed keysize".to_string()));
return Err(Error::InvalidOperation(
"Input bytes exceed keysize".to_string(),
));
}
let start_idx = keysize as usize - bytes.len();
@ -49,34 +54,39 @@ impl Location {
// Verify limits
if location.position > 0xFFFF {
return Err(Error::InvalidOperation(
"Position exceeds max value for keysize=2 (max 65535)".to_string()
"Position exceeds max value for keysize=2 (max 65535)".to_string(),
));
}
},
}
3 => {
// Only position, 3 bytes big endian
location.position = u32::from(padded[0]) << 16 | u32::from(padded[1]) << 8 | u32::from(padded[2]);
location.position =
u32::from(padded[0]) << 16 | u32::from(padded[1]) << 8 | u32::from(padded[2]);
location.file_nr = 0;
// Verify limits
if location.position > 0xFFFFFF {
return Err(Error::InvalidOperation(
"Position exceeds max value for keysize=3 (max 16777215)".to_string()
"Position exceeds max value for keysize=3 (max 16777215)".to_string(),
));
}
},
}
4 => {
// Only position, 4 bytes big endian
location.position = u32::from(padded[0]) << 24 | u32::from(padded[1]) << 16
| u32::from(padded[2]) << 8 | u32::from(padded[3]);
location.position = u32::from(padded[0]) << 24
| u32::from(padded[1]) << 16
| u32::from(padded[2]) << 8
| u32::from(padded[3]);
location.file_nr = 0;
},
}
6 => {
// 2 bytes file_nr + 4 bytes position, all big endian
location.file_nr = u16::from(padded[0]) << 8 | u16::from(padded[1]);
location.position = u32::from(padded[2]) << 24 | u32::from(padded[3]) << 16
| u32::from(padded[4]) << 8 | u32::from(padded[5]);
},
location.position = u32::from(padded[2]) << 24
| u32::from(padded[3]) << 16
| u32::from(padded[4]) << 8
| u32::from(padded[5]);
}
_ => unreachable!(),
}

View File

@ -46,7 +46,10 @@ impl LookupTable {
pub fn new(config: LookupConfig) -> Result<Self, Error> {
// Verify keysize is valid
if ![2, 3, 4, 6].contains(&config.keysize) {
return Err(Error::InvalidOperation(format!("Invalid keysize: {}", config.keysize)));
return Err(Error::InvalidOperation(format!(
"Invalid keysize: {}",
config.keysize
)));
}
let incremental = if config.incremental_mode {
@ -98,7 +101,9 @@ impl LookupTable {
if start_pos + entry_size as u64 > file_size {
return Err(Error::LookupError(format!(
"Invalid read for get in lut: {}: {} would exceed file size {}",
self.lookuppath, start_pos + entry_size as u64, file_size
self.lookuppath,
start_pos + entry_size as u64,
file_size
)));
}
@ -142,7 +147,7 @@ impl LookupTable {
if id > incremental {
return Err(Error::InvalidOperation(
"Cannot set ID for insertions when incremental mode is enabled".to_string()
"Cannot set ID for insertions when incremental mode is enabled".to_string(),
));
}
}
@ -151,46 +156,57 @@ impl LookupTable {
let location_bytes = match self.keysize {
2 => {
if location.file_nr != 0 {
return Err(Error::InvalidOperation("file_nr must be 0 for keysize=2".to_string()));
return Err(Error::InvalidOperation(
"file_nr must be 0 for keysize=2".to_string(),
));
}
if location.position > 0xFFFF {
return Err(Error::InvalidOperation(
"position exceeds max value for keysize=2 (max 65535)".to_string()
"position exceeds max value for keysize=2 (max 65535)".to_string(),
));
}
vec![(location.position >> 8) as u8, location.position as u8]
},
}
3 => {
if location.file_nr != 0 {
return Err(Error::InvalidOperation("file_nr must be 0 for keysize=3".to_string()));
return Err(Error::InvalidOperation(
"file_nr must be 0 for keysize=3".to_string(),
));
}
if location.position > 0xFFFFFF {
return Err(Error::InvalidOperation(
"position exceeds max value for keysize=3 (max 16777215)".to_string()
"position exceeds max value for keysize=3 (max 16777215)".to_string(),
));
}
vec![
(location.position >> 16) as u8,
(location.position >> 8) as u8,
location.position as u8
location.position as u8,
]
},
}
4 => {
if location.file_nr != 0 {
return Err(Error::InvalidOperation("file_nr must be 0 for keysize=4".to_string()));
return Err(Error::InvalidOperation(
"file_nr must be 0 for keysize=4".to_string(),
));
}
vec![
(location.position >> 24) as u8,
(location.position >> 16) as u8,
(location.position >> 8) as u8,
location.position as u8
location.position as u8,
]
},
}
6 => {
// Full location with file_nr and position
location.to_bytes()
},
_ => return Err(Error::InvalidOperation(format!("Invalid keysize: {}", self.keysize))),
}
_ => {
return Err(Error::InvalidOperation(format!(
"Invalid keysize: {}",
self.keysize
)))
}
};
if !self.lookuppath.is_empty() {
@ -224,9 +240,9 @@ impl LookupTable {
/// Gets the next available ID in incremental mode
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())
)?;
})?;
let table_size = if !self.lookuppath.is_empty() {
let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME);
@ -244,9 +260,9 @@ impl LookupTable {
/// Increments the index in incremental mode
pub fn increment_index(&mut self) -> Result<(), Error> {
let mut incremental = self.incremental.ok_or_else(||
let mut incremental = self.incremental.ok_or_else(|| {
Error::InvalidOperation("Lookup table not in incremental mode".to_string())
)?;
})?;
incremental += 1;
self.incremental = Some(incremental);
@ -344,7 +360,7 @@ impl LookupTable {
if data.len() % record_size != 0 {
return Err(Error::DataCorruption(
"Invalid sparse data format: size mismatch".to_string()
"Invalid sparse data format: size mismatch".to_string(),
));
}
@ -447,9 +463,9 @@ fn get_incremental_info(config: &LookupConfig) -> Result<u32, Error> {
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
use std::env::temp_dir;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
fn get_temp_dir() -> PathBuf {

View File

@ -1,9 +1,9 @@
use ourdb::{OurDB, OurDBConfig, OurDBSetArgs};
use rand;
use std::env::temp_dir;
use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use rand;
// Helper function to create a unique temporary directory for tests
fn get_temp_dir() -> PathBuf {
@ -33,22 +33,30 @@ fn test_basic_operations() {
incremental_mode: true,
file_size: None,
keysize: None,
reset: None
reset: None,
};
let mut db = OurDB::new(config).unwrap();
// Test set and get
let test_data = b"Hello, OurDB!";
let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap();
let id = db
.set(OurDBSetArgs {
id: None,
data: test_data,
})
.unwrap();
let retrieved = db.get(id).unwrap();
assert_eq!(retrieved, test_data);
// Test update
let updated_data = b"Updated data";
db.set(OurDBSetArgs { id: Some(id), data: updated_data }).unwrap();
db.set(OurDBSetArgs {
id: Some(id),
data: updated_data,
})
.unwrap();
let retrieved = db.get(id).unwrap();
assert_eq!(retrieved, updated_data);
@ -71,14 +79,13 @@ fn test_basic_operations() {
fn test_key_value_mode() {
let temp_dir = get_temp_dir();
// Create a new database with key-value mode
let config = OurDBConfig {
path: temp_dir.clone(),
incremental_mode: false,
file_size: None,
keysize: None,
reset: None
reset: None,
};
let mut db = OurDB::new(config).unwrap();
@ -86,7 +93,11 @@ fn test_key_value_mode() {
// Test set with explicit ID
let test_data = b"Key-value data";
let id = 42;
db.set(OurDBSetArgs { id: Some(id), data: test_data }).unwrap();
db.set(OurDBSetArgs {
id: Some(id),
data: test_data,
})
.unwrap();
let retrieved = db.get(id).unwrap();
assert_eq!(retrieved, test_data);
@ -108,18 +119,27 @@ fn test_incremental_mode() {
incremental_mode: true,
file_size: None,
keysize: None,
reset: None
reset: None,
};
let mut db = OurDB::new(config).unwrap();
// Test auto-increment IDs
let data1 = b"First record";
let id1 = db.set(OurDBSetArgs { id: None, data: data1 }).unwrap();
let id1 = db
.set(OurDBSetArgs {
id: None,
data: data1,
})
.unwrap();
let data2 = b"Second record";
let id2 = db.set(OurDBSetArgs { id: None, data: data2 }).unwrap();
let id2 = db
.set(OurDBSetArgs {
id: None,
data: data2,
})
.unwrap();
// IDs should be sequential
assert_eq!(id2, id1 + 1);
@ -136,7 +156,6 @@ fn test_incremental_mode() {
fn test_persistence() {
let temp_dir = get_temp_dir();
// Create data in a new database
{
let config = OurDBConfig {
@ -144,13 +163,18 @@ fn test_persistence() {
incremental_mode: true,
file_size: None,
keysize: None,
reset: None
reset: None,
};
let mut db = OurDB::new(config).unwrap();
let test_data = b"Persistent data";
let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap();
let id = db
.set(OurDBSetArgs {
id: None,
data: test_data,
})
.unwrap();
// Explicitly close the database
db.close().unwrap();
@ -166,7 +190,7 @@ fn test_persistence() {
incremental_mode: true,
file_size: None,
keysize: None,
reset: None
reset: None,
};
let mut db = OurDB::new(config).unwrap();
@ -198,14 +222,19 @@ fn test_different_keysizes() {
incremental_mode: true,
file_size: None,
keysize: Some(*keysize),
reset: None
reset: None,
};
let mut db = OurDB::new(config).unwrap();
// Test basic operations
let test_data = b"Keysize test data";
let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap();
let id = db
.set(OurDBSetArgs {
id: None,
data: test_data,
})
.unwrap();
let retrieved = db.get(id).unwrap();
assert_eq!(retrieved, test_data);
@ -225,7 +254,7 @@ fn test_large_data() {
incremental_mode: true,
file_size: None,
keysize: None,
reset: None
reset: None,
};
let mut db = OurDB::new(config).unwrap();
@ -234,7 +263,12 @@ fn test_large_data() {
let large_data = vec![b'X'; 60 * 1024];
// Store and retrieve large data
let id = db.set(OurDBSetArgs { id: None, data: &large_data }).unwrap();
let id = db
.set(OurDBSetArgs {
id: None,
data: &large_data,
})
.unwrap();
let retrieved = db.get(id).unwrap();
assert_eq!(retrieved.len(), large_data.len());
@ -254,7 +288,7 @@ fn test_exceed_size_limit() {
incremental_mode: true,
file_size: None,
keysize: None,
reset: None
reset: None,
};
let mut db = OurDB::new(config).unwrap();
@ -263,10 +297,16 @@ fn test_exceed_size_limit() {
let oversized_data = vec![b'X'; 70 * 1024];
// Attempt to store data that exceeds the size limit
let result = db.set(OurDBSetArgs { id: None, data: &oversized_data });
let result = db.set(OurDBSetArgs {
id: None,
data: &oversized_data,
});
// Verify that an error is returned
assert!(result.is_err(), "Expected an error when storing data larger than 64KB");
assert!(
result.is_err(),
"Expected an error when storing data larger than 64KB"
);
// Clean up
db.destroy().unwrap();
@ -276,14 +316,13 @@ fn test_exceed_size_limit() {
fn test_multiple_files() {
let temp_dir = get_temp_dir();
// Create a new database with small file size to force multiple files
let config = OurDBConfig {
path: temp_dir.clone(),
incremental_mode: true,
file_size: Some(1024), // Very small file size (1KB)
keysize: Some(6), // 6-byte keysize for multiple files
reset: None
reset: None,
};
let mut db = OurDB::new(config).unwrap();
@ -294,7 +333,12 @@ fn test_multiple_files() {
let mut ids = Vec::new();
for _ in 0..10 {
let id = db.set(OurDBSetArgs { id: None, data: &test_data }).unwrap();
let id = db
.set(OurDBSetArgs {
id: None,
data: &test_data,
})
.unwrap();
ids.push(id);
}
@ -305,7 +349,8 @@ fn test_multiple_files() {
}
// Verify multiple files were created
let files = fs::read_dir(&temp_dir).unwrap()
let files = fs::read_dir(&temp_dir)
.unwrap()
.filter_map(Result::ok)
.filter(|entry| {
let path = entry.path();
@ -313,7 +358,11 @@ fn test_multiple_files() {
})
.count();
assert!(files > 1, "Expected multiple database files, found {}", files);
assert!(
files > 1,
"Expected multiple database files, found {}",
files
);
// Clean up
db.destroy().unwrap();

View File

@ -1,6 +1,6 @@
use proc_macro::TokenStream;
use quote::{quote, format_ident};
use syn::{parse_macro_input, ItemFn, FnArg, Pat, PatType, ReturnType, parse_quote};
use quote::{format_ident, quote};
use syn::{parse_macro_input, parse_quote, FnArg, ItemFn, Pat, PatType, ReturnType};
/// Procedural macro that generates a Rhai client function for a Rust function.
///
@ -69,7 +69,10 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream {
};
// 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();
// Handle different parameter types
@ -86,7 +89,11 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream {
quote! {
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
quote! {
format!("{}", #name)
@ -235,7 +242,10 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream {
};
// 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();
// Handle different parameter types
@ -263,11 +273,13 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream {
format!("{:?}", #name)
}
}
}).collect::<Vec<_>>();
})
.collect::<Vec<_>>();
// Determine if the return type needs conversion
let return_type_str = quote! { #return_type }.to_string();
let needs_return_conversion = return_type_str.contains("i32") || return_type_str.contains("u32");
let needs_return_conversion =
return_type_str.contains("i32") || return_type_str.contains("u32");
// Generate the client function with appropriate type conversions
let client_fn = if needs_return_conversion {

View File

@ -1,5 +1,5 @@
use tst::TST;
use std::time::Instant;
use tst::TST;
fn main() -> Result<(), tst::Error> {
// 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::time::{Duration, Instant};
use tst::TST;
// Function to generate a test value of specified size
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));
print!("\rProgress: {}/{} records ({:.2}%) - {:.2} records/sec",
i + 1, TOTAL_RECORDS,
print!(
"\rProgress: {}/{} records ({:.2}%) - {:.2} records/sec",
i + 1,
TOTAL_RECORDS,
(i + 1) as f64 / TOTAL_RECORDS as f64 * 100.0,
records_per_second);
records_per_second
);
io::stdout().flush().unwrap();
last_batch_time = Instant::now();
@ -90,19 +93,27 @@ fn main() -> Result<(), tst::Error> {
let total_duration = start_time.elapsed();
println!("\n\nPerformance Summary:");
println!("Total time to insert {} records: {:?}", TOTAL_RECORDS, total_duration);
println!("Average insertion rate: {:.2} records/second",
TOTAL_RECORDS as f64 / total_duration.as_secs_f64());
println!(
"Total time to insert {} records: {:?}",
TOTAL_RECORDS, total_duration
);
println!(
"Average insertion rate: {:.2} records/second",
TOTAL_RECORDS as f64 / total_duration.as_secs_f64()
);
// Show performance trend
println!("\nPerformance Trend (records inserted vs. time per batch):");
for (i, (record_count, duration)) in insertion_times.iter().enumerate() {
if i % 10 == 0 || i == insertion_times.len() - 1 { // Only show every 10th point to avoid too much output
println!(" After {} records: {:?} for {} records ({:.2} records/sec)",
if i % 10 == 0 || i == insertion_times.len() - 1 {
// Only show every 10th point to avoid too much output
println!(
" After {} records: {:?} for {} records ({:.2} records/sec)",
record_count,
duration,
PROGRESS_INTERVAL,
PROGRESS_INTERVAL as f64 / duration.as_secs_f64());
PROGRESS_INTERVAL as f64 / duration.as_secs_f64()
);
}
}
@ -122,8 +133,10 @@ fn main() -> Result<(), tst::Error> {
total_get_time += get_start.elapsed();
}
println!("Average time to retrieve a record: {:?}",
total_get_time / num_samples as u32);
println!(
"Average time to retrieve a record: {:?}",
total_get_time / num_samples as u32
);
// Test prefix search performance
println!("\nTesting prefix search performance...");
@ -134,8 +147,12 @@ fn main() -> Result<(), tst::Error> {
let keys = tree.list(prefix)?;
let list_duration = list_start.elapsed();
println!("Found {} keys with prefix '{}' in {:?}",
keys.len(), prefix, list_duration);
println!(
"Found {} keys with prefix '{}' in {:?}",
keys.len(),
prefix,
list_duration
);
}
// Clean up (optional)

View File

@ -1,5 +1,5 @@
use tst::TST;
use std::time::Instant;
use tst::TST;
fn main() -> Result<(), tst::Error> {
// Create a temporary directory for the database
@ -16,11 +16,31 @@ fn main() -> Result<(), tst::Error> {
// Names
let names = [
"Alice", "Alexander", "Amanda", "Andrew", "Amy",
"Bob", "Barbara", "Benjamin", "Brenda", "Brian",
"Charlie", "Catherine", "Christopher", "Cynthia", "Carl",
"David", "Diana", "Daniel", "Deborah", "Donald",
"Edward", "Elizabeth", "Eric", "Emily", "Ethan"
"Alice",
"Alexander",
"Amanda",
"Andrew",
"Amy",
"Bob",
"Barbara",
"Benjamin",
"Brenda",
"Brian",
"Charlie",
"Catherine",
"Christopher",
"Cynthia",
"Carl",
"David",
"Diana",
"Daniel",
"Deborah",
"Donald",
"Edward",
"Elizabeth",
"Eric",
"Emily",
"Ethan",
];
for (i, name) in names.iter().enumerate() {
@ -30,10 +50,26 @@ fn main() -> Result<(), tst::Error> {
// Cities
let cities = [
"New York", "Los Angeles", "Chicago", "Houston", "Phoenix",
"Philadelphia", "San Antonio", "San Diego", "Dallas", "San Jose",
"Austin", "Jacksonville", "Fort Worth", "Columbus", "San Francisco",
"Charlotte", "Indianapolis", "Seattle", "Denver", "Washington"
"New York",
"Los Angeles",
"Chicago",
"Houston",
"Phoenix",
"Philadelphia",
"San Antonio",
"San Diego",
"Dallas",
"San Jose",
"Austin",
"Jacksonville",
"Fort Worth",
"Columbus",
"San Francisco",
"Charlotte",
"Indianapolis",
"Seattle",
"Denver",
"Washington",
];
for (i, city) in cities.iter().enumerate() {
@ -43,9 +79,21 @@ fn main() -> Result<(), tst::Error> {
// Countries
let countries = [
"United States", "Canada", "Mexico", "Brazil", "Argentina",
"United Kingdom", "France", "Germany", "Italy", "Spain",
"China", "Japan", "India", "Australia", "Russia"
"United States",
"Canada",
"Mexico",
"Brazil",
"Argentina",
"United Kingdom",
"France",
"Germany",
"Italy",
"Spain",
"China",
"Japan",
"India",
"Australia",
"Russia",
];
for (i, country) in countries.iter().enumerate() {
@ -53,7 +101,10 @@ fn main() -> Result<(), tst::Error> {
tree.set(country, value)?;
}
println!("Total items inserted: {}", names.len() + cities.len() + countries.len());
println!(
"Total items inserted: {}",
names.len() + cities.len() + countries.len()
);
// Test prefix operations
test_prefix(&mut tree, "A")?;
@ -71,7 +122,11 @@ fn main() -> Result<(), tst::Error> {
let all_keys = tree.list("")?;
let duration = start.elapsed();
println!("Found {} keys with empty prefix in {:?}", all_keys.len(), duration);
println!(
"Found {} keys with empty prefix in {:?}",
all_keys.len(),
duration
);
println!("First 5 keys (alphabetically):");
for key in all_keys.iter().take(5) {
println!(" {}", key);
@ -96,7 +151,12 @@ fn test_prefix(tree: &mut TST, prefix: &str) -> Result<(), tst::Error> {
let keys = tree.list(prefix)?;
let list_duration = start.elapsed();
println!("Found {} keys with prefix '{}' in {:?}", keys.len(), prefix, list_duration);
println!(
"Found {} keys with prefix '{}' in {:?}",
keys.len(),
prefix,
list_duration
);
if !keys.is_empty() {
println!("Keys:");
@ -110,12 +170,14 @@ fn test_prefix(tree: &mut TST, prefix: &str) -> Result<(), tst::Error> {
let getall_duration = start.elapsed();
println!("Retrieved {} values in {:?}", values.len(), getall_duration);
println!("First value: {}",
println!(
"First value: {}",
if !values.is_empty() {
String::from_utf8_lossy(&values[0])
} else {
"None".into()
});
}
);
}
Ok(())

View File

@ -1,7 +1,7 @@
//! Error types for the TST module.
use thiserror::Error;
use std::io;
use thiserror::Error;
/// Error type for TST operations.
#[derive(Debug, Error)]

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
};
Ok(TST {
db,
root_id,
})
Ok(TST { db, root_id })
}
/// 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.
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)?;
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.
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() {
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 {
match get(tree, &key) {
Ok(value) => values.push(value),
Err(e) => errors.push(format!("Error getting value for key '{}': {:?}", key, e))
Err(e) => errors.push(format!("Error getting value for key '{}': {:?}", key, e)),
}
}

View File

@ -46,7 +46,8 @@ impl TSTNode {
/// Deserializes bytes to a node.
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()));
}
@ -57,7 +58,10 @@ impl TSTNode {
pos += 1;
if version != VERSION {
return Err(Error::Deserialization(format!("Unsupported version: {}", version)));
return Err(Error::Deserialization(format!(
"Unsupported version: {}",
version
)));
}
// Character
@ -79,7 +83,9 @@ impl TSTNode {
// Value
let value = if value_len > 0 {
if pos + value_len > data.len() {
return Err(Error::Deserialization("Value length exceeds data".to_string()));
return Err(Error::Deserialization(
"Value length exceeds data".to_string(),
));
}
data[pos..pos + value_len].to_vec()
} else {
@ -89,7 +95,9 @@ impl TSTNode {
// Child pointers
if pos + 12 > data.len() {
return Err(Error::Deserialization("Data too short for child pointers".to_string()));
return Err(Error::Deserialization(
"Data too short for child pointers".to_string(),
));
}
let left_id_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]];
@ -108,7 +116,11 @@ impl TSTNode {
value,
is_end_of_key,
left_id: if left_id == 0 { None } else { Some(left_id) },
middle_id: if middle_id == 0 { None } else { Some(middle_id) },
middle_id: if middle_id == 0 {
None
} else {
Some(middle_id)
},
right_id: if right_id == 0 { None } else { Some(right_id) },
})
}

View File

@ -1,7 +1,7 @@
use tst::TST;
use std::env::temp_dir;
use std::fs;
use std::time::SystemTime;
use tst::TST;
fn get_test_db_path() -> String {
let timestamp = SystemTime::now()
@ -166,10 +166,7 @@ fn test_list_prefix() {
let mut tree = result.unwrap();
// Insert keys with common prefixes - use fewer keys to avoid filling the lookup table
let keys = [
"apple", "application", "append",
"banana", "bandana"
];
let keys = ["apple", "application", "append", "banana", "bandana"];
for key in &keys {
let set_result = tree.set(key, key.as_bytes().to_vec());
@ -223,9 +220,7 @@ fn test_getall_prefix() {
let mut tree = result.unwrap();
// Insert keys with common prefixes - use fewer keys to avoid filling the lookup table
let keys = [
"apple", "application", "append"
];
let keys = ["apple", "application", "append"];
for key in &keys {
let set_result = tree.set(key, key.as_bytes().to_vec());

View File

@ -1,7 +1,7 @@
use tst::TST;
use std::env::temp_dir;
use std::fs;
use std::time::SystemTime;
use tst::TST;
fn get_test_db_path() -> String {
let timestamp = SystemTime::now()
@ -232,9 +232,18 @@ fn test_prefix_with_long_keys() {
// Insert long keys
let test_data = [
("this_is_a_very_long_key_for_testing_purposes_1", b"value1".to_vec()),
("this_is_a_very_long_key_for_testing_purposes_2", b"value2".to_vec()),
("this_is_a_very_long_key_for_testing_purposes_3", b"value3".to_vec()),
(
"this_is_a_very_long_key_for_testing_purposes_1",
b"value1".to_vec(),
),
(
"this_is_a_very_long_key_for_testing_purposes_2",
b"value2".to_vec(),
),
(
"this_is_a_very_long_key_for_testing_purposes_3",
b"value3".to_vec(),
),
("this_is_another_long_key_for_testing", b"value4".to_vec()),
];