feat: order processing

feat: order commands result save/load
This commit is contained in:
Ilia Denisov
2026-02-20 17:44:41 +02:00
committed by GitHub
parent 0c0df976bd
commit 233c9ebc2a
26 changed files with 2028 additions and 385 deletions
+58 -56
View File
@@ -2,6 +2,7 @@ package handler
import (
"encoding/json"
"errors"
"fmt"
"net/http"
@@ -11,29 +12,30 @@ import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/iliadenisov/galaxy/internal/model/order"
"github.com/iliadenisov/galaxy/internal/model/rest"
)
func CommandHandler(c *gin.Context, executor CommandExecutor) {
var cmd rest.Command
if errorResponded(c, c.ShouldBindJSON(&cmd)) {
if errorResponse(c, c.ShouldBindJSON(&cmd)) {
return
}
commands := make([]Command, 0)
commands := make([]Command, len(cmd.Commands))
for i := range cmd.Commands {
command, err := parseCommand(cmd.Actor, cmd.Commands[i])
if errorResponded(c, err) {
if errorResponse(c, err) {
return
}
commands = append(commands, command)
commands[i] = command
}
if len(commands) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "no commands given"})
errorResponse(c, errors.New("no commands given"))
return
}
if errorResponded(c, executor.Execute(commands...)) {
if errorResponse(c, executor.Execute(commands...)) {
return
}
@@ -41,56 +43,56 @@ func CommandHandler(c *gin.Context, executor CommandExecutor) {
}
func parseCommand(actor string, c json.RawMessage) (Command, error) {
meta := new(rest.CommandMeta)
meta := new(order.CommandMeta)
if err := json.Unmarshal(c, meta); err != nil {
return nil, err
}
switch t := meta.Type; t {
case rest.CommandTypeRaceQuit:
switch t := meta.CmdType; t {
case order.CommandTypeRaceQuit:
return commandRaceQuit(actor)
case rest.CommandTypeRaceVote:
case order.CommandTypeRaceVote:
return commandRaceVote(actor, c)
case rest.CommandTypeRaceRelation:
case order.CommandTypeRaceRelation:
return commandRaceRelation(actor, c)
case rest.CommandTypeShipClassCreate:
case order.CommandTypeShipClassCreate:
return commandShipClassCreate(actor, c)
case rest.CommandTypeShipClassMerge:
case order.CommandTypeShipClassMerge:
return commandShipClassMerge(actor, c)
case rest.CommandTypeShipClassRemove:
case order.CommandTypeShipClassRemove:
return commandShipClassRemove(actor, c)
case rest.CommandTypeShipGroupBreak:
case order.CommandTypeShipGroupBreak:
return commandShipGroupBreak(actor, c)
case rest.CommandTypeShipGroupLoad:
case order.CommandTypeShipGroupLoad:
return commandShipGroupLoad(actor, c)
case rest.CommandTypeShipGroupUnload:
case order.CommandTypeShipGroupUnload:
return commandShipGroupUnload(actor, c)
case rest.CommandTypeShipGroupSend:
case order.CommandTypeShipGroupSend:
return commandShipGroupSend(actor, c)
case rest.CommandTypeShipGroupUpgrade:
case order.CommandTypeShipGroupUpgrade:
return commandShipGroupUpgrade(actor, c)
case rest.CommandTypeShipGroupMerge:
case order.CommandTypeShipGroupMerge:
return commandShipGroupMerge(actor, c)
case rest.CommandTypeShipGroupDismantle:
case order.CommandTypeShipGroupDismantle:
return commandShipGroupDismantle(actor, c)
case rest.CommandTypeShipGroupTransfer:
case order.CommandTypeShipGroupTransfer:
return commandShipGroupTransfer(actor, c)
case rest.CommandTypeShipGroupJoinFleet:
case order.CommandTypeShipGroupJoinFleet:
return commandShipGroupJoinFleet(actor, c)
case rest.CommandTypeFleetMerge:
case order.CommandTypeFleetMerge:
return commandFleetMerge(actor, c)
case rest.CommandTypeFleetSend:
case order.CommandTypeFleetSend:
return commandFleetSend(actor, c)
case rest.CommandTypeScienceCreate:
case order.CommandTypeScienceCreate:
return commandScienceCreate(actor, c)
case rest.CommandTypeScienceRemove:
case order.CommandTypeScienceRemove:
return commandScienceRemove(actor, c)
case rest.CommandTypePlanetRename:
case order.CommandTypePlanetRename:
return commandPlanetRename(actor, c)
case rest.CommandTypePlanetProduce:
case order.CommandTypePlanetProduce:
return commandPlanetProduce(actor, c)
case rest.CommandTypePlanetRouteSet:
case order.CommandTypePlanetRouteSet:
return commandPlanetRouteSet(actor, c)
case rest.CommandTypePlanetRouteRemove:
case order.CommandTypePlanetRouteRemove:
return commandPlanetRouteRemove(actor, c)
default:
return nil, fmt.Errorf("unknown comman type: %s", t)
@@ -102,7 +104,7 @@ func commandRaceQuit(actor string) (Command, error) {
}
func commandRaceVote(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandRaceVote)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandRaceVote)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -112,7 +114,7 @@ func commandRaceVote(actor string, c json.RawMessage) (Command, error) {
}
func commandRaceRelation(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandRaceRelation)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandRaceRelation)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -122,7 +124,7 @@ func commandRaceRelation(actor string, c json.RawMessage) (Command, error) {
}
func commandShipClassCreate(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandShipClassCreate)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandShipClassCreate)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -132,7 +134,7 @@ func commandShipClassCreate(actor string, c json.RawMessage) (Command, error) {
}
func commandShipClassMerge(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandShipClassMerge)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandShipClassMerge)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -142,7 +144,7 @@ func commandShipClassMerge(actor string, c json.RawMessage) (Command, error) {
}
func commandShipClassRemove(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandShipClassRemove)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandShipClassRemove)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -152,7 +154,7 @@ func commandShipClassRemove(actor string, c json.RawMessage) (Command, error) {
}
func commandShipGroupBreak(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupBreak)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandShipGroupBreak)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -162,7 +164,7 @@ func commandShipGroupBreak(actor string, c json.RawMessage) (Command, error) {
}
func commandShipGroupLoad(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupLoad)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandShipGroupLoad)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -172,7 +174,7 @@ func commandShipGroupLoad(actor string, c json.RawMessage) (Command, error) {
}
func commandShipGroupUnload(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupUnload)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandShipGroupUnload)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -182,7 +184,7 @@ func commandShipGroupUnload(actor string, c json.RawMessage) (Command, error) {
}
func commandShipGroupSend(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupSend)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandShipGroupSend)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -192,7 +194,7 @@ func commandShipGroupSend(actor string, c json.RawMessage) (Command, error) {
}
func commandShipGroupUpgrade(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupUpgrade)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandShipGroupUpgrade)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -208,7 +210,7 @@ func commandShipGroupMerge(actor string, c json.RawMessage) (Command, error) {
}
func commandShipGroupDismantle(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupDismantle)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandShipGroupDismantle)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -218,7 +220,7 @@ func commandShipGroupDismantle(actor string, c json.RawMessage) (Command, error)
}
func commandShipGroupTransfer(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupTransfer)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandShipGroupTransfer)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -228,7 +230,7 @@ func commandShipGroupTransfer(actor string, c json.RawMessage) (Command, error)
}
func commandShipGroupJoinFleet(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandShipGroupJoinFleet)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandShipGroupJoinFleet)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -238,7 +240,7 @@ func commandShipGroupJoinFleet(actor string, c json.RawMessage) (Command, error)
}
func commandFleetMerge(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandFleetMerge)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandFleetMerge)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -248,7 +250,7 @@ func commandFleetMerge(actor string, c json.RawMessage) (Command, error) {
}
func commandFleetSend(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandFleetSend)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandFleetSend)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -258,7 +260,7 @@ func commandFleetSend(actor string, c json.RawMessage) (Command, error) {
}
func commandScienceCreate(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandScienceCreate)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandScienceCreate)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -268,7 +270,7 @@ func commandScienceCreate(actor string, c json.RawMessage) (Command, error) {
}
func commandScienceRemove(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandScienceRemove)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandScienceRemove)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -278,7 +280,7 @@ func commandScienceRemove(actor string, c json.RawMessage) (Command, error) {
}
func commandPlanetRename(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandPlanetRename)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandPlanetRename)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -288,7 +290,7 @@ func commandPlanetRename(actor string, c json.RawMessage) (Command, error) {
}
func commandPlanetProduce(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandPlanetProduce)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandPlanetProduce)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -298,7 +300,7 @@ func commandPlanetProduce(actor string, c json.RawMessage) (Command, error) {
}
func commandPlanetRouteSet(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandPlanetRouteSet)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandPlanetRouteSet)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -308,7 +310,7 @@ func commandPlanetRouteSet(actor string, c json.RawMessage) (Command, error) {
}
func commandPlanetRouteRemove(actor string, c json.RawMessage) (Command, error) {
if v, err := unmarshallCommand(c, new(rest.CommandPlanetRouteRemove)); err != nil {
if v, err := unmarshallCommand(c, new(order.CommandPlanetRouteRemove)); err != nil {
return nil, err
} else {
return func(c controller.Ctrl) error {
@@ -319,17 +321,17 @@ func commandPlanetRouteRemove(actor string, c json.RawMessage) (Command, error)
// Helpers
func unmarshallCommand[T rest.DecodableCommand](c json.RawMessage, v *T) (*T, error) {
func unmarshallCommand[T order.DecodableCommand](c json.RawMessage, v T) (T, error) {
if err := json.Unmarshal(c, v); err != nil {
return nil, err
return v, err
}
if err := validateCommand(v); err != nil {
return nil, err
return v, err
}
return v, nil
}
func validateCommand(v any) error {
func validateCommand(v order.DecodableCommand) error {
if ve, ok := binding.Validator.Engine().(*validator.Validate); ok {
if err := ve.Struct(v); err != nil {
return err
+11 -7
View File
@@ -10,6 +10,7 @@ import (
"github.com/iliadenisov/galaxy/internal/controller"
e "github.com/iliadenisov/galaxy/internal/error"
"github.com/iliadenisov/galaxy/internal/model/game"
"github.com/iliadenisov/galaxy/internal/model/order"
"github.com/iliadenisov/galaxy/internal/model/rest"
)
@@ -18,6 +19,7 @@ type CommandExecutor interface {
GenerateTurn() (rest.StateResponse, error)
GameState() (rest.StateResponse, error)
Execute(cmd ...Command) error
ValidateOrder(actor string, cmd ...order.DecodableCommand) error
}
type Command func(controller.Ctrl) error
@@ -40,10 +42,10 @@ func NewDefaultConfigExecutor(configurer controller.Configurer) CommandExecutor
return &executor{cfg: configurer}
}
func (e *executor) Execute(command ...Command) error {
func (e *executor) Execute(cmd ...Command) error {
return controller.ExecuteCommand(e.cfg, func(c controller.Ctrl) error {
for i := range command {
if err := command[i](c); err != nil {
for i := range cmd {
if err := cmd[i](c); err != nil {
return err
}
}
@@ -51,6 +53,10 @@ func (e *executor) Execute(command ...Command) error {
})
}
func (e *executor) ValidateOrder(actor string, cmd ...order.DecodableCommand) error {
return controller.ValidateOrder(e.cfg, actor, cmd...)
}
func (e *executor) GenerateGame(races []string) (rest.StateResponse, error) {
s, err := controller.GenerateGame(e.cfg, races)
if err != nil {
@@ -90,19 +96,17 @@ func stateResponse(s game.State) rest.StateResponse {
return *result
}
func errorResponded(c *gin.Context, err error) bool {
func errorResponse(c *gin.Context, err error) bool {
if err == nil {
return false
}
var ge = new(e.GenericError)
if v, ok := err.(validator.ValidationErrors); ok {
c.JSON(http.StatusBadRequest, gin.H{"error": v.Error()})
return true
}
if errors.As(err, ge) {
if ge, ok := errors.AsType[*e.GenericError](err); ok {
switch ge.Code {
case e.ErrGameNotInitialized:
c.Status(http.StatusNotImplemented)
+2 -2
View File
@@ -9,7 +9,7 @@ import (
func InitHandler(c *gin.Context, executor CommandExecutor) {
var init rest.Init
if errorResponded(c, c.ShouldBindJSON(&init)) {
if errorResponse(c, c.ShouldBindJSON(&init)) {
return
}
@@ -19,7 +19,7 @@ func InitHandler(c *gin.Context, executor CommandExecutor) {
}
s, err := executor.GenerateGame(races)
if errorResponded(c, err) {
if errorResponse(c, err) {
return
}
+37
View File
@@ -0,0 +1,37 @@
package handler
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/iliadenisov/galaxy/internal/model/order"
"github.com/iliadenisov/galaxy/internal/model/rest"
"github.com/iliadenisov/galaxy/internal/repo"
)
func OrderHandler(c *gin.Context, executor CommandExecutor) {
var cmd rest.Command
if errorResponse(c, c.ShouldBindJSON(&cmd)) {
return
}
commands := make([]order.DecodableCommand, len(cmd.Commands))
for i := range cmd.Commands {
command, err := repo.ParseOrder(cmd.Commands[i], validateCommand)
if errorResponse(c, err) {
return
}
commands[i] = command
}
if len(commands) == 0 {
errorResponse(c, errors.New("no commands given"))
return
}
if errorResponse(c, executor.ValidateOrder(cmd.Actor, commands...)) {
return
}
c.Status(http.StatusNoContent)
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
func StatusHandler(c *gin.Context, executor CommandExecutor) {
state, err := executor.GameState()
if errorResponded(c, err) {
if errorResponse(c, err) {
return
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
func TurnHandler(c *gin.Context, executor CommandExecutor) {
state, err := executor.GenerateTurn()
if errorResponded(c, err) {
if errorResponse(c, err) {
return
}