From ff033f620a895f8030b449b27d3c102f1c417a59 Mon Sep 17 00:00:00 2001 From: "Zeewell.Yu" Date: Tue, 9 Jul 2024 11:18:59 +0800 Subject: [PATCH 1/2] feat: Add support for omitting empty and zero values in validation --- baked_in.go | 15 +++++++++++++++ cache.go | 5 +++++ validator.go | 17 +++++++++++++++++ validator_instance.go | 1 + 4 files changed, 38 insertions(+) diff --git a/baked_in.go b/baked_in.go index b6fbaafa..34f4e3f2 100644 --- a/baked_in.go +++ b/baked_in.go @@ -50,6 +50,7 @@ var ( keysTag: {}, endKeysTag: {}, structOnlyTag: {}, + omitzero: {}, omitempty: {}, omitnil: {}, skipValidationTag: {}, @@ -1777,6 +1778,20 @@ func hasValue(fl FieldLevel) bool { } } +// hasNotZeroValue is the validation function for validating if the current field's value is not the zero value for its type. +func hasNotZeroValue(fl FieldLevel) bool { + field := fl.Field() + switch field.Kind() { + case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: + return !field.IsNil() + default: + if fl.(*validate).fldIsPointer && field.Interface() != nil { + return !field.IsZero() + } + return field.IsValid() && !field.IsZero() + } +} + // requireCheckFieldKind is a func for check field kind func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue bool) bool { field := fl.Field() diff --git a/cache.go b/cache.go index 2063e1b7..cbf5ff09 100644 --- a/cache.go +++ b/cache.go @@ -21,6 +21,7 @@ const ( typeKeys typeEndKeys typeOmitNil + typeOmitZero ) const ( @@ -249,6 +250,10 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s } return + case omitzero: + current.typeof = typeOmitZero + continue + case omitempty: current.typeof = typeOmitEmpty continue diff --git a/validator.go b/validator.go index 901e7b50..d7c2e658 100644 --- a/validator.go +++ b/validator.go @@ -117,6 +117,10 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr return } + if ct.typeof == typeOmitZero { + return + } + if ct.hasTag { if kind == reflect.Invalid { v.str1 = string(append(ns, cf.altName...)) @@ -238,6 +242,19 @@ OUTER: ct = ct.next continue + case typeOmitZero: + v.slflParent = parent + v.flField = current + v.cf = cf + v.ct = ct + + if !hasNotZeroValue(v) { + return + } + + ct = ct.next + continue + case typeOmitNil: v.slflParent = parent v.flField = current diff --git a/validator_instance.go b/validator_instance.go index d9f148db..779f689a 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -21,6 +21,7 @@ const ( tagKeySeparator = "=" structOnlyTag = "structonly" noStructLevelTag = "nostructlevel" + omitzero = "omitzero" omitempty = "omitempty" omitnil = "omitnil" isdefault = "isdefault" From 835464d2710f195c6f642025bbe5b30c47a29a3b Mon Sep 17 00:00:00 2001 From: "Zeewell.Yu" Date: Tue, 9 Jul 2024 11:39:43 +0800 Subject: [PATCH 2/2] tests: Add test for omitzero --- validator_test.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/validator_test.go b/validator_test.go index 0f96b777..ad84c86a 100644 --- a/validator_test.go +++ b/validator_test.go @@ -13900,6 +13900,57 @@ func TestOmitNilAndRequired(t *testing.T) { }) } +func TestOmitZero(t *testing.T) { + type ( + OmitEmpty struct { + Str string `validate:"omitempty,min=10"` + StrPtr *string `validate:"omitempty,min=10"` + } + OmitZero struct { + Str string `validate:"omitzero,min=10"` + StrPtr *string `validate:"omitzero,min=10"` + } + ) + + var ( + validate = New() + valid = "this is the long string to pass the validation rule" + empty = "" + ) + + t.Run("compare using valid data", func(t *testing.T) { + err1 := validate.Struct(OmitEmpty{Str: valid, StrPtr: &valid}) + err2 := validate.Struct(OmitZero{Str: valid, StrPtr: &valid}) + + Equal(t, err1, nil) + Equal(t, err2, nil) + }) + + t.Run("compare fully empty omitempty and omitzero", func(t *testing.T) { + err1 := validate.Struct(OmitEmpty{}) + err2 := validate.Struct(OmitZero{}) + + Equal(t, err1, nil) + Equal(t, err2, nil) + }) + + t.Run("compare with zero value", func(t *testing.T) { + err1 := validate.Struct(OmitEmpty{Str: "", StrPtr: nil}) + err2 := validate.Struct(OmitZero{Str: "", StrPtr: nil}) + + Equal(t, err1, nil) + Equal(t, err2, nil) + }) + + t.Run("compare with empty value", func(t *testing.T) { + err1 := validate.Struct(OmitEmpty{Str: empty, StrPtr: &empty}) + err2 := validate.Struct(OmitZero{Str: empty, StrPtr: &empty}) + + AssertError(t, err1, "OmitEmpty.StrPtr", "OmitEmpty.StrPtr", "StrPtr", "StrPtr", "min") + Equal(t, err2, nil) + }) +} + func TestPrivateFieldsStruct(t *testing.T) { type tc struct { stct interface{}