From 0b5dc7605b29ceb5868ce8027332aa4196b8bf9d Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sun, 20 Aug 2017 09:53:47 -0700 Subject: [PATCH 1/3] Add isdefault validation --- README.md | 2 +- baked_in.go | 7 +++++++ cache.go | 5 +++++ doc.go | 7 +++++++ validator.go | 39 ++++++++++++++++++++++++++++++++++----- validator_instance.go | 1 + validator_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 95 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 344a0501f..335012d84 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 7ea3ec3f1..e170da9d5 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 f128f596b..5e9f6d0f5 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 6064f19ec..a3bdf8054 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 115dab44f..5aac46cbe 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 da5248103..e25156cf1 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 26e4e1768..6038ca1fd 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) + +} From 0d09605a05121204f4497e6aa65b0ed450dd041c Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sun, 20 Aug 2017 10:02:01 -0700 Subject: [PATCH 2/3] 100% test coverage --- validator_test.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/validator_test.go b/validator_test.go index 6038ca1fd..445952970 100644 --- a/validator_test.go +++ b/validator_test.go @@ -7240,8 +7240,22 @@ func TestIsDefault(t *testing.T) { Equal(t, fe.Namespace(), "Test.Inner") Equal(t, fe.Tag(), "isdefault") + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] + + if name == "-" { + return "" + } + + return name + }) + + type Inner2 struct { + String string `validate:"isdefault"` + } + type Test2 struct { - Inner Inner `validate:"isdefault"` + Inner Inner2 `validate:"isdefault" json:"inner"` } var t2 Test2 @@ -7252,4 +7266,8 @@ func TestIsDefault(t *testing.T) { errs = validate.Struct(t2) NotEqual(t, errs, nil) + fe = errs.(ValidationErrors)[0] + Equal(t, fe.Field(), "inner") + Equal(t, fe.Namespace(), "Test2.inner") + Equal(t, fe.Tag(), "isdefault") } From f16354ec0314901809dcba7b0baec6045021eaff Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Wed, 6 Sep 2017 20:37:05 -0700 Subject: [PATCH 3/3] Add isdefault + fix fqdn Closes #299 Fixes #306 --- baked_in.go | 4 ++++ validator_test.go | 1 + 2 files changed, 5 insertions(+) diff --git a/baked_in.go b/baked_in.go index e170da9d5..a74c5a705 100644 --- a/baked_in.go +++ b/baked_in.go @@ -1494,6 +1494,10 @@ func isHostname(fl FieldLevel) bool { func isFQDN(fl FieldLevel) bool { val := fl.Field().String() + if val == "" { + return false + } + if val[len(val)-1] == '.' { val = val[0 : len(val)-1] } diff --git a/validator_test.go b/validator_test.go index 445952970..6f57671b3 100644 --- a/validator_test.go +++ b/validator_test.go @@ -7189,6 +7189,7 @@ func TestFQDNValidation(t *testing.T) { {"2001:cdba:0000:0000:0000:0000:3257:9652", false}, {"2001:cdba:0:0:0:0:3257:9652", false}, {"2001:cdba::3257:9652", false}, + {"", false}, } validate := New()