package domain import ( "fmt" "regexp" ) // PreferenceKey is a typed key for a known preference. type PreferenceKey string const ( KeyTheme PreferenceKey = "theme" KeyLanguage PreferenceKey = "language" KeyNotificationsEnabled PreferenceKey = "notifications_enabled" ) // PreferenceDefinition describes a known preference key with its default and validator. type PreferenceDefinition struct { Key PreferenceKey DefaultValue string Validate func(value string) error } // UserPreferences is the aggregate representing all preferences for a user. type UserPreferences struct { UserID string Preferences map[PreferenceKey]string } // uuidRegex validates UUID format. var uuidRegex = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`) // bcp47Regex validates BCP 47 language tags. var bcp47Regex = regexp.MustCompile(`^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{1,8})*$`) // validThemes is the set of allowed theme values. var validThemes = map[string]bool{ "light": true, "dark": true, "system": true, } // registry holds the definitions for all known preference keys. var registry = map[PreferenceKey]PreferenceDefinition{ KeyTheme: { Key: KeyTheme, DefaultValue: "system", Validate: func(value string) error { if !validThemes[value] { return fmt.Errorf("%w: theme must be one of: light, dark, system", ErrInvalidPreferenceValue) } return nil }, }, KeyLanguage: { Key: KeyLanguage, DefaultValue: "en", Validate: func(value string) error { if !bcp47Regex.MatchString(value) { return fmt.Errorf("%w: language must be a valid BCP 47 tag", ErrInvalidPreferenceValue) } return nil }, }, KeyNotificationsEnabled: { Key: KeyNotificationsEnabled, DefaultValue: "true", Validate: func(value string) error { if value != "true" && value != "false" { return fmt.Errorf("%w: notifications_enabled must be true or false", ErrInvalidPreferenceValue) } return nil }, }, } // ValidateUserID checks that userID is a valid UUID. func ValidateUserID(userID string) error { if !uuidRegex.MatchString(userID) { return ErrInvalidUserID } return nil } // ValidateKey checks that key is a known preference key. func ValidateKey(key string) error { if _, ok := registry[PreferenceKey(key)]; !ok { return fmt.Errorf("%w: %s", ErrUnknownPreferenceKey, key) } return nil } // ValidateValue validates a value for the given preference key. func ValidateValue(key PreferenceKey, value string) error { def, ok := registry[key] if !ok { return fmt.Errorf("%w: %s", ErrUnknownPreferenceKey, key) } return def.Validate(value) } // DefaultPreferences returns all known keys with their default values. func DefaultPreferences() map[PreferenceKey]string { defaults := make(map[PreferenceKey]string, len(registry)) for k, def := range registry { defaults[k] = def.DefaultValue } return defaults } // MergeWithDefaults fills in missing keys from defaults, preserving stored values. func MergeWithDefaults(stored map[PreferenceKey]string) map[PreferenceKey]string { merged := DefaultPreferences() for k, v := range stored { merged[k] = v } return merged } // SerializeForResponse converts stored string values to typed values for JSON. // notifications_enabled "true"/"false" becomes boolean true/false. func SerializeForResponse(prefs map[PreferenceKey]string) map[string]any { result := make(map[string]any, len(prefs)) for k, v := range prefs { switch k { case KeyNotificationsEnabled: result[string(k)] = v == "true" default: result[string(k)] = v } } return result }