package domain import ( "fmt" "regexp" "time" ) // Allowed preference keys. var allowedKeys = map[string]bool{ "theme": true, "language": true, "notifications_enabled": true, } // Valid theme values. var validThemes = map[string]bool{ "light": true, "dark": true, } // languagePattern matches ISO 639-1 codes (two lowercase letters). var languagePattern = regexp.MustCompile(`^[a-z]{2}$`) // UserPreferences represents a user's stored preferences. type UserPreferences struct { UserID string Preferences map[string]any CreatedAt time.Time UpdatedAt time.Time } // ValidatePreferences validates all keys and values in a preferences map. func ValidatePreferences(prefs map[string]any) error { for key, value := range prefs { if err := ValidatePreferenceKey(key); err != nil { return err } if err := ValidatePreferenceValue(key, value); err != nil { return err } } return nil } // ValidatePreferenceKey checks that the key is in the allowed set. func ValidatePreferenceKey(key string) error { if !allowedKeys[key] { return fmt.Errorf("%w: %s", ErrInvalidPreferenceKey, key) } return nil } // ValidatePreferenceValue checks that the value is valid for the given key. func ValidatePreferenceValue(key string, value any) error { switch key { case "theme": s, ok := value.(string) if !ok { return fmt.Errorf("%w: theme must be a string", ErrInvalidPreferenceValue) } if !validThemes[s] { return fmt.Errorf("%w: theme must be \"light\" or \"dark\", got %q", ErrInvalidPreferenceValue, s) } case "language": s, ok := value.(string) if !ok { return fmt.Errorf("%w: language must be a string", ErrInvalidPreferenceValue) } if !languagePattern.MatchString(s) { return fmt.Errorf("%w: language must be a valid ISO 639-1 code (e.g. \"en\"), got %q", ErrInvalidPreferenceValue, s) } case "notifications_enabled": if _, ok := value.(bool); !ok { return fmt.Errorf("%w: notifications_enabled must be a boolean", ErrInvalidPreferenceValue) } } return nil }