From 893747e5ee93f123ea885d25bcc0c9247081d7ca Mon Sep 17 00:00:00 2001 From: Ravi Terala Date: Fri, 3 Jan 2020 11:30:08 -0800 Subject: [PATCH 1/7] Add hostname_port validator feature --- baked_in.go | 20 ++++++++++++++++++++ regexes.go | 10 +++++----- validator_test.go | 37 ++++++++++++++++++++++++++++++++++--- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/baked_in.go b/baked_in.go index 9eff0e4..3245202 100644 --- a/baked_in.go +++ b/baked_in.go @@ -166,6 +166,7 @@ var ( "html_encoded": isHTMLEncoded, "url_encoded": isURLEncoded, "dir": isDir, + "hostname_port": isHostnamePort, } ) @@ -2007,3 +2008,22 @@ func isDir(fl FieldLevel) bool { panic(fmt.Sprintf("Bad field type %T", field.Interface())) } + +// isHostnamePort validates a : combination for fields typically used for socket address. +func isHostnamePort(fl FieldLevel) bool { + val := fl.Field().String() + host, port, err := net.SplitHostPort(val) + if err != nil { + return false + } + // Port must be a iny <= 65535. + if portNum, err := strconv.ParseInt(port, 10, 32); err != nil || portNum > 65535 || portNum < 1 { + return false + } + + // If host is specified, it should match a DNS name + if host != "" { + return hostnameRegexRFC1123.MatchString(host) + } + return true +} diff --git a/regexes.go b/regexes.go index 63fdc1d..b8bf253 100644 --- a/regexes.go +++ b/regexes.go @@ -36,11 +36,11 @@ 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][a-zA-Z0-9\-\.]+[a-zA-Z0-9]$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 - btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address - 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 + 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 2c8b96b..0abdcab 100644 --- a/validator_test.go +++ b/validator_test.go @@ -7954,15 +7954,15 @@ func TestHostnameRFC1123Validation(t *testing.T) { if test.expected { if !IsEqual(errs, nil) { - t.Fatalf("Index: %d hostname failed Error: %v", i, errs) + t.Fatalf("Hostname: %v failed Error: %v", test, errs) } } else { if IsEqual(errs, nil) { - t.Fatalf("Index: %d hostname failed Error: %v", i, errs) + t.Fatalf("Hostname: %v failed Error: %v", test, errs) } else { val := getError(errs, "", "") if val.Tag() != "hostname_rfc1123" { - t.Fatalf("Index: %d hostname failed Error: %v", i, errs) + t.Fatalf("Hostname: %v failed Error: %v", i, errs) } } } @@ -9003,3 +9003,34 @@ func TestGetTag(t *testing.T) { Equal(t, errs, nil) Equal(t, tag, "mytag") } + +func Test_hostnameport_validator(t *testing.T) { + + type Host struct { + Addr string `validate:"hostname_port"` + } + + type testInput struct { + data string + expected bool + } + testData := []testInput{ + {"bad..domain.name:234", false}, + {"extra.dot.com.", false}, + {"localhost:1234", true}, + {"192.168.1.1:1234", true}, + {":1234", true}, + {"domain.com:1334", true}, + {"this.domain.com:234", true}, + {"domain:75000", false}, + {"missing.port", false}, + } + for _, td := range testData { + h := Host{Addr: td.data} + v := New() + err := v.Struct(h) + if td.expected != (err == nil) { + t.Fatalf("Test failed for data: %v Error: %v", td.data, err) + } + } +} From a1ac82a8653a8e36245594d0599a9412f9574ae3 Mon Sep 17 00:00:00 2001 From: Ravi Terala Date: Fri, 17 Jan 2020 09:52:57 -0800 Subject: [PATCH 2/7] Add documentation --- doc.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc.go b/doc.go index 799882c..4c58d95 100644 --- a/doc.go +++ b/doc.go @@ -1037,6 +1037,13 @@ This is done using os.Stat, which is a platform independent function. Usage: dir +HostPort + +This validates that a string value contains a valid DNS hostname and port that +can be used to valiate fields typically passed to sockets and connections. + + Usage: hostname_port + Alias Validators and Tags NOTE: When returning an error, the tag returned in "FieldError" will be From eb11afd9a8bc46a370e20962571205f2ab626b4d Mon Sep 17 00:00:00 2001 From: Rustam Date: Wed, 22 Jan 2020 16:08:32 +0300 Subject: [PATCH 3/7] Added russian translation --- translations/ru/ru.go | 1375 ++++++++++++++++++++++++++++++++++++ translations/ru/ru_test.go | 656 +++++++++++++++++ 2 files changed, 2031 insertions(+) create mode 100644 translations/ru/ru.go create mode 100644 translations/ru/ru_test.go diff --git a/translations/ru/ru.go b/translations/ru/ru.go new file mode 100644 index 0000000..6f9d41b --- /dev/null +++ b/translations/ru/ru.go @@ -0,0 +1,1375 @@ +package ru + +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} обязательное поле", + override: false, + }, + { + tag: "len", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("len-string", "{0} должен быть длиной в {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} символ", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} символы", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("len-number", "{0} должен быть равен {1}", false); err != nil { + return + } + + if err = ut.Add("len-items", "{0} должен содержать {1}", false); err != nil { + return + } + if err = ut.AddCardinal("len-items-item", "{0} элемент", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-items-item", "{0} элементы", 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} должен содержать минимум {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} символ", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} символы", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("min-number", "{0} должен быть больше или равно {1}", false); err != nil { + return + } + + if err = ut.Add("min-items", "{0} должен содержать минимум {1}", false); err != nil { + return + } + if err = ut.AddCardinal("min-items-item", "{0} элемент", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-items-item", "{0} элементы", 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} должен содержать максимум {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} символ", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} символы", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("max-number", "{0} должен быть меньше или равно {1}", false); err != nil { + return + } + + if err = ut.Add("max-items", "{0} должен содержать максимум {1}", false); err != nil { + return + } + if err = ut.AddCardinal("max-items-item", "{0} элемент", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-items-item", "{0} элементы", 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} не равен {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + fmt.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ne", + translation: "{0} должен быть не равен {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + fmt.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "lt", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("lt-string", "{0} должен иметь менее {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} символ", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} символы", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-number", "{0} должен быть менее {1}", false); err != nil { + return + } + + if err = ut.Add("lt-items", "{0} должен содержать менее {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} элемент", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} элементы", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-datetime", "{0} must be less than the current Date & Time", 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} должен содержать максимум {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} символ", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} символы", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-number", "{0} должен быть менее или равен {1}", false); err != nil { + return + } + + if err = ut.Add("lte-items", "{0} должен содержать максимум {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} элемент", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} элементы", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-datetime", "{0} must be less than or equal to the current Date & Time", 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} должен быть длиннее {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} символ", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} символы", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-number", "{0} должен быть больше {1}", false); err != nil { + return + } + + if err = ut.Add("gt-items", "{0} должен содержать более {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} элемент", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} элементы", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-datetime", "{0} должна быть позже текущего момента", 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} должен содержать минимум {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} символ", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} символы", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-number", "{0} должен быть больше или равно {1}", false); err != nil { + return + } + + if err = ut.Add("gte-items", "{0} должен содержать минимум {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} элемент", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} элементы", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-datetime", "{0} должна быть позже или равна текущему моменту", 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} должен быть равен {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "eqcsfield", + translation: "{0} должен быть равен {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "necsfield", + translation: "{0} не должен быть равен {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gtcsfield", + translation: "{0} должен быть больше {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gtecsfield", + translation: "{0} должен быть больше или равен {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ltcsfield", + translation: "{0} должен быть менее {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ltecsfield", + translation: "{0} должен быть менее или равен {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "nefield", + translation: "{0} не должен быть равен {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gtfield", + translation: "{0} должен быть больше {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gtefield", + translation: "{0} должен быть больше или равен {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ltfield", + translation: "{0} должен быть менее {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ltefield", + translation: "{0} должен быть менее или равен {1}", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "alpha", + translation: "{0} должен содержать только буквы", + override: false, + }, + { + tag: "alphanum", + translation: "{0} должен содержать только буквы и цифры", + override: false, + }, + { + tag: "numeric", + translation: "{0} должен быть цифровым значением", + override: false, + }, + { + tag: "number", + translation: "{0} должен быть цифрой", + override: false, + }, + { + tag: "hexadecimal", + translation: "{0} должен быть шестнадцатеричной строкой", + override: false, + }, + { + tag: "hexcolor", + translation: "{0} должен быть HEX цветом", + override: false, + }, + { + tag: "rgb", + translation: "{0} должен быть RGB цветом", + override: false, + }, + { + tag: "rgba", + translation: "{0} должен быть RGBA цветом", + override: false, + }, + { + tag: "hsl", + translation: "{0} должен быть HSL цветом", + override: false, + }, + { + tag: "hsla", + translation: "{0} должен быть HSLA цветом", + override: false, + }, + { + tag: "e164", + translation: "{0} должен быть E.164 formatted phone number", + override: false, + }, + { + tag: "email", + translation: "{0} должен быть email адресом", + override: false, + }, + { + tag: "url", + translation: "{0} должен быть URL", + override: false, + }, + { + tag: "uri", + translation: "{0} должен быть URI", + override: false, + }, + { + tag: "base64", + translation: "{0} должен быть Base64 строкой", + override: false, + }, + { + tag: "contains", + translation: "{0} должен содержать текст '{1}'", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "containsany", + translation: "{0} должен содержать минимум один из символов '{1}'", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "excludes", + translation: "{0} не должен содержать текст '{1}'", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "excludesall", + translation: "{0} не должен содержать символы '{1}'", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "excludesrune", + translation: "{0} не должен содержать '{1}'", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "isbn", + translation: "{0} должен быть ISBN номером", + override: false, + }, + { + tag: "isbn10", + translation: "{0} должен быть ISBN-10 номером", + override: false, + }, + { + tag: "isbn13", + translation: "{0} должен быть ISBN-13 номером", + override: false, + }, + { + tag: "uuid", + translation: "{0} должен быть UUID", + override: false, + }, + { + tag: "uuid3", + translation: "{0} должен быть UUID 3 версии", + override: false, + }, + { + tag: "uuid4", + translation: "{0} должен быть UUID 4 версии", + override: false, + }, + { + tag: "uuid5", + translation: "{0} должен быть UUID 5 версии", + override: false, + }, + { + tag: "ascii", + translation: "{0} должен содержать только ascii символы", + override: false, + }, + { + tag: "printascii", + translation: "{0} должен содержать только доступные для печати ascii символы", + override: false, + }, + { + tag: "multibyte", + translation: "{0} должен содержать мультибайтные символы", + override: false, + }, + { + tag: "datauri", + translation: "{0} должен содержать Data URI", + override: false, + }, + { + tag: "latitude", + translation: "{0} должен содержать координаты широты", + override: false, + }, + { + tag: "longitude", + translation: "{0} должен содержать координаты долготы", + override: false, + }, + { + tag: "ssn", + translation: "{0} должен быть SSN номером", + override: false, + }, + { + tag: "ipv4", + translation: "{0} должен быть IPv4 адресом", + override: false, + }, + { + tag: "ipv6", + translation: "{0} должен быть IPv6 адресом", + override: false, + }, + { + tag: "ip", + translation: "{0} должен быть IP адресом", + override: false, + }, + { + tag: "cidr", + translation: "{0} должен содержать CIDR обозначения", + override: false, + }, + { + tag: "cidrv4", + translation: "{0} должен содержать CIDR обозначения для IPv4 адреса", + override: false, + }, + { + tag: "cidrv6", + translation: "{0} должен содержать CIDR обозначения для IPv6 адреса", + override: false, + }, + { + tag: "tcp_addr", + translation: "{0} должен быть TCP адресом", + override: false, + }, + { + tag: "tcp4_addr", + translation: "{0} должен быть IPv4 TCP адресом", + override: false, + }, + { + tag: "tcp6_addr", + translation: "{0} должен быть IPv6 TCP адресом", + override: false, + }, + { + tag: "udp_addr", + translation: "{0} должен быть UDP адресом", + override: false, + }, + { + tag: "udp4_addr", + translation: "{0} должен быть IPv4 UDP адресом", + override: false, + }, + { + tag: "udp6_addr", + translation: "{0} должен быть IPv6 UDP адресом", + override: false, + }, + { + tag: "ip_addr", + translation: "{0} должен быть распознаваемым IP адресом", + override: false, + }, + { + tag: "ip4_addr", + translation: "{0} должен быть распознаваемым IPv4 адресом", + override: false, + }, + { + tag: "ip6_addr", + translation: "{0} должен быть распознаваемым IPv6 адресом", + override: false, + }, + { + tag: "unix_addr", + translation: "{0} должен быть распознаваемым UNIX адресом", + override: false, + }, + { + tag: "mac", + translation: "{0} должен содержать MAC адрес", + override: false, + }, + { + tag: "unique", + translation: "{0} должен содержать уникальные значения", + override: false, + }, + { + tag: "iscolor", + translation: "{0} должен быть цветом", + override: false, + }, + { + tag: "oneof", + translation: "{0} должен быть одним из [{1}]", + override: false, + customTransFunc: func(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 + }, + }, + } + + 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 +} diff --git a/translations/ru/ru_test.go b/translations/ru/ru_test.go new file mode 100644 index 0000000..aa5beb7 --- /dev/null +++ b/translations/ru/ru_test.go @@ -0,0 +1,656 @@ +package ru + +import ( + "log" + //"github.com/rustery/validator" + "testing" + "time" + + . "github.com/go-playground/assert/v2" + russian "github.com/go-playground/locales/en" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" +) + +func TestTranslations(t *testing.T) { + + ru := russian.New() + uni := ut.New(ru, ru) + trans, _ := uni.GetTranslator("ru") + + 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"` + 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"` + } + + 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" + + s := "toolong" + test.StrPtrMaxLen = &s + test.StrPtrLen = &s + + test.UniqueSlice = []string{"1234", "1234"} + test.UniqueMap = map[string]string{"key1": "1234", "key2": "1234"} + + 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 должен быть цветом", + }, + { + ns: "Test.MAC", + expected: "MAC должен содержать MAC адрес", + }, + { + ns: "Test.IPAddr", + expected: "IPAddr должен быть распознаваемым IP адресом", + }, + { + ns: "Test.IPAddrv4", + expected: "IPAddrv4 должен быть распознаваемым IPv4 адресом", + }, + { + ns: "Test.IPAddrv6", + expected: "IPAddrv6 должен быть распознаваемым IPv6 адресом", + }, + { + ns: "Test.UDPAddr", + expected: "UDPAddr должен быть UDP адресом", + }, + { + ns: "Test.UDPAddrv4", + expected: "UDPAddrv4 должен быть IPv4 UDP адресом", + }, + { + ns: "Test.UDPAddrv6", + expected: "UDPAddrv6 должен быть IPv6 UDP адресом", + }, + { + ns: "Test.TCPAddr", + expected: "TCPAddr должен быть TCP адресом", + }, + { + ns: "Test.TCPAddrv4", + expected: "TCPAddrv4 должен быть IPv4 TCP адресом", + }, + { + ns: "Test.TCPAddrv6", + expected: "TCPAddrv6 должен быть IPv6 TCP адресом", + }, + { + ns: "Test.CIDR", + expected: "CIDR должен содержать CIDR обозначения", + }, + { + ns: "Test.CIDRv4", + expected: "CIDRv4 должен содержать CIDR обозначения для IPv4 адреса", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6 должен содержать CIDR обозначения для IPv6 адреса", + }, + { + ns: "Test.SSN", + expected: "SSN должен быть SSN номером", + }, + { + ns: "Test.IP", + expected: "IP должен быть IP адресом", + }, + { + ns: "Test.IPv4", + expected: "IPv4 должен быть IPv4 адресом", + }, + { + ns: "Test.IPv6", + expected: "IPv6 должен быть IPv6 адресом", + }, + { + ns: "Test.DataURI", + expected: "DataURI должен содержать Data URI", + }, + { + ns: "Test.Latitude", + expected: "Latitude должен содержать координаты широты", + }, + { + ns: "Test.Longitude", + expected: "Longitude должен содержать координаты долготы", + }, + { + ns: "Test.MultiByte", + expected: "MultiByte должен содержать мультибайтные символы", + }, + { + ns: "Test.ASCII", + expected: "ASCII должен содержать только ascii символы", + }, + { + ns: "Test.PrintableASCII", + expected: "PrintableASCII должен содержать только доступные для печати ascii символы", + }, + { + ns: "Test.UUID", + expected: "UUID должен быть UUID", + }, + { + ns: "Test.UUID3", + expected: "UUID3 должен быть UUID 3 версии", + }, + { + ns: "Test.UUID4", + expected: "UUID4 должен быть UUID 4 версии", + }, + { + ns: "Test.UUID5", + expected: "UUID5 должен быть UUID 5 версии", + }, + { + ns: "Test.ISBN", + expected: "ISBN должен быть ISBN номером", + }, + { + ns: "Test.ISBN10", + expected: "ISBN10 должен быть ISBN-10 номером", + }, + { + ns: "Test.ISBN13", + expected: "ISBN13 должен быть ISBN-13 номером", + }, + { + ns: "Test.Excludes", + expected: "Excludes не должен содержать текст 'text'", + }, + { + ns: "Test.ExcludesAll", + expected: "ExcludesAll не должен содержать символы '!@#$'", + }, + { + ns: "Test.ExcludesRune", + expected: "ExcludesRune не должен содержать '☻'", + }, + { + ns: "Test.ContainsAny", + expected: "ContainsAny должен содержать минимум один из символов '!@#$'", + }, + { + ns: "Test.Contains", + expected: "Contains должен содержать текст 'purpose'", + }, + { + ns: "Test.Base64", + expected: "Base64 должен быть Base64 строкой", + }, + { + ns: "Test.Email", + expected: "Email должен быть email адресом", + }, + { + ns: "Test.URL", + expected: "URL должен быть URL", + }, + { + ns: "Test.URI", + expected: "URI должен быть URI", + }, + { + ns: "Test.RGBColorString", + expected: "RGBColorString должен быть RGB цветом", + }, + { + ns: "Test.RGBAColorString", + expected: "RGBAColorString должен быть RGBA цветом", + }, + { + ns: "Test.HSLColorString", + expected: "HSLColorString должен быть HSL цветом", + }, + { + ns: "Test.HSLAColorString", + expected: "HSLAColorString должен быть HSLA цветом", + }, + { + ns: "Test.HexadecimalString", + expected: "HexadecimalString должен быть шестнадцатеричной строкой", + }, + { + ns: "Test.HexColorString", + expected: "HexColorString должен быть HEX цветом", + }, + { + ns: "Test.NumberString", + expected: "NumberString должен быть цифрой", + }, + { + ns: "Test.NumericString", + expected: "NumericString должен быть цифровым значением", + }, + { + ns: "Test.AlphanumString", + expected: "AlphanumString должен содержать только буквы и цифры", + }, + { + ns: "Test.AlphaString", + expected: "AlphaString должен содержать только буквы", + }, + { + ns: "Test.LtFieldString", + expected: "LtFieldString должен быть менее MaxString", + }, + { + ns: "Test.LteFieldString", + expected: "LteFieldString должен быть менее или равен MaxString", + }, + { + ns: "Test.GtFieldString", + expected: "GtFieldString должен быть больше MaxString", + }, + { + ns: "Test.GteFieldString", + expected: "GteFieldString должен быть больше или равен MaxString", + }, + { + ns: "Test.NeFieldString", + expected: "NeFieldString не должен быть равен EqFieldString", + }, + { + ns: "Test.LtCSFieldString", + expected: "LtCSFieldString должен быть менее Inner.LtCSFieldString", + }, + { + ns: "Test.LteCSFieldString", + expected: "LteCSFieldString должен быть менее или равен Inner.LteCSFieldString", + }, + { + ns: "Test.GtCSFieldString", + expected: "GtCSFieldString должен быть больше Inner.GtCSFieldString", + }, + { + ns: "Test.GteCSFieldString", + expected: "GteCSFieldString должен быть больше или равен Inner.GteCSFieldString", + }, + { + ns: "Test.NeCSFieldString", + expected: "NeCSFieldString не должен быть равен Inner.NeCSFieldString", + }, + { + ns: "Test.EqCSFieldString", + expected: "EqCSFieldString должен быть равен Inner.EqCSFieldString", + }, + { + ns: "Test.EqFieldString", + expected: "EqFieldString должен быть равен MaxString", + }, + { + ns: "Test.GteString", + expected: "GteString должен содержать минимум 3 символы", + }, + { + ns: "Test.GteNumber", + expected: "GteNumber должен быть больше или равно 5.56", + }, + { + ns: "Test.GteMultiple", + expected: "GteMultiple должен содержать минимум 2 элементы", + }, + { + ns: "Test.GteTime", + expected: "GteTime должна быть позже или равна текущему моменту", + }, + { + ns: "Test.GtString", + expected: "GtString должен быть длиннее 3 символы", + }, + { + ns: "Test.GtNumber", + expected: "GtNumber должен быть больше 5.56", + }, + { + ns: "Test.GtMultiple", + expected: "GtMultiple должен содержать более 2 элементы", + }, + { + ns: "Test.GtTime", + expected: "GtTime должна быть позже текущего момента", + }, + { + ns: "Test.LteString", + expected: "LteString должен содержать максимум 3 символы", + }, + { + ns: "Test.LteNumber", + expected: "LteNumber должен быть менее или равен 5.56", + }, + { + ns: "Test.LteMultiple", + expected: "LteMultiple должен содержать максимум 2 элементы", + }, + { + ns: "Test.LteTime", + expected: "LteTime must be less than or equal to the current Date & Time", + }, + { + ns: "Test.LtString", + expected: "LtString должен иметь менее 3 символы", + }, + { + ns: "Test.LtNumber", + expected: "LtNumber должен быть менее 5.56", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultiple должен содержать менее 2 элементы", + }, + { + ns: "Test.LtTime", + expected: "LtTime must be less than the current Date & Time", + }, + { + ns: "Test.NeString", + expected: "NeString должен быть не равен ", + }, + { + ns: "Test.NeNumber", + expected: "NeNumber должен быть не равен 0.00", + }, + { + ns: "Test.NeMultiple", + expected: "NeMultiple должен быть не равен 0", + }, + { + ns: "Test.EqString", + expected: "EqString не равен 3", + }, + { + ns: "Test.EqNumber", + expected: "EqNumber не равен 2.33", + }, + { + ns: "Test.EqMultiple", + expected: "EqMultiple не равен 7", + }, + { + ns: "Test.MaxString", + expected: "MaxString должен содержать максимум 3 символы", + }, + { + ns: "Test.MaxNumber", + expected: "MaxNumber должен быть меньше или равно 1,113.00", + }, + { + ns: "Test.MaxMultiple", + expected: "MaxMultiple должен содержать максимум 7 элементы", + }, + { + ns: "Test.MinString", + expected: "MinString должен содержать минимум 1 символ", + }, + { + ns: "Test.MinNumber", + expected: "MinNumber должен быть больше или равно 1,113.00", + }, + { + ns: "Test.MinMultiple", + expected: "MinMultiple должен содержать минимум 7 элементы", + }, + { + ns: "Test.LenString", + expected: "LenString должен быть длиной в 1 символ", + }, + { + ns: "Test.LenNumber", + expected: "LenNumber должен быть равен 1,113.00", + }, + { + ns: "Test.LenMultiple", + expected: "LenMultiple должен содержать 7 элементы", + }, + { + ns: "Test.RequiredString", + expected: "RequiredString обязательное поле", + }, + { + ns: "Test.RequiredNumber", + expected: "RequiredNumber обязательное поле", + }, + { + ns: "Test.RequiredMultiple", + expected: "RequiredMultiple обязательное поле", + }, + { + ns: "Test.StrPtrMinLen", + expected: "StrPtrMinLen должен содержать минимум 10 символы", + }, + { + ns: "Test.StrPtrMaxLen", + expected: "StrPtrMaxLen должен содержать максимум 1 символ", + }, + { + ns: "Test.StrPtrLen", + expected: "StrPtrLen должен быть длиной в 2 символы", + }, + { + ns: "Test.StrPtrLt", + expected: "StrPtrLt должен иметь менее 1 символ", + }, + { + ns: "Test.StrPtrLte", + expected: "StrPtrLte должен содержать максимум 1 символ", + }, + { + ns: "Test.StrPtrGt", + expected: "StrPtrGt должен быть длиннее 10 символы", + }, + { + ns: "Test.StrPtrGte", + expected: "StrPtrGte должен содержать минимум 10 символы", + }, + { + ns: "Test.OneOfString", + expected: "OneOfString должен быть одним из [red green]", + }, + { + ns: "Test.OneOfInt", + expected: "OneOfInt должен быть одним из [5 63]", + }, + { + ns: "Test.UniqueSlice", + expected: "UniqueSlice должен содержать уникальные значения", + }, + { + ns: "Test.UniqueArray", + expected: "UniqueArray должен содержать уникальные значения", + }, + { + ns: "Test.UniqueMap", + expected: "UniqueMap должен содержать уникальные значения", + }, + } + + for _, tt := range tests { + + var fe validator.FieldError + + for _, e := range errs { + if tt.ns == e.Namespace() { + fe = e + break + } + } + + log.Println(fe) + + NotEqual(t, fe, nil) + Equal(t, tt.expected, fe.Translate(trans)) + } + +} From 5b51bff4e63d97b9e1f7f876012290285caa7c12 Mon Sep 17 00:00:00 2001 From: Pantelis Sampaziotis Date: Wed, 5 Feb 2020 22:39:47 +0200 Subject: [PATCH 4/7] Add isLowercase and isUppercase valitation --- baked_in.go | 24 +++++++++++++ doc.go | 12 +++++++ translations/en/en.go | 10 ++++++ translations/en/en_test.go | 15 +++++++- validator_test.go | 74 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 134 insertions(+), 1 deletion(-) diff --git a/baked_in.go b/baked_in.go index 9eff0e4..57440cb 100644 --- a/baked_in.go +++ b/baked_in.go @@ -166,6 +166,8 @@ var ( "html_encoded": isHTMLEncoded, "url_encoded": isURLEncoded, "dir": isDir, + "lowercase": isLowercase, + "uppercase": isUppercase, } ) @@ -2007,3 +2009,25 @@ func isDir(fl FieldLevel) bool { panic(fmt.Sprintf("Bad field type %T", field.Interface())) } + +// isLowercase is the validation function for validating if the current field's value is a lowercase string. +func isLowercase(fl FieldLevel) bool { + field := fl.Field() + + if field.Kind() == reflect.String { + return field.String() == strings.ToLower(field.String()) + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// isUppercase is the validation function for validating if the current field's value is an uppercase string. +func isUppercase(fl FieldLevel) bool { + field := fl.Field() + + if field.Kind() == reflect.String { + return field.String() == strings.ToUpper(field.String()) + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} diff --git a/doc.go b/doc.go index 799882c..85f0507 100644 --- a/doc.go +++ b/doc.go @@ -641,6 +641,18 @@ hashtag (#) Usage: hexcolor +Lowercase String + +This validates that a string value contains only lowercase characters. An empty string is considered a valid lowercase string. + + Usage: lowercase + +Uppercase String + +This validates that a string value contains only uppercase characters. An empty string is considered a valid uppercase string. + + Usage: uppercase + RGB String This validates that a string value contains a valid rgb color diff --git a/translations/en/en.go b/translations/en/en.go index 3b1058b..cf89fd2 100644 --- a/translations/en/en.go +++ b/translations/en/en.go @@ -1321,6 +1321,16 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er return s }, }, + { + tag: "lowercase", + translation: "{0} must be a lowercase string", + override: false, + }, + { + tag: "uppercase", + translation: "{0} must be an uppercase string", + override: false, + }, } for _, t := range translations { diff --git a/translations/en/en_test.go b/translations/en/en_test.go index cddcb84..769409a 100644 --- a/translations/en/en_test.go +++ b/translations/en/en_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" ) @@ -141,6 +141,8 @@ func TestTranslations(t *testing.T) { UniqueSlice []string `validate:"unique"` UniqueArray [3]string `validate:"unique"` UniqueMap map[string]string `validate:"unique"` + LowercaseString string `validate:"lowercase"` + UppercaseString string `validate:"uppercase"` } var test Test @@ -183,6 +185,9 @@ func TestTranslations(t *testing.T) { test.MultiByte = "1234feerf" + test.LowercaseString = "ABCDEFG" + test.UppercaseString = "abcdefg" + s := "toolong" test.StrPtrMaxLen = &s test.StrPtrLen = &s @@ -632,6 +637,14 @@ func TestTranslations(t *testing.T) { ns: "Test.UniqueMap", expected: "UniqueMap must contain unique values", }, + { + ns: "Test.LowercaseString", + expected: "LowercaseString must be a lowercase string", + }, + { + ns: "Test.UppercaseString", + expected: "UppercaseString must be an uppercase string", + }, } for _, tt := range tests { diff --git a/validator_test.go b/validator_test.go index 2c8b96b..1114071 100644 --- a/validator_test.go +++ b/validator_test.go @@ -9003,3 +9003,77 @@ func TestGetTag(t *testing.T) { Equal(t, errs, nil) Equal(t, tag, "mytag") } + +func TestLowercaseValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {`abcdefg`, true}, + {`Abcdefg`, false}, + {"", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "lowercase") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d lowercase failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d lowercase failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "lowercase" { + t.Fatalf("Index: %d lowercase failed Error: %s", i, errs) + } + } + } + } + + PanicMatches(t, func() { + _ = validate.Var(2, "lowercase") + }, "Bad field type int") +} + +func TestUppercaseValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {`ABCDEFG`, true}, + {`aBCDEFG`, false}, + {"", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "uppercase") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d uppercase failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d uppercase failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "uppercase" { + t.Fatalf("Index: %d uppercase failed Error: %s", i, errs) + } + } + } + } + + PanicMatches(t, func() { + _ = validate.Var(2, "uppercase") + }, "Bad field type int") +} From 6d2c659585798a684ab42d333ff204790108639c Mon Sep 17 00:00:00 2001 From: shenyiling Date: Thu, 6 Feb 2020 18:28:56 +0800 Subject: [PATCH 5/7] fix: typo --- doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc.go b/doc.go index 799882c..ecd8092 100644 --- a/doc.go +++ b/doc.go @@ -158,7 +158,7 @@ handy in ignoring embedded structs from being validated. (Usage: -) Or Operator This is the 'or' operator allowing multiple validators to be used and -accepted. (Usage: rbg|rgba) <-- this would allow either rgb or rgba +accepted. (Usage: rgb|rgba) <-- this would allow either rgb or rgba colors to be accepted. This can also be combined with 'and' for example ( Usage: omitempty,rgb|rgba) From 9e58bcdee1beffe9bc154650ab80f5f055ff74af Mon Sep 17 00:00:00 2001 From: Pantelis Sampaziotis Date: Fri, 7 Feb 2020 16:30:58 +0200 Subject: [PATCH 6/7] Empty strings are not lowercase or uppercase --- baked_in.go | 6 ++++++ validator_test.go | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/baked_in.go b/baked_in.go index 57440cb..91fd479 100644 --- a/baked_in.go +++ b/baked_in.go @@ -2015,6 +2015,9 @@ func isLowercase(fl FieldLevel) bool { field := fl.Field() if field.Kind() == reflect.String { + if field.String() == "" { + return false + } return field.String() == strings.ToLower(field.String()) } @@ -2026,6 +2029,9 @@ func isUppercase(fl FieldLevel) bool { field := fl.Field() if field.Kind() == reflect.String { + if field.String() == "" { + return false + } return field.String() == strings.ToUpper(field.String()) } diff --git a/validator_test.go b/validator_test.go index 1114071..f5d6d01 100644 --- a/validator_test.go +++ b/validator_test.go @@ -9011,7 +9011,7 @@ func TestLowercaseValidation(t *testing.T) { }{ {`abcdefg`, true}, {`Abcdefg`, false}, - {"", true}, + {"", false}, } validate := New() @@ -9048,7 +9048,7 @@ func TestUppercaseValidation(t *testing.T) { }{ {`ABCDEFG`, true}, {`aBCDEFG`, false}, - {"", true}, + {"", false}, } validate := New() From 2109f86248a14886cd9a21da5a7220a7145d3b56 Mon Sep 17 00:00:00 2001 From: Pantelis Sampaziotis Date: Fri, 7 Feb 2020 16:32:30 +0200 Subject: [PATCH 7/7] Update documenation --- doc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc.go b/doc.go index 85f0507..8893378 100644 --- a/doc.go +++ b/doc.go @@ -643,13 +643,13 @@ hashtag (#) Lowercase String -This validates that a string value contains only lowercase characters. An empty string is considered a valid lowercase string. +This validates that a string value contains only lowercase characters. An empty string is not a valid lowercase string. Usage: lowercase Uppercase String -This validates that a string value contains only uppercase characters. An empty string is considered a valid uppercase string. +This validates that a string value contains only uppercase characters. An empty string is not a valid uppercase string. Usage: uppercase