initial commit
This commit is contained in:
185
pkg/validation/struct_validator.go
Normal file
185
pkg/validation/struct_validator.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user