Implement Associated Type for Keys #3
@ -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, RepoInfo, RepositoryContext, RepositoryInfo,
|
||||
RepositoryItem, RepositoryList, Widget,
|
||||
};
|
||||
|
||||
mod auth {
|
||||
@ -54,6 +54,8 @@ pub mod repository {
|
||||
use async_trait::async_trait;
|
||||
use serde::{Serialize, Serializer};
|
||||
use serde_json::Value;
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::vec::IntoIter;
|
||||
|
||||
pub type RepositoryContext = AdminModel;
|
||||
@ -179,48 +181,6 @@ pub mod repository {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum LookupKey {
|
||||
Integer(usize),
|
||||
String(String),
|
||||
}
|
||||
|
||||
// Note that LookupKey auto converts to integer.
|
||||
impl From<String> for LookupKey {
|
||||
fn from(s: String) -> Self {
|
||||
if let Ok(int_key) = s.parse::<usize>() {
|
||||
LookupKey::Integer(int_key)
|
||||
} else {
|
||||
LookupKey::String(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for LookupKey {
|
||||
fn from(s: &str) -> Self {
|
||||
if let Ok(int_key) = s.parse::<usize>() {
|
||||
LookupKey::Integer(int_key)
|
||||
} else {
|
||||
LookupKey::String(s.to_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for LookupKey {
|
||||
fn from(i: usize) -> Self {
|
||||
LookupKey::Integer(i)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LookupKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LookupKey::Integer(i) => write!(f, "{}", i),
|
||||
LookupKey::String(s) => write!(f, "{}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct RepositoryItem {
|
||||
pub fields: Value,
|
||||
@ -350,11 +310,41 @@ pub mod repository {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PrimaryKeyType: Any + Debug + Send + Sync {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
impl PrimaryKeyType for i64 {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimaryKeyType for String {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Box<dyn PrimaryKeyType> {
|
||||
fn from(s: String) -> Box<dyn PrimaryKeyType> {
|
||||
if let Ok(i) = s.parse::<i64>() {
|
||||
Box::new(i)
|
||||
} else {
|
||||
Box::new(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait AdminRepository: Send + Sync {
|
||||
type Key: PrimaryKeyType;
|
||||
|
||||
fn key_from_string(&self, s: String) -> Option<Self::Key>;
|
||||
|
||||
async fn info(&self, context: &RepositoryContext) -> RepositoryInfo;
|
||||
async fn list(&self, context: &RepositoryContext) -> RepositoryList;
|
||||
async fn get(&self, context: &RepositoryContext, id: LookupKey) -> Option<RepositoryItem>;
|
||||
async fn get(&self, context: &RepositoryContext, id: &Self::Key) -> Option<RepositoryItem>;
|
||||
async fn create(
|
||||
&mut self,
|
||||
context: &RepositoryContext,
|
||||
@ -363,15 +353,140 @@ pub mod repository {
|
||||
async fn update(
|
||||
&mut self,
|
||||
context: &RepositoryContext,
|
||||
id: LookupKey,
|
||||
id: &Self::Key,
|
||||
data: Value,
|
||||
) -> Option<RepositoryItem>;
|
||||
async fn replace(
|
||||
&mut self,
|
||||
context: &RepositoryContext,
|
||||
id: LookupKey,
|
||||
id: &Self::Key,
|
||||
data: Value,
|
||||
) -> Option<RepositoryItem>;
|
||||
async fn delete(&mut self, context: &RepositoryContext, id: LookupKey) -> Option<Value>;
|
||||
async fn delete(&mut self, context: &RepositoryContext, id: &Self::Key) -> Option<Value>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait DynAdminRepository: Send + Sync {
|
||||
fn key_from_string(&self, s: String) -> Option<Box<dyn PrimaryKeyType>>;
|
||||
async fn info(&self, context: &RepositoryContext) -> RepositoryInfo;
|
||||
async fn list(&self, context: &RepositoryContext) -> RepositoryList;
|
||||
async fn get(
|
||||
&self,
|
||||
context: &RepositoryContext,
|
||||
id: &dyn PrimaryKeyType,
|
||||
) -> Option<RepositoryItem>;
|
||||
async fn create(
|
||||
&mut self,
|
||||
context: &RepositoryContext,
|
||||
data: Value,
|
||||
) -> Option<RepositoryItem>;
|
||||
async fn update(
|
||||
&mut self,
|
||||
context: &RepositoryContext,
|
||||
id: &dyn PrimaryKeyType,
|
||||
data: Value,
|
||||
) -> Option<RepositoryItem>;
|
||||
async fn replace(
|
||||
&mut self,
|
||||
context: &RepositoryContext,
|
||||
id: &dyn PrimaryKeyType,
|
||||
data: Value,
|
||||
) -> Option<RepositoryItem>;
|
||||
async fn delete(
|
||||
&mut self,
|
||||
context: &RepositoryContext,
|
||||
id: &dyn PrimaryKeyType,
|
||||
) -> Option<Value>;
|
||||
}
|
||||
|
||||
pub struct AdminRepositoryWrapper<T: AdminRepository> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: AdminRepository> AdminRepositoryWrapper<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
fn key_from_string(&self, s: String) -> Option<<T as AdminRepository>::Key> {
|
||||
self.inner.key_from_string(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: AdminRepository> DynAdminRepository for AdminRepositoryWrapper<T> {
|
||||
fn key_from_string(&self, s: String) -> Option<Box<dyn PrimaryKeyType>> {
|
||||
if let Some(key) = self.inner.key_from_string(s) {
|
||||
Some(Box::new(key))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
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<RepositoryItem> {
|
||||
if let Some(key) = id.as_any().downcast_ref::<T::Key>() {
|
||||
self.inner.get(context, key).await
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn create(
|
||||
&mut self,
|
||||
context: &RepositoryContext,
|
||||
data: Value,
|
||||
) -> Option<RepositoryItem> {
|
||||
self.inner.create(context, data).await
|
||||
}
|
||||
|
||||
async fn update(
|
||||
&mut self,
|
||||
context: &RepositoryContext,
|
||||
id: &dyn PrimaryKeyType,
|
||||
data: Value,
|
||||
) -> Option<RepositoryItem> {
|
||||
if let Some(key) = id.as_any().downcast_ref::<T::Key>() {
|
||||
self.inner.update(context, key, data).await
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn replace(
|
||||
&mut self,
|
||||
context: &RepositoryContext,
|
||||
id: &dyn PrimaryKeyType,
|
||||
data: Value,
|
||||
) -> Option<RepositoryItem> {
|
||||
if let Some(key) = id.as_any().downcast_ref::<T::Key>() {
|
||||
self.inner.replace(context, key, data).await
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete(
|
||||
&mut self,
|
||||
context: &RepositoryContext,
|
||||
id: &dyn PrimaryKeyType,
|
||||
) -> Option<Value> {
|
||||
if let Some(key) = id.as_any().downcast_ref::<T::Key>() {
|
||||
self.inner.delete(context, key).await
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<String, internal::AdminApp>,
|
||||
models: HashMap<String, internal::AdminModel>,
|
||||
repositories: HashMap<String, Arc<Mutex<dyn AdminRepository>>>,
|
||||
repositories: HashMap<String, Arc<Mutex<dyn DynAdminRepository>>>,
|
||||
}
|
||||
|
||||
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<Arc<Mutex<dyn AdminRepository>>, String> {
|
||||
) -> Result<Arc<Mutex<dyn DynAdminRepository>>, 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
|
||||
|
@ -9,7 +9,7 @@ use crate::admin::domain::{AdminApp, AdminModel};
|
||||
use crate::admin::state::AdminState;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::domain::{LookupKey, RepositoryInfo, RepositoryItem, RepositoryList};
|
||||
use super::domain::{RepositoryInfo, RepositoryItem, RepositoryList};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AdminRequest {
|
||||
@ -157,15 +157,22 @@ pub async fn view_item_details<S: AdminState + Clone + Send + Sync + 'static>(
|
||||
let admin_model = registry
|
||||
.get_model(&app_key, &model_key)
|
||||
.expect("Admin Model not found?");
|
||||
let key: LookupKey = id.into();
|
||||
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_model: Some(admin_model),
|
||||
..Default::default()
|
||||
if let Some(key) = repo.key_from_string(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.as_ref()).await,
|
||||
item_model: Some(admin_model),
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
AdminContext {
|
||||
base: base_template(&headers),
|
||||
available_apps: registry.get_apps(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
AdminContext {
|
||||
@ -257,15 +264,22 @@ pub async fn change_item<S: AdminState + Clone + Send + Sync + 'static>(
|
||||
let admin_model = registry
|
||||
.get_model(&app_key, &model_key)
|
||||
.expect("Admin Model not found?");
|
||||
let key: LookupKey = id.into();
|
||||
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_model: Some(admin_model),
|
||||
..Default::default()
|
||||
if let Some(key) = repo.key_from_string(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.as_ref()).await,
|
||||
item_model: Some(admin_model),
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
AdminContext {
|
||||
base: base_template(&headers),
|
||||
available_apps: registry.get_apps(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
AdminContext {
|
||||
@ -290,18 +304,23 @@ pub async fn update_item<S: AdminState + Clone + Send + Sync + 'static>(
|
||||
let admin_model = registry
|
||||
.get_model(&app_key, &model_key)
|
||||
.expect("Admin Model not found?");
|
||||
let key: LookupKey = id.into();
|
||||
|
||||
let result = repo.update(&admin_model, key, form).await;
|
||||
|
||||
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: result,
|
||||
item_model: Some(admin_model),
|
||||
..Default::default()
|
||||
if let Some(key) = repo.key_from_string(id) {
|
||||
let result = repo.update(&admin_model, key.as_ref(), form).await;
|
||||
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: result,
|
||||
item_model: Some(admin_model),
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
AdminContext {
|
||||
base: base_template(&headers),
|
||||
available_apps: registry.get_apps(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
AdminContext {
|
||||
|
@ -8,6 +8,16 @@ impl Repository {}
|
||||
|
||||
#[async_trait]
|
||||
impl AdminRepository for Repository {
|
||||
type Key = i64;
|
||||
|
||||
fn key_from_string(&self, s: String) -> Option<Self::Key> {
|
||||
if let Ok(i) = s.parse::<i64>() {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn info(&self, _: &RepositoryContext) -> RepositoryInfo {
|
||||
RepoInfo {
|
||||
name: "My Empty Repository",
|
||||
@ -33,7 +43,7 @@ impl AdminRepository for Repository {
|
||||
}
|
||||
|
||||
// GET single item.
|
||||
async fn get(&self, model: &RepositoryContext, id: LookupKey) -> Option<RepositoryItem> {
|
||||
async fn get(&self, model: &RepositoryContext, id: &Self::Key) -> Option<RepositoryItem> {
|
||||
None
|
||||
}
|
||||
|
||||
@ -41,7 +51,7 @@ impl AdminRepository for Repository {
|
||||
async fn update(
|
||||
&mut self,
|
||||
model: &RepositoryContext,
|
||||
id: LookupKey,
|
||||
id: &Self::Key,
|
||||
data: Value,
|
||||
) -> Option<RepositoryItem> {
|
||||
None
|
||||
@ -51,14 +61,14 @@ impl AdminRepository for Repository {
|
||||
async fn replace(
|
||||
&mut self,
|
||||
model: &RepositoryContext,
|
||||
id: LookupKey,
|
||||
id: &Self::Key,
|
||||
data: Value,
|
||||
) -> Option<RepositoryItem> {
|
||||
None
|
||||
}
|
||||
|
||||
// DELETE single item.
|
||||
async fn delete(&mut self, _: &RepositoryContext, id: LookupKey) -> Option<Value> {
|
||||
async fn delete(&mut self, _: &RepositoryContext, id: &Self::Key) -> Option<Value> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,16 @@ impl MyStaticRepository {
|
||||
|
||||
#[async_trait]
|
||||
impl AdminRepository for MyStaticRepository {
|
||||
type Key = i64;
|
||||
|
||||
fn key_from_string(&self, s: String) -> Option<Self::Key> {
|
||||
if let Ok(i) = s.parse::<i64>() {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn info(&self, _: &RepositoryContext) -> RepositoryInfo {
|
||||
RepoInfo {
|
||||
name: "My Static Repository",
|
||||
@ -48,15 +58,11 @@ impl AdminRepository for MyStaticRepository {
|
||||
//.set_widget("name", Widget::textarea().options(&[("disabled", "true")]))
|
||||
}
|
||||
|
||||
async fn get(&self, model: &RepositoryContext, id: LookupKey) -> Option<RepositoryItem> {
|
||||
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<RepositoryItem> {
|
||||
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 +100,7 @@ impl AdminRepository for MyStaticRepository {
|
||||
async fn update(
|
||||
&mut self,
|
||||
model: &RepositoryContext,
|
||||
id: LookupKey,
|
||||
id: &Self::Key,
|
||||
data: Value,
|
||||
) -> Option<RepositoryItem> {
|
||||
debug!("I would now update: {}, {}", id, data);
|
||||
@ -102,10 +108,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 +130,18 @@ impl AdminRepository for MyStaticRepository {
|
||||
async fn replace(
|
||||
&mut self,
|
||||
model: &RepositoryContext,
|
||||
id: LookupKey,
|
||||
id: &Self::Key,
|
||||
data: Value,
|
||||
) -> Option<RepositoryItem> {
|
||||
self.update(model, id, data).await
|
||||
}
|
||||
|
||||
async fn delete(&mut self, _: &RepositoryContext, id: LookupKey) -> Option<Value> {
|
||||
async fn delete(&mut self, _: &RepositoryContext, id: &Self::Key) -> Option<Value> {
|
||||
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
|
||||
}
|
||||
|
@ -19,6 +19,16 @@ impl UserRepository {
|
||||
|
||||
#[async_trait]
|
||||
impl AdminRepository for UserRepository {
|
||||
type Key = i64;
|
||||
|
||||
fn key_from_string(&self, s: String) -> Option<Self::Key> {
|
||||
if let Ok(i) = s.parse::<i64>() {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn info(&self, _: &RepositoryContext) -> RepositoryInfo {
|
||||
RepoInfo {
|
||||
name: "User",
|
||||
@ -29,25 +39,24 @@ impl AdminRepository for UserRepository {
|
||||
.into()
|
||||
}
|
||||
|
||||
async fn get(&self, model: &RepositoryContext, id: LookupKey) -> Option<RepositoryItem> {
|
||||
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<RepositoryItem> {
|
||||
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 +103,56 @@ impl AdminRepository for UserRepository {
|
||||
async fn update(
|
||||
&mut self,
|
||||
model: &RepositoryContext,
|
||||
id: LookupKey,
|
||||
id: &Self::Key,
|
||||
data: Value,
|
||||
) -> Option<RepositoryItem> {
|
||||
if let LookupKey::Integer(id) = id {
|
||||
let id: i32 = id as i32; // use try_into() instead.
|
||||
let user: Option<entity::user::Model> = 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::Model> = 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<RepositoryItem> {
|
||||
self.update(model, id, data).await
|
||||
}
|
||||
|
||||
async fn delete(&mut self, _: &RepositoryContext, id: LookupKey) -> Option<Value> {
|
||||
if let LookupKey::Integer(id) = id {
|
||||
let id: i32 = id as i32; // use try_into() instead.
|
||||
let user: Option<entity::user::Model> = 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<Value> {
|
||||
let id: i32 = *id as i32;
|
||||
let user: Option<entity::user::Model> = 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
|
||||
}
|
||||
}
|
||||
|
142
src/bin/generic_trait_type_erasure.rs
Normal file
142
src/bin/generic_trait_type_erasure.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
|
||||
// Define the Key trait with Any and Debug for type erasure and debugging
|
||||
pub trait Key: Any + Debug {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
// Implement Key for i32
|
||||
impl Key for i32 {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Key for String
|
||||
impl Key for String {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// Implement From<String> for i32
|
||||
impl From<String> for Box<dyn Key> {
|
||||
fn from(s: String) -> Box<dyn Key> {
|
||||
if let Ok(i) = s.parse::<i32>() {
|
||||
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<T: Repository> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: Repository> RepositoryWrapper<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Repository> DynRepository for RepositoryWrapper<T> {
|
||||
fn get_entry(&self, key: &dyn Key) -> String {
|
||||
if let Some(key) = key.as_any().downcast_ref::<T::Key>() {
|
||||
self.inner.get_entry(key)
|
||||
} else {
|
||||
"Invalid Key Type".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implement the Repository trait for IntRepository
|
||||
pub struct IntRepository {
|
||||
data: HashMap<i32, String>,
|
||||
}
|
||||
|
||||
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<String, String>,
|
||||
}
|
||||
|
||||
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<Box<dyn DynRepository>> = vec![
|
||||
Box::new(RepositoryWrapper::new(int_repo)),
|
||||
Box::new(RepositoryWrapper::new(string_repo)),
|
||||
];
|
||||
|
||||
// Creating keys from strings using the From<String> implementation
|
||||
let keys: Vec<Box<dyn Key>> = 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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user