diff --git a/README.md b/README.md index 6be6371..ef89f36 100644 --- a/README.md +++ b/README.md @@ -239,9 +239,9 @@ How to Contribute There will always be a development branch for each version i.e. `v1-development`. In order to contribute, please make your pull requests against those branches. -If the changes being proposed or requested are breaking changes, please create an issue, for discussion -or create a pull request against the highest development branch for example this package has a -v1 and v1-development branch however, there will also be a v2-development brach even though v2 doesn't exist yet. +If the changes being proposed or requested are breaking changes, please create an issue, for discussion +or create a pull request against the highest development branch for example this package has a +v1 and v1-development branch however, there will also be a v2-development branch even though v2 doesn't exist yet. I strongly encourage everyone whom creates a custom validation function to contribute them and help make this package even better. diff --git a/validator.go b/validator.go index 19904d4..c36371d 100644 --- a/validator.go +++ b/validator.go @@ -20,21 +20,22 @@ import ( ) const ( - utf8HexComma = "0x2C" - utf8Pipe = "0x7C" - tagSeparator = "," - orSeparator = "|" - tagKeySeparator = "=" - structOnlyTag = "structonly" - omitempty = "omitempty" - skipValidationTag = "-" - diveTag = "dive" - existsTag = "exists" - fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" - arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket - mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket - invalidValidation = "Invalid validation tag on field %s" - undefinedValidation = "Undefined validation function on field %s" + utf8HexComma = "0x2C" + utf8Pipe = "0x7C" + tagSeparator = "," + orSeparator = "|" + tagKeySeparator = "=" + structOnlyTag = "structonly" + omitempty = "omitempty" + skipValidationTag = "-" + diveTag = "dive" + existsTag = "exists" + fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" + arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket + mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket + invalidValidation = "Invalid validation tag on field %s" + undefinedValidation = "Undefined validation function on field %s" + validatorNotInitialized = "Validator instance not initialized" ) var ( @@ -86,6 +87,12 @@ type Validate struct { errsPool *sync.Pool } +func (v *Validate) initCheck() { + if v == nil { + panic(validatorNotInitialized) + } +} + // Config contains the options that a Validator instance will use. // It is passed to the New() function type Config struct { @@ -171,6 +178,7 @@ func New(config *Config) *Validate { // NOTE: if the key already exists, the previous validation function will be replaced. // NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterValidation(key string, f Func) error { + v.initCheck() if len(key) == 0 { return errors.New("Function Key cannot be empty") @@ -194,6 +202,7 @@ func (v *Validate) RegisterValidation(key string, f Func) error { // 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{}) { + v.initCheck() if v.customTypeFuncs == nil { v.customTypeFuncs = map[reflect.Type]CustomTypeFunc{} @@ -214,6 +223,7 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{ // will be the actual tag within the alias that failed. // NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterAliasValidation(alias, tags string) { + v.initCheck() _, ok := restrictedTags[alias] @@ -230,6 +240,7 @@ func (v *Validate) RegisterAliasValidation(alias, tags string) { // NOTE: it returns ValidationErrors instead of a single FieldError because this can also // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) Field(field interface{}, tag string) error { + v.initCheck() errs := v.errsPool.Get().(ValidationErrors) fieldVal := reflect.ValueOf(field) @@ -249,6 +260,7 @@ func (v *Validate) Field(field interface{}, tag string) error { // NOTE: it returns ValidationErrors instead of a single FieldError because this can also // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) error { + v.initCheck() errs := v.errsPool.Get().(ValidationErrors) topVal := reflect.ValueOf(val) @@ -268,6 +280,7 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string // i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and returns nil or ValidationErrors as error // You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors. func (v *Validate) StructPartial(current interface{}, fields ...string) error { + v.initCheck() sv, _ := v.extractType(reflect.ValueOf(current)) name := sv.Type().Name() @@ -325,6 +338,7 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) error { // i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and returns nil or ValidationErrors as error // You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors. func (v *Validate) StructExcept(current interface{}, fields ...string) error { + v.initCheck() sv, _ := v.extractType(reflect.ValueOf(current)) name := sv.Type().Name() @@ -350,6 +364,7 @@ func (v *Validate) StructExcept(current interface{}, fields ...string) error { // it returns nil or ValidationErrors as error. // You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors. func (v *Validate) Struct(current interface{}) error { + v.initCheck() errs := v.errsPool.Get().(ValidationErrors) sv := reflect.ValueOf(current) diff --git a/validator_test.go b/validator_test.go index 12c19ff..63b79c1 100644 --- a/validator_test.go +++ b/validator_test.go @@ -255,6 +255,30 @@ func TestAliasTags(t *testing.T) { PanicMatches(t, func() { validate.RegisterAliasValidation("exists", "gt=5,lt=10") }, "Alias \"exists\" either contains restricted characters or is the same as a restricted tag needed for normal operation") } +func TestNilValidator(t *testing.T) { + + type TestStruct struct { + Test string `validate:"required"` + } + + ts := TestStruct{} + + var val *Validate + + fn := func(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + return current.String() == field.String() + } + + PanicMatches(t, func() { val.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) }, validatorNotInitialized) + PanicMatches(t, func() { val.RegisterValidation("something", fn) }, validatorNotInitialized) + PanicMatches(t, func() { val.Field(ts.Test, "required") }, validatorNotInitialized) + PanicMatches(t, func() { val.FieldWithValue("test", ts.Test, "required") }, validatorNotInitialized) + PanicMatches(t, func() { val.Struct(ts) }, validatorNotInitialized) + PanicMatches(t, func() { val.StructExcept(ts, "Test") }, validatorNotInitialized) + PanicMatches(t, func() { val.StructPartial(ts, "Test") }, validatorNotInitialized) +} + func TestStructPartial(t *testing.T) { p1 := []string{