diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2022-11-15 19:49:54 -0500 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2022-11-15 19:49:54 -0500 |
commit | b114fe7940e77090861ac9ba60f4d0b8caec8978 (patch) | |
tree | d5c7a1f2250a899719225642bf876a29316a30b0 /server | |
parent | 87ede1e2b367a997440626ad9f583600d7cc42fc (diff) |
Add journaling GameState
Diffstat (limited to 'server')
-rw-r--r-- | server/src/error.rs | 3 | ||||
-rw-r--r-- | server/src/play.rs | 105 |
2 files changed, 90 insertions, 18 deletions
diff --git a/server/src/error.rs b/server/src/error.rs index cbd00c3..03735a7 100644 --- a/server/src/error.rs +++ b/server/src/error.rs @@ -41,6 +41,9 @@ pub enum BridgeError { #[error("Uuid parse failed")] UuidError(#[from] uuid::Error), + #[error("Serialization error")] + SerdeError(#[from] serde_json::Error), + #[error("Internal server error: {0}")] Internal(String), diff --git a/server/src/play.rs b/server/src/play.rs index 242bdb8..2632b24 100644 --- a/server/src/play.rs +++ b/server/src/play.rs @@ -1,11 +1,16 @@ use async_trait::async_trait; -use sqlx::{PgPool, query}; +use protocol::bridge_engine::{self, GameState, Player}; +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>; @@ -16,41 +21,79 @@ pub trait Journal { pub struct DbJournal { db: PgPool, id: Uuid, + seq: i64 } impl DbJournal { pub fn new(db: PgPool, id: Uuid) -> Self { - Self { db, id } + Self { db, id, seq: -1 } } } #[async_trait] impl Journal for DbJournal { - async fn append(&mut self,seq:i64,payload:serde_json::Value) -> Result<(),BridgeError> { - query!( - r#" + async fn append(&mut self, seq: i64, payload: serde_json::Value) -> Result<(), BridgeError> { + query!( + r#" insert into object_journal (id, seq, payload) values ($1, $2, $3) "#, - self.id, seq, payload, - ).execute(&self.db).await?; - Ok(()) + self.id, + seq, + payload, + ) + .execute(&self.db) + .await?; + Ok(()) } - async fn replay(&mut self,seq:i64) -> Result<Vec<serde_json::Value>,BridgeError> { - let results = query!( - r#" + async fn replay(&mut self, seq: i64) -> Result<Vec<serde_json::Value>, 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()) + self.id, + seq + ) + .fetch_all(&self.db) + .await?; + Ok(results.into_iter().map(|v| v.payload).collect()) + } + + fn next(&self) -> i64 { + self.seq + 1 } } -pub struct Table {} +pub struct Table<J> +where + J: Journal, +{ + journal: J, + game: GameState, +} + +impl<J: Journal> Table<J> { + pub async fn new(mut journal: J) -> Result<Self, BridgeError> { + let game = GameState::Bidding { + dealer: Player::East, + deal: bridge_engine::deal(), + }; + journal.append(0, json!(game)).await?; + Ok(Table { journal, game }) + } + + pub async fn replay(mut journal: J) -> Result<Self, BridgeError> { + let games = journal.replay(0).await?; + if games.is_empty() { + return Err(BridgeError::Internal(format!("empty journal"))); + } + let game = serde_json::from_value(games[games.len() - 1].clone())?; + Ok(Table { journal, game } ) + } +} #[cfg(test)] mod test { @@ -81,15 +124,41 @@ mod test { .filter_map(|e| e.clone()) .collect()) } + + fn next(&self) -> i64 { + self.log.len() as i64 + } } #[tokio::test] async fn test_journal() { - use serde_json::json; let mut jnl: TestJournal = Default::default(); - assert_eq!(jnl.append(0, json!(10)).await.unwrap(), ()); + 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()); - assert_eq!(jnl.append(1, json!(20)).await.unwrap(), ()); + 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<TestJournal> = Table::new(Default::default()).await.unwrap(); + match t1.game { + GameState::Bidding { dealer, deal } => (), + _ => panic!("should be Bidding"), + }; + } + + #[tokio::test] + async fn test_replay_table() { + let t1: Table<TestJournal> = 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); + } } |