From 19f8e61dba724697035547b453f4b12ea1d0601c Mon Sep 17 00:00:00 2001 From: Markus Tenghamn Date: Sun, 2 Jan 2022 02:18:13 +0100 Subject: [PATCH 01/22] Fixed country_code validation to properly handle strings (#873) --- baked_in.go | 6 ++++++ validator_test.go | 42 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/baked_in.go b/baked_in.go index f5fd239..51a224c 100644 --- a/baked_in.go +++ b/baked_in.go @@ -2351,6 +2351,12 @@ func isIso3166AlphaNumeric(fl FieldLevel) bool { var code int switch field.Kind() { + case reflect.String: + i, err := strconv.Atoi(field.String()) + if err != nil { + return false + } + code = i % 1000 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: code = int(field.Int() % 1000) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: diff --git a/validator_test.go b/validator_test.go index f694203..5064139 100644 --- a/validator_test.go +++ b/validator_test.go @@ -11053,12 +11053,15 @@ func TestIsIso3166Alpha3Validation(t *testing.T) { func TestIsIso3166AlphaNumericValidation(t *testing.T) { tests := []struct { - value int + value interface{} expected bool }{ {248, true}, + {"248", true}, {0, false}, {1, false}, + {"1", false}, + {"invalid_int", false}, } validate := New() @@ -11079,8 +11082,41 @@ func TestIsIso3166AlphaNumericValidation(t *testing.T) { } PanicMatches(t, func() { - _ = validate.Var("1", "iso3166_1_alpha_numeric") - }, "Bad field type string") + _ = validate.Var([]string{"1"}, "iso3166_1_alpha_numeric") + }, "Bad field type []string") +} + +func TestCountryCodeValidation(t *testing.T) { + tests := []struct { + value interface{} + expected bool + }{ + {248, true}, + {0, false}, + {1, false}, + {"POL", true}, + {"NO", true}, + {"248", true}, + {"1", false}, + {"0", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.value, "country_code") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d country_code failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d country_code failed Error: %s", i, errs) + } + } + } } func TestIsIso4217Validation(t *testing.T) { From c67d01d6108cad114344fca61ad2a789131b6dab Mon Sep 17 00:00:00 2001 From: nesty92 Date: Sat, 1 Jan 2022 20:23:23 -0500 Subject: [PATCH 02/22] Add support for all hex color notations (#866) (#867) --- regexes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regexes.go b/regexes.go index df00c4e..bac3a55 100644 --- a/regexes.go +++ b/regexes.go @@ -10,7 +10,7 @@ const ( numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$" numberRegexString = "^[0-9]+$" hexadecimalRegexString = "^(0[xX])?[0-9a-fA-F]+$" - hexColorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" + hexColorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$" rgbRegexString = "^rgb\\(\\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*\\)$" 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*\\)$" From ec2071b3830230cdd41bb8b799b055e0642378cc Mon Sep 17 00:00:00 2001 From: Kazuki Onishi <41312202+0n1shi@users.noreply.github.com> Date: Sun, 2 Jan 2022 10:27:23 +0900 Subject: [PATCH 03/22] Add DNS RFC 1035 label format validator (#833) --- baked_in.go | 9 +++++++++ doc.go | 7 +++++++ regexes.go | 2 ++ validator_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+) diff --git a/baked_in.go b/baked_in.go index 51a224c..d80ec48 100644 --- a/baked_in.go +++ b/baked_in.go @@ -198,6 +198,7 @@ var ( "postcode_iso3166_alpha2": isPostcodeByIso3166Alpha2, "postcode_iso3166_alpha2_field": isPostcodeByIso3166Alpha2Field, "bic": isIsoBicFormat, + "dns_rfc1035_label": isDnsRFC1035LabelFormat, } ) @@ -2413,3 +2414,11 @@ func isIsoBicFormat(fl FieldLevel) bool { return bicRegex.MatchString(bicString) } + +// isDnsRFC1035LabelFormat is the validation function +// for validating if the current field's value is +// a valid dns RFC 1035 label, defined in RFC 1035. +func isDnsRFC1035LabelFormat(fl FieldLevel) bool { + val := fl.Field().String() + return dnsRegexRFC1035Label.MatchString(val) +} diff --git a/doc.go b/doc.go index 8c25847..a5d394a 100644 --- a/doc.go +++ b/doc.go @@ -1255,6 +1255,13 @@ More information on https://www.iso.org/standard/60390.html Usage: bic +RFC 1035 label + +This validates that a string value is a valid dns RFC 1035 label, defined in RFC 1035. +More information on https://datatracker.ietf.org/doc/html/rfc1035 + + Usage: dns_rfc1035_label + TimeZone This validates that a string value is a valid time zone based on the time zone database present on the system. diff --git a/regexes.go b/regexes.go index bac3a55..19e7c29 100644 --- a/regexes.go +++ b/regexes.go @@ -51,6 +51,7 @@ const ( jWTRegexString = "^[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]*$" splitParamsRegexString = `'[^']*'|\S+` bicRegexString = `^[A-Za-z]{6}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$` + dnsRegexStringRFC1035Label = "^[a-z]([-a-z0-9]*[a-z0-9]){0,62}$" ) var ( @@ -102,4 +103,5 @@ var ( jWTRegex = regexp.MustCompile(jWTRegexString) splitParamsRegex = regexp.MustCompile(splitParamsRegexString) bicRegex = regexp.MustCompile(bicRegexString) + dnsRegexRFC1035Label = regexp.MustCompile(dnsRegexStringRFC1035Label) ) diff --git a/validator_test.go b/validator_test.go index 5064139..799efdf 100644 --- a/validator_test.go +++ b/validator_test.go @@ -11343,6 +11343,45 @@ func TestBicIsoFormatValidation(t *testing.T) { } } +func TestRFC1035LabelFormatValidation(t *testing.T) { + tests := []struct { + value string `validate:"dns_rfc1035_label"` + tag string + expected bool + }{ + {"abc", "dns_rfc1035_label", true}, + {"abc-", "dns_rfc1035_label", false}, + {"abc-123", "dns_rfc1035_label", true}, + {"ABC", "dns_rfc1035_label", false}, + {"ABC-123", "dns_rfc1035_label", false}, + {"abc-abc", "dns_rfc1035_label", true}, + {"ABC-ABC", "dns_rfc1035_label", false}, + {"123-abc", "dns_rfc1035_label", false}, + {"", "dns_rfc1035_label", 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 dns_rfc1035_label failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d dns_rfc1035_label failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "dns_rfc1035_label" { + t.Fatalf("Index: %d dns_rfc1035_label failed Error: %s", i, errs) + } + } + } + } +} + func TestPostCodeByIso3166Alpha2(t *testing.T) { tests := map[string][]struct { value string From 8fe074c5461acf8e244969417ef84067da1cc001 Mon Sep 17 00:00:00 2001 From: Markus Tenghamn Date: Sun, 2 Jan 2022 02:30:17 +0100 Subject: [PATCH 04/22] Added ulid validation (#826) --- README.md | 1 + baked_in.go | 6 +++++ doc.go | 6 +++++ regexes.go | 2 ++ translations/en/en.go | 5 ++++ translations/en/en_test.go | 5 ++++ translations/es/es.go | 5 ++++ translations/es/es_test.go | 7 +++++- translations/fa/fa.go | 5 ++++ translations/fa/fa_test.go | 5 ++++ translations/fr/fr.go | 5 ++++ translations/fr/fr_test.go | 5 ++++ translations/id/id.go | 5 ++++ translations/id/id_test.go | 7 +++++- translations/ja/ja.go | 5 ++++ translations/ja/ja_test.go | 7 +++++- translations/nl/nl.go | 5 ++++ translations/nl/nl_test.go | 7 +++++- translations/pt/pt.go | 5 ++++ translations/pt/pt_test.go | 5 ++++ translations/pt_BR/pt_BR.go | 5 ++++ translations/pt_BR/pt_BR_test.go | 7 +++++- translations/ru/ru.go | 5 ++++ translations/ru/ru_test.go | 5 ++++ translations/tr/tr.go | 5 ++++ translations/tr/tr_test.go | 7 +++++- translations/zh/zh.go | 5 ++++ translations/zh/zh_test.go | 5 ++++ translations/zh_tw/zh_tw.go | 5 ++++ translations/zh_tw/zh_tw_test.go | 5 ++++ validator_test.go | 40 ++++++++++++++++++++++++++++++++ 31 files changed, 191 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f56cff1..c5bb81d 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,7 @@ Baked-in Validations | uuid5 | Universally Unique Identifier UUID v5 | | uuid5_rfc4122 | Universally Unique Identifier UUID v5 RFC4122 | | uuid_rfc4122 | Universally Unique Identifier UUID RFC4122 | +| ulid | Universally Unique Lexicographically Sortable Identifier ULID | ### Comparisons: | Tag | Description | diff --git a/baked_in.go b/baked_in.go index d80ec48..4b82abb 100644 --- a/baked_in.go +++ b/baked_in.go @@ -148,6 +148,7 @@ var ( "uuid3_rfc4122": isUUID3RFC4122, "uuid4_rfc4122": isUUID4RFC4122, "uuid5_rfc4122": isUUID5RFC4122, + "ulid": isULID, "ascii": isASCII, "printascii": isPrintableASCII, "multibyte": hasMultiByteCharacter, @@ -499,6 +500,11 @@ func isUUIDRFC4122(fl FieldLevel) bool { return uUIDRFC4122Regex.MatchString(fl.Field().String()) } +// isULID is the validation function for validating if the field's value is a valid ULID. +func isULID(fl FieldLevel) bool { + return uLIDRegex.MatchString(fl.Field().String()) +} + // isISBN is the validation function for validating if the field's value is a valid v10 or v13 ISBN. func isISBN(fl FieldLevel) bool { return isISBN10(fl) || isISBN13(fl) diff --git a/doc.go b/doc.go index a5d394a..1df38e7 100644 --- a/doc.go +++ b/doc.go @@ -1007,6 +1007,12 @@ This validates that a string value contains a valid version 5 UUID. Uppercase U Usage: uuid5 +Universally Unique Lexicographically Sortable Identifier ULID + +This validates that a string value contains a valid ULID value. + + Usage: ulid + ASCII This validates that a string value contains only ASCII characters. diff --git a/regexes.go b/regexes.go index 19e7c29..58d8766 100644 --- a/regexes.go +++ b/regexes.go @@ -29,6 +29,7 @@ const ( uUID4RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" uUID5RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-5[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" uUIDRFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + uLIDRegexString = "^[A-HJKMNP-TV-Z0-9]{26}$" aSCIIRegexString = "^[\x00-\x7F]*$" printableASCIIRegexString = "^[\x20-\x7E]*$" multibyteRegexString = "[^\x00-\x7F]" @@ -81,6 +82,7 @@ var ( uUID4RFC4122Regex = regexp.MustCompile(uUID4RFC4122RegexString) uUID5RFC4122Regex = regexp.MustCompile(uUID5RFC4122RegexString) uUIDRFC4122Regex = regexp.MustCompile(uUIDRFC4122RegexString) + uLIDRegex = regexp.MustCompile(uLIDRegexString) aSCIIRegex = regexp.MustCompile(aSCIIRegexString) printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString) multibyteRegex = regexp.MustCompile(multibyteRegexString) diff --git a/translations/en/en.go b/translations/en/en.go index b95f7dd..5ed76ae 100644 --- a/translations/en/en.go +++ b/translations/en/en.go @@ -1136,6 +1136,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be a valid version 5 UUID", override: false, }, + { + tag: "ulid", + translation: "{0} must be a valid ULID", + override: false, + }, { tag: "ascii", translation: "{0} must contain only ascii characters", diff --git a/translations/en/en_test.go b/translations/en/en_test.go index 0f20768..146c475 100644 --- a/translations/en/en_test.go +++ b/translations/en/en_test.go @@ -103,6 +103,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -323,6 +324,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 must be a valid version 5 UUID", }, + { + ns: "Test.ULID", + expected: "ULID must be a valid ULID", + }, { ns: "Test.ISBN", expected: "ISBN must be a valid ISBN number", diff --git a/translations/es/es.go b/translations/es/es.go index 2b87f10..2635408 100644 --- a/translations/es/es.go +++ b/translations/es/es.go @@ -1178,6 +1178,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} debe ser una versión válida 5 UUID", override: false, }, + { + tag: "ulid", + translation: "{0} debe ser un ULID válido", + override: false, + }, { tag: "ascii", translation: "{0} debe contener sólo caracteres ascii", diff --git a/translations/es/es_test.go b/translations/es/es_test.go index 070e3a8..814f2ab 100644 --- a/translations/es/es_test.go +++ b/translations/es/es_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" + . "github.com/go-playground/assert/v2" spanish "github.com/go-playground/locales/es" ut "github.com/go-playground/universal-translator" - . "github.com/go-playground/assert/v2" "github.com/go-playground/validator/v10" ) @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -312,6 +313,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 debe ser una versión válida 5 UUID", }, + { + ns: "Test.ULID", + expected: "ULID debe ser un ULID válido", + }, { ns: "Test.ISBN", expected: "ISBN debe ser un número ISBN válido", diff --git a/translations/fa/fa.go b/translations/fa/fa.go index fc21aa0..b7c100b 100644 --- a/translations/fa/fa.go +++ b/translations/fa/fa.go @@ -1136,6 +1136,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} باید یک UUID نسخه 5 معتبر باشد", override: false, }, + { + tag: "ulid", + translation: "{0} باید یک ULID معتبر باشد", + override: false, + }, { tag: "ascii", translation: "{0} باید فقط شامل کاراکترهای اسکی باشد", diff --git a/translations/fa/fa_test.go b/translations/fa/fa_test.go index 611f23e..1acb8cf 100644 --- a/translations/fa/fa_test.go +++ b/translations/fa/fa_test.go @@ -103,6 +103,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -322,6 +323,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 باید یک UUID نسخه 5 معتبر باشد", }, + { + ns: "Test.ULID", + expected: "ULID باید یک ULID معتبر باشد", + }, { ns: "Test.ISBN", expected: "ISBN باید یک شابک معتبر باشد", diff --git a/translations/fr/fr.go b/translations/fr/fr.go index ddebdf9..c455195 100644 --- a/translations/fr/fr.go +++ b/translations/fr/fr.go @@ -1173,6 +1173,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} doit être un UUID version 5 valid", override: false, }, + { + tag: "ulid", + translation: "{0} doit être une ULID valide", + override: false, + }, { tag: "ascii", translation: "{0} ne doit contenir que des caractères ascii", diff --git a/translations/fr/fr_test.go b/translations/fr/fr_test.go index c2c88d0..f1ba280 100644 --- a/translations/fr/fr_test.go +++ b/translations/fr/fr_test.go @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -306,6 +307,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 doit être un UUID version 5 valid", }, + { + ns: "Test.ULID", + expected: "ULID doit être une ULID valide", + }, { ns: "Test.ISBN", expected: "ISBN doit être un numéro ISBN valid", diff --git a/translations/id/id.go b/translations/id/id.go index d185dfc..08b6ad5 100644 --- a/translations/id/id.go +++ b/translations/id/id.go @@ -1173,6 +1173,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} harus berupa UUID versi 5 yang valid", override: false, }, + { + tag: "ulid", + translation: "{0} harus berupa ULID yang valid", + override: false, + }, { tag: "ascii", translation: "{0} hanya boleh berisi karakter ascii", diff --git a/translations/id/id_test.go b/translations/id/id_test.go index 83a72aa..5d80940 100644 --- a/translations/id/id_test.go +++ b/translations/id/id_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" + . "github.com/go-playground/assert/v2" indonesia "github.com/go-playground/locales/id" ut "github.com/go-playground/universal-translator" - . "github.com/go-playground/assert/v2" "github.com/go-playground/validator/v10" ) @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -306,6 +307,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 harus berupa UUID versi 5 yang valid", }, + { + ns: "Test.ULID", + expected: "ULID harus berupa ULID yang valid", + }, { ns: "Test.ISBN", expected: "ISBN harus berupa nomor ISBN yang valid", diff --git a/translations/ja/ja.go b/translations/ja/ja.go index 7b887c0..4502e2d 100644 --- a/translations/ja/ja.go +++ b/translations/ja/ja.go @@ -1229,6 +1229,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0}はバージョンが5の正しいUUIDでなければなりません", override: false, }, + { + tag: "ulid", + translation: "{0}は正しいULIDでなければなりません", + override: false, + }, { tag: "ascii", translation: "{0}はASCII文字のみを含まなければなりません", diff --git a/translations/ja/ja_test.go b/translations/ja/ja_test.go index eaa2743..393bcb3 100644 --- a/translations/ja/ja_test.go +++ b/translations/ja/ja_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" + . "github.com/go-playground/assert/v2" ja_locale "github.com/go-playground/locales/ja" ut "github.com/go-playground/universal-translator" - . "github.com/go-playground/assert/v2" "github.com/go-playground/validator/v10" ) @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -306,6 +307,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5はバージョンが5の正しいUUIDでなければなりません", }, + { + ns: "Test.ULID", + expected: "ULIDは正しいULIDでなければなりません", + }, { ns: "Test.ISBN", expected: "ISBNは正しいISBN番号でなければなりません", diff --git a/translations/nl/nl.go b/translations/nl/nl.go index a64815d..ca7c554 100644 --- a/translations/nl/nl.go +++ b/translations/nl/nl.go @@ -1173,6 +1173,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} moet een geldige versie 5 UUID zijn", override: false, }, + { + tag: "ulid", + translation: "{0} moet een geldige ULID zijn", + override: false, + }, { tag: "ascii", translation: "{0} mag alleen ascii karakters bevatten", diff --git a/translations/nl/nl_test.go b/translations/nl/nl_test.go index 55f5048..137e0c4 100644 --- a/translations/nl/nl_test.go +++ b/translations/nl/nl_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" + . "github.com/go-playground/assert/v2" english "github.com/go-playground/locales/en" ut "github.com/go-playground/universal-translator" - . "github.com/go-playground/assert/v2" "github.com/go-playground/validator/v10" ) @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -306,6 +307,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 moet een geldige versie 5 UUID zijn", }, + { + ns: "Test.ULID", + expected: "ULID moet een geldige ULID zijn", + }, { ns: "Test.ISBN", expected: "ISBN moet een geldig ISBN nummer zijn", diff --git a/translations/pt/pt.go b/translations/pt/pt.go index 9dbaf5f..4748331 100644 --- a/translations/pt/pt.go +++ b/translations/pt/pt.go @@ -1178,6 +1178,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} deve ser um UUID versão 5 válido", override: false, }, + { + tag: "ulid", + translation: "{0} deve ser um ULID válido", + override: false, + }, { tag: "ascii", translation: "{0} deve conter apenas caracteres ascii", diff --git a/translations/pt/pt_test.go b/translations/pt/pt_test.go index ceec15f..0422ab0 100644 --- a/translations/pt/pt_test.go +++ b/translations/pt/pt_test.go @@ -105,6 +105,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -321,6 +322,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 deve ser um UUID versão 5 válido", }, + { + ns: "Test.ULID", + expected: "ULID deve ser um ULID válido", + }, { ns: "Test.ISBN", expected: "ISBN deve ser um número de ISBN válido", diff --git a/translations/pt_BR/pt_BR.go b/translations/pt_BR/pt_BR.go index df4fc67..ef04496 100644 --- a/translations/pt_BR/pt_BR.go +++ b/translations/pt_BR/pt_BR.go @@ -1173,6 +1173,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} deve ser um UUID versão 5 válido", override: false, }, + { + tag: "ulid", + translation: "{0} deve ser uma ULID válida", + override: false, + }, { tag: "ascii", translation: "{0} deve conter apenas caracteres ascii", diff --git a/translations/pt_BR/pt_BR_test.go b/translations/pt_BR/pt_BR_test.go index 32125f6..c8a6482 100644 --- a/translations/pt_BR/pt_BR_test.go +++ b/translations/pt_BR/pt_BR_test.go @@ -4,10 +4,10 @@ import ( "testing" "time" + . "github.com/go-playground/assert/v2" brazilian_portuguese "github.com/go-playground/locales/pt_BR" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" - . "github.com/go-playground/assert/v2" ) func TestTranslations(t *testing.T) { @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -306,6 +307,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 deve ser um UUID versão 5 válido", }, + { + ns: "Test.ULID", + expected: "ULID deve ser uma ULID válida", + }, { ns: "Test.ISBN", expected: "ISBN deve ser um número ISBN válido", diff --git a/translations/ru/ru.go b/translations/ru/ru.go index 7a12bd8..25400c5 100644 --- a/translations/ru/ru.go +++ b/translations/ru/ru.go @@ -1291,6 +1291,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} должен быть UUID 5 версии", override: false, }, + { + tag: "ulid", + translation: "{0} должен быть ULID", + override: false, + }, { tag: "ascii", translation: "{0} должен содержать только ascii символы", diff --git a/translations/ru/ru_test.go b/translations/ru/ru_test.go index e1a1069..c74f0d5 100644 --- a/translations/ru/ru_test.go +++ b/translations/ru/ru_test.go @@ -120,6 +120,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -340,6 +341,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 должен быть UUID 5 версии", }, + { + ns: "Test.ULID", + expected: "ULID должен быть ULID", + }, { ns: "Test.ISBN", expected: "ISBN должен быть ISBN номером", diff --git a/translations/tr/tr.go b/translations/tr/tr.go index 2709e4b..2e88a20 100644 --- a/translations/tr/tr.go +++ b/translations/tr/tr.go @@ -1173,6 +1173,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} geçerli bir sürüm 5 UUID olmalıdır", override: false, }, + { + tag: "ulid", + translation: "{0} geçerli bir ULID olmalıdır", + override: false, + }, { tag: "ascii", translation: "{0} yalnızca ascii karakterler içermelidir", diff --git a/translations/tr/tr_test.go b/translations/tr/tr_test.go index 7cfa5a4..b72329e 100644 --- a/translations/tr/tr_test.go +++ b/translations/tr/tr_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" + . "github.com/go-playground/assert/v2" turkish "github.com/go-playground/locales/tr" ut "github.com/go-playground/universal-translator" - . "github.com/go-playground/assert/v2" "github.com/go-playground/validator/v10" ) @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -312,6 +313,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5 geçerli bir sürüm 5 UUID olmalıdır", }, + { + ns: "Test.ULID", + expected: "ULID geçerli bir ULID olmalıdır", + }, { ns: "Test.ISBN", expected: "ISBN geçerli bir ISBN numarası olmalıdır", diff --git a/translations/zh/zh.go b/translations/zh/zh.go index 5fff6b4..3dc7356 100644 --- a/translations/zh/zh.go +++ b/translations/zh/zh.go @@ -1225,6 +1225,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0}必须是一个有效的V5 UUID", override: false, }, + { + tag: "ulid", + translation: "{0}必须是一个有效的ULID", + override: false, + }, { tag: "ascii", translation: "{0}必须只包含ascii字符", diff --git a/translations/zh/zh_test.go b/translations/zh/zh_test.go index 1d73c65..cf76590 100644 --- a/translations/zh/zh_test.go +++ b/translations/zh/zh_test.go @@ -109,6 +109,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -327,6 +328,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5必须是一个有效的V5 UUID", }, + { + ns: "Test.ULID", + expected: "ULID必须是一个有效的ULID", + }, { ns: "Test.ISBN", expected: "ISBN必须是一个有效的ISBN编号", diff --git a/translations/zh_tw/zh_tw.go b/translations/zh_tw/zh_tw.go index 391a010..c39a8d7 100644 --- a/translations/zh_tw/zh_tw.go +++ b/translations/zh_tw/zh_tw.go @@ -1166,6 +1166,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0}必須是一個有效的V5 UUID", override: false, }, + { + tag: "ulid", + translation: "{0}必須是一個有效的ULID", + override: false, + }, { tag: "ascii", translation: "{0}必須只包含ascii字元", diff --git a/translations/zh_tw/zh_tw_test.go b/translations/zh_tw/zh_tw_test.go index cf60d1d..129c51a 100644 --- a/translations/zh_tw/zh_tw_test.go +++ b/translations/zh_tw/zh_tw_test.go @@ -104,6 +104,7 @@ func TestTranslations(t *testing.T) { UUID3 string `validate:"uuid3"` UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -309,6 +310,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UUID5", expected: "UUID5必須是一個有效的V5 UUID", }, + { + ns: "Test.ULID", + expected: "ULID必須是一個有效的ULID", + }, { ns: "Test.ISBN", expected: "ISBN必須是一個有效的ISBN編號", diff --git a/validator_test.go b/validator_test.go index 799efdf..c86f8da 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4269,6 +4269,46 @@ func TestUUIDRFC4122Validation(t *testing.T) { } } +func TestULIDValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"01BX5ZZKBKACT-V9WEVGEMMVRZ", false}, + {"01bx5zzkbkactav9wevgemmvrz", false}, + {"a987Fbc9-4bed-3078-cf07-9141ba07c9f3xxx", false}, + {"01BX5ZZKBKACTAV9WEVGEMMVRZABC", false}, + {"01BX5ZZKBKACTAV9WEVGEMMVRZABC", false}, + {"0IBX5ZZKBKACTAV9WEVGEMMVRZ", false}, + {"O1BX5ZZKBKACTAV9WEVGEMMVRZ", false}, + {"01BX5ZZKBKACTAVLWEVGEMMVRZ", false}, + {"01BX5ZZKBKACTAV9WEVGEMMVRZ", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "ulid") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ULID failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ULID failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "ulid" { + t.Fatalf("Index: %d ULID failed Error: %s", i, errs) + } + } + } + } +} + func TestISBNValidation(t *testing.T) { tests := []struct { param string From 06ec79d98790ec2bc480036f0bb9defdcc78fbc6 Mon Sep 17 00:00:00 2001 From: jtseng-godaddy <85597752+jtseng-godaddy@users.noreply.github.com> Date: Sat, 1 Jan 2022 17:38:59 -0800 Subject: [PATCH 05/22] Including regex version validation (#831) --- README.md | 1 + baked_in.go | 8 +++++ doc.go | 6 ++++ regexes.go | 2 ++ validator_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 95 insertions(+) diff --git a/README.md b/README.md index c5bb81d..db38321 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,7 @@ Baked-in Validations | uuid5 | Universally Unique Identifier UUID v5 | | uuid5_rfc4122 | Universally Unique Identifier UUID v5 RFC4122 | | uuid_rfc4122 | Universally Unique Identifier UUID RFC4122 | +| semver | Semantic Versioning 2.0.0 | | ulid | Universally Unique Lexicographically Sortable Identifier ULID | ### Comparisons: diff --git a/baked_in.go b/baked_in.go index 4b82abb..7868b66 100644 --- a/baked_in.go +++ b/baked_in.go @@ -199,6 +199,7 @@ var ( "postcode_iso3166_alpha2": isPostcodeByIso3166Alpha2, "postcode_iso3166_alpha2_field": isPostcodeByIso3166Alpha2Field, "bic": isIsoBicFormat, + "semver": isSemverFormat, "dns_rfc1035_label": isDnsRFC1035LabelFormat, } ) @@ -2421,6 +2422,13 @@ func isIsoBicFormat(fl FieldLevel) bool { return bicRegex.MatchString(bicString) } +// isSemverFormat is the validation function for validating if the current field's value is a valid semver version, defined in Semantic Versioning 2.0.0 +func isSemverFormat(fl FieldLevel) bool { + semverString := fl.Field().String() + + return semverRegex.MatchString(semverString) +} + // isDnsRFC1035LabelFormat is the validation function // for validating if the current field's value is // a valid dns RFC 1035 label, defined in RFC 1035. diff --git a/doc.go b/doc.go index 1df38e7..b284c37 100644 --- a/doc.go +++ b/doc.go @@ -1276,6 +1276,12 @@ More information on https://golang.org/pkg/time/#LoadLocation Usage: timezone +Semantic Version + +This validates that a string value is a valid semver version, defined in Semantic Versioning 2.0.0. +More information on https://semver.org/ + + Usage: semver Alias Validators and Tags diff --git a/regexes.go b/regexes.go index 58d8766..48e51d5 100644 --- a/regexes.go +++ b/regexes.go @@ -52,6 +52,7 @@ const ( jWTRegexString = "^[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]*$" splitParamsRegexString = `'[^']*'|\S+` bicRegexString = `^[A-Za-z]{6}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$` + semverRegexString = `^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$` // numbered capture groups https://semver.org/ dnsRegexStringRFC1035Label = "^[a-z]([-a-z0-9]*[a-z0-9]){0,62}$" ) @@ -105,5 +106,6 @@ var ( jWTRegex = regexp.MustCompile(jWTRegexString) splitParamsRegex = regexp.MustCompile(splitParamsRegexString) bicRegex = regexp.MustCompile(bicRegexString) + semverRegex = regexp.MustCompile(semverRegexString) dnsRegexRFC1035Label = regexp.MustCompile(dnsRegexStringRFC1035Label) ) diff --git a/validator_test.go b/validator_test.go index c86f8da..3730fb9 100644 --- a/validator_test.go +++ b/validator_test.go @@ -11383,6 +11383,84 @@ func TestBicIsoFormatValidation(t *testing.T) { } } +func TestSemverFormatValidation(t *testing.T) { + tests := []struct { + value string `validate:"semver"` + tag string + expected bool + }{ + {"1.2.3", "semver", true}, + {"10.20.30", "semver", true}, + {"1.1.2-prerelease+meta", "semver", true}, + {"1.1.2+meta", "semver", true}, + {"1.1.2+meta-valid", "semver", true}, + {"1.0.0-alpha", "semver", true}, + {"1.0.0-alpha.1", "semver", true}, + {"1.0.0-alpha.beta", "semver", true}, + {"1.0.0-alpha.beta.1", "semver", true}, + {"1.0.0-alpha0.valid", "semver", true}, + {"1.0.0-alpha.0valid", "semver", true}, + {"1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay", "semver", true}, + {"1.0.0-rc.1+build.1", "semver", true}, + {"1.0.0-rc.1+build.123", "semver", true}, + {"1.2.3-beta", "semver", true}, + {"1.2.3-DEV-SNAPSHOT", "semver", true}, + {"1.2.3-SNAPSHOT-123", "semver", true}, + {"2.0.0+build.1848", "semver", true}, + {"2.0.1-alpha.1227", "semver", true}, + {"1.0.0-alpha+beta", "semver", true}, + {"1.2.3----RC-SNAPSHOT.12.9.1--.12+788", "semver", true}, + {"1.2.3----R-S.12.9.1--.12+meta", "semver", true}, + {"1.2.3----RC-SNAPSHOT.12.9.1--.12", "semver", true}, + {"1.0.0+0.build.1-rc.10000aaa-kk-0.1", "semver", true}, + {"99999999999999999999999.999999999999999999.99999999999999999", "semver", true}, + {"1.0.0-0A.is.legal", "semver", true}, + {"1", "semver", false}, + {"1.2", "semver", false}, + {"1.2.3-0123", "semver", false}, + {"1.2.3-0123.0123", "semver", false}, + {"1.1.2+.123", "semver", false}, + {"+invalid", "semver", false}, + {"-invalid", "semver", false}, + {"-invalid+invalid", "semver", false}, + {"alpha", "semver", false}, + {"alpha.beta.1", "semver", false}, + {"alpha.1", "semver", false}, + {"1.0.0-alpha_beta", "semver", false}, + {"1.0.0-alpha_beta", "semver", false}, + {"1.0.0-alpha...1", "semver", false}, + {"01.1.1", "semver", false}, + {"1.01.1", "semver", false}, + {"1.1.01", "semver", false}, + {"1.2", "semver", false}, + {"1.2.Dev", "semver", false}, + {"1.2.3.Dev", "semver", false}, + {"1.2-SNAPSHOT", "semver", 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 semver failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d semver failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "semver" { + t.Fatalf("Index: %d semver failed Error: %s", i, errs) + } + } + } + } +} + func TestRFC1035LabelFormatValidation(t *testing.T) { tests := []struct { value string `validate:"dns_rfc1035_label"` From 88976f39f600bb3979de1ae5efdfd9cff2cf10f1 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sat, 1 Jan 2022 17:40:00 -0800 Subject: [PATCH 06/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db38321..5c42ee4 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-10.9.0-green.svg) +![Project status](https://img.shields.io/badge/version-10.10.0-green.svg) [![Build Status](https://travis-ci.org/go-playground/validator.svg?branch=master)](https://travis-ci.org/go-playground/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-playground/validator?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) From dd2de9c1f7b7451245334310bc96fc133a02f07f Mon Sep 17 00:00:00 2001 From: Anmar85 Date: Mon, 7 Mar 2022 20:26:06 -0500 Subject: [PATCH 07/22] update /x/crypto to resolve CVE-2021-43565 (#904) Co-authored-by: Anmar85 --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index ddecff6..1e9ffee 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/leodido/go-urn v1.2.1 github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/stretchr/testify v1.7.0 // indirect - golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 + golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect golang.org/x/text v0.3.6 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 5750093..7e00541 100644 --- a/go.sum +++ b/go.sum @@ -28,15 +28,15 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 3e49fe4eb883c0e36e5f08c2611328e814ee11ce Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 8 Mar 2022 02:29:01 +0100 Subject: [PATCH 08/22] bump golang.org/x/text to fix CVE-2021-38561 (#881) Co-authored-by: Dean Karn --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1e9ffee..e281ec0 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/stretchr/testify v1.7.0 // indirect golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect - golang.org/x/text v0.3.6 + golang.org/x/text v0.3.7 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index 7e00541..a1c43c3 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 58d5778b183e89cc374ca4ebbf06da1eed088a63 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Mon, 7 Mar 2022 17:32:10 -0800 Subject: [PATCH 09/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c42ee4..6712e95 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-10.10.0-green.svg) +![Project status](https://img.shields.io/badge/version-10.10.1-green.svg) [![Build Status](https://travis-ci.org/go-playground/validator.svg?branch=master)](https://travis-ci.org/go-playground/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-playground/validator?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) From c0195b2b40b26d8d7f435f109bf827d52ec145e3 Mon Sep 17 00:00:00 2001 From: Jacob Hochstetler Date: Sun, 1 May 2022 09:59:09 -0500 Subject: [PATCH 10/22] added excluded_if/excluded_unless tags + tests (#847) --- README.md | 2 + baked_in.go | 33 ++++++++++ doc.go | 34 ++++++++++ validator_instance.go | 4 +- validator_test.go | 141 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 212 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6712e95..f25649a 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,8 @@ Baked-in Validations | required_with_all | Required With All | | required_without | Required Without | | required_without_all | Required Without All | +| excluded_if | Excluded If | +| excluded_unless | Excluded Unless | | excluded_with | Excluded With | | excluded_with_all | Excluded With All | | excluded_without | Excluded Without | diff --git a/baked_in.go b/baked_in.go index 7868b66..36a410b 100644 --- a/baked_in.go +++ b/baked_in.go @@ -75,6 +75,8 @@ var ( "required_with_all": requiredWithAll, "required_without": requiredWithout, "required_without_all": requiredWithoutAll, + "excluded_if": excludedIf, + "excluded_unless": excludedUnless, "excluded_with": excludedWith, "excluded_with_all": excludedWithAll, "excluded_without": excludedWithout, @@ -1542,6 +1544,22 @@ func requiredIf(fl FieldLevel) bool { return hasValue(fl) } +// excludedIf is the validation function +// The field under validation must not be present or is empty only if all the other specified fields are equal to the value following with the specified field. +func excludedIf(fl FieldLevel) bool { + params := parseOneOfParam2(fl.Param()) + if len(params)%2 != 0 { + panic(fmt.Sprintf("Bad param number for excluded_if %s", fl.FieldName())) + } + + for i := 0; i < len(params); i += 2 { + if !requireCheckFieldValue(fl, params[i], params[i+1], false) { + return false + } + } + return true +} + // requiredUnless is the validation function // The field under validation must be present and not empty only unless all the other specified fields are equal to the value following with the specified field. func requiredUnless(fl FieldLevel) bool { @@ -1558,6 +1576,21 @@ func requiredUnless(fl FieldLevel) bool { return hasValue(fl) } +// excludedUnless is the validation function +// The field under validation must not be present or is empty unless all the other specified fields are equal to the value following with the specified field. +func excludedUnless(fl FieldLevel) bool { + params := parseOneOfParam2(fl.Param()) + if len(params)%2 != 0 { + panic(fmt.Sprintf("Bad param number for excluded_unless %s", fl.FieldName())) + } + for i := 0; i < len(params); i += 2 { + if !requireCheckFieldValue(fl, params[i], params[i+1], false) { + return true + } + } + return !hasValue(fl) +} + // excludedWith is the validation function // The field under validation must not be present or is empty if any of the other specified fields are present. func excludedWith(fl FieldLevel) bool { diff --git a/doc.go b/doc.go index b284c37..6cf6208 100644 --- a/doc.go +++ b/doc.go @@ -349,6 +349,40 @@ Example: // require the field if the Field1 and Field2 is not present: Usage: required_without_all=Field1 Field2 +Excluded If + +The field under validation must not be present or not empty only if all +the other specified fields are equal to the value following the specified +field. For strings ensures value is not "". For slices, maps, pointers, +interfaces, channels and functions ensures the value is not nil. + + Usage: excluded_if + +Examples: + + // exclude the field if the Field1 is equal to the parameter given: + Usage: excluded_if=Field1 foobar + + // exclude the field if the Field1 and Field2 is equal to the value respectively: + Usage: excluded_if=Field1 foo Field2 bar + +Excluded Unless + +The field under validation must not be present or empty unless all +the other specified fields are equal to the value following the specified +field. For strings ensures value is not "". For slices, maps, pointers, +interfaces, channels and functions ensures the value is not nil. + + Usage: excluded_unless + +Examples: + + // exclude the field unless the Field1 is equal to the parameter given: + Usage: excluded_unless=Field1 foobar + + // exclude the field unless the Field1 and Field2 is equal to the value respectively: + Usage: excluded_unless=Field1 foo Field2 bar + Is Default This validates that the value is the default value and is almost the diff --git a/validator_instance.go b/validator_instance.go index 973964f..6d66060 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -33,6 +33,8 @@ const ( excludedWithoutTag = "excluded_without" excludedWithTag = "excluded_with" excludedWithAllTag = "excluded_with_all" + excludedIfTag = "excluded_if" + excludedUnlessTag = "excluded_unless" skipValidationTag = "-" diveTag = "dive" keysTag = "keys" @@ -120,7 +122,7 @@ func New() *Validate { switch k { // these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour case requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag, - excludedWithTag, excludedWithAllTag, excludedWithoutTag, excludedWithoutAllTag: + excludedIfTag, excludedUnlessTag, excludedWithTag, excludedWithAllTag, excludedWithoutTag, excludedWithoutAllTag: _ = v.registerValidation(k, wrapFunc(val), true, true) default: // no need to error check here, baked in will always be valid diff --git a/validator_test.go b/validator_test.go index 3730fb9..4da0de6 100644 --- a/validator_test.go +++ b/validator_test.go @@ -10675,6 +10675,145 @@ func TestRequiredWithoutAll(t *testing.T) { AssertError(t, errs, "Field2", "Field2", "Field2", "Field2", "required_without_all") } +func TestExcludedIf(t *testing.T) { + validate := New() + type Inner struct { + Field *string + } + + test1 := struct { + FieldE string `validate:"omitempty" json:"field_e"` + FieldER *string `validate:"excluded_if=FieldE test" json:"field_er"` + }{ + FieldE: "test", + } + errs := validate.Struct(test1) + Equal(t, errs, nil) + + test2 := struct { + FieldE string `validate:"omitempty" json:"field_e"` + FieldER string `validate:"excluded_if=FieldE test" json:"field_er"` + }{ + FieldE: "notest", + } + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + ve := errs.(ValidationErrors) + Equal(t, len(ve), 1) + AssertError(t, errs, "FieldER", "FieldER", "FieldER", "FieldER", "excluded_if") + + shouldError := "shouldError" + test3 := struct { + Inner *Inner + FieldE string `validate:"omitempty" json:"field_e"` + Field1 int `validate:"excluded_if=Inner.Field test" json:"field_1"` + }{ + Inner: &Inner{Field: &shouldError}, + } + errs = validate.Struct(test3) + NotEqual(t, errs, nil) + ve = errs.(ValidationErrors) + Equal(t, len(ve), 1) + AssertError(t, errs, "Field1", "Field1", "Field1", "Field1", "excluded_if") + + shouldPass := "test" + test4 := struct { + Inner *Inner + FieldE string `validate:"omitempty" json:"field_e"` + Field1 int `validate:"excluded_if=Inner.Field test" json:"field_1"` + }{ + Inner: &Inner{Field: &shouldPass}, + } + errs = validate.Struct(test4) + Equal(t, errs, nil) + + // Checks number of params in struct tag is correct + defer func() { + if r := recover(); r == nil { + t.Errorf("panicTest should have panicked!") + } + }() + fieldVal := "panicTest" + panicTest := struct { + Inner *Inner + Field1 string `validate:"excluded_if=Inner.Field" json:"field_1"` + }{ + Inner: &Inner{Field: &fieldVal}, + } + _ = validate.Struct(panicTest) +} + +func TestExcludedUnless(t *testing.T) { + validate := New() + type Inner struct { + Field *string + } + + fieldVal := "test" + test := struct { + FieldE string `validate:"omitempty" json:"field_e"` + FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"` + }{ + FieldE: "notest", + FieldER: "filled", + } + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + FieldE string `validate:"omitempty" json:"field_e"` + FieldER string `validate:"excluded_unless=FieldE test" json:"field_er"` + }{ + FieldE: "test", + FieldER: "filled", + } + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + ve := errs.(ValidationErrors) + Equal(t, len(ve), 1) + AssertError(t, errs, "FieldER", "FieldER", "FieldER", "FieldER", "excluded_unless") + + shouldError := "test" + test3 := struct { + Inner *Inner + Field1 string `validate:"excluded_unless=Inner.Field test" json:"field_1"` + }{ + Inner: &Inner{Field: &shouldError}, + Field1: "filled", + } + errs = validate.Struct(test3) + NotEqual(t, errs, nil) + ve = errs.(ValidationErrors) + Equal(t, len(ve), 1) + AssertError(t, errs, "Field1", "Field1", "Field1", "Field1", "excluded_unless") + + shouldPass := "shouldPass" + test4 := struct { + Inner *Inner + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_unless=Inner.Field test" json:"field_1"` + }{ + Inner: &Inner{Field: &shouldPass}, + Field1: "filled", + } + errs = validate.Struct(test4) + Equal(t, errs, nil) + + // Checks number of params in struct tag is correct + defer func() { + if r := recover(); r == nil { + t.Errorf("panicTest should have panicked!") + } + }() + panicTest := struct { + Inner *Inner + Field1 string `validate:"excluded_unless=Inner.Field" json:"field_1"` + }{ + Inner: &Inner{Field: &fieldVal}, + } + _ = validate.Struct(panicTest) +} + func TestLookup(t *testing.T) { type Lookup struct { FieldA *string `json:"fieldA,omitempty" validate:"required_without=FieldB"` @@ -11460,7 +11599,7 @@ func TestSemverFormatValidation(t *testing.T) { } } } - + func TestRFC1035LabelFormatValidation(t *testing.T) { tests := []struct { value string `validate:"dns_rfc1035_label"` From bb30072b4887f1e059af1573445847b3abd6db4d Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sun, 1 May 2022 08:09:26 -0700 Subject: [PATCH 11/22] update ci config --- .github/workflows/workflow.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index d1ca1e8..8e82f43 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -8,7 +8,7 @@ jobs: test: strategy: matrix: - go-version: [1.15.x, 1.16.x] + go-version: [1.17.x, 1.18.x] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -32,7 +32,7 @@ jobs: run: go test -race -covermode=atomic -coverprofile="profile.cov" ./... - name: Send Coverage - if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.16.x' + if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.18.x' uses: shogo82148/actions-goveralls@v1 with: path-to-profile: profile.cov @@ -45,4 +45,4 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: - version: v1.41.1 + version: v1.45.2 From dd2857a4cb6c53af5bf6944cc15fd04c865d28ae Mon Sep 17 00:00:00 2001 From: alessmar Date: Sun, 1 May 2022 17:17:59 +0200 Subject: [PATCH 12/22] Credit card validation (#924) --- .github/workflows/workflow.yml | 7 ++++-- README.md | 1 + baked_in.go | 39 ++++++++++++++++++++++++++++++++++ doc.go | 6 ++++++ validator_test.go | 38 +++++++++++++++++++++++++++++++++ 5 files changed, 89 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 8e82f43..d6bfd55 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -41,8 +41,11 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/setup-go@v3 + with: + go-version: 1.16 + - uses: actions/checkout@v3 - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v3 with: version: v1.45.2 diff --git a/README.md b/README.md index f25649a..1c89266 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,7 @@ Baked-in Validations | bcp47_language_tag | Language tag (BCP 47) | | btc_addr | Bitcoin Address | | btc_addr_bech32 | Bitcoin Bech32 Address (segwit) | +| credit_card | Credit Card Number | | datetime | Datetime | | e164 | e164 formatted phone number | | email | E-mail String diff --git a/baked_in.go b/baked_in.go index 36a410b..7833f24 100644 --- a/baked_in.go +++ b/baked_in.go @@ -203,6 +203,7 @@ var ( "bic": isIsoBicFormat, "semver": isSemverFormat, "dns_rfc1035_label": isDnsRFC1035LabelFormat, + "credit_card": isCreditCard, } ) @@ -2469,3 +2470,41 @@ func isDnsRFC1035LabelFormat(fl FieldLevel) bool { val := fl.Field().String() return dnsRegexRFC1035Label.MatchString(val) } + +// isCreditCard is the validation function for validating if the current field's value is a valid credit card number +func isCreditCard(fl FieldLevel) bool { + val := fl.Field().String() + var creditCard bytes.Buffer + segments := strings.Split(val, " ") + for _, segment := range segments { + if len(segment) < 3 { + return false + } + creditCard.WriteString(segment) + } + + ccDigits := strings.Split(creditCard.String(), "") + size := len(ccDigits) + if size < 12 || size > 19 { + return false + } + + sum := 0 + for i, digit := range ccDigits { + value, err := strconv.Atoi(digit) + if err != nil { + return false + } + if size%2 == 0 && i%2 == 0 || size%2 == 1 && i%2 == 1 { + v := value * 2 + if v >= 10 { + sum += 1 + (v % 10) + } else { + sum += v + } + } else { + sum += value + } + } + return (sum % 10) == 0 +} diff --git a/doc.go b/doc.go index 6cf6208..7341c67 100644 --- a/doc.go +++ b/doc.go @@ -1317,6 +1317,12 @@ More information on https://semver.org/ Usage: semver +Credit Card + +This validates that a string value contains a valid credit card number using Luhn algoritm. + + Usage: credit_card + Alias Validators and Tags NOTE: When returning an error, the tag returned in "FieldError" will be diff --git a/validator_test.go b/validator_test.go index 4da0de6..a5b1fa2 100644 --- a/validator_test.go +++ b/validator_test.go @@ -11750,3 +11750,41 @@ func TestPostCodeByIso3166Alpha2Field_InvalidKind(t *testing.T) { _ = New().Struct(test{"ABC", 123, false}) t.Errorf("Didn't panic as expected") } + +func TestCreditCardFormatValidation(t *testing.T) { + tests := []struct { + value string `validate:"credit_card"` + tag string + expected bool + }{ + {"586824160825533338", "credit_card", true}, + {"586824160825533328", "credit_card", false}, + {"4624748233249780", "credit_card", true}, + {"4624748233349780", "credit_card", false}, + {"378282246310005", "credit_card", true}, + {"378282146310005", "credit_card", false}, + {"4624 7482 3324 9780", "credit_card", true}, + {"4624 7482 3324 9780", "credit_card", 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 credit_card failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d credit_card failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "credit_card" { + t.Fatalf("Index: %d credit_card failed Error: %s", i, errs) + } + } + } + } +} From bc9f9dd2ebe8fd783fe5e8ad1ab694137f48ea4b Mon Sep 17 00:00:00 2001 From: NgeKaworu Date: Sun, 1 May 2022 23:23:51 +0800 Subject: [PATCH 13/22] Update zh.go (#856) --- translations/zh/zh.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/translations/zh/zh.go b/translations/zh/zh.go index 3dc7356..80165d0 100644 --- a/translations/zh/zh.go +++ b/translations/zh/zh.go @@ -29,6 +29,36 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0}为必填字段", override: false, }, + { + tag: "required_if", + translation: "{0}为必填字段", + override: false, + }, + { + tag: "required_unless", + translation: "{0}为必填字段", + override: false, + }, + { + tag: "required_with", + translation: "{0}为必填字段", + override: false, + }, + { + tag: "required_with_all", + translation: "{0}为必填字段", + override: false, + }, + { + tag: "required_without", + translation: "{0}为必填字段", + override: false, + }, + { + tag: "required_without_all", + translation: "{0}为必填字段", + override: false, + }, { tag: "len", customRegisFunc: func(ut ut.Translator) (err error) { From 24b5175a342e0953049e1c6fe13c98dca9533fe0 Mon Sep 17 00:00:00 2001 From: Sol <39742391+SolKuczala@users.noreply.github.com> Date: Sun, 1 May 2022 16:26:35 +0100 Subject: [PATCH 14/22] Fix typo (#891) --- validator_instance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validator_instance.go b/validator_instance.go index 6d66060..8917d8c 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -171,7 +171,7 @@ func (v Validate) ValidateMapCtx(ctx context.Context, data map[string]interface{ return errs } -// ValidateMap validates map data form a map of tags +// ValidateMap validates map data from a map of tags func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{} { return v.ValidateMapCtx(context.Background(), data, rules) } From aea8782168f5ea921dfab6569268a4d6b4274f73 Mon Sep 17 00:00:00 2001 From: "hehe.bu" Date: Sun, 1 May 2022 23:27:37 +0800 Subject: [PATCH 15/22] fix ja typos (#898) --- translations/ja/ja.go | 8 ++++---- translations/ja/ja_test.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/translations/ja/ja.go b/translations/ja/ja.go index 4502e2d..1cc67a4 100644 --- a/translations/ja/ja.go +++ b/translations/ja/ja.go @@ -136,7 +136,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return } - if err = ut.Add("min-number", "{0}は{1}かより大きくなければなりません", false); err != nil { + if err = ut.Add("min-number", "{0}は{1}より大きくなければなりません", false); err != nil { return } @@ -227,7 +227,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return } - if err = ut.Add("max-number", "{0}は{1}かより小さくなければなりません", false); err != nil { + if err = ut.Add("max-number", "{0}は{1}より小さくなければなりません", false); err != nil { return } @@ -525,7 +525,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return } - if err = ut.Add("lte-number", "{0}は{1}かより小さくなければなりません", false); err != nil { + if err = ut.Add("lte-number", "{0}は{1}より小さくなければなりません", false); err != nil { return } @@ -765,7 +765,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return } - if err = ut.Add("gte-number", "{0}は{1}かより大きくなければなりません", false); err != nil { + if err = ut.Add("gte-number", "{0}は{1}より大きくなければなりません", false); err != nil { return } diff --git a/translations/ja/ja_test.go b/translations/ja/ja_test.go index 393bcb3..7950251 100644 --- a/translations/ja/ja_test.go +++ b/translations/ja/ja_test.go @@ -453,7 +453,7 @@ func TestTranslations(t *testing.T) { }, { ns: "Test.GteNumber", - expected: "GteNumberは5.56かより大きくなければなりません", + expected: "GteNumberは5.56より大きくなければなりません", }, { ns: "Test.GteMultiple", @@ -485,7 +485,7 @@ func TestTranslations(t *testing.T) { }, { ns: "Test.LteNumber", - expected: "LteNumberは5.56かより小さくなければなりません", + expected: "LteNumberは5.56より小さくなければなりません", }, { ns: "Test.LteMultiple", @@ -541,7 +541,7 @@ func TestTranslations(t *testing.T) { }, { ns: "Test.MaxNumber", - expected: "MaxNumberは1,113.00かより小さくなければなりません", + expected: "MaxNumberは1,113.00より小さくなければなりません", }, { ns: "Test.MaxMultiple", @@ -553,7 +553,7 @@ func TestTranslations(t *testing.T) { }, { ns: "Test.MinNumber", - expected: "MinNumberは1,113.00かより大きくなければなりません", + expected: "MinNumberは1,113.00より大きくなければなりません", }, { ns: "Test.MinMultiple", From f2d3ff7f980910456594bee6f282c1d4fe6213dc Mon Sep 17 00:00:00 2001 From: Stefan Dillenburg Date: Sun, 1 May 2022 17:32:50 +0200 Subject: [PATCH 16/22] fix: Remove underscore from RFC-1123 based regex (#912) RFC-1123 is based on RFC-952, which doesn't allow underscores. RFC-1123 must be therefore implemented with the same constraint to disallow underscores in host names. --- regexes.go | 12 ++++++------ validator_test.go | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/regexes.go b/regexes.go index 48e51d5..a8668f8 100644 --- a/regexes.go +++ b/regexes.go @@ -37,12 +37,12 @@ const ( latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" 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-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952 - hostnameRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*?$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 - fqdnRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9_-]{0,62})(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*?(\.[a-zA-Z]{1}[a-zA-Z0-9]{0,62})\.?$` // same as hostnameRegexStringRFC1123 but must contain a non numerical TLD (possibly ending with '.') - btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address - btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 - btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 + hostnameRegexStringRFC952 = `^[a-zA-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952 + hostnameRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62}){1}(\.[a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})*?$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 + fqdnRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})(\.[a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})*?(\.[a-zA-Z]{1}[a-zA-Z0-9]{0,62})\.?$` // same as hostnameRegexStringRFC1123 but must contain a non numerical TLD (possibly ending with '.') + btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address + btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 + btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 ethAddressRegexString = `^0x[0-9a-fA-F]{40}$` ethAddressUpperRegexString = `^0x[0-9A-F]{40}$` ethAddressLowerRegexString = `^0x[0-9a-f]{40}$` diff --git a/validator_test.go b/validator_test.go index a5b1fa2..6aee559 100644 --- a/validator_test.go +++ b/validator_test.go @@ -9151,6 +9151,7 @@ func TestHostnameRFC1123Validation(t *testing.T) { {"test.example24.com.", false}, {"test24.example24.com.", false}, {"example.", false}, + {"test_example", false}, {"192.168.0.1", true}, {"email@example.com", false}, {"2001:cdba:0000:0000:0000:0000:3257:9652", false}, @@ -9199,6 +9200,7 @@ func TestHostnameRFC1123AliasValidation(t *testing.T) { {"test.example24.com.", false}, {"test24.example24.com.", false}, {"example.", false}, + {"test_example", false}, {"192.168.0.1", true}, {"email@example.com", false}, {"2001:cdba:0000:0000:0000:0000:3257:9652", false}, From af72f63d39e2f2ce4176386412f5dd8f6d3e4ecf Mon Sep 17 00:00:00 2001 From: XIE Long Date: Sun, 1 May 2022 23:40:00 +0800 Subject: [PATCH 17/22] Result is wrong while there are multiple group of OR operators #910 (#911) --- validator.go | 9 +++++++++ validator_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/validator.go b/validator.go index 2a4fad0..c2e0358 100644 --- a/validator.go +++ b/validator.go @@ -355,6 +355,10 @@ OUTER: v.ct = ct if ct.fn(ctx, v) { + if ct.isBlockEnd { + ct = ct.next + continue OUTER + } // drain rest of the 'or' values, then continue or leave for { @@ -368,6 +372,11 @@ OUTER: if ct.typeof != typeOr { continue OUTER } + + if ct.isBlockEnd { + ct = ct.next + continue OUTER + } } } diff --git a/validator_test.go b/validator_test.go index 6aee559..b7bcec2 100644 --- a/validator_test.go +++ b/validator_test.go @@ -11790,3 +11790,27 @@ func TestCreditCardFormatValidation(t *testing.T) { } } } + +func TestMultiOrOperatorGroup(t *testing.T) { + tests := []struct { + Value int `validate:"eq=1|gte=5,eq=1|lt=7"` + expected bool + }{ + {1, true}, {2, false}, {5, true}, {6, true}, {8, false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Struct(test) + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d multi_group_of_OR_operators failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d multi_group_of_OR_operators should have errs", i) + } + } + } + } From 090afeb8aca552e3a3f37ca640e3fb85161a3622 Mon Sep 17 00:00:00 2001 From: Massimo Costa Date: Sun, 1 May 2022 17:44:10 +0200 Subject: [PATCH 18/22] enhancement: add italian translation (#914) --- translations/it/it.go | 1244 ++++++++++++++++++++++++++++++++++++ translations/it/it_test.go | 724 +++++++++++++++++++++ 2 files changed, 1968 insertions(+) create mode 100644 translations/it/it.go create mode 100644 translations/it/it_test.go diff --git a/translations/it/it.go b/translations/it/it.go new file mode 100644 index 0000000..8abd67f --- /dev/null +++ b/translations/it/it.go @@ -0,0 +1,1244 @@ +package en + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "github.com/go-playground/locales" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" +) + +// RegisterDefaultTranslations registers a set of default translations +// for all built in tag's in validator; you may add your own as desired. +func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (err error) { + translations := []struct { + tag string + translation string + override bool + customRegisFunc validator.RegisterTranslationsFunc + customTransFunc validator.TranslationFunc + }{ + { + tag: "required", + translation: "{0} è un campo obbligatorio", + override: false, + }, + { + tag: "len", + customRegisFunc: func(ut ut.Translator) (err error) { + if err = ut.Add("len-string", "{0} deve essere lungo {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} carattere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} caratteri", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("len-number", "{0} deve essere uguale a {1}", false); err != nil { + return + } + + if err = ut.Add("len-items", "{0} deve contenere {1}", false); err != nil { + return + } + if err = ut.AddCardinal("len-items-item", "{0} elemento", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-items-item", "{0} elementi", locales.PluralRuleOther, false); err != nil { + return + } + + return + }, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + var err error + var t string + + var digits uint64 + var kind reflect.Kind + + if idx := strings.Index(fe.Param(), "."); idx != -1 { + digits = uint64(len(fe.Param()[idx+1:])) + } + + f64, err := strconv.ParseFloat(fe.Param(), 64) + if err != nil { + goto END + } + + kind = fe.Kind() + if kind == reflect.Ptr { + kind = fe.Type().Elem().Kind() + } + + switch kind { + case reflect.String: + + var c string + + c, err = ut.C("len-string-character", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("len-string", fe.Field(), c) + + case reflect.Slice, reflect.Map, reflect.Array: + var c string + + c, err = ut.C("len-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("len-items", fe.Field(), c) + + default: + t, err = ut.T("len-number", fe.Field(), ut.FmtNumber(f64, digits)) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "min", + customRegisFunc: func(ut ut.Translator) (err error) { + if err = ut.Add("min-string", "{0} deve essere lungo almeno {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} carattere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} caratteri", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("min-number", "{0} deve essere maggiore o uguale a {1}", false); err != nil { + return + } + + if err = ut.Add("min-items", "{0} deve contenere almeno {1}", false); err != nil { + return + } + if err = ut.AddCardinal("min-items-item", "{0} elemento", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-items-item", "{0} elementi", locales.PluralRuleOther, false); err != nil { + return + } + + return + }, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + var err error + var t string + + var digits uint64 + var kind reflect.Kind + + if idx := strings.Index(fe.Param(), "."); idx != -1 { + digits = uint64(len(fe.Param()[idx+1:])) + } + + f64, err := strconv.ParseFloat(fe.Param(), 64) + if err != nil { + goto END + } + + kind = fe.Kind() + if kind == reflect.Ptr { + kind = fe.Type().Elem().Kind() + } + + switch kind { + case reflect.String: + + var c string + + c, err = ut.C("min-string-character", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("min-string", fe.Field(), c) + + case reflect.Slice, reflect.Map, reflect.Array: + var c string + + c, err = ut.C("min-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("min-items", fe.Field(), c) + + default: + t, err = ut.T("min-number", fe.Field(), ut.FmtNumber(f64, digits)) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "max", + customRegisFunc: func(ut ut.Translator) (err error) { + if err = ut.Add("max-string", "{0} deve essere lungo al massimo {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} carattere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} caratteri", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("max-number", "{0} deve essere minore o uguale a {1}", false); err != nil { + return + } + + if err = ut.Add("max-items", "{0} deve contenere al massimo {1}", false); err != nil { + return + } + if err = ut.AddCardinal("max-items-item", "{0} elemento", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-items-item", "{0} elementi", locales.PluralRuleOther, false); err != nil { + return + } + + return + }, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + var err error + var t string + + var digits uint64 + var kind reflect.Kind + + if idx := strings.Index(fe.Param(), "."); idx != -1 { + digits = uint64(len(fe.Param()[idx+1:])) + } + + f64, err := strconv.ParseFloat(fe.Param(), 64) + if err != nil { + goto END + } + + kind = fe.Kind() + if kind == reflect.Ptr { + kind = fe.Type().Elem().Kind() + } + + switch kind { + case reflect.String: + + var c string + + c, err = ut.C("max-string-character", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("max-string", fe.Field(), c) + + case reflect.Slice, reflect.Map, reflect.Array: + var c string + + c, err = ut.C("max-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("max-items", fe.Field(), c) + + default: + t, err = ut.T("max-number", fe.Field(), ut.FmtNumber(f64, digits)) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "eq", + translation: "{0} non è uguale a {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "ne", + translation: "{0} deve essere diverso da {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "lt", + customRegisFunc: func(ut ut.Translator) (err error) { + if err = ut.Add("lt-string", "{0} deve essere lungo meno di {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} carattere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} caratteri", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-number", "{0} deve essere minore di {1}", false); err != nil { + return + } + + if err = ut.Add("lt-items", "{0} deve contenere meno di {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} elemento", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} elementi", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-datetime", "{0} deve essere precedente alla Data/Ora corrente", false); err != nil { + return + } + + return + }, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + var err error + var t string + var f64 float64 + var digits uint64 + var kind reflect.Kind + + fn := func() (err error) { + if idx := strings.Index(fe.Param(), "."); idx != -1 { + digits = uint64(len(fe.Param()[idx+1:])) + } + + f64, err = strconv.ParseFloat(fe.Param(), 64) + + return + } + + kind = fe.Kind() + if kind == reflect.Ptr { + kind = fe.Type().Elem().Kind() + } + + switch kind { + case reflect.String: + + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("lt-string-character", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("lt-string", fe.Field(), c) + + case reflect.Slice, reflect.Map, reflect.Array: + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("lt-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("lt-items", fe.Field(), c) + + case reflect.Struct: + if fe.Type() != reflect.TypeOf(time.Time{}) { + err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag()) + goto END + } + + t, err = ut.T("lt-datetime", fe.Field()) + + default: + err = fn() + if err != nil { + goto END + } + + t, err = ut.T("lt-number", fe.Field(), ut.FmtNumber(f64, digits)) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "lte", + customRegisFunc: func(ut ut.Translator) (err error) { + if err = ut.Add("lte-string", "{0} deve essere lungo al massimo {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} carattere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} caratteri", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-number", "{0} deve essere minore o uguale a {1}", false); err != nil { + return + } + + if err = ut.Add("lte-items", "{0} deve contenere al massimo {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} elemento", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} elementi", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-datetime", "{0} deve essere uguale o precedente alla Data/Ora corrente", false); err != nil { + return + } + + return + }, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + var err error + var t string + var f64 float64 + var digits uint64 + var kind reflect.Kind + + fn := func() (err error) { + if idx := strings.Index(fe.Param(), "."); idx != -1 { + digits = uint64(len(fe.Param()[idx+1:])) + } + + f64, err = strconv.ParseFloat(fe.Param(), 64) + + return + } + + kind = fe.Kind() + if kind == reflect.Ptr { + kind = fe.Type().Elem().Kind() + } + + switch kind { + case reflect.String: + + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("lte-string-character", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("lte-string", fe.Field(), c) + + case reflect.Slice, reflect.Map, reflect.Array: + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("lte-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("lte-items", fe.Field(), c) + + case reflect.Struct: + if fe.Type() != reflect.TypeOf(time.Time{}) { + err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag()) + goto END + } + + t, err = ut.T("lte-datetime", fe.Field()) + + default: + err = fn() + if err != nil { + goto END + } + + t, err = ut.T("lte-number", fe.Field(), ut.FmtNumber(f64, digits)) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gt", + customRegisFunc: func(ut ut.Translator) (err error) { + if err = ut.Add("gt-string", "{0} deve essere lungo più di {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} carattere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} caratteri", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-number", "{0} deve essere maggiore di {1}", false); err != nil { + return + } + + if err = ut.Add("gt-items", "{0} deve contenere più di {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} elemento", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} elementi", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-datetime", "{0} deve essere successivo alla Data/Ora corrente", false); err != nil { + return + } + + return + }, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + var err error + var t string + var f64 float64 + var digits uint64 + var kind reflect.Kind + + fn := func() (err error) { + if idx := strings.Index(fe.Param(), "."); idx != -1 { + digits = uint64(len(fe.Param()[idx+1:])) + } + + f64, err = strconv.ParseFloat(fe.Param(), 64) + + return + } + + kind = fe.Kind() + if kind == reflect.Ptr { + kind = fe.Type().Elem().Kind() + } + + switch kind { + case reflect.String: + + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("gt-string-character", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("gt-string", fe.Field(), c) + + case reflect.Slice, reflect.Map, reflect.Array: + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("gt-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("gt-items", fe.Field(), c) + + case reflect.Struct: + if fe.Type() != reflect.TypeOf(time.Time{}) { + err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag()) + goto END + } + + t, err = ut.T("gt-datetime", fe.Field()) + + default: + err = fn() + if err != nil { + goto END + } + + t, err = ut.T("gt-number", fe.Field(), ut.FmtNumber(f64, digits)) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gte", + customRegisFunc: func(ut ut.Translator) (err error) { + if err = ut.Add("gte-string", "{0} deve essere lungo almeno {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} carattere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} caratteri", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-number", "{0} deve essere maggiore o uguale a {1}", false); err != nil { + return + } + + if err = ut.Add("gte-items", "{0} deve contenere almeno {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} elemento", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} elementi", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-datetime", "{0} deve essere uguale o successivo alla Data/Ora corrente", false); err != nil { + return + } + + return + }, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + var err error + var t string + var f64 float64 + var digits uint64 + var kind reflect.Kind + + fn := func() (err error) { + if idx := strings.Index(fe.Param(), "."); idx != -1 { + digits = uint64(len(fe.Param()[idx+1:])) + } + + f64, err = strconv.ParseFloat(fe.Param(), 64) + + return + } + + kind = fe.Kind() + if kind == reflect.Ptr { + kind = fe.Type().Elem().Kind() + } + + switch kind { + case reflect.String: + + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("gte-string-character", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("gte-string", fe.Field(), c) + + case reflect.Slice, reflect.Map, reflect.Array: + var c string + + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("gte-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("gte-items", fe.Field(), c) + + case reflect.Struct: + if fe.Type() != reflect.TypeOf(time.Time{}) { + err = fmt.Errorf("tag '%s' cannot be used on a struct type", fe.Tag()) + goto END + } + + t, err = ut.T("gte-datetime", fe.Field()) + + default: + err = fn() + if err != nil { + goto END + } + + t, err = ut.T("gte-number", fe.Field(), ut.FmtNumber(f64, digits)) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "eqfield", + translation: "{0} deve essere uguale a {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "eqcsfield", + translation: "{0} deve essere uguale a {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "necsfield", + translation: "{0} deve essere diverso da {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "gtcsfield", + translation: "{0} deve essere maggiore di {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "gtecsfield", + translation: "{0} deve essere maggiore o uguale a {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "ltcsfield", + translation: "{0} deve essere minore di {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "ltecsfield", + translation: "{0} deve essere minore o uguale a {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "nefield", + translation: "{0} deve essere diverso da {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "gtfield", + translation: "{0} deve essere maggiore di {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "gtefield", + translation: "{0} deve essere maggiore o uguale a {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "ltfield", + translation: "{0} deve essere minore di {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "ltefield", + translation: "{0} deve essere minore o uguale a {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "alpha", + translation: "{0} può contenere solo caratteri alfabetici", + override: false, + }, + { + tag: "alphanum", + translation: "{0} può contenere solo caratteri alfanumerici", + override: false, + }, + { + tag: "numeric", + translation: "{0} deve essere un valore numerico valido", + override: false, + }, + { + tag: "number", + translation: "{0} deve essere un numero valido", + override: false, + }, + { + tag: "hexadecimal", + translation: "{0} deve essere un esadecimale valido", + override: false, + }, + { + tag: "hexcolor", + translation: "{0} deve essere un colore HEX valido", + override: false, + }, + { + tag: "rgb", + translation: "{0} deve essere un colore RGB valido", + override: false, + }, + { + tag: "rgba", + translation: "{0} deve essere un colore RGBA valido", + override: false, + }, + { + tag: "hsl", + translation: "{0} deve essere un colore HSL valido", + override: false, + }, + { + tag: "hsla", + translation: "{0} deve essere un colore HSLA valido", + override: false, + }, + { + tag: "e164", + translation: "{0} deve essere un numero telefonico in formato E.164 valido", + override: false, + }, + { + tag: "email", + translation: "{0} deve essere un indirizzo email valido", + override: false, + }, + { + tag: "url", + translation: "{0} deve essere un URL valido", + override: false, + }, + { + tag: "uri", + translation: "{0} deve essere un URI valido", + override: false, + }, + { + tag: "base64", + translation: "{0} deve essere una stringa Base64 valida", + override: false, + }, + { + tag: "contains", + translation: "{0} deve contenere il testo '{1}'", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "containsany", + translation: "{0} deve contenere almeno uno dei seguenti caratteri '{1}'", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "excludes", + translation: "{0} non deve contenere il testo '{1}'", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "excludesall", + translation: "{0} non deve contenere alcuno dei seguenti caratteri '{1}'", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "excludesrune", + translation: "{0} non deve contenere '{1}'", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "isbn", + translation: "{0} deve essere un numero ISBN valido", + override: false, + }, + { + tag: "isbn10", + translation: "{0} deve essere un numero ISBN-10 valido", + override: false, + }, + { + tag: "isbn13", + translation: "{0} deve essere un numero ISBN-13 valido", + override: false, + }, + { + tag: "uuid", + translation: "{0} deve essere un UUID valido", + override: false, + }, + { + tag: "uuid3", + translation: "{0} deve essere un UUID versione 3 valido", + override: false, + }, + { + tag: "uuid4", + translation: "{0} deve essere un UUID versione 4 valido", + override: false, + }, + { + tag: "uuid5", + translation: "{0} deve essere un UUID versione 5 valido", + override: false, + }, + { + tag: "ulid", + translation: "{0} deve essere un ULID valido", + override: false, + }, + { + tag: "ascii", + translation: "{0} deve contenere solo caratteri ascii", + override: false, + }, + { + tag: "printascii", + translation: "{0} deve contenere solo caratteri ascii stampabili", + override: false, + }, + { + tag: "multibyte", + translation: "{0} deve contenere caratteri multibyte", + override: false, + }, + { + tag: "datauri", + translation: "{0} deve contenere un Data URI valido", + override: false, + }, + { + tag: "latitude", + translation: "{0} deve contenere una latitudine valida", + override: false, + }, + { + tag: "longitude", + translation: "{0} deve contenere una longitudine valida", + override: false, + }, + { + tag: "ssn", + translation: "{0} deve essere un numero SSN valido", + override: false, + }, + { + tag: "ipv4", + translation: "{0} deve essere un indirizzo IPv4 valido", + override: false, + }, + { + tag: "ipv6", + translation: "{0} deve essere un indirizzo IPv6 valido", + override: false, + }, + { + tag: "ip", + translation: "{0} deve essere un indirizzo IP valido", + override: false, + }, + { + tag: "cidr", + translation: "{0} deve contenere una notazione CIDR valida", + override: false, + }, + { + tag: "cidrv4", + translation: "{0} deve contenere una notazione CIDR per un indirizzo IPv4 valida", + override: false, + }, + { + tag: "cidrv6", + translation: "{0} deve contenere una notazione CIDR per un indirizzo IPv6 valida", + override: false, + }, + { + tag: "tcp_addr", + translation: "{0} deve essere un indirizzo TCP valido", + override: false, + }, + { + tag: "tcp4_addr", + translation: "{0} deve essere un indirizzo IPv4 TCP valido", + override: false, + }, + { + tag: "tcp6_addr", + translation: "{0} deve essere un indirizzo IPv6 TCP valido", + override: false, + }, + { + tag: "udp_addr", + translation: "{0} deve essere un indirizzo UDP valido", + override: false, + }, + { + tag: "udp4_addr", + translation: "{0} deve essere un indirizzo IPv4 UDP valido", + override: false, + }, + { + tag: "udp6_addr", + translation: "{0} deve essere un indirizzo IPv6 UDP valido", + override: false, + }, + { + tag: "ip_addr", + translation: "{0} deve essere un indirizzo IP risolvibile", + override: false, + }, + { + tag: "ip4_addr", + translation: "{0} deve essere un indirizzo IPv4 risolvibile", + override: false, + }, + { + tag: "ip6_addr", + translation: "{0} deve essere un indirizzo IPv6 risolvibile", + override: false, + }, + { + tag: "unix_addr", + translation: "{0} deve essere un indirizzo UNIX risolvibile", + override: false, + }, + { + tag: "mac", + translation: "{0} deve contenere un indirizzo MAC valido", + override: false, + }, + { + tag: "unique", + translation: "{0} deve contenere valori unici", + override: false, + }, + { + tag: "iscolor", + translation: "{0} deve essere un colore valido", + override: false, + }, + { + tag: "oneof", + translation: "{0} deve essere uno di [{1}]", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "json", + translation: "{0} deve essere una stringa json valida", + override: false, + }, + { + tag: "jwt", + translation: "{0} deve essere una stringa jwt valida", + override: false, + }, + { + tag: "lowercase", + translation: "{0} deve essere una stringa minuscola", + override: false, + }, + { + tag: "boolean", + translation: "{0} deve rappresentare un valore booleano", + override: false, + }, + { + tag: "uppercase", + translation: "{0} deve essere una stringa maiuscola", + override: false, + }, + { + tag: "startswith", + translation: "{0} deve iniziare con {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "startsnotwith", + translation: "{0} non deve iniziare con {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "endswith", + translation: "{0} deve terminare con {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "endsnotwith", + translation: "{0} non deve terminare con {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "datetime", + translation: "{0} non corrisponde al formato {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "postcode_iso3166_alpha2", + translation: "{0} non corrisponde al formato del codice postale dello stato {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + { + tag: "postcode_iso3166_alpha2_field", + translation: "{0} non corrisponde al formato del codice postale dello stato nel campo {1}", + override: false, + customTransFunc: customTransFuncV1, + }, + } + + for _, t := range translations { + + if t.customTransFunc != nil && t.customRegisFunc != nil { + err = v.RegisterTranslation(t.tag, trans, t.customRegisFunc, t.customTransFunc) + } else if t.customTransFunc != nil && t.customRegisFunc == nil { + err = v.RegisterTranslation(t.tag, trans, registrationFunc(t.tag, t.translation, t.override), t.customTransFunc) + } else if t.customTransFunc == nil && t.customRegisFunc != nil { + err = v.RegisterTranslation(t.tag, trans, t.customRegisFunc, translateFunc) + } else { + err = v.RegisterTranslation(t.tag, trans, registrationFunc(t.tag, t.translation, t.override), translateFunc) + } + + if err != nil { + return + } + } + + return +} + +func registrationFunc(tag string, translation string, override bool) validator.RegisterTranslationsFunc { + return func(ut ut.Translator) (err error) { + if err = ut.Add(tag, translation, override); err != nil { + return + } + + return + } +} + +func translateFunc(ut ut.Translator, fe validator.FieldError) string { + t, err := ut.T(fe.Tag(), fe.Field()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t +} + +func customTransFuncV1(ut ut.Translator, fe validator.FieldError) string { + s, 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 s +} diff --git a/translations/it/it_test.go b/translations/it/it_test.go new file mode 100644 index 0000000..301e8c1 --- /dev/null +++ b/translations/it/it_test.go @@ -0,0 +1,724 @@ +package en + +import ( + "testing" + "time" + + . "github.com/go-playground/assert/v2" + italian "github.com/go-playground/locales/it" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" +) + +func TestTranslations(t *testing.T) { + it := italian.New() + uni := ut.New(it, it) + trans, _ := uni.GetTranslator("en") + + validate := validator.New() + + err := RegisterDefaultTranslations(validate, trans) + Equal(t, err, nil) + + type Inner struct { + EqCSFieldString string + NeCSFieldString string + GtCSFieldString string + GteCSFieldString string + LtCSFieldString string + LteCSFieldString string + } + + type Test struct { + Inner Inner + RequiredString string `validate:"required"` + RequiredNumber int `validate:"required"` + RequiredMultiple []string `validate:"required"` + LenString string `validate:"len=1"` + LenNumber float64 `validate:"len=1113.00"` + LenMultiple []string `validate:"len=7"` + MinString string `validate:"min=1"` + MinNumber float64 `validate:"min=1113.00"` + MinMultiple []string `validate:"min=7"` + MaxString string `validate:"max=3"` + MaxNumber float64 `validate:"max=1113.00"` + MaxMultiple []string `validate:"max=7"` + EqString string `validate:"eq=3"` + EqNumber float64 `validate:"eq=2.33"` + EqMultiple []string `validate:"eq=7"` + NeString string `validate:"ne="` + NeNumber float64 `validate:"ne=0.00"` + NeMultiple []string `validate:"ne=0"` + LtString string `validate:"lt=3"` + LtNumber float64 `validate:"lt=5.56"` + LtMultiple []string `validate:"lt=2"` + LtTime time.Time `validate:"lt"` + LteString string `validate:"lte=3"` + LteNumber float64 `validate:"lte=5.56"` + LteMultiple []string `validate:"lte=2"` + LteTime time.Time `validate:"lte"` + GtString string `validate:"gt=3"` + GtNumber float64 `validate:"gt=5.56"` + GtMultiple []string `validate:"gt=2"` + GtTime time.Time `validate:"gt"` + GteString string `validate:"gte=3"` + GteNumber float64 `validate:"gte=5.56"` + GteMultiple []string `validate:"gte=2"` + GteTime time.Time `validate:"gte"` + EqFieldString string `validate:"eqfield=MaxString"` + EqCSFieldString string `validate:"eqcsfield=Inner.EqCSFieldString"` + NeCSFieldString string `validate:"necsfield=Inner.NeCSFieldString"` + GtCSFieldString string `validate:"gtcsfield=Inner.GtCSFieldString"` + GteCSFieldString string `validate:"gtecsfield=Inner.GteCSFieldString"` + LtCSFieldString string `validate:"ltcsfield=Inner.LtCSFieldString"` + LteCSFieldString string `validate:"ltecsfield=Inner.LteCSFieldString"` + NeFieldString string `validate:"nefield=EqFieldString"` + GtFieldString string `validate:"gtfield=MaxString"` + GteFieldString string `validate:"gtefield=MaxString"` + LtFieldString string `validate:"ltfield=MaxString"` + LteFieldString string `validate:"ltefield=MaxString"` + AlphaString string `validate:"alpha"` + AlphanumString string `validate:"alphanum"` + NumericString string `validate:"numeric"` + NumberString string `validate:"number"` + HexadecimalString string `validate:"hexadecimal"` + HexColorString string `validate:"hexcolor"` + RGBColorString string `validate:"rgb"` + RGBAColorString string `validate:"rgba"` + HSLColorString string `validate:"hsl"` + HSLAColorString string `validate:"hsla"` + Email string `validate:"email"` + URL string `validate:"url"` + URI string `validate:"uri"` + Base64 string `validate:"base64"` + Contains string `validate:"contains=purpose"` + ContainsAny string `validate:"containsany=!@#$"` + Excludes string `validate:"excludes=text"` + ExcludesAll string `validate:"excludesall=!@#$"` + ExcludesRune string `validate:"excludesrune=☻"` + ISBN string `validate:"isbn"` + ISBN10 string `validate:"isbn10"` + ISBN13 string `validate:"isbn13"` + UUID string `validate:"uuid"` + UUID3 string `validate:"uuid3"` + UUID4 string `validate:"uuid4"` + UUID5 string `validate:"uuid5"` + ULID string `validate:"ulid"` + ASCII string `validate:"ascii"` + PrintableASCII string `validate:"printascii"` + MultiByte string `validate:"multibyte"` + DataURI string `validate:"datauri"` + Latitude string `validate:"latitude"` + Longitude string `validate:"longitude"` + SSN string `validate:"ssn"` + IP string `validate:"ip"` + IPv4 string `validate:"ipv4"` + IPv6 string `validate:"ipv6"` + CIDR string `validate:"cidr"` + CIDRv4 string `validate:"cidrv4"` + CIDRv6 string `validate:"cidrv6"` + TCPAddr string `validate:"tcp_addr"` + TCPAddrv4 string `validate:"tcp4_addr"` + TCPAddrv6 string `validate:"tcp6_addr"` + UDPAddr string `validate:"udp_addr"` + UDPAddrv4 string `validate:"udp4_addr"` + UDPAddrv6 string `validate:"udp6_addr"` + IPAddr string `validate:"ip_addr"` + IPAddrv4 string `validate:"ip4_addr"` + IPAddrv6 string `validate:"ip6_addr"` + UinxAddr string `validate:"unix_addr"` // can't fail from within Go's net package currently, but maybe in the future + MAC string `validate:"mac"` + IsColor string `validate:"iscolor"` + StrPtrMinLen *string `validate:"min=10"` + StrPtrMaxLen *string `validate:"max=1"` + StrPtrLen *string `validate:"len=2"` + StrPtrLt *string `validate:"lt=1"` + StrPtrLte *string `validate:"lte=1"` + StrPtrGt *string `validate:"gt=10"` + StrPtrGte *string `validate:"gte=10"` + OneOfString string `validate:"oneof=red green"` + OneOfInt int `validate:"oneof=5 63"` + UniqueSlice []string `validate:"unique"` + UniqueArray [3]string `validate:"unique"` + UniqueMap map[string]string `validate:"unique"` + BooleanString string `validate:"boolean"` + JSONString string `validate:"json"` + JWTString string `validate:"jwt"` + LowercaseString string `validate:"lowercase"` + UppercaseString string `validate:"uppercase"` + StartsWithString string `validate:"startswith=foo"` + StartsNotWithString string `validate:"startsnotwith=foo"` + EndsWithString string `validate:"endswith=foo"` + EndsNotWithString string `validate:"endsnotwith=foo"` + Datetime string `validate:"datetime=2006-01-02"` + PostCode string `validate:"postcode_iso3166_alpha2=SG"` + PostCodeCountry string + PostCodeByField string `validate:"postcode_iso3166_alpha2_field=PostCodeCountry"` + } + + var test Test + + test.Inner.EqCSFieldString = "1234" + test.Inner.GtCSFieldString = "1234" + test.Inner.GteCSFieldString = "1234" + + test.MaxString = "1234" + test.MaxNumber = 2000 + test.MaxMultiple = make([]string, 9) + + test.LtString = "1234" + test.LtNumber = 6 + test.LtMultiple = make([]string, 3) + test.LtTime = time.Now().Add(time.Hour * 24) + + test.LteString = "1234" + test.LteNumber = 6 + test.LteMultiple = make([]string, 3) + test.LteTime = time.Now().Add(time.Hour * 24) + + test.LtFieldString = "12345" + test.LteFieldString = "12345" + + test.LtCSFieldString = "1234" + test.LteCSFieldString = "1234" + + test.AlphaString = "abc3" + test.AlphanumString = "abc3!" + test.NumericString = "12E.00" + test.NumberString = "12E" + + test.Excludes = "this is some test text" + test.ExcludesAll = "This is Great!" + test.ExcludesRune = "Love it ☻" + + test.ASCII = "カタカナ" + test.PrintableASCII = "カタカナ" + + test.MultiByte = "1234feerf" + + test.LowercaseString = "ABCDEFG" + test.UppercaseString = "abcdefg" + test.StartsWithString = "hello" + test.StartsNotWithString = "foo-hello" + test.EndsWithString = "hello" + test.EndsNotWithString = "hello-foo" + + s := "toolong" + test.StrPtrMaxLen = &s + test.StrPtrLen = &s + + 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) + + errs, ok := err.(validator.ValidationErrors) + Equal(t, ok, true) + + tests := []struct { + ns string + expected string + }{ + { + ns: "Test.IsColor", + expected: "IsColor deve essere un colore valido", + }, + { + ns: "Test.MAC", + expected: "MAC deve contenere un indirizzo MAC valido", + }, + { + ns: "Test.IPAddr", + expected: "IPAddr deve essere un indirizzo IP risolvibile", + }, + { + ns: "Test.IPAddrv4", + expected: "IPAddrv4 deve essere un indirizzo IPv4 risolvibile", + }, + { + ns: "Test.IPAddrv6", + expected: "IPAddrv6 deve essere un indirizzo IPv6 risolvibile", + }, + { + ns: "Test.UDPAddr", + expected: "UDPAddr deve essere un indirizzo UDP valido", + }, + { + ns: "Test.UDPAddrv4", + expected: "UDPAddrv4 deve essere un indirizzo IPv4 UDP valido", + }, + { + ns: "Test.UDPAddrv6", + expected: "UDPAddrv6 deve essere un indirizzo IPv6 UDP valido", + }, + { + ns: "Test.TCPAddr", + expected: "TCPAddr deve essere un indirizzo TCP valido", + }, + { + ns: "Test.TCPAddrv4", + expected: "TCPAddrv4 deve essere un indirizzo IPv4 TCP valido", + }, + { + ns: "Test.TCPAddrv6", + expected: "TCPAddrv6 deve essere un indirizzo IPv6 TCP valido", + }, + { + ns: "Test.CIDR", + expected: "CIDR deve contenere una notazione CIDR valida", + }, + { + ns: "Test.CIDRv4", + expected: "CIDRv4 deve contenere una notazione CIDR per un indirizzo IPv4 valida", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6 deve contenere una notazione CIDR per un indirizzo IPv6 valida", + }, + { + ns: "Test.SSN", + expected: "SSN deve essere un numero SSN valido", + }, + { + ns: "Test.IP", + expected: "IP deve essere un indirizzo IP valido", + }, + { + ns: "Test.IPv4", + expected: "IPv4 deve essere un indirizzo IPv4 valido", + }, + { + ns: "Test.IPv6", + expected: "IPv6 deve essere un indirizzo IPv6 valido", + }, + { + ns: "Test.DataURI", + expected: "DataURI deve contenere un Data URI valido", + }, + { + ns: "Test.Latitude", + expected: "Latitude deve contenere una latitudine valida", + }, + { + ns: "Test.Longitude", + expected: "Longitude deve contenere una longitudine valida", + }, + { + ns: "Test.MultiByte", + expected: "MultiByte deve contenere caratteri multibyte", + }, + { + ns: "Test.ASCII", + expected: "ASCII deve contenere solo caratteri ascii", + }, + { + ns: "Test.PrintableASCII", + expected: "PrintableASCII deve contenere solo caratteri ascii stampabili", + }, + { + ns: "Test.UUID", + expected: "UUID deve essere un UUID valido", + }, + { + ns: "Test.UUID3", + expected: "UUID3 deve essere un UUID versione 3 valido", + }, + { + ns: "Test.UUID4", + expected: "UUID4 deve essere un UUID versione 4 valido", + }, + { + ns: "Test.UUID5", + expected: "UUID5 deve essere un UUID versione 5 valido", + }, + { + ns: "Test.ULID", + expected: "ULID deve essere un ULID valido", + }, + { + ns: "Test.ISBN", + expected: "ISBN deve essere un numero ISBN valido", + }, + { + ns: "Test.ISBN10", + expected: "ISBN10 deve essere un numero ISBN-10 valido", + }, + { + ns: "Test.ISBN13", + expected: "ISBN13 deve essere un numero ISBN-13 valido", + }, + { + ns: "Test.Excludes", + expected: "Excludes non deve contenere il testo 'text'", + }, + { + ns: "Test.ExcludesAll", + expected: "ExcludesAll non deve contenere alcuno dei seguenti caratteri '!@#$'", + }, + { + ns: "Test.ExcludesRune", + expected: "ExcludesRune non deve contenere '☻'", + }, + { + ns: "Test.ContainsAny", + expected: "ContainsAny deve contenere almeno uno dei seguenti caratteri '!@#$'", + }, + { + ns: "Test.Contains", + expected: "Contains deve contenere il testo 'purpose'", + }, + { + ns: "Test.Base64", + expected: "Base64 deve essere una stringa Base64 valida", + }, + { + ns: "Test.Email", + expected: "Email deve essere un indirizzo email valido", + }, + { + ns: "Test.URL", + expected: "URL deve essere un URL valido", + }, + { + ns: "Test.URI", + expected: "URI deve essere un URI valido", + }, + { + ns: "Test.RGBColorString", + expected: "RGBColorString deve essere un colore RGB valido", + }, + { + ns: "Test.RGBAColorString", + expected: "RGBAColorString deve essere un colore RGBA valido", + }, + { + ns: "Test.HSLColorString", + expected: "HSLColorString deve essere un colore HSL valido", + }, + { + ns: "Test.HSLAColorString", + expected: "HSLAColorString deve essere un colore HSLA valido", + }, + { + ns: "Test.HexadecimalString", + expected: "HexadecimalString deve essere un esadecimale valido", + }, + { + ns: "Test.HexColorString", + expected: "HexColorString deve essere un colore HEX valido", + }, + { + ns: "Test.NumberString", + expected: "NumberString deve essere un numero valido", + }, + { + ns: "Test.NumericString", + expected: "NumericString deve essere un valore numerico valido", + }, + { + ns: "Test.AlphanumString", + expected: "AlphanumString può contenere solo caratteri alfanumerici", + }, + { + ns: "Test.AlphaString", + expected: "AlphaString può contenere solo caratteri alfabetici", + }, + { + ns: "Test.LtFieldString", + expected: "LtFieldString deve essere minore di MaxString", + }, + { + ns: "Test.LteFieldString", + expected: "LteFieldString deve essere minore o uguale a MaxString", + }, + { + ns: "Test.GtFieldString", + expected: "GtFieldString deve essere maggiore di MaxString", + }, + { + ns: "Test.GteFieldString", + expected: "GteFieldString deve essere maggiore o uguale a MaxString", + }, + { + ns: "Test.NeFieldString", + expected: "NeFieldString deve essere diverso da EqFieldString", + }, + { + ns: "Test.LtCSFieldString", + expected: "LtCSFieldString deve essere minore di Inner.LtCSFieldString", + }, + { + ns: "Test.LteCSFieldString", + expected: "LteCSFieldString deve essere minore o uguale a Inner.LteCSFieldString", + }, + { + ns: "Test.GtCSFieldString", + expected: "GtCSFieldString deve essere maggiore di Inner.GtCSFieldString", + }, + { + ns: "Test.GteCSFieldString", + expected: "GteCSFieldString deve essere maggiore o uguale a Inner.GteCSFieldString", + }, + { + ns: "Test.NeCSFieldString", + expected: "NeCSFieldString deve essere diverso da Inner.NeCSFieldString", + }, + { + ns: "Test.EqCSFieldString", + expected: "EqCSFieldString deve essere uguale a Inner.EqCSFieldString", + }, + { + ns: "Test.EqFieldString", + expected: "EqFieldString deve essere uguale a MaxString", + }, + { + ns: "Test.GteString", + expected: "GteString deve essere lungo almeno 3 caratteri", + }, + { + ns: "Test.GteNumber", + expected: "GteNumber deve essere maggiore o uguale a 5,56", + }, + { + ns: "Test.GteMultiple", + expected: "GteMultiple deve contenere almeno 2 elementi", + }, + { + ns: "Test.GteTime", + expected: "GteTime deve essere uguale o successivo alla Data/Ora corrente", + }, + { + ns: "Test.GtString", + expected: "GtString deve essere lungo più di 3 caratteri", + }, + { + ns: "Test.GtNumber", + expected: "GtNumber deve essere maggiore di 5,56", + }, + { + ns: "Test.GtMultiple", + expected: "GtMultiple deve contenere più di 2 elementi", + }, + { + ns: "Test.GtTime", + expected: "GtTime deve essere successivo alla Data/Ora corrente", + }, + { + ns: "Test.LteString", + expected: "LteString deve essere lungo al massimo 3 caratteri", + }, + { + ns: "Test.LteNumber", + expected: "LteNumber deve essere minore o uguale a 5,56", + }, + { + ns: "Test.LteMultiple", + expected: "LteMultiple deve contenere al massimo 2 elementi", + }, + { + ns: "Test.LteTime", + expected: "LteTime deve essere uguale o precedente alla Data/Ora corrente", + }, + { + ns: "Test.LtString", + expected: "LtString deve essere lungo meno di 3 caratteri", + }, + { + ns: "Test.LtNumber", + expected: "LtNumber deve essere minore di 5,56", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultiple deve contenere meno di 2 elementi", + }, + { + ns: "Test.LtTime", + expected: "LtTime deve essere precedente alla Data/Ora corrente", + }, + { + ns: "Test.NeString", + expected: "NeString deve essere diverso da ", + }, + { + ns: "Test.NeNumber", + expected: "NeNumber deve essere diverso da 0.00", + }, + { + ns: "Test.NeMultiple", + expected: "NeMultiple deve essere diverso da 0", + }, + { + ns: "Test.EqString", + expected: "EqString non è uguale a 3", + }, + { + ns: "Test.EqNumber", + expected: "EqNumber non è uguale a 2.33", + }, + { + ns: "Test.EqMultiple", + expected: "EqMultiple non è uguale a 7", + }, + { + ns: "Test.MaxString", + expected: "MaxString deve essere lungo al massimo 3 caratteri", + }, + { + ns: "Test.MaxNumber", + expected: "MaxNumber deve essere minore o uguale a 1.113,00", + }, + { + ns: "Test.MaxMultiple", + expected: "MaxMultiple deve contenere al massimo 7 elementi", + }, + { + ns: "Test.MinString", + expected: "MinString deve essere lungo almeno 1 carattere", + }, + { + ns: "Test.MinNumber", + expected: "MinNumber deve essere maggiore o uguale a 1.113,00", + }, + { + ns: "Test.MinMultiple", + expected: "MinMultiple deve contenere almeno 7 elementi", + }, + { + ns: "Test.LenString", + expected: "LenString deve essere lungo 1 carattere", + }, + { + ns: "Test.LenNumber", + expected: "LenNumber deve essere uguale a 1.113,00", + }, + { + ns: "Test.LenMultiple", + expected: "LenMultiple deve contenere 7 elementi", + }, + { + ns: "Test.RequiredString", + expected: "RequiredString è un campo obbligatorio", + }, + { + ns: "Test.RequiredNumber", + expected: "RequiredNumber è un campo obbligatorio", + }, + { + ns: "Test.RequiredMultiple", + expected: "RequiredMultiple è un campo obbligatorio", + }, + { + ns: "Test.StrPtrMinLen", + expected: "StrPtrMinLen deve essere lungo almeno 10 caratteri", + }, + { + ns: "Test.StrPtrMaxLen", + expected: "StrPtrMaxLen deve essere lungo al massimo 1 carattere", + }, + { + ns: "Test.StrPtrLen", + expected: "StrPtrLen deve essere lungo 2 caratteri", + }, + { + ns: "Test.StrPtrLt", + expected: "StrPtrLt deve essere lungo meno di 1 carattere", + }, + { + ns: "Test.StrPtrLte", + expected: "StrPtrLte deve essere lungo al massimo 1 carattere", + }, + { + ns: "Test.StrPtrGt", + expected: "StrPtrGt deve essere lungo più di 10 caratteri", + }, + { + ns: "Test.StrPtrGte", + expected: "StrPtrGte deve essere lungo almeno 10 caratteri", + }, + { + ns: "Test.OneOfString", + expected: "OneOfString deve essere uno di [red green]", + }, + { + ns: "Test.OneOfInt", + expected: "OneOfInt deve essere uno di [5 63]", + }, + { + ns: "Test.UniqueSlice", + expected: "UniqueSlice deve contenere valori unici", + }, + { + ns: "Test.UniqueArray", + expected: "UniqueArray deve contenere valori unici", + }, + { + ns: "Test.UniqueMap", + expected: "UniqueMap deve contenere valori unici", + }, + { + ns: "Test.BooleanString", + expected: "BooleanString deve rappresentare un valore booleano", + }, + { + ns: "Test.JSONString", + expected: "JSONString deve essere una stringa json valida", + }, + { + ns: "Test.JWTString", + expected: "JWTString deve essere una stringa jwt valida", + }, + { + ns: "Test.LowercaseString", + expected: "LowercaseString deve essere una stringa minuscola", + }, + { + ns: "Test.UppercaseString", + expected: "UppercaseString deve essere una stringa maiuscola", + }, + { + ns: "Test.StartsWithString", + expected: "StartsWithString deve iniziare con foo", + }, + { + ns: "Test.StartsNotWithString", + expected: "StartsNotWithString non deve iniziare con foo", + }, + { + ns: "Test.EndsWithString", + expected: "EndsWithString deve terminare con foo", + }, + { + ns: "Test.EndsNotWithString", + expected: "EndsNotWithString non deve terminare con foo", + }, + { + ns: "Test.Datetime", + expected: "Datetime non corrisponde al formato 2006-01-02", + }, + { + ns: "Test.PostCode", + expected: "PostCode non corrisponde al formato del codice postale dello stato SG", + }, + { + ns: "Test.PostCodeByField", + expected: "PostCodeByField non corrisponde al formato del codice postale dello stato nel campo PostCodeCountry", + }, + } + + for _, tt := range tests { + + var fe validator.FieldError + + for _, e := range errs { + if tt.ns == e.Namespace() { + fe = e + break + } + } + + NotEqual(t, fe, nil) + Equal(t, tt.expected, fe.Translate(trans)) + } +} From f09500fca7d3aa986ebfbea8087b4e477b5a4103 Mon Sep 17 00:00:00 2001 From: Lauris BH Date: Sun, 1 May 2022 18:44:56 +0300 Subject: [PATCH 19/22] Fix support for aliased time.Time types (#890) --- baked_in.go | 202 +++++++++++++++++++++--------------------- util.go | 2 +- validator.go | 2 +- validator_instance.go | 8 +- validator_test.go | 22 +++++ 5 files changed, 128 insertions(+), 108 deletions(-) diff --git a/baked_in.go b/baked_in.go index 7833f24..bc638f2 100644 --- a/baked_in.go +++ b/baked_in.go @@ -819,12 +819,7 @@ func isNeField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != currentField.Type() { - return true - } - - if fieldType == timeType { + if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { t := currentField.Interface().(time.Time) fieldTime := field.Interface().(time.Time) @@ -832,6 +827,10 @@ func isNeField(fl FieldLevel) bool { return !fieldTime.Equal(t) } + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return true + } } // default reflect.String: @@ -872,18 +871,18 @@ func isLteCrossStructField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != topField.Type() { - return false - } + if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { - if fieldType == timeType { - - fieldTime := field.Interface().(time.Time) - topTime := topField.Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) + topTime := topField.Convert(timeType).Interface().(time.Time) return fieldTime.Before(topTime) || fieldTime.Equal(topTime) } + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } } // default reflect.String: @@ -920,18 +919,18 @@ func isLtCrossStructField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != topField.Type() { - return false - } - - if fieldType == timeType { + if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { - fieldTime := field.Interface().(time.Time) - topTime := topField.Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) + topTime := topField.Convert(timeType).Interface().(time.Time) return fieldTime.Before(topTime) } + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } } // default reflect.String: @@ -967,18 +966,18 @@ func isGteCrossStructField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != topField.Type() { - return false - } - - if fieldType == timeType { + if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { - fieldTime := field.Interface().(time.Time) - topTime := topField.Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) + topTime := topField.Convert(timeType).Interface().(time.Time) return fieldTime.After(topTime) || fieldTime.Equal(topTime) } + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } } // default reflect.String: @@ -1014,18 +1013,18 @@ func isGtCrossStructField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != topField.Type() { - return false - } + if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { - if fieldType == timeType { - - fieldTime := field.Interface().(time.Time) - topTime := topField.Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) + topTime := topField.Convert(timeType).Interface().(time.Time) return fieldTime.After(topTime) } + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } } // default reflect.String: @@ -1064,18 +1063,18 @@ func isNeCrossStructField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != topField.Type() { - return true - } - - if fieldType == timeType { + if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { - t := field.Interface().(time.Time) - fieldTime := topField.Interface().(time.Time) + t := field.Convert(timeType).Interface().(time.Time) + fieldTime := topField.Convert(timeType).Interface().(time.Time) return !fieldTime.Equal(t) } + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return true + } } // default reflect.String: @@ -1114,18 +1113,18 @@ func isEqCrossStructField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != topField.Type() { - return false - } - - if fieldType == timeType { + if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { - t := field.Interface().(time.Time) - fieldTime := topField.Interface().(time.Time) + t := field.Convert(timeType).Interface().(time.Time) + fieldTime := topField.Convert(timeType).Interface().(time.Time) return fieldTime.Equal(t) } + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } } // default reflect.String: @@ -1164,19 +1163,18 @@ func isEqField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != currentField.Type() { - return false - } + if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { - if fieldType == timeType { - - t := currentField.Interface().(time.Time) - fieldTime := field.Interface().(time.Time) + t := currentField.Convert(timeType).Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) return fieldTime.Equal(t) } + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } } // default reflect.String: @@ -1711,18 +1709,18 @@ func isGteField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != currentField.Type() { - return false - } - - if fieldType == timeType { + if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { - t := currentField.Interface().(time.Time) - fieldTime := field.Interface().(time.Time) + t := currentField.Convert(timeType).Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) return fieldTime.After(t) || fieldTime.Equal(t) } + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } } // default reflect.String @@ -1758,18 +1756,18 @@ func isGtField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != currentField.Type() { - return false - } - - if fieldType == timeType { + if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { - t := currentField.Interface().(time.Time) - fieldTime := field.Interface().(time.Time) + t := currentField.Convert(timeType).Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) return fieldTime.After(t) } + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } } // default reflect.String @@ -1811,10 +1809,10 @@ func isGte(fl FieldLevel) bool { case reflect.Struct: - if field.Type() == timeType { + if field.Type().ConvertibleTo(timeType) { now := time.Now().UTC() - t := field.Interface().(time.Time) + t := field.Convert(timeType).Interface().(time.Time) return t.After(now) || t.Equal(now) } @@ -1857,9 +1855,9 @@ func isGt(fl FieldLevel) bool { return field.Float() > p case reflect.Struct: - if field.Type() == timeType { + if field.Type().ConvertibleTo(timeType) { - return field.Interface().(time.Time).After(time.Now().UTC()) + return field.Convert(timeType).Interface().(time.Time).After(time.Now().UTC()) } } @@ -1937,18 +1935,18 @@ func isLteField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != currentField.Type() { - return false - } + if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { - if fieldType == timeType { - - t := currentField.Interface().(time.Time) - fieldTime := field.Interface().(time.Time) + t := currentField.Convert(timeType).Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) return fieldTime.Before(t) || fieldTime.Equal(t) } + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } } // default reflect.String @@ -1984,18 +1982,18 @@ func isLtField(fl FieldLevel) bool { fieldType := field.Type() - // Not Same underlying type i.e. struct and time - if fieldType != currentField.Type() { - return false - } - - if fieldType == timeType { + if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { - t := currentField.Interface().(time.Time) - fieldTime := field.Interface().(time.Time) + t := currentField.Convert(timeType).Interface().(time.Time) + fieldTime := field.Convert(timeType).Interface().(time.Time) return fieldTime.Before(t) } + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } } // default reflect.String @@ -2037,10 +2035,10 @@ func isLte(fl FieldLevel) bool { case reflect.Struct: - if field.Type() == timeType { + if field.Type().ConvertibleTo(timeType) { now := time.Now().UTC() - t := field.Interface().(time.Time) + t := field.Convert(timeType).Interface().(time.Time) return t.Before(now) || t.Equal(now) } @@ -2084,9 +2082,9 @@ func isLt(fl FieldLevel) bool { case reflect.Struct: - if field.Type() == timeType { + if field.Type().ConvertibleTo(timeType) { - return field.Interface().(time.Time).Before(time.Now().UTC()) + return field.Convert(timeType).Interface().(time.Time).Before(time.Now().UTC()) } } diff --git a/util.go b/util.go index 56420f4..36da855 100644 --- a/util.go +++ b/util.go @@ -82,7 +82,7 @@ BEGIN: fld := namespace var ns string - if typ != timeType { + if !typ.ConvertibleTo(timeType) { idx := strings.Index(namespace, namespaceSeparator) diff --git a/validator.go b/validator.go index c2e0358..80da095 100644 --- a/validator.go +++ b/validator.go @@ -164,7 +164,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr typ = current.Type() - if typ != timeType { + if !typ.ConvertibleTo(timeType) { if ct != nil { diff --git a/validator_instance.go b/validator_instance.go index 8917d8c..316ffb0 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -333,7 +333,7 @@ func (v *Validate) StructCtx(ctx context.Context, s interface{}) (err error) { val = val.Elem() } - if val.Kind() != reflect.Struct || val.Type() == timeType { + if val.Kind() != reflect.Struct || val.Type().ConvertibleTo(timeType) { return &InvalidValidationError{Type: reflect.TypeOf(s)} } @@ -378,7 +378,7 @@ func (v *Validate) StructFilteredCtx(ctx context.Context, s interface{}, fn Filt val = val.Elem() } - if val.Kind() != reflect.Struct || val.Type() == timeType { + if val.Kind() != reflect.Struct || val.Type().ConvertibleTo(timeType) { return &InvalidValidationError{Type: reflect.TypeOf(s)} } @@ -426,7 +426,7 @@ func (v *Validate) StructPartialCtx(ctx context.Context, s interface{}, fields . val = val.Elem() } - if val.Kind() != reflect.Struct || val.Type() == timeType { + if val.Kind() != reflect.Struct || val.Type().ConvertibleTo(timeType) { return &InvalidValidationError{Type: reflect.TypeOf(s)} } @@ -516,7 +516,7 @@ func (v *Validate) StructExceptCtx(ctx context.Context, s interface{}, fields .. val = val.Elem() } - if val.Kind() != reflect.Struct || val.Type() == timeType { + if val.Kind() != reflect.Struct || val.Type().ConvertibleTo(timeType) { return &InvalidValidationError{Type: reflect.TypeOf(s)} } diff --git a/validator_test.go b/validator_test.go index b7bcec2..8abe0ce 100644 --- a/validator_test.go +++ b/validator_test.go @@ -5021,6 +5021,28 @@ func TestIsEqFieldValidation(t *testing.T) { Equal(t, errs, nil) } +func TestIsEqFieldValidationWithAliasTime(t *testing.T) { + var errs error + validate := New() + + type CustomTime time.Time + + type Test struct { + Start CustomTime `validate:"eqfield=End"` + End *time.Time + } + + now := time.Now().UTC() + + sv := &Test{ + Start: CustomTime(now), + End: &now, + } + + errs = validate.Struct(sv) + Equal(t, errs, nil) +} + func TestIsEqValidation(t *testing.T) { var errs error validate := New() From 0a26ee57e4acca0fedd7936b5570bd28ff92e532 Mon Sep 17 00:00:00 2001 From: Ciprian Date: Sun, 1 May 2022 17:48:44 +0200 Subject: [PATCH 20/22] Updated `endsnotwith` description in Readme (#824) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c89266..0201880 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ Baked-in Validations | contains | Contains | | containsany | Contains Any | | containsrune | Contains Rune | -| endsnotwith | Ends With | +| endsnotwith | Ends Not With | | endswith | Ends With | | excludes | Excludes | | excludesall | Excludes All | From d37da5e53c4b42b70227b383814692133a5f738f Mon Sep 17 00:00:00 2001 From: Renato Alves Torres Date: Sun, 1 May 2022 16:50:01 +0100 Subject: [PATCH 21/22] fix: add en translation for required_if (#884) --- translations/en/en.go | 5 +++++ translations/en/en_test.go | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/translations/en/en.go b/translations/en/en.go index 5ed76ae..5784d1f 100644 --- a/translations/en/en.go +++ b/translations/en/en.go @@ -28,6 +28,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} is a required field", override: false, }, + { + tag: "required_if", + translation: "{0} is a required field", + override: false, + }, { tag: "len", customRegisFunc: func(ut ut.Translator) (err error) { diff --git a/translations/en/en_test.go b/translations/en/en_test.go index 146c475..4c1d605 100644 --- a/translations/en/en_test.go +++ b/translations/en/en_test.go @@ -27,6 +27,7 @@ func TestTranslations(t *testing.T) { GteCSFieldString string LtCSFieldString string LteCSFieldString string + RequiredIf string } type Test struct { @@ -34,6 +35,7 @@ func TestTranslations(t *testing.T) { RequiredString string `validate:"required"` RequiredNumber int `validate:"required"` RequiredMultiple []string `validate:"required"` + RequiredIf string `validate:"required_if=Inner.RequiredIf abcd"` LenString string `validate:"len=1"` LenNumber float64 `validate:"len=1113.00"` LenMultiple []string `validate:"len=7"` @@ -202,6 +204,8 @@ func TestTranslations(t *testing.T) { test.UniqueMap = map[string]string{"key1": "1234", "key2": "1234"} test.Datetime = "2008-Feb-01" + test.Inner.RequiredIf = "abcd" + err = validate.Struct(test) NotEqual(t, err, nil) @@ -592,6 +596,10 @@ func TestTranslations(t *testing.T) { ns: "Test.RequiredString", expected: "RequiredString is a required field", }, + { + ns: "Test.RequiredIf", + expected: "RequiredIf is a required field", + }, { ns: "Test.RequiredNumber", expected: "RequiredNumber is a required field", From e3f29bf0888e6fbcfa5c8ae9a2b0508c73858b6d Mon Sep 17 00:00:00 2001 From: Eduardo Mello Date: Sun, 1 May 2022 12:51:09 -0300 Subject: [PATCH 22/22] Boolean translation (#930) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ add en boolean translation * ✨ add pt-BR boolean translation --- translations/en/en.go | 5 +++++ translations/en/en_test.go | 6 ++++++ translations/pt_BR/pt_BR.go | 5 +++++ translations/pt_BR/pt_BR_test.go | 6 ++++++ 4 files changed, 22 insertions(+) diff --git a/translations/en/en.go b/translations/en/en.go index 5784d1f..ee05f91 100644 --- a/translations/en/en.go +++ b/translations/en/en.go @@ -1351,6 +1351,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return t }, }, + { + tag: "boolean", + translation: "{0} must be a valid boolean value", + override: false, + }, } for _, t := range translations { diff --git a/translations/en/en_test.go b/translations/en/en_test.go index 4c1d605..9cb6deb 100644 --- a/translations/en/en_test.go +++ b/translations/en/en_test.go @@ -151,6 +151,7 @@ func TestTranslations(t *testing.T) { PostCode string `validate:"postcode_iso3166_alpha2=SG"` PostCodeCountry string PostCodeByField string `validate:"postcode_iso3166_alpha2_field=PostCodeCountry"` + BooleanString string `validate:"boolean"` } var test Test @@ -203,6 +204,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" + test.BooleanString = "A" test.Inner.RequiredIf = "abcd" @@ -684,6 +686,10 @@ func TestTranslations(t *testing.T) { ns: "Test.PostCodeByField", expected: "PostCodeByField does not match postcode format of country in PostCodeCountry field", }, + { + ns: "Test.BooleanString", + expected: "BooleanString must be a valid boolean value", + }, } for _, tt := range tests { diff --git a/translations/pt_BR/pt_BR.go b/translations/pt_BR/pt_BR.go index ef04496..d6883aa 100644 --- a/translations/pt_BR/pt_BR.go +++ b/translations/pt_BR/pt_BR.go @@ -1316,6 +1316,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return s }, }, + { + tag: "boolean", + translation: "{0} deve ser um valor booleano válido", + override: false, + }, } for _, t := range translations { diff --git a/translations/pt_BR/pt_BR_test.go b/translations/pt_BR/pt_BR_test.go index c8a6482..426f246 100644 --- a/translations/pt_BR/pt_BR_test.go +++ b/translations/pt_BR/pt_BR_test.go @@ -139,6 +139,7 @@ func TestTranslations(t *testing.T) { StrPtrGte *string `validate:"gte=10"` OneOfString string `validate:"oneof=red green"` OneOfInt int `validate:"oneof=5 63"` + BooleanString string `validate:"boolean"` } var test Test @@ -171,6 +172,7 @@ func TestTranslations(t *testing.T) { test.AlphanumString = "abc3!" test.NumericString = "12E.00" test.NumberString = "12E" + test.BooleanString = "A" test.Excludes = "este é um texto de teste" test.ExcludesAll = "Isso é Ótimo!" @@ -619,6 +621,10 @@ func TestTranslations(t *testing.T) { ns: "Test.OneOfInt", expected: "OneOfInt deve ser um de [5 63]", }, + { + ns: "Test.BooleanString", + expected: "BooleanString deve ser um valor booleano válido", + }, } for _, tt := range tests {