package redisstate import ( "encoding/json" "fmt" ) // registeredRecord stores the strict Redis JSON shape of one registered // race name. The canonical key is stored only as the Redis key suffix and // is not duplicated inside the blob. type registeredRecord struct { UserID string `json:"user_id"` RaceName string `json:"race_name"` SourceGameID string `json:"source_game_id"` RegisteredAtMS int64 `json:"registered_at_ms"` } // reservationStatusReserved marks a per-game race name reservation that // has not yet been promoted by capability evaluation. const reservationStatusReserved = "reserved" // reservationStatusPending marks a reservation that has been promoted to // pending_registration by the capability evaluator at game_finished. const reservationStatusPending = "pending_registration" // reservationRecord stores the strict Redis JSON shape of one per-game // race name reservation. The game_id and canonical key are carried by the // Redis key suffix; the blob never duplicates them. type reservationRecord struct { UserID string `json:"user_id"` RaceName string `json:"race_name"` ReservedAtMS int64 `json:"reserved_at_ms"` Status string `json:"status"` EligibleUntilMS *int64 `json:"eligible_until_ms,omitempty"` } // canonicalLookupRecord stores the eager canonical-lookup cache entry // used by Check to return availability without scanning the authoritative // keys. GameID is populated only for reservation and pending_registration // kinds; it is omitted for registered bindings. type canonicalLookupRecord struct { Kind string `json:"kind"` HolderUserID string `json:"holder_user_id"` GameID string `json:"game_id,omitempty"` } // marshalRegisteredRecord encodes record into the strict Redis JSON shape // used for registered race names. func marshalRegisteredRecord(record registeredRecord) ([]byte, error) { payload, err := json.Marshal(record) if err != nil { return nil, fmt.Errorf("marshal redis registered race name record: %w", err) } return payload, nil } // unmarshalRegisteredRecord decodes payload from the strict Redis JSON // shape used for registered race names. func unmarshalRegisteredRecord(payload []byte) (registeredRecord, error) { var record registeredRecord if err := decodeStrictJSON("decode redis registered race name record", payload, &record); err != nil { return registeredRecord{}, err } return record, nil } // marshalReservationRecord encodes record into the strict Redis JSON // shape used for per-game race name reservations. func marshalReservationRecord(record reservationRecord) ([]byte, error) { payload, err := json.Marshal(record) if err != nil { return nil, fmt.Errorf("marshal redis race name reservation record: %w", err) } return payload, nil } // unmarshalReservationRecord decodes payload from the strict Redis JSON // shape used for per-game race name reservations. func unmarshalReservationRecord(payload []byte) (reservationRecord, error) { var record reservationRecord if err := decodeStrictJSON("decode redis race name reservation record", payload, &record); err != nil { return reservationRecord{}, err } return record, nil } // marshalCanonicalLookupRecord encodes record into the strict Redis JSON // shape used for canonical-lookup cache entries. func marshalCanonicalLookupRecord(record canonicalLookupRecord) ([]byte, error) { payload, err := json.Marshal(record) if err != nil { return nil, fmt.Errorf("marshal redis race name canonical lookup record: %w", err) } return payload, nil } // unmarshalCanonicalLookupRecord decodes payload from the strict Redis // JSON shape used for canonical-lookup cache entries. func unmarshalCanonicalLookupRecord(payload []byte) (canonicalLookupRecord, error) { var record canonicalLookupRecord if err := decodeStrictJSON("decode redis race name canonical lookup record", payload, &record); err != nil { return canonicalLookupRecord{}, err } return record, nil }