package validator import ( "fmt" "net/mail" "strings" "unicode/utf8" ) // Errors collects validation errors for a request. type Errors struct { Fields map[string]string `json:"fields"` } // New creates a new Errors instance. func New() *Errors { return &Errors{Fields: make(map[string]string)} } // Add records a validation error for a field. func (e *Errors) Add(field, message string) { e.Fields[field] = message } // Valid returns true if there are no validation errors. func (e *Errors) Valid() bool { return len(e.Fields) == 0 } // Error implements the error interface. func (e *Errors) Error() string { parts := make([]string, 0, len(e.Fields)) for field, msg := range e.Fields { parts = append(parts, fmt.Sprintf("%s: %s", field, msg)) } return strings.Join(parts, "; ") } // --- Common validators --- // Required checks that a string is not empty. func Required(v *Errors, field, value string) { if strings.TrimSpace(value) == "" { v.Add(field, "is required") } } // MaxLength checks that a string doesn't exceed a maximum length. func MaxLength(v *Errors, field, value string, max int) { if utf8.RuneCountInString(value) > max { v.Add(field, fmt.Sprintf("must be at most %d characters", max)) } } // MinLength checks that a string meets a minimum length. func MinLength(v *Errors, field, value string, min int) { if utf8.RuneCountInString(value) < min { v.Add(field, fmt.Sprintf("must be at least %d characters", min)) } } // Email checks that a string is a valid email address. func Email(v *Errors, field, value string) { if _, err := mail.ParseAddress(value); err != nil { v.Add(field, "must be a valid email address") } } // In checks that a string is one of the allowed values. func In(v *Errors, field, value string, allowed ...string) { for _, a := range allowed { if value == a { return } } v.Add(field, fmt.Sprintf("must be one of: %s", strings.Join(allowed, ", "))) }