package domain import ( "fmt" "regexp" "strings" ) // languageRegex validates ISO 639-1 language codes (two lowercase letters). var languageRegex = regexp.MustCompile(`^[a-z]{2}$`) // AllowedKeys defines the valid preference keys and their allowed values. // An empty slice means the value is validated by a custom rule (e.g., regex). var AllowedKeys = map[string][]string{ "theme": {"light", "dark", "system"}, "language": {}, // validated via languageRegex "notifications_enabled": {"true", "false"}, } // Preference represents a single user preference key-value pair. type Preference struct { UserID string Key string Value string } // Validate checks that Key is known and Value is valid for that key. func (p *Preference) Validate() error { if err := ValidateKey(p.Key); err != nil { return err } return ValidateValue(p.Key, p.Value) } // ValidateKey checks if a key is in the allowed set. func ValidateKey(key string) error { if _, ok := AllowedKeys[key]; !ok { return fmt.Errorf("%w: %s", ErrUnknownKey, key) } return nil } // ValidateValue checks if a value is valid for the given key. func ValidateValue(key, value string) error { allowed, ok := AllowedKeys[key] if !ok { return fmt.Errorf("%w: %s", ErrUnknownKey, key) } // Language uses regex validation if key == "language" { if !languageRegex.MatchString(value) { return fmt.Errorf("%w for key '%s': must be an ISO 639-1 code (e.g., en, fr)", ErrInvalidValue, key) } return nil } // Enum validation for keys with explicit allowed values for _, v := range allowed { if v == value { return nil } } return fmt.Errorf("%w '%s' for key '%s': allowed values are [%s]", ErrInvalidValue, value, key, strings.Join(allowed, ", ")) }