From 6b29637aaaa3f199f74dd7572c113b41774ba83b Mon Sep 17 00:00:00 2001 From: Andrei Avram Date: Sat, 17 Nov 2018 11:46:59 +0200 Subject: [PATCH 1/4] Add NotBlank validation function. For validating if the current field has a value or length greater than zero. --- baked_in.go | 15 +++++++++++++++ validator_test.go | 29 ++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/baked_in.go b/baked_in.go index 0fae541..965c651 100644 --- a/baked_in.go +++ b/baked_in.go @@ -63,6 +63,7 @@ var ( // or even disregard and use your own map if so desired. bakedInValidators = map[string]Func{ "required": hasValue, + "notblank": notBlank, "isdefault": isDefault, "len": hasLengthOf, "min": hasMinOf, @@ -1264,6 +1265,20 @@ func hasValue(fl FieldLevel) bool { } } +// NotBlank is the validation function for validating if the current field has a value or length greater than zero. +func notBlank(fl FieldLevel) bool { + field := fl.Field() + + switch field.Kind() { + case reflect.String: + return len(strings.TrimSpace(field.String())) > 0 + case reflect.Chan, reflect.Map, reflect.Slice, reflect.Array: + return field.Len() > 0 + default: + return hasValue(fl) + } +} + // IsGteField is the validation function for validating if the current field's value is greater than or equal to the field specified by the param's value. func isGteField(fl FieldLevel) bool { diff --git a/validator_test.go b/validator_test.go index 5534aa5..69724c1 100644 --- a/validator_test.go +++ b/validator_test.go @@ -62,6 +62,7 @@ type TestInterface struct { type TestString struct { BlankTag string `validate:""` Required string `validate:"required"` + NotBlank string `validate:"notblank"` Len string `validate:"len=10"` Min string `validate:"min=1"` Max string `validate:"max=10"` @@ -81,6 +82,7 @@ type TestString struct { type TestUint64 struct { Required uint64 `validate:"required"` + NotBlank uint64 `validate:"notblank"` Len uint64 `validate:"len=10"` Min uint64 `validate:"min=1"` Max uint64 `validate:"max=10"` @@ -90,6 +92,7 @@ type TestUint64 struct { type TestFloat64 struct { Required float64 `validate:"required"` + NotBlank float64 `validate:"notblank"` Len float64 `validate:"len=10"` Min float64 `validate:"min=1"` Max float64 `validate:"max=10"` @@ -100,6 +103,7 @@ type TestFloat64 struct { type TestSlice struct { Required []int `validate:"required"` + NotBlank []int `validate:"notblank"` Len []int `validate:"len=10"` Min []int `validate:"min=1"` Max []int `validate:"max=10"` @@ -6725,6 +6729,7 @@ func TestStructStringValidation(t *testing.T) { tSuccess := &TestString{ Required: "Required", + NotBlank: "NotBLank", Len: "length==10", Min: "min=1", Max: "1234567890", @@ -6755,6 +6760,7 @@ func TestStructStringValidation(t *testing.T) { tFail := &TestString{ Required: "", + NotBlank: " ", Len: "", Min: "", Max: "12345678901", @@ -6781,10 +6787,11 @@ func TestStructStringValidation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs.(ValidationErrors)), 13) + Equal(t, len(errs.(ValidationErrors)), 14) // Assert Fields AssertError(t, errs, "TestString.Required", "TestString.Required", "Required", "Required", "required") + AssertError(t, errs, "TestString.NotBlank", "TestString.NotBlank", "NotBlank", "NotBlank", "notblank") AssertError(t, errs, "TestString.Len", "TestString.Len", "Len", "Len", "len") AssertError(t, errs, "TestString.Min", "TestString.Min", "Min", "Min", "min") AssertError(t, errs, "TestString.Max", "TestString.Max", "Max", "Max", "max") @@ -6805,6 +6812,7 @@ func TestStructInt32Validation(t *testing.T) { type TestInt32 struct { Required int `validate:"required"` + NotBlank int `validate:"notblank"` Len int `validate:"len=10"` Min int `validate:"min=1"` Max int `validate:"max=10"` @@ -6818,6 +6826,7 @@ func TestStructInt32Validation(t *testing.T) { tSuccess := &TestInt32{ Required: 1, + NotBlank: 1, Len: 10, Min: 1, Max: 10, @@ -6835,6 +6844,7 @@ func TestStructInt32Validation(t *testing.T) { tFail := &TestInt32{ Required: 0, + NotBlank: 0, Len: 11, Min: -1, Max: 11, @@ -6850,10 +6860,11 @@ func TestStructInt32Validation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs.(ValidationErrors)), 10) + Equal(t, len(errs.(ValidationErrors)), 11) // Assert Fields AssertError(t, errs, "TestInt32.Required", "TestInt32.Required", "Required", "Required", "required") + AssertError(t, errs, "TestInt32.NotBlank", "TestInt32.NotBlank", "NotBlank", "NotBlank", "notblank") AssertError(t, errs, "TestInt32.Len", "TestInt32.Len", "Len", "Len", "len") AssertError(t, errs, "TestInt32.Min", "TestInt32.Min", "Min", "Min", "min") AssertError(t, errs, "TestInt32.Max", "TestInt32.Max", "Max", "Max", "max") @@ -6871,6 +6882,7 @@ func TestStructUint64Validation(t *testing.T) { tSuccess := &TestUint64{ Required: 1, + NotBlank: 1, Len: 10, Min: 1, Max: 10, @@ -6883,6 +6895,7 @@ func TestStructUint64Validation(t *testing.T) { tFail := &TestUint64{ Required: 0, + NotBlank: 0, Len: 11, Min: 0, Max: 11, @@ -6894,10 +6907,11 @@ func TestStructUint64Validation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs.(ValidationErrors)), 6) + Equal(t, len(errs.(ValidationErrors)), 7) // Assert Fields AssertError(t, errs, "TestUint64.Required", "TestUint64.Required", "Required", "Required", "required") + AssertError(t, errs, "TestUint64.NotBlank", "TestUint64.NotBlank", "NotBlank", "NotBlank", "notblank") AssertError(t, errs, "TestUint64.Len", "TestUint64.Len", "Len", "Len", "len") AssertError(t, errs, "TestUint64.Min", "TestUint64.Min", "Min", "Min", "min") AssertError(t, errs, "TestUint64.Max", "TestUint64.Max", "Max", "Max", "max") @@ -6911,6 +6925,7 @@ func TestStructFloat64Validation(t *testing.T) { tSuccess := &TestFloat64{ Required: 1, + NotBlank: 1, Len: 10, Min: 1, Max: 10, @@ -6923,6 +6938,7 @@ func TestStructFloat64Validation(t *testing.T) { tFail := &TestFloat64{ Required: 0, + NotBlank: 0, Len: 11, Min: 0, Max: 11, @@ -6934,10 +6950,11 @@ func TestStructFloat64Validation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs.(ValidationErrors)), 6) + Equal(t, len(errs.(ValidationErrors)), 7) // Assert Fields AssertError(t, errs, "TestFloat64.Required", "TestFloat64.Required", "Required", "Required", "required") + AssertError(t, errs, "TestFloat64.NotBlank", "TestFloat64.NotBlank", "NotBlank", "NotBlank", "notblank") AssertError(t, errs, "TestFloat64.Len", "TestFloat64.Len", "Len", "Len", "len") AssertError(t, errs, "TestFloat64.Min", "TestFloat64.Min", "Min", "Min", "min") AssertError(t, errs, "TestFloat64.Max", "TestFloat64.Max", "Max", "Max", "max") @@ -6951,6 +6968,7 @@ func TestStructSliceValidation(t *testing.T) { tSuccess := &TestSlice{ Required: []int{1}, + NotBlank: []int{1}, Len: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, Min: []int{1, 2}, Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, @@ -6963,6 +6981,7 @@ func TestStructSliceValidation(t *testing.T) { tFail := &TestSlice{ Required: nil, + NotBlank: []int{}, Len: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1}, Min: []int{}, Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1}, @@ -6972,7 +6991,7 @@ func TestStructSliceValidation(t *testing.T) { errs = validate.Struct(tFail) NotEqual(t, errs, nil) - Equal(t, len(errs.(ValidationErrors)), 6) + Equal(t, len(errs.(ValidationErrors)), 7) // Assert Field Errors AssertError(t, errs, "TestSlice.Required", "TestSlice.Required", "Required", "Required", "required") From faaace938dd72610c32ac0e4bdbc67ab08fd9f75 Mon Sep 17 00:00:00 2001 From: Andrei Avram Date: Sun, 18 Nov 2018 22:00:52 +0200 Subject: [PATCH 2/4] Add documentation. --- doc.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc.go b/doc.go index 64cbcdf..4c4cbf3 100644 --- a/doc.go +++ b/doc.go @@ -245,6 +245,14 @@ ensures the value is not nil. Usage: required +NotBlank + +This validates that the value is not blank or with length zero. +For strings ensures they do not contain only spaces. For channels, maps, slices and arrays +ensures they don't have zero length. For others, the "required" validation is used. + + Usage: notblank + Is Default This validates that the value is the default value and is almost the From 1d286c8332ed3caac7603c5c0c159e6a43912904 Mon Sep 17 00:00:00 2001 From: Andrei Avram Date: Wed, 12 Dec 2018 20:47:09 +0200 Subject: [PATCH 3/4] Define NotBlank as non standard validator. --- baked_in.go | 15 ------ doc.go | 28 +++++++--- non-standard/validators/notblank.go | 24 +++++++++ non-standard/validators/notblank_test.go | 65 ++++++++++++++++++++++++ validator_test.go | 29 ++--------- 5 files changed, 114 insertions(+), 47 deletions(-) create mode 100644 non-standard/validators/notblank.go create mode 100644 non-standard/validators/notblank_test.go diff --git a/baked_in.go b/baked_in.go index 965c651..0fae541 100644 --- a/baked_in.go +++ b/baked_in.go @@ -63,7 +63,6 @@ var ( // or even disregard and use your own map if so desired. bakedInValidators = map[string]Func{ "required": hasValue, - "notblank": notBlank, "isdefault": isDefault, "len": hasLengthOf, "min": hasMinOf, @@ -1265,20 +1264,6 @@ func hasValue(fl FieldLevel) bool { } } -// NotBlank is the validation function for validating if the current field has a value or length greater than zero. -func notBlank(fl FieldLevel) bool { - field := fl.Field() - - switch field.Kind() { - case reflect.String: - return len(strings.TrimSpace(field.String())) > 0 - case reflect.Chan, reflect.Map, reflect.Slice, reflect.Array: - return field.Len() > 0 - default: - return hasValue(fl) - } -} - // IsGteField is the validation function for validating if the current field's value is greater than or equal to the field specified by the param's value. func isGteField(fl FieldLevel) bool { diff --git a/doc.go b/doc.go index 4c4cbf3..4b5c373 100644 --- a/doc.go +++ b/doc.go @@ -245,14 +245,6 @@ ensures the value is not nil. Usage: required -NotBlank - -This validates that the value is not blank or with length zero. -For strings ensures they do not contain only spaces. For channels, maps, slices and arrays -ensures they don't have zero length. For others, the "required" validation is used. - - Usage: notblank - Is Default This validates that the value is the default value and is almost the @@ -998,5 +990,25 @@ that should not make it to production. } validate.Struct(t) // this will panic + +Non standard validators + + type Test struct { + TestField string `validate:"yourtag"` + } + + t := &Test{ + TestField: "Test" + } + + validate := validator.New() + validate.RegisterValidation("yourtag", validations.ValidatorName) + + NotBlank + This validates that the value is not blank or with length zero. + For strings ensures they do not contain only spaces. For channels, maps, slices and arrays + ensures they don't have zero length. For others, a non empty value is required. + + Usage: notblank */ package validator diff --git a/non-standard/validators/notblank.go b/non-standard/validators/notblank.go new file mode 100644 index 0000000..3a38319 --- /dev/null +++ b/non-standard/validators/notblank.go @@ -0,0 +1,24 @@ +package validators + +import ( + "reflect" + "strings" + + "github.com/andreiavrammsd/validator" +) + +// NotBlank is the validation function for validating if the current field has a value or length greater than zero. +func NotBlank(fl validator.FieldLevel) bool { + field := fl.Field() + + switch field.Kind() { + case reflect.String: + return len(strings.TrimSpace(field.String())) > 0 + case reflect.Chan, reflect.Map, reflect.Slice, reflect.Array: + return field.Len() > 0 + case reflect.Ptr, reflect.Interface, reflect.Func: + return !field.IsNil() + default: + return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface() + } +} diff --git a/non-standard/validators/notblank_test.go b/non-standard/validators/notblank_test.go new file mode 100644 index 0000000..1eecae6 --- /dev/null +++ b/non-standard/validators/notblank_test.go @@ -0,0 +1,65 @@ +package validators + +import ( + "testing" + + "github.com/andreiavrammsd/validator" + "gopkg.in/go-playground/assert.v1" +) + +type test struct { + String string `validate:"notblank"` + Array []int `validate:"notblank"` + Pointer *int `validate:"notblank"` + Number int `validate:"notblank"` + Interface interface{} `validate:"notblank"` + Func func() `validate:"notblank"` +} + +func TestNotBlank(t *testing.T) { + v := validator.New() + err := v.RegisterValidation("notblank", NotBlank) + assert.Equal(t, nil, err) + + // Errors + var x *int + invalid := test{ + String: " ", + Array: []int{}, + Pointer: x, + Number: 0, + Interface: nil, + Func: nil, + } + fieldsWithError := []string{ + "String", + "Array", + "Pointer", + "Number", + "Interface", + "Func", + } + + errors := v.Struct(invalid).(validator.ValidationErrors) + var fields []string + for _, err := range errors { + fields = append(fields, err.Field()) + } + + assert.Equal(t, fieldsWithError, fields) + + // No errors + y := 1 + x = &y + valid := test{ + String: "str", + Array: []int{1}, + Pointer: x, + Number: 1, + Interface: "value", + Func: func() {}, + } + + err = v.Struct(valid) + assert.Equal(t, nil, err) +} diff --git a/validator_test.go b/validator_test.go index 69724c1..5534aa5 100644 --- a/validator_test.go +++ b/validator_test.go @@ -62,7 +62,6 @@ type TestInterface struct { type TestString struct { BlankTag string `validate:""` Required string `validate:"required"` - NotBlank string `validate:"notblank"` Len string `validate:"len=10"` Min string `validate:"min=1"` Max string `validate:"max=10"` @@ -82,7 +81,6 @@ type TestString struct { type TestUint64 struct { Required uint64 `validate:"required"` - NotBlank uint64 `validate:"notblank"` Len uint64 `validate:"len=10"` Min uint64 `validate:"min=1"` Max uint64 `validate:"max=10"` @@ -92,7 +90,6 @@ type TestUint64 struct { type TestFloat64 struct { Required float64 `validate:"required"` - NotBlank float64 `validate:"notblank"` Len float64 `validate:"len=10"` Min float64 `validate:"min=1"` Max float64 `validate:"max=10"` @@ -103,7 +100,6 @@ type TestFloat64 struct { type TestSlice struct { Required []int `validate:"required"` - NotBlank []int `validate:"notblank"` Len []int `validate:"len=10"` Min []int `validate:"min=1"` Max []int `validate:"max=10"` @@ -6729,7 +6725,6 @@ func TestStructStringValidation(t *testing.T) { tSuccess := &TestString{ Required: "Required", - NotBlank: "NotBLank", Len: "length==10", Min: "min=1", Max: "1234567890", @@ -6760,7 +6755,6 @@ func TestStructStringValidation(t *testing.T) { tFail := &TestString{ Required: "", - NotBlank: " ", Len: "", Min: "", Max: "12345678901", @@ -6787,11 +6781,10 @@ func TestStructStringValidation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs.(ValidationErrors)), 14) + Equal(t, len(errs.(ValidationErrors)), 13) // Assert Fields AssertError(t, errs, "TestString.Required", "TestString.Required", "Required", "Required", "required") - AssertError(t, errs, "TestString.NotBlank", "TestString.NotBlank", "NotBlank", "NotBlank", "notblank") AssertError(t, errs, "TestString.Len", "TestString.Len", "Len", "Len", "len") AssertError(t, errs, "TestString.Min", "TestString.Min", "Min", "Min", "min") AssertError(t, errs, "TestString.Max", "TestString.Max", "Max", "Max", "max") @@ -6812,7 +6805,6 @@ func TestStructInt32Validation(t *testing.T) { type TestInt32 struct { Required int `validate:"required"` - NotBlank int `validate:"notblank"` Len int `validate:"len=10"` Min int `validate:"min=1"` Max int `validate:"max=10"` @@ -6826,7 +6818,6 @@ func TestStructInt32Validation(t *testing.T) { tSuccess := &TestInt32{ Required: 1, - NotBlank: 1, Len: 10, Min: 1, Max: 10, @@ -6844,7 +6835,6 @@ func TestStructInt32Validation(t *testing.T) { tFail := &TestInt32{ Required: 0, - NotBlank: 0, Len: 11, Min: -1, Max: 11, @@ -6860,11 +6850,10 @@ func TestStructInt32Validation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs.(ValidationErrors)), 11) + Equal(t, len(errs.(ValidationErrors)), 10) // Assert Fields AssertError(t, errs, "TestInt32.Required", "TestInt32.Required", "Required", "Required", "required") - AssertError(t, errs, "TestInt32.NotBlank", "TestInt32.NotBlank", "NotBlank", "NotBlank", "notblank") AssertError(t, errs, "TestInt32.Len", "TestInt32.Len", "Len", "Len", "len") AssertError(t, errs, "TestInt32.Min", "TestInt32.Min", "Min", "Min", "min") AssertError(t, errs, "TestInt32.Max", "TestInt32.Max", "Max", "Max", "max") @@ -6882,7 +6871,6 @@ func TestStructUint64Validation(t *testing.T) { tSuccess := &TestUint64{ Required: 1, - NotBlank: 1, Len: 10, Min: 1, Max: 10, @@ -6895,7 +6883,6 @@ func TestStructUint64Validation(t *testing.T) { tFail := &TestUint64{ Required: 0, - NotBlank: 0, Len: 11, Min: 0, Max: 11, @@ -6907,11 +6894,10 @@ func TestStructUint64Validation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs.(ValidationErrors)), 7) + Equal(t, len(errs.(ValidationErrors)), 6) // Assert Fields AssertError(t, errs, "TestUint64.Required", "TestUint64.Required", "Required", "Required", "required") - AssertError(t, errs, "TestUint64.NotBlank", "TestUint64.NotBlank", "NotBlank", "NotBlank", "notblank") AssertError(t, errs, "TestUint64.Len", "TestUint64.Len", "Len", "Len", "len") AssertError(t, errs, "TestUint64.Min", "TestUint64.Min", "Min", "Min", "min") AssertError(t, errs, "TestUint64.Max", "TestUint64.Max", "Max", "Max", "max") @@ -6925,7 +6911,6 @@ func TestStructFloat64Validation(t *testing.T) { tSuccess := &TestFloat64{ Required: 1, - NotBlank: 1, Len: 10, Min: 1, Max: 10, @@ -6938,7 +6923,6 @@ func TestStructFloat64Validation(t *testing.T) { tFail := &TestFloat64{ Required: 0, - NotBlank: 0, Len: 11, Min: 0, Max: 11, @@ -6950,11 +6934,10 @@ func TestStructFloat64Validation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs.(ValidationErrors)), 7) + Equal(t, len(errs.(ValidationErrors)), 6) // Assert Fields AssertError(t, errs, "TestFloat64.Required", "TestFloat64.Required", "Required", "Required", "required") - AssertError(t, errs, "TestFloat64.NotBlank", "TestFloat64.NotBlank", "NotBlank", "NotBlank", "notblank") AssertError(t, errs, "TestFloat64.Len", "TestFloat64.Len", "Len", "Len", "len") AssertError(t, errs, "TestFloat64.Min", "TestFloat64.Min", "Min", "Min", "min") AssertError(t, errs, "TestFloat64.Max", "TestFloat64.Max", "Max", "Max", "max") @@ -6968,7 +6951,6 @@ func TestStructSliceValidation(t *testing.T) { tSuccess := &TestSlice{ Required: []int{1}, - NotBlank: []int{1}, Len: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, Min: []int{1, 2}, Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, @@ -6981,7 +6963,6 @@ func TestStructSliceValidation(t *testing.T) { tFail := &TestSlice{ Required: nil, - NotBlank: []int{}, Len: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1}, Min: []int{}, Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1}, @@ -6991,7 +6972,7 @@ func TestStructSliceValidation(t *testing.T) { errs = validate.Struct(tFail) NotEqual(t, errs, nil) - Equal(t, len(errs.(ValidationErrors)), 7) + Equal(t, len(errs.(ValidationErrors)), 6) // Assert Field Errors AssertError(t, errs, "TestSlice.Required", "TestSlice.Required", "Required", "Required", "required") From f2ac4efd5734abf0eb3151d3df94fd37f4895024 Mon Sep 17 00:00:00 2001 From: Andrei Avram Date: Tue, 12 Feb 2019 22:00:29 +0200 Subject: [PATCH 4/4] Update non standard validation docs. --- doc.go | 5 +++++ non-standard/validators/notblank.go | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/doc.go b/doc.go index 4b5c373..2309733 100644 --- a/doc.go +++ b/doc.go @@ -993,6 +993,11 @@ that should not make it to production. Non standard validators +A collection of validation rules that are frequently needed but are more +complex than the ones found in the baked in validators. +A non standard validator must be registered manually using any tag you like. +See below examples of registration and use. + type Test struct { TestField string `validate:"yourtag"` } diff --git a/non-standard/validators/notblank.go b/non-standard/validators/notblank.go index 3a38319..5c2d806 100644 --- a/non-standard/validators/notblank.go +++ b/non-standard/validators/notblank.go @@ -7,7 +7,8 @@ import ( "github.com/andreiavrammsd/validator" ) -// NotBlank is the validation function for validating if the current field has a value or length greater than zero. +// NotBlank is the validation function for validating if the current field +// has a value or length greater than zero, or is not a space only string. func NotBlank(fl validator.FieldLevel) bool { field := fl.Field()