use async_trait::async_trait;
use serde::{Serialize, de::DeserializeOwned};
#[async_trait]
pub trait ReadItem<T: DeserializeOwned + Serialize> {
type DataSource;
type ItemIdentifier;
type Error;
async fn read_item(ds: &Self::DataSource, id: &Self::ItemIdentifier) -> Result<T, Self::Error>;
}
#[async_trait]
pub trait ReadItemList<T: DeserializeOwned + Serialize> {
type DataSource;
type Error;
async fn read_item_list(ds: &Self::DataSource) -> Result<Vec<T>, Self::Error>;
}
#[async_trait]
pub trait ReadIdentifiedItemList<T: DeserializeOwned + Serialize> {
type DataSource;
type ItemIdentifier;
type Error;
async fn read_identified_item_list(
ds: &Self::DataSource,
id: &Self::ItemIdentifier,
) -> Result<Vec<T>, Self::Error>;
}
#[async_trait]
pub trait CreateItem<T: DeserializeOwned + Serialize, R> {
type DataSource;
type Error;
async fn create_item(ds: &Self::DataSource, new_item: &T) -> Result<R, Self::Error>;
}
#[async_trait]
pub trait DeleteItem<R: DeserializeOwned + Serialize> {
type DataSource;
type ItemIdentifier;
type ExtraParameters;
type Error;
async fn delete_item(
ds: &Self::DataSource,
id: &Self::ItemIdentifier,
params: &Self::ExtraParameters,
) -> Result<R, Self::Error>;
}
#[async_trait]
pub trait UpdateItem<T: DeserializeOwned + Serialize, R> {
type DataSource;
type ItemIdentifier;
type Error;
async fn update_item(
ds: &Self::DataSource,
id: &Self::ItemIdentifier,
item: &T,
) -> Result<R, Self::Error>;
}
#[cfg(test)]
mod tests {
use super::*;
use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Mutex;
#[actix_web::test]
async fn read_item_trait() {
let mut ds: HashMap<i32, String> = HashMap::new();
ds.insert(1, "Foo".to_string());
struct Data;
#[async_trait]
impl ReadItem<String> for Data {
type DataSource = HashMap<i32, String>;
type ItemIdentifier = i32;
type Error = std::io::Error;
async fn read_item(
ds: &Self::DataSource,
id: &Self::ItemIdentifier,
) -> Result<String, Self::Error> {
let val = ds.get(id).ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::NotFound, "not found")
})?;
Ok(val.to_owned())
}
}
assert_eq!(Data::read_item(&ds, &1).await.unwrap(), "Foo");
}
#[actix_web::test]
async fn read_item_list_trait() {
let mut ds: HashMap<i32, String> = HashMap::new();
ds.insert(1, "Foo".to_string());
ds.insert(2, "Bar".to_string());
struct Data;
#[async_trait]
impl ReadItemList<String> for Data {
type DataSource = HashMap<i32, String>;
type Error = std::io::Error;
async fn read_item_list(ds: &Self::DataSource) -> Result<Vec<String>, Self::Error> {
Ok(ds.values().cloned().collect())
}
}
let item_list = Data::read_item_list(&ds).await.unwrap();
assert!(item_list.contains(&"Foo".to_string()) && item_list.contains(&"Bar".to_string()));
}
#[actix_web::test]
async fn read_identified_item_list_trait() {
let ds = vec![
(1, "Foo".to_string()),
(2, "Bar".to_string()),
(1, "Boo".to_string()),
];
struct Data;
#[async_trait]
impl ReadIdentifiedItemList<String> for Data {
type DataSource = Vec<(i32, String)>;
type ItemIdentifier = (i32, String);
type Error = std::io::Error;
async fn read_identified_item_list(
ds: &Self::DataSource,
id: &Self::ItemIdentifier,
) -> Result<Vec<String>, Self::Error> {
let val = ds
.iter()
.filter(|v| v.0 == id.0 && v.1.ends_with(&id.1))
.map(|v| v.1.clone())
.collect::<Vec<String>>();
Ok(val)
}
}
let item_list = Data::read_identified_item_list(&ds, &(1, "oo".to_string()))
.await
.unwrap();
assert!(item_list.contains(&"Foo".to_string()) && item_list.contains(&"Boo".to_string()));
}
#[actix_web::test]
async fn create_item_trait() {
let data: Vec<String> = Vec::new();
let ds = Mutex::new(data);
struct Data;
#[async_trait]
impl CreateItem<String, usize> for Data {
type DataSource = Mutex<Vec<String>>;
type Error = std::io::Error;
async fn create_item(
ds: &Self::DataSource,
new_item: &String,
) -> Result<usize, Self::Error> {
let mut ds = ds.try_lock().unwrap();
ds.push(new_item.clone());
Ok(ds.len() - 1)
}
}
assert_eq!(Data::create_item(&ds, &"Foo".to_string()).await.unwrap(), 0);
assert_eq!(ds.lock().unwrap()[0], "Foo");
}
#[actix_web::test]
async fn delete_item_trait() {
let data: Vec<String> = vec![
"This".to_owned(),
"is".to_owned(),
"a".to_owned(),
"test".to_owned(),
];
let ds = Mutex::new(data);
struct Data;
#[async_trait]
impl DeleteItem<usize> for Data {
type DataSource = Mutex<Vec<String>>;
type ItemIdentifier = usize;
type ExtraParameters = String;
type Error = std::io::Error;
async fn delete_item(
ds: &Self::DataSource,
id: &Self::ItemIdentifier,
params: &Self::ExtraParameters,
) -> Result<usize, Self::Error> {
let mut ds = ds.lock().unwrap();
let o_val = ds[*id].clone();
ds[*id] = Default::default();
ds.push(format!("{params}: {o_val}"));
Ok(*id)
}
}
assert_eq!(
Data::delete_item(&ds, &2usize, &"deleted".to_owned())
.await
.unwrap(),
2
);
let ds_l = ds.lock().unwrap();
assert_eq!(ds_l[0], "This");
assert_eq!(ds_l[1], "is");
assert_eq!(ds_l[2], String::default());
assert_eq!(ds_l[3], "test");
assert_eq!(ds_l[4], "deleted: a");
}
#[actix_web::test]
async fn update_item_trait() {
let mut data: HashMap<usize, String> = HashMap::new();
data.insert(1, "Bar".to_string());
let ds = Mutex::new(data);
let id = 1;
let item = "Foo".to_string();
struct Data;
#[async_trait]
impl UpdateItem<String, Option<String>> for Data {
type DataSource = Mutex<HashMap<usize, String>>;
type ItemIdentifier = usize;
type Error = std::io::Error;
async fn update_item(
ds: &Self::DataSource,
id: &Self::ItemIdentifier,
new_item: &String,
) -> Result<Option<String>, Self::Error> {
let mut ds = ds.lock().unwrap();
Ok(ds.insert(*id, new_item.clone()))
}
}
let old_val = Data::update_item(&ds, &id, &item).await.unwrap();
assert_eq!(old_val, Some("Bar".to_string()));
assert_eq!(ds.lock().unwrap().get(&id), Some(&item));
}
}