diff --git a/README.md b/README.md index 344a050..335012d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Package validator ================ [![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -![Project status](https://img.shields.io/badge/version-9.6.0-green.svg) +![Project status](https://img.shields.io/badge/version-9.7.0-green.svg) [![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) diff --git a/baked_in.go b/baked_in.go index 7ea3ec3..e170da9 100644 --- a/baked_in.go +++ b/baked_in.go @@ -37,6 +37,7 @@ var ( utf8Pipe: {}, noStructLevelTag: {}, requiredTag: {}, + isdefault: {}, } // BakedInAliasValidators is a default mapping of a single validation tag that @@ -51,6 +52,7 @@ var ( // or even disregard and use your own map if so desired. bakedInValidators = map[string]Func{ "required": hasValue, + "isdefault": isDefault, "len": hasLengthOf, "min": hasMinOf, "max": hasMaxOf, @@ -903,6 +905,11 @@ func isAlphaUnicode(fl FieldLevel) bool { return alphaUnicodeRegex.MatchString(fl.Field().String()) } +// isDefault is the opposite of required aka hasValue +func isDefault(fl FieldLevel) bool { + return !hasValue(fl) +} + // HasValue is the validation function for validating if the current field's value is not the default static value. func hasValue(fl FieldLevel) bool { diff --git a/cache.go b/cache.go index f128f59..5e9f6d0 100644 --- a/cache.go +++ b/cache.go @@ -13,6 +13,7 @@ type tagType uint8 const ( typeDefault tagType = iota typeOmitEmpty + typeIsDefault typeNoStructLevel typeStructOnly typeDive @@ -224,6 +225,10 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s default: + if t == isdefault { + current.typeof = typeIsDefault + } + // if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C" orVals := strings.Split(t, orSeparator) diff --git a/doc.go b/doc.go index 6064f19..a3bdf80 100644 --- a/doc.go +++ b/doc.go @@ -220,6 +220,13 @@ ensures the value is not nil. Usage: required +Is Default + +This validates that the value is the default value and is almost the +opposite of required. + + Usage: isdefault + Length For numbers, length will ensure that the value is diff --git a/validator.go b/validator.go index 115dab4..5aac46c 100644 --- a/validator.go +++ b/validator.go @@ -112,7 +112,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr return } - if ct.typeof == typeOmitEmpty { + if ct.typeof == typeOmitEmpty || ct.typeof == typeIsDefault { return } @@ -174,6 +174,39 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr if ct.typeof == typeStructOnly { goto CONTINUE + } else if ct.typeof == typeIsDefault { + // set Field Level fields + v.slflParent = parent + v.flField = current + v.cf = cf + v.ct = ct + + if !ct.fn(ctx, v) { + v.str1 = string(append(ns, cf.altName...)) + + if v.v.hasTagNameFunc { + v.str2 = string(append(structNs, cf.name...)) + } else { + v.str2 = v.str1 + } + + v.errs = append(v.errs, + &fieldError{ + v: v.v, + tag: ct.aliasTag, + actualTag: ct.tag, + ns: v.str1, + structNs: v.str2, + fieldLen: uint8(len(cf.altName)), + structfieldLen: uint8(len(cf.name)), + value: current.Interface(), + param: ct.param, + kind: kind, + typ: typ, + }, + ) + return + } } ct = ct.next @@ -404,10 +437,6 @@ OUTER: v.cf = cf v.ct = ct - // // report error interface functions need these - // v.ns = ns - // v.actualNs = structNs - if !ct.fn(ctx, v) { v.str1 = string(append(ns, cf.altName...)) diff --git a/validator_instance.go b/validator_instance.go index da52481..e25156c 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -22,6 +22,7 @@ const ( structOnlyTag = "structonly" noStructLevelTag = "nostructlevel" omitempty = "omitempty" + isdefault = "isdefault" skipValidationTag = "-" diveTag = "dive" requiredTag = "required" diff --git a/validator_test.go b/validator_test.go index 26e4e17..6038ca1 100644 --- a/validator_test.go +++ b/validator_test.go @@ -7213,3 +7213,43 @@ func TestFQDNValidation(t *testing.T) { } } } + +func TestIsDefault(t *testing.T) { + + validate := New() + + type Inner struct { + String string `validate:"isdefault"` + } + type Test struct { + String string `validate:"isdefault"` + Inner *Inner `validate:"isdefault"` + } + + var tt Test + + errs := validate.Struct(tt) + Equal(t, errs, nil) + + tt.Inner = &Inner{String: ""} + errs = validate.Struct(tt) + NotEqual(t, errs, nil) + + fe := errs.(ValidationErrors)[0] + Equal(t, fe.Field(), "Inner") + Equal(t, fe.Namespace(), "Test.Inner") + Equal(t, fe.Tag(), "isdefault") + + type Test2 struct { + Inner Inner `validate:"isdefault"` + } + + var t2 Test2 + errs = validate.Struct(t2) + Equal(t, errs, nil) + + t2.Inner.String = "Changed" + errs = validate.Struct(t2) + NotEqual(t, errs, nil) + +}