184 lines
5.2 KiB
Go
184 lines
5.2 KiB
Go
// Package validation provides centralized input validation
|
|
package validation
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// Validator provides validation functions
|
|
type Validator struct{}
|
|
|
|
// New creates a new Validator
|
|
func New() *Validator {
|
|
return &Validator{}
|
|
}
|
|
|
|
// ValidationError represents a validation error
|
|
type ValidationError struct {
|
|
Field string
|
|
Message string
|
|
}
|
|
|
|
// ValidationResult contains validation results
|
|
type ValidationResult struct {
|
|
Valid bool
|
|
Errors []ValidationError
|
|
}
|
|
|
|
// AddError adds an error to the result
|
|
func (r *ValidationResult) AddError(field, message string) {
|
|
r.Errors = append(r.Errors, ValidationError{Field: field, Message: message})
|
|
r.Valid = false
|
|
}
|
|
|
|
// NewResult creates a new valid result
|
|
func NewResult() *ValidationResult {
|
|
return &ValidationResult{Valid: true}
|
|
}
|
|
|
|
// Required validates that a string is not empty
|
|
func (v *Validator) Required(value, field string, result *ValidationResult) {
|
|
if strings.TrimSpace(value) == "" {
|
|
result.AddError(field, fmt.Sprintf("%s is required", field))
|
|
}
|
|
}
|
|
|
|
// MinLength validates minimum string length
|
|
func (v *Validator) MinLength(value, field string, min int, result *ValidationResult) {
|
|
if len(value) < min {
|
|
result.AddError(field, fmt.Sprintf("%s must be at least %d characters", field, min))
|
|
}
|
|
}
|
|
|
|
// MaxLength validates maximum string length
|
|
func (v *Validator) MaxLength(value, field string, max int, result *ValidationResult) {
|
|
if len(value) > max {
|
|
result.AddError(field, fmt.Sprintf("%s must be at most %d characters", field, max))
|
|
}
|
|
}
|
|
|
|
// Email validates email format
|
|
func (v *Validator) Email(value, field string, result *ValidationResult) {
|
|
if value == "" {
|
|
return // Skip if empty, use Required for mandatory
|
|
}
|
|
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
|
|
if !emailRegex.MatchString(value) {
|
|
result.AddError(field, fmt.Sprintf("%s must be a valid email address", field))
|
|
}
|
|
}
|
|
|
|
// DN validates LDAP Distinguished Name format
|
|
func (v *Validator) DN(value, field string, result *ValidationResult) {
|
|
if value == "" {
|
|
return
|
|
}
|
|
// Basic DN validation - must contain at least one RDN component
|
|
if !strings.Contains(value, "=") {
|
|
result.AddError(field, fmt.Sprintf("%s must be a valid Distinguished Name", field))
|
|
}
|
|
}
|
|
|
|
// CronExpression validates a cron expression (6 fields)
|
|
func (v *Validator) CronExpression(value, field string, result *ValidationResult) {
|
|
if value == "" {
|
|
return
|
|
}
|
|
parts := strings.Fields(value)
|
|
if len(parts) != 6 {
|
|
result.AddError(field, fmt.Sprintf("%s must have 6 fields (second minute hour day month weekday)", field))
|
|
}
|
|
}
|
|
|
|
// Regex validates that a value matches a regex pattern
|
|
func (v *Validator) Regex(value, pattern, field string, result *ValidationResult) {
|
|
if value == "" {
|
|
return
|
|
}
|
|
re, err := regexp.Compile(pattern)
|
|
if err != nil {
|
|
result.AddError(field, fmt.Sprintf("invalid validation pattern for %s", field))
|
|
return
|
|
}
|
|
if !re.MatchString(value) {
|
|
result.AddError(field, fmt.Sprintf("%s has invalid format", field))
|
|
}
|
|
}
|
|
|
|
// ValidRegex validates that a string is a valid regex pattern
|
|
func (v *Validator) ValidRegex(value, field string, result *ValidationResult) {
|
|
if value == "" {
|
|
return
|
|
}
|
|
_, err := regexp.Compile(value)
|
|
if err != nil {
|
|
result.AddError(field, fmt.Sprintf("%s must be a valid regular expression: %v", field, err))
|
|
}
|
|
}
|
|
|
|
// InList validates that a value is in a list of allowed values
|
|
func (v *Validator) InList(value, field string, allowed []string, result *ValidationResult) {
|
|
for _, a := range allowed {
|
|
if value == a {
|
|
return
|
|
}
|
|
}
|
|
result.AddError(field, fmt.Sprintf("%s must be one of: %s", field, strings.Join(allowed, ", ")))
|
|
}
|
|
|
|
// Port validates a port number
|
|
func (v *Validator) Port(value int, field string, result *ValidationResult) {
|
|
if value < 1 || value > 65535 {
|
|
result.AddError(field, fmt.Sprintf("%s must be between 1 and 65535", field))
|
|
}
|
|
}
|
|
|
|
// PositiveInt validates that an integer is positive
|
|
func (v *Validator) PositiveInt(value int, field string, result *ValidationResult) {
|
|
if value <= 0 {
|
|
result.AddError(field, fmt.Sprintf("%s must be a positive number", field))
|
|
}
|
|
}
|
|
|
|
// UUID validates UUID format
|
|
func (v *Validator) UUID(value, field string, result *ValidationResult) {
|
|
if value == "" {
|
|
return
|
|
}
|
|
uuidRegex := regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
|
|
if !uuidRegex.MatchString(value) {
|
|
result.AddError(field, fmt.Sprintf("%s must be a valid UUID", field))
|
|
}
|
|
}
|
|
|
|
// LDAPFilter validates basic LDAP filter syntax
|
|
func (v *Validator) LDAPFilter(value, field string, result *ValidationResult) {
|
|
if value == "" {
|
|
return
|
|
}
|
|
// Basic validation - must start and end with parentheses
|
|
value = strings.TrimSpace(value)
|
|
if !strings.HasPrefix(value, "(") || !strings.HasSuffix(value, ")") {
|
|
result.AddError(field, fmt.Sprintf("%s must be enclosed in parentheses", field))
|
|
return
|
|
}
|
|
// Check balanced parentheses
|
|
count := 0
|
|
for _, c := range value {
|
|
if c == '(' {
|
|
count++
|
|
} else if c == ')' {
|
|
count--
|
|
}
|
|
if count < 0 {
|
|
result.AddError(field, fmt.Sprintf("%s has unbalanced parentheses", field))
|
|
return
|
|
}
|
|
}
|
|
if count != 0 {
|
|
result.AddError(field, fmt.Sprintf("%s has unbalanced parentheses", field))
|
|
}
|
|
}
|