feat: game lobby service
This commit is contained in:
@@ -8,10 +8,12 @@ import (
|
||||
"net/mail"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"galaxy/util"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRaceNameLength = 64
|
||||
maxUserNameLength = 64
|
||||
maxLanguageTagLength = 32
|
||||
maxTimeZoneNameLength = 128
|
||||
)
|
||||
@@ -64,29 +66,64 @@ func (email Email) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RaceName stores one original-casing race name selected for the user
|
||||
// account.
|
||||
type RaceName string
|
||||
// UserName stores one immutable auto-generated platform handle in
|
||||
// `player-<suffix>` form. It is unique platform-wide and never changes after
|
||||
// account creation.
|
||||
type UserName string
|
||||
|
||||
// String returns RaceName as its stored value.
|
||||
func (name RaceName) String() string {
|
||||
// String returns UserName as its stored value.
|
||||
func (name UserName) String() string {
|
||||
return string(name)
|
||||
}
|
||||
|
||||
// IsZero reports whether RaceName does not contain a usable value.
|
||||
func (name RaceName) IsZero() bool {
|
||||
// IsZero reports whether UserName does not contain a usable value.
|
||||
func (name UserName) IsZero() bool {
|
||||
return strings.TrimSpace(string(name)) == ""
|
||||
}
|
||||
|
||||
// Validate reports whether RaceName is non-empty, trimmed, and within the
|
||||
// frozen OpenAPI length bound.
|
||||
func (name RaceName) Validate() error {
|
||||
// Validate reports whether UserName is non-empty, trimmed, uses the frozen
|
||||
// `player-` prefix, and stays within the reserved length bound.
|
||||
func (name UserName) Validate() error {
|
||||
raw := string(name)
|
||||
if err := validateToken("race name", raw); err != nil {
|
||||
if err := validatePrefixedToken("user name", raw, "player-"); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(raw) > maxRaceNameLength {
|
||||
return fmt.Errorf("race name must be at most %d bytes", maxRaceNameLength)
|
||||
if len(raw) > maxUserNameLength {
|
||||
return fmt.Errorf("user name must be at most %d bytes", maxUserNameLength)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisplayName stores one optional free-text user-facing label. It may be
|
||||
// empty and is not required to be unique; validation delegates to
|
||||
// galaxy/util.ValidateTypeName when a value is present.
|
||||
type DisplayName string
|
||||
|
||||
// String returns DisplayName as its stored value.
|
||||
func (name DisplayName) String() string {
|
||||
return string(name)
|
||||
}
|
||||
|
||||
// IsZero reports whether DisplayName is empty after trimming surrounding
|
||||
// whitespace.
|
||||
func (name DisplayName) IsZero() bool {
|
||||
return strings.TrimSpace(string(name)) == ""
|
||||
}
|
||||
|
||||
// Validate reports whether DisplayName is either empty or a valid
|
||||
// util.ValidateTypeName value. Trimming is the caller's responsibility;
|
||||
// Validate rejects values that still contain surrounding whitespace.
|
||||
func (name DisplayName) Validate() error {
|
||||
raw := string(name)
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(raw) != raw {
|
||||
return fmt.Errorf("display name must not contain surrounding whitespace")
|
||||
}
|
||||
if _, ok := util.ValidateTypeName(raw); !ok {
|
||||
return fmt.Errorf("display name %q is invalid", raw)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -65,17 +65,51 @@ func TestEmailValidate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRaceNameValidate(t *testing.T) {
|
||||
func TestUserNameValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value RaceName
|
||||
value UserName
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "valid", value: RaceName("Admiral Nova")},
|
||||
{name: "empty", value: RaceName(""), wantErr: true},
|
||||
{name: "too long", value: RaceName("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmn"), wantErr: true},
|
||||
{name: "valid", value: UserName("player-abcd1234")},
|
||||
{name: "empty", value: UserName(""), wantErr: true},
|
||||
{name: "wrong prefix", value: UserName("user-abcdefgh"), wantErr: true},
|
||||
{name: "prefix only", value: UserName("player-"), wantErr: true},
|
||||
{name: "surrounding whitespace", value: UserName(" player-abcd1234 "), wantErr: true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := tt.value.Validate()
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisplayNameValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value DisplayName
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "empty accepted", value: DisplayName("")},
|
||||
{name: "valid simple", value: DisplayName("PilotNova")},
|
||||
{name: "valid unicode", value: DisplayName("АдмиралНова")},
|
||||
{name: "internal whitespace", value: DisplayName("Pilot Nova"), wantErr: true},
|
||||
{name: "leading whitespace", value: DisplayName(" PilotNova"), wantErr: true},
|
||||
{name: "trailing whitespace", value: DisplayName("PilotNova "), wantErr: true},
|
||||
{name: "leading special", value: DisplayName("-Pilot"), wantErr: true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
Reference in New Issue
Block a user