mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
172 lines
5.2 KiB
Go
172 lines
5.2 KiB
Go
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()
|