package controllers import ( "fmt" "regexp" "strings" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" ) // ValidationHelper provides validation utilities type ValidationHelper struct { validator *validator.Validate } // ValidationError represents a validation error type ValidationError struct { Field string `json:"field"` Message string `json:"message"` Tag string `json:"tag"` } // NewValidationHelper creates a new ValidationHelper instance func NewValidationHelper() *ValidationHelper { v := validator.New() // Register custom validators _ = v.RegisterValidation("slug", validateSlug) _ = v.RegisterValidation("color", validateColor) return &ValidationHelper{ validator: v, } } // Validate validates a struct and returns user-friendly errors func (vh *ValidationHelper) Validate(data interface{}) []ValidationError { err := vh.validator.Struct(data) if err == nil { return nil } var validationErrors []ValidationError for _, err := range err.(validator.ValidationErrors) { validationErrors = append(validationErrors, ValidationError{ Field: strings.ToLower(err.Field()), Message: vh.getErrorMessage(err), Tag: err.Tag(), }) } return validationErrors } // ValidateAndRespond validates data and sends error response if invalid // Returns true if valid, false if invalid (and response already sent) func (vh *ValidationHelper) ValidateAndRespond(c *gin.Context, data interface{}) bool { errors := vh.Validate(data) if len(errors) > 0 { Respond.ValidationError(c, errors) return false } return true } // getErrorMessage returns a user-friendly error message for a validation error func (vh *ValidationHelper) getErrorMessage(err validator.FieldError) string { field := err.Field() switch err.Tag() { case "required": return fmt.Sprintf("%s is required", field) case "email": return fmt.Sprintf("%s must be a valid email address", field) case "min": return fmt.Sprintf("%s must be at least %s characters", field, err.Param()) case "max": return fmt.Sprintf("%s must be at most %s characters", field, err.Param()) case "len": return fmt.Sprintf("%s must be exactly %s characters", field, err.Param()) case "url": return fmt.Sprintf("%s must be a valid URL", field) case "slug": return fmt.Sprintf("%s must be a valid slug (lowercase, alphanumeric, hyphens)", field) case "color": return fmt.Sprintf("%s must be a valid hex color code", field) case "oneof": return fmt.Sprintf("%s must be one of: %s", field, err.Param()) case "gte": return fmt.Sprintf("%s must be greater than or equal to %s", field, err.Param()) case "lte": return fmt.Sprintf("%s must be less than or equal to %s", field, err.Param()) case "gt": return fmt.Sprintf("%s must be greater than %s", field, err.Param()) case "lt": return fmt.Sprintf("%s must be less than %s", field, err.Param()) default: return fmt.Sprintf("%s is invalid", field) } } // IsValidEmail checks if a string is a valid email func (vh *ValidationHelper) IsValidEmail(email string) bool { return vh.validator.Var(email, "required,email") == nil } // IsValidURL checks if a string is a valid URL func (vh *ValidationHelper) IsValidURL(url string) bool { return vh.validator.Var(url, "required,url") == nil } // IsValidSlug checks if a string is a valid slug func (vh *ValidationHelper) IsValidSlug(slug string) bool { return vh.validator.Var(slug, "required,slug") == nil } // SanitizeString removes leading/trailing whitespace and normalizes spaces func (vh *ValidationHelper) SanitizeString(s string) string { // Trim whitespace s = strings.TrimSpace(s) // Replace multiple spaces with single space s = regexp.MustCompile(`\s+`).ReplaceAllString(s, " ") return s } // SanitizeEmail normalizes an email address func (vh *ValidationHelper) SanitizeEmail(email string) string { return strings.ToLower(strings.TrimSpace(email)) } // SanitizeSlug normalizes a slug func (vh *ValidationHelper) SanitizeSlug(slug string) string { slug = strings.ToLower(strings.TrimSpace(slug)) // Replace spaces with hyphens slug = regexp.MustCompile(`\s+`).ReplaceAllString(slug, "-") // Remove non-alphanumeric characters except hyphens slug = regexp.MustCompile(`[^a-z0-9-]`).ReplaceAllString(slug, "") // Replace multiple hyphens with single hyphen slug = regexp.MustCompile(`-+`).ReplaceAllString(slug, "-") // Remove leading/trailing hyphens slug = strings.Trim(slug, "-") return slug } // Custom validator functions // validateSlug validates that a string is a valid slug func validateSlug(fl validator.FieldLevel) bool { slug := fl.Field().String() if slug == "" { return true // Let 'required' handle empty values } // Valid slug: lowercase alphanumeric and hyphens, no leading/trailing hyphens matched, _ := regexp.MatchString(`^[a-z0-9]+(-[a-z0-9]+)*$`, slug) return matched } // validateColor validates that a string is a valid hex color code func validateColor(fl validator.FieldLevel) bool { color := fl.Field().String() if color == "" { return true // Let 'required' handle empty values } // Valid color: #RGB, #RRGGBB, #RGBA, #RRGGBBAA matched, _ := regexp.MatchString(`^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{8})$`, color) return matched } // Global ValidationHelper instance for convenience var Validator = NewValidationHelper()