From 6b29637aaaa3f199f74dd7572c113b41774ba83b Mon Sep 17 00:00:00 2001 From: Andrei Avram Date: Sat, 17 Nov 2018 11:46:59 +0200 Subject: [PATCH 01/10] 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 02/10] 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 03/10] 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 04/10] 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() From dbd65cccbc6d38f6daa51ac39d8f97430d405988 Mon Sep 17 00:00:00 2001 From: rucas Date: Tue, 5 Mar 2019 16:01:59 -0800 Subject: [PATCH 05/10] better SSN regex * does not allow 00 group number * does not allow 0000 serial number --- regexes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regexes.go b/regexes.go index 3f79c61..e61d02f 100644 --- a/regexes.go +++ b/regexes.go @@ -34,7 +34,7 @@ const ( dataURIRegexString = "^data:.+\\/(.+);base64$" latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" - sSNRegexString = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` + sSNRegexString = `^[0-9]{3}[ -]?(0[1-9]|[1-9][0-9])[ -]?([1-9][0-9]{3}|[0-9][1-9][0-9]{2}|[0-9]{2}[1-9][0-9]|[0-9]{3}[1-9])$` hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // https://tools.ietf.org/html/rfc952 hostnameRegexStringRFC1123 = `^[a-zA-Z0-9][a-zA-Z0-9\-\.]+[a-z-Az0-9]$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address From 3945da16ee820591754b888b40b1361fb112ed66 Mon Sep 17 00:00:00 2001 From: Tyler Cipriani Date: Fri, 15 Mar 2019 15:21:21 -0600 Subject: [PATCH 06/10] Add `startswith` and `endswith` validators `HasPrefix` and `HasSuffix` are both part of the `strings` package. These seem like generally useful validations to include and cover some subset of the use-cases of a general regex validator without having any of the problems outlined by the validator docs. --- baked_in.go | 12 ++++++++++ doc.go | 12 ++++++++++ validator_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/baked_in.go b/baked_in.go index 52729b8..702e78d 100644 --- a/baked_in.go +++ b/baked_in.go @@ -112,6 +112,8 @@ var ( "excludes": excludes, "excludesall": excludesAll, "excludesrune": excludesRune, + "startswith": startsWith, + "endswith": endsWith, "isbn": isISBN, "isbn10": isISBN10, "isbn13": isISBN13, @@ -650,6 +652,16 @@ func contains(fl FieldLevel) bool { return strings.Contains(fl.Field().String(), fl.Param()) } +// StartsWith is the validation function for validating that the field's value starts with the text specified within the param. +func startsWith(fl FieldLevel) bool { + return strings.HasPrefix(fl.Field().String(), fl.Param()) +} + +// EndsWith is the validation function for validating that the field's value ends with the text specified within the param. +func endsWith(fl FieldLevel) bool { + return strings.HasSuffix(fl.Field().String(), fl.Param()) +} + // FieldContains is the validation function for validating if the current field's value contains the field specified by the param's value. func fieldContains(fl FieldLevel) bool { field := fl.Field() diff --git a/doc.go b/doc.go index 0bc27a1..5ce3800 100644 --- a/doc.go +++ b/doc.go @@ -714,6 +714,18 @@ This validates that a string value does not contain the supplied rune value. Usage: excludesrune=@ +Starts With + +This validates that a string value starts with the supplied string value + + Usage: startswith=hello + +Ends With + +This validates that a string value ends with the supplied string value + + Usage: endswith=goodbye + International Standard Book Number This validates that a string value contains a valid isbn10 or isbn13 value. diff --git a/validator_test.go b/validator_test.go index aded71f..86cfd5c 100644 --- a/validator_test.go +++ b/validator_test.go @@ -8555,3 +8555,59 @@ func TestDirValidation(t *testing.T) { validate.Var(2, "dir") }, "Bad field type int") } + +func TestStartsWithValidation(t *testing.T) { + tests := []struct { + Value string `validate:"startswith=(/^ヮ^)/*:・゚✧"` + Tag string + ExpectedNil bool + }{ + {Value: "(/^ヮ^)/*:・゚✧ glitter", Tag: "startswith=(/^ヮ^)/*:・゚✧", ExpectedNil: true}, + {Value: "abcd", Tag: "startswith=(/^ヮ^)/*:・゚✧", ExpectedNil: false}, + } + + validate := New() + + for i, s := range tests { + errs := validate.Var(s.Value, s.Tag) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + + errs = validate.Struct(s) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + } +} + +func TestEndsWithValidation(t *testing.T) { + tests := []struct { + Value string `validate:"endswith=(/^ヮ^)/*:・゚✧"` + Tag string + ExpectedNil bool + }{ + {Value: "glitter (/^ヮ^)/*:・゚✧", Tag: "endswith=(/^ヮ^)/*:・゚✧", ExpectedNil: true}, + {Value: "(/^ヮ^)/*:・゚✧ glitter", Tag: "endswith=(/^ヮ^)/*:・゚✧", ExpectedNil: false}, + } + + validate := New() + + for i, s := range tests { + errs := validate.Var(s.Value, s.Tag) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + + errs = validate.Struct(s) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + } +} + + From a918b74270707d779505b37313d2306d7bd127ca Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Mon, 25 Mar 2019 16:43:28 +0000 Subject: [PATCH 07/10] Updated doc.go with uuid_rfc4122 examples --- doc.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc.go b/doc.go index 0bc27a1..8589c63 100644 --- a/doc.go +++ b/doc.go @@ -734,25 +734,25 @@ This validates that a string value contains a valid isbn13 value. Universally Unique Identifier UUID -This validates that a string value contains a valid UUID. +This validates that a string value contains a valid UUID. Uppercase UUID values will not pass (use `uuid_rfc4122`) instead. Usage: uuid Universally Unique Identifier UUID v3 -This validates that a string value contains a valid version 3 UUID. +This validates that a string value contains a valid version 3 UUID. Uppercase UUID values will not pass (use `uuid3_rfc4122`) instead. Usage: uuid3 Universally Unique Identifier UUID v4 -This validates that a string value contains a valid version 4 UUID. +This validates that a string value contains a valid version 4 UUID. Uppercase UUID values will not pass (use `uuid4_rfc4122`) instead. Usage: uuid4 Universally Unique Identifier UUID v5 -This validates that a string value contains a valid version 5 UUID. +This validates that a string value contains a valid version 5 UUID. Uppercase UUID values will not pass (use `uuid5_rfc4122`) instead. Usage: uuid5 From ab7e4092fbbd048c8477c18b5d6d1e9cd050f2e0 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Mon, 25 Mar 2019 16:45:35 +0000 Subject: [PATCH 08/10] Grammar fix --- doc.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc.go b/doc.go index 8589c63..21d89ac 100644 --- a/doc.go +++ b/doc.go @@ -734,25 +734,25 @@ This validates that a string value contains a valid isbn13 value. Universally Unique Identifier UUID -This validates that a string value contains a valid UUID. Uppercase UUID values will not pass (use `uuid_rfc4122`) instead. +This validates that a string value contains a valid UUID. Uppercase UUID values will not pass - use `uuid_rfc4122` instead. Usage: uuid Universally Unique Identifier UUID v3 -This validates that a string value contains a valid version 3 UUID. Uppercase UUID values will not pass (use `uuid3_rfc4122`) instead. +This validates that a string value contains a valid version 3 UUID. Uppercase UUID values will not pass - use `uuid3_rfc4122` instead. Usage: uuid3 Universally Unique Identifier UUID v4 -This validates that a string value contains a valid version 4 UUID. Uppercase UUID values will not pass (use `uuid4_rfc4122`) instead. +This validates that a string value contains a valid version 4 UUID. Uppercase UUID values will not pass - use `uuid4_rfc4122` instead. Usage: uuid4 Universally Unique Identifier UUID v5 -This validates that a string value contains a valid version 5 UUID. Uppercase UUID values will not pass (use `uuid5_rfc4122`) instead. +This validates that a string value contains a valid version 5 UUID. Uppercase UUID values will not pass - use `uuid5_rfc4122` instead. Usage: uuid5 From 9099388d96e1abedb86f62c6d3451399cb731cf1 Mon Sep 17 00:00:00 2001 From: Ben Paxton Date: Wed, 27 Mar 2019 17:01:11 +0000 Subject: [PATCH 09/10] Fix email regex --- regexes.go | 2 +- validator_test.go | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/regexes.go b/regexes.go index 3f79c61..8f3febe 100644 --- a/regexes.go +++ b/regexes.go @@ -15,7 +15,7 @@ const ( rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$" hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" - emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" + emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$" iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$" diff --git a/validator_test.go b/validator_test.go index aded71f..8f6a163 100644 --- a/validator_test.go +++ b/validator_test.go @@ -6535,6 +6535,15 @@ func TestEmail(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "email") + s = `"test test"@email.com` + errs = validate.Var(s, "email") + Equal(t, errs, nil) + + s = `"@email.com` + errs = validate.Var(s, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "email") + i := true errs = validate.Var(i, "email") NotEqual(t, errs, nil) From 46b4b1e301c24cac870ffcb4ba5c8a703d1ef475 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sun, 31 Mar 2019 06:31:25 -0700 Subject: [PATCH 10/10] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b45b1b..db03584 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.27.0-green.svg) +![Project status](https://img.shields.io/badge/version-9.28.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)