This commit is contained in:
despiegk 2025-04-22 08:24:17 +04:00
parent d75de1e73c
commit cad285fd59
13 changed files with 659 additions and 129 deletions

329
heromodels/Cargo.lock generated Normal file
View File

@ -0,0 +1,329 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bumpalo"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "cc"
version = "1.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "heromodels"
version = "0.1.0"
dependencies = [
"bincode",
"chrono",
"serde",
]
[[package]]
name = "iana-time-zone"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "windows-core"
version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-result"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
dependencies = [
"windows-link",
]

View File

@ -1,7 +1,7 @@
[package]
name = "heromodels"
version = "0.1.0"
edition = "2021"
edition = "2024"
description = "A library for hero models with base model trait implementation"
authors = ["Your Name <your.email@example.com>"]

133
heromodels/README.md Normal file
View File

@ -0,0 +1,133 @@
# Hero Models
A Rust library for model management with a base model trait implementation and builder pattern support.
## Features
- **Base Model Trait**: Common interface for all models with standard methods
- **Builder Pattern**: All models use the builder pattern for flexible and clear construction
- **Indexing Support**: Built-in support for model indexing with customizable keys
- **Comment System**: Integrated comment system that can be attached to any model
- **Timestamps**: Automatic creation and modification timestamps
## Usage
Add this to your `Cargo.toml`:
```toml
[dependencies]
heromodels = "0.1.0"
```
### Basic Example
```rust
use heromodels::{BaseModel, User, Comment};
fn main() {
// Create a new user using the builder pattern
let user_result = User::builder(1)
.username("johndoe")
.email("john.doe@example.com")
.full_name("John Doe")
.build();
let mut user = user_result.expect("Failed to build user");
// Create a comment for the user using the builder pattern
let comment_result = Comment::builder(1)
.user_id(2) // commenter's user ID
.model_id(user.get_id())
.model_type(User::db_prefix())
.content("This is a comment on the user")
.build();
let comment = comment_result.expect("Failed to build comment");
// Add the comment to the user
user.base_data.add_comment(comment.get_id());
// Get the database keys for the user
let keys = user.db_keys();
for key in keys {
println!("{}: {}", key.name, key.value);
}
}
```
### Creating Custom Models
To create your own model that inherits from the base model:
```rust
use serde::{Deserialize, Serialize};
use heromodels::model::{BaseModel, BaseModelData, IndexKey};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Product {
pub base_data: BaseModelData,
pub name: String,
pub price: f64,
pub sku: String,
}
impl BaseModel for Product {
fn db_prefix() -> &'static str {
"product"
}
fn get_id(&self) -> u32 {
self.base_data.id
}
fn db_keys(&self) -> Vec<IndexKey> {
vec![
IndexKey {
name: "name",
value: self.name.clone(),
},
IndexKey {
name: "sku",
value: self.sku.clone(),
},
]
}
}
// Implement a builder for your custom model
pub struct ProductBuilder {
id: u32,
base_data_builder: Option<heromodels::model::BaseModelDataBuilder>,
name: Option<String>,
price: Option<f64>,
sku: Option<String>,
}
impl ProductBuilder {
pub fn new(id: u32) -> Self {
Self {
id,
base_data_builder: Some(BaseModelData::builder(id)),
name: None,
price: None,
sku: None,
}
}
// Add builder methods...
pub fn build(self) -> Result<Product, &'static str> {
// Implementation...
}
}
impl Product {
pub fn builder(id: u32) -> ProductBuilder {
ProductBuilder::new(id)
}
}
```
## License
MIT

View File

@ -0,0 +1,28 @@
use heromodels::{BaseModel, Comment, User, ModelBuilder};
fn main() {
println!("Hero Models - Basic Usage Example");
println!("================================");
// Create a new user using the fluent interface
let user = User::new(1)
.username("johndoe")
.email("john.doe@example.com")
.full_name("John Doe")
.build();
println!("Created user: {:?}", user);
println!("User ID: {}", user.get_id());
println!("User DB Prefix: {}", User::db_prefix());
// Create a comment for the user
let comment = Comment::new(1)
.user_id(2) // commenter's user ID
.content("This is a comment on the user")
.build();
println!("\nCreated comment: {:?}", comment);
println!("Comment ID: {}", comment.get_id());
println!("Comment DB Prefix: {}", Comment::db_prefix());
}

View File

@ -1,63 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::model::{BaseModel, BaseModelData, IndexKey};
/// Represents a comment on a model
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Comment {
/// Base model data
pub base_data: BaseModelData,
/// The ID of the user who created the comment
pub user_id: u32,
/// The ID of the model this comment is attached to
pub model_id: u32,
/// The type of model this comment is attached to
pub model_type: String,
/// The content of the comment
pub content: String,
}
impl Comment {
/// Create a new comment
pub fn new(id: u32, user_id: u32, model_id: u32, model_type: String, content: String) -> Self {
Self {
base_data: BaseModelData::new(id),
user_id,
model_id,
model_type,
content,
}
}
/// Update the comment content
pub fn update_content(&mut self, content: String) {
self.content = content;
self.base_data.update_modified();
}
}
impl BaseModel for Comment {
fn db_prefix() -> &'static str {
"comment"
}
fn get_id(&self) -> u32 {
self.base_data.id
}
fn db_keys(&self) -> Vec<IndexKey> {
vec![
IndexKey {
name: "user_id",
value: self.user_id.to_string(),
},
IndexKey {
name: "model_id",
value: format!("{}:{}", self.model_type, self.model_id),
},
]
}
}

View File

@ -0,0 +1,56 @@
use serde::{Deserialize, Serialize};
use crate::core::model::{BaseModel, BaseModelData, IndexKey, ModelBuilder};
use crate::impl_model_builder;
/// Represents a comment on a model
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Comment {
pub base_data: BaseModelData,
pub user_id: u32,
pub content: String,
}
impl Comment {
/// Create a new comment
pub fn new(id: u32) -> Self {
Self {
base_data: BaseModelData::new(id),
user_id: 0,
content: String::new(),
}
}
/// Set the user ID
pub fn user_id(mut self, id: u32) -> Self {
self.user_id = id;
self
}
/// Set the content
pub fn content(mut self, content: impl ToString) -> Self {
self.content = content.to_string();
self
}
}
impl BaseModel for Comment {
fn db_prefix() -> &'static str {
"comment"
}
fn get_id(&self) -> u32 {
self.base_data.id
}
fn db_keys(&self) -> Vec<IndexKey> {
vec![
IndexKey {
name: "user_id",
value: self.user_id.to_string(),
},
]
}
}
// Implement ModelBuilder for Comment
impl_model_builder!(Comment);

View File

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

@ -0,0 +1,7 @@
// 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;

View File

@ -100,7 +100,7 @@ impl BaseModelData {
/// Remove a comment from this model
pub fn remove_comment(&mut self, comment_id: u32) {
self.comments.retain(|&id| id != comment_id);
self.modified_at = chrono::Utc::now().timestamp();
self.update_modified();
}
/// Update the modified timestamp
@ -164,11 +164,29 @@ 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_export]
macro_rules! impl_base_model {
($type:ty, $prefix:expr) => {
impl BaseModel for $type {
impl $crate::core::model::BaseModel for $type {
fn db_prefix() -> &'static str {
$prefix
}
@ -178,4 +196,16 @@ macro_rules! impl_base_model {
}
}
};
}
/// 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

@ -1,47 +1,12 @@
//! # Hero Models
//!
//! A library for hero models with base model trait implementation.
//!
//! This crate provides a base model trait and implementation that other models can inherit from.
//! It also provides a Comment model that can be used to add comments to any model.
// Export core module
pub mod core;
pub mod model;
pub mod comment;
pub mod user;
// Export userexample module
pub mod userexample;
// Re-export key types for convenience
pub use model::{BaseModel, BaseModelData, IndexKey, impl_base_model};
pub use comment::Comment;
pub use user::User;
pub use core::{BaseModel, BaseModelData, IndexKey, ModelBuilder};
pub use core::Comment;
pub use userexample::User;
/// Example of how to use the heromodels crate
///
/// ```rust
/// use heromodels::{BaseModel, User, Comment};
///
/// // Create a new user
/// let mut user = User::new(
/// 1,
/// "johndoe".to_string(),
/// "john.doe@example.com".to_string(),
/// "John Doe".to_string()
/// );
///
/// // Create a comment for the user
/// let comment = Comment::new(
/// 1,
/// 2, // commenter's user ID
/// user.get_id(),
/// User::db_prefix().to_string(),
/// "This is a comment on the user".to_string()
/// );
///
/// // Add the comment to the user
/// user.base_data.add_comment(comment.get_id());
///
/// // Get the database prefix for the User model
/// assert_eq!(User::db_prefix(), "user");
///
/// // Get the database keys for the user
/// let keys = user.db_keys();
/// assert!(keys.iter().any(|k| k.name == "username" && k.value == "johndoe"));
// No need to re-export macros as they are already exported at the crate root

View File

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

View File

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

View File

@ -1,5 +1,6 @@
use serde::{Deserialize, Serialize};
use crate::model::{BaseModel, BaseModelData, IndexKey};
use crate::core::model::{BaseModel, BaseModelData, IndexKey, ModelBuilder};
use crate::impl_model_builder;
/// Represents a user in the system
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -22,38 +23,58 @@ pub struct User {
impl User {
/// Create a new user
pub fn new(id: u32, username: String, email: String, full_name: String) -> Self {
pub fn new(id: u32) -> Self {
Self {
base_data: BaseModelData::new(id),
username,
email,
full_name,
username: String::new(),
email: String::new(),
full_name: String::new(),
is_active: true,
}
}
/// Set the username
pub fn username(mut self, username: impl ToString) -> Self {
self.username = username.to_string();
self
}
/// Set the email
pub fn email(mut self, email: impl ToString) -> Self {
self.email = email.to_string();
self
}
/// Set the full name
pub fn full_name(mut self, full_name: impl ToString) -> Self {
self.full_name = full_name.to_string();
self
}
/// Set whether the user is active
pub fn is_active(mut self, is_active: bool) -> Self {
self.is_active = is_active;
self
}
/// Add a comment ID
pub fn add_comment(mut self, comment_id: u32) -> Self {
self.base_data.add_comment(comment_id);
self
}
/// Deactivate the user
pub fn deactivate(&mut self) {
self.is_active = false;
self.base_data.update_modified();
}
/// Activate the user
pub fn activate(&mut self) {
self.is_active = true;
self.base_data.update_modified();
}
/// Update user's email
pub fn update_email(&mut self, email: String) {
self.email = email;
self.base_data.update_modified();
}
/// Update user's full name
pub fn update_full_name(&mut self, full_name: String) {
self.full_name = full_name;
self.base_data.update_modified();
}
}
@ -83,4 +104,7 @@ impl BaseModel for User {
},
]
}
}
}
// Implement ModelBuilder for User
impl_model_builder!(User);