add game engine openapi
This commit is contained in:
+4
-4
@@ -579,7 +579,7 @@ Minimum runtime-level status set:
|
|||||||
All game-related `message_type` include `game_id`.
|
All game-related `message_type` include `game_id`.
|
||||||
|
|
||||||
Gateway enriches them with authenticated `user_id` and routes them to `Game Master`.
|
Gateway enriches them with authenticated `user_id` and routes them to `Game Master`.
|
||||||
`Game Master` checks whether this user may access this running game, using membership data sourced from `Game Lobby`, then routes the command to the correct engine container.
|
`Game Master` checks whether this user may access this running game, using membership data sourced from `Game Lobby`, then routes the command to the correct engine container using [Game Engine](./game/README.md)'s API.
|
||||||
|
|
||||||
The gateway never routes directly to game engine containers.
|
The gateway never routes directly to game engine containers.
|
||||||
|
|
||||||
@@ -649,7 +649,7 @@ After a game has started, two different actions exist:
|
|||||||
|
|
||||||
This distinction is architectural and must remain explicit.
|
This distinction is architectural and must remain explicit.
|
||||||
|
|
||||||
## 9. Runtime Manager
|
## 9. [Runtime Manager](rtmanager/README.md)
|
||||||
|
|
||||||
`Runtime Manager` is the only internal service allowed to access Docker API directly.
|
`Runtime Manager` is the only internal service allowed to access Docker API directly.
|
||||||
|
|
||||||
@@ -1275,11 +1275,11 @@ Recommended order for implementation is:
|
|||||||
4. **Mail Service** (implemented)
|
4. **Mail Service** (implemented)
|
||||||
Internal email delivery for auth codes and platform notification mail.
|
Internal email delivery for auth codes and platform notification mail.
|
||||||
|
|
||||||
5. **Notification Service** (implemented)
|
5. **Notification Service** (implemented)
|
||||||
Unified async delivery of push and non-auth email notifications, with
|
Unified async delivery of push and non-auth email notifications, with
|
||||||
real Gateway and Mail Service boundary coverage.
|
real Gateway and Mail Service boundary coverage.
|
||||||
|
|
||||||
6. **Game Lobby Service**
|
6. **Game Lobby Service** (implemented)
|
||||||
Platform game records, membership, invites, applications, approvals, schedules, user-facing lists, pre-start lifecycle.
|
Platform game records, membership, invites, applications, approvals, schedules, user-facing lists, pre-start lifecycle.
|
||||||
|
|
||||||
7. **Runtime Manager**
|
7. **Runtime Manager**
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Game Service Engine
|
||||||
|
|
||||||
|
Galaxy game engine — hosts a single game instance and exposes a REST API for
|
||||||
|
game initialization, turn advancement, player reports, and command execution.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
The REST contract is documented in [`openapi.yaml`](openapi.yaml).
|
||||||
+10
@@ -3,6 +3,7 @@ module galaxy/game
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/getkin/kin-openapi v0.135.0
|
||||||
github.com/gin-gonic/gin v1.12.0
|
github.com/gin-gonic/gin v1.12.0
|
||||||
github.com/go-playground/validator/v10 v10.30.2
|
github.com/go-playground/validator/v10 v10.30.2
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
@@ -17,23 +18,32 @@ require (
|
|||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||||
|
github.com/go-openapi/swag v0.23.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.6 // indirect
|
github.com/goccy/go-json v0.10.6 // indirect
|
||||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.21 // indirect
|
github.com/mattn/go-isatty v0.0.21 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
|
github.com/oasdiff/yaml v0.0.9 // indirect
|
||||||
|
github.com/oasdiff/yaml3 v0.0.9 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.3.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.3.0 // indirect
|
||||||
|
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
|
github.com/woodsbury/decimal128 v1.3.0 // indirect
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||||
golang.org/x/arch v0.25.0 // indirect
|
golang.org/x/arch v0.25.0 // indirect
|
||||||
golang.org/x/crypto v0.50.0 // indirect
|
golang.org/x/crypto v0.50.0 // indirect
|
||||||
|
|||||||
+39
@@ -1,16 +1,27 @@
|
|||||||
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
||||||
|
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
|
||||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||||
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
|
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
|
||||||
|
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
|
github.com/getkin/kin-openapi v0.135.0 h1:751SjYfbiwqukYuVjwYEIKNfrSwS5YpA7DZnKSwQgtg=
|
||||||
|
github.com/getkin/kin-openapi v0.135.0/go.mod h1:6dd5FJl6RdX4usBtFBaQhk9q62Yb2J0Mk5IhUO/QqFI=
|
||||||
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
|
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
|
||||||
|
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
|
||||||
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||||
|
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||||
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
@@ -18,7 +29,11 @@ github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/Nu
|
|||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
|
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
|
||||||
|
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
|
||||||
|
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||||
|
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||||
|
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
@@ -26,6 +41,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
|||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
@@ -36,20 +53,34 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||||
|
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||||
|
github.com/oasdiff/yaml v0.0.9 h1:zQOvd2UKoozsSsAknnWoDJlSK4lC0mpmjfDsfqNwX48=
|
||||||
|
github.com/oasdiff/yaml v0.0.9/go.mod h1:8lvhgJG4xiKPj3HN5lDow4jZHPlx1i7dIwzkdAo6oAM=
|
||||||
|
github.com/oasdiff/yaml3 v0.0.9 h1:rWPrKccrdUm8J0F3sGuU+fuh9+1K/RdJlWF7O/9yw2g=
|
||||||
|
github.com/oasdiff/yaml3 v0.0.9/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||||
github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
|
github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||||
|
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||||
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@@ -65,14 +96,22 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
|
||||||
|
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||||
|
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=
|
golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=
|
||||||
|
golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||||
|
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||||
|
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||||
|
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -0,0 +1,964 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: Galaxy Game Service REST API
|
||||||
|
version: v1
|
||||||
|
description: |
|
||||||
|
This specification documents the REST contract of `galaxy/game`.
|
||||||
|
|
||||||
|
The service hosts a single game instance and exposes endpoints for game
|
||||||
|
initialization, turn advancement, game-state queries, player reports, and
|
||||||
|
batched player command execution.
|
||||||
|
|
||||||
|
Transport rules:
|
||||||
|
- request bodies are JSON
|
||||||
|
- `PUT /api/v1/command` is rate-limited to one concurrent execution;
|
||||||
|
requests that cannot acquire the execution slot within 100 ms receive
|
||||||
|
`504 Gateway Timeout`
|
||||||
|
- `501 Not Implemented` is returned without a body when the game has not
|
||||||
|
been initialized
|
||||||
|
- validation errors return `400` with `{"error": "message"}`
|
||||||
|
- game-engine errors return `500` with `{"generic_error": "message", "code": integer}`
|
||||||
|
- other internal errors return `500` with `{"error": "message"}`
|
||||||
|
servers:
|
||||||
|
- url: http://localhost:8080
|
||||||
|
description: Default local listener for Game Service.
|
||||||
|
tags:
|
||||||
|
- name: GameLifecycle
|
||||||
|
description: Game initialization, state retrieval, and turn advancement.
|
||||||
|
- name: PlayerActions
|
||||||
|
description: Player command execution, order validation, and turn-report retrieval.
|
||||||
|
paths:
|
||||||
|
/api/v1/status:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- GameLifecycle
|
||||||
|
operationId: getGameStatus
|
||||||
|
summary: Get the current game state
|
||||||
|
description: |
|
||||||
|
Returns the current game state including turn number, stage, and a
|
||||||
|
summary of all players. Returns `501` if the game has not yet been
|
||||||
|
initialized.
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Current game state.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/StateResponse"
|
||||||
|
"501":
|
||||||
|
description: Game has not been initialized yet.
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/InternalError"
|
||||||
|
/api/v1/init:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- GameLifecycle
|
||||||
|
operationId: initGame
|
||||||
|
summary: Initialize a new game
|
||||||
|
description: |
|
||||||
|
Generates a new game instance with the supplied list of races.
|
||||||
|
Requires at least 10 race entries.
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/InitRequest"
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Game initialized successfully.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/StateResponse"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/ValidationError"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/InternalError"
|
||||||
|
/api/v1/report:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- PlayerActions
|
||||||
|
operationId: getReport
|
||||||
|
summary: Get a player turn report
|
||||||
|
description: |
|
||||||
|
Returns the full game report for the specified player and turn.
|
||||||
|
`player` must be a non-blank race name. `turn` defaults to `0`.
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/PlayerParam"
|
||||||
|
- $ref: "#/components/parameters/TurnParam"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Player turn report.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Report"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/ValidationError"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/InternalError"
|
||||||
|
/api/v1/command:
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- PlayerActions
|
||||||
|
operationId: executeCommands
|
||||||
|
summary: Execute a batch of player commands
|
||||||
|
description: |
|
||||||
|
Applies one or more game commands for the specified actor. Serialized
|
||||||
|
to one concurrent execution; requests that cannot acquire the execution
|
||||||
|
slot within 100 ms return `504 Gateway Timeout`. Returns `204 No
|
||||||
|
Content` on success.
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/CommandRequest"
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: All commands applied successfully.
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/ValidationError"
|
||||||
|
"504":
|
||||||
|
description: Command execution slot not acquired within 100 ms.
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/InternalError"
|
||||||
|
/api/v1/order:
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- PlayerActions
|
||||||
|
operationId: validateOrder
|
||||||
|
summary: Validate and store a player order without executing it
|
||||||
|
description: |
|
||||||
|
Validates and stores the game commands structurally without executing them.
|
||||||
|
Returns `204 No Content` if the order is valid and accepted.
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/CommandRequest"
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: Order is structurally valid.
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/ValidationError"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/InternalError"
|
||||||
|
/api/v1/turn:
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- GameLifecycle
|
||||||
|
operationId: generateTurn
|
||||||
|
summary: Advance the game to the next turn
|
||||||
|
description: |
|
||||||
|
Processes the current turn and generates the next one. Returns the
|
||||||
|
updated game state.
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Updated game state after turn generation.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/StateResponse"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/InternalError"
|
||||||
|
components:
|
||||||
|
parameters:
|
||||||
|
PlayerParam:
|
||||||
|
name: player
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
description: Race name of the player requesting the report. Must be non-blank.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
TurnParam:
|
||||||
|
name: turn
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Turn number to load the report for. Defaults to 0.
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
default: 0
|
||||||
|
schemas:
|
||||||
|
StateResponse:
|
||||||
|
type: object
|
||||||
|
description: Summary game state returned after initialization and at each turn boundary.
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- turn
|
||||||
|
- stage
|
||||||
|
- player
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: Unique identifier of this game instance.
|
||||||
|
turn:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Current turn number.
|
||||||
|
stage:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Current stage within the turn for games that support state modification.
|
||||||
|
player:
|
||||||
|
type: array
|
||||||
|
description: Summary state for each player participating in the game.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/PlayerState"
|
||||||
|
PlayerState:
|
||||||
|
type: object
|
||||||
|
description: Brief player state returned as part of the game state response.
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- raceName
|
||||||
|
- planets
|
||||||
|
- population
|
||||||
|
- extinct
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: Unique player identifier within this game.
|
||||||
|
raceName:
|
||||||
|
type: string
|
||||||
|
description: Name of the player's race.
|
||||||
|
planets:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Number of planets currently owned by the player.
|
||||||
|
population:
|
||||||
|
type: number
|
||||||
|
description: Total population summed across all player planets.
|
||||||
|
extinct:
|
||||||
|
type: boolean
|
||||||
|
description: True when the race has been eliminated or voluntarily quit.
|
||||||
|
InitRequest:
|
||||||
|
type: object
|
||||||
|
description: Initialization request specifying the race list for a new game.
|
||||||
|
required:
|
||||||
|
- races
|
||||||
|
properties:
|
||||||
|
races:
|
||||||
|
type: array
|
||||||
|
description: List of participating races. Minimum 10 entries required.
|
||||||
|
minItems: 10
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/InitRace"
|
||||||
|
InitRace:
|
||||||
|
type: object
|
||||||
|
description: A single race entry in an initialization request.
|
||||||
|
required:
|
||||||
|
- raceName
|
||||||
|
properties:
|
||||||
|
raceName:
|
||||||
|
type: string
|
||||||
|
description: Name of the race. Must be non-blank and satisfy the entity-name format.
|
||||||
|
minLength: 1
|
||||||
|
CommandRequest:
|
||||||
|
type: object
|
||||||
|
description: |
|
||||||
|
Batch command payload. `actor` identifies the race submitting the commands.
|
||||||
|
Each element of `cmd` is a polymorphic command object discriminated by the
|
||||||
|
`@type` field. At least one command is required.
|
||||||
|
required:
|
||||||
|
- actor
|
||||||
|
- cmd
|
||||||
|
properties:
|
||||||
|
actor:
|
||||||
|
type: string
|
||||||
|
description: Race name of the actor submitting the commands. Must be non-blank.
|
||||||
|
minLength: 1
|
||||||
|
cmd:
|
||||||
|
type: array
|
||||||
|
description: One or more commands to execute in order.
|
||||||
|
minItems: 1
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Command"
|
||||||
|
Command:
|
||||||
|
type: object
|
||||||
|
description: |
|
||||||
|
Polymorphic game command. The `@type` field identifies the variant.
|
||||||
|
Each variant extends the base fields with additional type-specific
|
||||||
|
parameters documented in `pkg/model/order/order.go`.
|
||||||
|
required:
|
||||||
|
- "@type"
|
||||||
|
- cmdId
|
||||||
|
properties:
|
||||||
|
"@type":
|
||||||
|
$ref: "#/components/schemas/CommandType"
|
||||||
|
cmdId:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: Unique command identifier (RFC 4122 UUID).
|
||||||
|
cmdApplied:
|
||||||
|
type: boolean
|
||||||
|
description: Set in command-result responses; true when the command was applied.
|
||||||
|
cmdErrorCode:
|
||||||
|
type: integer
|
||||||
|
description: Set in command-result responses; non-zero when the command was rejected.
|
||||||
|
CommandType:
|
||||||
|
type: string
|
||||||
|
description: Discriminator identifying the game command variant carried in a `cmd` element.
|
||||||
|
enum:
|
||||||
|
- raceQuit
|
||||||
|
- raceVote
|
||||||
|
- raceRelation
|
||||||
|
- shipClassCreate
|
||||||
|
- shipClassMerge
|
||||||
|
- shipClassRemove
|
||||||
|
- shipGroupBreak
|
||||||
|
- shipGroupLoad
|
||||||
|
- shipGroupUnload
|
||||||
|
- shipGroupSend
|
||||||
|
- shipGroupUpgrade
|
||||||
|
- shipGroupMerge
|
||||||
|
- shipGroupDismantle
|
||||||
|
- shipGroupTransfer
|
||||||
|
- shipGroupJoinFleet
|
||||||
|
- fleetMerge
|
||||||
|
- fleetSend
|
||||||
|
- scienceCreate
|
||||||
|
- scienceRemove
|
||||||
|
- planetRename
|
||||||
|
- planetProduce
|
||||||
|
- planetRouteSet
|
||||||
|
- planetRouteRemove
|
||||||
|
Report:
|
||||||
|
type: object
|
||||||
|
description: |
|
||||||
|
Full game report for one player at one turn boundary. Optional array
|
||||||
|
fields are omitted when empty.
|
||||||
|
required:
|
||||||
|
- version
|
||||||
|
- turn
|
||||||
|
- mapWidth
|
||||||
|
- mapHeight
|
||||||
|
- mapPlanets
|
||||||
|
- race
|
||||||
|
- votes
|
||||||
|
- voteFor
|
||||||
|
- player
|
||||||
|
properties:
|
||||||
|
version:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Report format version.
|
||||||
|
turn:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Turn number this report covers.
|
||||||
|
mapWidth:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Width of the star map.
|
||||||
|
mapHeight:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Height of the star map.
|
||||||
|
mapPlanets:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Total number of planets on the map.
|
||||||
|
race:
|
||||||
|
type: string
|
||||||
|
description: Race name of the report recipient.
|
||||||
|
votes:
|
||||||
|
type: number
|
||||||
|
description: Fraction of alliance votes held by this race.
|
||||||
|
voteFor:
|
||||||
|
type: string
|
||||||
|
description: Race name this player is currently voting for.
|
||||||
|
player:
|
||||||
|
type: array
|
||||||
|
description: Diplomatic and aggregate statistics for each known player.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/ReportPlayer"
|
||||||
|
localScience:
|
||||||
|
type: array
|
||||||
|
description: Science projects owned by this race.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Science"
|
||||||
|
otherScience:
|
||||||
|
type: array
|
||||||
|
description: Science projects owned by other known races.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/OtherScience"
|
||||||
|
localShipClass:
|
||||||
|
type: array
|
||||||
|
description: Ship classes designed by this race.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/ShipClass"
|
||||||
|
otherShipClass:
|
||||||
|
type: array
|
||||||
|
description: Ship classes belonging to other known races.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/OtherShipClass"
|
||||||
|
battle:
|
||||||
|
type: array
|
||||||
|
description: UUIDs of battle reports relevant to this turn.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
bombing:
|
||||||
|
type: array
|
||||||
|
description: Bombing events that occurred during this turn.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Bombing"
|
||||||
|
incomingGroup:
|
||||||
|
type: array
|
||||||
|
description: Identified ship groups inbound toward this race's planets.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/IncomingGroup"
|
||||||
|
localPlanet:
|
||||||
|
type: array
|
||||||
|
description: Full state of planets owned by this race.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/LocalPlanet"
|
||||||
|
shipProduction:
|
||||||
|
type: array
|
||||||
|
description: Active ship construction status on this race's planets.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/ShipProduction"
|
||||||
|
route:
|
||||||
|
type: array
|
||||||
|
description: Cargo route configuration per planet.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Route"
|
||||||
|
otherPlanet:
|
||||||
|
type: array
|
||||||
|
description: Partial state of planets owned by other known races.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/OtherPlanet"
|
||||||
|
uninhabitedPlanet:
|
||||||
|
type: array
|
||||||
|
description: Uninhabited planets within sensor range.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/UninhabitedPlanet"
|
||||||
|
unidentifiedPlanet:
|
||||||
|
type: array
|
||||||
|
description: Unidentified planet positions within sensor range.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/UnidentifiedPlanet"
|
||||||
|
localFleet:
|
||||||
|
type: array
|
||||||
|
description: Named fleets belonging to this race.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/LocalFleet"
|
||||||
|
localGroup:
|
||||||
|
type: array
|
||||||
|
description: Ship groups belonging to this race.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/LocalGroup"
|
||||||
|
otherGroup:
|
||||||
|
type: array
|
||||||
|
description: Ship groups belonging to other known races.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/OtherGroup"
|
||||||
|
unidentifiedGroup:
|
||||||
|
type: array
|
||||||
|
description: Unidentified ship group positions within sensor range.
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/UnidentifiedGroup"
|
||||||
|
ReportPlayer:
|
||||||
|
type: object
|
||||||
|
description: Diplomatic and aggregate statistics for one player as seen in a report.
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- drive
|
||||||
|
- weapons
|
||||||
|
- shields
|
||||||
|
- cargo
|
||||||
|
- population
|
||||||
|
- industry
|
||||||
|
- planets
|
||||||
|
- relation
|
||||||
|
- votes
|
||||||
|
- extinct
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Race name.
|
||||||
|
drive:
|
||||||
|
type: number
|
||||||
|
description: Current drive technology level.
|
||||||
|
weapons:
|
||||||
|
type: number
|
||||||
|
description: Current weapons technology level.
|
||||||
|
shields:
|
||||||
|
type: number
|
||||||
|
description: Current shields technology level.
|
||||||
|
cargo:
|
||||||
|
type: number
|
||||||
|
description: Current cargo technology level.
|
||||||
|
population:
|
||||||
|
type: number
|
||||||
|
description: Total population across all planets.
|
||||||
|
industry:
|
||||||
|
type: number
|
||||||
|
description: Total industrial output.
|
||||||
|
planets:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Number of planets owned.
|
||||||
|
relation:
|
||||||
|
type: string
|
||||||
|
description: Current diplomatic relation toward this race.
|
||||||
|
votes:
|
||||||
|
type: number
|
||||||
|
description: Fraction of alliance votes held.
|
||||||
|
extinct:
|
||||||
|
type: boolean
|
||||||
|
description: True when the race has been eliminated or quit.
|
||||||
|
Science:
|
||||||
|
type: object
|
||||||
|
description: A science project describing technology investment ratios.
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- drive
|
||||||
|
- weapons
|
||||||
|
- shields
|
||||||
|
- cargo
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Science project name.
|
||||||
|
drive:
|
||||||
|
type: number
|
||||||
|
description: Investment ratio for drive technology (0–1).
|
||||||
|
weapons:
|
||||||
|
type: number
|
||||||
|
description: Investment ratio for weapons technology (0–1).
|
||||||
|
shields:
|
||||||
|
type: number
|
||||||
|
description: Investment ratio for shields technology (0–1).
|
||||||
|
cargo:
|
||||||
|
type: number
|
||||||
|
description: Investment ratio for cargo technology (0–1).
|
||||||
|
OtherScience:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/Science"
|
||||||
|
- type: object
|
||||||
|
required:
|
||||||
|
- race
|
||||||
|
properties:
|
||||||
|
race:
|
||||||
|
type: string
|
||||||
|
description: Race that owns this science project.
|
||||||
|
ShipClass:
|
||||||
|
type: object
|
||||||
|
description: Design parameters for one ship class.
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- drive
|
||||||
|
- armament
|
||||||
|
- weapons
|
||||||
|
- shields
|
||||||
|
- cargo
|
||||||
|
- mass
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Ship class name.
|
||||||
|
drive:
|
||||||
|
type: number
|
||||||
|
description: Drive technology level.
|
||||||
|
armament:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Number of weapon mounts (ammo units).
|
||||||
|
weapons:
|
||||||
|
type: number
|
||||||
|
description: Weapons technology multiplier.
|
||||||
|
shields:
|
||||||
|
type: number
|
||||||
|
description: Shields technology multiplier.
|
||||||
|
cargo:
|
||||||
|
type: number
|
||||||
|
description: Cargo technology multiplier.
|
||||||
|
mass:
|
||||||
|
type: number
|
||||||
|
description: Computed ship mass.
|
||||||
|
OtherShipClass:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/ShipClass"
|
||||||
|
- type: object
|
||||||
|
required:
|
||||||
|
- race
|
||||||
|
properties:
|
||||||
|
race:
|
||||||
|
type: string
|
||||||
|
description: Race that owns this ship class.
|
||||||
|
Bombing:
|
||||||
|
type: object
|
||||||
|
description: Record of a bombing event that occurred during turn processing.
|
||||||
|
required:
|
||||||
|
- planet
|
||||||
|
- planetName
|
||||||
|
- owner
|
||||||
|
- attacker
|
||||||
|
- production
|
||||||
|
- industry
|
||||||
|
- population
|
||||||
|
- colonists
|
||||||
|
- capital
|
||||||
|
- material
|
||||||
|
- attack
|
||||||
|
- wiped
|
||||||
|
properties:
|
||||||
|
planet:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Global planet number.
|
||||||
|
planetName:
|
||||||
|
type: string
|
||||||
|
description: Planet name.
|
||||||
|
owner:
|
||||||
|
type: string
|
||||||
|
description: Race name of the planet owner.
|
||||||
|
attacker:
|
||||||
|
type: string
|
||||||
|
description: Race name of the attacker.
|
||||||
|
production:
|
||||||
|
type: string
|
||||||
|
description: Production type active on the planet at the time of bombing.
|
||||||
|
industry:
|
||||||
|
type: number
|
||||||
|
description: Industrial capacity remaining after the bombing.
|
||||||
|
population:
|
||||||
|
type: number
|
||||||
|
description: Population remaining after the bombing.
|
||||||
|
colonists:
|
||||||
|
type: number
|
||||||
|
description: Colonist units remaining after the bombing.
|
||||||
|
capital:
|
||||||
|
type: number
|
||||||
|
description: Capital reserves remaining after the bombing.
|
||||||
|
material:
|
||||||
|
type: number
|
||||||
|
description: Material reserves remaining after the bombing.
|
||||||
|
attack:
|
||||||
|
type: number
|
||||||
|
description: Aggregate attack power applied during the bombing.
|
||||||
|
wiped:
|
||||||
|
type: boolean
|
||||||
|
description: True when all population was eliminated by the bombing.
|
||||||
|
IncomingGroup:
|
||||||
|
type: object
|
||||||
|
description: An identified ship group inbound toward a planet of this race.
|
||||||
|
required:
|
||||||
|
- origin
|
||||||
|
- destination
|
||||||
|
- distance
|
||||||
|
- speed
|
||||||
|
- mass
|
||||||
|
properties:
|
||||||
|
origin:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Planet number where this group originated.
|
||||||
|
destination:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Planet number this group is heading toward.
|
||||||
|
distance:
|
||||||
|
type: number
|
||||||
|
description: Remaining travel distance.
|
||||||
|
speed:
|
||||||
|
type: number
|
||||||
|
description: Travel speed.
|
||||||
|
mass:
|
||||||
|
type: number
|
||||||
|
description: Total mass of the group.
|
||||||
|
UnidentifiedPlanet:
|
||||||
|
type: object
|
||||||
|
description: Minimal sensor reading for an unidentified planet.
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- "y"
|
||||||
|
- number
|
||||||
|
properties:
|
||||||
|
x:
|
||||||
|
type: number
|
||||||
|
description: Horizontal map coordinate.
|
||||||
|
"y":
|
||||||
|
type: number
|
||||||
|
description: Vertical map coordinate.
|
||||||
|
number:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Global planet number.
|
||||||
|
UninhabitedPlanet:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/UnidentifiedPlanet"
|
||||||
|
- type: object
|
||||||
|
description: An uninhabited planet within sensor range.
|
||||||
|
required:
|
||||||
|
- size
|
||||||
|
- name
|
||||||
|
- resources
|
||||||
|
- capital
|
||||||
|
- material
|
||||||
|
properties:
|
||||||
|
size:
|
||||||
|
type: number
|
||||||
|
description: Planet size.
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Planet name.
|
||||||
|
resources:
|
||||||
|
type: number
|
||||||
|
description: Natural resource yield (R).
|
||||||
|
capital:
|
||||||
|
type: number
|
||||||
|
description: Capital reserves stored on the planet (CAP).
|
||||||
|
material:
|
||||||
|
type: number
|
||||||
|
description: Material reserves stored on the planet (MAT).
|
||||||
|
LocalPlanet:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/UninhabitedPlanet"
|
||||||
|
- type: object
|
||||||
|
description: Full state for a planet owned by this race.
|
||||||
|
required:
|
||||||
|
- industry
|
||||||
|
- population
|
||||||
|
- colonists
|
||||||
|
- production
|
||||||
|
- freeIndustry
|
||||||
|
properties:
|
||||||
|
industry:
|
||||||
|
type: number
|
||||||
|
description: Industrial capacity of the planet (I).
|
||||||
|
population:
|
||||||
|
type: number
|
||||||
|
description: Population of the planet (P).
|
||||||
|
colonists:
|
||||||
|
type: number
|
||||||
|
description: Number of colonists on the planet (COL).
|
||||||
|
production:
|
||||||
|
type: string
|
||||||
|
description: Current production assignment.
|
||||||
|
freeIndustry:
|
||||||
|
type: number
|
||||||
|
description: Unused industrial capacity available for new production (L).
|
||||||
|
OtherPlanet:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/LocalPlanet"
|
||||||
|
- type: object
|
||||||
|
description: Partial planet state for a planet owned by another race.
|
||||||
|
required:
|
||||||
|
- owner
|
||||||
|
properties:
|
||||||
|
owner:
|
||||||
|
type: string
|
||||||
|
description: Race name of the planet owner.
|
||||||
|
Route:
|
||||||
|
type: object
|
||||||
|
description: Cargo route configuration originating from one planet.
|
||||||
|
required:
|
||||||
|
- planet
|
||||||
|
- route
|
||||||
|
properties:
|
||||||
|
planet:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Source planet number.
|
||||||
|
route:
|
||||||
|
type: object
|
||||||
|
description: |
|
||||||
|
Mapping from destination planet number (as a string key) to the
|
||||||
|
cargo load type being routed (MAT, CAP, COL, EMP).
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
ShipProduction:
|
||||||
|
type: object
|
||||||
|
description: Active ship construction progress on one planet.
|
||||||
|
required:
|
||||||
|
- planet
|
||||||
|
- class
|
||||||
|
- cost
|
||||||
|
- prodUsed
|
||||||
|
- percent
|
||||||
|
- free
|
||||||
|
properties:
|
||||||
|
planet:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Planet number where construction is taking place.
|
||||||
|
class:
|
||||||
|
type: string
|
||||||
|
description: Name of the ship class being built.
|
||||||
|
cost:
|
||||||
|
type: number
|
||||||
|
description: Total production cost for one unit of this class.
|
||||||
|
prodUsed:
|
||||||
|
type: number
|
||||||
|
description: Production units already spent on this build.
|
||||||
|
percent:
|
||||||
|
type: number
|
||||||
|
description: Completion percentage of the current build.
|
||||||
|
free:
|
||||||
|
type: number
|
||||||
|
description: Remaining free industrial capacity on this planet.
|
||||||
|
LocalFleet:
|
||||||
|
type: object
|
||||||
|
description: A named fleet belonging to this race.
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- groups
|
||||||
|
- destination
|
||||||
|
- speed
|
||||||
|
- state
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Fleet name.
|
||||||
|
groups:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Number of ship groups in this fleet.
|
||||||
|
destination:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Destination planet number.
|
||||||
|
origin:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Origin planet number when the fleet is in transit.
|
||||||
|
range:
|
||||||
|
type: number
|
||||||
|
description: Remaining travel range when the fleet is in transit.
|
||||||
|
speed:
|
||||||
|
type: number
|
||||||
|
description: Travel speed.
|
||||||
|
state:
|
||||||
|
type: string
|
||||||
|
description: Fleet operational state.
|
||||||
|
OtherGroup:
|
||||||
|
type: object
|
||||||
|
description: A ship group visible to this race belonging to another race.
|
||||||
|
required:
|
||||||
|
- number
|
||||||
|
- class
|
||||||
|
- tech
|
||||||
|
- cargo
|
||||||
|
- load
|
||||||
|
- destination
|
||||||
|
- speed
|
||||||
|
- mass
|
||||||
|
properties:
|
||||||
|
number:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Group identifier number.
|
||||||
|
class:
|
||||||
|
type: string
|
||||||
|
description: Ship class name.
|
||||||
|
tech:
|
||||||
|
type: object
|
||||||
|
description: Technology levels of this group keyed by tech type name.
|
||||||
|
additionalProperties:
|
||||||
|
type: number
|
||||||
|
cargo:
|
||||||
|
type: string
|
||||||
|
description: Type of cargo carried by this group.
|
||||||
|
load:
|
||||||
|
type: number
|
||||||
|
description: Current cargo load quantity.
|
||||||
|
destination:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Destination planet number.
|
||||||
|
origin:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
description: Origin planet number when the group is in transit.
|
||||||
|
range:
|
||||||
|
type: number
|
||||||
|
description: Remaining travel range when the group is in transit.
|
||||||
|
speed:
|
||||||
|
type: number
|
||||||
|
description: Travel speed.
|
||||||
|
mass:
|
||||||
|
type: number
|
||||||
|
description: Total mass of the group.
|
||||||
|
LocalGroup:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/OtherGroup"
|
||||||
|
- type: object
|
||||||
|
description: A ship group belonging to this race with full ownership detail.
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- state
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: Unique group identifier.
|
||||||
|
state:
|
||||||
|
type: string
|
||||||
|
description: Group operational state.
|
||||||
|
fleet:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Name of the fleet this group belongs to, or null if ungrouped.
|
||||||
|
UnidentifiedGroup:
|
||||||
|
type: object
|
||||||
|
description: Positional reading for an unidentified ship group.
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- "y"
|
||||||
|
properties:
|
||||||
|
x:
|
||||||
|
type: number
|
||||||
|
description: Horizontal map coordinate.
|
||||||
|
"y":
|
||||||
|
type: number
|
||||||
|
description: Vertical map coordinate.
|
||||||
|
ValidationErrorResponse:
|
||||||
|
type: object
|
||||||
|
description: Validation error returned for malformed requests.
|
||||||
|
required:
|
||||||
|
- error
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
description: Human-readable validation error message from the binding layer.
|
||||||
|
InternalErrorResponse:
|
||||||
|
type: object
|
||||||
|
description: |
|
||||||
|
Internal server error. The shape depends on the error source:
|
||||||
|
- `{"error": "message"}` for generic runtime errors
|
||||||
|
- `{"generic_error": "message", "code": integer}` for game-engine errors
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
description: Generic runtime error message.
|
||||||
|
generic_error:
|
||||||
|
type: string
|
||||||
|
description: Game-engine error message.
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
description: Game-engine error code.
|
||||||
|
responses:
|
||||||
|
ValidationError:
|
||||||
|
description: Request body or query parameters are invalid.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ValidationErrorResponse"
|
||||||
|
examples:
|
||||||
|
validationError:
|
||||||
|
value:
|
||||||
|
error: "Key: 'InitRequest.Races' Error:Field validation for 'Races' failed on the 'gte' tag"
|
||||||
|
InternalError:
|
||||||
|
description: Internal Game Service error.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/InternalErrorResponse"
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGameOpenAPISpecValidates(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
loadOpenAPISpec(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGameOpenAPISpecFreezesResponseSchemas(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
doc := loadOpenAPISpec(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
method string
|
||||||
|
status int
|
||||||
|
wantRef string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "get game status",
|
||||||
|
path: "/api/v1/status",
|
||||||
|
method: http.MethodGet,
|
||||||
|
status: http.StatusOK,
|
||||||
|
wantRef: "#/components/schemas/StateResponse",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "init game",
|
||||||
|
path: "/api/v1/init",
|
||||||
|
method: http.MethodPost,
|
||||||
|
status: http.StatusCreated,
|
||||||
|
wantRef: "#/components/schemas/StateResponse",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get report",
|
||||||
|
path: "/api/v1/report",
|
||||||
|
method: http.MethodGet,
|
||||||
|
status: http.StatusOK,
|
||||||
|
wantRef: "#/components/schemas/Report",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "generate turn",
|
||||||
|
path: "/api/v1/turn",
|
||||||
|
method: http.MethodPut,
|
||||||
|
status: http.StatusOK,
|
||||||
|
wantRef: "#/components/schemas/StateResponse",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
operation := getOpenAPIOperation(t, doc, tt.path, tt.method)
|
||||||
|
assertSchemaRef(t, responseSchemaRef(t, operation, tt.status), tt.wantRef, tt.name+" response schema")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGameOpenAPISpecFreezesInitRequest(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
doc := loadOpenAPISpec(t)
|
||||||
|
operation := getOpenAPIOperation(t, doc, "/api/v1/init", http.MethodPost)
|
||||||
|
|
||||||
|
assertSchemaRef(t, requestSchemaRef(t, operation), "#/components/schemas/InitRequest", "init request schema")
|
||||||
|
|
||||||
|
schema := componentSchemaRef(t, doc, "InitRequest")
|
||||||
|
assertRequiredFields(t, schema, "races")
|
||||||
|
|
||||||
|
racesSchema := schema.Value.Properties["races"]
|
||||||
|
require.NotNil(t, racesSchema, "InitRequest.races schema must exist")
|
||||||
|
require.Equal(t, uint64(10), racesSchema.Value.MinItems, "InitRequest.races minItems must be 10")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGameOpenAPISpecFreezesCommandRequest(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
doc := loadOpenAPISpec(t)
|
||||||
|
|
||||||
|
for _, path := range []string{"/api/v1/command", "/api/v1/order"} {
|
||||||
|
t.Run(path, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
operation := getOpenAPIOperation(t, doc, path, http.MethodPut)
|
||||||
|
assertSchemaRef(t, requestSchemaRef(t, operation), "#/components/schemas/CommandRequest", path+" command request schema")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
schema := componentSchemaRef(t, doc, "CommandRequest")
|
||||||
|
assertRequiredFields(t, schema, "actor", "cmd")
|
||||||
|
|
||||||
|
cmdSchema := schema.Value.Properties["cmd"]
|
||||||
|
require.NotNil(t, cmdSchema, "CommandRequest.cmd schema must exist")
|
||||||
|
require.Equal(t, uint64(1), cmdSchema.Value.MinItems, "CommandRequest.cmd minItems must be 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGameOpenAPISpecCommandTypeEnumIsComplete(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
doc := loadOpenAPISpec(t)
|
||||||
|
schema := componentSchemaRef(t, doc, "CommandType")
|
||||||
|
|
||||||
|
enumValues := make([]string, 0, len(schema.Value.Enum))
|
||||||
|
for _, v := range schema.Value.Enum {
|
||||||
|
s, ok := v.(string)
|
||||||
|
require.True(t, ok, "CommandType enum entry must be a string")
|
||||||
|
enumValues = append(enumValues, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ElementsMatch(t, []string{
|
||||||
|
"raceQuit",
|
||||||
|
"raceVote",
|
||||||
|
"raceRelation",
|
||||||
|
"shipClassCreate",
|
||||||
|
"shipClassMerge",
|
||||||
|
"shipClassRemove",
|
||||||
|
"shipGroupBreak",
|
||||||
|
"shipGroupLoad",
|
||||||
|
"shipGroupUnload",
|
||||||
|
"shipGroupSend",
|
||||||
|
"shipGroupUpgrade",
|
||||||
|
"shipGroupMerge",
|
||||||
|
"shipGroupDismantle",
|
||||||
|
"shipGroupTransfer",
|
||||||
|
"shipGroupJoinFleet",
|
||||||
|
"fleetMerge",
|
||||||
|
"fleetSend",
|
||||||
|
"scienceCreate",
|
||||||
|
"scienceRemove",
|
||||||
|
"planetRename",
|
||||||
|
"planetProduce",
|
||||||
|
"planetRouteSet",
|
||||||
|
"planetRouteRemove",
|
||||||
|
}, enumValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// helpers (modelled after user/openapi_contract_test.go)
|
||||||
|
|
||||||
|
func loadOpenAPISpec(t *testing.T) *openapi3.T {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
_, thisFile, _, ok := runtime.Caller(0)
|
||||||
|
if !ok {
|
||||||
|
require.FailNow(t, "runtime.Caller failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
specPath := filepath.Join(filepath.Dir(thisFile), "openapi.yaml")
|
||||||
|
loader := openapi3.NewLoader()
|
||||||
|
doc, err := loader.LoadFromFile(specPath)
|
||||||
|
if err != nil {
|
||||||
|
require.Failf(t, "test failed", "load spec %s: %v", specPath, err)
|
||||||
|
}
|
||||||
|
if doc == nil {
|
||||||
|
require.Failf(t, "test failed", "load spec %s: returned nil document", specPath)
|
||||||
|
}
|
||||||
|
if doc.Info == nil {
|
||||||
|
require.Failf(t, "test failed", "load spec %s: missing info section", specPath)
|
||||||
|
}
|
||||||
|
if doc.Info.Version != "v1" {
|
||||||
|
require.Failf(t, "test failed", "spec %s version = %q, want v1", specPath, doc.Info.Version)
|
||||||
|
}
|
||||||
|
if err := doc.Validate(context.Background()); err != nil {
|
||||||
|
require.Failf(t, "test failed", "validate spec %s: %v", specPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOpenAPIOperation(t *testing.T, doc *openapi3.T, path string, method string) *openapi3.Operation {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if doc.Paths == nil {
|
||||||
|
require.Failf(t, "test failed", "spec is missing paths while looking up %s %s", method, path)
|
||||||
|
}
|
||||||
|
pathItem := doc.Paths.Value(path)
|
||||||
|
if pathItem == nil {
|
||||||
|
require.Failf(t, "test failed", "spec is missing path %s", path)
|
||||||
|
}
|
||||||
|
operation := pathItem.GetOperation(method)
|
||||||
|
if operation == nil {
|
||||||
|
require.Failf(t, "test failed", "spec is missing %s operation for path %s", method, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return operation
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestSchemaRef(t *testing.T, operation *openapi3.Operation) *openapi3.SchemaRef {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if operation.RequestBody == nil || operation.RequestBody.Value == nil {
|
||||||
|
require.FailNow(t, "operation is missing request body")
|
||||||
|
}
|
||||||
|
mediaType := operation.RequestBody.Value.Content.Get("application/json")
|
||||||
|
if mediaType == nil || mediaType.Schema == nil {
|
||||||
|
require.FailNow(t, "operation is missing application/json request schema")
|
||||||
|
}
|
||||||
|
|
||||||
|
return mediaType.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func responseSchemaRef(t *testing.T, operation *openapi3.Operation, status int) *openapi3.SchemaRef {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if operation.Responses == nil {
|
||||||
|
require.Failf(t, "test failed", "operation is missing responses for status %d", status)
|
||||||
|
}
|
||||||
|
response := operation.Responses.Status(status)
|
||||||
|
if response == nil || response.Value == nil {
|
||||||
|
require.Failf(t, "test failed", "operation is missing response for status %d", status)
|
||||||
|
}
|
||||||
|
mediaType := response.Value.Content.Get("application/json")
|
||||||
|
if mediaType == nil || mediaType.Schema == nil {
|
||||||
|
require.Failf(t, "test failed", "operation response %d is missing application/json schema", status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mediaType.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func componentSchemaRef(t *testing.T, doc *openapi3.T, name string) *openapi3.SchemaRef {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if doc.Components == nil {
|
||||||
|
require.Failf(t, "test failed", "spec is missing components while looking up schema %s", name)
|
||||||
|
}
|
||||||
|
schema := doc.Components.Schemas[name]
|
||||||
|
if schema == nil || schema.Value == nil {
|
||||||
|
require.Failf(t, "test failed", "spec is missing schema %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSchemaRef(t *testing.T, schemaRef *openapi3.SchemaRef, want string, name string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if schemaRef == nil {
|
||||||
|
require.Failf(t, "test failed", "%s schema ref is nil", name)
|
||||||
|
}
|
||||||
|
if schemaRef.Ref != want {
|
||||||
|
require.Failf(t, "test failed", "%s ref = %q, want %q", name, schemaRef.Ref, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertRequiredFields(t *testing.T, schemaRef *openapi3.SchemaRef, fields ...string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
required := append([]string(nil), schemaRef.Value.Required...)
|
||||||
|
slices.Sort(required)
|
||||||
|
want := append([]string(nil), fields...)
|
||||||
|
slices.Sort(want)
|
||||||
|
if !slices.Equal(required, want) {
|
||||||
|
require.Failf(t, "test failed", "schema required fields = %v, want %v", required, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
go 1.26.1
|
go 1.26.2
|
||||||
|
|
||||||
use (
|
use (
|
||||||
./authsession
|
./authsession
|
||||||
@@ -21,6 +21,7 @@ use (
|
|||||||
./pkg/storage
|
./pkg/storage
|
||||||
./pkg/transcoder
|
./pkg/transcoder
|
||||||
./pkg/util
|
./pkg/util
|
||||||
|
./rtmanager
|
||||||
./user
|
./user
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module galaxy/rtmanager
|
||||||
|
|
||||||
|
go 1.26.2
|
||||||
Reference in New Issue
Block a user