Update macro to use #[index] attributes
- Also use proper types for index. - Update DB interface to be more flexible for index params Signed-off-by: Lee Smet <lee.smet@hotmail.com>
This commit is contained in:
parent
dc93518a35
commit
96a1ecd974
@ -1,5 +1,5 @@
|
|||||||
use heromodels::db::{Collection, Db};
|
use heromodels::db::{Collection, Db};
|
||||||
use heromodels::models::userexample::user::{IsActive, UserName};
|
use heromodels::models::userexample::user::index::{is_active, username};
|
||||||
use heromodels::models::{Comment, Model, User};
|
use heromodels::models::{Comment, Model, User};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -64,7 +64,7 @@ fn main() {
|
|||||||
let stored_users = db
|
let stored_users = db
|
||||||
.collection::<User>()
|
.collection::<User>()
|
||||||
.expect("can open user collection")
|
.expect("can open user collection")
|
||||||
.get::<UserName>("johndoe")
|
.get::<username, _>("johndoe")
|
||||||
.expect("can load stored user");
|
.expect("can load stored user");
|
||||||
|
|
||||||
assert_eq!(stored_users.len(), 1);
|
assert_eq!(stored_users.len(), 1);
|
||||||
@ -80,7 +80,7 @@ fn main() {
|
|||||||
let active_users = db
|
let active_users = db
|
||||||
.collection::<User>()
|
.collection::<User>()
|
||||||
.expect("can open user collection")
|
.expect("can open user collection")
|
||||||
.get::<IsActive>(&true)
|
.get::<is_active, _>(&true)
|
||||||
.expect("can load stored users");
|
.expect("can load stored users");
|
||||||
// We should have 2 active users
|
// We should have 2 active users
|
||||||
assert_eq!(active_users.len(), 2);
|
assert_eq!(active_users.len(), 2);
|
||||||
@ -95,14 +95,14 @@ fn main() {
|
|||||||
let active_users = db
|
let active_users = db
|
||||||
.collection::<User>()
|
.collection::<User>()
|
||||||
.expect("can open user collection")
|
.expect("can open user collection")
|
||||||
.get::<IsActive>(&true)
|
.get::<is_active, _>(&true)
|
||||||
.expect("can load stored users");
|
.expect("can load stored users");
|
||||||
assert_eq!(active_users.len(), 1);
|
assert_eq!(active_users.len(), 1);
|
||||||
// And verify we still have 2 inactive users
|
// And verify we still have 2 inactive users
|
||||||
let inactive_users = db
|
let inactive_users = db
|
||||||
.collection::<User>()
|
.collection::<User>()
|
||||||
.expect("can open user collection")
|
.expect("can open user collection")
|
||||||
.get::<IsActive>(&false)
|
.get::<is_active, _>(&false)
|
||||||
.expect("can load stored users");
|
.expect("can load stored users");
|
||||||
assert_eq!(inactive_users.len(), 2);
|
assert_eq!(inactive_users.len(), 2);
|
||||||
|
|
||||||
|
@ -1,37 +1,37 @@
|
|||||||
use heromodels::model;
|
use heromodels::model;
|
||||||
use heromodels::models::core::model::{BaseModelData, Model, Index, IndexKey};
|
use heromodels::models::core::model::{BaseModelData, Model};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
// Define a custom attribute for indexing
|
// Define a custom attribute for indexing
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[model]
|
#[model]
|
||||||
pub struct CustomUser {
|
pub struct CustomUser {
|
||||||
pub base_data: BaseModelData,
|
pub base_data: BaseModelData,
|
||||||
|
|
||||||
// Mark fields for indexing with a comment
|
// Mark fields for indexing with attributes
|
||||||
// #[index]
|
#[index]
|
||||||
pub login: String,
|
pub login: String,
|
||||||
|
|
||||||
// #[index]
|
#[index]
|
||||||
pub is_active: bool,
|
pub is_active: bool,
|
||||||
|
|
||||||
pub full_name: String,
|
pub full_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("Hero Models - Custom Model Example");
|
println!("Hero Models - Custom Model Example");
|
||||||
println!("==================================");
|
println!("==================================");
|
||||||
|
|
||||||
// Example usage of the generated implementation
|
// Example usage of the generated implementation
|
||||||
println!("CustomUser DB Prefix: {}", CustomUser::db_prefix());
|
println!("CustomUser DB Prefix: {}", CustomUser::db_prefix());
|
||||||
|
|
||||||
let user = CustomUser {
|
let user = CustomUser {
|
||||||
base_data: BaseModelData::new(1),
|
base_data: BaseModelData::new(1),
|
||||||
login: "johndoe".to_string(),
|
login: "johndoe".to_string(),
|
||||||
is_active: true,
|
is_active: true,
|
||||||
full_name: "John Doe".to_string(),
|
full_name: "John Doe".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("\nCustomUser ID: {}", user.get_id());
|
println!("\nCustomUser ID: {}", user.get_id());
|
||||||
println!("CustomUser DB Keys: {:?}", user.db_keys());
|
println!("CustomUser DB Keys: {:?}", user.db_keys());
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use serde::{Serialize, Deserialize};
|
|||||||
pub struct SimpleUser {
|
pub struct SimpleUser {
|
||||||
pub base_data: BaseModelData,
|
pub base_data: BaseModelData,
|
||||||
|
|
||||||
/// @index
|
#[index]
|
||||||
pub login: String,
|
pub login: String,
|
||||||
|
|
||||||
pub full_name: String,
|
pub full_name: String,
|
||||||
@ -20,10 +20,10 @@ pub struct SimpleUser {
|
|||||||
pub struct CustomUser {
|
pub struct CustomUser {
|
||||||
pub base_data: BaseModelData,
|
pub base_data: BaseModelData,
|
||||||
|
|
||||||
/// @index(name = "user_name", key_type = "str")
|
#[index(name = "user_name")]
|
||||||
pub login_name: String,
|
pub login_name: String,
|
||||||
|
|
||||||
/// @index(key_type = "bool")
|
#[index]
|
||||||
pub is_active: bool,
|
pub is_active: bool,
|
||||||
|
|
||||||
pub full_name: String,
|
pub full_name: String,
|
||||||
|
68
heromodels/heromodels-derive/Cargo.lock
generated
Normal file
68
heromodels/heromodels-derive/Cargo.lock
generated
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heromodels-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "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 = "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"
|
@ -11,4 +11,7 @@ proc-macro = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
syn = { version = "2.0", features = ["full", "extra-traits", "parsing"] }
|
syn = { version = "2.0", features = ["full", "extra-traits", "parsing"] }
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
@ -1,6 +1,6 @@
|
|||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::{quote, format_ident};
|
use quote::{format_ident, quote};
|
||||||
use syn::{parse_macro_input, DeriveInput, Data, Fields};
|
use syn::{Data, DeriveInput, Fields, parse_macro_input};
|
||||||
|
|
||||||
/// Convert a string to snake_case
|
/// Convert a string to snake_case
|
||||||
fn to_snake_case(s: &str) -> String {
|
fn to_snake_case(s: &str) -> String {
|
||||||
@ -15,144 +15,183 @@ fn to_snake_case(s: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a string to PascalCase
|
/// Convert a string to PascalCase
|
||||||
fn to_pascal_case(s: &str) -> String {
|
// fn to_pascal_case(s: &str) -> String {
|
||||||
let mut result = String::new();
|
// let mut result = String::new();
|
||||||
let mut capitalize_next = true;
|
// let mut capitalize_next = true;
|
||||||
|
//
|
||||||
for c in s.chars() {
|
// for c in s.chars() {
|
||||||
if c == '_' {
|
// if c == '_' {
|
||||||
capitalize_next = true;
|
// capitalize_next = true;
|
||||||
} else if capitalize_next {
|
// } else if capitalize_next {
|
||||||
result.push(c.to_uppercase().next().unwrap());
|
// result.push(c.to_uppercase().next().unwrap());
|
||||||
capitalize_next = false;
|
// capitalize_next = false;
|
||||||
} else {
|
// } else {
|
||||||
result.push(c);
|
// result.push(c);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
result
|
// result
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
/// Implements the Model trait and generates Index trait implementations for fields marked with #[index].
|
/// Implements the Model trait and generates Index trait implementations for fields marked with #[index].
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn model(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn model(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
// Parse the input tokens into a syntax tree
|
// Parse the input tokens into a syntax tree
|
||||||
let input = parse_macro_input!(item as DeriveInput);
|
let mut input = parse_macro_input!(item as DeriveInput);
|
||||||
|
|
||||||
// Extract struct name
|
// Extract struct name
|
||||||
let struct_name = &input.ident;
|
let struct_name = &input.ident;
|
||||||
|
|
||||||
// Convert struct name to snake_case for db_prefix
|
// Convert struct name to snake_case for db_prefix
|
||||||
let name_str = struct_name.to_string();
|
let name_str = struct_name.to_string();
|
||||||
let db_prefix = to_snake_case(&name_str);
|
let db_prefix = to_snake_case(&name_str);
|
||||||
|
|
||||||
// Extract fields with /// @index doc comment
|
// Extract fields with #[index] attribute
|
||||||
let mut indexed_fields = Vec::new();
|
let mut indexed_fields = Vec::new();
|
||||||
|
let mut custom_index_names = std::collections::HashMap::new();
|
||||||
if let Data::Struct(data_struct) = &input.data {
|
|
||||||
if let Fields::Named(fields_named) = &data_struct.fields {
|
if let Data::Struct(ref mut data_struct) = input.data {
|
||||||
for field in &fields_named.named {
|
if let Fields::Named(ref mut fields_named) = data_struct.fields {
|
||||||
for attr in &field.attrs {
|
for field in &mut fields_named.named {
|
||||||
if attr.path().is_ident("doc") {
|
let mut attr_idx = None;
|
||||||
let meta = attr.meta.clone().try_into().unwrap();
|
for (i, attr) in field.attrs.iter().enumerate() {
|
||||||
if let syn::Meta::NameValue(name_value) = meta {
|
if attr.path().is_ident("index") {
|
||||||
if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit_str), .. }) = name_value.value {
|
attr_idx = Some(i);
|
||||||
let doc_str = lit_str.value();
|
if let Some(ref field_name) = field.ident {
|
||||||
if doc_str.trim().starts_with("@index") {
|
// Check if the attribute has parameters
|
||||||
if let Some(field_name) = &field.ident {
|
let mut custom_name = None;
|
||||||
indexed_fields.push(field_name);
|
|
||||||
|
// Parse attribute arguments if any
|
||||||
|
let meta = attr.meta.clone();
|
||||||
|
if let syn::Meta::List(list) = meta {
|
||||||
|
if let Ok(nested) = list.parse_args_with(syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated) {
|
||||||
|
for meta in nested {
|
||||||
|
if let syn::Meta::NameValue(name_value) = meta {
|
||||||
|
if name_value.path.is_ident("name") {
|
||||||
|
if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit_str), .. }) = name_value.value {
|
||||||
|
custom_name = Some(lit_str.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
indexed_fields.push((field_name.clone(), field.ty.clone()));
|
||||||
|
|
||||||
|
if let Some(name) = custom_name {
|
||||||
|
custom_index_names.insert(field_name.to_string(), name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(idx) = attr_idx {
|
||||||
|
field.attrs.remove(idx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate Model trait implementation
|
// Generate Model trait implementation
|
||||||
let db_keys_impl = if indexed_fields.is_empty() {
|
let db_keys_impl = if indexed_fields.is_empty() {
|
||||||
quote! {
|
quote! {
|
||||||
fn db_keys(&self) -> Vec<IndexKey> {
|
fn db_keys(&self) -> Vec<crate::models::IndexKey> {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let field_keys = indexed_fields.iter().map(|field_name| {
|
let field_keys = indexed_fields.iter().map(|(field_name, _)| {
|
||||||
let name_str = field_name.to_string();
|
let name_str = custom_index_names
|
||||||
|
.get(&field_name.to_string())
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(field_name.to_string());
|
||||||
quote! {
|
quote! {
|
||||||
IndexKey {
|
crate::models::IndexKey {
|
||||||
name: #name_str,
|
name: #name_str,
|
||||||
value: self.#field_name.to_string(),
|
value: self.#field_name.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
fn db_keys(&self) -> Vec<IndexKey> {
|
fn db_keys(&self) -> Vec<crate::models::IndexKey> {
|
||||||
vec![
|
vec![
|
||||||
#(#field_keys),*
|
#(#field_keys),*
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let model_impl = quote! {
|
let model_impl = quote! {
|
||||||
impl Model for #struct_name {
|
impl crate::models::Model for #struct_name {
|
||||||
fn db_prefix() -> &'static str {
|
fn db_prefix() -> &'static str {
|
||||||
#db_prefix
|
#db_prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_id(&self) -> u32 {
|
fn get_id(&self) -> u32 {
|
||||||
self.base_data.id
|
self.base_data.id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base_data_mut(&mut self) -> &mut BaseModelData {
|
fn base_data_mut(&mut self) -> &mut crate::models::BaseModelData {
|
||||||
&mut self.base_data
|
&mut self.base_data
|
||||||
}
|
}
|
||||||
|
|
||||||
#db_keys_impl
|
#db_keys_impl
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate Index trait implementations
|
// Generate Index trait implementations
|
||||||
let mut index_impls = proc_macro2::TokenStream::new();
|
let mut index_impls = proc_macro2::TokenStream::new();
|
||||||
|
|
||||||
for field_name in &indexed_fields {
|
for (field_name, field_type) in &indexed_fields {
|
||||||
let name_str = field_name.to_string();
|
let name_str = field_name.to_string();
|
||||||
|
|
||||||
|
// Get custom index name if specified, otherwise use field name
|
||||||
|
let index_key = match custom_index_names.get(&name_str) {
|
||||||
|
Some(custom_name) => custom_name.clone(),
|
||||||
|
None => name_str.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
// Convert field name to PascalCase for struct name
|
// Convert field name to PascalCase for struct name
|
||||||
let struct_name_str = to_pascal_case(&name_str);
|
// let struct_name_str = to_pascal_case(&name_str);
|
||||||
let index_struct_name = format_ident!("{}", struct_name_str);
|
// let index_struct_name = format_ident!("{}", struct_name_str);
|
||||||
|
let index_struct_name = format_ident!("{}", &name_str);
|
||||||
|
|
||||||
// Default to str for key type
|
// Default to str for key type
|
||||||
let index_impl = quote! {
|
let index_impl = quote! {
|
||||||
pub struct #index_struct_name;
|
pub struct #index_struct_name;
|
||||||
|
|
||||||
impl Index for #index_struct_name {
|
impl crate::models::Index for #index_struct_name {
|
||||||
type Model = #struct_name;
|
type Model = super::#struct_name;
|
||||||
type Key = str;
|
type Key = #field_type;
|
||||||
|
|
||||||
fn key() -> &'static str {
|
fn key() -> &'static str {
|
||||||
#name_str
|
#index_key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
index_impls.extend(index_impl);
|
index_impls.extend(index_impl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !index_impls.is_empty() {
|
||||||
|
index_impls = quote! {
|
||||||
|
pub mod index {
|
||||||
|
#index_impls
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Combine the original struct with the generated implementations
|
// Combine the original struct with the generated implementations
|
||||||
let expanded = quote! {
|
let expanded = quote! {
|
||||||
#input
|
#input
|
||||||
|
|
||||||
#model_impl
|
#model_impl
|
||||||
|
|
||||||
#index_impls
|
#index_impls
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return the generated code
|
// Return the generated code
|
||||||
expanded.into()
|
expanded.into()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,48 @@
|
|||||||
use heromodels::model;
|
use heromodels_derive::model;
|
||||||
use heromodels::models::core::model::{BaseModelData, Model, Index};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
// Define the necessary structs and traits for testing
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct BaseModelData {
|
||||||
|
pub id: u32,
|
||||||
|
pub created_at: i64,
|
||||||
|
pub modified_at: i64,
|
||||||
|
pub comments: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BaseModelData {
|
||||||
|
pub fn new(id: u32) -> Self {
|
||||||
|
let now = 1000; // Mock timestamp
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
created_at: now,
|
||||||
|
modified_at: now,
|
||||||
|
comments: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct IndexKey {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Model: std::fmt::Debug + Clone {
|
||||||
|
fn db_prefix() -> &'static str;
|
||||||
|
fn get_id(&self) -> u32;
|
||||||
|
fn base_data_mut(&mut self) -> &mut BaseModelData;
|
||||||
|
fn db_keys(&self) -> Vec<IndexKey>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Index {
|
||||||
|
type Model: Model;
|
||||||
|
type Key: ?Sized;
|
||||||
|
fn key() -> &'static str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test struct using the model macro
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[model]
|
#[model]
|
||||||
struct TestUser {
|
struct TestUser {
|
||||||
base_data: BaseModelData,
|
base_data: BaseModelData,
|
||||||
@ -12,6 +54,19 @@ struct TestUser {
|
|||||||
is_active: bool,
|
is_active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test struct with custom index name
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[model]
|
||||||
|
struct TestUserWithCustomIndex {
|
||||||
|
base_data: BaseModelData,
|
||||||
|
|
||||||
|
#[index(name = "custom_username")]
|
||||||
|
username: String,
|
||||||
|
|
||||||
|
#[index]
|
||||||
|
is_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic_model() {
|
fn test_basic_model() {
|
||||||
assert_eq!(TestUser::db_prefix(), "test_user");
|
assert_eq!(TestUser::db_prefix(), "test_user");
|
||||||
@ -28,4 +83,24 @@ fn test_basic_model() {
|
|||||||
assert_eq!(keys[0].value, "test");
|
assert_eq!(keys[0].value, "test");
|
||||||
assert_eq!(keys[1].name, "is_active");
|
assert_eq!(keys[1].name, "is_active");
|
||||||
assert_eq!(keys[1].value, "true");
|
assert_eq!(keys[1].value, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_custom_index_name() {
|
||||||
|
let user = TestUserWithCustomIndex {
|
||||||
|
base_data: BaseModelData::new(1),
|
||||||
|
username: "test".to_string(),
|
||||||
|
is_active: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check that the Username struct uses the custom index name
|
||||||
|
assert_eq!(Username::key(), "custom_username");
|
||||||
|
|
||||||
|
// Check that the db_keys method returns the correct keys
|
||||||
|
let keys = user.db_keys();
|
||||||
|
assert_eq!(keys.len(), 2);
|
||||||
|
assert_eq!(keys[0].name, "custom_username");
|
||||||
|
assert_eq!(keys[0].value, "test");
|
||||||
|
assert_eq!(keys[1].name, "is_active");
|
||||||
|
assert_eq!(keys[1].value, "true");
|
||||||
}
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
use crate::models::{Index, Model};
|
use crate::models::{Index, Model};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -22,9 +24,11 @@ where
|
|||||||
type Error: std::fmt::Debug;
|
type Error: std::fmt::Debug;
|
||||||
|
|
||||||
/// Get all items where the given index field is equal to key.
|
/// Get all items where the given index field is equal to key.
|
||||||
fn get<I>(&self, key: &I::Key) -> Result<Vec<V>, Error<Self::Error>>
|
fn get<I, Q>(&self, key: &Q) -> Result<Vec<V>, Error<Self::Error>>
|
||||||
where
|
where
|
||||||
I: Index<Model = V>;
|
I: Index<Model = V>,
|
||||||
|
I::Key: Borrow<Q>,
|
||||||
|
Q: ToString + ?Sized;
|
||||||
|
|
||||||
/// Get an object from its ID. This does not use an index lookup
|
/// Get an object from its ID. This does not use an index lookup
|
||||||
fn get_by_id(&self, id: u32) -> Result<Option<V>, Error<Self::Error>>;
|
fn get_by_id(&self, id: u32) -> Result<Option<V>, Error<Self::Error>>;
|
||||||
@ -33,9 +37,11 @@ where
|
|||||||
fn set(&self, value: &V) -> Result<(), Error<Self::Error>>;
|
fn set(&self, value: &V) -> Result<(), Error<Self::Error>>;
|
||||||
|
|
||||||
/// Delete all items from the db with a given index.
|
/// Delete all items from the db with a given index.
|
||||||
fn delete<I>(&self, key: &I::Key) -> Result<(), Error<Self::Error>>
|
fn delete<I, Q>(&self, key: &Q) -> Result<(), Error<Self::Error>>
|
||||||
where
|
where
|
||||||
I: Index<Model = V>;
|
I: Index<Model = V>,
|
||||||
|
I::Key: Borrow<Q>,
|
||||||
|
Q: ToString + ?Sized;
|
||||||
|
|
||||||
/// Delete an object with a given ID
|
/// Delete an object with a given ID
|
||||||
fn delete_by_id(&self, id: u32) -> Result<(), Error<Self::Error>>;
|
fn delete_by_id(&self, id: u32) -> Result<(), Error<Self::Error>>;
|
||||||
|
@ -4,6 +4,7 @@ use serde::Deserialize;
|
|||||||
use crate::models::{Index, Model};
|
use crate::models::{Index, Model};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
borrow::Borrow,
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
@ -42,9 +43,11 @@ where
|
|||||||
{
|
{
|
||||||
type Error = tst::Error;
|
type Error = tst::Error;
|
||||||
|
|
||||||
fn get<I>(&self, key: &I::Key) -> Result<Vec<M>, super::Error<Self::Error>>
|
fn get<I, Q>(&self, key: &Q) -> Result<Vec<M>, super::Error<Self::Error>>
|
||||||
where
|
where
|
||||||
I: Index<Model = M>,
|
I: Index<Model = M>,
|
||||||
|
I::Key: Borrow<Q>,
|
||||||
|
Q: ToString + ?Sized,
|
||||||
{
|
{
|
||||||
let mut index_db = self.index.lock().expect("can lock index DB");
|
let mut index_db = self.index.lock().expect("can lock index DB");
|
||||||
let index_key = Self::index_key(M::db_prefix(), I::key(), &key.to_string());
|
let index_key = Self::index_key(M::db_prefix(), I::key(), &key.to_string());
|
||||||
@ -140,9 +143,11 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete<I>(&self, key: &I::Key) -> Result<(), super::Error<Self::Error>>
|
fn delete<I, Q>(&self, key: &Q) -> Result<(), super::Error<Self::Error>>
|
||||||
where
|
where
|
||||||
I: Index<Model = M>,
|
I: Index<Model = M>,
|
||||||
|
I::Key: Borrow<Q>,
|
||||||
|
Q: ToString + ?Sized,
|
||||||
{
|
{
|
||||||
let mut index_db = self.index.lock().expect("can lock index db");
|
let mut index_db = self.index.lock().expect("can lock index db");
|
||||||
let key = Self::index_key(M::db_prefix(), I::key(), &key.to_string());
|
let key = Self::index_key(M::db_prefix(), I::key(), &key.to_string());
|
||||||
|
@ -1,22 +1,27 @@
|
|||||||
use crate::models::core::model::{BaseModelData, Index, IndexKey, Model};
|
use crate::models::core::model::BaseModelData;
|
||||||
|
use heromodels_derive::model;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Represents a user in the system
|
/// Represents a user in the system
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[model]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
/// Base model data
|
/// Base model data
|
||||||
pub base_data: BaseModelData,
|
pub base_data: BaseModelData,
|
||||||
|
|
||||||
/// User's username
|
/// User's username
|
||||||
|
#[index]
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
|
||||||
/// User's email address
|
/// User's email address
|
||||||
|
#[index]
|
||||||
pub email: String,
|
pub email: String,
|
||||||
|
|
||||||
/// User's full name
|
/// User's full name
|
||||||
pub full_name: String,
|
pub full_name: String,
|
||||||
|
|
||||||
/// Whether the user is active
|
/// Whether the user is active
|
||||||
|
#[index(name = "ac")]
|
||||||
pub is_active: bool,
|
pub is_active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,70 +83,70 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Implement the Model trait for User
|
// Implement the Model trait for User
|
||||||
impl Model for User {
|
// impl Model for User {
|
||||||
fn db_prefix() -> &'static str {
|
// fn db_prefix() -> &'static str {
|
||||||
"user"
|
// "user"
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fn get_id(&self) -> u32 {
|
// fn get_id(&self) -> u32 {
|
||||||
self.base_data.id
|
// self.base_data.id
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
//WHY?
|
// //WHY?
|
||||||
fn base_data_mut(&mut self) -> &mut BaseModelData {
|
// fn base_data_mut(&mut self) -> &mut BaseModelData {
|
||||||
&mut self.base_data
|
// &mut self.base_data
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fn db_keys(&self) -> Vec<IndexKey> {
|
// fn db_keys(&self) -> Vec<IndexKey> {
|
||||||
vec![
|
// vec![
|
||||||
IndexKey {
|
// IndexKey {
|
||||||
name: "username",
|
// name: "username",
|
||||||
value: self.username.clone(),
|
// value: self.username.clone(),
|
||||||
},
|
// },
|
||||||
IndexKey {
|
// IndexKey {
|
||||||
name: "email",
|
// name: "email",
|
||||||
value: self.email.clone(),
|
// value: self.email.clone(),
|
||||||
},
|
// },
|
||||||
IndexKey {
|
// IndexKey {
|
||||||
name: "is_active",
|
// name: "is_active",
|
||||||
value: self.is_active.to_string(),
|
// value: self.is_active.to_string(),
|
||||||
},
|
// },
|
||||||
]
|
// ]
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// Marker structs for indexed fields
|
// // Marker structs for indexed fields
|
||||||
|
//
|
||||||
pub struct UserName;
|
// pub struct UserName;
|
||||||
pub struct Email;
|
// pub struct Email;
|
||||||
pub struct IsActive;
|
// pub struct IsActive;
|
||||||
|
//
|
||||||
impl Index for UserName {
|
// impl Index for UserName {
|
||||||
type Model = User;
|
// type Model = User;
|
||||||
|
//
|
||||||
type Key = str;
|
// type Key = str;
|
||||||
|
//
|
||||||
fn key() -> &'static str {
|
// fn key() -> &'static str {
|
||||||
"username"
|
// "username"
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
impl Index for Email {
|
// impl Index for Email {
|
||||||
type Model = User;
|
// type Model = User;
|
||||||
|
//
|
||||||
type Key = str;
|
// type Key = str;
|
||||||
|
//
|
||||||
fn key() -> &'static str {
|
// fn key() -> &'static str {
|
||||||
"email"
|
// "email"
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
impl Index for IsActive {
|
// impl Index for IsActive {
|
||||||
type Model = User;
|
// type Model = User;
|
||||||
|
//
|
||||||
type Key = bool;
|
// type Key = bool;
|
||||||
|
//
|
||||||
fn key() -> &'static str {
|
// fn key() -> &'static str {
|
||||||
"is_active"
|
// "is_active"
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
Loading…
Reference in New Issue
Block a user