parent
5f57d2222a
commit
e0e1af6a61
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,202 @@ |
|||||||
|
package validator |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
"reflect" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag" |
||||||
|
) |
||||||
|
|
||||||
|
// InvalidValidationError describes an invalid argument passed to
|
||||||
|
// `Struct`, `StructExcept`, StructPartial` or `Field`
|
||||||
|
type InvalidValidationError struct { |
||||||
|
Type reflect.Type |
||||||
|
} |
||||||
|
|
||||||
|
// Error returns InvalidValidationError message
|
||||||
|
func (e *InvalidValidationError) Error() string { |
||||||
|
|
||||||
|
if e.Type == nil { |
||||||
|
return "validator: (nil)" |
||||||
|
} |
||||||
|
|
||||||
|
return "validator: (nil " + e.Type.String() + ")" |
||||||
|
} |
||||||
|
|
||||||
|
// ValidationErrors is an array of FieldError's
|
||||||
|
// for use in custom error messages post validation.
|
||||||
|
type ValidationErrors []FieldError |
||||||
|
|
||||||
|
// Error is intended for use in development + debugging and not intended to be a production error message.
|
||||||
|
// It allows ValidationErrors to subscribe to the Error interface.
|
||||||
|
// All information to create an error message specific to your application is contained within
|
||||||
|
// the FieldError found within the ValidationErrors array
|
||||||
|
func (ve ValidationErrors) Error() string { |
||||||
|
|
||||||
|
buff := bytes.NewBufferString("") |
||||||
|
|
||||||
|
var err *fieldError |
||||||
|
|
||||||
|
for i := 0; i < len(ve); i++ { |
||||||
|
|
||||||
|
err = ve[i].(*fieldError) |
||||||
|
buff.WriteString(err.Error()) |
||||||
|
buff.WriteString("\n") |
||||||
|
} |
||||||
|
|
||||||
|
return strings.TrimSpace(buff.String()) |
||||||
|
} |
||||||
|
|
||||||
|
// FieldError contains all functions to get error details
|
||||||
|
type FieldError interface { |
||||||
|
|
||||||
|
// returns the validation tag that failed. if the
|
||||||
|
// validation was an alias, this will return the
|
||||||
|
// alias name and not the underlying tag that failed.
|
||||||
|
//
|
||||||
|
// eg. alias "iscolor": "hexcolor|rgb|rgba|hsl|hsla"
|
||||||
|
// will return "iscolor"
|
||||||
|
Tag() string |
||||||
|
|
||||||
|
// returns the validation tag that failed, even if an
|
||||||
|
// alias the actual tag within the alias will be returned.
|
||||||
|
// If an 'or' validation fails the entire or will be returned.
|
||||||
|
//
|
||||||
|
// eg. alias "iscolor": "hexcolor|rgb|rgba|hsl|hsla"
|
||||||
|
// will return "hexcolor|rgb|rgba|hsl|hsla"
|
||||||
|
ActualTag() string |
||||||
|
|
||||||
|
// returns the namespace for the field error, with the tag
|
||||||
|
// name taking precedence over the fields actual name.
|
||||||
|
//
|
||||||
|
// eq. JSON name "User.fname" see ActualNamespace for comparison
|
||||||
|
//
|
||||||
|
// NOTE: this field can be blank when validating a single primitive field
|
||||||
|
// using validate.Field(...) as there is no way to extract it's name
|
||||||
|
Namespace() string |
||||||
|
|
||||||
|
// returns the namespace for the field error, with the fields
|
||||||
|
// actual name.
|
||||||
|
//
|
||||||
|
// eq. "User.FirstName" see Namespace for comparison
|
||||||
|
//
|
||||||
|
// NOTE: this field can be blank when validating a single primitive field
|
||||||
|
// using validate.Field(...) as there is no way to extract it's name
|
||||||
|
ActualNamespace() string |
||||||
|
|
||||||
|
// returns the fields name with the tag name taking precedence over the
|
||||||
|
// fields actual name.
|
||||||
|
//
|
||||||
|
// eq. JSON name "fname"
|
||||||
|
// see ActualField for comparison
|
||||||
|
Field() string |
||||||
|
|
||||||
|
// returns the fields actual name.
|
||||||
|
//
|
||||||
|
// eq. "FirstName"
|
||||||
|
// see Field for comparison
|
||||||
|
ActualField() string |
||||||
|
|
||||||
|
// returns the actual fields value in case needed for creating the error
|
||||||
|
// message
|
||||||
|
Value() interface{} |
||||||
|
|
||||||
|
// returns the param value, already converted into the fields type for
|
||||||
|
// comparison; this will also help with generating an error message
|
||||||
|
Param() interface{} |
||||||
|
|
||||||
|
// Kind returns the Field's reflect Kind
|
||||||
|
//
|
||||||
|
// eg. time.Time's kind is a struct
|
||||||
|
Kind() reflect.Kind |
||||||
|
|
||||||
|
// Type returns the Field's reflect Type
|
||||||
|
//
|
||||||
|
// // eg. time.Time's type is time.Time
|
||||||
|
Type() reflect.Type |
||||||
|
} |
||||||
|
|
||||||
|
// compile time interface checks
|
||||||
|
var _ FieldError = new(fieldError) |
||||||
|
var _ error = new(fieldError) |
||||||
|
|
||||||
|
// fieldError contains a single field's validation error along
|
||||||
|
// with other properties that may be needed for error message creation
|
||||||
|
// it complies with the FieldError interface
|
||||||
|
type fieldError struct { |
||||||
|
tag string |
||||||
|
actualTag string |
||||||
|
ns string |
||||||
|
actualNs string |
||||||
|
field string |
||||||
|
actualField string |
||||||
|
value interface{} |
||||||
|
param interface{} |
||||||
|
kind reflect.Kind |
||||||
|
typ reflect.Type |
||||||
|
} |
||||||
|
|
||||||
|
// Tag returns the validation tag that failed.
|
||||||
|
func (fe *fieldError) Tag() string { |
||||||
|
return fe.tag |
||||||
|
} |
||||||
|
|
||||||
|
// ActualTag returns the validation tag that failed, even if an
|
||||||
|
// alias the actual tag within the alias will be returned.
|
||||||
|
func (fe *fieldError) ActualTag() string { |
||||||
|
return fe.actualTag |
||||||
|
} |
||||||
|
|
||||||
|
// Namespace returns the namespace for the field error, with the tag
|
||||||
|
// name taking precedence over the fields actual name.
|
||||||
|
func (fe *fieldError) Namespace() string { |
||||||
|
return fe.ns |
||||||
|
} |
||||||
|
|
||||||
|
// ActualNamespace returns the namespace for the field error, with the fields
|
||||||
|
// actual name.
|
||||||
|
func (fe *fieldError) ActualNamespace() string { |
||||||
|
return fe.actualNs |
||||||
|
} |
||||||
|
|
||||||
|
// Field returns the fields name with the tag name taking precedence over the
|
||||||
|
// fields actual name.
|
||||||
|
func (fe *fieldError) Field() string { |
||||||
|
return fe.field |
||||||
|
} |
||||||
|
|
||||||
|
// ActualField returns the fields actual name.
|
||||||
|
func (fe *fieldError) ActualField() string { |
||||||
|
return fe.actualField |
||||||
|
} |
||||||
|
|
||||||
|
// Value returns the actual fields value in case needed for creating the error
|
||||||
|
// message
|
||||||
|
func (fe *fieldError) Value() interface{} { |
||||||
|
return fe.value |
||||||
|
} |
||||||
|
|
||||||
|
// Param returns the param value, already converted into the fields type for
|
||||||
|
// comparison; this will also help with generating an error message
|
||||||
|
func (fe *fieldError) Param() interface{} { |
||||||
|
return fe.param |
||||||
|
} |
||||||
|
|
||||||
|
// Kind returns the Field's reflect Kind
|
||||||
|
func (fe *fieldError) Kind() reflect.Kind { |
||||||
|
return fe.kind |
||||||
|
} |
||||||
|
|
||||||
|
// Type returns the Field's reflect Type
|
||||||
|
func (fe *fieldError) Type() reflect.Type { |
||||||
|
return fe.typ |
||||||
|
} |
||||||
|
|
||||||
|
// Error returns the fieldError's error message
|
||||||
|
func (fe *fieldError) Error() string { |
||||||
|
return fmt.Sprintf(fieldErrMsg, fe.ns, fe.field, fe.tag) |
||||||
|
} |
@ -1,83 +1,83 @@ |
|||||||
package validator_test |
package validator_test |
||||||
|
|
||||||
import ( |
// import (
|
||||||
"fmt" |
// "fmt"
|
||||||
|
|
||||||
"gopkg.in/go-playground/validator.v8" |
// "gopkg.in/go-playground/validator.v8"
|
||||||
) |
// )
|
||||||
|
|
||||||
func ExampleValidate_new() { |
// func ExampleValidate_new() {
|
||||||
config := &validator.Config{TagName: "validate"} |
// config := &validator.Config{TagName: "validate"}
|
||||||
|
|
||||||
validator.New(config) |
// validator.New(config)
|
||||||
} |
// }
|
||||||
|
|
||||||
func ExampleValidate_field() { |
// func ExampleValidate_field() {
|
||||||
// This should be stored somewhere globally
|
// // This should be stored somewhere globally
|
||||||
var validate *validator.Validate |
// var validate *validator.Validate
|
||||||
|
|
||||||
config := &validator.Config{TagName: "validate"} |
// config := &validator.Config{TagName: "validate"}
|
||||||
|
|
||||||
validate = validator.New(config) |
// validate = validator.New(config)
|
||||||
|
|
||||||
i := 0 |
// i := 0
|
||||||
errs := validate.Field(i, "gt=1,lte=10") |
// errs := validate.Field(i, "gt=1,lte=10")
|
||||||
err := errs.(validator.ValidationErrors)[""] |
// err := errs.(validator.ValidationErrors)[""]
|
||||||
fmt.Println(err.Field) |
// fmt.Println(err.Field)
|
||||||
fmt.Println(err.Tag) |
// fmt.Println(err.Tag)
|
||||||
fmt.Println(err.Kind) // NOTE: Kind and Type can be different i.e. time Kind=struct and Type=time.Time
|
// fmt.Println(err.Kind) // NOTE: Kind and Type can be different i.e. time Kind=struct and Type=time.Time
|
||||||
fmt.Println(err.Type) |
// fmt.Println(err.Type)
|
||||||
fmt.Println(err.Param) |
// fmt.Println(err.Param)
|
||||||
fmt.Println(err.Value) |
// fmt.Println(err.Value)
|
||||||
//Output:
|
// //Output:
|
||||||
//
|
// //
|
||||||
//gt
|
// //gt
|
||||||
//int
|
// //int
|
||||||
//int
|
// //int
|
||||||
//1
|
// //1
|
||||||
//0
|
// //0
|
||||||
} |
// }
|
||||||
|
|
||||||
func ExampleValidate_struct() { |
// func ExampleValidate_struct() {
|
||||||
// This should be stored somewhere globally
|
// // This should be stored somewhere globally
|
||||||
var validate *validator.Validate |
// var validate *validator.Validate
|
||||||
|
|
||||||
config := &validator.Config{TagName: "validate"} |
// config := &validator.Config{TagName: "validate"}
|
||||||
|
|
||||||
validate = validator.New(config) |
// validate = validator.New(config)
|
||||||
|
|
||||||
type ContactInformation struct { |
// type ContactInformation struct {
|
||||||
Phone string `validate:"required"` |
// Phone string `validate:"required"`
|
||||||
Street string `validate:"required"` |
// Street string `validate:"required"`
|
||||||
City string `validate:"required"` |
// City string `validate:"required"`
|
||||||
} |
// }
|
||||||
|
|
||||||
type User struct { |
// type User struct {
|
||||||
Name string `validate:"required,excludesall=!@#$%^&*()_+-=:;?/0x2C"` // 0x2C = comma (,)
|
// Name string `validate:"required,excludesall=!@#$%^&*()_+-=:;?/0x2C"` // 0x2C = comma (,)
|
||||||
Age int8 `validate:"required,gt=0,lt=150"` |
// Age int8 `validate:"required,gt=0,lt=150"`
|
||||||
Email string `validate:"email"` |
// Email string `validate:"email"`
|
||||||
ContactInformation []*ContactInformation |
// ContactInformation []*ContactInformation
|
||||||
} |
// }
|
||||||
|
|
||||||
contactInfo := &ContactInformation{ |
// contactInfo := &ContactInformation{
|
||||||
Street: "26 Here Blvd.", |
// Street: "26 Here Blvd.",
|
||||||
City: "Paradeso", |
// City: "Paradeso",
|
||||||
} |
// }
|
||||||
|
|
||||||
user := &User{ |
// user := &User{
|
||||||
Name: "Joey Bloggs", |
// Name: "Joey Bloggs",
|
||||||
Age: 31, |
// Age: 31,
|
||||||
Email: "joeybloggs@gmail.com", |
// Email: "joeybloggs@gmail.com",
|
||||||
ContactInformation: []*ContactInformation{contactInfo}, |
// ContactInformation: []*ContactInformation{contactInfo},
|
||||||
} |
// }
|
||||||
|
|
||||||
errs := validate.Struct(user) |
// errs := validate.Struct(user)
|
||||||
for _, v := range errs.(validator.ValidationErrors) { |
// for _, v := range errs.(validator.ValidationErrors) {
|
||||||
fmt.Println(v.Field) // Phone
|
// fmt.Println(v.Field) // Phone
|
||||||
fmt.Println(v.Tag) // required
|
// fmt.Println(v.Tag) // required
|
||||||
//... and so forth
|
// //... and so forth
|
||||||
//Output:
|
// //Output:
|
||||||
//Phone
|
// //Phone
|
||||||
//required
|
// //required
|
||||||
} |
// }
|
||||||
} |
// }
|
||||||
|
@ -0,0 +1,50 @@ |
|||||||
|
package validator |
||||||
|
|
||||||
|
import "reflect" |
||||||
|
|
||||||
|
// FieldLevel contains all the information and helper functions
|
||||||
|
// to validate a field
|
||||||
|
type FieldLevel interface { |
||||||
|
|
||||||
|
// returns the top level struct, if any
|
||||||
|
Top() reflect.Value |
||||||
|
|
||||||
|
// returns the current fields parent struct, if any
|
||||||
|
Parent() reflect.Value |
||||||
|
|
||||||
|
// returns current field for validation
|
||||||
|
Field() reflect.Value |
||||||
|
|
||||||
|
// returns param for validation against current field
|
||||||
|
Param() string |
||||||
|
|
||||||
|
// ExtractType gets the actual underlying type of field value.
|
||||||
|
// It will dive into pointers, customTypes and return you the
|
||||||
|
// underlying value and it's kind.
|
||||||
|
ExtractType(field reflect.Value) (value reflect.Value, kind reflect.Kind, nullable bool) |
||||||
|
|
||||||
|
// traverses the parent struct to retrieve a specific field denoted by the provided namespace
|
||||||
|
// in the param and returns the field, field kind and whether is was successful in retrieving
|
||||||
|
// the field at all.
|
||||||
|
//
|
||||||
|
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
|
||||||
|
// could not be retrieved because it didn't exist.
|
||||||
|
GetStructFieldOK() (reflect.Value, reflect.Kind, bool) |
||||||
|
} |
||||||
|
|
||||||
|
var _ FieldLevel = new(validate) |
||||||
|
|
||||||
|
// Field returns current field for validation
|
||||||
|
func (v *validate) Field() reflect.Value { |
||||||
|
return v.flField |
||||||
|
} |
||||||
|
|
||||||
|
// Param returns param for validation against current field
|
||||||
|
func (v *validate) Param() string { |
||||||
|
return v.flParam |
||||||
|
} |
||||||
|
|
||||||
|
// GetStructFieldOK returns Param returns param for validation against current field
|
||||||
|
func (v *validate) GetStructFieldOK() (reflect.Value, reflect.Kind, bool) { |
||||||
|
return v.getStructFieldOKInternal(v.slflParent, v.flParam) |
||||||
|
} |
@ -0,0 +1,166 @@ |
|||||||
|
package validator |
||||||
|
|
||||||
|
import "reflect" |
||||||
|
|
||||||
|
// StructLevelFunc accepts all values needed for struct level validation
|
||||||
|
type StructLevelFunc func(sl StructLevel) |
||||||
|
|
||||||
|
// StructLevel contains all the information and helper functions
|
||||||
|
// to validate a struct
|
||||||
|
type StructLevel interface { |
||||||
|
|
||||||
|
// returns the main validation object, in case one want to call validations internally.
|
||||||
|
Validator() *Validate |
||||||
|
|
||||||
|
// returns the top level struct, if any
|
||||||
|
Top() reflect.Value |
||||||
|
|
||||||
|
// returns the current fields parent struct, if any
|
||||||
|
Parent() reflect.Value |
||||||
|
|
||||||
|
// returns the current struct.
|
||||||
|
// this is not needed when implementing 'Validatable' interface,
|
||||||
|
// only when a StructLevel is registered
|
||||||
|
Current() reflect.Value |
||||||
|
|
||||||
|
// ExtractType gets the actual underlying type of field value.
|
||||||
|
// It will dive into pointers, customTypes and return you the
|
||||||
|
// underlying value and it's kind.
|
||||||
|
ExtractType(field reflect.Value) (value reflect.Value, kind reflect.Kind, nullable bool) |
||||||
|
|
||||||
|
// reports an error just by passing the field and tag information
|
||||||
|
//
|
||||||
|
// NOTES:
|
||||||
|
//
|
||||||
|
// fieldName and altName get appended to the existing namespace that
|
||||||
|
// validator is on. eg. pass 'FirstName' or 'Names[0]' depending
|
||||||
|
// on the nesting
|
||||||
|
//
|
||||||
|
// tag can be an existing validation tag or just something you make up
|
||||||
|
// and process on the flip side it's up to you.
|
||||||
|
ReportError(field interface{}, fieldName, altName, tag string) |
||||||
|
|
||||||
|
// reports an error just by passing ValidationErrors
|
||||||
|
//
|
||||||
|
// NOTES:
|
||||||
|
//
|
||||||
|
// relativeNamespace and relativeActualNamespace get appended to the
|
||||||
|
// existing namespace that validator is on.
|
||||||
|
// eg. pass 'User.FirstName' or 'Users[0].FirstName' depending
|
||||||
|
// on the nesting. most of the time they will be blank, unless you validate
|
||||||
|
// at a level lower the the current field depth
|
||||||
|
//
|
||||||
|
// tag can be an existing validation tag or just something you make up
|
||||||
|
// and process on the flip side it's up to you.
|
||||||
|
ReportValidationErrors(relativeNamespace, relativeActualNamespace string, errs ValidationErrors) |
||||||
|
} |
||||||
|
|
||||||
|
var _ StructLevel = new(validate) |
||||||
|
|
||||||
|
// Top returns the top level struct
|
||||||
|
//
|
||||||
|
// NOTE: this can be the same as the current struct being validated
|
||||||
|
// if not is a nested struct.
|
||||||
|
//
|
||||||
|
// this is only called when within Struct and Field Level validation and
|
||||||
|
// should not be relied upon for an acurate value otherwise.
|
||||||
|
func (v *validate) Top() reflect.Value { |
||||||
|
return v.top |
||||||
|
} |
||||||
|
|
||||||
|
// Parent returns the current structs parent
|
||||||
|
//
|
||||||
|
// NOTE: this can be the same as the current struct being validated
|
||||||
|
// if not is a nested struct.
|
||||||
|
//
|
||||||
|
// this is only called when within Struct and Field Level validation and
|
||||||
|
// should not be relied upon for an acurate value otherwise.
|
||||||
|
func (v *validate) Parent() reflect.Value { |
||||||
|
return v.slflParent |
||||||
|
} |
||||||
|
|
||||||
|
// Current returns the current struct.
|
||||||
|
func (v *validate) Current() reflect.Value { |
||||||
|
return v.slCurrent |
||||||
|
} |
||||||
|
|
||||||
|
// Validator returns the main validation object, in case one want to call validations internally.
|
||||||
|
func (v *validate) Validator() *Validate { |
||||||
|
return v.v |
||||||
|
} |
||||||
|
|
||||||
|
// ExtractType gets the actual underlying type of field value.
|
||||||
|
func (v *validate) ExtractType(field reflect.Value) (reflect.Value, reflect.Kind, bool) { |
||||||
|
return v.extractTypeInternal(field, false) |
||||||
|
} |
||||||
|
|
||||||
|
// ReportError reports an error just by passing the field and tag information
|
||||||
|
func (v *validate) ReportError(field interface{}, fieldName, altName, tag string) { |
||||||
|
|
||||||
|
fv, kind, _ := v.extractTypeInternal(reflect.ValueOf(field), false) |
||||||
|
|
||||||
|
if len(altName) == 0 { |
||||||
|
altName = fieldName |
||||||
|
} |
||||||
|
|
||||||
|
ns := append(v.slNs, fieldName...) |
||||||
|
nsActual := append(v.slActualNs, altName...) |
||||||
|
|
||||||
|
switch kind { |
||||||
|
case reflect.Invalid: |
||||||
|
|
||||||
|
v.errs = append(v.errs, |
||||||
|
&fieldError{ |
||||||
|
tag: tag, |
||||||
|
actualTag: tag, |
||||||
|
ns: string(ns), |
||||||
|
actualNs: string(nsActual), |
||||||
|
field: fieldName, |
||||||
|
actualField: altName, |
||||||
|
param: "", |
||||||
|
kind: kind, |
||||||
|
}, |
||||||
|
) |
||||||
|
|
||||||
|
default: |
||||||
|
|
||||||
|
v.errs = append(v.errs, |
||||||
|
&fieldError{ |
||||||
|
tag: tag, |
||||||
|
actualTag: tag, |
||||||
|
ns: string(ns), |
||||||
|
actualNs: string(nsActual), |
||||||
|
field: fieldName, |
||||||
|
actualField: altName, |
||||||
|
value: fv.Interface(), |
||||||
|
param: "", |
||||||
|
kind: kind, |
||||||
|
typ: fv.Type(), |
||||||
|
}, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ReportValidationErrors reports ValidationErrors obtained from running validations within the Struct Level validation.
|
||||||
|
//
|
||||||
|
// NOTE: this function prepends the current namespace to the relative ones.
|
||||||
|
func (v *validate) ReportValidationErrors(relativeNamespace, relativeActualNamespace string, errs ValidationErrors) { |
||||||
|
|
||||||
|
var err *fieldError |
||||||
|
|
||||||
|
for i := 0; i < len(errs); i++ { |
||||||
|
|
||||||
|
err = errs[i].(*fieldError) |
||||||
|
err.ns = string(append(append(v.slNs, err.ns...), err.field...)) |
||||||
|
err.actualNs = string(append(append(v.slActualNs, err.actualNs...), err.actualField...)) |
||||||
|
|
||||||
|
v.errs = append(v.errs, err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Validatable is the interface a struct can implement and
|
||||||
|
// be validated just like registering a StructLevel validation
|
||||||
|
// (they actually have the exact same signature.)
|
||||||
|
type Validatable interface { |
||||||
|
Validate(sl StructLevel) |
||||||
|
} |
@ -0,0 +1,442 @@ |
|||||||
|
package validator |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"reflect" |
||||||
|
"strings" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
defaultTagName = "validate" |
||||||
|
utf8HexComma = "0x2C" |
||||||
|
utf8Pipe = "0x7C" |
||||||
|
tagSeparator = "," |
||||||
|
orSeparator = "|" |
||||||
|
tagKeySeparator = "=" |
||||||
|
structOnlyTag = "structonly" |
||||||
|
noStructLevelTag = "nostructlevel" |
||||||
|
omitempty = "omitempty" |
||||||
|
skipValidationTag = "-" |
||||||
|
diveTag = "dive" |
||||||
|
namespaceSeparator = "." |
||||||
|
leftBracket = "[" |
||||||
|
rightBracket = "]" |
||||||
|
restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}" |
||||||
|
restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation" |
||||||
|
restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
timeType = reflect.TypeOf(time.Time{}) |
||||||
|
defaultCField = new(cField) |
||||||
|
) |
||||||
|
|
||||||
|
// CustomTypeFunc allows for overriding or adding custom field type handler functions
|
||||||
|
// field = field value of the type to return a value to be validated
|
||||||
|
// example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29
|
||||||
|
type CustomTypeFunc func(field reflect.Value) interface{} |
||||||
|
|
||||||
|
// TagNameFunc allows for adding of a custom tag name parser
|
||||||
|
type TagNameFunc func(field reflect.StructField) string |
||||||
|
|
||||||
|
// Validate contains the validator settings and cache
|
||||||
|
type Validate struct { |
||||||
|
tagName string |
||||||
|
pool *sync.Pool |
||||||
|
hasCustomFuncs bool |
||||||
|
hasTagNameFunc bool |
||||||
|
tagNameFunc TagNameFunc |
||||||
|
structLevelFuncs map[reflect.Type]StructLevelFunc |
||||||
|
customFuncs map[reflect.Type]CustomTypeFunc |
||||||
|
aliases map[string]string |
||||||
|
validations map[string]Func |
||||||
|
tagCache *tagCache |
||||||
|
structCache *structCache |
||||||
|
} |
||||||
|
|
||||||
|
// New returns a new instacne of 'validate' with sane defaults.
|
||||||
|
func New() *Validate { |
||||||
|
|
||||||
|
tc := new(tagCache) |
||||||
|
tc.m.Store(make(map[string]*cTag)) |
||||||
|
|
||||||
|
sc := new(structCache) |
||||||
|
sc.m.Store(make(map[reflect.Type]*cStruct)) |
||||||
|
|
||||||
|
v := &Validate{ |
||||||
|
tagName: defaultTagName, |
||||||
|
aliases: make(map[string]string, len(bakedInAliases)), |
||||||
|
validations: make(map[string]Func, len(bakedInValidators)), |
||||||
|
tagCache: tc, |
||||||
|
structCache: sc, |
||||||
|
} |
||||||
|
|
||||||
|
// must copy alias validators for separate validations to be used in each validator instance
|
||||||
|
for k, val := range bakedInAliases { |
||||||
|
v.RegisterAlias(k, val) |
||||||
|
} |
||||||
|
|
||||||
|
// must copy validators for separate validations to be used in each instance
|
||||||
|
for k, val := range bakedInValidators { |
||||||
|
|
||||||
|
// no need to error check here, baked in will alwaays be valid
|
||||||
|
v.RegisterValidation(k, val) |
||||||
|
} |
||||||
|
|
||||||
|
v.pool = &sync.Pool{ |
||||||
|
New: func() interface{} { |
||||||
|
return &validate{ |
||||||
|
v: v, |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
return v |
||||||
|
} |
||||||
|
|
||||||
|
// SetTagName allows for changing of the default tag name of 'validate'
|
||||||
|
func (v *Validate) SetTagName(name string) { |
||||||
|
v.tagName = name |
||||||
|
} |
||||||
|
|
||||||
|
// RegisterTagNameFunc registers a function to get another name from the
|
||||||
|
// StructField eg. the JSON name
|
||||||
|
func (v *Validate) RegisterTagNameFunc(fn TagNameFunc) { |
||||||
|
v.tagNameFunc = fn |
||||||
|
v.hasTagNameFunc = true |
||||||
|
} |
||||||
|
|
||||||
|
// RegisterValidation adds a validation with the given tag
|
||||||
|
//
|
||||||
|
// NOTES:
|
||||||
|
// - if the key already exists, the previous validation function will be replaced.
|
||||||
|
// - this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||||
|
func (v *Validate) RegisterValidation(tag string, fn Func) error { |
||||||
|
|
||||||
|
if len(tag) == 0 { |
||||||
|
return errors.New("Function Key cannot be empty") |
||||||
|
} |
||||||
|
|
||||||
|
if fn == nil { |
||||||
|
return errors.New("Function cannot be empty") |
||||||
|
} |
||||||
|
|
||||||
|
_, ok := restrictedTags[tag] |
||||||
|
|
||||||
|
if ok || strings.ContainsAny(tag, restrictedTagChars) { |
||||||
|
panic(fmt.Sprintf(restrictedTagErr, tag)) |
||||||
|
} |
||||||
|
|
||||||
|
v.validations[tag] = fn |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// RegisterAlias registers a mapping of a single validation tag that
|
||||||
|
// defines a common or complex set of validation(s) to simplify adding validation
|
||||||
|
// to structs.
|
||||||
|
//
|
||||||
|
// NOTE: this function is not thread-safe it is intended that these all be registered prior to any validation
|
||||||
|
func (v *Validate) RegisterAlias(alias, tags string) { |
||||||
|
|
||||||
|
_, ok := restrictedTags[alias] |
||||||
|
|
||||||
|
if ok || strings.ContainsAny(alias, restrictedTagChars) { |
||||||
|
panic(fmt.Sprintf(restrictedAliasErr, alias)) |
||||||
|
} |
||||||
|
|
||||||
|
v.aliases[alias] = tags |
||||||
|
} |
||||||
|
|
||||||
|
// RegisterStructValidation registers a StructLevelFunc against a number of types.
|
||||||
|
// This is akin to implementing the 'Validatable' interface, but for structs for which
|
||||||
|
// you may not have access or rights to change.
|
||||||
|
//
|
||||||
|
// NOTES:
|
||||||
|
// - if this and the 'Validatable' interface are implemented the Struct Level takes precedence as to enable
|
||||||
|
// a struct out of your control's validation to be overridden
|
||||||
|
// - this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||||
|
func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interface{}) { |
||||||
|
|
||||||
|
if v.structLevelFuncs == nil { |
||||||
|
v.structLevelFuncs = make(map[reflect.Type]StructLevelFunc) |
||||||
|
} |
||||||
|
|
||||||
|
for _, t := range types { |
||||||
|
v.structLevelFuncs[reflect.TypeOf(t)] = fn |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
|
||||||
|
//
|
||||||
|
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||||
|
func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) { |
||||||
|
|
||||||
|
if v.customFuncs == nil { |
||||||
|
v.customFuncs = make(map[reflect.Type]CustomTypeFunc) |
||||||
|
} |
||||||
|
|
||||||
|
for _, t := range types { |
||||||
|
v.customFuncs[reflect.TypeOf(t)] = fn |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified.
|
||||||
|
//
|
||||||
|
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
|
||||||
|
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
|
||||||
|
func (v *Validate) Struct(s interface{}) (err error) { |
||||||
|
|
||||||
|
val := reflect.ValueOf(s) |
||||||
|
top := val |
||||||
|
|
||||||
|
if val.Kind() == reflect.Ptr && !val.IsNil() { |
||||||
|
val = val.Elem() |
||||||
|
} |
||||||
|
|
||||||
|
typ := val.Type() |
||||||
|
|
||||||
|
if val.Kind() != reflect.Struct || typ == timeType { |
||||||
|
return &InvalidValidationError{Type: typ} |
||||||
|
} |
||||||
|
|
||||||
|
// good to validate
|
||||||
|
vd := v.pool.Get().(*validate) |
||||||
|
vd.top = top |
||||||
|
vd.isPartial = false |
||||||
|
// vd.hasExcludes = false // only need to reset in StructPartial and StructExcept
|
||||||
|
|
||||||
|
vd.validateStruct(top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) |
||||||
|
|
||||||
|
if len(vd.errs) > 0 { |
||||||
|
err = vd.errs |
||||||
|
vd.errs = nil |
||||||
|
} |
||||||
|
|
||||||
|
v.pool.Put(vd) |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// StructPartial validates the fields passed in only, ignoring all others.
|
||||||
|
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||||
|
// eg. NestedStruct.Field or NestedArrayField[0].Struct.Name
|
||||||
|
//
|
||||||
|
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
|
||||||
|
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
|
||||||
|
func (v *Validate) StructPartial(s interface{}, fields ...string) (err error) { |
||||||
|
|
||||||
|
val := reflect.ValueOf(s) |
||||||
|
top := val |
||||||
|
|
||||||
|
if val.Kind() == reflect.Ptr && !val.IsNil() { |
||||||
|
val = val.Elem() |
||||||
|
} |
||||||
|
|
||||||
|
typ := val.Type() |
||||||
|
|
||||||
|
if val.Kind() != reflect.Struct || typ == timeType { |
||||||
|
return &InvalidValidationError{Type: typ} |
||||||
|
} |
||||||
|
|
||||||
|
// good to validate
|
||||||
|
vd := v.pool.Get().(*validate) |
||||||
|
vd.top = top |
||||||
|
vd.isPartial = true |
||||||
|
vd.hasExcludes = false |
||||||
|
vd.includeExclude = make(map[string]struct{}) |
||||||
|
|
||||||
|
name := typ.Name() |
||||||
|
|
||||||
|
if fields != nil { |
||||||
|
for _, k := range fields { |
||||||
|
|
||||||
|
flds := strings.Split(k, namespaceSeparator) |
||||||
|
if len(flds) > 0 { |
||||||
|
|
||||||
|
key := name + namespaceSeparator |
||||||
|
for _, s := range flds { |
||||||
|
|
||||||
|
idx := strings.Index(s, leftBracket) |
||||||
|
|
||||||
|
if idx != -1 { |
||||||
|
for idx != -1 { |
||||||
|
key += s[:idx] |
||||||
|
vd.includeExclude[key] = struct{}{} |
||||||
|
|
||||||
|
idx2 := strings.Index(s, rightBracket) |
||||||
|
idx2++ |
||||||
|
key += s[idx:idx2] |
||||||
|
vd.includeExclude[key] = struct{}{} |
||||||
|
s = s[idx2:] |
||||||
|
idx = strings.Index(s, leftBracket) |
||||||
|
} |
||||||
|
} else { |
||||||
|
|
||||||
|
key += s |
||||||
|
vd.includeExclude[key] = struct{}{} |
||||||
|
} |
||||||
|
|
||||||
|
key += namespaceSeparator |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
vd.validateStruct(top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) |
||||||
|
|
||||||
|
if len(vd.errs) > 0 { |
||||||
|
err = vd.errs |
||||||
|
vd.errs = nil |
||||||
|
} |
||||||
|
|
||||||
|
v.pool.Put(vd) |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// StructExcept validates all fields except the ones passed in.
|
||||||
|
// Fields may be provided in a namespaced fashion relative to the struct provided
|
||||||
|
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name
|
||||||
|
//
|
||||||
|
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
|
||||||
|
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
|
||||||
|
func (v *Validate) StructExcept(s interface{}, fields ...string) (err error) { |
||||||
|
|
||||||
|
val := reflect.ValueOf(s) |
||||||
|
top := val |
||||||
|
|
||||||
|
if val.Kind() == reflect.Ptr && !val.IsNil() { |
||||||
|
val = val.Elem() |
||||||
|
} |
||||||
|
|
||||||
|
typ := val.Type() |
||||||
|
|
||||||
|
if val.Kind() != reflect.Struct || typ == timeType { |
||||||
|
return &InvalidValidationError{Type: typ} |
||||||
|
} |
||||||
|
|
||||||
|
// good to validate
|
||||||
|
vd := v.pool.Get().(*validate) |
||||||
|
vd.top = top |
||||||
|
vd.isPartial = true |
||||||
|
vd.hasExcludes = true |
||||||
|
vd.includeExclude = make(map[string]struct{}) |
||||||
|
|
||||||
|
name := typ.Name() |
||||||
|
|
||||||
|
for _, key := range fields { |
||||||
|
vd.includeExclude[name+namespaceSeparator+key] = struct{}{} |
||||||
|
} |
||||||
|
|
||||||
|
vd.validateStruct(top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) |
||||||
|
|
||||||
|
if len(vd.errs) > 0 { |
||||||
|
err = vd.errs |
||||||
|
vd.errs = nil |
||||||
|
} |
||||||
|
|
||||||
|
v.pool.Put(vd) |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// func (v *validate) traverseField(parent reflect.Value, current reflect.Value, ns []byte, actualNs []byte, cf *cField, ct *cTag) {
|
||||||
|
|
||||||
|
// Var validates a single variable using tag style validation.
|
||||||
|
// eg.
|
||||||
|
// var i int
|
||||||
|
// validate.Var(i, "gt=1,lt=10")
|
||||||
|
//
|
||||||
|
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
|
||||||
|
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
|
||||||
|
// validate Array, Slice and maps fields which may contain more than one error
|
||||||
|
func (v *Validate) Var(field interface{}, tag string) (err error) { |
||||||
|
|
||||||
|
if len(tag) == 0 || tag == skipValidationTag { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// find cached tag
|
||||||
|
ctag, ok := v.tagCache.Get(tag) |
||||||
|
if !ok { |
||||||
|
v.tagCache.lock.Lock() |
||||||
|
defer v.tagCache.lock.Unlock() |
||||||
|
|
||||||
|
// could have been multiple trying to access, but once first is done this ensures tag
|
||||||
|
// isn't parsed again.
|
||||||
|
ctag, ok = v.tagCache.Get(tag) |
||||||
|
if !ok { |
||||||
|
ctag, _ = v.parseFieldTagsRecursive(tag, "", "", false) |
||||||
|
v.tagCache.Set(tag, ctag) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val := reflect.ValueOf(field) |
||||||
|
|
||||||
|
vd := v.pool.Get().(*validate) |
||||||
|
vd.top = val |
||||||
|
vd.isPartial = false |
||||||
|
|
||||||
|
vd.traverseField(val, val, vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag) |
||||||
|
|
||||||
|
if len(vd.errs) > 0 { |
||||||
|
err = vd.errs |
||||||
|
vd.errs = nil |
||||||
|
} |
||||||
|
|
||||||
|
v.pool.Put(vd) |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// VarWithValue validates a single variable, against another variable/field's value using tag style validation
|
||||||
|
// eg.
|
||||||
|
// s1 := "abcd"
|
||||||
|
// s2 := "abcd"
|
||||||
|
// validate.VarWithValue(s1, s2, "eqcsfield") // returns true
|
||||||
|
//
|
||||||
|
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
|
||||||
|
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
|
||||||
|
// validate Array, Slice and maps fields which may contain more than one error
|
||||||
|
func (v *Validate) VarWithValue(field interface{}, other interface{}, tag string) (err error) { |
||||||
|
|
||||||
|
if len(tag) == 0 || tag == skipValidationTag { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// find cached tag
|
||||||
|
ctag, ok := v.tagCache.Get(tag) |
||||||
|
if !ok { |
||||||
|
v.tagCache.lock.Lock() |
||||||
|
defer v.tagCache.lock.Unlock() |
||||||
|
|
||||||
|
// could have been multiple trying to access, but once first is done this ensures tag
|
||||||
|
// isn't parsed again.
|
||||||
|
ctag, ok = v.tagCache.Get(tag) |
||||||
|
if !ok { |
||||||
|
ctag, _ = v.parseFieldTagsRecursive(tag, "", "", false) |
||||||
|
v.tagCache.Set(tag, ctag) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
otherVal := reflect.ValueOf(other) |
||||||
|
|
||||||
|
vd := v.pool.Get().(*validate) |
||||||
|
vd.top = otherVal |
||||||
|
vd.isPartial = false |
||||||
|
|
||||||
|
vd.traverseField(otherVal, reflect.ValueOf(field), vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag) |
||||||
|
|
||||||
|
if len(vd.errs) > 0 { |
||||||
|
err = vd.errs |
||||||
|
vd.errs = nil |
||||||
|
} |
||||||
|
|
||||||
|
v.pool.Put(vd) |
||||||
|
|
||||||
|
return |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue