2024-02-18 10:42:21 +00:00
|
|
|
// Copyright 2016 Qiang Xue. All rights reserved.
|
|
|
|
// Use of this source code is governed by a MIT-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package validation
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrStructPointer is the error that a struct being validated is not specified as a pointer.
|
|
|
|
ErrStructPointer = errors.New("only a pointer to a struct can be validated")
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
// ErrFieldPointer is the error that a field is not specified as a pointer.
|
|
|
|
ErrFieldPointer int
|
|
|
|
|
|
|
|
// ErrFieldNotFound is the error that a field cannot be found in the struct.
|
|
|
|
ErrFieldNotFound int
|
|
|
|
|
|
|
|
// FieldRules represents a rule set associated with a struct field.
|
|
|
|
FieldRules struct {
|
|
|
|
fieldPtr interface{}
|
|
|
|
rules []Rule
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// Error returns the error string of ErrFieldPointer.
|
|
|
|
func (e ErrFieldPointer) Error() string {
|
|
|
|
return fmt.Sprintf("field #%v must be specified as a pointer", int(e))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error returns the error string of ErrFieldNotFound.
|
|
|
|
func (e ErrFieldNotFound) Error() string {
|
|
|
|
return fmt.Sprintf("field #%v cannot be found in the struct", int(e))
|
|
|
|
}
|
|
|
|
|
|
|
|
// ValidateStruct validates a struct by checking the specified struct fields against the corresponding validation rules.
|
|
|
|
// Note that the struct being validated must be specified as a pointer to it. If the pointer is nil, it is considered valid.
|
|
|
|
// Use Field() to specify struct fields that need to be validated. Each Field() call specifies a single field which
|
|
|
|
// should be specified as a pointer to the field. A field can be associated with multiple rules.
|
|
|
|
// For example,
|
|
|
|
//
|
2024-06-14 08:41:36 +00:00
|
|
|
// value := struct {
|
|
|
|
// Name string
|
|
|
|
// Value string
|
|
|
|
// }{"name", "demo"}
|
|
|
|
// err := validation.ValidateStruct(&value,
|
|
|
|
// validation.Field(&a.Name, validation.Required),
|
|
|
|
// validation.Field(&a.Value, validation.Required, validation.Length(5, 10)),
|
|
|
|
// )
|
|
|
|
// fmt.Println(err)
|
|
|
|
// // Value: the length must be between 5 and 10.
|
2024-02-18 10:42:21 +00:00
|
|
|
//
|
|
|
|
// An error will be returned if validation fails.
|
|
|
|
func ValidateStruct(structPtr interface{}, fields ...*FieldRules) error {
|
|
|
|
return ValidateStructWithContext(nil, structPtr, fields...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ValidateStructWithContext validates a struct with the given context.
|
|
|
|
// The only difference between ValidateStructWithContext and ValidateStruct is that the former will
|
|
|
|
// validate struct fields with the provided context.
|
|
|
|
// Please refer to ValidateStruct for the detailed instructions on how to use this function.
|
|
|
|
func ValidateStructWithContext(ctx context.Context, structPtr interface{}, fields ...*FieldRules) error {
|
|
|
|
value := reflect.ValueOf(structPtr)
|
|
|
|
if value.Kind() != reflect.Ptr || !value.IsNil() && value.Elem().Kind() != reflect.Struct {
|
|
|
|
// must be a pointer to a struct
|
|
|
|
return NewInternalError(ErrStructPointer)
|
|
|
|
}
|
|
|
|
if value.IsNil() {
|
|
|
|
// treat a nil struct pointer as valid
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
value = value.Elem()
|
|
|
|
|
|
|
|
errs := Errors{}
|
|
|
|
|
|
|
|
for i, fr := range fields {
|
|
|
|
fv := reflect.ValueOf(fr.fieldPtr)
|
|
|
|
if fv.Kind() != reflect.Ptr {
|
|
|
|
return NewInternalError(ErrFieldPointer(i))
|
|
|
|
}
|
|
|
|
ft := findStructField(value, fv)
|
|
|
|
if ft == nil {
|
|
|
|
return NewInternalError(ErrFieldNotFound(i))
|
|
|
|
}
|
|
|
|
var err error
|
|
|
|
if ctx == nil {
|
|
|
|
err = Validate(fv.Elem().Interface(), fr.rules...)
|
|
|
|
} else {
|
|
|
|
err = ValidateWithContext(ctx, fv.Elem().Interface(), fr.rules...)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
if ie, ok := err.(InternalError); ok && ie.InternalError() != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if ft.Anonymous {
|
|
|
|
// merge errors from anonymous struct field
|
|
|
|
if es, ok := err.(Errors); ok {
|
|
|
|
for name, value := range es {
|
|
|
|
errs[name] = value
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
errs[getErrorFieldName(ft)] = err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
return errs
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Field specifies a struct field and the corresponding validation rules.
|
|
|
|
// The struct field must be specified as a pointer to it.
|
|
|
|
func Field(fieldPtr interface{}, rules ...Rule) *FieldRules {
|
|
|
|
return &FieldRules{
|
|
|
|
fieldPtr: fieldPtr,
|
|
|
|
rules: rules,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// findStructField looks for a field in the given struct.
|
|
|
|
// The field being looked for should be a pointer to the actual struct field.
|
|
|
|
// If found, the field info will be returned. Otherwise, nil will be returned.
|
|
|
|
func findStructField(structValue reflect.Value, fieldValue reflect.Value) *reflect.StructField {
|
|
|
|
ptr := fieldValue.Pointer()
|
|
|
|
for i := structValue.NumField() - 1; i >= 0; i-- {
|
|
|
|
sf := structValue.Type().Field(i)
|
|
|
|
if ptr == structValue.Field(i).UnsafeAddr() {
|
|
|
|
// do additional type comparison because it's possible that the address of
|
|
|
|
// an embedded struct is the same as the first field of the embedded struct
|
|
|
|
if sf.Type == fieldValue.Elem().Type() {
|
|
|
|
return &sf
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if sf.Anonymous {
|
|
|
|
// delve into anonymous struct to look for the field
|
|
|
|
fi := structValue.Field(i)
|
|
|
|
if sf.Type.Kind() == reflect.Ptr {
|
|
|
|
fi = fi.Elem()
|
|
|
|
}
|
|
|
|
if fi.Kind() == reflect.Struct {
|
|
|
|
if f := findStructField(fi, fieldValue); f != nil {
|
|
|
|
return f
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getErrorFieldName returns the name that should be used to represent the validation error of a struct field.
|
|
|
|
func getErrorFieldName(f *reflect.StructField) string {
|
|
|
|
if tag := f.Tag.Get(ErrorTag); tag != "" && tag != "-" {
|
|
|
|
if cps := strings.SplitN(tag, ",", 2); cps[0] != "" {
|
|
|
|
return cps[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return f.Name
|
|
|
|
}
|