use crate::entity::gateway::EntityGateway; use thiserror::Error; use std::borrow::Cow; use crate::ship::items::manager::{ItemManager, ItemManagerError}; use std::collections::HashMap; use crate::entity::character::{CharacterEntity, CharacterEntityId, TechLevel}; use crate::ship::items::bank::*; use crate::ship::items::floor::*; use crate::ship::items::inventory::*; use crate::ship::items::ClientItemId; use crate::entity::gateway::GatewayError; use crate::ship::location::{AreaClient, RoomId}; #[derive(Error, Debug)] #[error("")] pub enum TransactionCommitError { Gateway(#[from] GatewayError), ItemManager(#[from] ItemManagerError), } #[async_trait::async_trait] pub trait ItemAction<EG: EntityGateway>: std::marker::Send + std::marker::Sync { async fn commit(&self, manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError>; } pub struct ItemTransactionActions<'a, EG: EntityGateway> { action_queue: Vec<Box<dyn ItemAction<EG>>>, pub manager: &'a ItemManager, } impl<'a, EG: EntityGateway> ItemTransactionActions<'a, EG> { fn new(manager: &'a ItemManager) -> ItemTransactionActions<'a, EG> { ItemTransactionActions { action_queue: Vec::new(), manager } } pub fn action<A: ItemAction<EG> + 'static>(&mut self, action: A) { //pub fn action<A: Into<Box<dyn ItemAction<EG>>>>(&mut self, action: A) { //pub fn action(&mut self, action: impl ItemAction<EG>) { self.action_queue.push(Box::new(action)) //self.action_queue.push(action.into()) } } pub struct ItemTransaction<'a, T, EG: EntityGateway> { data: T, actions: ItemTransactionActions<'a, EG>, } impl<'a, T, EG: EntityGateway> ItemTransaction<'a, T, EG> { pub fn new(manager: &'a ItemManager, arg: T) -> ItemTransaction<'a, T, EG> { ItemTransaction { data: arg, actions: ItemTransactionActions::new(manager), } } /* pub fn act<U>(mut self, action: fn(&mut ItemTransactionActions<EG>, &T) -> Result<U, E>) -> ItemTransaction<'a, U, E, EG> { if self.error.is_none() { let k = action(&mut self.actions, &self.prev.unwrap()); match k { Ok(k) => { ItemTransaction { error: None, prev: Some(k), actions: self.actions, } }, Err(err) => { ItemTransaction { error: Some(err), prev: None, actions: self.actions, } } } } else { ItemTransaction { error: self.error, prev: None, actions: self.actions, } } } */ pub fn act<E: std::fmt::Debug, U>(mut self, action: fn(&mut ItemTransactionActions<EG>, &T) -> Result<U, E>) -> FinalizedItemTransaction<U, E, EG> { match action(&mut self.actions, &self.data) { Ok(k) => { FinalizedItemTransaction { value: Ok(k), action_queue: self.actions.action_queue, } }, Err(err) => { FinalizedItemTransaction { value: Err(err), action_queue: Vec::new(), } } } } /* pub fn finalize(self) -> FinalizedItemTransaction<T, E, EG> { FinalizedItemTransaction { error: self.error, prev: self.prev, action_queue: self.actions.action_queue, } } */ } #[derive(Error, Debug)] #[error("")] pub enum TransactionError<E: std::fmt::Debug> { Action(E), Commit(#[from] TransactionCommitError), } // this only exists to drop the ItemManager borrow of ItemTransaction so a mutable ItemTransaction can be passed in later pub struct FinalizedItemTransaction<T, E: std::fmt::Debug, EG: EntityGateway> { value: Result<T, E>, action_queue: Vec<Box<dyn ItemAction<EG>>>, } impl<T, E: std::fmt::Debug, EG: EntityGateway> FinalizedItemTransaction<T, E, EG> { pub async fn commit(self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<T, TransactionError<E>> { match self.value { Ok(value) => { for action in self.action_queue.into_iter() { // TODO: better handle rolling back if this ever errors out //let result = action.item_action(item_manager).await.map_err(|err| TransactionError::Commit(err.into()))?; //action.gateway_action(entity_gateway, result).await.map_err(|err| TransactionError::Commit(err.into()))?; action.commit(item_manager, entity_gateway).await.map_err(|err| TransactionError::Commit(err))?; } Ok(value) }, Err(err) => Err(TransactionError::Action(err)), } } } #[cfg(test)] mod test { use super::*; use crate::entity::account::{UserAccountId, NewUserAccountEntity, UserAccountEntity}; use crate::entity::character::{NewCharacterEntity, CharacterEntity}; use crate::entity::gateway::GatewayError; use thiserror::Error; #[async_std::test] async fn test_item_transaction() { struct DummyAction1 { name: String, } struct DummyAction2 { value: u32, } #[derive(Error, Debug)] #[error("")] enum DummyError { Error } #[derive(Default, Clone)] struct DummyGateway { d1_set: String, d2_inc: u32, } #[async_trait::async_trait] impl EntityGateway for DummyGateway { async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> { self.d1_set = user.username; Ok(UserAccountEntity::default()) } async fn create_character(&mut self, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> { self.d2_inc += char.slot; Ok(CharacterEntity::default()) } } #[async_trait::async_trait] impl<EG: EntityGateway> ItemAction<EG> for DummyAction1 { async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { item_manager.id_counter = 55555; entity_gateway.create_user(NewUserAccountEntity { username: self.name.clone(), ..NewUserAccountEntity::default() }) .await?; Ok(()) } } #[async_trait::async_trait] impl<EG: EntityGateway> ItemAction<EG> for DummyAction2 { async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { item_manager.id_counter += self.value; entity_gateway.create_character(NewCharacterEntity { slot: self.value, ..NewCharacterEntity::new(UserAccountId(0)) }) .await?; Ok(()) } } let mut item_manager = ItemManager::default(); let mut entity_gateway = DummyGateway::default(); let result = ItemTransaction::new(&item_manager, 12) .act(|it, k| { it.action(DummyAction1 {name: "asdf".into()}); it.action(DummyAction2 {value: 11}); it.action(DummyAction2 {value: *k}); if *k == 99 { return Err(DummyError::Error) } Ok(String::from("hello")) }) .commit(&mut item_manager, &mut entity_gateway) .await; assert!(entity_gateway.d1_set == "asdf"); assert!(entity_gateway.d2_inc == 23); assert!(item_manager.id_counter == 55578); assert!(result.unwrap() == "hello"); } #[async_std::test] async fn test_item_transaction_with_action_error() { struct DummyAction1 { } struct DummyAction2 { } #[derive(Error, Debug, PartialEq, Eq)] #[error("")] enum DummyError { Error } #[derive(Default, Clone)] struct DummyGateway { d1_set: String, d2_inc: u32, } #[async_trait::async_trait] impl EntityGateway for DummyGateway { async fn create_character(&mut self, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> { self.d2_inc += char.slot; Ok(CharacterEntity::default()) } } #[async_trait::async_trait] impl<EG: EntityGateway> ItemAction<EG> for DummyAction1 { async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { entity_gateway.create_character(NewCharacterEntity { slot: 1, ..NewCharacterEntity::new(UserAccountId(0)) }) .await?; Ok(()) } } #[async_trait::async_trait] impl<EG: EntityGateway> ItemAction<EG> for DummyAction2 { async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { entity_gateway.create_character(NewCharacterEntity { slot: 1, ..NewCharacterEntity::new(UserAccountId(0)) }) .await?; Ok(()) } } let mut item_manager = ItemManager::default(); let mut entity_gateway = DummyGateway::default(); let result = ItemTransaction::new(&item_manager, 12) .act(|it, _| -> Result<(), _> { it.action(DummyAction1 {}); it.action(DummyAction2 {}); it.action(DummyAction2 {}); Err(DummyError::Error) }) .commit(&mut item_manager, &mut entity_gateway) .await; assert!(entity_gateway.d2_inc == 0); assert!(matches!(result, Err(TransactionError::Action(DummyError::Error)))); } #[async_std::test] async fn test_item_transaction_with_commit_error() { struct DummyAction1 { } struct DummyAction2 { } #[derive(Error, Debug, PartialEq, Eq)] #[error("")] enum DummyError { } #[derive(Default, Clone)] struct DummyGateway { d1_set: String, d2_inc: u32, } #[async_trait::async_trait] impl EntityGateway for DummyGateway { async fn create_character(&mut self, char: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> { self.d2_inc += char.slot; Ok(CharacterEntity::default()) } } #[async_trait::async_trait] impl<EG: EntityGateway> ItemAction<EG> for DummyAction1 { async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { entity_gateway.create_character(NewCharacterEntity { slot: 1, ..NewCharacterEntity::new(UserAccountId(0)) }) .await?; Err(GatewayError::Error.into()) } } #[async_trait::async_trait] impl<EG: EntityGateway> ItemAction<EG> for DummyAction2 { async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { entity_gateway.create_character(NewCharacterEntity { slot: 1, ..NewCharacterEntity::new(UserAccountId(0)) }) .await?; Ok(()) } } let mut item_manager = ItemManager::default(); let mut entity_gateway = DummyGateway::default(); let result = ItemTransaction::new(&item_manager, 12) .act(|it, _| -> Result<_, DummyError> { it.action(DummyAction1 {}); it.action(DummyAction2 {}); it.action(DummyAction2 {}); Ok(()) }) .commit(&mut item_manager, &mut entity_gateway) .await; // in an ideal world this would be 0 as rollbacks would occur assert!(entity_gateway.d2_inc == 1); assert!(matches!(result, Err(TransactionError::Commit(TransactionCommitError::Gateway(GatewayError::Error))))); } }