1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
|
package client
import (
"errors"
"fmt"
"reflect"
"strings"
"github.com/ovn-kubernetes/libovsdb/mapper"
"github.com/go-playground/validator/v10"
"github.com/ovn-kubernetes/libovsdb/model"
)
// global validator instance
// Validator is designed to be thread-safe and used as a singleton instance. https://pkg.go.dev/github.com/go-playground/validator/v10#hdr-Singleton
var validate *validator.Validate
func init() {
validate = validator.New() // validator.WithRequiredStructEnabled())
// Register custom validations if needed in the future
// e.g., validate.RegisterValidation("custom_tag", customValidationFunc)
}
// formatValidationErrors formats validator.ValidationErrors into a detailed human-readable string
func formatValidationErrors(modelName string, context string, validationErrs validator.ValidationErrors) string {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("validation error for model %s", modelName))
// Append context if provided (e.g., "mutation on column X")
if context != "" {
sb.WriteString(fmt.Sprintf(": %s", context))
}
if len(validationErrs) > 0 {
sb.WriteString("; details: [")
var fieldErrorMessages []string
for _, fe := range validationErrs {
targetField := fe.Namespace() // e.g., "Model.Field" or "Model.Nested.Field"
// For validate.Var on simple type, Namespace might be empty.
if targetField == "" {
targetField = fe.Field() // Fallback to field name if any
}
if targetField == "" { // If still empty, use a generic term
targetField = "<value>"
}
errMsg := fmt.Sprintf("field '%s' (value: '%v') failed on rule '%s'", targetField, fe.Value(), fe.ActualTag())
if fe.Param() != "" {
errMsg += fmt.Sprintf(" (param: %s)", fe.Param())
}
fieldErrorMessages = append(fieldErrorMessages, errMsg)
}
sb.WriteString(strings.Join(fieldErrorMessages, ", "))
sb.WriteString("]")
}
return sb.String()
}
// validateModel performs validation on a given model struct using its tags.
func validateModel(m model.Model) error {
if m == nil {
return fmt.Errorf("model cannot be nil")
}
// Perform the validation
err := validate.Struct(m)
if err != nil {
modelType := reflect.TypeOf(m).Elem()
modelNameStr := modelType.String()
var validationErrs validator.ValidationErrors
if errors.As(err, &validationErrs) {
formattedErr := formatValidationErrors(modelNameStr, "", validationErrs)
return fmt.Errorf("model validation failed: %s: %w", formattedErr, validationErrs)
}
return fmt.Errorf("error while validating model of type %s: %w", modelNameStr, err)
}
return nil
}
// validateMutations performs validation on a given slice of mutations.
func validateMutations(model model.Model, info *mapper.Info, mutations ...model.Mutation) error {
modelType := reflect.TypeOf(model).Elem()
modelNameStr := modelType.String()
for _, mutation := range mutations {
columnName, err := info.ColumnByPtr(mutation.Field)
if err != nil {
return fmt.Errorf("could not get column for mutation field: %w", err)
}
// Find the struct field corresponding to the column name
var structField reflect.StructField
var found bool
for i := 0; i < modelType.NumField(); i++ {
if modelType.Field(i).Tag.Get("ovsdb") == columnName {
structField = modelType.Field(i)
found = true
break
}
}
if !found {
return fmt.Errorf("could not find struct field for column %s", columnName)
}
// Extract the validate tag
validateTag := structField.Tag.Get("validate")
// Validate the mutation value if a tag exists
if validateTag != "" {
err = validate.Var(mutation.Value, validateTag)
if err != nil {
var validationErrs validator.ValidationErrors
if errors.As(err, &validationErrs) {
context := fmt.Sprintf("mutation on column %s", columnName)
formattedErr := formatValidationErrors(modelNameStr, context, validationErrs)
return fmt.Errorf("mutation validation failed: %s: %w", formattedErr, validationErrs)
}
return fmt.Errorf("error while validating mutation for model of type %s on column %s: %w", modelNameStr, columnName, err)
}
}
}
return nil
}
|