package validation import ( "fmt" "reflect" "regexp" "strings" "github.com/go-playground/validator/v10" ) var ( validate *validator.Validate // Common validation patterns passwordRegex = regexp.MustCompile(`^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};:'",.<>/?]{8,}$`) phoneRegex = regexp.MustCompile(`^\+?[1-9]\d{1,14}$`) ) func init() { validate = validator.New() // Register custom validation tags validate.RegisterValidation("password", validatePassword) validate.RegisterValidation("phone", validatePhone) // Register custom error messages validate.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return fld.Name } return name }) } // Validate performs validation on the given struct and returns formatted error messages func Validate(i interface{}) []ValidationError { err := validate.Struct(i) if err == nil { return nil } var validationErrors []ValidationError for _, err := range err.(validator.ValidationErrors) { validationErrors = append(validationErrors, ValidationError{ Field: err.Field(), Message: formatErrorMessage(err), }) } return validationErrors } // ValidationError represents a single validation error type ValidationError struct { Field string `json:"field"` Message string `json:"message"` } // Custom validators func validatePassword(fl validator.FieldLevel) bool { return passwordRegex.MatchString(fl.Field().String()) } func validatePhone(fl validator.FieldLevel) bool { if fl.Field().String() == "" { return true // Phone is optional } return phoneRegex.MatchString(fl.Field().String()) } // formatErrorMessage returns a user-friendly error message for validation errors func formatErrorMessage(err validator.FieldError) string { switch err.Tag() { case "required": return fmt.Sprintf("%s is required", err.Field()) case "email": return fmt.Sprintf("%s must be a valid email address", err.Field()) case "min": return fmt.Sprintf("%s must be at least %s characters long", err.Field(), err.Param()) case "max": return fmt.Sprintf("%s must not exceed %s characters", err.Field(), err.Param()) case "password": return fmt.Sprintf("%s must be at least 8 characters long and contain only letters, numbers, and special characters", err.Field()) case "phone": return fmt.Sprintf("%s must be a valid phone number in E.164 format", err.Field()) case "uuid": return fmt.Sprintf("%s must be a valid UUID", err.Field()) default: return fmt.Sprintf("%s failed validation: %s", err.Field(), err.Tag()) } }