This commit is contained in:
2025-04-22 12:53:17 +04:00
parent cad285fd59
commit 1708e6dd06
116 changed files with 1919 additions and 81 deletions

View File

@@ -1,4 +1,4 @@
use heromodels::{BaseModel, Comment, User, ModelBuilder};
use heromodels::models::{Model, Comment, User};
fn main() {
println!("Hero Models - Basic Usage Example");

View File

@@ -1,10 +0,0 @@
pub mod model;
pub mod comment;
// Re-export key types for convenience
pub use model::{BaseModel, BaseModelData, IndexKey, IndexKeyBuilder, ModelBuilder};
pub use comment::Comment;
pub use crate::impl_model_builder;

View File

@@ -1,7 +0,0 @@
// Export submodules
pub mod model;
pub mod comment;
// Re-export key types for convenience
pub use model::{BaseModel, BaseModelData, IndexKey, IndexKeyBuilder, ModelBuilder};
pub use comment::Comment;

646
heromodels/src/herodb/db.rs Normal file
View File

@@ -0,0 +1,646 @@
use crate::db::error::{DbError, DbResult};
use crate::db::model::{Model, IndexKey};
use crate::db::store::{DbOperations, OurDbStore};
use crate::db::generic_store::{GenericStore, GetId};
use crate::db::tst_index::TSTIndexManager;
use std::any::TypeId;
use std::collections::HashMap;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use rhai::{CustomType, EvalAltResult, TypeBuilder};
use serde::{Serialize, de::DeserializeOwned};
/// Represents a single database operation in a transaction
#[derive(Debug, Clone)]
enum DbOperation {
Set {
model_type: TypeId,
serialized: Vec<u8>,
model_prefix: String, // Add model prefix
model_id: u32, // Add model ID
},
Delete {
model_type: TypeId,
id: u32,
model_prefix: String, // Add model prefix
},
}
/// Transaction state for DB operations
#[derive(Clone)]
pub struct TransactionState {
operations: Vec<DbOperation>,
active: bool,
}
impl TransactionState {
/// Create a new transaction state
pub fn new() -> Self {
Self {
operations: Vec::new(),
active: true,
}
}
}
/// Main DB manager that automatically handles all models
#[derive(Clone, CustomType)]
pub struct DB {
db_path: PathBuf,
// Type map for generic operations
type_map: HashMap<TypeId, Arc<RwLock<dyn DbOperations>>>,
// TST index manager
tst_index: Arc<RwLock<TSTIndexManager>>,
// Transaction state
transaction: Arc<RwLock<Option<TransactionState>>>,
}
/// Builder for DB that allows registering models
#[derive(Clone, CustomType)]
pub struct DBBuilder {
base_path: PathBuf,
model_registrations: Vec<Arc<dyn ModelRegistration>>,
}
/// Trait for model registration
pub trait ModelRegistration: Send + Sync {
fn register(&self, path: &Path) -> DbResult<(TypeId, Arc<RwLock<dyn DbOperations>>)>;
}
/// Implementation of ModelRegistration for any Model type
pub struct ModelRegistrar<T: Model> {
phantom: std::marker::PhantomData<T>,
}
impl<T: Model> ModelRegistrar<T> {
pub fn new() -> Self {
Self {
phantom: std::marker::PhantomData,
}
}
}
/// Implementation of ModelRegistration for any serializable type that implements GetId
pub struct TypeRegistrar<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static> {
prefix: &'static str,
phantom: std::marker::PhantomData<T>,
}
impl<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static> TypeRegistrar<T> {
pub fn new(prefix: &'static str) -> Self {
Self {
prefix,
phantom: std::marker::PhantomData,
}
}
}
impl<T: Model> ModelRegistration for ModelRegistrar<T> {
fn register(&self, path: &Path) -> DbResult<(TypeId, Arc<RwLock<dyn DbOperations>>)> {
let store = OurDbStore::<T>::open(path.join(T::db_prefix()))?;
Ok((TypeId::of::<T>(), Arc::new(RwLock::new(store)) as Arc<RwLock<dyn DbOperations>>))
}
}
impl<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static> ModelRegistration for TypeRegistrar<T> {
fn register(&self, path: &Path) -> DbResult<(TypeId, Arc<RwLock<dyn DbOperations>>)> {
let store = GenericStore::<T>::open(path, self.prefix)?;
Ok((TypeId::of::<T>(), Arc::new(RwLock::new(store)) as Arc<RwLock<dyn DbOperations>>))
}
}
impl DBBuilder {
/// Create a new DB builder
pub fn new<P: Into<PathBuf>>(base_path: P) -> Self {
Self {
base_path: base_path.into(),
model_registrations: Vec::new(),
}
}
pub fn with_path<P: Into<PathBuf>>(base_path: P) -> Self {
Self {
base_path: base_path.into(),
model_registrations: Vec::new(),
}
}
/// Register a model type with the DB
pub fn register_model<T: Model>(mut self) -> Self {
self.model_registrations
.push(Arc::new(ModelRegistrar::<T>::new()));
self
}
/// Register any serializable type with the DB
pub fn register_type<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static>(
mut self,
prefix: &'static str
) -> Self {
self.model_registrations
.push(Arc::new(TypeRegistrar::<T>::new(prefix)));
self
}
/// Build the DB with the registered models
pub fn build(self) -> Result<DB, Box<EvalAltResult>> {
let base_path = self.base_path;
// Ensure base directory exists
if !base_path.exists() {
std::fs::create_dir_all(&base_path).map_err(|e| {
EvalAltResult::ErrorSystem("Could not create base dir".to_string(), Box::new(e))
})?;
}
// Register all models
let mut type_map: HashMap<TypeId, Arc<RwLock<dyn DbOperations>>> = HashMap::new();
for registration in self.model_registrations {
let (type_id, store) = registration.register(&base_path).map_err(|e| {
EvalAltResult::ErrorSystem("Could not register type".to_string(), Box::new(e))
})?;
type_map.insert(type_id, store);
}
// Create the TST index manager
let tst_index = TSTIndexManager::new(&base_path).map_err(|e| {
EvalAltResult::ErrorSystem("Could not create TST index manager".to_string(), Box::new(e))
})?;
let transaction = Arc::new(RwLock::new(None));
Ok(DB {
db_path: base_path,
type_map,
tst_index: Arc::new(RwLock::new(tst_index)),
transaction,
})
}
}
impl DB {
/// Create a new empty DB instance without any models
pub fn new<P: Into<PathBuf>>(base_path: P) -> DbResult<Self> {
let base_path = base_path.into();
// Ensure base directory exists
if !base_path.exists() {
std::fs::create_dir_all(&base_path)?;
}
// Create the TST index manager
let tst_index = TSTIndexManager::new(&base_path)?;
let transaction = Arc::new(RwLock::new(None));
Ok(Self {
db_path: base_path,
type_map: HashMap::new(),
tst_index: Arc::new(RwLock::new(tst_index)),
transaction,
})
}
// Transaction-related methods
/// Begin a new transaction
pub fn begin_transaction(&self) -> DbResult<()> {
let mut tx = self.transaction.write().unwrap();
if tx.is_some() {
return Err(DbError::TransactionError(
"Transaction already in progress".into(),
));
}
*tx = Some(TransactionState::new());
Ok(())
}
/// Check if a transaction is active
pub fn has_active_transaction(&self) -> bool {
let tx = self.transaction.read().unwrap();
tx.is_some() && tx.as_ref().unwrap().active
}
/// Apply a set operation with the serialized data - bypass transaction check
fn apply_set_operation(&self, model_type: TypeId, serialized: &[u8]) -> DbResult<()> {
// Get the database operations for this model type
if let Some(db_ops) = self.type_map.get(&model_type) {
// Just pass the raw serialized data to a special raw insert method
let mut db_ops_guard = db_ops.write().unwrap();
return db_ops_guard.insert_raw(serialized);
}
Err(DbError::GeneralError(format!(
"No DB registered for type ID {:?}",
model_type
)))
}
/// Commit the current transaction, applying all operations
pub fn commit_transaction(&self) -> DbResult<()> {
let mut tx_guard = self.transaction.write().unwrap();
if let Some(tx_state) = tx_guard.take() {
if !tx_state.active {
return Err(DbError::TransactionError("Transaction not active".into()));
}
// Create a backup of the transaction state in case we need to rollback
let backup = tx_state.clone();
// Try to execute all operations
let result = (|| {
for op in tx_state.operations {
match op {
DbOperation::Set {
model_type,
serialized,
model_prefix,
model_id,
} => {
// Apply to OurDB
self.apply_set_operation(model_type, &serialized)?;
// Apply to TST index (primary key only)
// We can't easily get the index keys in the transaction commit
// because we don't have the model type information at runtime
let mut tst_index = self.tst_index.write().unwrap();
tst_index.set(&model_prefix, model_id, serialized.clone())?;
}
DbOperation::Delete {
model_type,
id,
model_prefix,
} => {
// For delete operations, we can't get the index keys from the model
// because it's already deleted. We'll just delete the primary key.
// Apply to OurDB
let db_ops = self
.type_map
.get(&model_type)
.ok_or_else(|| DbError::TypeError)?;
let mut db_ops_guard = db_ops.write().unwrap();
db_ops_guard.delete(id)?;
// Apply to TST index (primary key only)
let mut tst_index = self.tst_index.write().unwrap();
tst_index.delete(&model_prefix, id)?;
}
}
}
Ok(())
})();
// If any operation failed, restore the transaction state
if result.is_err() {
*tx_guard = Some(backup);
return result;
}
Ok(())
} else {
Err(DbError::TransactionError("No active transaction".into()))
}
}
/// Rollback the current transaction, discarding all operations
pub fn rollback_transaction(&self) -> DbResult<()> {
let mut tx = self.transaction.write().unwrap();
if tx.is_none() {
return Err(DbError::TransactionError("No active transaction".into()));
}
*tx = None;
Ok(())
}
/// Get the path to the database
pub fn path(&self) -> &PathBuf {
&self.db_path
}
// Generic methods that work with any supported model type
/// Insert a model instance into its appropriate database based on type
pub fn set<T: Model>(&self, model: &T) -> DbResult<()> {
// Try to acquire a write lock on the transaction
let mut tx_guard = self.transaction.write().unwrap();
// Check if there's an active transaction
if let Some(tx_state) = tx_guard.as_mut() {
if tx_state.active {
// Serialize the model for later use
let serialized = model.to_bytes()?;
// Get the index keys for this model
let index_keys = model.db_keys();
// Record a Set operation in the transaction with prefix and ID
tx_state.operations.push(DbOperation::Set {
model_type: TypeId::of::<T>(),
serialized,
model_prefix: T::db_prefix().to_string(),
model_id: model.get_id(),
});
return Ok(());
}
}
// If we got here, either there's no transaction or it's not active
// Drop the write lock before doing a direct database operation
drop(tx_guard);
// Execute directly
match self.type_map.get(&TypeId::of::<T>()) {
Some(db_ops) => {
let mut db_ops_guard = db_ops.write().unwrap();
db_ops_guard.insert(model)?;
// Also update the TST index with all index keys
let mut tst_index = self.tst_index.write().unwrap();
let prefix = T::db_prefix();
let id = model.get_id();
let data = model.to_bytes()?;
let index_keys = model.db_keys();
tst_index.set_with_indexes(prefix, id, data, &index_keys)?;
Ok(())
},
None => Err(DbError::TypeError),
}
}
/// Insert any serializable struct that implements GetId
pub fn set_any<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static>(
&self,
item: &T,
prefix: &str
) -> DbResult<()> {
// Try to acquire a write lock on the transaction
let mut tx_guard = self.transaction.write().unwrap();
// Check if there's an active transaction
if let Some(tx_state) = tx_guard.as_mut() {
if tx_state.active {
// Serialize the item for later use
let serialized = bincode::serialize(item).map_err(DbError::SerializationError)?;
// Record a Set operation in the transaction with prefix and ID
tx_state.operations.push(DbOperation::Set {
model_type: TypeId::of::<T>(),
serialized,
model_prefix: prefix.to_string(),
model_id: item.get_id(),
});
return Ok(());
}
}
// If we got here, either there's no transaction or it's not active
// Drop the write lock before doing a direct database operation
drop(tx_guard);
// Execute directly
match self.type_map.get(&TypeId::of::<T>()) {
Some(db_ops) => {
// Serialize the item
let data = bincode::serialize(item).map_err(DbError::SerializationError)?;
// Insert the raw data
let mut db_ops_guard = db_ops.write().unwrap();
db_ops_guard.insert_raw(&data)?;
// Also update the TST index (primary key only)
let mut tst_index = self.tst_index.write().unwrap();
tst_index.set(prefix, item.get_id(), data)?;
Ok(())
},
None => Err(DbError::TypeError),
}
}
/// Check the transaction state for the given type and id
fn check_transaction<T: Model>(&self, id: u32) -> Option<Result<Option<T>, DbError>> {
// Try to acquire a read lock on the transaction
let tx_guard = self.transaction.read().unwrap();
if let Some(tx_state) = tx_guard.as_ref() {
if !tx_state.active {
return None;
}
let type_id = TypeId::of::<T>();
// Process operations in reverse order (last operation wins)
for op in tx_state.operations.iter().rev() {
match op {
// First check if this ID has been deleted in the transaction
DbOperation::Delete {
model_type,
id: op_id,
model_prefix: _,
} => {
if *model_type == type_id && *op_id == id {
// Return NotFound error for deleted records
return Some(Err(DbError::NotFound(id)));
}
}
// Then check if it has been set in the transaction
DbOperation::Set {
model_type,
serialized,
model_prefix: _,
model_id,
} => {
if *model_type == type_id && *model_id == id {
// Try to deserialize
match T::from_bytes(serialized) {
Ok(model) => {
return Some(Ok(Some(model)));
}
Err(_) => continue, // Skip if deserialization fails
}
}
}
}
}
}
// Not found in transaction (continue to database)
None
}
/// Get a model instance by its ID and type
pub fn get<T: Model>(&self, id: u32) -> DbResult<T> {
// First check if there's a pending value in the current transaction
if let Some(tx_result) = self.check_transaction::<T>(id) {
match tx_result {
Ok(Some(model)) => return Ok(model),
Ok(None) => return Err(DbError::NotFound(id)),
Err(e) => return Err(e),
}
}
// If not found in transaction, get from database
match self.type_map.get(&TypeId::of::<T>()) {
Some(db_ops) => {
let mut db_ops_guard = db_ops.write().unwrap();
let any_result = db_ops_guard.get(id)?;
// Try to downcast to T
match any_result.downcast::<T>() {
Ok(boxed_t) => Ok(*boxed_t),
Err(_) => Err(DbError::TypeError),
}
}
None => Err(DbError::TypeError),
}
}
/// Get any serializable struct by its ID and type
pub fn get_any<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static>(
&self,
id: u32
) -> DbResult<T> {
// If not found in transaction, get from database
match self.type_map.get(&TypeId::of::<T>()) {
Some(db_ops) => {
let mut db_ops_guard = db_ops.write().unwrap();
let any_result = db_ops_guard.get(id)?;
// Try to downcast to T
match any_result.downcast::<T>() {
Ok(boxed_t) => Ok(*boxed_t),
Err(_) => Err(DbError::TypeError),
}
}
None => Err(DbError::TypeError),
}
}
/// Delete a model instance by its ID and type
pub fn delete<T: Model>(&self, id: u32) -> DbResult<()> {
// Try to acquire a write lock on the transaction
let mut tx_guard = self.transaction.write().unwrap();
// Check if there's an active transaction
if let Some(tx_state) = tx_guard.as_mut() {
if tx_state.active {
// Record a Delete operation in the transaction
tx_state.operations.push(DbOperation::Delete {
model_type: TypeId::of::<T>(),
id,
model_prefix: T::db_prefix().to_string(),
});
return Ok(());
}
}
// If we got here, either there's no transaction or it's not active
// Drop the write lock before doing a direct database operation
drop(tx_guard);
// Execute directly
match self.type_map.get(&TypeId::of::<T>()) {
Some(db_ops) => {
let mut db_ops_guard = db_ops.write().unwrap();
db_ops_guard.delete(id)?;
// Also delete from the TST index
let mut tst_index = self.tst_index.write().unwrap();
tst_index.delete(T::db_prefix(), id)?;
Ok(())
}
None => Err(DbError::TypeError),
}
}
/// Delete any serializable struct by its ID and type
pub fn delete_any<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static>(
&self,
id: u32,
prefix: &str
) -> DbResult<()> {
// Execute directly
match self.type_map.get(&TypeId::of::<T>()) {
Some(db_ops) => {
let mut db_ops_guard = db_ops.write().unwrap();
db_ops_guard.delete(id)?;
// Also delete from the TST index
let mut tst_index = self.tst_index.write().unwrap();
tst_index.delete(prefix, id)?;
Ok(())
}
None => Err(DbError::TypeError),
}
}
/// List all model instances of a given type
pub fn list<T: Model>(&self) -> DbResult<Vec<T>> {
match self.type_map.get(&TypeId::of::<T>()) {
Some(db_ops) => {
let db_ops_guard = db_ops.read().unwrap();
let any_result = db_ops_guard.list()?;
// Try to downcast to Vec<T>
match any_result.downcast::<Vec<T>>() {
Ok(boxed_vec) => Ok(*boxed_vec),
Err(_) => Err(DbError::TypeError),
}
}
None => Err(DbError::TypeError),
}
}
/// List all instances of any serializable type
pub fn list_any<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static>(
&self
) -> DbResult<Vec<T>> {
match self.type_map.get(&TypeId::of::<T>()) {
Some(db_ops) => {
let db_ops_guard = db_ops.read().unwrap();
let any_result = db_ops_guard.list()?;
// Try to downcast to Vec<T>
match any_result.downcast::<Vec<T>>() {
Ok(boxed_vec) => Ok(*boxed_vec),
Err(_) => Err(DbError::TypeError),
}
}
None => Err(DbError::TypeError),
}
}
/// Get the history of a model instance
pub fn get_history<T: Model>(&self, id: u32, depth: u8) -> DbResult<Vec<T>> {
match self.type_map.get(&TypeId::of::<T>()) {
Some(db_ops) => {
let mut db_ops_guard = db_ops.write().unwrap();
let any_results = db_ops_guard.get_history(id, depth)?;
let mut results = Vec::with_capacity(any_results.len());
for any_result in any_results {
match any_result.downcast::<T>() {
Ok(boxed_t) => results.push(*boxed_t),
Err(_) => return Err(DbError::TypeError),
}
}
Ok(results)
}
None => Err(DbError::TypeError),
}
}
}

View File

@@ -0,0 +1,30 @@
use thiserror::Error;
use std::fmt::Debug;
/// Errors that can occur during database operations
#[derive(Error, Debug)]
pub enum DbError {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Serialization/Deserialization error: {0}")]
SerializationError(#[from] bincode::Error),
#[error("Record not found for ID: {0}")]
NotFound(u32),
#[error("Type mismatch during deserialization")]
TypeError,
#[error("Transaction error: {0}")]
TransactionError(String),
#[error("OurDB error: {0}")]
OurDbError(#[from] ourdb::Error),
#[error("General database error: {0}")]
GeneralError(String),
}
/// Result type for DB operations
pub type DbResult<T> = Result<T, DbError>;

View File

@@ -0,0 +1,140 @@
use crate::db::error::{DbError, DbResult};
use crate::db::store::DbOperations;
use ourdb::{OurDB, OurDBConfig, OurDBSetArgs};
use serde::{Serialize, de::DeserializeOwned};
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::any::Any;
// Trait for getting ID from any serializable type
pub trait GetId {
fn get_id(&self) -> u32;
}
/// A store implementation for any serializable type using OurDB as the backend
pub struct GenericStore<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static> {
db: OurDB,
path: PathBuf,
prefix: String,
_phantom: PhantomData<T>,
}
impl<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static> GenericStore<T> {
/// Opens or creates an OurDB database at the specified path
pub fn open<P: AsRef<Path>>(path: P, prefix: &str) -> DbResult<Self> {
let path_buf = path.as_ref().to_path_buf();
let db_path = path_buf.join(prefix);
// Create directory if it doesn't exist
std::fs::create_dir_all(&db_path).map_err(DbError::IoError)?;
let config = OurDBConfig {
path: db_path.clone(),
incremental_mode: true, // Always use incremental mode for auto IDs
file_size: None, // Use default (500MB)
keysize: None, // Use default (4 bytes)
reset: None, // Don't reset existing database
};
let db = OurDB::new(config).map_err(DbError::OurDbError)?;
Ok(Self {
db,
path: db_path,
prefix: prefix.to_string(),
_phantom: PhantomData,
})
}
/// Serializes an item to bytes
fn serialize(item: &T) -> DbResult<Vec<u8>> {
bincode::serialize(item).map_err(DbError::SerializationError)
}
/// Deserializes bytes to an item
fn deserialize(data: &[u8]) -> DbResult<T> {
bincode::deserialize(data).map_err(DbError::SerializationError)
}
/// Gets the raw bytes for an item by ID
pub fn get_raw(&self, id: u32) -> DbResult<Vec<u8>> {
self.db.get(id).map_err(DbError::OurDbError)
}
/// Lists all raw items as bytes
pub fn list_raw(&self) -> DbResult<Vec<Vec<u8>>> {
let items = self.db.list().map_err(DbError::OurDbError)?;
Ok(items)
}
/// Get the prefix for this store
pub fn prefix(&self) -> &str {
&self.prefix
}
}
impl<T: Serialize + DeserializeOwned + GetId + Send + Sync + 'static> DbOperations for GenericStore<T> {
fn delete(&mut self, id: u32) -> DbResult<()> {
self.db.delete(id).map_err(DbError::OurDbError)
}
fn get(&mut self, id: u32) -> DbResult<Box<dyn Any>> {
let data = self.db.get(id).map_err(DbError::OurDbError)?;
let item = Self::deserialize(&data)?;
Ok(Box::new(item))
}
fn list(&self) -> DbResult<Box<dyn Any>> {
let items = self.db.list().map_err(DbError::OurDbError)?;
let mut result = Vec::with_capacity(items.len());
for data in items {
let item = Self::deserialize(&data)?;
result.push(item);
}
Ok(Box::new(result))
}
fn insert(&mut self, model: &dyn Any) -> DbResult<()> {
// Try to downcast to T
if let Some(item) = model.downcast_ref::<T>() {
let data = Self::serialize(item)?;
let id = item.get_id();
let args = OurDBSetArgs {
id: Some(id),
data,
};
self.db.set(args).map_err(DbError::OurDbError)
} else {
Err(DbError::TypeError)
}
}
fn insert_raw(&mut self, serialized: &[u8]) -> DbResult<()> {
// Deserialize to get the ID
let item = Self::deserialize(serialized)?;
let id = item.get_id();
let args = OurDBSetArgs {
id: Some(id),
data: serialized.to_vec(),
};
self.db.set(args).map_err(DbError::OurDbError)
}
fn get_history(&mut self, id: u32, depth: u8) -> DbResult<Vec<Box<dyn Any>>> {
let history = self.db.get_history(id, depth).map_err(DbError::OurDbError)?;
let mut result = Vec::with_capacity(history.len());
for data in history {
let item = Self::deserialize(&data)?;
result.push(Box::new(item));
}
Ok(result)
}
}

View File

@@ -0,0 +1,38 @@
//! Macros for implementing model methods
/// Macro to implement typed access methods on the DB struct for a given model
#[macro_export]
macro_rules! impl_model_methods {
($model:ty, $singular:ident, $plural:ident) => {
impl DB {
paste::paste! {
/// Insert a model instance into the database
pub fn [<insert_ $singular>](&mut self, item: $model) -> Result<(), Box<rhai::EvalAltResult>> {
Ok(self.set(&item).map_err(|e| {
rhai::EvalAltResult::ErrorSystem("could not insert $singular".to_string(), Box::new(e))
})?)
}
/// Get a model instance by its ID
pub fn [<get_ $singular>](&mut self, id: u32) -> DbResult<$model> {
self.get::<$model>(id)
}
/// Delete a model instance by its ID
pub fn [<delete_ $singular>](&mut self, id: u32) -> DbResult<()> {
self.delete::<$model>(id)
}
/// List all model instances
pub fn [<list_ $plural>](&mut self) -> DbResult<Vec<$model>> {
self.list::<$model>()
}
/// Get history of a model instance
pub fn [<get_ $singular _history>](&mut self, id: u32, depth: u8) -> DbResult<Vec<$model>> {
self.get_history::<$model>(id, depth)
}
}
}
};
}

View File

@@ -0,0 +1,33 @@
// Export the error module
pub mod error;
pub use error::{DbError, DbResult};
// Export the model module
pub mod model;
pub use model::{Model, Storable, IndexKey, GetId};
// Export the store module
pub mod store;
pub use store::{DbOperations, OurDbStore};
// Export the generic store module
pub mod generic_store;
pub use generic_store::GenericStore;
// Export the db module
pub mod db;
pub use db::{DB, DBBuilder, ModelRegistration, ModelRegistrar};
// Export the TST index module
pub mod tst_index;
pub use tst_index::TSTIndexManager;
// Export macros for model methods
pub mod macros;
// Export model-specific methods
pub mod model_methods;
// Tests
#[cfg(test)]
mod tests;

View File

@@ -0,0 +1,96 @@
use crate::db::error::{DbError, DbResult};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::fmt::Debug;
/// Trait for models that can be serialized and deserialized
pub trait Storable: Serialize + for<'de> Deserialize<'de> + Sized {
/// Serializes the instance using bincode
fn to_bytes(&self) -> DbResult<Vec<u8>> {
bincode::serialize(self).map_err(DbError::SerializationError)
}
/// Deserializes data from bytes into an instance
fn from_bytes(data: &[u8]) -> DbResult<Self> {
bincode::deserialize(data).map_err(DbError::SerializationError)
}
}
/// Represents an index key for a model
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexKey {
/// The name of the index key
pub name: &'static str,
/// The value of the index key for a specific model instance
pub value: String,
}
/// Trait identifying a model suitable for the database
/// The 'static lifetime bound is required for type identification via Any
pub trait Model: Storable + Debug + Clone + Send + Sync + 'static {
/// Returns the unique ID for this model instance
fn get_id(&self) -> u32;
/// Returns a prefix used for this model type in the database
/// Helps to logically separate different model types
fn db_prefix() -> &'static str;
/// Returns a list of index keys for this model instance
/// These keys will be used to create additional indexes in the TST
/// The default implementation returns an empty vector
/// Override this method to provide custom indexes
fn db_keys(&self) -> Vec<IndexKey> {
Vec::new()
}
}
/// Trait for adapting any serializable struct to work with the database
/// This is a lighter-weight alternative to the Model trait
pub trait ModelAdapter {
/// Returns the unique ID for this model instance
fn get_id(&self) -> u32;
/// Returns a prefix used for this model type in the database
fn db_prefix() -> &'static str;
/// Returns a list of index keys for this model instance
fn db_keys(&self) -> Vec<IndexKey> {
Vec::new()
}
}
/// Trait for getting ID from any serializable type
pub trait GetId {
/// Returns the unique ID for this instance
fn get_id(&self) -> u32;
}
/// Macro to automatically implement GetId for any struct with an id field of type u32
#[macro_export]
macro_rules! impl_get_id {
($type:ty) => {
impl GetId for $type {
fn get_id(&self) -> u32 {
self.id
}
}
};
}
/// Helper functions for serializing and deserializing any type
pub mod serialization {
use super::*;
/// Serialize any serializable type to bytes
pub fn to_bytes<T: Serialize>(value: &T) -> DbResult<Vec<u8>> {
bincode::serialize(value).map_err(DbError::SerializationError)
}
/// Deserialize bytes to any deserializable type
pub fn from_bytes<T: DeserializeOwned>(data: &[u8]) -> DbResult<T> {
bincode::deserialize(data).map_err(DbError::SerializationError)
}
}
// Note: We don't provide a blanket implementation of Storable
// Each model type must implement Storable explicitly

View File

@@ -0,0 +1,78 @@
use crate::db::db::DB;
use crate::db::model::Model;
use crate::impl_model_methods;
use crate::DbResult; // Add DbResult import
use crate::models::biz::{Product, Sale, Currency, ExchangeRate, Service, Customer, Contract, Invoice};
use crate::models::gov::{
Company, Shareholder, Meeting, User, Vote, Resolution,
Committee
// ComplianceRequirement, ComplianceDocument, ComplianceAudit - These don't exist
};
use crate::models::circle::{Circle, Member, Name, Wallet}; // Remove Asset
// Implement model-specific methods for Product
impl_model_methods!(Product, product, products);
// Implement model-specific methods for Sale
impl_model_methods!(Sale, sale, sales);
// Implement model-specific methods for Currency
impl_model_methods!(Currency, currency, currencies);
// Implement model-specific methods for ExchangeRate
impl_model_methods!(ExchangeRate, exchange_rate, exchange_rates);
// Implement model-specific methods for Service
impl_model_methods!(Service, service, services);
// Implement model-specific methods for Customer
impl_model_methods!(Customer, customer, customers);
// Implement model-specific methods for Contract
impl_model_methods!(Contract, contract, contracts);
// Implement model-specific methods for Invoice
impl_model_methods!(Invoice, invoice, invoices);
// Implement model-specific methods for Company
impl_model_methods!(Company, company, companies);
// Implement model-specific methods for Shareholder
impl_model_methods!(Shareholder, shareholder, shareholders);
// Implement model-specific methods for Meeting
impl_model_methods!(Meeting, meeting, meetings);
// Implement model-specific methods for User
impl_model_methods!(User, user, users);
// Implement model-specific methods for Vote
impl_model_methods!(Vote, vote, votes);
// Implement model-specific methods for Resolution
impl_model_methods!(Resolution, resolution, resolutions);
// Implement model-specific methods for Committee
impl_model_methods!(Committee, committee, committees);
// These models don't exist, so comment them out
// // Implement model-specific methods for ComplianceRequirement
// impl_model_methods!(ComplianceRequirement, compliance_requirement, compliance_requirements);
// // Implement model-specific methods for ComplianceDocument
// impl_model_methods!(ComplianceDocument, compliance_document, compliance_documents);
// // Implement model-specific methods for ComplianceAudit
// impl_model_methods!(ComplianceAudit, compliance_audit, compliance_audits);
// Implement model-specific methods for Circle
impl_model_methods!(Circle, circle, circles);
// Implement model-specific methods for Member
impl_model_methods!(Member, member, members);
// Implement model-specific methods for Name
impl_model_methods!(Name, name, names);
// Implement model-specific methods for Wallet
impl_model_methods!(Wallet, wallet, wallets);

View File

@@ -0,0 +1,156 @@
use crate::db::error::{DbError, DbResult};
use crate::db::model::Model;
use ourdb::{OurDB, OurDBConfig, OurDBSetArgs};
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::any::Any;
/// Trait for type-erased database operations
pub trait DbOperations: Send + Sync {
fn delete(&mut self, id: u32) -> DbResult<()>;
fn get(&mut self, id: u32) -> DbResult<Box<dyn Any>>;
fn list(&self) -> DbResult<Box<dyn Any>>;
fn insert(&mut self, model: &dyn Any) -> DbResult<()>;
fn insert_raw(&mut self, serialized: &[u8]) -> DbResult<()>;
fn get_history(&mut self, id: u32, depth: u8) -> DbResult<Vec<Box<dyn Any>>>;
}
/// A store implementation using OurDB as the backend
pub struct OurDbStore<T: Model> {
db: OurDB,
path: PathBuf,
_phantom: PhantomData<T>,
}
impl<T: Model> OurDbStore<T> {
/// Opens or creates an OurDB database at the specified path
pub fn open<P: AsRef<Path>>(path: P) -> DbResult<Self> {
let path_buf = path.as_ref().to_path_buf();
let db_path = path_buf.join(T::db_prefix());
// Create directory if it doesn't exist
std::fs::create_dir_all(&db_path).map_err(DbError::IoError)?;
let config = OurDBConfig {
path: db_path.clone(),
incremental_mode: true, // Always use incremental mode for auto IDs
file_size: None, // Use default (500MB)
keysize: None, // Use default (4 bytes)
reset: None, // Don't reset existing database
};
let db = OurDB::new(config).map_err(DbError::OurDbError)?;
Ok(Self {
db,
path: db_path,
_phantom: PhantomData,
})
}
/// Inserts or updates a model instance in the database
pub fn insert(&mut self, model: &T) -> DbResult<()> {
// Use the new method name
let data = model.to_bytes()?;
// Don't pass the ID when using incremental mode
// OurDB will automatically assign an ID
self.db.set(OurDBSetArgs {
id: None,
data: &data,
}).map_err(DbError::OurDbError)?;
Ok(())
}
/// Retrieves a model instance by its ID
pub fn get(&mut self, id: u32) -> DbResult<T> {
let data = self.db.get(id).map_err(|e| {
match e {
ourdb::Error::NotFound(_) => DbError::NotFound(id),
_ => DbError::OurDbError(e),
}
})?;
// Use the new method name
T::from_bytes(&data)
}
/// Deletes a model instance by its ID
pub fn delete(&mut self, id: u32) -> DbResult<()> {
self.db.delete(id).map_err(|e| {
match e {
ourdb::Error::NotFound(_) => DbError::NotFound(id),
_ => DbError::OurDbError(e),
}
})
}
/// Lists all models of this type
pub fn list(&self) -> DbResult<Vec<T>> {
// OurDB doesn't have a built-in list function, so we need to implement it
// This is a placeholder - in a real implementation, we would need to
// maintain a list of all IDs for each model type
Err(DbError::GeneralError("List operation not implemented yet".to_string()))
}
/// Gets the history of a model by its ID
pub fn get_history(&mut self, id: u32, depth: u8) -> DbResult<Vec<T>> {
let history_data = self.db.get_history(id, depth).map_err(|e| {
match e {
ourdb::Error::NotFound(_) => DbError::NotFound(id),
_ => DbError::OurDbError(e),
}
})?;
let mut result = Vec::with_capacity(history_data.len());
for data in history_data {
result.push(T::from_bytes(&data)?);
}
Ok(result)
}
}
impl<T: Model> DbOperations for OurDbStore<T> {
fn delete(&mut self, id: u32) -> DbResult<()> {
self.delete(id)
}
fn get(&mut self, id: u32) -> DbResult<Box<dyn Any>> {
let result = self.get(id)?;
Ok(Box::new(result))
}
fn list(&self) -> DbResult<Box<dyn Any>> {
// This doesn't require &mut self
let result = self.list()?;
Ok(Box::new(result))
}
fn insert(&mut self, model: &dyn Any) -> DbResult<()> {
// Downcast the Any to T
if let Some(model_t) = model.downcast_ref::<T>() {
self.insert(model_t)
} else {
Err(DbError::TypeError)
}
}
fn insert_raw(&mut self, serialized: &[u8]) -> DbResult<()> {
// Deserialize the raw bytes to a model
let model = T::from_bytes(serialized)?;
self.insert(&model)
}
fn get_history(&mut self, id: u32, depth: u8) -> DbResult<Vec<Box<dyn Any>>> {
let history = self.get_history(id, depth)?;
let mut result = Vec::with_capacity(history.len());
for item in history {
result.push(Box::new(item) as Box<dyn Any>);
}
Ok(result)
}
}

View File

@@ -0,0 +1,98 @@
use super::*;
use crate::db::model::Storable;
use serde::{Deserialize, Serialize};
use tempfile::tempdir;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct TestModel {
id: u32,
name: String,
}
impl Storable for TestModel {}
impl Model for TestModel {
fn get_id(&self) -> u32 {
self.id
}
fn db_prefix() -> &'static str {
"test"
}
}
#[test]
fn test_tst_integration() {
// Create a temporary directory for the test
let temp_dir = tempdir().unwrap();
let path = temp_dir.path();
// Create a DB instance
let mut db = DB::new(path).unwrap();
db.register::<TestModel>().unwrap();
// Create some test models
let model1 = TestModel { id: 1, name: "Test 1".to_string() };
let model2 = TestModel { id: 2, name: "Test 2".to_string() };
let model3 = TestModel { id: 3, name: "Test 3".to_string() };
// Insert the models
db.set(&model1).unwrap();
db.set(&model2).unwrap();
db.set(&model3).unwrap();
// List all models
let models = db.list::<TestModel>().unwrap();
assert_eq!(models.len(), 3);
// Verify that all models are in the list
assert!(models.contains(&model1));
assert!(models.contains(&model2));
assert!(models.contains(&model3));
// Delete a model
db.delete::<TestModel>(2).unwrap();
// List again
let models = db.list::<TestModel>().unwrap();
assert_eq!(models.len(), 2);
assert!(models.contains(&model1));
assert!(models.contains(&model3));
assert!(!models.contains(&model2));
// Test transaction with commit
db.begin_transaction().unwrap();
db.set(&model2).unwrap(); // Add back model2
db.delete::<TestModel>(1).unwrap(); // Delete model1
db.commit_transaction().unwrap();
// List again after transaction
let models = db.list::<TestModel>().unwrap();
assert_eq!(models.len(), 2);
assert!(!models.contains(&model1));
assert!(models.contains(&model2));
assert!(models.contains(&model3));
// Test transaction with rollback
db.begin_transaction().unwrap();
db.delete::<TestModel>(3).unwrap(); // Delete model3
db.rollback_transaction().unwrap();
// List again after rollback
let models = db.list::<TestModel>().unwrap();
assert_eq!(models.len(), 2);
assert!(!models.contains(&model1));
assert!(models.contains(&model2));
assert!(models.contains(&model3));
// Test the synchronize_tst_index method
// Since we can't directly access private fields, we'll just verify that
// the method runs without errors
db.synchronize_tst_index::<TestModel>().unwrap();
// Verify that our models are still accessible
let models = db.list::<TestModel>().unwrap();
assert_eq!(models.len(), 2);
assert!(models.contains(&model2));
assert!(models.contains(&model3));
}

View File

@@ -0,0 +1,261 @@
use crate::db::error::{DbError, DbResult};
use crate::db::model::IndexKey;
use std::path::{Path, PathBuf};
use std::collections::HashMap;
use tst::TST;
/// Manages TST-based indexes for model objects
pub struct TSTIndexManager {
/// Base path for TST databases
base_path: PathBuf,
/// Map of model prefixes to their TST instances
tst_instances: HashMap<String, TST>,
}
impl TSTIndexManager {
/// Creates a new TST index manager
pub fn new<P: AsRef<Path>>(base_path: P) -> DbResult<Self> {
let base_path = base_path.as_ref().to_path_buf();
// Create directory if it doesn't exist
std::fs::create_dir_all(&base_path).map_err(DbError::IoError)?;
Ok(Self {
base_path,
tst_instances: HashMap::new(),
})
}
/// Gets or creates a TST instance for a model prefix
pub fn get_tst(&mut self, prefix: &str) -> DbResult<&mut TST> {
if !self.tst_instances.contains_key(prefix) {
// Create a new TST instance for this prefix
let tst_path = self.base_path.join(format!("{}_tst", prefix));
let tst_path_str = tst_path.to_string_lossy().to_string();
// Create the TST
let tst = TST::new(&tst_path_str, false)
.map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?;
// Insert it into the map
self.tst_instances.insert(prefix.to_string(), tst);
}
// Return a mutable reference to the TST
Ok(self.tst_instances.get_mut(prefix).unwrap())
}
/// Adds or updates an object in the TST index with primary key
pub fn set(&mut self, prefix: &str, id: u32, data: Vec<u8>) -> DbResult<()> {
// Get the TST for this prefix
let tst = self.get_tst(prefix)?;
// Create the primary key in the format prefix_id
let key = format!("{}_{}", prefix, id);
// Set the key-value pair in the TST
tst.set(&key, data.clone())
.map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?;
Ok(())
}
/// Adds or updates an object in the TST index with additional index keys
pub fn set_with_indexes(&mut self, prefix: &str, id: u32, data: Vec<u8>, index_keys: &[IndexKey]) -> DbResult<()> {
// First set the primary key
self.set(prefix, id, data.clone())?;
// Get the TST for this prefix
let tst = self.get_tst(prefix)?;
// Add additional index keys
for index_key in index_keys {
// Create the index key in the format prefix_indexname_value
let key = format!("{}_{}_{}", prefix, index_key.name, index_key.value);
// Set the key-value pair in the TST
// For index keys, we store the ID as the value
let id_bytes = id.to_be_bytes().to_vec();
tst.set(&key, id_bytes)
.map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?;
}
Ok(())
}
/// Removes an object from the TST index (primary key only)
pub fn delete(&mut self, prefix: &str, id: u32) -> DbResult<()> {
// Get the TST for this prefix
let tst = self.get_tst(prefix)?;
// Create the key in the format prefix_id
let key = format!("{}_{}", prefix, id);
// Delete the key from the TST
tst.delete(&key)
.map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?;
Ok(())
}
/// Removes an object from the TST index including all index keys
pub fn delete_with_indexes(&mut self, prefix: &str, id: u32, index_keys: &[IndexKey]) -> DbResult<()> {
// First delete the primary key
self.delete(prefix, id)?;
// Get the TST for this prefix
let tst = self.get_tst(prefix)?;
// Delete additional index keys
for index_key in index_keys {
// Create the index key in the format prefix_indexname_value
let key = format!("{}_{}_{}", prefix, index_key.name, index_key.value);
// Delete the key from the TST
tst.delete(&key)
.map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?;
}
Ok(())
}
/// Lists all objects with a given prefix (primary keys only)
pub fn list(&mut self, prefix: &str) -> DbResult<Vec<(u32, Vec<u8>)>> {
// Get the TST for this prefix
let tst = self.get_tst(prefix)?;
// Get all keys with this prefix followed by an underscore
let search_prefix = format!("{}_", prefix);
let keys = tst.list(&search_prefix)
.map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?;
// Get all values for these keys
let mut result = Vec::with_capacity(keys.len());
for key in keys {
// Check if this is a primary key (prefix_id) and not an index key (prefix_indexname_value)
let parts: Vec<&str> = key.split('_').collect();
if parts.len() != 2 {
continue; // Skip index keys
}
// Extract the ID from the key (format: prefix_id)
let id_str = parts[1];
let id = id_str.parse::<u32>().map_err(|_| {
DbError::GeneralError(format!("Invalid ID in key: {}", key))
})?;
// Get the value from the TST
let data = tst.get(&key)
.map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?;
result.push((id, data));
}
Ok(result)
}
/// Finds objects by a specific index key
pub fn find_by_index(&mut self, prefix: &str, index_name: &str, index_value: &str) -> DbResult<Vec<u32>> {
// Get the TST for this prefix
let tst = self.get_tst(prefix)?;
// Create the index key in the format prefix_indexname_value
let key = format!("{}_{}_{}", prefix, index_name, index_value);
// Try to get the value from the TST
match tst.get(&key) {
Ok(id_bytes) => {
// Convert the bytes to a u32 ID
if id_bytes.len() == 4 {
let mut bytes = [0u8; 4];
bytes.copy_from_slice(&id_bytes[0..4]);
let id = u32::from_be_bytes(bytes);
Ok(vec![id])
} else {
Err(DbError::GeneralError(format!("Invalid ID bytes for key: {}", key)))
}
},
Err(_) => Ok(Vec::new()), // No matches found
}
}
/// Finds objects by a prefix of an index key
pub fn find_by_index_prefix(&mut self, prefix: &str, index_name: &str, index_value_prefix: &str) -> DbResult<Vec<u32>> {
// Get the TST for this prefix
let tst = self.get_tst(prefix)?;
// Create the index key prefix in the format prefix_indexname_valueprefix
let key_prefix = format!("{}_{}_{}", prefix, index_name, index_value_prefix);
// Get all keys with this prefix
let keys = tst.list(&key_prefix)
.map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?;
// Extract the IDs from the values
let mut result = Vec::with_capacity(keys.len());
for key in keys {
// Get the value from the TST
let id_bytes = tst.get(&key)
.map_err(|e| DbError::GeneralError(format!("TST error: {:?}", e)))?;
// Convert the bytes to a u32 ID
if id_bytes.len() == 4 {
let mut bytes = [0u8; 4];
bytes.copy_from_slice(&id_bytes[0..4]);
let id = u32::from_be_bytes(bytes);
result.push(id);
}
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_tst_index_manager() {
// Create a temporary directory for the test
let temp_dir = tempdir().unwrap();
let path = temp_dir.path();
// Create a TST index manager
let mut manager = TSTIndexManager::new(path).unwrap();
// Test setting values
let data1 = vec![1, 2, 3];
let data2 = vec![4, 5, 6];
manager.set("test", 1, data1.clone()).unwrap();
manager.set("test", 2, data2.clone()).unwrap();
// Test listing values
let items = manager.list("test").unwrap();
assert_eq!(items.len(), 2);
// Check that the values are correct
let mut found_data1 = false;
let mut found_data2 = false;
for (id, data) in items {
if id == 1 && data == data1 {
found_data1 = true;
} else if id == 2 && data == data2 {
found_data2 = true;
}
}
assert!(found_data1);
assert!(found_data2);
// Test deleting a value
manager.delete("test", 1).unwrap();
// Test listing again
let items = manager.list("test").unwrap();
assert_eq!(items.len(), 1);
assert_eq!(items[0].0, 2);
assert_eq!(items[0].1, data2);
}
}

View File

@@ -1,12 +1,2 @@
// Export core module
pub mod core;
// Export userexample module
pub mod userexample;
// Re-export key types for convenience
pub use core::{BaseModel, BaseModelData, IndexKey, ModelBuilder};
pub use core::Comment;
pub use userexample::User;
// No need to re-export macros as they are already exported at the crate root
// Export the models module
pub mod models;

View File

@@ -1,6 +1,5 @@
use serde::{Deserialize, Serialize};
use crate::core::model::{BaseModel, BaseModelData, IndexKey, ModelBuilder};
use crate::impl_model_builder;
use crate::models::core::model::{Model, BaseModelData, IndexKey};
/// Represents a comment on a model
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -33,7 +32,8 @@ impl Comment {
}
}
impl BaseModel for Comment {
// Implement the Model trait for Comment
impl Model for Comment {
fn db_prefix() -> &'static str {
"comment"
}
@@ -42,6 +42,10 @@ impl BaseModel for Comment {
self.base_data.id
}
fn base_data_mut(&mut self) -> &mut BaseModelData {
&mut self.base_data
}
fn db_keys(&self) -> Vec<IndexKey> {
vec![
IndexKey {
@@ -51,6 +55,3 @@ impl BaseModel for Comment {
]
}
}
// Implement ModelBuilder for Comment
impl_model_builder!(Comment);

View File

@@ -0,0 +1,7 @@
// Export submodules
pub mod model;
pub mod comment;
// Re-export key types for convenience
pub use model::{Model, BaseModelData, IndexKey, IndexKeyBuilder};
pub use comment::Comment;

View File

@@ -41,8 +41,8 @@ impl IndexKeyBuilder {
}
}
/// Base trait for all models
pub trait BaseModel: Debug + Clone + Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static {
/// Unified trait for all models
pub trait Model: Debug + Clone + Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static {
/// Get the database prefix for this model type
fn db_prefix() -> &'static str where Self: Sized;
@@ -56,6 +56,21 @@ pub trait BaseModel: Debug + Clone + Serialize + for<'de> Deserialize<'de> + Sen
/// Get the unique ID for this model
fn get_id(&self) -> u32;
/// Get a mutable reference to the base_data field
fn base_data_mut(&mut self) -> &mut BaseModelData;
/// Set the ID for this model
fn id(mut self, id: u32) -> Self where Self: Sized {
self.base_data_mut().id = id;
self
}
/// Build the model, updating the modified timestamp
fn build(mut self) -> Self where Self: Sized {
self.base_data_mut().update_modified();
self
}
}
/// Base struct that all models should include
@@ -164,29 +179,12 @@ impl BaseModelDataBuilder {
}
}
/// Trait for model builders that have a base_data field
pub trait ModelBuilder: Sized {
/// Get a mutable reference to the base_data field
fn base_data_mut(&mut self) -> &mut BaseModelData;
/// Set the ID for this model
fn id(mut self, id: u32) -> Self {
self.base_data_mut().id = id;
self
}
/// Build the model, updating the modified timestamp
fn build(mut self) -> Self {
self.base_data_mut().update_modified();
self
}
}
/// Macro to implement BaseModel for a struct that contains a base_data field of type BaseModelData
/// Macro to implement Model for a struct that contains a base_data field of type BaseModelData
#[macro_export]
macro_rules! impl_base_model {
macro_rules! impl_model {
// Basic implementation with default db_keys
($type:ty, $prefix:expr) => {
impl $crate::core::model::BaseModel for $type {
impl $crate::core::model::Model for $type {
fn db_prefix() -> &'static str {
$prefix
}
@@ -194,15 +192,7 @@ macro_rules! impl_base_model {
fn get_id(&self) -> u32 {
self.base_data.id
}
}
};
}
/// Macro to implement ModelBuilder for a struct that contains a base_data field of type BaseModelData
#[macro_export]
macro_rules! impl_model_builder {
($type:ty) => {
impl $crate::core::model::ModelBuilder for $type {
fn base_data_mut(&mut self) -> &mut $crate::core::model::BaseModelData {
&mut self.base_data
}

View File

@@ -0,0 +1,11 @@
// Export core module
pub mod core;
// Export userexample module
pub mod userexample;
// Re-export key types for convenience
pub use core::*;
pub use core::model::{Model, BaseModelData, IndexKey};
pub use core::Comment;
pub use userexample::User;

View File

@@ -0,0 +1,8 @@
// Export submodules
pub mod core;
pub mod userexample;
// Re-export key types for convenience
pub use core::model::{Model, BaseModelData, IndexKey};
pub use core::Comment;
pub use userexample::User;

View File

@@ -1,6 +1,5 @@
use serde::{Deserialize, Serialize};
use crate::core::model::{BaseModel, BaseModelData, IndexKey, ModelBuilder};
use crate::impl_model_builder;
use crate::models::core::model::{Model, BaseModelData, IndexKey};
/// Represents a user in the system
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -78,8 +77,8 @@ impl User {
}
}
// Implement BaseModel for User
impl BaseModel for User {
// Implement the Model trait for User
impl Model for User {
fn db_prefix() -> &'static str {
"user"
}
@@ -88,6 +87,11 @@ impl BaseModel for User {
self.base_data.id
}
//WHY?
fn base_data_mut(&mut self) -> &mut BaseModelData {
&mut self.base_data
}
fn db_keys(&self) -> Vec<IndexKey> {
vec![
IndexKey {
@@ -105,6 +109,3 @@ impl BaseModel for User {
]
}
}
// Implement ModelBuilder for User
impl_model_builder!(User);

View File

@@ -1,6 +0,0 @@
// Export user module
pub mod user;
// Re-export User for convenience
pub use user::User;