Files
galaxy-game/lobby/internal/adapters/postgres/gamestore/codecs.go
T
2026-04-26 20:34:39 +02:00

95 lines
3.1 KiB
Go

package gamestore
import (
"encoding/json"
"fmt"
"time"
"galaxy/lobby/internal/domain/game"
)
// runtimeSnapshotJSON is the on-disk JSONB shape used for the denormalised
// runtime snapshot column on `games`. Keys mirror the field names in
// `game.RuntimeSnapshot` so a round-trip remains naked-comparable.
type runtimeSnapshotJSON struct {
CurrentTurn int `json:"current_turn"`
RuntimeStatus string `json:"runtime_status,omitempty"`
EngineHealthSummary string `json:"engine_health_summary,omitempty"`
}
func marshalRuntimeSnapshot(snapshot game.RuntimeSnapshot) ([]byte, error) {
payload := runtimeSnapshotJSON{
CurrentTurn: snapshot.CurrentTurn,
RuntimeStatus: snapshot.RuntimeStatus,
EngineHealthSummary: snapshot.EngineHealthSummary,
}
encoded, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("marshal runtime snapshot: %w", err)
}
return encoded, nil
}
func unmarshalRuntimeSnapshot(payload []byte) (game.RuntimeSnapshot, error) {
if len(payload) == 0 {
return game.RuntimeSnapshot{}, nil
}
var stored runtimeSnapshotJSON
if err := json.Unmarshal(payload, &stored); err != nil {
return game.RuntimeSnapshot{}, fmt.Errorf("unmarshal runtime snapshot: %w", err)
}
return game.RuntimeSnapshot{
CurrentTurn: stored.CurrentTurn,
RuntimeStatus: stored.RuntimeStatus,
EngineHealthSummary: stored.EngineHealthSummary,
}, nil
}
// runtimeBindingJSON is the on-disk JSONB shape used for the optional
// runtime binding column on `games`. The `bound_at_ms` field stores Unix
// milliseconds so the JSON serialisation matches the previous Redis JSON
// shape and the timezone is irrelevant inside the JSON payload itself; the
// adapter still re-wraps the resulting time.Time with .UTC() before exposing
// it to callers.
type runtimeBindingJSON struct {
ContainerID string `json:"container_id"`
EngineEndpoint string `json:"engine_endpoint"`
RuntimeJobID string `json:"runtime_job_id"`
BoundAtMS int64 `json:"bound_at_ms"`
}
// marshalRuntimeBinding returns nil bytes (SQL NULL) when binding is nil,
// otherwise the JSON encoding of the binding.
func marshalRuntimeBinding(binding *game.RuntimeBinding) ([]byte, error) {
if binding == nil {
return nil, nil
}
payload := runtimeBindingJSON{
ContainerID: binding.ContainerID,
EngineEndpoint: binding.EngineEndpoint,
RuntimeJobID: binding.RuntimeJobID,
BoundAtMS: binding.BoundAt.UTC().UnixMilli(),
}
encoded, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("marshal runtime binding: %w", err)
}
return encoded, nil
}
func unmarshalRuntimeBinding(payload []byte) (*game.RuntimeBinding, error) {
if len(payload) == 0 {
return nil, nil
}
var stored runtimeBindingJSON
if err := json.Unmarshal(payload, &stored); err != nil {
return nil, fmt.Errorf("unmarshal runtime binding: %w", err)
}
return &game.RuntimeBinding{
ContainerID: stored.ContainerID,
EngineEndpoint: stored.EngineEndpoint,
RuntimeJobID: stored.RuntimeJobID,
BoundAt: time.UnixMilli(stored.BoundAtMS).UTC(),
}, nil
}