slate-test-1770505673/services/preferences-api/internal/domain/preference.go
rdev-worker 868f79c67a
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
build: /implement-feature user-preferences
2026-02-07 23:47:42 +00:00

67 lines
1.7 KiB
Go

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, ", "))
}