package validation import ( "strings" "sync" v10 "github.com/go-playground/validator/v10" "gorm.io/gorm" ) type ValidOptionSource interface { GetValidOptions(key string) ([]string, error) GetValidKeys() []string } // -------------------- // InMemoryOptionSource // -------------------- type InMemoryOptionSource struct{} var inMemoryOptions = map[string][]string{ "last_education": {"SD", "SMP", "SMA", "D1", "D2", "D3", "D4", "D5", "S1", "S2", "S3"}, "marital_status": {"Belum Menikah", "Duda", "Janda"}, "gender": {"Laki-laki", "Perempuan"}, "monthly_expenses": {"< 2 Juta", "2-5 Juta", "5-20 Juta", "> 10 Juta"}, "monthly_income": {"< 3 Juta", "3-5 Juta", "5-10 Juta", "> 10 Juta"}, "religion": {"Islam", "Non-Islam"}, "family_role": {"Ayah", "Ibu", "Kakak", "Adik", "Anak"}, "life_status": {"Hidup", "Wafat"}, "body_shape": {"Ideal", "Kurus", "Berisi", "Gemuk"}, "skin_color": {"Putih", "Kuning Langsat", "Sawo Matang"}, "hair_type": {"Lurus", "Bergelombang", "Keriting"}, "frequently": {"Selalu", "Sering", "Kadang", "Jarang"}, "quran_reading_ability": {"Lancar", "Menengah", "Perlu Bimbingan"}, } func (s *InMemoryOptionSource) GetValidOptions(key string) ([]string, error) { return inMemoryOptions[key], nil } func (s *InMemoryOptionSource) GetValidKeys() []string { keys := make([]string, 0, len(inMemoryOptions)) for k := range inMemoryOptions { keys = append(keys, k) } return keys } // -------------------- // DBOptionSource // -------------------- type DBOptionSource struct { options map[string][]string mu sync.RWMutex } type ( OptionCategory struct { ID int64 `gorm:"primaryKey" json:"id"` OptionName string `json:"option_name"` OptionSlug string `json:"option_slug" gorm:"uniqueIndex"` } OptionValues struct { ID int64 `gorm:"primaryKey" json:"id"` OptionCategoryID int64 `json:"option_category_id"` OptionValue string `json:"option_value"` } ) func NewDBOptionSource(db *gorm.DB) (*DBOptionSource, error) { var categories []OptionCategory if err := db.Find(&categories).Error; err != nil { return nil, err } options := make(map[string][]string) for _, cat := range categories { var values []OptionValues if err := db.Where("option_category_id = ?", cat.ID).Find(&values).Error; err != nil { return nil, err } for _, val := range values { options[cat.OptionSlug] = append(options[cat.OptionSlug], val.OptionValue) } } return &DBOptionSource{options: options}, nil } func (s *DBOptionSource) GetValidOptions(key string) ([]string, error) { s.mu.RLock() defer s.mu.RUnlock() return s.options[key], nil } func (s *DBOptionSource) GetValidKeys() []string { s.mu.RLock() defer s.mu.RUnlock() keys := make([]string, 0, len(s.options)) for k := range s.options { keys = append(keys, k) } return keys } // -------------------- // Validator // -------------------- type Validator struct { source ValidOptionSource } func NewValidatorRules(source ValidOptionSource) *Validator { return &Validator{source: source} } func (v *Validator) GenericOptionRule(key string) func(fl v10.FieldLevel) bool { return func(fl v10.FieldLevel) bool { value := fl.Field().String() if value == "" { return true } validOptions, err := v.source.GetValidOptions(key) if err != nil { return false } for _, opt := range validOptions { if opt == value { return true } } return false } } func (v *Validator) RegisterAllCustomRules(validate *v10.Validate) error { for _, key := range v.source.GetValidKeys() { err := validate.RegisterValidation(key, v.GenericOptionRule(key)) if err != nil { return err } } err := validate.RegisterValidation("password", v.PasswordRule) if err != nil { return err } return nil } func (v *Validator) PasswordRule(fl v10.FieldLevel) bool { password := fl.Field().String() return len(password) >= 8 && strings.ContainsAny(password, "abcdefghijklmnopqrstuvwxyz") && strings.ContainsAny(password, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") && strings.ContainsAny(password, "0123456789") }