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:
@@ -13,17 +13,24 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func (c *Controller) ValidateOrder(actor string, commands ...order.DecodableCommand) (err error) {
|
||||
// ValidateOrder applies every command in the order against a transient
|
||||
// view of the engine state, records the per-command outcome in each
|
||||
// command's CommandMeta via applyCommand, and reports only order-level
|
||||
// structural errors as the function return. Per-command rejections are
|
||||
// surfaced through CommandMeta.Result so the caller can persist and
|
||||
// forward them as `cmdApplied`/`cmdErrorCode` in the response body.
|
||||
func (c *Controller) ValidateOrder(actor string, commands ...order.DecodableCommand) error {
|
||||
for i := range commands {
|
||||
if _, ok := commands[i].(order.CommandRaceQuit); ok && i != len(commands)-1 {
|
||||
err = e.NewQuitCommandFollowedByCommandError()
|
||||
if _, ok := commands[i].(*order.CommandRaceQuit); ok && i != len(commands)-1 {
|
||||
return e.NewQuitCommandFollowedByCommandError()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = errors.Join(err, c.applyCommand(actor, commands[i]))
|
||||
// applyCommand never returns a non-GenericError outside of
|
||||
// programmer-error panics; the per-command code, if any, is
|
||||
// already recorded on the command's meta and must not abort
|
||||
// validation of the remaining commands in this order.
|
||||
_ = c.applyCommand(actor, commands[i])
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) applyCommand(actor string, cmd order.DecodableCommand) (err error) {
|
||||
|
||||
Reference in New Issue
Block a user