initial commit

This commit is contained in:
m.zare
2026-04-10 18:25:21 +03:30
commit 77ca6c34a3
263 changed files with 34470 additions and 0 deletions

View File

@@ -0,0 +1,185 @@
package validation
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
)
// StructValidator validates a struct using individual validation functions
type StructValidator struct {
errors []error
}
// NewStructValidator creates a new struct validator
func NewStructValidator() *StructValidator {
return &StructValidator{
errors: make([]error, 0),
}
}
// Validate validates a struct and returns all validation errors
func (sv *StructValidator) Validate(data map[string]interface{}, structType interface{}) []error {
sv.errors = make([]error, 0)
// Get struct type information
val := reflect.ValueOf(structType)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
typ := val.Type()
// Build expected fields map
expectedFields := make(map[string]struct{})
requiredFields := make(map[string]struct{})
fieldValidations := make(map[string]map[string]string)
// Extract field information from struct tags
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
jsonTag := field.Tag.Get("json")
validateTag := field.Tag.Get("validate")
minTag := field.Tag.Get("min")
maxTag := field.Tag.Get("max")
if jsonTag != "" && jsonTag != "-" {
expectedFields[jsonTag] = struct{}{}
// Store validations for this field
fieldValidations[jsonTag] = make(map[string]string)
if validateTag != "" {
fieldValidations[jsonTag]["validate"] = validateTag
}
if minTag != "" {
fieldValidations[jsonTag]["min"] = minTag
}
if maxTag != "" {
fieldValidations[jsonTag]["max"] = maxTag
}
// Check if field is required
if strings.Contains(validateTag, "required") {
requiredFields[jsonTag] = struct{}{}
}
}
}
// Validate required fields exist
for field := range requiredFields {
if err := ExistKey(field, data, fmt.Sprintf("Field '%s' is required", field)); err != nil {
sv.errors = append(sv.errors, err)
}
}
// Validate each field in the data
for key, value := range data {
// Check for unexpected fields
if _, ok := expectedFields[key]; !ok {
err := ErrBadRequest.SetMessage(fmt.Sprintf("Unexpected field '%s'", key))
sv.errors = append(sv.errors, err)
continue
}
// Get field validations
validations, exists := fieldValidations[key]
if !exists {
continue
}
// Apply validations based on struct tags
sv.applyFieldValidations(key, value, data, validations)
}
return sv.errors
}
// applyFieldValidations applies all validations for a specific field
func (sv *StructValidator) applyFieldValidations(key string, value interface{}, data map[string]interface{}, validations map[string]string) {
// Check if field is required
if validateTag, ok := validations["validate"]; ok && strings.Contains(validateTag, "required") {
if err := NotBlank(key, data, fmt.Sprintf("Field '%s' cannot be blank", key)); err != nil {
sv.errors = append(sv.errors, err)
}
}
// Type validations
if value != nil {
switch value.(type) {
case string:
if err := IsString(key, data, fmt.Sprintf("Field '%s' must be a string", key)); err != nil {
sv.errors = append(sv.errors, err)
}
case float64:
// Check if it's an integer
if validateTag, ok := validations["validate"]; ok && strings.Contains(validateTag, "int") {
if err := IsInt(key, data, fmt.Sprintf("Field '%s' must be an integer", key)); err != nil {
sv.errors = append(sv.errors, err)
}
} else {
if err := IsFloat64(key, data, fmt.Sprintf("Field '%s' must be a number", key)); err != nil {
sv.errors = append(sv.errors, err)
}
}
case bool:
if err := IsBool(key, data, fmt.Sprintf("Field '%s' must be a boolean", key)); err != nil {
sv.errors = append(sv.errors, err)
}
case []interface{}:
// Slice validation - could be extended for specific slice types
if validateTag, ok := validations["validate"]; ok && strings.Contains(validateTag, "required") {
if err := NotBlank(key, data, fmt.Sprintf("Field '%s' cannot be empty", key)); err != nil {
sv.errors = append(sv.errors, err)
}
}
}
}
// Range validations
if minTag, ok := validations["min"]; ok {
if min, err := strconv.Atoi(minTag); err == nil {
if err := MinRange(key, min, data, fmt.Sprintf("Field '%s' must be at least %d", key, min)); err != nil {
sv.errors = append(sv.errors, err)
}
}
}
if maxTag, ok := validations["max"]; ok {
if max, err := strconv.Atoi(maxTag); err == nil {
if err := MaxRange(key, max, data, fmt.Sprintf("Field '%s' must be at most %d", key, max)); err != nil {
sv.errors = append(sv.errors, err)
}
}
}
}
// ValidateStruct is a convenience function that validates a struct directly
func ValidateStruct(data map[string]interface{}, structType interface{}) []error {
validator := NewStructValidator()
return validator.Validate(data, structType)
}
// ValidateJSON validates JSON data against a struct
func ValidateJSON(jsonData []byte, structType interface{}) []error {
var data map[string]interface{}
if err := json.Unmarshal(jsonData, &data); err != nil {
return []error{ErrBadRequest.SetMessage(fmt.Sprintf("Invalid JSON: %v", err))}
}
return ValidateStruct(data, structType)
}
// HasErrors returns true if there are validation errors
func (sv *StructValidator) HasErrors() bool {
return len(sv.errors) > 0
}
// GetErrors returns all validation errors
func (sv *StructValidator) GetErrors() []error {
return sv.errors
}
// AddError adds a custom error
func (sv *StructValidator) AddError(err error) {
sv.errors = append(sv.errors, err)
}