summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-11-15 19:49:54 -0500
committerKjetil Orbekk <kj@orbekk.com>2022-11-15 19:49:54 -0500
commitb114fe7940e77090861ac9ba60f4d0b8caec8978 (patch)
treed5c7a1f2250a899719225642bf876a29316a30b0 /server
parent87ede1e2b367a997440626ad9f583600d7cc42fc (diff)
Add journaling GameState
Diffstat (limited to 'server')
-rw-r--r--server/src/error.rs3
-rw-r--r--server/src/play.rs105
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);
+ }
}