Merge pull request 'Implement Associated Type for Keys' (#3) from try-associated-type into main

Reviewed-on: #3
This commit was merged in pull request #3.
This commit is contained in:
2024-06-01 17:47:14 +02:00
7 changed files with 450 additions and 155 deletions

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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
}
}

View 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);
}
}
}