validator.go 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. package validation
  2. import (
  3. "fmt"
  4. "reflect"
  5. "regexp"
  6. "strings"
  7. "github.com/go-playground/validator/v10"
  8. )
  9. var (
  10. validate *validator.Validate
  11. // Common validation patterns
  12. passwordRegex = regexp.MustCompile(`^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};:'",.<>/?]{8,}$`)
  13. phoneRegex = regexp.MustCompile(`^\+?[1-9]\d{1,14}$`)
  14. )
  15. func init() {
  16. validate = validator.New()
  17. // Register custom validation tags
  18. validate.RegisterValidation("password", validatePassword)
  19. validate.RegisterValidation("phone", validatePhone)
  20. // Register custom error messages
  21. validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
  22. name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
  23. if name == "-" {
  24. return fld.Name
  25. }
  26. return name
  27. })
  28. }
  29. // Validate performs validation on the given struct and returns formatted error messages
  30. func Validate(i interface{}) []ValidationError {
  31. err := validate.Struct(i)
  32. if err == nil {
  33. return nil
  34. }
  35. var validationErrors []ValidationError
  36. for _, err := range err.(validator.ValidationErrors) {
  37. validationErrors = append(validationErrors, ValidationError{
  38. Field: err.Field(),
  39. Message: formatErrorMessage(err),
  40. })
  41. }
  42. return validationErrors
  43. }
  44. // ValidationError represents a single validation error
  45. type ValidationError struct {
  46. Field string `json:"field"`
  47. Message string `json:"message"`
  48. }
  49. // Custom validators
  50. func validatePassword(fl validator.FieldLevel) bool {
  51. return passwordRegex.MatchString(fl.Field().String())
  52. }
  53. func validatePhone(fl validator.FieldLevel) bool {
  54. if fl.Field().String() == "" {
  55. return true // Phone is optional
  56. }
  57. return phoneRegex.MatchString(fl.Field().String())
  58. }
  59. // formatErrorMessage returns a user-friendly error message for validation errors
  60. func formatErrorMessage(err validator.FieldError) string {
  61. switch err.Tag() {
  62. case "required":
  63. return fmt.Sprintf("%s is required", err.Field())
  64. case "email":
  65. return fmt.Sprintf("%s must be a valid email address", err.Field())
  66. case "min":
  67. return fmt.Sprintf("%s must be at least %s characters long", err.Field(), err.Param())
  68. case "max":
  69. return fmt.Sprintf("%s must not exceed %s characters", err.Field(), err.Param())
  70. case "password":
  71. return fmt.Sprintf("%s must be at least 8 characters long and contain only letters, numbers, and special characters", err.Field())
  72. case "phone":
  73. return fmt.Sprintf("%s must be a valid phone number in E.164 format", err.Field())
  74. case "uuid":
  75. return fmt.Sprintf("%s must be a valid UUID", err.Field())
  76. default:
  77. return fmt.Sprintf("%s failed validation: %s", err.Field(), err.Tag())
  78. }
  79. }