use async_trait::async_trait; use protocol::{bridge_engine::{GameState, Player, Bid, Deal}}; use rand::random; use serde::{Deserialize, Serialize}; use serde_json::json; use sqlx::{query, PgPool}; use uuid::Uuid; use crate::error::BridgeError; #[async_trait] pub trait Journal { // Next sequence number to use. fn next(&self) -> i64; // Append payload to the journal at sequence number `seq`. async fn append(&mut self, seq: i64, payload: serde_json::Value) -> Result<(), BridgeError>; // Fetch all journal entries with sequence number greater or equal to `seq`. async fn replay(&mut self, seq: i64) -> Result, BridgeError>; } pub struct DbJournal { db: PgPool, id: Uuid, seq: i64 } impl DbJournal { pub fn new(db: PgPool, id: Uuid) -> Self { Self { db, id, seq: -1 } } } #[async_trait] impl Journal for DbJournal { async fn append(&mut self, seq: i64, payload: serde_json::Value) -> Result<(), BridgeError> { let result = query!( r#" insert into object_journal (id, seq, payload) values ($1, $2, $3) "#, self.id, seq, payload, ) .execute(&self.db) .await; if let Err(sqlx::Error::Database(e)) = result { if e.constraint() == Some("journal_entry") { return Err(BridgeError::JournalConflict(format!("{}", self.id), seq)); } } Ok(()) } async fn replay(&mut self, seq: i64) -> Result, BridgeError> { let results = query!( r#" select payload from object_journal where id = $1 and seq >= $2 order by seq "#, self.id, seq ) .fetch_all(&self.db) .await?; Ok(results.into_iter().map(|v| v.payload).collect()) } fn next(&self) -> i64 { self.seq + 1 } } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub enum TableUpdate { NewDeal(Deal, Player), ChangeSettings(TableSettings), Bid(Bid), } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Default)] pub struct TableSettings { west_player: Option, nort_player: Option, east_player: Option, south_player: Option, } pub struct Table where J: Journal, { journal: J, settings: TableSettings, game: GameState, } impl Table { pub fn game(&self) -> &GameState { &self.game } } impl Table { pub async fn new(mut journal: J) -> Result { let game = Self::init(&mut journal).await?; Ok(Table { journal, game, settings: Default::default() }) } async fn init(journal: &mut J) -> Result { let game = GameState::new(random(), random()); journal.append(0, json!(game)).await?; Ok(game) } pub async fn replay(mut journal: J) -> Result { let games = journal.replay(0).await?; if games.is_empty() { return Err(BridgeError::NotFound("table journal missing".to_string())); } let game = serde_json::from_value(games[games.len() - 1].clone())?; Ok(Table { journal, game, settings: Default::default() } ) } pub async fn new_or_replay(mut journal: J) -> Result { let game = Self::init(&mut journal).await; if let Err(BridgeError::JournalConflict(..)) = game { return Self::replay(journal).await; } Ok(Self { journal, game: game?, settings: Default::default() } ) } } pub fn advance_play(table: &mut Table) { todo!() } #[cfg(test)] mod test { use super::*; #[derive(Default)] pub struct TestJournal { log: Vec>, } #[async_trait] impl Journal for TestJournal { async fn append( &mut self, seq: i64, payload: serde_json::Value, ) -> Result<(), BridgeError> { if seq != self.log.len() as i64 { return Err(BridgeError::UpdateConflict(self.log.len() as i64, seq)); } self.log.push(Some(payload)); Ok(()) } async fn replay(&mut self, seq: i64) -> Result, BridgeError> { Ok(self.log[seq as usize..] .into_iter() .filter_map(|e| e.clone()) .collect()) } fn next(&self) -> i64 { self.log.len() as i64 } } #[tokio::test] async fn test_journal() { let mut jnl: TestJournal = Default::default(); let seq = jnl.next(); assert_eq!(jnl.next(), 0); assert_eq!(jnl.append(seq, json!(10)).await.unwrap(), ()); assert_eq!(jnl.next(), 1); assert!(jnl.append(0, json!(0)).await.is_err()); let seq = jnl.next(); assert_eq!(jnl.append(seq, json!(20)).await.unwrap(), ()); assert_eq!(jnl.replay(1).await.unwrap(), vec!(json!(20))); } #[tokio::test] async fn test_new_table() { let t1: Table = Table::new(Default::default()).await.unwrap(); match t1.game { GameState::Bidding { .. } => (), _ => panic!("should be Bidding"), }; } #[tokio::test] async fn test_replay_table() { let t1: Table = Table::new(Default::default()).await.unwrap(); let game = t1.game; let journal = t1.journal; let t2 = Table::replay(journal).await.unwrap(); assert_eq!(game, t2.game); } #[tokio::test] async fn test_advance_play() { let mut t1: Table = Table::new(Default::default()).await.unwrap(); let player = t1.game().current_player(); advance_play(&mut t1); assert_ne!(player, t1.game().current_player()); } }