chore: re-package

This commit is contained in:
IliaDenisov
2026-03-10 15:46:18 +02:00
parent bb2bb899de
commit dabe1f091a
99 changed files with 151 additions and 98 deletions
+220
View File
@@ -0,0 +1,220 @@
package error
import (
"fmt"
)
const (
ErrStorageFailure int = 1000 + iota
ErrGameNotInitialized
ErrGameStateInvalid
ErrReportNotFound
)
const (
ErrDummy int = -1
ErrDeleteShipTypeExistingGroup = 5000
ErrDeleteShipTypePlanetProduction = 5001
ErrDeleteSciencePlanetProduction = 5002
ErrMergeShipTypeNotEqual = 5003
ErrBeakGroupNumberNotEnough = 5005
ErrEntityInUse = 5006
ErrShipsBusy = 5007
ErrShipsNotOnSamePlanet = 5008
ErrUpgradeGroupNumberNotEnough = 5010
ErrUpgradeInsufficientResources = 5011
ErrSendShipHasNoDrives = 5012
ErrSendUnreachableDestination = 5013
ErrSendShipOwnerHasNoPlanets = 5014
ErrRaceExinct = 5015
)
const (
ErrInputUnknownRace int = 3000 + iota
ErrInputUnknownRelation
ErrInputSameRace
ErrInputEntityTypeNameInvalid
ErrInputNewEntityDuplicateIdentifier
ErrInputEntityTypeNameEquality
ErrInputEntityNotExists
ErrInputEntityNotOwned
ErrInputPlanetNumber
ErrInputDriveValue
ErrInputWeaponsValue
ErrInputShieldsValue
ErrInputCargoValue
ErrInputShipTypeArmamentValue
ErrInputShipTypeWeaponsAndArmamentValue
ErrInputShipTypeZeroValues
ErrInputScienceSumValues
ErrInputProductionInvalid
ErrInputCargoTypeInvalid
ErrInputCargoLoadNotEnough
ErrInputCargoLoadNotEqual
ErrInputNoCargoBay
ErrInputCargoLoadNoSpaceLeft
ErrInputCargoUnloadEmpty
ErrInputBreakGroupIllegalNumber
ErrInputTechUnknown
ErrInputTechInvalidMixing
ErrInputUpgradeShipTechNotUsed
ErrInputUpgradeParameterNotAllowed
ErrInputUpgradeShipsAlreadyUpToDate
ErrInputUpgradeTechLevelInsufficient
ErrInputQuitCommandFollowedByCommand
ErrInputUnrecognizedCommand
)
func GenericErrorText(code int) string {
switch code {
case ErrDummy:
return "Dummy"
case ErrStorageFailure:
return "Storage failure"
case ErrGameNotInitialized:
return "Game not yet initialized"
case ErrGameStateInvalid:
return "Invalid game state"
case ErrInputUnknownRace:
return "Race name is unknown to this game"
case ErrInputUnknownRelation:
return "Unknown relation"
case ErrInputSameRace:
return "Race name must be different from your own"
case ErrInputEntityTypeNameInvalid:
return "Name has invalid length or symbols"
case ErrInputNewEntityDuplicateIdentifier:
return "Entity already exists"
case ErrInputEntityTypeNameEquality:
return "Names should differ"
case ErrInputEntityNotExists:
return "Entity does not exists"
case ErrInputEntityNotOwned:
return "Entity is not owned"
case ErrEntityInUse:
return "Entity currently in use"
case ErrInputPlanetNumber:
return "Invalid Planet number"
case ErrInputDriveValue:
return "Invalid Drive value"
case ErrInputWeaponsValue:
return "Invalid Weapons value"
case ErrInputShieldsValue:
return "Invalid Shields value"
case ErrInputCargoValue:
return "Invalid Cargo value"
case ErrInputShipTypeArmamentValue:
return "Invalid Armament value"
case ErrInputShipTypeWeaponsAndArmamentValue:
return "Invalid Armament or Weapons value"
case ErrInputShipTypeZeroValues:
return "Ship type values cannot be all zeros"
case ErrDeleteShipTypeExistingGroup:
return "Ship type exists in a Group"
case ErrDeleteShipTypePlanetProduction:
return "Ship type in production on the Planet"
case ErrDeleteSciencePlanetProduction:
return "Science in production on the Planet"
case ErrInputScienceSumValues:
return "Science proportions sum should be equal 1"
case ErrInputProductionInvalid:
return "Invalid Production type"
case ErrInputCargoTypeInvalid:
return "Invalid cargo type"
case ErrInputCargoLoadNotEnough:
return "Not enough cargo to load"
case ErrInputCargoLoadNotEqual:
return "Ship(s) already loaded with another cargo"
case ErrInputNoCargoBay:
return "Ship type is not designed to carry cargo"
case ErrInputCargoLoadNoSpaceLeft:
return "No space left on the ships to load cargo"
case ErrInputCargoUnloadEmpty:
return "Ships are not carrying any cargo"
case ErrInputBreakGroupIllegalNumber:
return "Illegal ships number to make new group"
case ErrMergeShipTypeNotEqual:
return "Source and target ship types are not the same"
case ErrBeakGroupNumberNotEnough:
return "Not enough ships in the group to make a separate group"
case ErrShipsBusy:
return "Ship(s) are'n free to use"
case ErrShipsNotOnSamePlanet:
return "Ships not on the same planet"
case ErrInputTechUnknown:
return "Technology name unknown"
case ErrInputTechInvalidMixing:
return "Technologies list must containt only specific values"
case ErrInputUpgradeShipTechNotUsed:
return "Technology is not used with ship class"
case ErrInputUpgradeParameterNotAllowed:
return "Parameter not allowed for upgrade"
case ErrInputUpgradeShipsAlreadyUpToDate:
return "Ships already up to date, nothing to upgrade"
case ErrUpgradeGroupNumberNotEnough:
return "Not enough ships in the group to make an upgrade"
case ErrUpgradeInsufficientResources:
return "Insufficient planet production capacity"
case ErrInputUpgradeTechLevelInsufficient:
return "Insifficient Tech level for requested upgrade"
case ErrInputQuitCommandFollowedByCommand:
return "'Quit' must be the last order's command"
case ErrInputUnrecognizedCommand:
return "Unrecognized command"
case ErrSendShipHasNoDrives:
return "One or more ships are not equipped with hyperdrive and cannot be moved"
case ErrSendUnreachableDestination:
return "Destination planet is too far for current Drive level"
case ErrSendShipOwnerHasNoPlanets:
return "Race is not owning any planet, all flights impossible"
case ErrRaceExinct:
return "Race is extinct"
default:
return fmt.Sprintf("Undescribed error with code %d", code)
}
}
type GenericError struct {
Code int
subject string
err error
}
func (ge GenericError) Error() string {
msg := GenericErrorText(ge.Code)
if ge.subject != "" {
msg += ": " + ge.subject
}
if ge.err != nil {
msg = fmt.Errorf("%s: %w", msg, ge.err).Error()
}
return msg
}
func newGenericError(code int, arg ...any) error {
e := &GenericError{Code: code}
if len(arg) > 0 {
i := 0
switch arg[i].(type) {
case error:
e.err = arg[i].(error)
i += 1
}
if len(arg) >= i+2 {
e.subject = fmt.Sprintf(asString(arg[i]), arg[i+1:]...)
} else if len(arg) == i+1 {
e.subject = asString(arg[i])
}
}
return *e
}
func asString(v any) string {
switch s := v.(type) {
case string:
return s
default:
return fmt.Sprint(v)
}
}
+27
View File
@@ -0,0 +1,27 @@
package error
import (
"errors"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewGenericError(t *testing.T) {
for i, tc := range []struct {
arg []any
message string
}{
{arg: []any{"Foo"}, message: "Dummy: Foo"},
{arg: []any{"Foo%s", "Bar"}, message: "Dummy: FooBar"},
{arg: []any{errors.New("Error")}, message: "Dummy: Error"},
{arg: []any{errors.New("Error"), "Foo"}, message: "Dummy: Foo: Error"},
{arg: []any{errors.New("Error"), "Foo%s", "Bar"}, message: "Dummy: FooBar: Error"},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
err := newGenericError(ErrDummy, tc.arg...)
assert.EqualError(t, err, tc.message)
})
}
}
+14
View File
@@ -0,0 +1,14 @@
module galaxy/error
go 1.26.0
require github.com/stretchr/testify v1.11.1
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+13
View File
@@ -0,0 +1,13 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+173
View File
@@ -0,0 +1,173 @@
package error
func NewRaceUnknownError(arg ...any) error {
return newGenericError(ErrInputUnknownRace, arg...)
}
func NewUnknownRelationError(arg ...any) error {
return newGenericError(ErrInputUnknownRelation, arg...)
}
func NewSameRaceError(arg ...any) error {
return newGenericError(ErrInputSameRace, arg...)
}
func NewEntityTypeNameValidationError(arg ...any) error {
return newGenericError(ErrInputEntityTypeNameInvalid, arg...)
}
func NewEntityDuplicateIdentifierError(arg ...any) error {
return newGenericError(ErrInputNewEntityDuplicateIdentifier, arg...)
}
func NewEntityTypeNameEqualityError(arg ...any) error {
return newGenericError(ErrInputEntityTypeNameEquality, arg...)
}
func NewEntityNotExistsError(arg ...any) error {
return newGenericError(ErrInputEntityNotExists, arg...)
}
func NewEntityNotOwnedError(arg ...any) error {
return newGenericError(ErrInputEntityNotOwned, arg...)
}
func NewEntityInUseError(arg ...any) error {
return newGenericError(ErrEntityInUse, arg...)
}
func NewPlanetNumberError(arg ...any) error {
return newGenericError(ErrInputPlanetNumber, arg...)
}
func NewDriveValueError(arg ...any) error {
return newGenericError(ErrInputDriveValue, arg...)
}
func NewWeaponsValueError(arg ...any) error {
return newGenericError(ErrInputWeaponsValue, arg...)
}
func NewShieldsValueError(arg ...any) error {
return newGenericError(ErrInputShieldsValue, arg...)
}
func NewCargoValueError(arg ...any) error {
return newGenericError(ErrInputCargoValue, arg...)
}
func NewShipTypeArmamentValueError(arg ...any) error {
return newGenericError(ErrInputShipTypeArmamentValue, arg...)
}
func NewShipTypeArmamentAndWeaponsValueError(arg ...any) error {
return newGenericError(ErrInputShipTypeWeaponsAndArmamentValue, arg...)
}
func NewShipTypeShipTypeZeroValuesError(arg ...any) error {
return newGenericError(ErrInputShipTypeZeroValues, arg...)
}
func NewScienceSumValuesError(arg ...any) error {
return newGenericError(ErrInputScienceSumValues, arg...)
}
func NewProductionInvalidError(arg ...any) error {
return newGenericError(ErrInputProductionInvalid, arg...)
}
func NewCargoTypeInvalidError(arg ...any) error {
return newGenericError(ErrInputCargoTypeInvalid, arg...)
}
func NewCargoLoadNotEnoughError(arg ...any) error {
return newGenericError(ErrInputCargoLoadNotEnough, arg...)
}
func NewCargoLoadNotEqualError(arg ...any) error {
return newGenericError(ErrInputCargoLoadNotEqual, arg...)
}
func NewNoCargoBayError(arg ...any) error {
return newGenericError(ErrInputNoCargoBay, arg...)
}
func NewCargoLoadNoSpaceLeftError(arg ...any) error {
return newGenericError(ErrInputCargoLoadNoSpaceLeft, arg...)
}
func NewCargoUnloadEmptyError(arg ...any) error {
return newGenericError(ErrInputCargoUnloadEmpty, arg...)
}
func NewBreakGroupIllegalNumberError(arg ...any) error {
return newGenericError(ErrInputBreakGroupIllegalNumber, arg...)
}
func NewMergeShipTypeNotEqualError(arg ...any) error {
return newGenericError(ErrMergeShipTypeNotEqual, arg...)
}
func NewBeakGroupNumberNotEnoughError(arg ...any) error {
return newGenericError(ErrBeakGroupNumberNotEnough, arg...)
}
func NewShipsBusyError(arg ...any) error {
return newGenericError(ErrShipsBusy, arg...)
}
func NewShipsNotOnSamePlanetError(arg ...any) error {
return newGenericError(ErrShipsNotOnSamePlanet, arg...)
}
func NewTechUnknownError(arg ...any) error {
return newGenericError(ErrInputTechUnknown, arg...)
}
func NewTechInvalidMixingError(arg ...any) error {
return newGenericError(ErrInputTechInvalidMixing, arg...)
}
func NewUpgradeShipTechNotUsedError(arg ...any) error {
return newGenericError(ErrInputUpgradeShipTechNotUsed, arg...)
}
func NewUpgradeParameterNotAllowedError(arg ...any) error {
return newGenericError(ErrInputUpgradeParameterNotAllowed, arg...)
}
func NewUpgradeShipsAlreadyUpToDateError(arg ...any) error {
return newGenericError(ErrInputUpgradeShipsAlreadyUpToDate, arg...)
}
func NewUpgradeGroupNumberNotEnoughError(arg ...any) error {
return newGenericError(ErrUpgradeGroupNumberNotEnough, arg...)
}
func NewUpgradeInsufficientResourcesError(arg ...any) error {
return newGenericError(ErrUpgradeInsufficientResources, arg...)
}
func NewUpgradeTechLevelInsufficientError(arg ...any) error {
return newGenericError(ErrInputUpgradeTechLevelInsufficient, arg...)
}
func NewSendShipHasNoDrivesError(arg ...any) error {
return newGenericError(ErrSendShipHasNoDrives, arg...)
}
func NewSendUnreachableDestinationError(arg ...any) error {
return newGenericError(ErrSendUnreachableDestination, arg...)
}
func NewSendShipOwnerHasNoPlanetsError(arg ...any) error {
return newGenericError(ErrSendShipOwnerHasNoPlanets, arg...)
}
func NewQuitCommandFollowedByCommandError(arg ...any) error {
return newGenericError(ErrInputQuitCommandFollowedByCommand, arg...)
}
func NewUnrecognizedCommandError(arg ...any) error {
return newGenericError(ErrInputUnrecognizedCommand, arg...)
}
+5
View File
@@ -0,0 +1,5 @@
package error
func NewRepoError(arg ...any) error {
return newGenericError(ErrStorageFailure, arg...)
}
+29
View File
@@ -0,0 +1,29 @@
package error
func NewRaceExinctError(arg ...any) error {
return newGenericError(ErrRaceExinct, arg...)
}
func NewGameNotInitializedError(arg ...any) error {
return newGenericError(ErrGameNotInitialized, arg...)
}
func NewReportNotFoundError(arg ...any) error {
return newGenericError(ErrReportNotFound, arg...)
}
func NewGameStateError(arg ...any) error {
return newGenericError(ErrGameStateInvalid, arg...)
}
func NewDeleteShipTypeExistingGroupError(arg ...any) error {
return newGenericError(ErrDeleteShipTypeExistingGroup, arg...)
}
func NewDeleteShipTypePlanetProductionError(arg ...any) error {
return newGenericError(ErrDeleteShipTypePlanetProduction, arg...)
}
func NewDeleteSciencePlanetProductionError(arg ...any) error {
return newGenericError(ErrDeleteSciencePlanetProduction, arg...)
}
+5
View File
@@ -0,0 +1,5 @@
module galaxy/model
go 1.26.0
require github.com/google/uuid v1.6.0
+1
View File
@@ -0,0 +1 @@
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+223
View File
@@ -0,0 +1,223 @@
package order
import (
"encoding/json"
)
type Order struct {
Commands []DecodableCommand `json:"cmd"`
}
func (o Order) MarshalBinary() (data []byte, err error) {
return json.Marshal(&o)
}
func (o *Order) UnmarshalBinary(data []byte) error {
return json.Unmarshal(data, o)
}
func AsCommand[E DecodableCommand](c DecodableCommand) (result E, ok bool) {
if v, ok := c.(E); ok {
return v, true
}
return
}
type CommandType string
const (
CommandTypeRaceQuit CommandType = "raceQuit"
CommandTypeRaceVote CommandType = "raceVote"
CommandTypeRaceRelation CommandType = "raceRelation"
CommandTypeShipClassCreate CommandType = "shipClassCreate"
CommandTypeShipClassMerge CommandType = "shipClassMerge"
CommandTypeShipClassRemove CommandType = "shipClassRemove"
CommandTypeShipGroupBreak CommandType = "shipGroupBreak"
CommandTypeShipGroupLoad CommandType = "shipGroupLoad"
CommandTypeShipGroupUnload CommandType = "shipGroupUnload"
CommandTypeShipGroupSend CommandType = "shipGroupSend"
CommandTypeShipGroupUpgrade CommandType = "shipGroupUpgrade"
CommandTypeShipGroupMerge CommandType = "shipGroupMerge"
CommandTypeShipGroupDismantle CommandType = "shipGroupDismantle"
CommandTypeShipGroupTransfer CommandType = "shipGroupTransfer"
CommandTypeShipGroupJoinFleet CommandType = "shipGroupJoinFleet"
CommandTypeFleetMerge CommandType = "fleetMerge"
CommandTypeFleetSend CommandType = "fleetSend"
CommandTypeScienceCreate CommandType = "scienceCreate"
CommandTypeScienceRemove CommandType = "scienceRemove"
CommandTypePlanetRename CommandType = "planetRename"
CommandTypePlanetProduce CommandType = "planetProduce"
CommandTypePlanetRouteSet CommandType = "planetRouteSet"
CommandTypePlanetRouteRemove CommandType = "planetRouteRemove"
)
func (ct CommandType) String() string {
return string(ct)
}
type DecodableCommand interface {
CommandID() string
CommandType() CommandType
}
type CommandMeta struct {
CmdType CommandType `json:"@type" binding:"notblank"`
CmdID string `json:"cmdId" binding:"required,uuid_rfc4122"`
CmdApplied *bool `json:"cmdApplied,omitempty"`
CmdErrCode *int `json:"cmdErrorCode,omitempty"`
}
func (cm CommandMeta) CommandType() CommandType {
return cm.CmdType
}
func (cm CommandMeta) CommandID() string {
return cm.CmdID
}
func (cm *CommandMeta) Result(errCode int) {
cm.CmdErrCode = &errCode
cm.CmdApplied = new(bool(errCode == 0))
}
type CommandRaceQuit struct {
CommandMeta
}
type CommandRaceVote struct {
CommandMeta
Acceptor string `json:"acceptor" binding:"notblank,entity"`
}
type CommandRaceRelation struct {
CommandMeta
Acceptor string `json:"acceptor" binding:"notblank,entity"`
Relation string `json:"relation" binding:"oneof=WAR PEACE"`
}
type CommandShipClassCreate struct {
CommandMeta
Name string `json:"name" binding:"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:"notblank,entity,nefield=Target"`
Target string `json:"target" binding:"notblank,entity,nefield=Name"`
}
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:"oneof=COL MAT CAP"`
Quantity float64 `json:"quantity" binding:"gte=0"`
}
type CommandShipGroupUnload struct {
CommandMeta
ID string `json:"id" binding:"required,uuid_rfc4122"`
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"`
}
type CommandShipGroupUpgrade struct {
CommandMeta
ID string `json:"id" binding:"required,uuid_rfc4122"`
Tech string `json:"tech" binding:"oneof=ALL DRIVE WEAPONS SHIELDS CARGO"`
Level float64 `json:"level" binding:"eq=0|gt=1"`
}
type CommandShipGroupMerge struct {
CommandMeta
}
type CommandShipGroupBreak struct {
CommandMeta
ID string `json:"id" binding:"uuid_rfc4122,nefield=NewID"`
NewID string `json:"newId" binding:"uuid_rfc4122,nefield=ID"`
Quantity int `json:"quantity" binding:"gte=0"`
}
type CommandShipGroupDismantle struct {
CommandMeta
ID string `json:"id" binding:"required,uuid_rfc4122"`
}
type CommandShipGroupTransfer struct {
CommandMeta
ID string `json:"id" binding:"required,uuid_rfc4122"`
Acceptor string `json:"acceptor" binding:"required,notblank,entity"`
}
type CommandShipGroupJoinFleet struct {
CommandMeta
ID string `json:"id" binding:"required,uuid_rfc4122"`
Name string `json:"name" binding:"required,notblank,entity"`
}
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=Production"`
}
type CommandPlanetRouteSet struct {
CommandMeta
Origin int `json:"fromPlanetNumber" binding:"gte=0,nefield=Destination"`
Destination int `json:"toPlanetNumber" binding:"gte=0,nefield=Origin"`
LoadType string `json:"loadType" binding:"oneof=MAT CAP COL EMP"`
}
type CommandPlanetRouteRemove struct {
CommandMeta
Origin int `json:"fromPlanetNumber" binding:"gte=0"`
LoadType string `json:"loadType" binding:"oneof=MAT CAP COL EMP"`
}
+43
View File
@@ -0,0 +1,43 @@
package report
import (
"encoding/json"
"github.com/google/uuid"
)
type BattleReport struct {
ID uuid.UUID `json:"id"`
Planet uint `json:"planet"`
PlanetName string `json:"planetName"`
Races map[int]uuid.UUID `json:"races"`
Ships map[int]BattleReportGroup `json:"ships"`
Protocol []BattleActionReport `json:"protocol"`
}
type BattleReportGroup struct {
InBattle bool `json:"inBattle"`
Number uint `json:"num"`
NumberLeft uint `json:"numLeft"`
LoadQuantity Float `json:"loadQuantity"`
Tech map[string]Float `json:"tech"`
Race string `json:"race"`
ClassName string `json:"className"`
LoadType string `json:"loadType"`
}
type BattleActionReport struct {
Attacker int `json:"a"`
AttackerShipClass int `json:"sa"`
Defender int `json:"d"`
DefenderShipClass int `json:"sd"`
Destroyed bool `json:"x"`
}
func (b BattleReport) MarshalBinary() (data []byte, err error) {
return json.Marshal(&b)
}
func (b *BattleReport) UnmarshalBinary(data []byte) error {
return json.Unmarshal(data, b)
}
+19
View File
@@ -0,0 +1,19 @@
package report
import "github.com/google/uuid"
type Bombing struct {
PlanetOwnedID uuid.UUID `json:"-"` // make report: filter by planet's owner before bombing
Number uint `json:"planet"`
Planet string `json:"planetName"`
Owner string `json:"owner"`
Attacker string `json:"attacker"`
Production string `json:"production"`
Industry Float `json:"industry"` // I - Промышленность
Population Float `json:"population"` // P - Население
Colonists Float `json:"colonists"` // COL C - Количество колонистов
Capital Float `json:"capital"` // CAP $ - Запасы промышленности
Material Float `json:"material"` // MAT M - Запасы ресурсов / сырья
AttackPower Float `json:"attack"`
Wiped bool `json:"wiped"`
}
+31
View File
@@ -0,0 +1,31 @@
package report
type OtherPlanet struct {
Owner string `json:"owner"`
LocalPlanet
}
type LocalPlanet struct {
UninhabitedPlanet
Industry Float `json:"industry"` // I - Промышленность
Population Float `json:"population"` // P - Население
Colonists Float `json:"colonists"` // COL C - Количество колонистов
Production string `json:"production"`
FreeIndustry Float `json:"freeInductry"` // Параметр "L" - Свободный производственный потенциал
// [ ] FreeIndustry - неактуальная информация, т.к. модернизация происходит в процессе производства хода
}
type UninhabitedPlanet struct {
UnidentifiedPlanet
Size Float `json:"size"`
Name string `json:"name"`
Resources Float `json:"resources"` // R - Ресурсы
Capital Float `json:"capital"` // CAP $ - Запасы промышленности
Material Float `json:"material"` // MAT M - Запасы ресурсов / сырьё
}
type UnidentifiedPlanet struct {
X Float `json:"x"`
Y Float `json:"y"`
Number uint `json:"number"`
}
+76
View File
@@ -0,0 +1,76 @@
package report
import (
"encoding/json"
n "galaxy/util"
"github.com/google/uuid"
)
type Float float64
func F(v float64) Float {
return Float(n.Fixed3(v))
}
type Report struct {
Version uint `json:"version"`
Turn uint `json:"turn"`
Width uint32 `json:"mapWidth"`
Height uint32 `json:"mapHeight"`
PlanetCount uint32 `json:"mapPlanets"`
Race string `json:"race"`
RaceID uuid.UUID `json:"-"`
Votes Float `json:"votes"`
VoteFor string `json:"voteFor"`
Player []Player `json:"player"`
LocalScience []Science `json:"localScience,omitempty"`
OtherScience []OtherScience `json:"otherScience,omitempty"`
LocalShipClass []ShipClass `json:"localShipClass,omitempty"`
OtherShipClass []OthersShipClass `json:"otherShipClass,omitempty"`
Battle []uuid.UUID `json:"battle,omitempty"`
Bombing []*Bombing `json:"bombing,omitempty"`
IncomingGroup []IncomingGroup `json:"incomingGroup,omitempty"`
LocalPlanet []LocalPlanet `json:"localPlanet,omitempty"`
ShipProduction []ShipProduction `json:"shipProduction,omitempty"`
Route []Route `json:"route,omitempty"`
OtherPlanet []OtherPlanet `json:"otherPlanet,omitempty"`
UninhabitedPlanet []UninhabitedPlanet `json:"uninhabitedPlanet,omitempty"`
UnidentifiedPlanet []UnidentifiedPlanet `json:"unidentifiedPlanet,omitempty"`
LocalFleet []LocalFleet `json:"localFleet,omitempty"`
LocalGroup []LocalGroup `json:"localGroup,omitempty"`
OtherGroup []OtherGroup `json:"otherGroup,omitempty"`
UnidentifiedGroup []UnidentifiedGroup `json:"unidentifiedGroup,omitempty"`
OnPlanetGroupCache map[uint][]int `json:"-"`
InSpaceGroupRangeCache map[int]map[uint]float64 `json:"-"`
}
type Route struct {
Planet uint `json:"planet"`
Route map[uint]string `json:"route"`
}
type Player struct {
ID uuid.UUID `json:"-"`
Name string `json:"name"`
Drive Float `json:"drive"`
Weapons Float `json:"weapons"`
Shields Float `json:"shields"`
Cargo Float `json:"cargo"`
Population Float `json:"population"`
Industry Float `json:"industry"`
Planets uint16 `json:"planets"`
Relation string `json:"relation"`
Votes Float `json:"votes"`
Extinct bool `json:"extinct"`
}
func (r Report) MarshalBinary() (data []byte, err error) {
return json.Marshal(&r)
}
func (r *Report) UnmarshalBinary(data []byte) error {
return json.Unmarshal(data, r)
}
+14
View File
@@ -0,0 +1,14 @@
package report
type Science struct {
Name string `json:"name"`
Drive Float `json:"drive"`
Weapons Float `json:"weapons"`
Shields Float `json:"shields"`
Cargo Float `json:"cargo"`
}
type OtherScience struct {
Race string `json:"race"`
Science
}
+70
View File
@@ -0,0 +1,70 @@
package report
import "github.com/google/uuid"
type ShipClass struct {
Name string `json:"name"`
Drive Float `json:"drive"`
Armament uint `json:"armament"`
Weapons Float `json:"weapons"`
Shields Float `json:"shields"`
Cargo Float `json:"cargo"`
Mass Float `json:"mass"`
}
type OthersShipClass struct {
Race string `json:"race"`
ShipClass
}
type ShipProduction struct {
Planet uint `json:"planet"` // Галактический номер планеты
Class string `json:"class"` // Наименование типа строящегося корабля
Cost Float `json:"cost"` // Стоимость постройки одного такого корабля (в производственных ед.) без учета расходов на добычу сырья
ProdUsed Float `json:"prodUsed"` // Сколько производственных единиц уже было затрачено на постройку этого корабля (уже учитывая производство сырья)
Percent Float `json:"percent"` // Процент завершения активного производства
Free Float `json:"free"` // Свободный производственный потенциал
}
type IncomingGroup struct {
Origin uint `json:"origin"`
Destination uint `json:"destination"`
Distance Float `json:"distance"`
Speed Float `json:"speed"`
Mass Float `json:"mass"`
}
type LocalGroup struct {
OtherGroup
ID uuid.UUID `json:"id"`
State string `json:"state"`
Fleet *string `json:"fleet"`
}
type OtherGroup struct {
Number uint `json:"number"`
Class string `json:"class"`
Tech map[string]Float `json:"tech"`
Cargo string `json:"cargo"`
Load Float `json:"load"`
Destination uint `json:"destination"`
Origin *uint `json:"origin,omitempty"`
Range *Float `json:"range,omitempty"`
Speed Float `json:"speed"`
Mass Float `json:"mass"`
}
type UnidentifiedGroup struct {
X Float `json:"x"`
Y Float `json:"y"`
}
type LocalFleet struct {
Name string `json:"name"`
Groups uint `json:"groups"`
Destination uint `json:"destination"`
Origin *uint `json:"origin,omitempty"`
Range *Float `json:"range,omitempty"`
Speed Float `json:"speed"`
State string `json:"state"`
}
+16
View File
@@ -0,0 +1,16 @@
package rest
import "encoding/json"
type Command struct {
Actor string `json:"actor" binding:"notblank"`
Commands []json.RawMessage `json:"cmd" binding:"min=1"`
}
func (o Command) MarshalBinary() (data []byte, err error) {
return json.Marshal(&o)
}
func (o *Command) UnmarshalBinary(data []byte) error {
return json.Unmarshal(data, o)
}
+9
View File
@@ -0,0 +1,9 @@
package rest
type Init struct {
Races []Race `json:"races" binding:"required,gte=10"`
}
type Race struct {
Name string `json:"name" binding:"required,notblank"`
}
+16
View File
@@ -0,0 +1,16 @@
package rest
import "github.com/google/uuid"
type StateResponse struct {
ID uuid.UUID `json:"id"`
Turn uint `json:"turn"`
Stage uint `json:"stage"`
Players []PlayerState `json:"player"`
}
type PlayerState struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Extinct bool `json:"extinct"`
}
+19
View File
@@ -0,0 +1,19 @@
package util
import (
"os"
"testing"
)
func CreateWorkDir(t *testing.T) (string, func()) {
t.Helper()
dir, err := os.MkdirTemp("", "fs-test-workdir")
if err != nil {
t.Fatalf("create temp dir: %s", err)
}
return dir, func() {
if err := os.RemoveAll(dir); err != nil {
t.Fatalf("remove temp dir: %s", err)
}
}
}
+14
View File
@@ -0,0 +1,14 @@
module galaxy/util
go 1.26.0
require github.com/stretchr/testify v1.11.1
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+8
View File
@@ -0,0 +1,8 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+81
View File
@@ -0,0 +1,81 @@
package util
import "math"
func ShortDistance(w, h uint32, x1, y1, x2, y2 float64) float64 {
return math.Hypot(deltas(w, h, x1, y1, x2, y2))
}
func NextTravelCoord(w, h uint32, x1, y1, x2, y2, delta float64) (float64, float64, bool) {
deltaX, deltaY := deltas(w, h, x1, y1, x2, y2)
distance := math.Hypot(deltaX, deltaY)
if distance <= delta {
return x2, y2, true
}
// [ ] refactor - remove extra vars
xa := 0.
ya := 0.
xb := deltaX
yb := deltaY
d := distance
d2 := delta
xc := xa - (d2*(xa-xb))/d
yc := ya - (d2*(ya-yb))/d
// ---
var tx, ty float64
if math.Abs(x2-x1) > float64(w/2) {
// moving across X boundary
if x2 < x1 {
// moving across higher border
tx = math.Mod(x1+xc, float64(w))
} else {
// moving across lower border
tx = x1 - xc
if tx < 0 {
tx = float64(w) + tx
}
}
} else {
if x2 < x1 {
tx = x1 - xc
} else {
tx = x1 + xc
}
}
if math.Abs(y2-y1) > float64(h/2) {
// moving across Y boundary
if y2 < y1 {
// moving across higher border
ty = math.Mod(y1+yc, float64(h))
} else {
// moving across lower border
ty = y1 - yc
if ty < 0 {
ty = float64(h) + ty
}
}
} else {
if y2 < y1 {
ty = y1 - yc
} else {
ty = y1 + yc
}
}
return tx, ty, false
}
func deltas(w, h uint32, x1, y1, x2, y2 float64) (float64, float64) {
dx := math.Abs(x2 - x1)
dy := math.Abs(y2 - y1)
if dx > float64(w/2) {
dx = float64(h) - dx
}
if dy > float64(h/2) {
dy = float64(h) - dy
}
return dx, dy
}
+56
View File
@@ -0,0 +1,56 @@
package util_test
import (
"fmt"
"testing"
"galaxy/util"
"github.com/stretchr/testify/assert"
)
func TestShortDistance(t *testing.T) {
for i, tc := range []struct {
w, h uint32
x1, y1, x2, y2, d float64
}{
{10, 10, 0, 0, 5, 5, 7.071},
{10, 10, 0, 0, 5.01, 5.01, 7.057},
{10, 10, 2, 2, 8, 2, 4.},
{10, 10, 8, 7, 1, 7, 3.},
} {
t.Run(fmt.Sprint(i), func(t *testing.T) {
d := util.ShortDistance(tc.w, tc.h, tc.x1, tc.y1, tc.x2, tc.y2)
assert.Equal(t, tc.d, util.Fixed3(d))
})
}
}
func TestNextTravelCoord(t *testing.T) {
for i, tc := range []struct {
w, h uint32
ox, oy, dx, dy, delta float64
tx, ty float64
arrived bool
}{
{w: 10, h: 10, ox: 0.0, oy: 0.0, dx: 2.0, dy: 0.0, delta: 1.0, tx: 1.0, ty: 0.0, arrived: false},
{w: 10, h: 10, ox: 0.0, oy: 0.0, dx: 0.0, dy: 2.0, delta: 1.0, tx: 0.0, ty: 1.0, arrived: false},
{w: 10, h: 10, ox: 1.0, oy: 1.0, dx: 9.0, dy: 1.0, delta: 1.0, tx: 0.0, ty: 1.0, arrived: false},
{w: 10, h: 10, ox: 1.0, oy: 9.5, dx: 1.0, dy: 1.0, delta: 1.0, tx: 1.0, ty: 0.5, arrived: false},
{w: 10, h: 10, ox: 1.0, oy: 1.0, dx: 5.0, dy: 5.0, delta: 2.0, tx: 2.414, ty: 2.414, arrived: false},
{w: 10, h: 10, ox: 1.0, oy: 1.0, dx: 9.0, dy: 9.0, delta: 2.0, tx: 9.586, ty: 9.586, arrived: false},
{w: 10, h: 10, ox: 5.0, oy: 5.0, dx: 9.0, dy: 9.0, delta: 6.0, tx: 9.0, ty: 9.0, arrived: true},
{w: 10, h: 10, ox: 6.0, oy: 6.0, dx: 10.0, dy: 10.0, delta: 6.0, tx: 10.0, ty: 10.0, arrived: true},
{w: 10, h: 10, ox: 1.0, oy: 2.0, dx: 7.0, dy: 8.0, delta: 6.0, tx: 7.0, ty: 8.0, arrived: true},
} {
t.Run(fmt.Sprint(i), func(t *testing.T) {
tx, ty, arrived := util.NextTravelCoord(tc.w, tc.h, tc.ox, tc.oy, tc.dx, tc.dy, tc.delta)
assert.Equal(t, tc.arrived, arrived)
assert.Equal(t, tc.tx, util.Fixed3(tx))
assert.Equal(t, tc.ty, util.Fixed3(ty))
})
}
}
+22
View File
@@ -0,0 +1,22 @@
package util
import (
"math"
)
func Fixed3(num float64) float64 {
return fixed(num, 3)
}
func Fixed12(num float64) float64 {
return fixed(num, 12)
}
func fixed(num float64, precision int) float64 {
output := math.Pow(10, float64(precision))
return float64(round(num*output)) / output
}
func round(num float64) int {
return int(num + math.Copysign(0.5, num))
}
+33
View File
@@ -0,0 +1,33 @@
package util
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFixed(t *testing.T) {
for _, tc := range []struct {
precision int
source, expected float64
}{
{3, 0, 0},
{3, -1, -1},
{3, 1.5, 1.5},
{3, 2.25, 2.25},
{3, 3.275, 3.275},
{3, 4.0004, 4.000},
{5, 5.000005, 5.00001},
{4, -6.00004, -6.},
{4, -6.00005, -6.0001},
} {
t.Run(fmt.Sprintf("%f", tc.source), func(t *testing.T) {
if tc.precision == 3 {
assert.Equal(t, tc.expected, Fixed3(tc.source))
} else {
assert.Equal(t, tc.expected, fixed(tc.source, tc.precision))
}
})
}
}
+99
View File
@@ -0,0 +1,99 @@
package util
import (
"fmt"
"math/rand"
"strings"
"unicode"
)
const (
maxNameLength = 30
specialChars = "!@#$%^*-_=+~()[]{}" // Allowed special characters
)
var allowedSpecialChars map[rune]bool
func init() {
allowedSpecialChars = make(map[rune]bool)
for _, r := range []rune(specialChars) {
allowedSpecialChars[r] = true
}
}
func ValidateTypeName(input string) (string, bool) {
// Trim leading and trailing spaces
trimmed := strings.TrimSpace(input)
// If the string is empty after trimming, return false
if len(trimmed) == 0 {
return "", false
}
runes := []rune(trimmed)
if len(runes) > maxNameLength {
return "", false
}
// Dash cannot be at the beginning or end
if allowedSpecialChars[runes[0]] || allowedSpecialChars[runes[len(runes)-1]] {
return "", false
}
// if runes[0] == '-' || runes[len(runes)-1] == '-' {
// return "", false
// }
var specialCount uint8
for _, r := range runes {
// Check if the character is a whitespace, which is not allowed
if unicode.IsSpace(r) {
return "", false
}
// Letters (including any alphabet) and digits are allowed
if unicode.IsLetter(r) || unicode.IsDigit(r) {
specialCount = 0
continue
}
// Combining marks (accents) are allowed
if unicode.IsMark(r) {
specialCount = 0
continue
}
// Check for allowed special characters
if allowedSpecialChars[r] {
if specialCount == 2 {
return "", false
}
specialCount++
continue
}
// If any other character is encountered, return false
return "", false
}
// Return the trimmed string and true if all conditions are met
return trimmed, true
}
func AppendRandomSuffix(v string) string {
return AppendRandomSuffixGenerator(v, RandomSuffixGenerator)
}
func AppendRandomSuffixGenerator(v string, s func() string) string {
suffix := []rune(s())
str := []rune(v)
max := maxNameLength - len(suffix)
if len(str) > max {
str = str[:max]
}
return string(append(str, suffix...))
}
func RandomSuffixGenerator() string {
return fmt.Sprintf("%04d", rand.Intn(9999))
}
+293
View File
@@ -0,0 +1,293 @@
package util_test
import (
"strings"
"testing"
"unicode/utf8"
"galaxy/util"
"github.com/stretchr/testify/assert"
)
func TestValidateString(t *testing.T) {
tests := []struct {
name string
input string
expected string
ok bool
}{
// Basic cases
{
name: "Valid string with Latin characters and digits",
input: "Hello_World-123",
expected: "Hello_World-123",
ok: true,
},
{
name: "Valid string with Cyrillic characters",
input: "Привет_мир-42",
expected: "Привет_мир-42",
ok: true,
},
{
name: "Valid Greek alphabet string",
input: "Αλφα_Βητα-2024",
expected: "Αλφα_Βητα-2024",
ok: true,
},
{
name: "Valid Arabic alphabet string",
input: "مرحبا_العالم-7",
expected: "مرحبا_العالم-7",
ok: true,
},
{
name: "Valid Japanese Katakana string",
input: "テスト_ケース-1",
expected: "テスト_ケース-1",
ok: true,
},
{
name: "Valid Chinese characters",
input: "你好_世界-123", // "Hello World" in Chinese
expected: "你好_世界-123",
ok: true,
},
{
name: "Valid Hindi characters",
input: "नमस्ते_दुनिया-456", // "Hello World" in Hindi
expected: "नमस्ते_दुनिया-456",
ok: true,
},
{
name: "Valid Thai characters",
input: "สวัสดี_โลก-789", // "Hello World" in Thai
expected: "สวัสดี_โลก-789",
ok: true,
},
{
name: "Valid Korean characters",
input: "안녕하세요_세계-101", // "Hello World" in Korean
expected: "안녕하세요_세계-101",
ok: true,
},
{
name: "Valid Hebrew characters",
input: "שלום_עולם-202", // "Hello World" in Hebrew
expected: "שלום_עולם-202",
ok: true,
},
// Special characters test cases
{
name: "Valid special character @",
input: "Test@Name",
expected: "Test@Name",
ok: true,
},
{
name: "Valid special character ^",
input: "Test^Name",
expected: "Test^Name",
ok: true,
},
{
name: "Valid special character ~",
input: "Test~Name",
expected: "Test~Name",
ok: true,
},
// Edge cases
{
name: "Spaces are trimmed from both ends",
input: " Test123_Name ",
expected: "Test123_Name",
ok: true,
},
{
name: "Spaces in the middle are not allowed",
input: "Test 123",
expected: "",
ok: false,
},
{
name: "Tab character in the middle is not allowed",
input: "Test\tName",
expected: "",
ok: false,
},
{
name: "Newline character is not allowed",
input: "Test\nName",
expected: "",
ok: false,
},
{
name: "Dash at the beginning after TrimSpace is not allowed",
input: " -Test123",
expected: "",
ok: false,
},
{
name: "Dash at the end after TrimSpace is not allowed",
input: "Test123- ",
expected: "",
ok: false,
},
{
name: "Emoji is not allowed",
input: "Test🙂Name",
expected: "",
ok: false,
},
{
name: "String containing only spaces",
input: " ",
expected: "",
ok: false,
},
{
name: "Empty string",
input: "",
expected: "",
ok: false,
},
{
name: "Too long string",
input: "ValidatedStringHasTooManyCharacters",
expected: "",
ok: false,
},
{
name: "Valid consecutive special chars",
input: "Valid_(special)_Chars",
expected: "Valid_(special)_Chars",
ok: true,
},
{
name: "Too many consecutive special chars",
input: "Too_Many_(special[_]Chars",
expected: "",
ok: false,
},
{
name: "Special char at the beginning",
input: "$pecialString",
expected: "",
ok: false,
},
{
name: "Special char at the end",
input: "SpecialString_",
expected: "",
ok: false,
},
{
name: "All valid special chars",
input: "A@#b$%c^*d-_e=+f~(g)[h]{i}j",
expected: "A@#b$%c^*d-_e=+f~(g)[h]{i}j",
ok: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, ok := util.ValidateTypeName(tt.input)
assert.Equal(t, tt.ok, ok)
assert.Equal(t, tt.expected, result)
})
}
}
// Fuzz test for ValidateString function
func FuzzValidateString(f *testing.F) {
// Adding a few basic strings to start the fuzz test
f.Add("Hello_World-123")
f.Add("Test@Name")
f.Add("Привет_мир-42")
f.Add("αβγ@~")
f.Add("مرحبا_العالم-7")
// Fuzz function
f.Fuzz(func(t *testing.T, input string) {
// Call the function and check if the result matches expectations
result, ok := util.ValidateTypeName(input)
// Check if the string is non-empty and valid UTF-8
if len(input) > 0 {
if !utf8.ValidString(input) {
t.Errorf("Error: string is not a valid UTF-8 string: %s", input)
}
}
// If the string is empty, ok should be false
if len(result) == 0 {
if ok {
t.Errorf("Expected false for invalid string, but got true: %s", input)
}
} else {
// If the result is not empty, ok should be true
if !ok {
t.Errorf("Expected true for valid string, but got false: %s", input)
}
}
// Additional check: if input has spaces at the beginning or end, it should fail
if input[0] == ' ' || input[len(input)-1] == ' ' {
if ok {
t.Errorf("Error: string contains spaces at the beginning or end: %s", input)
}
}
})
}
func TestAppendRandomSuffixGenerator(t *testing.T) {
tests := []struct {
name string
input string
suffix string
expected string
}{
{
name: "Regular String",
input: "Regular_String",
suffix: "1234",
expected: "Regular_String1234",
},
{
name: "Zero Length String",
input: "",
suffix: "1234",
expected: "1234",
},
{
name: "Edge Case String len=28",
input: "Edge_Case_String_ABCDEFGHIGK",
suffix: "1234",
expected: "Edge_Case_String_ABCDEFGHI1234",
},
{
name: "Extra Long String len=31",
input: "Extra_Long_String_ABCDEFGHIGKLM",
suffix: "1234",
expected: "Extra_Long_String_ABCDEFGH1234",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := util.AppendRandomSuffixGenerator(tt.input, func() string { return tt.suffix })
assert.Equal(t, tt.expected, result)
})
}
}
func TestRandomSuffixGenerator(t *testing.T) {
var last string
for range 100 {
s := util.RandomSuffixGenerator()
assert.Len(t, s, 4)
assert.NotEqual(t, last, s)
assert.True(t, strings.ContainsFunc(s, func(r rune) bool { return r >= '0' && r <= '9' }))
last = s
}
}