refactor: using type erasure so users can define their own primary key types instead of lookupkey

This commit is contained in:
Gabor Körber 2024-05-21 22:53:44 +02:00
parent 6b207872a1
commit d1abc6b6c2
7 changed files with 323 additions and 157 deletions

View File

@ -2,8 +2,8 @@ pub use config::AdminModelConfig;
pub use dto::AdminApp; pub use dto::AdminApp;
pub use dto::AdminModel; pub use dto::AdminModel;
pub use repository::{ pub use repository::{
AdminRepository, LookupKey, RepoInfo, RepositoryContext, RepositoryInfo, RepositoryItem, AdminRepository, DynAdminRepository, LookupKey, RepoInfo, RepositoryContext, RepositoryInfo,
RepositoryList, Widget, RepositoryItem, RepositoryList, Widget,
}; };
mod auth { mod auth {
@ -350,18 +350,32 @@ pub mod repository {
} }
} }
pub trait PrimaryKeyType { use std::any::Any;
fn key_as_string(&self) -> String; use std::fmt::Debug;
fn key_from_string(&self, s: String) -> Self;
pub trait PrimaryKeyType: Any + Debug + Send + Sync {
fn as_any(&self) -> &dyn Any;
} }
impl PrimaryKeyType for i64 { impl PrimaryKeyType for i64 {
fn key_as_string(&self) -> String { fn as_any(&self) -> &dyn Any {
self.to_string() self
} }
}
fn key_from_string(&self, s: String) -> Self { impl PrimaryKeyType for String {
s.parse::<usize>().unwrap() as i64 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)
}
} }
} }
@ -371,7 +385,7 @@ pub mod repository {
async fn info(&self, context: &RepositoryContext) -> RepositoryInfo; async fn info(&self, context: &RepositoryContext) -> RepositoryInfo;
async fn list(&self, context: &RepositoryContext) -> RepositoryList; async fn list(&self, context: &RepositoryContext) -> RepositoryList;
async fn get(&self, context: &RepositoryContext, id: Self::Key) -> Option<RepositoryItem>; async fn get(&self, context: &RepositoryContext, id: &Self::Key) -> Option<RepositoryItem>;
async fn create( async fn create(
&mut self, &mut self,
context: &RepositoryContext, context: &RepositoryContext,
@ -380,76 +394,127 @@ pub mod repository {
async fn update( async fn update(
&mut self, &mut self,
context: &RepositoryContext, context: &RepositoryContext,
id: Self::Key, id: &Self::Key,
data: Value, data: Value,
) -> Option<RepositoryItem>; ) -> Option<RepositoryItem>;
async fn replace( async fn replace(
&mut self, &mut self,
context: &RepositoryContext, context: &RepositoryContext,
id: Self::Key, id: &Self::Key,
data: Value, data: Value,
) -> Option<RepositoryItem>; ) -> Option<RepositoryItem>;
async fn delete(&mut self, context: &RepositoryContext, id: Self::Key) -> Option<Value>; async fn delete(&mut self, context: &RepositoryContext, id: &Self::Key) -> Option<Value>;
} }
use std::any::Any; #[async_trait]
pub trait DynAdminRepository: Send + Sync {
trait Key: Any { async fn info(&self, context: &RepositoryContext) -> RepositoryInfo;
fn as_any(&self) -> &dyn Any; async fn list(&self, context: &RepositoryContext) -> RepositoryList;
fn from_str(s: &str) -> Self async fn get(
where &self,
Self: Sized; context: &RepositoryContext,
fn to_string(&self) -> String; 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>;
} }
impl<T: Any> Key for T { pub struct AdminRepositoryWrapper<T: AdminRepository> {
fn as_any(&self) -> &dyn Any { inner: T,
self
}
} }
impl Key for String { impl<T: AdminRepository> AdminRepositoryWrapper<T> {
fn from_str(s: &str) -> Self { pub fn new(inner: T) -> Self {
s.to_string() Self { inner }
}
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()
} }
} }
#[async_trait] #[async_trait]
pub trait ErasedAdminRepository: Send + Sync { impl<T: AdminRepository> DynAdminRepository for AdminRepositoryWrapper<T> {
async fn info(&self, context: &RepositoryContext) -> RepositoryInfo; async fn info(&self, context: &RepositoryContext) -> RepositoryInfo {
async fn list(&self, context: &RepositoryContext) -> RepositoryList; self.inner.info(context).await
async fn get(&self, context: &RepositoryContext, id: &dyn Key) -> Option<RepositoryItem>; }
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( async fn create(
&mut self, &mut self,
context: &RepositoryContext, context: &RepositoryContext,
data: Value, data: Value,
) -> Option<RepositoryItem>; ) -> Option<RepositoryItem> {
self.inner.create(context, data).await
}
async fn update( async fn update(
&mut self, &mut self,
context: &RepositoryContext, context: &RepositoryContext,
id: &dyn Key, id: &dyn PrimaryKeyType,
data: Value, data: Value,
) -> Option<RepositoryItem>; ) -> 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( async fn replace(
&mut self, &mut self,
context: &RepositoryContext, context: &RepositoryContext,
id: &dyn Key, id: &dyn PrimaryKeyType,
data: Value, data: Value,
) -> Option<RepositoryItem>; ) -> Option<RepositoryItem> {
async fn delete(&mut self, context: &RepositoryContext, id: &dyn Key) -> Option<Value>; 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
}
}
} }
} }

View File

@ -1,6 +1,7 @@
use tokio::sync::Mutex; 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 crate::service::templates::Templates;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@ -17,7 +18,7 @@ pub struct AdminRegistry {
base_path: String, base_path: String,
apps: HashMap<String, internal::AdminApp>, apps: HashMap<String, internal::AdminApp>,
models: HashMap<String, internal::AdminModel>, models: HashMap<String, internal::AdminModel>,
repositories: HashMap<String, Arc<Mutex<dyn AdminRepository>>>, repositories: HashMap<String, Arc<Mutex<dyn DynAdminRepository>>>,
} }
impl AdminRegistry { impl AdminRegistry {
@ -113,6 +114,7 @@ impl AdminRegistry {
repository: R, repository: R,
) -> Result<(), String> { ) -> Result<(), String> {
let model_key = self.register_model_config(model)?; let model_key = self.register_model_config(model)?;
let repository = AdminRepositoryWrapper::new(repository);
self.repositories self.repositories
.insert(model_key, Arc::new(Mutex::new(repository))); .insert(model_key, Arc::new(Mutex::new(repository)));
Ok(()) Ok(())
@ -122,7 +124,7 @@ impl AdminRegistry {
&self, &self,
app_key: &str, app_key: &str,
model_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); let full_model_key = format!("{}.{}", app_key, model_key);
if let Some(repo) = self.repositories.get(&full_model_key) { if let Some(repo) = self.repositories.get(&full_model_key) {
// Clone the Arc to return a reference to the repository // Clone the Arc to return a reference to the repository

View File

@ -9,6 +9,7 @@ use crate::admin::domain::{AdminApp, AdminModel};
use crate::admin::state::AdminState; use crate::admin::state::AdminState;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::domain::repository::PrimaryKeyType;
use super::domain::{LookupKey, RepositoryInfo, RepositoryItem, RepositoryList}; use super::domain::{LookupKey, RepositoryInfo, RepositoryItem, RepositoryList};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -157,13 +158,13 @@ pub async fn view_item_details<S: AdminState + Clone + Send + Sync + 'static>(
let admin_model = registry let admin_model = registry
.get_model(&app_key, &model_key) .get_model(&app_key, &model_key)
.expect("Admin Model not found?"); .expect("Admin Model not found?");
let key: LookupKey = id.into(); let key: Box<dyn PrimaryKeyType> = Box::new(id);
AdminContext { AdminContext {
base: base_template(&headers), base: base_template(&headers),
available_apps: registry.get_apps(), available_apps: registry.get_apps(),
item_info: Some(repo.info(&admin_model).await), item_info: Some(repo.info(&admin_model).await),
item_list: repo.list(&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), item_model: Some(admin_model),
..Default::default() ..Default::default()
} }
@ -257,13 +258,13 @@ pub async fn change_item<S: AdminState + Clone + Send + Sync + 'static>(
let admin_model = registry let admin_model = registry
.get_model(&app_key, &model_key) .get_model(&app_key, &model_key)
.expect("Admin Model not found?"); .expect("Admin Model not found?");
let key: LookupKey = id.into(); let key: Box<dyn PrimaryKeyType> = Box::new(id);
AdminContext { AdminContext {
base: base_template(&headers), base: base_template(&headers),
available_apps: registry.get_apps(), available_apps: registry.get_apps(),
item_info: Some(repo.info(&admin_model).await), item_info: Some(repo.info(&admin_model).await),
item_list: repo.list(&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), item_model: Some(admin_model),
..Default::default() ..Default::default()
} }
@ -290,9 +291,9 @@ pub async fn update_item<S: AdminState + Clone + Send + Sync + 'static>(
let admin_model = registry let admin_model = registry
.get_model(&app_key, &model_key) .get_model(&app_key, &model_key)
.expect("Admin Model not found?"); .expect("Admin Model not found?");
let key: LookupKey = id.into(); let key: Box<dyn PrimaryKeyType> = Box::new(id);
let result = repo.update(&admin_model, key, form).await; let result = repo.update(&admin_model, key.as_ref(), form).await;
AdminContext { AdminContext {
base: base_template(&headers), base: base_template(&headers),

View File

@ -8,6 +8,8 @@ impl Repository {}
#[async_trait] #[async_trait]
impl AdminRepository for Repository { impl AdminRepository for Repository {
type Key = i64;
async fn info(&self, _: &RepositoryContext) -> RepositoryInfo { async fn info(&self, _: &RepositoryContext) -> RepositoryInfo {
RepoInfo { RepoInfo {
name: "My Empty Repository", name: "My Empty Repository",
@ -33,7 +35,7 @@ impl AdminRepository for Repository {
} }
// GET single item. // 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 None
} }
@ -41,7 +43,7 @@ impl AdminRepository for Repository {
async fn update( async fn update(
&mut self, &mut self,
model: &RepositoryContext, model: &RepositoryContext,
id: LookupKey, id: &Self::Key,
data: Value, data: Value,
) -> Option<RepositoryItem> { ) -> Option<RepositoryItem> {
None None
@ -51,14 +53,14 @@ impl AdminRepository for Repository {
async fn replace( async fn replace(
&mut self, &mut self,
model: &RepositoryContext, model: &RepositoryContext,
id: LookupKey, id: &Self::Key,
data: Value, data: Value,
) -> Option<RepositoryItem> { ) -> Option<RepositoryItem> {
None None
} }
// DELETE single item. // 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 None
} }
} }

View File

@ -36,6 +36,8 @@ impl MyStaticRepository {
#[async_trait] #[async_trait]
impl AdminRepository for MyStaticRepository { impl AdminRepository for MyStaticRepository {
type Key = i64;
async fn info(&self, _: &RepositoryContext) -> RepositoryInfo { async fn info(&self, _: &RepositoryContext) -> RepositoryInfo {
RepoInfo { RepoInfo {
name: "My Static Repository", name: "My Static Repository",
@ -48,15 +50,11 @@ impl AdminRepository for MyStaticRepository {
//.set_widget("name", Widget::textarea().options(&[("disabled", "true")])) //.set_widget("name", Widget::textarea().options(&[("disabled", "true")]))
} }
async fn get(&self, model: &RepositoryContext, id: LookupKey) -> Option<RepositoryItem> { async fn get(&self, model: &RepositoryContext, id: &Self::Key) -> Option<RepositoryItem> {
if let LookupKey::Integer(id) = id { let id = *id as usize;
let item = self.content.get(id - 1).cloned().unwrap(); let item = self.content.get(id - 1).cloned().unwrap();
let id = item.get("id").unwrap(); let id = item.get("id").unwrap();
Some(model.build_item(&*id.to_string(), item)) Some(model.build_item(&*id.to_string(), item))
} else {
warn!("Got non-integer lookup key: {}", id);
None
}
} }
async fn list(&self, model: &RepositoryContext) -> RepositoryList { async fn list(&self, model: &RepositoryContext) -> RepositoryList {
@ -94,7 +92,7 @@ impl AdminRepository for MyStaticRepository {
async fn update( async fn update(
&mut self, &mut self,
model: &RepositoryContext, model: &RepositoryContext,
id: LookupKey, id: &Self::Key,
data: Value, data: Value,
) -> Option<RepositoryItem> { ) -> Option<RepositoryItem> {
debug!("I would now update: {}, {}", id, data); debug!("I would now update: {}, {}", id, data);
@ -102,10 +100,7 @@ impl AdminRepository for MyStaticRepository {
// First, find the index of the item to update // First, find the index of the item to update
let item_index = self.content.iter().position(|item| { let item_index = self.content.iter().position(|item| {
if let Some(item_id) = item.get("id") { if let Some(item_id) = item.get("id") {
match id { item_id == id
LookupKey::Integer(i) => item_id == &i,
LookupKey::String(ref s) => item_id == s,
}
} else { } else {
false false
} }
@ -127,21 +122,18 @@ impl AdminRepository for MyStaticRepository {
async fn replace( async fn replace(
&mut self, &mut self,
model: &RepositoryContext, model: &RepositoryContext,
id: LookupKey, id: &Self::Key,
data: Value, data: Value,
) -> Option<RepositoryItem> { ) -> Option<RepositoryItem> {
self.update(model, id, data).await 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); debug!("Would delete: {}", id);
let item_index = self.content.iter().position(|item| { let item_index = self.content.iter().position(|item| {
if let Some(item_id) = item.get("id") { if let Some(item_id) = item.get("id") {
match id { item_id == id
LookupKey::Integer(i) => item_id == &i,
LookupKey::String(ref s) => item_id == s,
}
} else { } else {
false false
} }

View File

@ -19,6 +19,8 @@ impl UserRepository {
#[async_trait] #[async_trait]
impl AdminRepository for UserRepository { impl AdminRepository for UserRepository {
type Key = i64;
async fn info(&self, _: &RepositoryContext) -> RepositoryInfo { async fn info(&self, _: &RepositoryContext) -> RepositoryInfo {
RepoInfo { RepoInfo {
name: "User", name: "User",
@ -29,25 +31,24 @@ impl AdminRepository for UserRepository {
.into() .into()
} }
async fn get(&self, model: &RepositoryContext, id: LookupKey) -> Option<RepositoryItem> { async fn get(&self, model: &RepositoryContext, id: &Self::Key) -> Option<RepositoryItem> {
if let LookupKey::Integer(id) = id { let id: i32 = *id as i32; // use try_into() instead.
let id: i32 = id as i32; // use try_into() instead. let get_user = entity::User::find_by_id(id).one(&self.connection).await;
let get_user = entity::User::find_by_id(id).one(&self.connection).await; match get_user {
match get_user { Ok(get_user) => {
Ok(get_user) => { if let Some(user) = get_user {
if let Some(user) = get_user { let id = user.id.to_string();
let id = user.id.to_string(); match serde_json::to_value(&user) {
match serde_json::to_value(&user) { Ok(item) => {
Ok(item) => { return Some(model.build_item(&*id, item));
return Some(model.build_item(&*id, item));
}
Err(_) => return None,
} }
Err(_) => return None,
} }
} }
Err(_) => return None,
} }
Err(_) => return None,
} }
None None
} }
@ -94,58 +95,56 @@ impl AdminRepository for UserRepository {
async fn update( async fn update(
&mut self, &mut self,
model: &RepositoryContext, model: &RepositoryContext,
id: LookupKey, id: &Self::Key,
data: Value, data: Value,
) -> Option<RepositoryItem> { ) -> Option<RepositoryItem> {
if let LookupKey::Integer(id) = id { let id: i32 = *id as i32;
let id: i32 = id as i32; // use try_into() instead. let user: Option<entity::user::Model> = entity::User::find_by_id(id)
let user: Option<entity::user::Model> = entity::User::find_by_id(id) .one(&self.connection)
.one(&self.connection) .await
.await .unwrap();
.unwrap(); let mut user: entity::user::ActiveModel = user.unwrap().into();
let mut user: entity::user::ActiveModel = user.unwrap().into();
// change values // change values
if let Some(value) = data.get("username") { if let Some(value) = data.get("username") {
if let Some(value) = value.as_str() { if let Some(value) = value.as_str() {
user.username = Set(value.to_owned()); 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()));
} }
} }
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 None
} }
async fn replace( async fn replace(
&mut self, &mut self,
model: &RepositoryContext, model: &RepositoryContext,
id: LookupKey, id: &Self::Key,
data: Value, data: Value,
) -> Option<RepositoryItem> { ) -> Option<RepositoryItem> {
self.update(model, id, data).await 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> {
if let LookupKey::Integer(id) = id { let id: i32 = *id as i32;
let id: i32 = id as i32; // use try_into() instead. let user: Option<entity::user::Model> = entity::User::find_by_id(id)
let user: Option<entity::user::Model> = entity::User::find_by_id(id) .one(&self.connection)
.one(&self.connection) .await
.await .unwrap();
.unwrap(); if let Some(user) = user {
if let Some(user) = user { let delete_result = user.delete(&self.connection).await.unwrap();
let delete_result = user.delete(&self.connection).await.unwrap(); debug!("deleted rows: {}", delete_result.rows_affected);
debug!("deleted rows: {}", delete_result.rows_affected);
}
} }
None None
} }
} }

View File

@ -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::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 as_any(&self) -> &dyn Any;
fn from_str(s: &str) -> Self where Self: Sized;
fn to_string(&self) -> String;
} }
impl<T: Any> Key for T { // Implement Key for i32
impl Key for i32 {
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
self self
} }
} }
// Implement Key for String
impl Key for String { impl Key for String {
fn from_str(s: &str) -> Self { fn as_any(&self) -> &dyn Any {
s.to_string() self
}
fn to_string(&self) -> String {
self.clone()
} }
} }
pub trait RepositoryTypeErased { // Implement From<String> for i32
fn get_data(&self, key: Key) -> String; impl From<String> for Box<dyn Key> {
fn set_data(&self, key: Key, value: String); 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);
}
}
} }