feat: more validators
This commit is contained in:
@@ -83,7 +83,7 @@ func (c *Controller) ShipGroupLoad(actor string, groupID uuid.UUID, cargoType st
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ct, ok := game.CargoTypeSet[cargoType]
|
||||
ct, ok := game.CargoTypeSet[strings.ToLower(cargoType)]
|
||||
if !ok {
|
||||
return e.NewCargoTypeInvalidError(cargoType)
|
||||
}
|
||||
@@ -151,12 +151,12 @@ func (c *Controller) ShipGroupTransfer(actor, acceptor string, groupID uuid.UUID
|
||||
return c.Cache.shipGroupTransfer(ri, riAccept, groupID, quantity)
|
||||
}
|
||||
|
||||
func (c *Controller) ShipGroupJoinFleet(actor, fleetName string, groupID uuid.UUID, count uint) error {
|
||||
func (c *Controller) ShipGroupJoinFleet(actor, fleetName string, groupID uuid.UUID, quantity uint) error {
|
||||
ri, err := c.Cache.validActor(actor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Cache.ShipGroupJoinFleet(ri, fleetName, groupID, count)
|
||||
return c.Cache.ShipGroupJoinFleet(ri, fleetName, groupID, quantity)
|
||||
}
|
||||
|
||||
func (c *Controller) FleetMerge(actor, fleetSourceName, fleetTargetName string) error {
|
||||
@@ -195,12 +195,12 @@ func (c *Controller) ScienceRemove(actor, typeName string) error {
|
||||
return c.Cache.ScienceRemove(ri, typeName)
|
||||
}
|
||||
|
||||
func (c *Controller) PlanetRename(actor string, planetNumber int, typeName string) error {
|
||||
func (c *Controller) PlanetRename(actor string, planetNumber int, name string) error {
|
||||
ri, err := c.Cache.validActor(actor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Cache.PlanetRename(ri, planetNumber, typeName)
|
||||
return c.Cache.PlanetRename(ri, planetNumber, name)
|
||||
}
|
||||
|
||||
func (c *Controller) PlanetProduce(actor string, planetNumber int, prodType, subject string) error {
|
||||
@@ -237,7 +237,7 @@ func (c *Controller) PlanetRouteSet(actor, loadType string, origin, destination
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rt, ok := game.RouteTypeSet[loadType]
|
||||
rt, ok := game.RouteTypeSet[strings.ToLower(loadType)]
|
||||
if !ok {
|
||||
return e.NewCargoTypeInvalidError(loadType)
|
||||
}
|
||||
@@ -249,7 +249,7 @@ func (c *Controller) PlanetRouteRemove(actor, loadType string, origin uint) erro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rt, ok := game.RouteTypeSet[loadType]
|
||||
rt, ok := game.RouteTypeSet[strings.ToLower(loadType)]
|
||||
if !ok {
|
||||
return e.NewCargoTypeInvalidError(loadType)
|
||||
}
|
||||
|
||||
@@ -143,3 +143,7 @@ func (c *Cache) CreateShipsUnsafe_T(ri int, classID uuid.UUID, planet uint, quan
|
||||
func (c *Cache) WipeRace(ri int) {
|
||||
c.wipeRace(ri)
|
||||
}
|
||||
|
||||
func (c *Cache) UnsafeDeleteShipGroup(sgi int) {
|
||||
c.unsafeDeleteShipGroup(sgi)
|
||||
}
|
||||
|
||||
@@ -407,7 +407,6 @@ func (c *Cache) shipGroupTransfer(ri, riAccept int, groupID uuid.UUID, quantity
|
||||
}
|
||||
|
||||
if quantity == 0 || quantity == sg.Number {
|
||||
// FIXME: remove fleet & invalidate cache?
|
||||
c.unsafeDeleteShipGroup(sgi)
|
||||
} else {
|
||||
newGroup.Number = quantity
|
||||
@@ -437,6 +436,7 @@ func (c *Cache) ShipGroupBreak(ri int, groupID uuid.UUID, quantity uint) error {
|
||||
if quantity == 0 || quantity == c.ShipGroup(sgi).Number {
|
||||
c.internalShipGroupJoinFleet(sgi, nil)
|
||||
} else {
|
||||
// TODO: which group stays in fleet?
|
||||
if _, err := c.breakGroup(ri, groupID, quantity); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -572,3 +572,20 @@ func TestShipGroupDestroyItem(t *testing.T) {
|
||||
func TestState(t *testing.T) {
|
||||
assert.Equal(t, "In_Orbit", fmt.Sprintf("%s", game.StateInOrbit))
|
||||
}
|
||||
|
||||
func TestUnsafeDeleteShipGroup(t *testing.T) {
|
||||
c, g := newCache()
|
||||
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_0_num, 3)) // 0
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Gunship, R0_Planet_2_num, 5)) // 1
|
||||
assert.NoError(t, g.ShipGroupJoinFleet(Race_0.Name, "Fleet", c.ShipGroup(0).ID, 0))
|
||||
assert.NoError(t, c.CreateShips(Race_0_idx, Race_0_Freighter, R0_Planet_2_num, 7)) // 2
|
||||
|
||||
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 3)
|
||||
|
||||
c.UnsafeDeleteShipGroup(1)
|
||||
|
||||
assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 2)
|
||||
assert.Equal(t, uint(3), c.ShipGroup(0).Number)
|
||||
assert.Equal(t, uint(7), c.ShipGroup(1).Number)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package game
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@@ -17,9 +18,9 @@ const (
|
||||
|
||||
var (
|
||||
CargoTypeSet map[string]CargoType = map[string]CargoType{
|
||||
CargoColonist.String(): CargoColonist,
|
||||
CargoMaterial.String(): CargoMaterial,
|
||||
CargoCapital.String(): CargoCapital,
|
||||
strings.ToLower(CargoColonist.String()): CargoColonist,
|
||||
strings.ToLower(CargoMaterial.String()): CargoMaterial,
|
||||
strings.ToLower(CargoCapital.String()): CargoCapital,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package game
|
||||
|
||||
import "strings"
|
||||
|
||||
type RouteType string
|
||||
|
||||
const (
|
||||
@@ -11,10 +13,10 @@ const (
|
||||
|
||||
var (
|
||||
RouteTypeSet map[string]RouteType = map[string]RouteType{
|
||||
RouteMaterial.String(): RouteMaterial,
|
||||
RouteCapital.String(): RouteCapital,
|
||||
RouteColonist.String(): RouteColonist,
|
||||
RouteEmpty.String(): RouteEmpty,
|
||||
strings.ToLower(RouteMaterial.String()): RouteMaterial,
|
||||
strings.ToLower(RouteCapital.String()): RouteCapital,
|
||||
strings.ToLower(RouteColonist.String()): RouteColonist,
|
||||
strings.ToLower(RouteEmpty.String()): RouteEmpty,
|
||||
}
|
||||
RouteToCargo map[RouteType]CargoType = map[RouteType]CargoType{
|
||||
RouteColonist: CargoColonist,
|
||||
|
||||
@@ -35,31 +35,162 @@ const (
|
||||
CommandTypePlanetRouteRemove CommandType = "planetRouteRemove"
|
||||
)
|
||||
|
||||
type DecodableCommand interface {
|
||||
CommandType() CommandType
|
||||
}
|
||||
|
||||
type CommandMeta struct {
|
||||
Type CommandType `json:"@type" binding:"required,notblank"`
|
||||
}
|
||||
|
||||
func (cm CommandMeta) CommandType() CommandType {
|
||||
return cm.Type
|
||||
}
|
||||
|
||||
type CommandRaceQuit struct {
|
||||
CommandMeta
|
||||
}
|
||||
|
||||
type CommandRaceVote struct {
|
||||
CommandMeta
|
||||
Recipient string `json:"recipient" binding:"required,notblank"`
|
||||
Acceptor string `json:"acceptor" binding:"required,notblank"`
|
||||
}
|
||||
|
||||
type CommandRaceRelation struct {
|
||||
CommandMeta
|
||||
Opponent string `json:"recipient" binding:"required,notblank"`
|
||||
Acceptor string `json:"acceptor" binding:"required,notblank"`
|
||||
Relation string `json:"relation" binding:"required,notblank"`
|
||||
}
|
||||
|
||||
type CommandShipClassCreate struct {
|
||||
CommandMeta
|
||||
Name string `json:"name" binding:"required,notblank"`
|
||||
Name string `json:"name" binding:"required,notblank,entity"`
|
||||
Drive float64 `json:"drive" binding:"eq=0|gte=1"`
|
||||
Armament int `json:"armament" binding:"ammoWeapons=Weapons"`
|
||||
Weapons float64 `json:"weapons" binding:"ammoWeapons=Armament"`
|
||||
Shields float64 `json:"shields" binding:"eq=0|gte=1"`
|
||||
Cargo float64 `json:"cargo" binding:"eq=0|gte=1"`
|
||||
}
|
||||
|
||||
type CommandShipClassMerge struct {
|
||||
CommandMeta
|
||||
Name string `json:"name" binding:"required,notblank,entity,nefield=Target"`
|
||||
Target string `json:"target" binding:"required,notblank,entity,nefield=Class"`
|
||||
}
|
||||
|
||||
type CommandShipClassRemove struct {
|
||||
CommandMeta
|
||||
Name string `json:"name" binding:"required,notblank,entity"`
|
||||
}
|
||||
|
||||
type CommandShipGroupLoad struct {
|
||||
CommandMeta
|
||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||
Cargo string `json:"cargo" binding:"required,notblank,oneof=COL MAT CAP"`
|
||||
Ships int `json:"ships" binding:"gte=0"`
|
||||
Quantity float64 `json:"quantity" binding:"gte=0"`
|
||||
}
|
||||
|
||||
type CommandShipGroupUnload struct {
|
||||
CommandMeta
|
||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||
Ships int `json:"ships" binding:"gte=0"`
|
||||
Quantity float64 `json:"quantity" binding:"gte=0"`
|
||||
}
|
||||
|
||||
type CommandShipGroupSend struct {
|
||||
CommandMeta
|
||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||
Destination int `json:"planetNumber" binding:"gte=0"`
|
||||
Quantity int `json:"quantity" binding:"gte=0"`
|
||||
}
|
||||
|
||||
type CommandShipGroupUpgrade struct {
|
||||
CommandMeta
|
||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||
Tech string `json:"tech" binding:"oneof=ALL DRIVE WEAPONS SHIELDS CARGO"`
|
||||
MaxShips int `json:"maxShips" binding:"gte=0"`
|
||||
Level int `json:"level" binding:"gte=1"`
|
||||
}
|
||||
|
||||
type CommandShipGroupMerge struct {
|
||||
CommandMeta
|
||||
}
|
||||
|
||||
type CommandShipGroupBreak struct {
|
||||
CommandMeta
|
||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||
Quantity int `json:"quantity" binding:"gte=0"`
|
||||
}
|
||||
|
||||
type CommandShipGroupDismantle struct {
|
||||
CommandMeta
|
||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||
Quantity int `json:"quantity" binding:"gte=0"`
|
||||
}
|
||||
|
||||
type CommandShipGroupTransfer struct {
|
||||
CommandMeta
|
||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||
Acceptor string `json:"acceptor" binding:"required,notblank"`
|
||||
Quantity int `json:"quantity" binding:"gte=0"`
|
||||
}
|
||||
|
||||
type CommandShipGroupJoinFleet struct {
|
||||
CommandMeta
|
||||
ID string `json:"id" binding:"required,uuid_rfc4122"`
|
||||
Name string `json:"name" binding:"required,notblank,entity"`
|
||||
Quantity int `json:"quantity" binding:"gte=0"`
|
||||
}
|
||||
|
||||
type CommandFleetMerge struct {
|
||||
CommandMeta
|
||||
Name string `json:"name" binding:"required,notblank,entity,nefield=Target"`
|
||||
Target string `json:"target" binding:"required,notblank,entity,nefield=Name"`
|
||||
}
|
||||
|
||||
type CommandFleetSend struct {
|
||||
CommandMeta
|
||||
Name string `json:"name" binding:"required,notblank,entity"`
|
||||
Destination int `json:"planetNumber" binding:"gte=0"`
|
||||
}
|
||||
|
||||
type CommandScienceCreate struct {
|
||||
CommandMeta
|
||||
Name string `json:"name" binding:"required,notblank,entity"`
|
||||
Drive float64 `json:"drive" binding:"gte=0,lte=1"`
|
||||
Weapons float64 `json:"weapons" binding:"gte=0,lte=1"`
|
||||
Shields float64 `json:"shields" binding:"gte=0,lte=1"`
|
||||
Cargo float64 `json:"cargo" binding:"gte=0,lte=1"`
|
||||
}
|
||||
|
||||
type CommandScienceRemove struct {
|
||||
CommandMeta
|
||||
Name string `json:"name" binding:"required,notblank,entity"`
|
||||
}
|
||||
|
||||
type CommandPlanetRename struct {
|
||||
CommandMeta
|
||||
Number int `json:"planetNumber" binding:"gte=0"`
|
||||
Name string `json:"name" binding:"required,notblank,entity"`
|
||||
}
|
||||
|
||||
type CommandPlanetProduce struct {
|
||||
CommandMeta
|
||||
Number int `json:"planetNumber" binding:"gte=0"`
|
||||
Production string `json:"production" binding:"oneof=MAT CAP DRIVE WEAPONS SHIELDS CARGO SCIENCE SHIP"`
|
||||
Subject string `json:"subject" binding:"subject"`
|
||||
}
|
||||
|
||||
type CommandPlanetRouteSet struct {
|
||||
CommandMeta
|
||||
Origin int `json:"fromPlanetNumber" binding:"gte=0,nefield=Destination"`
|
||||
Destination int `json:"toPlanetNumber" binding:"gte=0,nefield=Number"`
|
||||
LoadType string `json:"loadType" binding:"oneof=MAT CAP COL EMP"`
|
||||
}
|
||||
|
||||
type CommandPlanetRouteRemove struct {
|
||||
CommandMeta
|
||||
Origin int `json:"fromPlanetNumber" binding:"gte=0,nefield=Destination"`
|
||||
LoadType string `json:"loadType" binding:"oneof=MAT CAP COL EMP"`
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestCommand(t *testing.T) {
|
||||
Commands: []json.RawMessage{
|
||||
encodeCommand(&rest.CommandRaceVote{
|
||||
CommandMeta: rest.CommandMeta{Type: rest.CommandTypeRaceVote},
|
||||
Recipient: "AnotherRace",
|
||||
Acceptor: "AnotherRace",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -63,42 +63,43 @@ func commandRaceQuit(actor string) (Command, error) {
|
||||
}
|
||||
|
||||
func commandRaceVote(actor string, c json.RawMessage) (Command, error) {
|
||||
var v rest.CommandRaceVote
|
||||
if err := json.Unmarshal(c, &v); err != nil {
|
||||
if v, err := unmarshallCommand(c, new(rest.CommandRaceVote)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return func(c controller.Ctrl) error {
|
||||
return c.RaceVote(actor, v.Acceptor)
|
||||
}, nil
|
||||
}
|
||||
if err := validateCommand(v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(c controller.Ctrl) error {
|
||||
return c.RaceVote(actor, v.Recipient)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func commandRaceRelation(actor string, c json.RawMessage) (Command, error) {
|
||||
var v rest.CommandRaceRelation
|
||||
if err := json.Unmarshal(c, &v); err != nil {
|
||||
if v, err := unmarshallCommand(c, new(rest.CommandRaceRelation)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return func(c controller.Ctrl) error {
|
||||
return c.RaceRelation(actor, v.Acceptor, v.Relation)
|
||||
}, nil
|
||||
}
|
||||
if err := validateCommand(v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(c controller.Ctrl) error {
|
||||
return c.RaceRelation(actor, v.Opponent, v.Relation)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func commandShipClassCreate(actor string, c json.RawMessage) (Command, error) {
|
||||
v := new(rest.CommandShipClassCreate)
|
||||
if v, err := unmarshallCommand(c, new(rest.CommandShipClassCreate)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return func(c controller.Ctrl) error {
|
||||
return c.ShipClassCreate(actor, v.Name, v.Drive, int(v.Armament), v.Weapons, v.Shields, v.Cargo)
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshallCommand[T rest.DecodableCommand](c json.RawMessage, v *T) (*T, error) {
|
||||
if err := json.Unmarshal(c, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateCommand(v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(c controller.Ctrl) error {
|
||||
return c.ShipClassCreate(actor, v.Name, v.Drive, int(v.Armament), v.Weapons, v.Shields, v.Cargo)
|
||||
}, nil
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func validateCommand(v any) error {
|
||||
|
||||
@@ -54,6 +54,12 @@ func setupRouter(executor handler.CommandExecutor) *gin.Engine {
|
||||
if err := v.RegisterValidation("ammoWeapons", armamentWithWeaponsValidator); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := v.RegisterValidation("entity", entityNameStringValidator); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := v.RegisterValidation("subject", productionTypeStringValidator); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
groupV1 := r.Group("/api/v1")
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/iliadenisov/galaxy/internal/util"
|
||||
)
|
||||
|
||||
var notBlankStringValidator validator.Func = func(fl validator.FieldLevel) bool {
|
||||
@@ -16,6 +17,27 @@ var notBlankStringValidator validator.Func = func(fl validator.FieldLevel) bool
|
||||
return true
|
||||
}
|
||||
|
||||
var entityNameStringValidator validator.Func = func(fl validator.FieldLevel) bool {
|
||||
s, ok := fl.Field().Interface().(string)
|
||||
if ok {
|
||||
if _, ok := util.ValidateTypeName(s); !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var productionTypeStringValidator validator.Func = func(fl validator.FieldLevel) bool {
|
||||
v, ok := fl.Field().Interface().(string)
|
||||
if ok {
|
||||
f := fl.Parent().FieldByName(fl.Param())
|
||||
if s, ok := f.Interface().(string); ok && (s == "SHIP" || s == "SCIENCE") && len(strings.TrimSpace(v)) == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var armamentWithWeaponsValidator validator.Func = func(fl validator.FieldLevel) bool {
|
||||
var v, compareTo float64
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ var allowedSpecialChars = map[rune]bool{
|
||||
'_': true,
|
||||
}
|
||||
|
||||
// TODO: router validator
|
||||
func ValidateTypeName(input string) (string, bool) {
|
||||
// Trim leading and trailing spaces
|
||||
trimmed := strings.TrimSpace(input)
|
||||
|
||||
Reference in New Issue
Block a user