From 14f963703b75c8d5021ea60b410de9573bbe0491 Mon Sep 17 00:00:00 2001 From: Pantelis Sampaziotis Date: Thu, 6 Feb 2020 18:13:19 +0200 Subject: [PATCH] Add datetime validation --- baked_in.go | 23 ++++++++++++++++++++-- doc.go | 7 +++++++ translations/en/en.go | 17 +++++++++++++++- translations/en/en_test.go | 9 +++++++++ validator_test.go | 40 +++++++++++++++++++++++++++++++++++++- 5 files changed, 92 insertions(+), 4 deletions(-) diff --git a/baked_in.go b/baked_in.go index deb00a2..4124c62 100644 --- a/baked_in.go +++ b/baked_in.go @@ -171,6 +171,7 @@ var ( "hostname_port": isHostnamePort, "lowercase": isLowercase, "uppercase": isUppercase, + "datetime": isDatetime, } ) @@ -2020,8 +2021,9 @@ func isJSON(fl FieldLevel) bool { if field.Kind() == reflect.String { val := field.String() return json.Valid([]byte(val)) - } - panic(fmt.Sprintf("Bad field type %T", field.Interface())) + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) } // isHostnamePort validates a : combination for fields typically used for socket address. @@ -2070,3 +2072,20 @@ func isUppercase(fl FieldLevel) bool { panic(fmt.Sprintf("Bad field type %T", field.Interface())) } + +// isDatetime is the validation function for validating if the current field's value is a valid datetime string. +func isDatetime(fl FieldLevel) bool { + field := fl.Field() + param := fl.Param() + + if field.Kind() == reflect.String { + _, err := time.Parse(param, field.String()) + if err != nil { + return false + } + + return true + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} diff --git a/doc.go b/doc.go index 9bcba9a..291c629 100644 --- a/doc.go +++ b/doc.go @@ -1062,6 +1062,13 @@ can be used to valiate fields typically passed to sockets and connections. Usage: hostname_port +Datetime + +This validates that a string value is a valid datetime based on the supplied datetime format. +Supplied format must match the official Go time format layout as documented in https://golang.org/pkg/time/ + + Usage: datetime=2006-01-02 + Alias Validators and Tags NOTE: When returning an error, the tag returned in "FieldError" will be diff --git a/translations/en/en.go b/translations/en/en.go index be61d85..1fdea95 100644 --- a/translations/en/en.go +++ b/translations/en/en.go @@ -1324,7 +1324,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er { tag: "json", translation: "{0} must be a valid json string", - override: false, + override: false, }, { tag: "lowercase", @@ -1336,6 +1336,21 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be an uppercase string", override: false, }, + { + tag: "datetime", + translation: "{0} does not match the {1} format", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, } for _, t := range translations { diff --git a/translations/en/en_test.go b/translations/en/en_test.go index b95ca98..39567b6 100644 --- a/translations/en/en_test.go +++ b/translations/en/en_test.go @@ -141,9 +141,13 @@ func TestTranslations(t *testing.T) { UniqueSlice []string `validate:"unique"` UniqueArray [3]string `validate:"unique"` UniqueMap map[string]string `validate:"unique"` +<<<<<<< HEAD JSONString string `validate:"json"` LowercaseString string `validate:"lowercase"` UppercaseString string `validate:"uppercase"` +======= + Datetime string `validate:"datetime=2006-01-02"` +>>>>>>> Add datetime validation } var test Test @@ -195,6 +199,7 @@ func TestTranslations(t *testing.T) { test.UniqueSlice = []string{"1234", "1234"} test.UniqueMap = map[string]string{"key1": "1234", "key2": "1234"} + test.Datetime = "2008-Feb-01" err = validate.Struct(test) NotEqual(t, err, nil) @@ -650,6 +655,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UppercaseString", expected: "UppercaseString must be an uppercase string", }, + { + ns: "Test.Datetime", + expected: "Datetime does not match the 2006-01-02 format", + }, } for _, tt := range tests { diff --git a/validator_test.go b/validator_test.go index 1323867..2e4215f 100644 --- a/validator_test.go +++ b/validator_test.go @@ -9155,4 +9155,42 @@ func TestUppercaseValidation(t *testing.T) { PanicMatches(t, func() { _ = validate.Var(2, "uppercase") }, "Bad field type int") -} \ No newline at end of file + +} + +func TestDatetimeValidation(t *testing.T) { + tests := []struct { + value string `validate:"datetime=2006-01-02"` + tag string + expected bool + }{ + {"2008-02-01", `datetime=2006-01-02`, true}, + {"2008-Feb-01", `datetime=2006-01-02`, false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.value, test.tag) + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d datetime failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d datetime failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "datetime" { + t.Fatalf("Index: %d datetime failed Error: %s", i, errs) + } + } + } + } + + PanicMatches(t, func() { + _ = validate.Var(2, "datetime") + }, "Bad field type int") +}