fix(game): #59 — per-command rejection on PUT /api/v1/order
Validation of a player's order now applies every command against a transient game-state snapshot and records the per-command outcome (cmdApplied, cmdErrorCode) in each command's meta. The order is persisted even when some commands are rejected, and the response is 202 + UserGamesOrder so clients can surface the partial failure without the chain collapsing into "downstream service is unavailable". Pkg/error consts are reshelved onto three explicit ranges with a package doc and helpers (IsInternalCode/IsInputCode/IsGameStateCode): 1xxx internal/server (500/501), 2xxx structural input (400), 3xxx game-state per-command rejection (400 when escaping HTTP, otherwise recorded as cmdErrorCode). Two pre-existing typos fixed mechanically (ErrBeakGroupNumberNotEnough -> ErrBreakGroupNumberNotEnough, ErrRaceExinct -> ErrRaceExtinct) along with all callsites. Engine errorResponse maps *GenericError by shelf rather than mapping everything to 500. The Quit-not-last structural check in Controller.ValidateOrder is preserved and its type assertion fixed (was a value assertion against a pointer-typed command, so the check silently never fired). Backend, gateway and UI are unchanged — they were already correct on the 202 path; only the engine collapsing per-command rejection into 500 was needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+45
-10
@@ -16,9 +16,19 @@ info:
|
||||
`504 Gateway Timeout`
|
||||
- `501 Not Implemented` is returned without a body when the game has not
|
||||
been initialized
|
||||
- validation errors return `400` with `{"error": "message"}`
|
||||
- game-engine errors return `500` with `{"generic_error": "message", "code": integer}`
|
||||
- other internal errors return `500` with `{"error": "message"}`
|
||||
- request-binding validation errors return `400` with `{"error": "message"}`
|
||||
- structural input errors and game-state rejections that escape to
|
||||
HTTP return `400` with `{"generic_error": "message", "code": integer}`;
|
||||
the `code` carries the engine's `GenericError` code (see
|
||||
`pkg/error/generic.go` — shelf `2xxx` for structural input and
|
||||
`3xxx` for game-state rejection)
|
||||
- on `PUT /api/v1/order`, game-state rejections do not become HTTP
|
||||
errors; the engine returns `202 Accepted` with the full
|
||||
`UserGamesOrder` body and reports the failure on the offending
|
||||
command via `cmdApplied=false` and `cmdErrorCode=<integer>`
|
||||
- internal engine failures return `500` with
|
||||
`{"generic_error": "message", "code": integer}` (code on shelf `1xxx`)
|
||||
or `{"error": "message"}` for unclassified failures
|
||||
servers:
|
||||
- url: http://localhost:8080
|
||||
description: Default local listener for Game Service.
|
||||
@@ -161,10 +171,22 @@ paths:
|
||||
operationId: validateOrder
|
||||
summary: Validate and store a player order without executing it
|
||||
description: |
|
||||
Validates and stores the game commands structurally without executing them.
|
||||
On success returns `202 Accepted` with the stored order, including the
|
||||
engine-assigned `updatedAt` timestamp used by clients to detect stale
|
||||
submissions.
|
||||
Validates and stores the game commands without executing them. The
|
||||
engine applies each command in submission order against a transient
|
||||
view of the game state, records the per-command outcome on the
|
||||
command's meta, and persists the resulting `UserGamesOrder` so
|
||||
clients can reload the same per-command verdict via
|
||||
`GET /api/v1/order`.
|
||||
|
||||
On success returns `202 Accepted` with the stored order; the
|
||||
engine-assigned `updatedAt` timestamp is used by clients to detect
|
||||
stale submissions. Game-state rejections (e.g. a "produce ship of
|
||||
class X" command after class X was removed) are reported per
|
||||
command via `cmdApplied=false` and `cmdErrorCode=<integer>` inside
|
||||
the same `202` response — they do **not** become a `400` or `500`
|
||||
on the whole order. Order-level structural rejections (e.g. a
|
||||
`quit` command that is not the last command in the order) return
|
||||
`400`.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -173,7 +195,10 @@ paths:
|
||||
$ref: "#/components/schemas/CommandRequest"
|
||||
responses:
|
||||
"202":
|
||||
description: Order is structurally valid and stored.
|
||||
description: |
|
||||
Order is stored. Each entry of `cmd` carries `cmdApplied` and
|
||||
`cmdErrorCode` describing the per-command outcome; the order
|
||||
is considered stored even when some commands were rejected.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -481,10 +506,20 @@ components:
|
||||
description: Unique command identifier (RFC 4122 UUID).
|
||||
cmdApplied:
|
||||
type: boolean
|
||||
description: Set in command-result responses; true when the command was applied.
|
||||
description: |
|
||||
Per-command outcome. Set by the engine in every response that
|
||||
carries a stored or freshly-validated order (`PUT /api/v1/order`
|
||||
and `GET /api/v1/order`): `true` when the command was applied
|
||||
against the engine state during validation or turn generation,
|
||||
`false` when it was rejected (see `cmdErrorCode`). Omitted on
|
||||
requests.
|
||||
cmdErrorCode:
|
||||
type: integer
|
||||
description: Set in command-result responses; non-zero when the command was rejected.
|
||||
description: |
|
||||
Per-command error code. Set by the engine alongside `cmdApplied`:
|
||||
`0` when the command was applied, a non-zero `GenericError`
|
||||
code (shelves `2xxx`/`3xxx` in `pkg/error/generic.go`) when
|
||||
the command was rejected. Omitted on requests.
|
||||
CommandType:
|
||||
type: string
|
||||
description: Discriminator identifying the game command variant carried in a `cmd` element.
|
||||
|
||||
Reference in New Issue
Block a user