feat(scum-companion): validate typed adapters

This commit is contained in:
npc0-hue
2026-07-29 10:50:32 +08:00
parent 7155e755f8
commit acf4e4a8f0
5 changed files with 261 additions and 6 deletions
@@ -20,6 +20,7 @@ type SafeAdapter interface {
}
type HandlerAvailability struct {
BoundServerID string
ServerVersion string
Capabilities map[string]bool
Approved bool
@@ -66,7 +67,7 @@ func (registry *HandlerRegistry) Execute(ctx context.Context, command ClaimedCom
if err := validateDeclaredCommandAt(command, time.Now); err != nil {
return unsupportedResult("validation-failed"), nil
}
if !registry.availability.Approved || !registry.availability.Capabilities[command.CommandType] {
if strings.TrimSpace(registry.availability.BoundServerID) == "" || !registry.availability.Approved || !registry.availability.Capabilities[command.CommandType] {
return unsupportedResult("unsupported"), nil
}
handler, exists := registry.handlers[command.CommandType]
@@ -93,7 +94,104 @@ func validateDeclaredCommandAt(command ClaimedCommand, now func() time.Time) err
return fmt.Errorf("unsafe payload")
}
}
return nil
return validateCommandPayload(command.CommandType, command.Payload)
}
func validateCommandPayload(commandType string, payload map[string]any) error {
require := func(keys ...string) error {
for _, key := range keys {
if _, ok := payload[key]; !ok {
return fmt.Errorf("payload is incomplete")
}
}
return nil
}
noUnknown := func(keys ...string) error {
allowed := map[string]bool{}
for _, key := range keys {
allowed[key] = true
}
for key := range payload {
if !allowed[key] {
return fmt.Errorf("payload has unsupported field")
}
}
return nil
}
switch commandType {
case "config.read":
return noUnknown()
case "config.patch":
if err := require("revision", "fields"); err != nil {
return err
}
if err := noUnknown("revision", "fields"); err != nil {
return err
}
_, revisionOK := payload["revision"].(string)
fields, fieldsOK := payload["fields"].([]any)
if !revisionOK || !fieldsOK || len(fields) == 0 || len(fields) > 32 {
return fmt.Errorf("config patch payload is invalid")
}
return nil
case "companion.diagnostics":
if err := noUnknown("includeWindowState", "maxEntries"); err != nil {
return err
}
if value, ok := payload["includeWindowState"]; ok {
if _, valid := value.(bool); !valid {
return fmt.Errorf("diagnostics payload is invalid")
}
}
if value, ok := payload["maxEntries"]; ok {
if !boundedDiagnosticsEntries(value) {
return fmt.Errorf("diagnostics payload is invalid")
}
}
return nil
case "game-state.patch":
if err := require("playerId", "gameVersion", "expectedStateVersion", "safetyWindow", "reason", "changes"); err != nil {
return err
}
if err := noUnknown("playerId", "gameVersion", "expectedStateVersion", "safetyWindow", "reason", "changes"); err != nil {
return err
}
version, ok := payload["gameVersion"].(string)
changes, changesOK := payload["changes"].([]any)
if !ok || version == "" || !changesOK || len(changes) == 0 || len(changes) > 8 {
return fmt.Errorf("state patch payload is invalid")
}
return nil
case "reward.deliver":
if err := require("grantId", "playerId", "items"); err != nil {
return err
}
if err := noUnknown("grantId", "playerId", "items"); err != nil {
return err
}
_, grantOK := payload["grantId"].(string)
_, playerOK := payload["playerId"].(string)
items, itemsOK := payload["items"].([]any)
if !grantOK || !playerOK || !itemsOK || len(items) == 0 || len(items) > 8 {
return fmt.Errorf("reward payload is invalid")
}
return nil
case "player.notify":
if err := require("playerId", "message"); err != nil {
return err
}
if err := noUnknown("playerId", "message"); err != nil {
return err
}
_, playerOK := payload["playerId"].(string)
message, messageOK := payload["message"].(string)
if !playerOK || !messageOK || strings.TrimSpace(message) == "" || len(message) > 200 {
return fmt.Errorf("notification payload is invalid")
}
return nil
default:
return fmt.Errorf("command type is not declared")
}
}
func safeCommandField(key string, value any) bool {