diff --git a/apps/server/src/logging/logEvent.ts b/apps/server/src/logging/logEvent.ts index 7bf8083..90c67cd 100644 --- a/apps/server/src/logging/logEvent.ts +++ b/apps/server/src/logging/logEvent.ts @@ -3,7 +3,6 @@ * 共通ログ出力で利用するイベントログ関数を提供する */ import type { LogPayloadByScope, LogScope } from "./logEvents"; -import type { LogRequiredFieldsByScope } from "./logEvents"; type LogEventPayload = { socketId?: string; @@ -12,9 +11,11 @@ }; /** スコープ名とイベント情報を標準出力へ記録する */ -export const logEvent = ( +export const logEvent = < + TScope extends LogScope, +>( scope: TScope, - payload: LogEventPayload & LogPayloadByScope[TScope] & LogRequiredFieldsByScope[TScope] + payload: LogEventPayload & LogPayloadByScope[TScope] ) => { console.log(`[${scope}]`, payload); }; diff --git a/apps/server/src/logging/logEvents.ts b/apps/server/src/logging/logEvents.ts index c14dd1c..d672902 100644 --- a/apps/server/src/logging/logEvents.ts +++ b/apps/server/src/logging/logEvents.ts @@ -17,189 +17,6 @@ ROOM_EXIT_SERVICE: "RoomExitService", } as const; -/** ログ出力で利用するスコープ名型 */ -export type LogScope = (typeof logScopes)[keyof typeof logScopes]; - -type ValueOf = T[keyof T]; - -/** Networkスコープで利用可能なイベント名型 */ -export type NetworkLogEvent = ValueOf; - -/** Networkスコープで利用可能な結果値型 */ -export type NetworkLogResult = - | "connected" - | "disconnected" - | "rejected_room_full" - | "rejected_duplicate" - | "ignored_invalid_payload"; - -/** GameUseCaseスコープで利用可能なイベント名型 */ -export type GameUseCaseLogEvent = - | "start-game" - | "ready-for-game" - | "game-start" - | "game-end" - | "disconnect"; - -/** GameUseCaseスコープで利用可能な結果値型 */ -export type GameUseCaseLogResult = - | "ignored_no_room" - | "ignored_already_playing" - | "ignored_room_not_found" - | "ignored_missing_room" - | "accepted" - | "received" - | "emitted" - | "player_removed"; - -/** RoomUseCaseスコープで利用可能なイベント名型 */ -export type RoomUseCaseLogEvent = - | "join-room" - | "disconnect" - | "room-update"; - -/** RoomUseCaseスコープで利用可能な結果値型 */ -export type RoomUseCaseLogResult = - | "received" - | "rejected" - | "processed" - | "emitted"; - -/** GameLoopスコープで利用可能なイベント名型 */ -export type GameLoopLogEvent = "GAME_LOOP"; - -/** GameLoopスコープで利用可能な結果値型 */ -export type GameLoopLogResult = - | "started" - | "stopped"; - -/** GameRoomSessionスコープで利用可能なイベント名型 */ -export type GameRoomSessionLogEvent = "move"; - -/** GameRoomSessionスコープで利用可能な結果値型 */ -export type GameRoomSessionLogResult = - | "ignored_player_not_found" - | "ignored_invalid_payload"; - -/** GamePlayerOperationServiceスコープで利用可能なイベント名型 */ -export type GamePlayerOperationServiceLogEvent = - | "PLAYER_MOVE" - | "PLAYER_REMOVE"; - -/** GamePlayerOperationServiceスコープで利用可能な結果値型 */ -export type GamePlayerOperationServiceLogResult = - | "ignored_player_not_in_session" - | "session_disposed_empty_room"; - -/** GameSessionLifecycleServiceスコープで利用可能なイベント名型 */ -export type GameSessionLifecycleServiceLogEvent = "SESSION_START"; - -/** GameSessionLifecycleServiceスコープで利用可能な結果値型 */ -export type GameSessionLifecycleServiceLogResult = - | "ignored_already_running" - | "started"; - -/** RoomJoinServiceスコープで利用可能なイベント名型 */ -export type RoomJoinServiceLogEvent = - | "ROOM_CREATE" - | "PLAYER_JOIN"; - -/** RoomJoinServiceスコープで利用可能な結果値型 */ -export type RoomJoinServiceLogResult = - | "created" - | "ignored_duplicate" - | "ignored_room_full" - | "joined"; - -/** RoomExitServiceスコープで利用可能なイベント名型 */ -export type RoomExitServiceLogEvent = - | "PLAYER_LEAVE" - | "ROOM_DELETE" - | "OWNER_TRANSFER"; - -/** RoomExitServiceスコープで利用可能な結果値型 */ -export type RoomExitServiceLogResult = - | "removed" - | "deleted" - | "transferred"; - -/** スコープごとの event/result 型契約 */ -export type LogPayloadByScope = { - [logScopes.NETWORK]: { - event: NetworkLogEvent; - result: NetworkLogResult; - }; - [logScopes.GAME_USE_CASE]: { - event: GameUseCaseLogEvent; - result: GameUseCaseLogResult; - }; - [logScopes.ROOM_USE_CASE]: { - event: RoomUseCaseLogEvent; - result: RoomUseCaseLogResult; - }; - [logScopes.GAME_LOOP]: { - event: GameLoopLogEvent; - result: GameLoopLogResult; - }; - [logScopes.GAME_ROOM_SESSION]: { - event: GameRoomSessionLogEvent; - result: GameRoomSessionLogResult; - }; - [logScopes.GAME_PLAYER_OPERATION_SERVICE]: { - event: GamePlayerOperationServiceLogEvent; - result: GamePlayerOperationServiceLogResult; - }; - [logScopes.GAME_SESSION_LIFECYCLE_SERVICE]: { - event: GameSessionLifecycleServiceLogEvent; - result: GameSessionLifecycleServiceLogResult; - }; - [logScopes.ROOM_JOIN_SERVICE]: { - event: RoomJoinServiceLogEvent; - result: RoomJoinServiceLogResult; - }; - [logScopes.ROOM_EXIT_SERVICE]: { - event: RoomExitServiceLogEvent; - result: RoomExitServiceLogResult; - }; -}; - -/** スコープごとの必須追加フィールド型契約 */ -export type LogRequiredFieldsByScope = { - [logScopes.NETWORK]: { - socketId: string; - }; - [logScopes.GAME_USE_CASE]: { - socketId?: string; - roomId?: string; - }; - [logScopes.ROOM_USE_CASE]: { - socketId: string; - roomId?: string; - }; - [logScopes.GAME_LOOP]: { - roomId: string; - }; - [logScopes.GAME_ROOM_SESSION]: { - roomId: string; - socketId: string; - }; - [logScopes.GAME_PLAYER_OPERATION_SERVICE]: { - socketId: string; - roomId?: string; - }; - [logScopes.GAME_SESSION_LIFECYCLE_SERVICE]: { - roomId: string; - }; - [logScopes.ROOM_JOIN_SERVICE]: { - roomId: string; - socketId: string; - }; - [logScopes.ROOM_EXIT_SERVICE]: { - roomId: string; - socketId: string; - }; -}; - /** GameUseCaseログで利用するイベント名定数 */ export const gameUseCaseLogEvents = { START_GAME: protocol.SocketEvents.START_GAME, @@ -265,3 +82,168 @@ STOPPED: "stopped", TRANSFERRED: "transferred", } as const; + +/** ログ出力で利用するスコープ名型 */ +export type LogScope = (typeof logScopes)[keyof typeof logScopes]; + +type ValueOf = T[keyof T]; + +/** スコープごとの event/result と必須項目の型契約 */ +export type LogPayloadByScope = { + [logScopes.NETWORK]: + | { + event: typeof protocol.SocketEvents.CONNECT; + result: typeof logResults.CONNECTED; + socketId: string; + } + | { + event: typeof protocol.SocketEvents.DISCONNECT; + result: typeof logResults.DISCONNECTED; + socketId: string; + } + | { + event: typeof protocol.SocketEvents.JOIN_ROOM; + result: + | typeof logResults.REJECTED_ROOM_FULL + | typeof logResults.REJECTED_DUPLICATE + | typeof logResults.IGNORED_INVALID_PAYLOAD; + socketId: string; + roomId?: string; + } + | { + event: typeof protocol.SocketEvents.PING; + result: typeof logResults.IGNORED_INVALID_PAYLOAD; + socketId: string; + } + | { + event: typeof protocol.SocketEvents.MOVE; + result: typeof logResults.IGNORED_INVALID_PAYLOAD; + socketId: string; + }; + [logScopes.GAME_USE_CASE]: + | { + event: typeof gameUseCaseLogEvents.START_GAME; + result: + | typeof logResults.IGNORED_NO_ROOM + | typeof logResults.IGNORED_ALREADY_PLAYING + | typeof logResults.IGNORED_ROOM_NOT_FOUND + | typeof logResults.ACCEPTED; + socketId: string; + roomId?: string; + } + | { + event: typeof gameUseCaseLogEvents.READY_FOR_GAME; + result: + | typeof logResults.IGNORED_MISSING_ROOM + | typeof logResults.RECEIVED; + socketId: string; + roomId?: string; + } + | { + event: typeof gameUseCaseLogEvents.GAME_START; + result: typeof logResults.EMITTED; + socketId: string; + roomId: string; + } + | { + event: typeof gameUseCaseLogEvents.GAME_END; + result: typeof logResults.EMITTED; + roomId: string; + } + | { + event: typeof gameUseCaseLogEvents.DISCONNECT; + result: typeof logResults.PLAYER_REMOVED; + socketId: string; + }; + [logScopes.ROOM_USE_CASE]: + | { + event: typeof roomUseCaseLogEvents.JOIN_ROOM; + result: + | typeof logResults.RECEIVED + | typeof logResults.REJECTED; + socketId: string; + roomId: string; + } + | { + event: typeof roomUseCaseLogEvents.DISCONNECT; + result: typeof logResults.PROCESSED; + socketId: string; + } + | { + event: typeof roomUseCaseLogEvents.ROOM_UPDATE; + result: typeof logResults.EMITTED; + socketId: string; + roomId: string; + }; + [logScopes.GAME_LOOP]: { + event: typeof gameDomainLogEvents.GAME_LOOP; + result: + | typeof logResults.STARTED + | typeof logResults.STOPPED; + roomId: string; + }; + [logScopes.GAME_ROOM_SESSION]: { + event: typeof gameDomainLogEvents.MOVE; + result: + | typeof logResults.IGNORED_PLAYER_NOT_FOUND + | typeof logResults.IGNORED_INVALID_PAYLOAD; + roomId: string; + socketId: string; + }; + [logScopes.GAME_PLAYER_OPERATION_SERVICE]: + | { + event: typeof gameDomainLogEvents.PLAYER_MOVE; + result: typeof logResults.IGNORED_PLAYER_NOT_IN_SESSION; + socketId: string; + } + | { + event: typeof gameDomainLogEvents.PLAYER_REMOVE; + result: + | typeof logResults.IGNORED_PLAYER_NOT_IN_SESSION + | typeof logResults.SESSION_DISPOSED_EMPTY_ROOM; + socketId: string; + roomId?: string; + }; + [logScopes.GAME_SESSION_LIFECYCLE_SERVICE]: { + event: typeof gameDomainLogEvents.SESSION_START; + result: + | typeof logResults.IGNORED_ALREADY_RUNNING + | typeof logResults.STARTED; + roomId: string; + }; + [logScopes.ROOM_JOIN_SERVICE]: + | { + event: typeof roomDomainLogEvents.ROOM_CREATE; + result: typeof logResults.CREATED; + roomId: string; + socketId: string; + } + | { + event: typeof roomDomainLogEvents.PLAYER_JOIN; + result: + | typeof logResults.IGNORED_DUPLICATE + | typeof logResults.IGNORED_ROOM_FULL + | typeof logResults.JOINED; + roomId: string; + socketId: string; + }; + [logScopes.ROOM_EXIT_SERVICE]: + | { + event: typeof roomDomainLogEvents.PLAYER_LEAVE; + result: typeof logResults.REMOVED; + roomId: string; + socketId: string; + } + | { + event: typeof roomDomainLogEvents.ROOM_DELETE; + result: typeof logResults.DELETED; + roomId: string; + socketId: string; + } + | { + event: typeof roomDomainLogEvents.OWNER_TRANSFER; + result: typeof logResults.TRANSFERRED; + roomId: string; + socketId: string; + }; +}; diff --git a/apps/server/src/network/handlers/payloadGuard.ts b/apps/server/src/network/handlers/payloadGuard.ts index 6aa24ae..21852ea 100644 --- a/apps/server/src/network/handlers/payloadGuard.ts +++ b/apps/server/src/network/handlers/payloadGuard.ts @@ -7,15 +7,18 @@ import { logResults, logScopes } from "@server/logging/logEvents"; type PayloadValidator = (value: unknown) => value is TPayload; -type SocketEventName = (typeof protocol.SocketEvents)[keyof typeof protocol.SocketEvents]; +type PayloadGuardEventName = + | typeof protocol.SocketEvents.JOIN_ROOM + | typeof protocol.SocketEvents.PING + | typeof protocol.SocketEvents.MOVE; type EventBoundPayloadGuard = (payload: unknown) => payload is TPayload; /** * 受信ペイロードを検証し,不正時は共通ログを記録するガード関数を生成する */ export const createPayloadGuard = (socketId: string) => { - const isValidPayload = ( - event: SocketEventName, + const isValidPayload = ( + event: TEvent, payload: unknown, validator: PayloadValidator ): payload is TPayload => { @@ -23,17 +26,37 @@ return true; } - logEvent(logScopes.NETWORK, { - event, - result: logResults.IGNORED_INVALID_PAYLOAD, - socketId, - }); + switch (event) { + case protocol.SocketEvents.JOIN_ROOM: + logEvent(logScopes.NETWORK, { + event: protocol.SocketEvents.JOIN_ROOM, + result: logResults.IGNORED_INVALID_PAYLOAD, + socketId, + }); + break; + + case protocol.SocketEvents.PING: + logEvent(logScopes.NETWORK, { + event: protocol.SocketEvents.PING, + result: logResults.IGNORED_INVALID_PAYLOAD, + socketId, + }); + break; + + case protocol.SocketEvents.MOVE: + logEvent(logScopes.NETWORK, { + event: protocol.SocketEvents.MOVE, + result: logResults.IGNORED_INVALID_PAYLOAD, + socketId, + }); + break; + } return false; }; - const guardOnEvent = ( - event: SocketEventName, + const guardOnEvent = ( + event: TEvent, validator: PayloadValidator ): EventBoundPayloadGuard => { return (payload: unknown): payload is TPayload => {