feat: status api
This commit is contained in:
@@ -22,6 +22,8 @@ type Repo interface {
|
|||||||
|
|
||||||
// LoadState retrieves game current state
|
// LoadState retrieves game current state
|
||||||
LoadState() (game.Game, error)
|
LoadState() (game.Game, error)
|
||||||
|
|
||||||
|
LoadStateSafe() (game.Game, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
@@ -50,7 +52,7 @@ func NewController(configure func(*Param)) (*Controller, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) ExecuteInit(consumer func(Repo)) error {
|
func (c *Controller) ExecuteState(consumer func(Repo)) error {
|
||||||
if err := c.Repo.Lock(); err != nil {
|
if err := c.Repo.Lock(); err != nil {
|
||||||
return fmt.Errorf("execute: lock failed: %s", err)
|
return fmt.Errorf("execute: lock failed: %s", err)
|
||||||
}
|
}
|
||||||
@@ -58,7 +60,7 @@ func (c *Controller) ExecuteInit(consumer func(Repo)) error {
|
|||||||
return c.Repo.Release()
|
return c.Repo.Release()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) Execute(consumer func(Repo, game.Game)) error {
|
func (c *Controller) ExecuteGame(consumer func(Repo, game.Game)) error {
|
||||||
if err := c.Repo.Lock(); err != nil {
|
if err := c.Repo.Lock(); err != nil {
|
||||||
return fmt.Errorf("execute: lock failed: %s", err)
|
return fmt.Errorf("execute: lock failed: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ErrDummy int = -1
|
ErrStorageFailure int = 1000 + iota
|
||||||
|
ErrGameNotInitialized
|
||||||
|
ErrGameStateInvalid
|
||||||
|
)
|
||||||
|
|
||||||
ErrStorageFailure int = 1000
|
const (
|
||||||
ErrGameStateInvalid int = 2000
|
ErrDummy int = -1
|
||||||
|
|
||||||
ErrDeleteShipTypeExistingGroup = 5000
|
ErrDeleteShipTypeExistingGroup = 5000
|
||||||
ErrDeleteShipTypePlanetProduction = 5001
|
ErrDeleteShipTypePlanetProduction = 5001
|
||||||
@@ -68,6 +71,8 @@ func GenericErrorText(code int) string {
|
|||||||
return "Dummy"
|
return "Dummy"
|
||||||
case ErrStorageFailure:
|
case ErrStorageFailure:
|
||||||
return "Storage failure"
|
return "Storage failure"
|
||||||
|
case ErrGameNotInitialized:
|
||||||
|
return "Game not yet initialized"
|
||||||
case ErrGameStateInvalid:
|
case ErrGameStateInvalid:
|
||||||
return "Invalid game state"
|
return "Invalid game state"
|
||||||
case ErrInputUnknownRace:
|
case ErrInputUnknownRace:
|
||||||
@@ -170,13 +175,13 @@ func GenericErrorText(code int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GenericError struct {
|
type GenericError struct {
|
||||||
code int
|
Code int
|
||||||
subject string
|
subject string
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ge GenericError) Error() string {
|
func (ge GenericError) Error() string {
|
||||||
msg := GenericErrorText(ge.code)
|
msg := GenericErrorText(ge.Code)
|
||||||
if ge.subject != "" {
|
if ge.subject != "" {
|
||||||
msg += ": " + ge.subject
|
msg += ": " + ge.subject
|
||||||
}
|
}
|
||||||
@@ -187,7 +192,7 @@ func (ge GenericError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newGenericError(code int, arg ...any) error {
|
func newGenericError(code int, arg ...any) error {
|
||||||
e := &GenericError{code: code}
|
e := &GenericError{Code: code}
|
||||||
if len(arg) > 0 {
|
if len(arg) > 0 {
|
||||||
i := 0
|
i := 0
|
||||||
switch arg[i].(type) {
|
switch arg[i].(type) {
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package error
|
package error
|
||||||
|
|
||||||
|
func NewGameNotInitializedError(arg ...any) error {
|
||||||
|
return newGenericError(ErrGameNotInitialized, arg...)
|
||||||
|
}
|
||||||
|
|
||||||
func NewGameStateError(arg ...any) error {
|
func NewGameStateError(arg ...any) error {
|
||||||
return newGenericError(ErrGameStateInvalid, arg...)
|
return newGenericError(ErrGameStateInvalid, arg...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
func JoinEqualGroups(configure func(*controller.Param), race string) (err error) {
|
func JoinEqualGroups(configure func(*controller.Param), race string) (err error) {
|
||||||
control(configure, func(c *controller.Controller) {
|
control(configure, func(c *controller.Controller) {
|
||||||
c.Execute(func(r controller.Repo, g game.Game) {
|
c.ExecuteGame(func(r controller.Repo, g game.Game) {
|
||||||
err = joinEqualGroups(r, g, race)
|
err = joinEqualGroups(r, g, race)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
func RenamePlanet(configure func(*controller.Param), race string, number int, name string) (err error) {
|
func RenamePlanet(configure func(*controller.Param), race string, number int, name string) (err error) {
|
||||||
control(configure, func(c *controller.Controller) {
|
control(configure, func(c *controller.Controller) {
|
||||||
c.Execute(func(r controller.Repo, g game.Game) {
|
c.ExecuteGame(func(r controller.Repo, g game.Game) {
|
||||||
err = renamePlanet(r, g, race, number, name)
|
err = renamePlanet(r, g, race, number, name)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
func PlanetProduction(configure func(*controller.Param), race string, planetNumber int, prodType, subject string) (err error) {
|
func PlanetProduction(configure func(*controller.Param), race string, planetNumber int, prodType, subject string) (err error) {
|
||||||
control(configure, func(c *controller.Controller) {
|
control(configure, func(c *controller.Controller) {
|
||||||
c.Execute(func(r controller.Repo, g game.Game) {
|
c.ExecuteGame(func(r controller.Repo, g game.Game) {
|
||||||
err = planetProduction(r, g, race, planetNumber, prodType, subject)
|
err = planetProduction(r, g, race, planetNumber, prodType, subject)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
func CreateScience(configure func(*controller.Param), race, typeName string, drive, weapons, shields, cargo float64) (err error) {
|
func CreateScience(configure func(*controller.Param), race, typeName string, drive, weapons, shields, cargo float64) (err error) {
|
||||||
control(configure, func(c *controller.Controller) {
|
control(configure, func(c *controller.Controller) {
|
||||||
c.Execute(func(r controller.Repo, g game.Game) {
|
c.ExecuteGame(func(r controller.Repo, g game.Game) {
|
||||||
err = createScience(r, g, race, typeName, drive, weapons, shields, cargo)
|
err = createScience(r, g, race, typeName, drive, weapons, shields, cargo)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -23,7 +23,7 @@ func createScience(r controller.Repo, g game.Game, race, typeName string, d, w,
|
|||||||
|
|
||||||
func DeleteScience(configure func(*controller.Param), race, typeName string) (err error) {
|
func DeleteScience(configure func(*controller.Param), race, typeName string) (err error) {
|
||||||
control(configure, func(c *controller.Controller) {
|
control(configure, func(c *controller.Controller) {
|
||||||
c.Execute(func(r controller.Repo, g game.Game) {
|
c.ExecuteGame(func(r controller.Repo, g game.Game) {
|
||||||
err = deleteScience(r, g, race, typeName)
|
err = deleteScience(r, g, race, typeName)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
func CreateShipType(configure func(*controller.Param), race, typeName string, drive, weapons, shields, cargo float64, armament int) (err error) {
|
func CreateShipType(configure func(*controller.Param), race, typeName string, drive, weapons, shields, cargo float64, armament int) (err error) {
|
||||||
control(configure, func(c *controller.Controller) {
|
control(configure, func(c *controller.Controller) {
|
||||||
c.Execute(func(r controller.Repo, g game.Game) {
|
c.ExecuteGame(func(r controller.Repo, g game.Game) {
|
||||||
err = createShipType(r, g, race, typeName, drive, weapons, shields, cargo, armament)
|
err = createShipType(r, g, race, typeName, drive, weapons, shields, cargo, armament)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -23,7 +23,7 @@ func createShipType(r controller.Repo, g game.Game, race, typeName string, d, w,
|
|||||||
|
|
||||||
func MergeShipType(configure func(*controller.Param), race, source, target string) (err error) {
|
func MergeShipType(configure func(*controller.Param), race, source, target string) (err error) {
|
||||||
control(configure, func(c *controller.Controller) {
|
control(configure, func(c *controller.Controller) {
|
||||||
c.Execute(func(r controller.Repo, g game.Game) {
|
c.ExecuteGame(func(r controller.Repo, g game.Game) {
|
||||||
err = mergeShipType(r, g, race, source, target)
|
err = mergeShipType(r, g, race, source, target)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -39,7 +39,7 @@ func mergeShipType(r controller.Repo, g game.Game, race, source, target string)
|
|||||||
|
|
||||||
func DeleteShipType(configure func(*controller.Param), race, typeName string) (err error) {
|
func DeleteShipType(configure func(*controller.Param), race, typeName string) (err error) {
|
||||||
control(configure, func(c *controller.Controller) {
|
control(configure, func(c *controller.Controller) {
|
||||||
c.Execute(func(r controller.Repo, g game.Game) {
|
c.ExecuteGame(func(r controller.Repo, g game.Game) {
|
||||||
err = deleteShipType(r, g, race, typeName)
|
err = deleteShipType(r, g, race, typeName)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import (
|
|||||||
|
|
||||||
func DeclareWar(configure func(*controller.Param), from, to string) (err error) {
|
func DeclareWar(configure func(*controller.Param), from, to string) (err error) {
|
||||||
control(configure, func(c *controller.Controller) {
|
control(configure, func(c *controller.Controller) {
|
||||||
c.Execute(func(r controller.Repo, g game.Game) { err = updateRelation(r, g, from, to, game.RelationWar) })
|
c.ExecuteGame(func(r controller.Repo, g game.Game) { err = updateRelation(r, g, from, to, game.RelationWar) })
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeclarePeace(configure func(*controller.Param), from, to string) (err error) {
|
func DeclarePeace(configure func(*controller.Param), from, to string) (err error) {
|
||||||
control(configure, func(c *controller.Controller) {
|
control(configure, func(c *controller.Controller) {
|
||||||
c.Execute(func(r controller.Repo, g game.Game) { err = updateRelation(r, g, from, to, game.RelationPeace) })
|
c.ExecuteGame(func(r controller.Repo, g game.Game) { err = updateRelation(r, g, from, to, game.RelationPeace) })
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,23 @@ import (
|
|||||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LoadState(configure func(*controller.Param)) (g game.Game, err error) {
|
func GenerateGame(configure func(*controller.Param), races []string) (gameID uuid.UUID, err error) {
|
||||||
control(configure, func(c *controller.Controller) { c.ExecuteInit(func(r controller.Repo) { g, err = c.Repo.LoadState() }) })
|
control(configure, func(c *controller.Controller) {
|
||||||
|
c.ExecuteState(func(r controller.Repo) { gameID, err = controller.NewGame(r, races) })
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateGame(configure func(*controller.Param), races []string) (gameID uuid.UUID, err error) {
|
// LoadState used for lock-safe loading game state and may be called concurrently.
|
||||||
|
func LoadState(configure func(*controller.Param)) (g game.Game, err error) {
|
||||||
|
control(configure, func(c *controller.Controller) { g, err = c.Repo.LoadStateSafe() })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: command for loading report by players (MUST be limited by router)
|
||||||
|
func LoadReport(configure func(*controller.Param)) (g game.Game, err error) {
|
||||||
control(configure, func(c *controller.Controller) {
|
control(configure, func(c *controller.Controller) {
|
||||||
c.ExecuteInit(func(r controller.Repo) { gameID, err = controller.NewGame(r, races) })
|
c.ExecuteState(func(r controller.Repo) { g, err = c.Repo.LoadState() })
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,6 +122,11 @@ func (g Game) deleteShipTypeInternal(ri int, name string) error {
|
|||||||
}); pl >= 0 {
|
}); pl >= 0 {
|
||||||
return e.NewDeleteShipTypePlanetProductionError(g.Map.Planet[pl].Name)
|
return e.NewDeleteShipTypePlanetProductionError(g.Map.Planet[pl].Name)
|
||||||
}
|
}
|
||||||
|
for sg := range g.listShipGroups(ri) {
|
||||||
|
if sg.TypeID == g.Race[ri].ShipTypes[st].ID {
|
||||||
|
return e.NewDeleteShipTypeExistingGroupError("group: %v", sg.Index)
|
||||||
|
}
|
||||||
|
}
|
||||||
g.Race[ri].ShipTypes = append(g.Race[ri].ShipTypes[:st], g.Race[ri].ShipTypes[st+1:]...)
|
g.Race[ri].ShipTypes = append(g.Race[ri].ShipTypes[:st], g.Race[ri].ShipTypes[st+1:]...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
type Status struct {
|
||||||
|
Turn uint `json:"turn"`
|
||||||
|
Players int `json:"players"`
|
||||||
|
}
|
||||||
+34
-5
@@ -161,15 +161,44 @@ func (f *fs) Write(path string, v encoding.BinaryMarshaler) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *fs) Read(path string, v encoding.BinaryUnmarshaler) error {
|
func (f *fs) Read(path string, v encoding.BinaryUnmarshaler) error {
|
||||||
if v == nil {
|
|
||||||
return errors.New("can't unmarshal to a nil object")
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.lock == nil {
|
if f.lock == nil {
|
||||||
return errors.New("lock must be acquired before read")
|
return errors.New("lock must be acquired before read")
|
||||||
}
|
}
|
||||||
|
|
||||||
targetFilePath := filepath.Join(f.root, path)
|
return f.readUnsafe(path, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fs) ReadSafe(path string, v encoding.BinaryUnmarshaler) error {
|
||||||
|
if f.lock != nil {
|
||||||
|
timeout := time.NewTimer(time.Millisecond * 100)
|
||||||
|
checker := time.NewTicker(time.Millisecond)
|
||||||
|
out:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-checker.C:
|
||||||
|
if f.lock == nil {
|
||||||
|
checker.Stop()
|
||||||
|
timeout.Stop()
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
case <-timeout.C:
|
||||||
|
checker.Stop()
|
||||||
|
return errors.New("lock acquired, timeout waiting for release")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.readUnsafe(path, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readUnsafe reads the file contents without locking mechanism in mind.
|
||||||
|
// Using readUnsafe directly may cause errors if file being written at the moment.
|
||||||
|
func (f *fs) readUnsafe(file string, v encoding.BinaryUnmarshaler) error {
|
||||||
|
if v == nil {
|
||||||
|
return errors.New("can't unmarshal to a nil object")
|
||||||
|
}
|
||||||
|
|
||||||
|
targetFilePath := filepath.Join(f.root, file)
|
||||||
if targetFilePath == f.lockFilePath() {
|
if targetFilePath == f.lockFilePath() {
|
||||||
return errors.New("can't read from the lock file")
|
return errors.New("can't read from the lock file")
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-5
@@ -58,10 +58,14 @@ func saveState(s Storage, g game.Game) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *repo) LoadState() (game.Game, error) {
|
func (r *repo) LoadState() (game.Game, error) {
|
||||||
return loadState(r.s)
|
return loadState(r.s, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadState(s Storage) (game.Game, error) {
|
func (r *repo) LoadStateSafe() (game.Game, error) {
|
||||||
|
return loadState(r.s, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadState(s Storage, locked bool) (game.Game, error) {
|
||||||
var g game.Game
|
var g game.Game
|
||||||
path := statePath
|
path := statePath
|
||||||
exist, err := s.Exists(path)
|
exist, err := s.Exists(path)
|
||||||
@@ -69,10 +73,16 @@ func loadState(s Storage) (game.Game, error) {
|
|||||||
return g, NewStorageError(err)
|
return g, NewStorageError(err)
|
||||||
}
|
}
|
||||||
if !exist {
|
if !exist {
|
||||||
return g, NewStateError("latest state was never stored")
|
return g, NewGameNotInitializedError()
|
||||||
}
|
}
|
||||||
if err := s.Read(path, &g); err != nil {
|
if locked {
|
||||||
return g, NewStorageError(err)
|
if err := s.Read(path, &g); err != nil {
|
||||||
|
return g, NewStorageError(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := s.ReadSafe(path, &g); err != nil {
|
||||||
|
return g, NewStorageError(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ func NewStorageError(err error) error {
|
|||||||
return e.NewRepoError(err)
|
return e.NewRepoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewGameNotInitializedError() error {
|
||||||
|
return e.NewGameNotInitializedError()
|
||||||
|
}
|
||||||
|
|
||||||
func NewStateError(msg string) error {
|
func NewStateError(msg string) error {
|
||||||
return e.NewGameStateError(msg)
|
return e.NewGameStateError(msg)
|
||||||
}
|
}
|
||||||
@@ -21,6 +25,7 @@ type Storage interface {
|
|||||||
Exists(string) (bool, error)
|
Exists(string) (bool, error)
|
||||||
Write(string, encoding.BinaryMarshaler) error
|
Write(string, encoding.BinaryMarshaler) error
|
||||||
Read(string, encoding.BinaryUnmarshaler) error
|
Read(string, encoding.BinaryUnmarshaler) error
|
||||||
|
ReadSafe(string, encoding.BinaryUnmarshaler) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type repo struct {
|
type repo struct {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"github.com/iliadenisov/galaxy/internal/game"
|
"github.com/iliadenisov/galaxy/internal/game"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/iliadenisov/galaxy/internal/controller"
|
|
||||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,18 +25,15 @@ func CommandHandler(c *gin.Context, executor Executor) {
|
|||||||
case err == nil:
|
case err == nil:
|
||||||
c.Status(http.StatusOK)
|
c.Status(http.StatusOK)
|
||||||
case errors.Is(err, ErrCommandNotProcessed):
|
case errors.Is(err, ErrCommandNotProcessed):
|
||||||
c.Status(http.StatusInternalServerError)
|
c.Status(http.StatusInternalServerError) // TODO: add error text?
|
||||||
default:
|
default:
|
||||||
// TODO: separate bad value errors and game state errors
|
// TODO: separate invalid input and game state errors
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Execute(cmd rest.Command) error {
|
func Execute(cmd rest.Command) error {
|
||||||
p := func(p *controller.Param) {
|
p := param()
|
||||||
// TODO: initialize base controller settings
|
|
||||||
p.StoragePath = ""
|
|
||||||
}
|
|
||||||
switch {
|
switch {
|
||||||
case cmd.DeclareWar != nil:
|
case cmd.DeclareWar != nil:
|
||||||
return game.DeclareWar(p, cmd.Race, cmd.DeclareWar.Opponent)
|
return game.DeclareWar(p, cmd.Race, cmd.DeclareWar.Opponent)
|
||||||
|
|||||||
@@ -1,12 +1,30 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/controller"
|
||||||
"github.com/iliadenisov/galaxy/internal/model/rest"
|
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
StoragePath string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
StoragePath = os.Getenv("STORAGE_PATH")
|
||||||
|
}
|
||||||
|
|
||||||
|
func param() func(*controller.Param) {
|
||||||
|
return func(p *controller.Param) {
|
||||||
|
// TODO: initialize base controller settings
|
||||||
|
p.StoragePath = StoragePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Executor func(rest.Command) error
|
type Executor func(rest.Command) error
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
@@ -35,6 +53,7 @@ func setupRouter(executor Executor) *gin.Engine {
|
|||||||
v.RegisterValidation("notblank", notBlankStringValidator)
|
v.RegisterValidation("notblank", notBlankStringValidator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.GET("/api/v1/status", StatusHandler)
|
||||||
r.PUT("/api/v1/command", LimitMiddleware(1), func(ctx *gin.Context) { CommandHandler(ctx, executor) })
|
r.PUT("/api/v1/command", LimitMiddleware(1), func(ctx *gin.Context) { CommandHandler(ctx, executor) })
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func TestRouter(t *testing.T) {
|
|||||||
req, _ := http.NewRequest("PUT", "/api/v1/command", cmdBody(exampleCommand))
|
req, _ := http.NewRequest("PUT", "/api/v1/command", cmdBody(exampleCommand))
|
||||||
r.ServeHTTP(w, req)
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 200, w.Code, w.Body)
|
assert.Equal(t, http.StatusOK, w.Code, w.Body)
|
||||||
|
|
||||||
// error: notblank validator
|
// error: notblank validator
|
||||||
exampleCommand.Race = ""
|
exampleCommand.Race = ""
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
e "github.com/iliadenisov/galaxy/internal/error"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/game"
|
||||||
|
"github.com/iliadenisov/galaxy/internal/model/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StatusHandler(c *gin.Context) {
|
||||||
|
g, err := game.LoadState(param())
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
c.JSON(http.StatusOK, rest.Status{
|
||||||
|
Turn: g.Age,
|
||||||
|
Players: len(g.Race),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ge = new(e.GenericError)
|
||||||
|
|
||||||
|
if errors.As(err, ge) {
|
||||||
|
switch ge.Code {
|
||||||
|
case e.ErrGameNotInitialized:
|
||||||
|
c.Status(http.StatusNotImplemented)
|
||||||
|
default:
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"generic_error": ge.Error(), "code": ge.Code})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package router_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/iliadenisov/galaxy/internal/router"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetStatus(t *testing.T) {
|
||||||
|
r := router.SetupRouter()
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", "/api/v1/status", nil)
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusNotImplemented, w.Code, w.Body)
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user