Files
galaxy-game/user/internal/service/shared/normalize.go
T
2026-04-25 23:20:55 +02:00

148 lines
4.3 KiB
Go

package shared
import (
"fmt"
"strings"
"time"
"galaxy/user/internal/domain/common"
"galaxy/util"
"golang.org/x/text/language"
)
// NormalizeString trims surrounding Unicode whitespace from value.
func NormalizeString(value string) string {
return strings.TrimSpace(value)
}
// ParseEmail trims value and validates it as one exact normalized e-mail
// subject used by the auth-facing contract.
func ParseEmail(value string) (common.Email, error) {
email := common.Email(NormalizeString(value))
if err := email.Validate(); err != nil {
return "", InvalidRequest(err.Error())
}
return email, nil
}
// ParseUserID trims value and validates it as one stable user identifier.
func ParseUserID(value string) (common.UserID, error) {
userID := common.UserID(NormalizeString(value))
if err := userID.Validate(); err != nil {
return "", InvalidRequest(err.Error())
}
return userID, nil
}
// ParseUserName trims value and validates it as one exact stored user name.
func ParseUserName(value string) (common.UserName, error) {
userName := common.UserName(NormalizeString(value))
if err := userName.Validate(); err != nil {
return "", InvalidRequest(err.Error())
}
return userName, nil
}
// ParseDisplayName trims value and validates it as one self-service display
// name. An empty trimmed value is accepted and represents a reset to no
// display name.
func ParseDisplayName(value string) (common.DisplayName, error) {
trimmed := NormalizeString(value)
if trimmed == "" {
return "", nil
}
if _, ok := util.ValidateTypeName(trimmed); !ok {
return "", InvalidRequest(fmt.Sprintf("display_name %q is invalid", trimmed))
}
return common.DisplayName(trimmed), nil
}
// ParseReasonCode trims value and validates it as one machine-readable reason
// code.
func ParseReasonCode(value string) (common.ReasonCode, error) {
reasonCode := common.ReasonCode(NormalizeString(value))
if err := reasonCode.Validate(); err != nil {
return "", InvalidRequest(err.Error())
}
return reasonCode, nil
}
// ParseLanguageTag trims value and validates it against the current Stage 03
// boundary and BCP 47 semantics, returning the canonical tag form.
func ParseLanguageTag(value string) (common.LanguageTag, error) {
languageTag := common.LanguageTag(NormalizeString(value))
if err := languageTag.Validate(); err != nil {
return "", InvalidRequest(err.Error())
}
parsedTag, err := language.Parse(languageTag.String())
if err != nil {
return "", InvalidRequest("language tag must be a valid BCP 47 language tag")
}
canonicalTag := common.LanguageTag(parsedTag.String())
if err := canonicalTag.Validate(); err != nil {
return "", InvalidRequest(err.Error())
}
return canonicalTag, nil
}
// ParseTimeZoneName trims value and validates it against the current Stage 03
// boundary and IANA time-zone semantics.
func ParseTimeZoneName(value string) (common.TimeZoneName, error) {
timeZoneName := common.TimeZoneName(NormalizeString(value))
if err := timeZoneName.Validate(); err != nil {
return "", InvalidRequest(err.Error())
}
if _, err := time.LoadLocation(timeZoneName.String()); err != nil {
return "", InvalidRequest("time zone name must be a valid IANA time zone name")
}
return timeZoneName, nil
}
// ParseRegistrationPreferredLanguage trims value, validates it as one create-
// only BCP 47 registration language tag, and returns the canonical tag form.
func ParseRegistrationPreferredLanguage(value string) (common.LanguageTag, error) {
languageTag, err := ParseLanguageTag(value)
if err != nil {
return "", reframeFieldError("registration_context.preferred_language", "language tag", err)
}
return languageTag, nil
}
// ParseRegistrationTimeZoneName trims value and validates it as one create-
// only IANA registration time-zone name.
func ParseRegistrationTimeZoneName(value string) (common.TimeZoneName, error) {
timeZoneName, err := ParseTimeZoneName(value)
if err != nil {
return "", reframeFieldError("registration_context.time_zone", "time zone name", err)
}
return timeZoneName, nil
}
func reframeFieldError(fieldName string, valueName string, err error) error {
if err == nil {
return nil
}
message := err.Error()
prefix := valueName + " "
if strings.HasPrefix(message, prefix) {
message = fieldName + " " + strings.TrimPrefix(message, prefix)
} else {
message = fmt.Sprintf("%s: %s", fieldName, message)
}
return InvalidRequest(message)
}