diff --git a/rear/src/admin/domain.rs b/rear/src/admin/domain.rs index 45f56f7..aa2e139 100644 --- a/rear/src/admin/domain.rs +++ b/rear/src/admin/domain.rs @@ -2,8 +2,8 @@ pub use config::AdminModelConfig; pub use dto::AdminApp; pub use dto::AdminModel; pub use repository::{ - AdminRepository, LookupKey, RepoInfo, RepositoryContext, RepositoryInfo, RepositoryItem, - RepositoryList, Widget, + AdminRepository, DynAdminRepository, LookupKey, RepoInfo, RepositoryContext, RepositoryInfo, + RepositoryItem, RepositoryList, Widget, }; mod auth { @@ -350,18 +350,32 @@ pub mod repository { } } - pub trait PrimaryKeyType { - fn key_as_string(&self) -> String; - fn key_from_string(&self, s: String) -> Self; + use std::any::Any; + use std::fmt::Debug; + + pub trait PrimaryKeyType: Any + Debug + Send + Sync { + fn as_any(&self) -> &dyn Any; } impl PrimaryKeyType for i64 { - fn key_as_string(&self) -> String { - self.to_string() + fn as_any(&self) -> &dyn Any { + self } + } - fn key_from_string(&self, s: String) -> Self { - s.parse::().unwrap() as i64 + impl PrimaryKeyType for String { + fn as_any(&self) -> &dyn Any { + self + } + } + + impl From for Box { + fn from(s: String) -> Box { + if let Ok(i) = s.parse::() { + Box::new(i) + } else { + Box::new(s) + } } } @@ -371,7 +385,7 @@ pub mod repository { async fn info(&self, context: &RepositoryContext) -> RepositoryInfo; async fn list(&self, context: &RepositoryContext) -> RepositoryList; - async fn get(&self, context: &RepositoryContext, id: Self::Key) -> Option; + async fn get(&self, context: &RepositoryContext, id: &Self::Key) -> Option; async fn create( &mut self, context: &RepositoryContext, @@ -380,76 +394,127 @@ pub mod repository { async fn update( &mut self, context: &RepositoryContext, - id: Self::Key, + id: &Self::Key, data: Value, ) -> Option; async fn replace( &mut self, context: &RepositoryContext, - id: Self::Key, + id: &Self::Key, data: Value, ) -> Option; - async fn delete(&mut self, context: &RepositoryContext, id: Self::Key) -> Option; + async fn delete(&mut self, context: &RepositoryContext, id: &Self::Key) -> Option; } - use std::any::Any; - - trait Key: Any { - fn as_any(&self) -> &dyn Any; - fn from_str(s: &str) -> Self - where - Self: Sized; - fn to_string(&self) -> String; + #[async_trait] + pub trait DynAdminRepository: Send + Sync { + async fn info(&self, context: &RepositoryContext) -> RepositoryInfo; + async fn list(&self, context: &RepositoryContext) -> RepositoryList; + async fn get( + &self, + context: &RepositoryContext, + id: &dyn PrimaryKeyType, + ) -> Option; + async fn create( + &mut self, + context: &RepositoryContext, + data: Value, + ) -> Option; + async fn update( + &mut self, + context: &RepositoryContext, + id: &dyn PrimaryKeyType, + data: Value, + ) -> Option; + async fn replace( + &mut self, + context: &RepositoryContext, + id: &dyn PrimaryKeyType, + data: Value, + ) -> Option; + async fn delete( + &mut self, + context: &RepositoryContext, + id: &dyn PrimaryKeyType, + ) -> Option; } - impl Key for T { - fn as_any(&self) -> &dyn Any { - self - } + pub struct AdminRepositoryWrapper { + inner: T, } - impl Key for String { - fn from_str(s: &str) -> Self { - s.to_string() - } - - fn to_string(&self) -> String { - self.clone() - } - } - - impl Key for i64 { - fn from_str(s: &str) -> Self { - s.parse().expect("Invalid i64 string") - } - - fn to_string(&self) -> String { - self.to_string() + impl AdminRepositoryWrapper { + pub fn new(inner: T) -> Self { + Self { inner } } } #[async_trait] - pub trait ErasedAdminRepository: Send + Sync { - async fn info(&self, context: &RepositoryContext) -> RepositoryInfo; - async fn list(&self, context: &RepositoryContext) -> RepositoryList; - async fn get(&self, context: &RepositoryContext, id: &dyn Key) -> Option; + impl DynAdminRepository for AdminRepositoryWrapper { + async fn info(&self, context: &RepositoryContext) -> RepositoryInfo { + self.inner.info(context).await + } + + async fn list(&self, context: &RepositoryContext) -> RepositoryList { + self.inner.list(context).await + } + + async fn get( + &self, + context: &RepositoryContext, + id: &dyn PrimaryKeyType, + ) -> Option { + if let Some(key) = id.as_any().downcast_ref::() { + self.inner.get(context, key).await + } else { + None + } + } + async fn create( &mut self, context: &RepositoryContext, data: Value, - ) -> Option; + ) -> Option { + self.inner.create(context, data).await + } + async fn update( &mut self, context: &RepositoryContext, - id: &dyn Key, + id: &dyn PrimaryKeyType, data: Value, - ) -> Option; + ) -> Option { + if let Some(key) = id.as_any().downcast_ref::() { + self.inner.update(context, key, data).await + } else { + None + } + } + async fn replace( &mut self, context: &RepositoryContext, - id: &dyn Key, + id: &dyn PrimaryKeyType, data: Value, - ) -> Option; - async fn delete(&mut self, context: &RepositoryContext, id: &dyn Key) -> Option; + ) -> Option { + if let Some(key) = id.as_any().downcast_ref::() { + self.inner.replace(context, key, data).await + } else { + None + } + } + + async fn delete( + &mut self, + context: &RepositoryContext, + id: &dyn PrimaryKeyType, + ) -> Option { + if let Some(key) = id.as_any().downcast_ref::() { + self.inner.delete(context, key).await + } else { + None + } + } } } diff --git a/rear/src/admin/state.rs b/rear/src/admin/state.rs index 5464eb7..6adf430 100644 --- a/rear/src/admin/state.rs +++ b/rear/src/admin/state.rs @@ -1,6 +1,7 @@ use tokio::sync::Mutex; -use super::domain::{AdminApp, AdminModel, AdminModelConfig, AdminRepository}; +use super::domain::repository::AdminRepositoryWrapper; +use super::domain::{AdminApp, AdminModel, AdminModelConfig, AdminRepository, DynAdminRepository}; use crate::service::templates::Templates; use std::collections::HashMap; use std::sync::Arc; @@ -17,7 +18,7 @@ pub struct AdminRegistry { base_path: String, apps: HashMap, models: HashMap, - repositories: HashMap>>, + repositories: HashMap>>, } impl AdminRegistry { @@ -113,6 +114,7 @@ impl AdminRegistry { repository: R, ) -> Result<(), String> { let model_key = self.register_model_config(model)?; + let repository = AdminRepositoryWrapper::new(repository); self.repositories .insert(model_key, Arc::new(Mutex::new(repository))); Ok(()) @@ -122,7 +124,7 @@ impl AdminRegistry { &self, app_key: &str, model_key: &str, - ) -> Result>, String> { + ) -> Result>, String> { let full_model_key = format!("{}.{}", app_key, model_key); if let Some(repo) = self.repositories.get(&full_model_key) { // Clone the Arc to return a reference to the repository diff --git a/rear/src/admin/views.rs b/rear/src/admin/views.rs index 2357653..23a3047 100644 --- a/rear/src/admin/views.rs +++ b/rear/src/admin/views.rs @@ -9,6 +9,7 @@ use crate::admin::domain::{AdminApp, AdminModel}; use crate::admin::state::AdminState; use serde::{Deserialize, Serialize}; +use super::domain::repository::PrimaryKeyType; use super::domain::{LookupKey, RepositoryInfo, RepositoryItem, RepositoryList}; #[derive(Serialize, Deserialize)] @@ -157,13 +158,13 @@ pub async fn view_item_details( let admin_model = registry .get_model(&app_key, &model_key) .expect("Admin Model not found?"); - let key: LookupKey = id.into(); + let key: Box = Box::new(id); AdminContext { base: base_template(&headers), available_apps: registry.get_apps(), item_info: Some(repo.info(&admin_model).await), item_list: repo.list(&admin_model).await, - item: repo.get(&admin_model, key).await, + item: repo.get(&admin_model, key.as_ref()).await, item_model: Some(admin_model), ..Default::default() } @@ -257,13 +258,13 @@ pub async fn change_item( let admin_model = registry .get_model(&app_key, &model_key) .expect("Admin Model not found?"); - let key: LookupKey = id.into(); + let key: Box = Box::new(id); AdminContext { base: base_template(&headers), available_apps: registry.get_apps(), item_info: Some(repo.info(&admin_model).await), item_list: repo.list(&admin_model).await, - item: repo.get(&admin_model, key).await, + item: repo.get(&admin_model, key.as_ref()).await, item_model: Some(admin_model), ..Default::default() } @@ -290,9 +291,9 @@ pub async fn update_item( let admin_model = registry .get_model(&app_key, &model_key) .expect("Admin Model not found?"); - let key: LookupKey = id.into(); + let key: Box = Box::new(id); - let result = repo.update(&admin_model, key, form).await; + let result = repo.update(&admin_model, key.as_ref(), form).await; AdminContext { base: base_template(&headers), diff --git a/src/admin_examples/empty_repository.rs b/src/admin_examples/empty_repository.rs index 114d1ec..05d08d5 100644 --- a/src/admin_examples/empty_repository.rs +++ b/src/admin_examples/empty_repository.rs @@ -8,6 +8,8 @@ impl Repository {} #[async_trait] impl AdminRepository for Repository { + type Key = i64; + async fn info(&self, _: &RepositoryContext) -> RepositoryInfo { RepoInfo { name: "My Empty Repository", @@ -33,7 +35,7 @@ impl AdminRepository for Repository { } // GET single item. - async fn get(&self, model: &RepositoryContext, id: LookupKey) -> Option { + async fn get(&self, model: &RepositoryContext, id: &Self::Key) -> Option { None } @@ -41,7 +43,7 @@ impl AdminRepository for Repository { async fn update( &mut self, model: &RepositoryContext, - id: LookupKey, + id: &Self::Key, data: Value, ) -> Option { None @@ -51,14 +53,14 @@ impl AdminRepository for Repository { async fn replace( &mut self, model: &RepositoryContext, - id: LookupKey, + id: &Self::Key, data: Value, ) -> Option { None } // DELETE single item. - async fn delete(&mut self, _: &RepositoryContext, id: LookupKey) -> Option { + async fn delete(&mut self, _: &RepositoryContext, id: &Self::Key) -> Option { None } } diff --git a/src/admin_examples/static_repository.rs b/src/admin_examples/static_repository.rs index 1ac82d0..2e90b78 100644 --- a/src/admin_examples/static_repository.rs +++ b/src/admin_examples/static_repository.rs @@ -36,6 +36,8 @@ impl MyStaticRepository { #[async_trait] impl AdminRepository for MyStaticRepository { + type Key = i64; + async fn info(&self, _: &RepositoryContext) -> RepositoryInfo { RepoInfo { name: "My Static Repository", @@ -48,15 +50,11 @@ impl AdminRepository for MyStaticRepository { //.set_widget("name", Widget::textarea().options(&[("disabled", "true")])) } - async fn get(&self, model: &RepositoryContext, id: LookupKey) -> Option { - if let LookupKey::Integer(id) = id { - let item = self.content.get(id - 1).cloned().unwrap(); - let id = item.get("id").unwrap(); - Some(model.build_item(&*id.to_string(), item)) - } else { - warn!("Got non-integer lookup key: {}", id); - None - } + async fn get(&self, model: &RepositoryContext, id: &Self::Key) -> Option { + let id = *id as usize; + let item = self.content.get(id - 1).cloned().unwrap(); + let id = item.get("id").unwrap(); + Some(model.build_item(&*id.to_string(), item)) } async fn list(&self, model: &RepositoryContext) -> RepositoryList { @@ -94,7 +92,7 @@ impl AdminRepository for MyStaticRepository { async fn update( &mut self, model: &RepositoryContext, - id: LookupKey, + id: &Self::Key, data: Value, ) -> Option { debug!("I would now update: {}, {}", id, data); @@ -102,10 +100,7 @@ impl AdminRepository for MyStaticRepository { // First, find the index of the item to update let item_index = self.content.iter().position(|item| { if let Some(item_id) = item.get("id") { - match id { - LookupKey::Integer(i) => item_id == &i, - LookupKey::String(ref s) => item_id == s, - } + item_id == id } else { false } @@ -127,21 +122,18 @@ impl AdminRepository for MyStaticRepository { async fn replace( &mut self, model: &RepositoryContext, - id: LookupKey, + id: &Self::Key, data: Value, ) -> Option { self.update(model, id, data).await } - async fn delete(&mut self, _: &RepositoryContext, id: LookupKey) -> Option { + async fn delete(&mut self, _: &RepositoryContext, id: &Self::Key) -> Option { debug!("Would delete: {}", id); let item_index = self.content.iter().position(|item| { if let Some(item_id) = item.get("id") { - match id { - LookupKey::Integer(i) => item_id == &i, - LookupKey::String(ref s) => item_id == s, - } + item_id == id } else { false } diff --git a/src/admin_examples/user_repository.rs b/src/admin_examples/user_repository.rs index 16a483c..f815cd8 100644 --- a/src/admin_examples/user_repository.rs +++ b/src/admin_examples/user_repository.rs @@ -19,6 +19,8 @@ impl UserRepository { #[async_trait] impl AdminRepository for UserRepository { + type Key = i64; + async fn info(&self, _: &RepositoryContext) -> RepositoryInfo { RepoInfo { name: "User", @@ -29,25 +31,24 @@ impl AdminRepository for UserRepository { .into() } - async fn get(&self, model: &RepositoryContext, id: LookupKey) -> Option { - if let LookupKey::Integer(id) = id { - let id: i32 = id as i32; // use try_into() instead. - let get_user = entity::User::find_by_id(id).one(&self.connection).await; - match get_user { - Ok(get_user) => { - if let Some(user) = get_user { - let id = user.id.to_string(); - match serde_json::to_value(&user) { - Ok(item) => { - return Some(model.build_item(&*id, item)); - } - Err(_) => return None, + async fn get(&self, model: &RepositoryContext, id: &Self::Key) -> Option { + let id: i32 = *id as i32; // use try_into() instead. + let get_user = entity::User::find_by_id(id).one(&self.connection).await; + match get_user { + Ok(get_user) => { + if let Some(user) = get_user { + let id = user.id.to_string(); + match serde_json::to_value(&user) { + Ok(item) => { + return Some(model.build_item(&*id, item)); } + Err(_) => return None, } } - Err(_) => return None, } + Err(_) => return None, } + None } @@ -94,58 +95,56 @@ impl AdminRepository for UserRepository { async fn update( &mut self, model: &RepositoryContext, - id: LookupKey, + id: &Self::Key, data: Value, ) -> Option { - if let LookupKey::Integer(id) = id { - let id: i32 = id as i32; // use try_into() instead. - let user: Option = entity::User::find_by_id(id) - .one(&self.connection) - .await - .unwrap(); - let mut user: entity::user::ActiveModel = user.unwrap().into(); + let id: i32 = *id as i32; + let user: Option = entity::User::find_by_id(id) + .one(&self.connection) + .await + .unwrap(); + let mut user: entity::user::ActiveModel = user.unwrap().into(); - // change values - if let Some(value) = data.get("username") { - if let Some(value) = value.as_str() { - user.username = Set(value.to_owned()); - } - } - - if let Some(value) = data.get("description") { - user.description = Set(value.as_str().map(|s| s.to_owned())); - } - - // update - if let Ok(user) = user.update(&self.connection).await { - let id = user.id.to_string(); - return Some(model.build_item(&*id, serde_json::to_value(&user).unwrap())); + // change values + if let Some(value) = data.get("username") { + if let Some(value) = value.as_str() { + user.username = Set(value.to_owned()); } } + + if let Some(value) = data.get("description") { + user.description = Set(value.as_str().map(|s| s.to_owned())); + } + + // update + if let Ok(user) = user.update(&self.connection).await { + let id = user.id.to_string(); + return Some(model.build_item(&*id, serde_json::to_value(&user).unwrap())); + } + None } async fn replace( &mut self, model: &RepositoryContext, - id: LookupKey, + id: &Self::Key, data: Value, ) -> Option { self.update(model, id, data).await } - async fn delete(&mut self, _: &RepositoryContext, id: LookupKey) -> Option { - if let LookupKey::Integer(id) = id { - let id: i32 = id as i32; // use try_into() instead. - let user: Option = entity::User::find_by_id(id) - .one(&self.connection) - .await - .unwrap(); - if let Some(user) = user { - let delete_result = user.delete(&self.connection).await.unwrap(); - debug!("deleted rows: {}", delete_result.rows_affected); - } + async fn delete(&mut self, _: &RepositoryContext, id: &Self::Key) -> Option { + let id: i32 = *id as i32; + let user: Option = entity::User::find_by_id(id) + .one(&self.connection) + .await + .unwrap(); + if let Some(user) = user { + let delete_result = user.delete(&self.connection).await.unwrap(); + debug!("deleted rows: {}", delete_result.rows_affected); } + None } } diff --git a/src/bin/generic_trait_type_erasure.rs b/src/bin/generic_trait_type_erasure.rs index 1ab1bfb..d6c9428 100644 --- a/src/bin/generic_trait_type_erasure.rs +++ b/src/bin/generic_trait_type_erasure.rs @@ -1,37 +1,142 @@ - - -pub trait Repository { - type Key; - - fn get_data(&self, key: Self::Key) -> String; - fn set_data(&self, key: Self::Key, value: String); -} - use std::any::Any; +use std::collections::HashMap; +use std::fmt::Debug; -trait Key: Any { +// Define the Key trait with Any and Debug for type erasure and debugging +pub trait Key: Any + Debug { fn as_any(&self) -> &dyn Any; - fn from_str(s: &str) -> Self where Self: Sized; - fn to_string(&self) -> String; } -impl Key for T { +// Implement Key for i32 +impl Key for i32 { fn as_any(&self) -> &dyn Any { self } } +// Implement Key for String impl Key for String { - fn from_str(s: &str) -> Self { - s.to_string() - } - - fn to_string(&self) -> String { - self.clone() + fn as_any(&self) -> &dyn Any { + self } } -pub trait RepositoryTypeErased { - fn get_data(&self, key: Key) -> String; - fn set_data(&self, key: Key, value: String); -} \ No newline at end of file +// Implement From for i32 +impl From for Box { + fn from(s: String) -> Box { + if let Ok(i) = s.parse::() { + Box::new(i) + } else { + Box::new(s) + } + } +} + +// Define the Repository trait with an associated type Key +pub trait Repository { + type Key: Key; + fn get_entry(&self, key: &Self::Key) -> String; +} + +// Define a helper trait for dynamic dispatch +pub trait DynRepository { + fn get_entry(&self, key: &dyn Key) -> String; +} + +// Define a type-erased wrapper for Repository +pub struct RepositoryWrapper { + inner: T, +} + +impl RepositoryWrapper { + pub fn new(inner: T) -> Self { + Self { inner } + } +} + +impl DynRepository for RepositoryWrapper { + fn get_entry(&self, key: &dyn Key) -> String { + if let Some(key) = key.as_any().downcast_ref::() { + self.inner.get_entry(key) + } else { + "Invalid Key Type".to_string() + } + } +} + +// Implement the Repository trait for IntRepository +pub struct IntRepository { + data: HashMap, +} + +impl IntRepository { + pub fn new() -> Self { + let mut data = HashMap::new(); + data.insert(1, "IntEntry1".to_string()); + data.insert(2, "IntEntry2".to_string()); + Self { data } + } +} + +impl Repository for IntRepository { + type Key = i32; + + fn get_entry(&self, key: &Self::Key) -> String { + self.data + .get(key) + .unwrap_or(&"Not Found".to_string()) + .clone() + } +} + +// Implement the Repository trait for StringRepository +pub struct StringRepository { + data: HashMap, +} + +impl StringRepository { + pub fn new() -> Self { + let mut data = HashMap::new(); + data.insert("one".to_string(), "StringEntry1".to_string()); + data.insert("two".to_string(), "StringEntry2".to_string()); + Self { data } + } +} + +impl Repository for StringRepository { + type Key = String; + + fn get_entry(&self, key: &Self::Key) -> String { + self.data + .get(key) + .unwrap_or(&"Not Found".to_string()) + .clone() + } +} + +fn main() { + let int_repo = IntRepository::new(); + let string_repo = StringRepository::new(); + + let repositories: Vec> = vec![ + Box::new(RepositoryWrapper::new(int_repo)), + Box::new(RepositoryWrapper::new(string_repo)), + ]; + + // Creating keys from strings using the From implementation + let keys: Vec> = vec![ + Box::new(1_i32), + Box::new("one".to_string()), + Box::new(99_i32), // non-existing int key + Box::new("unknown".to_string()), // non-existing string key + Box::from("123".to_string()), // i32 key from string + Box::from("hello".to_string()), // String key from string + ]; + + for key in keys { + for repo in &repositories { + let result = repo.get_entry(key.as_ref()); + println!("Result: {}", result); + } + } +}