338 lines
12 KiB
Rust
338 lines
12 KiB
Rust
use crate::entity::gateway::EntityGateway;
|
|
use thiserror::Error;
|
|
use crate::ship::items::manager::{ItemManager, ItemManagerError};
|
|
use crate::entity::gateway::GatewayError;
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum TransactionCommitError {
|
|
#[error("transaction commit gateway error {0}")]
|
|
Gateway(#[from] GatewayError),
|
|
#[error("transaction commit itemmanager error {0}")]
|
|
ItemManager(#[from] ItemManagerError),
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
pub trait ItemAction<EG: EntityGateway>: std::marker::Send + std::marker::Sync + std::fmt::Debug {
|
|
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(&mut self, action: Box<dyn ItemAction<EG>>) {
|
|
self.action_queue.push(action)
|
|
}
|
|
}
|
|
|
|
|
|
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<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(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum TransactionError<E: std::fmt::Debug> {
|
|
#[error("transaction action error {0:?}")]
|
|
Action(E),
|
|
#[error("transaction commit error {0}")]
|
|
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
|
|
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() {
|
|
#[derive(Debug)]
|
|
struct DummyAction1 {
|
|
name: String,
|
|
}
|
|
#[derive(Debug)]
|
|
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), 1) // TODO: handle different keyboard_config_presets
|
|
})
|
|
.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(Box::new(DummyAction1 {name: "asdf".into()}));
|
|
it.action(Box::new(DummyAction2 {value: 11}));
|
|
it.action(Box::new(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() {
|
|
#[derive(Debug)]
|
|
struct DummyAction1 {
|
|
}
|
|
#[derive(Debug)]
|
|
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), 1) // TODO: handle different keyboard_config_presets
|
|
})
|
|
.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), 1) // TODO: handle different keyboard_config_presets
|
|
})
|
|
.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(Box::new(DummyAction1 {}));
|
|
it.action(Box::new(DummyAction2 {}));
|
|
it.action(Box::new(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() {
|
|
#[derive(Debug)]
|
|
struct DummyAction1 {
|
|
}
|
|
#[derive(Debug)]
|
|
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), 1) // TODO: handle different keyboard_config_presets
|
|
})
|
|
.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), 1) // TODO: handle different keyboard_config_presets
|
|
})
|
|
.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(Box::new(DummyAction1 {}));
|
|
it.action(Box::new(DummyAction2 {}));
|
|
it.action(Box::new(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)))));
|
|
}
|
|
}
|
|
|