diff --git a/crates/lobby/src/games/db.rs b/crates/lobby/src/games/db.rs index a309b5d3..41d4ab3d 100644 --- a/crates/lobby/src/games/db.rs +++ b/crates/lobby/src/games/db.rs @@ -178,6 +178,7 @@ impl Games { AdditionError::OrdinalConflict, "UNIQUE constraint failed: players.game, players.ordinal" ); + db_error_message!(result, AdditionError::OrdinalTooLarge, "TOO-LARGE-ORDINAL"); result.map_err(AdditionError::Database)?; @@ -278,6 +279,8 @@ pub(super) enum AdditionError { AlreadyInAGame, #[error("Another player already joined the game with the same ordinal")] OrdinalConflict, + #[error("Player ordinal is larger than maximum number of players in the game")] + OrdinalTooLarge, #[error("The user or the game does not exist")] UserOrGameDoesNotExist, #[error("A database error encountered")] diff --git a/crates/lobby/src/games/endpoints.rs b/crates/lobby/src/games/endpoints.rs index 74ef2679..550ebac3 100644 --- a/crates/lobby/src/games/endpoints.rs +++ b/crates/lobby/src/games/endpoints.rs @@ -80,7 +80,11 @@ async fn join( ) -> impl Responder { let name = path.into_inner(); - // TODO error if ordinal >= max players + let ordinal = player_info.ordinal(); + if ordinal == 0 { + warn!("Game joining error: got ordinal equal to 0."); + return HttpResponse::BadRequest().json("Ordinals start with 0, got 1."); + } let player = GamePlayer::new(claims.username().to_owned(), player_info.0); match games.add_player(&player, name.as_str()).await { @@ -94,6 +98,11 @@ async fn join( HttpResponse::Conflict() .json("Another player has already joined the game under the given ordinal.") } + Err(AdditionError::OrdinalTooLarge) => { + warn!("Game joining error: too large ordinal: {ordinal}"); + HttpResponse::Conflict() + .json("The given ordinal is larger than maximum number of players.") + } Err(AdditionError::UserOrGameDoesNotExist) => { warn!("Game joining error: the game or the user does not exist"); HttpResponse::NotFound().json("Game not found.") diff --git a/crates/lobby/src/games/init.sql b/crates/lobby/src/games/init.sql index 8262b324..ba8cce22 100644 --- a/crates/lobby/src/games/init.sql +++ b/crates/lobby/src/games/init.sql @@ -22,3 +22,14 @@ CREATE TABLE IF NOT EXISTS players ( ON UPDATE CASCADE ON DELETE CASCADE ); + + +CREATE TRIGGER IF NOT EXISTS check_ordinal +BEFORE INSERT ON players +FOR EACH ROW +BEGIN + SELECT CASE + WHEN (SELECT max_players FROM games WHERE name = NEW.game) IS NOT NULL AND NEW.ordinal > (SELECT max_players FROM games WHERE name = NEW.game) + THEN RAISE(FAIL, 'TOO-LARGE-ORDINAL') + END; +END; diff --git a/crates/lobby_model/src/games.rs b/crates/lobby_model/src/games.rs index 6b995b35..c388e1bd 100644 --- a/crates/lobby_model/src/games.rs +++ b/crates/lobby_model/src/games.rs @@ -17,10 +17,10 @@ pub struct Game { } impl Game { - /// Creates a new game with the author having ordinal number of 0 and being + /// Creates a new game with the author having ordinal number of 1 and being /// the only player. pub fn from_author(setup: GameSetup, author: String) -> Self { - Self::new(setup, vec![GamePlayer::new(author, GamePlayerInfo::new(0))]) + Self::new(setup, vec![GamePlayer::new(author, GamePlayerInfo::new(1))]) } pub fn new(setup: GameSetup, players: Vec) -> Self { @@ -64,7 +64,11 @@ pub struct GamePlayerInfo { } impl GamePlayerInfo { + /// # Panics + /// + /// Panics if ordinal equal to 0 is used. pub fn new(ordinal: u8) -> Self { + assert!(ordinal > 0); Self { ordinal } } diff --git a/docs/src/multiplayer/openapi.yaml b/docs/src/multiplayer/openapi.yaml index 4225db16..b036b106 100644 --- a/docs/src/multiplayer/openapi.yaml +++ b/docs/src/multiplayer/openapi.yaml @@ -170,10 +170,10 @@ paths: properties: ordinal: type: integer - minimum: 0 + minimum: 1 description: >- Each player of the game has a unique number in the range - from 0 to N, where N is the maximum allowed number of + from 1 to N, where N is the maximum allowed number of players in the game. This parameter sets the number for the joining player. responses: @@ -189,7 +189,8 @@ paths: description: The game does not exist. "409": description: >- - Another player with the same ordinal has already joined the game. + The ordinal is too large or another player with the same ordinal + has already joined the game. /a/games/{name}/leave: put: