From 2e2ba084bff2517bf4767fdb91e5946b198621a0 Mon Sep 17 00:00:00 2001 From: gamife Date: Fri, 27 Nov 2020 23:07:41 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=8C=85=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gin/b/b.go | 62 + gin/binding/binding.go | 116 + gin/binding/binding_nomsgpack.go | 111 + gin/binding/default_validator.go | 74 + gin/binding/form.go | 63 + gin/binding/form_mapping.go | 392 + gin/binding/form_mapping_benchmark_test.go | 67 + gin/binding/form_mapping_test.go | 281 + gin/binding/header.go | 34 + gin/binding/json.go | 56 + gin/binding/json_test.go | 30 + gin/binding/msgpack.go | 37 + gin/binding/msgpack_test.go | 34 + gin/binding/multipart_form_mapping.go | 66 + gin/binding/multipart_form_mapping_test.go | 138 + gin/binding/protobuf.go | 36 + gin/binding/query.go | 21 + gin/binding/uri.go | 18 + gin/binding/validate_test.go | 228 + gin/binding/xml.go | 33 + gin/binding/xml_test.go | 25 + gin/binding/yaml.go | 35 + gin/binding/yaml_test.go | 21 + gin/internal/bytesconv/bytesconv.go | 23 + gin/internal/bytesconv/bytesconv_test.go | 99 + gin/internal/json/json.go | 22 + gin/internal/json/jsoniter.go | 23 + go-playground/locales/.gitignore | 24 + go-playground/locales/.travis.yml | 26 + go-playground/locales/LICENSE | 21 + go-playground/locales/README.md | 172 + go-playground/locales/currency/currency.go | 308 + go-playground/locales/go.sum | 3 + go-playground/locales/logo.png | Bin 0 -> 37360 bytes go-playground/locales/rules.go | 293 + go-playground/locales/zh/zh.go | 619 ++ go-playground/universal-translator/.gitignore | 25 + go-playground/universal-translator/.travis.yml | 27 + go-playground/universal-translator/LICENSE | 21 + go-playground/universal-translator/README.md | 89 + go-playground/universal-translator/errors.go | 148 + go-playground/universal-translator/go.sum | 4 + .../universal-translator/import_export.go | 274 + go-playground/universal-translator/logo.png | Bin 0 -> 16598 bytes go-playground/universal-translator/translator.go | 420 + .../universal-translator/universal_translator.go | 113 + .../validator/v10/.github/CONTRIBUTING.md | 9 + .../validator/v10/.github/ISSUE_TEMPLATE.md | 13 + .../validator/v10/.github/PULL_REQUEST_TEMPLATE.md | 13 + go-playground/validator/v10/.gitignore | 30 + go-playground/validator/v10/.travis.yml | 29 + go-playground/validator/v10/LICENSE | 22 + go-playground/validator/v10/Makefile | 18 + go-playground/validator/v10/README.md | 299 + .../v10/_examples/custom-validation/main.go | 39 + .../validator/v10/_examples/custom/main.go | 51 + go-playground/validator/v10/_examples/dive/main.go | 39 + .../v10/_examples/gin-upgrading-overriding/main.go | 10 + .../_examples/gin-upgrading-overriding/v8_to_v9.go | 55 + .../validator/v10/_examples/simple/main.go | 101 + .../validator/v10/_examples/struct-level/main.go | 120 + .../validator/v10/_examples/translations/main.go | 129 + go-playground/validator/v10/baked_in.go | 2285 ++++ go-playground/validator/v10/benchmarks_test.go | 1099 ++ go-playground/validator/v10/cache.go | 322 + go-playground/validator/v10/country_codes.go | 162 + go-playground/validator/v10/doc.go | 1308 +++ go-playground/validator/v10/errors.go | 295 + go-playground/validator/v10/field_level.go | 119 + go-playground/validator/v10/go.sum | 48 + go-playground/validator/v10/logo.png | Bin 0 -> 13443 bytes .../v10/non-standard/validators/notblank.go | 25 + .../v10/non-standard/validators/notblank_test.go | 65 + go-playground/validator/v10/regexes.go | 101 + go-playground/validator/v10/struct_level.go | 175 + go-playground/validator/v10/testdata/a.go | 1 + go-playground/validator/v10/translations.go | 11 + go-playground/validator/v10/translations/en/en.go | 1405 +++ .../validator/v10/translations/en/en_test.go | 676 ++ go-playground/validator/v10/translations/es/es.go | 1375 +++ .../validator/v10/translations/es/es_test.go | 652 ++ go-playground/validator/v10/translations/fr/fr.go | 1365 +++ .../validator/v10/translations/fr/fr_test.go | 634 ++ go-playground/validator/v10/translations/id/id.go | 1365 +++ .../validator/v10/translations/id/id_test.go | 634 ++ go-playground/validator/v10/translations/ja/ja.go | 1421 +++ .../validator/v10/translations/ja/ja_test.go | 634 ++ go-playground/validator/v10/translations/nl/nl.go | 1365 +++ .../validator/v10/translations/nl/nl_test.go | 634 ++ go-playground/validator/v10/translations/pt/pt.go | 1405 +++ .../validator/v10/translations/pt/pt_test.go | 677 ++ .../validator/v10/translations/pt_BR/pt_BR.go | 1365 +++ .../validator/v10/translations/pt_BR/pt_BR_test.go | 634 ++ go-playground/validator/v10/translations/ru/ru.go | 1375 +++ .../validator/v10/translations/ru/ru_test.go | 656 ++ go-playground/validator/v10/translations/tr/tr.go | 1370 +++ .../validator/v10/translations/tr/tr_test.go | 652 ++ go-playground/validator/v10/translations/zh/zh.go | 1392 +++ .../validator/v10/translations/zh/zh_test.go | 661 ++ .../validator/v10/translations/zh_tw/zh_tw.go | 1373 +++ .../validator/v10/translations/zh_tw/zh_tw_test.go | 641 ++ go-playground/validator/v10/util.go | 288 + go-playground/validator/v10/validator.go | 482 + go-playground/validator/v10/validator_instance.go | 619 ++ go-playground/validator/v10/validator_test.go | 11035 +++++++++++++++++++ go.mod | 15 + main.go | 5 + 107 files changed, 48530 insertions(+) create mode 100644 gin/b/b.go create mode 100644 gin/binding/binding.go create mode 100644 gin/binding/binding_nomsgpack.go create mode 100644 gin/binding/default_validator.go create mode 100644 gin/binding/form.go create mode 100644 gin/binding/form_mapping.go create mode 100644 gin/binding/form_mapping_benchmark_test.go create mode 100644 gin/binding/form_mapping_test.go create mode 100644 gin/binding/header.go create mode 100644 gin/binding/json.go create mode 100644 gin/binding/json_test.go create mode 100644 gin/binding/msgpack.go create mode 100644 gin/binding/msgpack_test.go create mode 100644 gin/binding/multipart_form_mapping.go create mode 100644 gin/binding/multipart_form_mapping_test.go create mode 100644 gin/binding/protobuf.go create mode 100644 gin/binding/query.go create mode 100644 gin/binding/uri.go create mode 100644 gin/binding/validate_test.go create mode 100644 gin/binding/xml.go create mode 100644 gin/binding/xml_test.go create mode 100644 gin/binding/yaml.go create mode 100644 gin/binding/yaml_test.go create mode 100644 gin/internal/bytesconv/bytesconv.go create mode 100644 gin/internal/bytesconv/bytesconv_test.go create mode 100644 gin/internal/json/json.go create mode 100644 gin/internal/json/jsoniter.go create mode 100644 go-playground/locales/.gitignore create mode 100644 go-playground/locales/.travis.yml create mode 100644 go-playground/locales/LICENSE create mode 100644 go-playground/locales/README.md create mode 100644 go-playground/locales/currency/currency.go create mode 100644 go-playground/locales/go.sum create mode 100644 go-playground/locales/logo.png create mode 100644 go-playground/locales/rules.go create mode 100644 go-playground/locales/zh/zh.go create mode 100644 go-playground/universal-translator/.gitignore create mode 100644 go-playground/universal-translator/.travis.yml create mode 100644 go-playground/universal-translator/LICENSE create mode 100644 go-playground/universal-translator/README.md create mode 100644 go-playground/universal-translator/errors.go create mode 100644 go-playground/universal-translator/go.sum create mode 100644 go-playground/universal-translator/import_export.go create mode 100644 go-playground/universal-translator/logo.png create mode 100644 go-playground/universal-translator/translator.go create mode 100644 go-playground/universal-translator/universal_translator.go create mode 100644 go-playground/validator/v10/.github/CONTRIBUTING.md create mode 100644 go-playground/validator/v10/.github/ISSUE_TEMPLATE.md create mode 100644 go-playground/validator/v10/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 go-playground/validator/v10/.gitignore create mode 100644 go-playground/validator/v10/.travis.yml create mode 100644 go-playground/validator/v10/LICENSE create mode 100644 go-playground/validator/v10/Makefile create mode 100644 go-playground/validator/v10/README.md create mode 100644 go-playground/validator/v10/_examples/custom-validation/main.go create mode 100644 go-playground/validator/v10/_examples/custom/main.go create mode 100644 go-playground/validator/v10/_examples/dive/main.go create mode 100644 go-playground/validator/v10/_examples/gin-upgrading-overriding/main.go create mode 100644 go-playground/validator/v10/_examples/gin-upgrading-overriding/v8_to_v9.go create mode 100644 go-playground/validator/v10/_examples/simple/main.go create mode 100644 go-playground/validator/v10/_examples/struct-level/main.go create mode 100644 go-playground/validator/v10/_examples/translations/main.go create mode 100644 go-playground/validator/v10/baked_in.go create mode 100644 go-playground/validator/v10/benchmarks_test.go create mode 100644 go-playground/validator/v10/cache.go create mode 100644 go-playground/validator/v10/country_codes.go create mode 100644 go-playground/validator/v10/doc.go create mode 100644 go-playground/validator/v10/errors.go create mode 100644 go-playground/validator/v10/field_level.go create mode 100644 go-playground/validator/v10/go.sum create mode 100644 go-playground/validator/v10/logo.png create mode 100644 go-playground/validator/v10/non-standard/validators/notblank.go create mode 100644 go-playground/validator/v10/non-standard/validators/notblank_test.go create mode 100644 go-playground/validator/v10/regexes.go create mode 100644 go-playground/validator/v10/struct_level.go create mode 100644 go-playground/validator/v10/testdata/a.go create mode 100644 go-playground/validator/v10/translations.go create mode 100644 go-playground/validator/v10/translations/en/en.go create mode 100644 go-playground/validator/v10/translations/en/en_test.go create mode 100644 go-playground/validator/v10/translations/es/es.go create mode 100644 go-playground/validator/v10/translations/es/es_test.go create mode 100644 go-playground/validator/v10/translations/fr/fr.go create mode 100644 go-playground/validator/v10/translations/fr/fr_test.go create mode 100644 go-playground/validator/v10/translations/id/id.go create mode 100644 go-playground/validator/v10/translations/id/id_test.go create mode 100644 go-playground/validator/v10/translations/ja/ja.go create mode 100644 go-playground/validator/v10/translations/ja/ja_test.go create mode 100644 go-playground/validator/v10/translations/nl/nl.go create mode 100644 go-playground/validator/v10/translations/nl/nl_test.go create mode 100644 go-playground/validator/v10/translations/pt/pt.go create mode 100644 go-playground/validator/v10/translations/pt/pt_test.go create mode 100644 go-playground/validator/v10/translations/pt_BR/pt_BR.go create mode 100644 go-playground/validator/v10/translations/pt_BR/pt_BR_test.go create mode 100644 go-playground/validator/v10/translations/ru/ru.go create mode 100644 go-playground/validator/v10/translations/ru/ru_test.go create mode 100644 go-playground/validator/v10/translations/tr/tr.go create mode 100644 go-playground/validator/v10/translations/tr/tr_test.go create mode 100644 go-playground/validator/v10/translations/zh/zh.go create mode 100644 go-playground/validator/v10/translations/zh/zh_test.go create mode 100644 go-playground/validator/v10/translations/zh_tw/zh_tw.go create mode 100644 go-playground/validator/v10/translations/zh_tw/zh_tw_test.go create mode 100644 go-playground/validator/v10/util.go create mode 100644 go-playground/validator/v10/validator.go create mode 100644 go-playground/validator/v10/validator_instance.go create mode 100644 go-playground/validator/v10/validator_test.go create mode 100644 go.mod create mode 100644 main.go diff --git a/gin/b/b.go b/gin/b/b.go new file mode 100644 index 0000000..d56b65d --- /dev/null +++ b/gin/b/b.go @@ -0,0 +1,62 @@ +package b + +import ( + "gin-valid/gin/binding" + "gin-valid/go-playground/validator/v10" + "mime" + "net/http" +) + +type ValidError struct { + ErrString string +} + +func (e *ValidError) Error() string { + return e.ErrString +} + +func ShouldBind(req *http.Request, obj interface{}) error { + content, err := contentType(req) + if err != nil { + return err + } + b := binding.Default(req.Method, content) + err = ShouldBindWith(req, obj, b) + errs, ok := err.(validator.ValidationErrors) + if !ok { + // 非validator.ValidationErrors类型错误直接返回 + return err + } + return errs.Translate(binding.ValidTrans) +} + +func ShouldBindWith(req *http.Request, obj interface{}, b binding.Binding) error { + return b.Bind(req, obj) +} +func ShouldBindJSON(req *http.Request, obj interface{}) error { + return ShouldBindWith(req, obj, binding.JSON) +} +func ShouldBindHeader(req *http.Request, obj interface{}) error { + return ShouldBindWith(req, obj, binding.Header) +} +func ShouldBindQuery(req *http.Request, obj interface{}) error { + return ShouldBindWith(req, obj, binding.Query) +} + +func contentType(r *http.Request) (string, error) { + ct := r.Header.Get("Content-Type") + if ct == "" { + ct = "application/octet-stream" + } + ct, _, err := mime.ParseMediaType(ct) + return ct, err +} + +func filterFlags(content string) string { + for i, char := range content { + if char == ' ' || char == ';' { + return content[:i] + } + } + return content +} diff --git a/gin/binding/binding.go b/gin/binding/binding.go new file mode 100644 index 0000000..5756284 --- /dev/null +++ b/gin/binding/binding.go @@ -0,0 +1,116 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build !nomsgpack + +package binding + +import "net/http" + +// Content-Type MIME of the most common data formats. +const ( + MIMEJSON = "application/json" + MIMEHTML = "text/html" + MIMEXML = "application/xml" + MIMEXML2 = "text/xml" + MIMEPlain = "text/plain" + MIMEPOSTForm = "application/x-www-form-urlencoded" + MIMEMultipartPOSTForm = "multipart/form-data" + MIMEPROTOBUF = "application/x-protobuf" + MIMEMSGPACK = "application/x-msgpack" + MIMEMSGPACK2 = "application/msgpack" + MIMEYAML = "application/x-yaml" +) + +// Binding describes the interface which needs to be implemented for binding the +// data present in the request such as JSON request body, query parameters or +// the form POST. +type Binding interface { + Name() string + Bind(*http.Request, interface{}) error +} + +// BindingBody adds BindBody method to Binding. BindBody is similar with Bind, +// but it reads the body from supplied bytes instead of req.Body. +type BindingBody interface { + Binding + BindBody([]byte, interface{}) error +} + +// BindingUri adds BindUri method to Binding. BindUri is similar with Bind, +// but it read the Params. +type BindingUri interface { + Name() string + BindUri(map[string][]string, interface{}) error +} + +// StructValidator is the minimal interface which needs to be implemented in +// order for it to be used as the validator engine for ensuring the correctness +// of the request. Gin provides a default implementation for this using +// https://github.com/go-playground/validator/tree/v8.18.2. +type StructValidator interface { + // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. + // If the received type is not a struct, any validation should be skipped and nil must be returned. + // If the received type is a struct or pointer to a struct, the validation should be performed. + // If the struct is not valid or the validation itself fails, a descriptive error should be returned. + // Otherwise nil must be returned. + ValidateStruct(interface{}) error + + // Engine returns the underlying validator engine which powers the + // StructValidator implementation. + Engine() interface{} +} + +// Validator is the default validator which implements the StructValidator +// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2 +// under the hood. +var Validator StructValidator = &defaultValidator{} + +// These implement the Binding interface and can be used to bind the data +// present in the request to struct instances. +var ( + JSON = jsonBinding{} + XML = xmlBinding{} + Form = formBinding{} + Query = queryBinding{} + FormPost = formPostBinding{} + FormMultipart = formMultipartBinding{} + ProtoBuf = protobufBinding{} + MsgPack = msgpackBinding{} + YAML = yamlBinding{} + Uri = uriBinding{} + Header = headerBinding{} +) + +// Default returns the appropriate Binding instance based on the HTTP method +// and the content type. +func Default(method, contentType string) Binding { + if method == http.MethodGet { + return Form + } + + switch contentType { + case MIMEJSON: + return JSON + case MIMEXML, MIMEXML2: + return XML + case MIMEPROTOBUF: + return ProtoBuf + case MIMEMSGPACK, MIMEMSGPACK2: + return MsgPack + case MIMEYAML: + return YAML + case MIMEMultipartPOSTForm: + return FormMultipart + default: // case MIMEPOSTForm: + return Form + } +} + +func validate(obj interface{}) error { + if Validator == nil { + return nil + } + return Validator.ValidateStruct(obj) +} diff --git a/gin/binding/binding_nomsgpack.go b/gin/binding/binding_nomsgpack.go new file mode 100644 index 0000000..fd227b1 --- /dev/null +++ b/gin/binding/binding_nomsgpack.go @@ -0,0 +1,111 @@ +// Copyright 2020 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build nomsgpack + +package binding + +import "net/http" + +// Content-Type MIME of the most common data formats. +const ( + MIMEJSON = "application/json" + MIMEHTML = "text/html" + MIMEXML = "application/xml" + MIMEXML2 = "text/xml" + MIMEPlain = "text/plain" + MIMEPOSTForm = "application/x-www-form-urlencoded" + MIMEMultipartPOSTForm = "multipart/form-data" + MIMEPROTOBUF = "application/x-protobuf" + MIMEYAML = "application/x-yaml" +) + +// Binding describes the interface which needs to be implemented for binding the +// data present in the request such as JSON request body, query parameters or +// the form POST. +type Binding interface { + Name() string + Bind(*http.Request, interface{}) error +} + +// BindingBody adds BindBody method to Binding. BindBody is similar with Bind, +// but it reads the body from supplied bytes instead of req.Body. +type BindingBody interface { + Binding + BindBody([]byte, interface{}) error +} + +// BindingUri adds BindUri method to Binding. BindUri is similar with Bind, +// but it read the Params. +type BindingUri interface { + Name() string + BindUri(map[string][]string, interface{}) error +} + +// StructValidator is the minimal interface which needs to be implemented in +// order for it to be used as the validator engine for ensuring the correctness +// of the request. Gin provides a default implementation for this using +// https://github.com/go-playground/validator/tree/v8.18.2. +type StructValidator interface { + // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. + // If the received type is not a struct, any validation should be skipped and nil must be returned. + // If the received type is a struct or pointer to a struct, the validation should be performed. + // If the struct is not valid or the validation itself fails, a descriptive error should be returned. + // Otherwise nil must be returned. + ValidateStruct(interface{}) error + + // Engine returns the underlying validator engine which powers the + // StructValidator implementation. + Engine() interface{} +} + +// Validator is the default validator which implements the StructValidator +// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2 +// under the hood. +var Validator StructValidator = &defaultValidator{} + +// These implement the Binding interface and can be used to bind the data +// present in the request to struct instances. +var ( + JSON = jsonBinding{} + XML = xmlBinding{} + Form = formBinding{} + Query = queryBinding{} + FormPost = formPostBinding{} + FormMultipart = formMultipartBinding{} + ProtoBuf = protobufBinding{} + YAML = yamlBinding{} + Uri = uriBinding{} + Header = headerBinding{} +) + +// Default returns the appropriate Binding instance based on the HTTP method +// and the content type. +func Default(method, contentType string) Binding { + if method == "GET" { + return Form + } + + switch contentType { + case MIMEJSON: + return JSON + case MIMEXML, MIMEXML2: + return XML + case MIMEPROTOBUF: + return ProtoBuf + case MIMEYAML: + return YAML + case MIMEMultipartPOSTForm: + return FormMultipart + default: // case MIMEPOSTForm: + return Form + } +} + +func validate(obj interface{}) error { + if Validator == nil { + return nil + } + return Validator.ValidateStruct(obj) +} diff --git a/gin/binding/default_validator.go b/gin/binding/default_validator.go new file mode 100644 index 0000000..684e029 --- /dev/null +++ b/gin/binding/default_validator.go @@ -0,0 +1,74 @@ +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "gin-valid/go-playground/validator/v10" + ut "github.com/go-playground/universal-translator" + zhTrans "go-playground/validator/v10/translations/zh" + "go-playground/locales/zh" + "reflect" + "strings" + "sync" + + "go-playground/validator/v10" +) + +type defaultValidator struct { + once sync.Once + validate *validator.Validate +} + +var _ StructValidator = &defaultValidator{} //这是啥情况?yang + +// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type. +func (v *defaultValidator) ValidateStruct(obj interface{}) error { + value := reflect.ValueOf(obj) + valueType := value.Kind() + if valueType == reflect.Ptr { + valueType = value.Elem().Kind() + } + if valueType == reflect.Struct { + v.lazyinit() + if err := v.validate.Struct(obj); err != nil { + return err + } + } + return nil +} + +// Engine returns the underlying validator engine which powers the default +// Validator instance. This is useful if you want to register custom validations +// or struct level validations. See validator GoDoc for more info - +// https://godoc.org/gopkg.in/go-playground/validator.v8 +func (v *defaultValidator) Engine() interface{} { + v.lazyinit() + return v.validate +} + +var ValidTrans ut.Translator + +func (v *defaultValidator) lazyinit() { + v.once.Do(func() { + v.validate = validator.New() + zh := zh.New() + uni := ut.New(zh, zh) + + // this is usually know or extracted from http 'Accept-Language' header + // also see uni.FindTranslator(...) + ValidTrans, _ = uni.GetTranslator("zh") + + zhTrans.RegisterDefaultTranslations(v.validate, ValidTrans) // 为gin的校验 注册翻译 + v.validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("description"), ",", 2)[0] + //if name == "-" { + // return "" + //} + return name + }) + // 设置 tag 的名字 + v.validate.SetTagName("binding") + }) +} diff --git a/gin/binding/form.go b/gin/binding/form.go new file mode 100644 index 0000000..b93c34c --- /dev/null +++ b/gin/binding/form.go @@ -0,0 +1,63 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "net/http" +) + +const defaultMemory = 32 << 20 + +type formBinding struct{} +type formPostBinding struct{} +type formMultipartBinding struct{} + +func (formBinding) Name() string { + return "form" +} + +func (formBinding) Bind(req *http.Request, obj interface{}) error { + if err := req.ParseForm(); err != nil { + return err + } + if err := req.ParseMultipartForm(defaultMemory); err != nil { + if err != http.ErrNotMultipart { + return err + } + } + if err := mapForm(obj, req.Form); err != nil { + return err + } + return validate(obj) +} + +func (formPostBinding) Name() string { + return "form-urlencoded" +} + +func (formPostBinding) Bind(req *http.Request, obj interface{}) error { + if err := req.ParseForm(); err != nil { + return err + } + if err := mapForm(obj, req.PostForm); err != nil { + return err + } + return validate(obj) +} + +func (formMultipartBinding) Name() string { + return "multipart/form-data" +} + +func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { + if err := req.ParseMultipartForm(defaultMemory); err != nil { + return err + } + if err := mappingByPtr(obj, (*multipartRequest)(req), "form"); err != nil { + return err + } + + return validate(obj) +} diff --git a/gin/binding/form_mapping.go b/gin/binding/form_mapping.go new file mode 100644 index 0000000..a2d3b3d --- /dev/null +++ b/gin/binding/form_mapping.go @@ -0,0 +1,392 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "time" + + "gin-valid/gin/internal/bytesconv" + "gin-valid/gin/internal/json" +) + +var errUnknownType = errors.New("unknown type") + +func mapUri(ptr interface{}, m map[string][]string) error { + return mapFormByTag(ptr, m, "uri") +} + +func mapForm(ptr interface{}, form map[string][]string) error { + return mapFormByTag(ptr, form, "form") +} + +var emptyField = reflect.StructField{} + +func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { + // Check if ptr is a map + ptrVal := reflect.ValueOf(ptr) + var pointed interface{} + if ptrVal.Kind() == reflect.Ptr { + ptrVal = ptrVal.Elem() + pointed = ptrVal.Interface() + } + if ptrVal.Kind() == reflect.Map && + ptrVal.Type().Key().Kind() == reflect.String { + if pointed != nil { + ptr = pointed + } + return setFormMap(ptr, form) + } + + return mappingByPtr(ptr, formSource(form), tag) +} + +// setter tries to set value on a walking by fields of a struct +type setter interface { + TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) +} + +type formSource map[string][]string + +var _ setter = formSource(nil) + +// TrySet tries to set a value by request's form source (like map[string][]string) +func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { + return setByForm(value, field, form, tagValue, opt) +} + +func mappingByPtr(ptr interface{}, setter setter, tag string) error { + _, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag) + return err +} + +func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { + if field.Tag.Get(tag) == "-" { // just ignoring this field + return false, nil + } + + var vKind = value.Kind() + + if vKind == reflect.Ptr { + var isNew bool + vPtr := value + if value.IsNil() { + isNew = true + vPtr = reflect.New(value.Type().Elem()) + } + isSetted, err := mapping(vPtr.Elem(), field, setter, tag) + if err != nil { + return false, err + } + if isNew && isSetted { + value.Set(vPtr) + } + return isSetted, nil + } + + if vKind != reflect.Struct || !field.Anonymous { + ok, err := tryToSetValue(value, field, setter, tag) + if err != nil { + return false, err + } + if ok { + return true, nil + } + } + + if vKind == reflect.Struct { + tValue := value.Type() + + var isSetted bool + for i := 0; i < value.NumField(); i++ { + sf := tValue.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { // unexported + continue + } + ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag) + if err != nil { + return false, err + } + isSetted = isSetted || ok + } + return isSetted, nil + } + return false, nil +} + +type setOptions struct { + isDefaultExists bool + defaultValue string +} + +func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { + var tagValue string + var setOpt setOptions + + tagValue = field.Tag.Get(tag) + tagValue, opts := head(tagValue, ",") + + if tagValue == "" { // default value is FieldName + tagValue = field.Name + } + if tagValue == "" { // when field is "emptyField" variable + return false, nil + } + + var opt string + for len(opts) > 0 { + opt, opts = head(opts, ",") + + if k, v := head(opt, "="); k == "default" { + setOpt.isDefaultExists = true + setOpt.defaultValue = v + } + } + + return setter.TrySet(value, field, tagValue, setOpt) +} + +func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) { + vs, ok := form[tagValue] + if !ok && !opt.isDefaultExists { + return false, nil + } + + switch value.Kind() { + case reflect.Slice: + if !ok { + vs = []string{opt.defaultValue} + } + return true, setSlice(vs, value, field) + case reflect.Array: + if !ok { + vs = []string{opt.defaultValue} + } + if len(vs) != value.Len() { + return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) + } + return true, setArray(vs, value, field) + default: + var val string + if !ok { + val = opt.defaultValue + } + + if len(vs) > 0 { + val = vs[0] + } + return true, setWithProperType(val, value, field) + } +} + +func setWithProperType(val string, value reflect.Value, field reflect.StructField) error { + switch value.Kind() { + case reflect.Int: + return setIntField(val, 0, value) + case reflect.Int8: + return setIntField(val, 8, value) + case reflect.Int16: + return setIntField(val, 16, value) + case reflect.Int32: + return setIntField(val, 32, value) + case reflect.Int64: + switch value.Interface().(type) { + case time.Duration: + return setTimeDuration(val, value, field) + } + return setIntField(val, 64, value) + case reflect.Uint: + return setUintField(val, 0, value) + case reflect.Uint8: + return setUintField(val, 8, value) + case reflect.Uint16: + return setUintField(val, 16, value) + case reflect.Uint32: + return setUintField(val, 32, value) + case reflect.Uint64: + return setUintField(val, 64, value) + case reflect.Bool: + return setBoolField(val, value) + case reflect.Float32: + return setFloatField(val, 32, value) + case reflect.Float64: + return setFloatField(val, 64, value) + case reflect.String: + value.SetString(val) + case reflect.Struct: + switch value.Interface().(type) { + case time.Time: + return setTimeField(val, field, value) + } + return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) + case reflect.Map: + return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) + default: + return errUnknownType + } + return nil +} + +func setIntField(val string, bitSize int, field reflect.Value) error { + if val == "" { + val = "0" + } + intVal, err := strconv.ParseInt(val, 10, bitSize) + if err == nil { + field.SetInt(intVal) + } + return err +} + +func setUintField(val string, bitSize int, field reflect.Value) error { + if val == "" { + val = "0" + } + uintVal, err := strconv.ParseUint(val, 10, bitSize) + if err == nil { + field.SetUint(uintVal) + } + return err +} + +func setBoolField(val string, field reflect.Value) error { + if val == "" { + val = "false" + } + boolVal, err := strconv.ParseBool(val) + if err == nil { + field.SetBool(boolVal) + } + return err +} + +func setFloatField(val string, bitSize int, field reflect.Value) error { + if val == "" { + val = "0.0" + } + floatVal, err := strconv.ParseFloat(val, bitSize) + if err == nil { + field.SetFloat(floatVal) + } + return err +} + +func setTimeField(val string, structField reflect.StructField, value reflect.Value) error { + timeFormat := structField.Tag.Get("time_format") + if timeFormat == "" { + timeFormat = time.RFC3339 + } + + switch tf := strings.ToLower(timeFormat); tf { + case "unix", "unixnano": + tv, err := strconv.ParseInt(val, 10, 64) + if err != nil { + return err + } + + d := time.Duration(1) + if tf == "unixnano" { + d = time.Second + } + + t := time.Unix(tv/int64(d), tv%int64(d)) + value.Set(reflect.ValueOf(t)) + return nil + + } + + if val == "" { + value.Set(reflect.ValueOf(time.Time{})) + return nil + } + + l := time.Local + if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC { + l = time.UTC + } + + if locTag := structField.Tag.Get("time_location"); locTag != "" { + loc, err := time.LoadLocation(locTag) + if err != nil { + return err + } + l = loc + } + + t, err := time.ParseInLocation(timeFormat, val, l) + if err != nil { + return err + } + + value.Set(reflect.ValueOf(t)) + return nil +} + +func setArray(vals []string, value reflect.Value, field reflect.StructField) error { + for i, s := range vals { + err := setWithProperType(s, value.Index(i), field) + if err != nil { + return err + } + } + return nil +} + +func setSlice(vals []string, value reflect.Value, field reflect.StructField) error { + slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) + err := setArray(vals, slice, field) + if err != nil { + return err + } + value.Set(slice) + return nil +} + +func setTimeDuration(val string, value reflect.Value, field reflect.StructField) error { + d, err := time.ParseDuration(val) + if err != nil { + return err + } + value.Set(reflect.ValueOf(d)) + return nil +} + +func head(str, sep string) (head string, tail string) { + idx := strings.Index(str, sep) + if idx < 0 { + return str, "" + } + return str[:idx], str[idx+len(sep):] +} + +func setFormMap(ptr interface{}, form map[string][]string) error { + el := reflect.TypeOf(ptr).Elem() + + if el.Kind() == reflect.Slice { + ptrMap, ok := ptr.(map[string][]string) + if !ok { + return errors.New("cannot convert to map slices of strings") + } + for k, v := range form { + ptrMap[k] = v + } + + return nil + } + + ptrMap, ok := ptr.(map[string]string) + if !ok { + return errors.New("cannot convert to map of strings") + } + for k, v := range form { + ptrMap[k] = v[len(v)-1] // pick last + } + + return nil +} diff --git a/gin/binding/form_mapping_benchmark_test.go b/gin/binding/form_mapping_benchmark_test.go new file mode 100644 index 0000000..9572ea0 --- /dev/null +++ b/gin/binding/form_mapping_benchmark_test.go @@ -0,0 +1,67 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +var form = map[string][]string{ + "name": {"mike"}, + "friends": {"anna", "nicole"}, + "id_number": {"12345678"}, + "id_date": {"2018-01-20"}, +} + +type structFull struct { + Name string `form:"name"` + Age int `form:"age,default=25"` + Friends []string `form:"friends"` + ID *struct { + Number string `form:"id_number"` + DateOfIssue time.Time `form:"id_date" time_format:"2006-01-02" time_utc:"true"` + } + Nationality *string `form:"nationality"` +} + +func BenchmarkMapFormFull(b *testing.B) { + var s structFull + for i := 0; i < b.N; i++ { + err := mapForm(&s, form) + if err != nil { + b.Fatalf("Error on a form mapping") + } + } + b.StopTimer() + + t := b + assert.Equal(t, "mike", s.Name) + assert.Equal(t, 25, s.Age) + assert.Equal(t, []string{"anna", "nicole"}, s.Friends) + assert.Equal(t, "12345678", s.ID.Number) + assert.Equal(t, time.Date(2018, 1, 20, 0, 0, 0, 0, time.UTC), s.ID.DateOfIssue) + assert.Nil(t, s.Nationality) +} + +type structName struct { + Name string `form:"name"` +} + +func BenchmarkMapFormName(b *testing.B) { + var s structName + for i := 0; i < b.N; i++ { + err := mapForm(&s, form) + if err != nil { + b.Fatalf("Error on a form mapping") + } + } + b.StopTimer() + + t := b + assert.Equal(t, "mike", s.Name) +} diff --git a/gin/binding/form_mapping_test.go b/gin/binding/form_mapping_test.go new file mode 100644 index 0000000..2675d46 --- /dev/null +++ b/gin/binding/form_mapping_test.go @@ -0,0 +1,281 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestMappingBaseTypes(t *testing.T) { + intPtr := func(i int) *int { + return &i + } + for _, tt := range []struct { + name string + value interface{} + form string + expect interface{} + }{ + {"base type", struct{ F int }{}, "9", int(9)}, + {"base type", struct{ F int8 }{}, "9", int8(9)}, + {"base type", struct{ F int16 }{}, "9", int16(9)}, + {"base type", struct{ F int32 }{}, "9", int32(9)}, + {"base type", struct{ F int64 }{}, "9", int64(9)}, + {"base type", struct{ F uint }{}, "9", uint(9)}, + {"base type", struct{ F uint8 }{}, "9", uint8(9)}, + {"base type", struct{ F uint16 }{}, "9", uint16(9)}, + {"base type", struct{ F uint32 }{}, "9", uint32(9)}, + {"base type", struct{ F uint64 }{}, "9", uint64(9)}, + {"base type", struct{ F bool }{}, "True", true}, + {"base type", struct{ F float32 }{}, "9.1", float32(9.1)}, + {"base type", struct{ F float64 }{}, "9.1", float64(9.1)}, + {"base type", struct{ F string }{}, "test", string("test")}, + {"base type", struct{ F *int }{}, "9", intPtr(9)}, + + // zero values + {"zero value", struct{ F int }{}, "", int(0)}, + {"zero value", struct{ F uint }{}, "", uint(0)}, + {"zero value", struct{ F bool }{}, "", false}, + {"zero value", struct{ F float32 }{}, "", float32(0)}, + } { + tp := reflect.TypeOf(tt.value) + testName := tt.name + ":" + tp.Field(0).Type.String() + + val := reflect.New(reflect.TypeOf(tt.value)) + val.Elem().Set(reflect.ValueOf(tt.value)) + + field := val.Elem().Type().Field(0) + + _, err := mapping(val, emptyField, formSource{field.Name: {tt.form}}, "form") + assert.NoError(t, err, testName) + + actual := val.Elem().Field(0).Interface() + assert.Equal(t, tt.expect, actual, testName) + } +} + +func TestMappingDefault(t *testing.T) { + var s struct { + Int int `form:",default=9"` + Slice []int `form:",default=9"` + Array [1]int `form:",default=9"` + } + err := mappingByPtr(&s, formSource{}, "form") + assert.NoError(t, err) + + assert.Equal(t, 9, s.Int) + assert.Equal(t, []int{9}, s.Slice) + assert.Equal(t, [1]int{9}, s.Array) +} + +func TestMappingSkipField(t *testing.T) { + var s struct { + A int + } + err := mappingByPtr(&s, formSource{}, "form") + assert.NoError(t, err) + + assert.Equal(t, 0, s.A) +} + +func TestMappingIgnoreField(t *testing.T) { + var s struct { + A int `form:"A"` + B int `form:"-"` + } + err := mappingByPtr(&s, formSource{"A": {"9"}, "B": {"9"}}, "form") + assert.NoError(t, err) + + assert.Equal(t, 9, s.A) + assert.Equal(t, 0, s.B) +} + +func TestMappingUnexportedField(t *testing.T) { + var s struct { + A int `form:"a"` + b int `form:"b"` + } + err := mappingByPtr(&s, formSource{"a": {"9"}, "b": {"9"}}, "form") + assert.NoError(t, err) + + assert.Equal(t, 9, s.A) + assert.Equal(t, 0, s.b) +} + +func TestMappingPrivateField(t *testing.T) { + var s struct { + f int `form:"field"` + } + err := mappingByPtr(&s, formSource{"field": {"6"}}, "form") + assert.NoError(t, err) + assert.Equal(t, int(0), s.f) +} + +func TestMappingUnknownFieldType(t *testing.T) { + var s struct { + U uintptr + } + + err := mappingByPtr(&s, formSource{"U": {"unknown"}}, "form") + assert.Error(t, err) + assert.Equal(t, errUnknownType, err) +} + +func TestMappingURI(t *testing.T) { + var s struct { + F int `uri:"field"` + } + err := mapUri(&s, map[string][]string{"field": {"6"}}) + assert.NoError(t, err) + assert.Equal(t, int(6), s.F) +} + +func TestMappingForm(t *testing.T) { + var s struct { + F int `form:"field"` + } + err := mapForm(&s, map[string][]string{"field": {"6"}}) + assert.NoError(t, err) + assert.Equal(t, int(6), s.F) +} + +func TestMappingTime(t *testing.T) { + var s struct { + Time time.Time + LocalTime time.Time `time_format:"2006-01-02"` + ZeroValue time.Time + CSTTime time.Time `time_format:"2006-01-02" time_location:"Asia/Shanghai"` + UTCTime time.Time `time_format:"2006-01-02" time_utc:"1"` + } + + var err error + time.Local, err = time.LoadLocation("Europe/Berlin") + assert.NoError(t, err) + + err = mapForm(&s, map[string][]string{ + "Time": {"2019-01-20T16:02:58Z"}, + "LocalTime": {"2019-01-20"}, + "ZeroValue": {}, + "CSTTime": {"2019-01-20"}, + "UTCTime": {"2019-01-20"}, + }) + assert.NoError(t, err) + + assert.Equal(t, "2019-01-20 16:02:58 +0000 UTC", s.Time.String()) + assert.Equal(t, "2019-01-20 00:00:00 +0100 CET", s.LocalTime.String()) + assert.Equal(t, "2019-01-19 23:00:00 +0000 UTC", s.LocalTime.UTC().String()) + assert.Equal(t, "0001-01-01 00:00:00 +0000 UTC", s.ZeroValue.String()) + assert.Equal(t, "2019-01-20 00:00:00 +0800 CST", s.CSTTime.String()) + assert.Equal(t, "2019-01-19 16:00:00 +0000 UTC", s.CSTTime.UTC().String()) + assert.Equal(t, "2019-01-20 00:00:00 +0000 UTC", s.UTCTime.String()) + + // wrong location + var wrongLoc struct { + Time time.Time `time_location:"wrong"` + } + err = mapForm(&wrongLoc, map[string][]string{"Time": {"2019-01-20T16:02:58Z"}}) + assert.Error(t, err) + + // wrong time value + var wrongTime struct { + Time time.Time + } + err = mapForm(&wrongTime, map[string][]string{"Time": {"wrong"}}) + assert.Error(t, err) +} + +func TestMappingTimeDuration(t *testing.T) { + var s struct { + D time.Duration + } + + // ok + err := mappingByPtr(&s, formSource{"D": {"5s"}}, "form") + assert.NoError(t, err) + assert.Equal(t, 5*time.Second, s.D) + + // error + err = mappingByPtr(&s, formSource{"D": {"wrong"}}, "form") + assert.Error(t, err) +} + +func TestMappingSlice(t *testing.T) { + var s struct { + Slice []int `form:"slice,default=9"` + } + + // default value + err := mappingByPtr(&s, formSource{}, "form") + assert.NoError(t, err) + assert.Equal(t, []int{9}, s.Slice) + + // ok + err = mappingByPtr(&s, formSource{"slice": {"3", "4"}}, "form") + assert.NoError(t, err) + assert.Equal(t, []int{3, 4}, s.Slice) + + // error + err = mappingByPtr(&s, formSource{"slice": {"wrong"}}, "form") + assert.Error(t, err) +} + +func TestMappingArray(t *testing.T) { + var s struct { + Array [2]int `form:"array,default=9"` + } + + // wrong default + err := mappingByPtr(&s, formSource{}, "form") + assert.Error(t, err) + + // ok + err = mappingByPtr(&s, formSource{"array": {"3", "4"}}, "form") + assert.NoError(t, err) + assert.Equal(t, [2]int{3, 4}, s.Array) + + // error - not enough vals + err = mappingByPtr(&s, formSource{"array": {"3"}}, "form") + assert.Error(t, err) + + // error - wrong value + err = mappingByPtr(&s, formSource{"array": {"wrong"}}, "form") + assert.Error(t, err) +} + +func TestMappingStructField(t *testing.T) { + var s struct { + J struct { + I int + } + } + + err := mappingByPtr(&s, formSource{"J": {`{"I": 9}`}}, "form") + assert.NoError(t, err) + assert.Equal(t, 9, s.J.I) +} + +func TestMappingMapField(t *testing.T) { + var s struct { + M map[string]int + } + + err := mappingByPtr(&s, formSource{"M": {`{"one": 1}`}}, "form") + assert.NoError(t, err) + assert.Equal(t, map[string]int{"one": 1}, s.M) +} + +func TestMappingIgnoredCircularRef(t *testing.T) { + type S struct { + S *S `form:"-"` + } + var s S + + err := mappingByPtr(&s, formSource{}, "form") + assert.NoError(t, err) +} diff --git a/gin/binding/header.go b/gin/binding/header.go new file mode 100644 index 0000000..179ce4e --- /dev/null +++ b/gin/binding/header.go @@ -0,0 +1,34 @@ +package binding + +import ( + "net/http" + "net/textproto" + "reflect" +) + +type headerBinding struct{} + +func (headerBinding) Name() string { + return "header" +} + +func (headerBinding) Bind(req *http.Request, obj interface{}) error { + + if err := mapHeader(obj, req.Header); err != nil { + return err + } + + return validate(obj) +} + +func mapHeader(ptr interface{}, h map[string][]string) error { + return mappingByPtr(ptr, headerSource(h), "header") +} + +type headerSource map[string][]string + +var _ setter = headerSource(nil) + +func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { + return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt) +} diff --git a/gin/binding/json.go b/gin/binding/json.go new file mode 100644 index 0000000..26d390f --- /dev/null +++ b/gin/binding/json.go @@ -0,0 +1,56 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "bytes" + "fmt" + "io" + "net/http" + + "gin-valid/gin/internal/json" +) + +// EnableDecoderUseNumber is used to call the UseNumber method on the JSON +// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an +// interface{} as a Number instead of as a float64. +var EnableDecoderUseNumber = false + +// EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method +// on the JSON Decoder instance. DisallowUnknownFields causes the Decoder to +// return an error when the destination is a struct and the input contains object +// keys which do not match any non-ignored, exported fields in the destination. +var EnableDecoderDisallowUnknownFields = false + +type jsonBinding struct{} + +func (jsonBinding) Name() string { + return "json" +} + +func (jsonBinding) Bind(req *http.Request, obj interface{}) error { + if req == nil || req.Body == nil { + return fmt.Errorf("invalid request") + } + return decodeJSON(req.Body, obj) +} + +func (jsonBinding) BindBody(body []byte, obj interface{}) error { + return decodeJSON(bytes.NewReader(body), obj) +} + +func decodeJSON(r io.Reader, obj interface{}) error { + decoder := json.NewDecoder(r) + if EnableDecoderUseNumber { + decoder.UseNumber() + } + if EnableDecoderDisallowUnknownFields { + decoder.DisallowUnknownFields() + } + if err := decoder.Decode(obj); err != nil { + return err + } + return validate(obj) +} diff --git a/gin/binding/json_test.go b/gin/binding/json_test.go new file mode 100644 index 0000000..0f61a20 --- /dev/null +++ b/gin/binding/json_test.go @@ -0,0 +1,30 @@ +/ Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestJSONBindingBindBody(t *testing.T) { + var s struct { + Foo string `json:"foo"` + } + err := jsonBinding{}.BindBody([]byte(`{"foo": "FOO"}`), &s) + require.NoError(t, err) + assert.Equal(t, "FOO", s.Foo) +} + +func TestJSONBindingBindBodyMap(t *testing.T) { + s := make(map[string]string) + err := jsonBinding{}.BindBody([]byte(`{"foo": "FOO","hello":"world"}`), &s) + require.NoError(t, err) + assert.Len(t, s, 2) + assert.Equal(t, "FOO", s["foo"]) + assert.Equal(t, "world", s["hello"]) +} diff --git a/gin/binding/msgpack.go b/gin/binding/msgpack.go new file mode 100644 index 0000000..a5bc2ad --- /dev/null +++ b/gin/binding/msgpack.go @@ -0,0 +1,37 @@ +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build !nomsgpack + +package binding + +import ( + "bytes" + "io" + "net/http" + + "github.com/ugorji/go/codec" +) + +type msgpackBinding struct{} + +func (msgpackBinding) Name() string { + return "msgpack" +} + +func (msgpackBinding) Bind(req *http.Request, obj interface{}) error { + return decodeMsgPack(req.Body, obj) +} + +func (msgpackBinding) BindBody(body []byte, obj interface{}) error { + return decodeMsgPack(bytes.NewReader(body), obj) +} + +func decodeMsgPack(r io.Reader, obj interface{}) error { + cdc := new(codec.MsgpackHandle) + if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil { + return err + } + return validate(obj) +} diff --git a/gin/binding/msgpack_test.go b/gin/binding/msgpack_test.go new file mode 100644 index 0000000..296d3eb --- /dev/null +++ b/gin/binding/msgpack_test.go @@ -0,0 +1,34 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build !nomsgpack + +package binding + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/ugorji/go/codec" +) + +func TestMsgpackBindingBindBody(t *testing.T) { + type teststruct struct { + Foo string `msgpack:"foo"` + } + var s teststruct + err := msgpackBinding{}.BindBody(msgpackBody(t, teststruct{"FOO"}), &s) + require.NoError(t, err) + assert.Equal(t, "FOO", s.Foo) +} + +func msgpackBody(t *testing.T, obj interface{}) []byte { + var bs bytes.Buffer + h := &codec.MsgpackHandle{} + err := codec.NewEncoder(&bs, h).Encode(obj) + require.NoError(t, err) + return bs.Bytes() +} diff --git a/gin/binding/multipart_form_mapping.go b/gin/binding/multipart_form_mapping.go new file mode 100644 index 0000000..f85a1aa --- /dev/null +++ b/gin/binding/multipart_form_mapping.go @@ -0,0 +1,66 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "errors" + "mime/multipart" + "net/http" + "reflect" +) + +type multipartRequest http.Request + +var _ setter = (*multipartRequest)(nil) + +// TrySet tries to set a value by the multipart request with the binding a form file +func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) { + if files := r.MultipartForm.File[key]; len(files) != 0 { + return setByMultipartFormFile(value, field, files) + } + + return setByForm(value, field, r.MultipartForm.Value, key, opt) +} + +func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) { + switch value.Kind() { + case reflect.Ptr: + switch value.Interface().(type) { + case *multipart.FileHeader: + value.Set(reflect.ValueOf(files[0])) + return true, nil + } + case reflect.Struct: + switch value.Interface().(type) { + case multipart.FileHeader: + value.Set(reflect.ValueOf(*files[0])) + return true, nil + } + case reflect.Slice: + slice := reflect.MakeSlice(value.Type(), len(files), len(files)) + isSetted, err = setArrayOfMultipartFormFiles(slice, field, files) + if err != nil || !isSetted { + return isSetted, err + } + value.Set(slice) + return true, nil + case reflect.Array: + return setArrayOfMultipartFormFiles(value, field, files) + } + return false, errors.New("unsupported field type for multipart.FileHeader") +} + +func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) { + if value.Len() != len(files) { + return false, errors.New("unsupported len of array for []*multipart.FileHeader") + } + for i := range files { + setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1]) + if err != nil || !setted { + return setted, err + } + } + return true, nil +} diff --git a/gin/binding/multipart_form_mapping_test.go b/gin/binding/multipart_form_mapping_test.go new file mode 100644 index 0000000..4c75d1f --- /dev/null +++ b/gin/binding/multipart_form_mapping_test.go @@ -0,0 +1,138 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "bytes" + "io/ioutil" + "mime/multipart" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFormMultipartBindingBindOneFile(t *testing.T) { + var s struct { + FileValue multipart.FileHeader `form:"file"` + FilePtr *multipart.FileHeader `form:"file"` + SliceValues []multipart.FileHeader `form:"file"` + SlicePtrs []*multipart.FileHeader `form:"file"` + ArrayValues [1]multipart.FileHeader `form:"file"` + ArrayPtrs [1]*multipart.FileHeader `form:"file"` + } + file := testFile{"file", "file1", []byte("hello")} + + req := createRequestMultipartFiles(t, file) + err := FormMultipart.Bind(req, &s) + assert.NoError(t, err) + + assertMultipartFileHeader(t, &s.FileValue, file) + assertMultipartFileHeader(t, s.FilePtr, file) + assert.Len(t, s.SliceValues, 1) + assertMultipartFileHeader(t, &s.SliceValues[0], file) + assert.Len(t, s.SlicePtrs, 1) + assertMultipartFileHeader(t, s.SlicePtrs[0], file) + assertMultipartFileHeader(t, &s.ArrayValues[0], file) + assertMultipartFileHeader(t, s.ArrayPtrs[0], file) +} + +func TestFormMultipartBindingBindTwoFiles(t *testing.T) { + var s struct { + SliceValues []multipart.FileHeader `form:"file"` + SlicePtrs []*multipart.FileHeader `form:"file"` + ArrayValues [2]multipart.FileHeader `form:"file"` + ArrayPtrs [2]*multipart.FileHeader `form:"file"` + } + files := []testFile{ + {"file", "file1", []byte("hello")}, + {"file", "file2", []byte("world")}, + } + + req := createRequestMultipartFiles(t, files...) + err := FormMultipart.Bind(req, &s) + assert.NoError(t, err) + + assert.Len(t, s.SliceValues, len(files)) + assert.Len(t, s.SlicePtrs, len(files)) + assert.Len(t, s.ArrayValues, len(files)) + assert.Len(t, s.ArrayPtrs, len(files)) + + for i, file := range files { + assertMultipartFileHeader(t, &s.SliceValues[i], file) + assertMultipartFileHeader(t, s.SlicePtrs[i], file) + assertMultipartFileHeader(t, &s.ArrayValues[i], file) + assertMultipartFileHeader(t, s.ArrayPtrs[i], file) + } +} + +func TestFormMultipartBindingBindError(t *testing.T) { + files := []testFile{ + {"file", "file1", []byte("hello")}, + {"file", "file2", []byte("world")}, + } + + for _, tt := range []struct { + name string + s interface{} + }{ + {"wrong type", &struct { + Files int `form:"file"` + }{}}, + {"wrong array size", &struct { + Files [1]*multipart.FileHeader `form:"file"` + }{}}, + {"wrong slice type", &struct { + Files []int `form:"file"` + }{}}, + } { + req := createRequestMultipartFiles(t, files...) + err := FormMultipart.Bind(req, tt.s) + assert.Error(t, err) + } +} + +type testFile struct { + Fieldname string + Filename string + Content []byte +} + +func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request { + var body bytes.Buffer + + mw := multipart.NewWriter(&body) + for _, file := range files { + fw, err := mw.CreateFormFile(file.Fieldname, file.Filename) + assert.NoError(t, err) + + n, err := fw.Write(file.Content) + assert.NoError(t, err) + assert.Equal(t, len(file.Content), n) + } + err := mw.Close() + assert.NoError(t, err) + + req, err := http.NewRequest("POST", "/", &body) + assert.NoError(t, err) + + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+mw.Boundary()) + return req +} + +func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file testFile) { + assert.Equal(t, file.Filename, fh.Filename) + // assert.Equal(t, int64(len(file.Content)), fh.Size) // fh.Size does not exist on go1.8 + + fl, err := fh.Open() + assert.NoError(t, err) + + body, err := ioutil.ReadAll(fl) + assert.NoError(t, err) + assert.Equal(t, string(file.Content), string(body)) + + err = fl.Close() + assert.NoError(t, err) +} diff --git a/gin/binding/protobuf.go b/gin/binding/protobuf.go new file mode 100644 index 0000000..f9ece92 --- /dev/null +++ b/gin/binding/protobuf.go @@ -0,0 +1,36 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "io/ioutil" + "net/http" + + "github.com/golang/protobuf/proto" +) + +type protobufBinding struct{} + +func (protobufBinding) Name() string { + return "protobuf" +} + +func (b protobufBinding) Bind(req *http.Request, obj interface{}) error { + buf, err := ioutil.ReadAll(req.Body) + if err != nil { + return err + } + return b.BindBody(buf, obj) +} + +func (protobufBinding) BindBody(body []byte, obj interface{}) error { + if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil { + return err + } + // Here it's same to return validate(obj), but util now we can't add + // `binding:""` to the struct which automatically generate by gen-proto + return nil + // return validate(obj) +} diff --git a/gin/binding/query.go b/gin/binding/query.go new file mode 100644 index 0000000..219743f --- /dev/null +++ b/gin/binding/query.go @@ -0,0 +1,21 @@ +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import "net/http" + +type queryBinding struct{} + +func (queryBinding) Name() string { + return "query" +} + +func (queryBinding) Bind(req *http.Request, obj interface{}) error { + values := req.URL.Query() + if err := mapForm(obj, values); err != nil { + return err + } + return validate(obj) +} diff --git a/gin/binding/uri.go b/gin/binding/uri.go new file mode 100644 index 0000000..f91ec38 --- /dev/null +++ b/gin/binding/uri.go @@ -0,0 +1,18 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +type uriBinding struct{} + +func (uriBinding) Name() string { + return "uri" +} + +func (uriBinding) BindUri(m map[string][]string, obj interface{}) error { + if err := mapUri(obj, m); err != nil { + return err + } + return validate(obj) +} diff --git a/gin/binding/validate_test.go b/gin/binding/validate_test.go new file mode 100644 index 0000000..18bc8ec --- /dev/null +++ b/gin/binding/validate_test.go @@ -0,0 +1,228 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "bytes" + "testing" + "time" + + "go-playground/validator/v10" + "github.com/stretchr/testify/assert" +) + +type testInterface interface { + String() string +} + +type substructNoValidation struct { + IString string + IInt int +} + +type mapNoValidationSub map[string]substructNoValidation + +type structNoValidationValues struct { + substructNoValidation + + Boolean bool + + Uinteger uint + Integer int + Integer8 int8 + Integer16 int16 + Integer32 int32 + Integer64 int64 + Uinteger8 uint8 + Uinteger16 uint16 + Uinteger32 uint32 + Uinteger64 uint64 + + Float32 float32 + Float64 float64 + + String string + + Date time.Time + + Struct substructNoValidation + InlinedStruct struct { + String []string + Integer int + } + + IntSlice []int + IntPointerSlice []*int + StructPointerSlice []*substructNoValidation + StructSlice []substructNoValidation + InterfaceSlice []testInterface + + UniversalInterface interface{} + CustomInterface testInterface + + FloatMap map[string]float32 + StructMap mapNoValidationSub +} + +func createNoValidationValues() structNoValidationValues { + integer := 1 + s := structNoValidationValues{ + Boolean: true, + Uinteger: 1 << 29, + Integer: -10000, + Integer8: 120, + Integer16: -20000, + Integer32: 1 << 29, + Integer64: 1 << 61, + Uinteger8: 250, + Uinteger16: 50000, + Uinteger32: 1 << 31, + Uinteger64: 1 << 62, + Float32: 123.456, + Float64: 123.456789, + String: "text", + Date: time.Time{}, + CustomInterface: &bytes.Buffer{}, + Struct: substructNoValidation{}, + IntSlice: []int{-3, -2, 1, 0, 1, 2, 3}, + IntPointerSlice: []*int{&integer}, + StructSlice: []substructNoValidation{}, + UniversalInterface: 1.2, + FloatMap: map[string]float32{ + "foo": 1.23, + "bar": 232.323, + }, + StructMap: mapNoValidationSub{ + "foo": substructNoValidation{}, + "bar": substructNoValidation{}, + }, + // StructPointerSlice []noValidationSub + // InterfaceSlice []testInterface + } + s.InlinedStruct.Integer = 1000 + s.InlinedStruct.String = []string{"first", "second"} + s.IString = "substring" + s.IInt = 987654 + return s +} + +func TestValidateNoValidationValues(t *testing.T) { + origin := createNoValidationValues() + test := createNoValidationValues() + empty := structNoValidationValues{} + + assert.Nil(t, validate(test)) + assert.Nil(t, validate(&test)) + assert.Nil(t, validate(empty)) + assert.Nil(t, validate(&empty)) + + assert.Equal(t, origin, test) +} + +type structNoValidationPointer struct { + substructNoValidation + + Boolean bool + + Uinteger *uint + Integer *int + Integer8 *int8 + Integer16 *int16 + Integer32 *int32 + Integer64 *int64 + Uinteger8 *uint8 + Uinteger16 *uint16 + Uinteger32 *uint32 + Uinteger64 *uint64 + + Float32 *float32 + Float64 *float64 + + String *string + + Date *time.Time + + Struct *substructNoValidation + + IntSlice *[]int + IntPointerSlice *[]*int + StructPointerSlice *[]*substructNoValidation + StructSlice *[]substructNoValidation + InterfaceSlice *[]testInterface + + FloatMap *map[string]float32 + StructMap *mapNoValidationSub +} + +func TestValidateNoValidationPointers(t *testing.T) { + //origin := createNoValidation_values() + //test := createNoValidation_values() + empty := structNoValidationPointer{} + + //assert.Nil(t, validate(test)) + //assert.Nil(t, validate(&test)) + assert.Nil(t, validate(empty)) + assert.Nil(t, validate(&empty)) + + //assert.Equal(t, origin, test) +} + +type Object map[string]interface{} + +func TestValidatePrimitives(t *testing.T) { + obj := Object{"foo": "bar", "bar": 1} + assert.NoError(t, validate(obj)) + assert.NoError(t, validate(&obj)) + assert.Equal(t, Object{"foo": "bar", "bar": 1}, obj) + + obj2 := []Object{{"foo": "bar", "bar": 1}, {"foo": "bar", "bar": 1}} + assert.NoError(t, validate(obj2)) + assert.NoError(t, validate(&obj2)) + + nu := 10 + assert.NoError(t, validate(nu)) + assert.NoError(t, validate(&nu)) + assert.Equal(t, 10, nu) + + str := "value" + assert.NoError(t, validate(str)) + assert.NoError(t, validate(&str)) + assert.Equal(t, "value", str) +} + +// structCustomValidation is a helper struct we use to check that +// custom validation can be registered on it. +// The `notone` binding directive is for custom validation and registered later. +type structCustomValidation struct { + Integer int `binding:"notone"` +} + +func notOne(f1 validator.FieldLevel) bool { + if val, ok := f1.Field().Interface().(int); ok { + return val != 1 + } + return false +} + +func TestValidatorEngine(t *testing.T) { + // This validates that the function `notOne` matches + // the expected function signature by `defaultValidator` + // and by extension the validator library. + engine, ok := Validator.Engine().(*validator.Validate) + assert.True(t, ok) + + err := engine.RegisterValidation("notone", notOne) + // Check that we can register custom validation without error + assert.Nil(t, err) + + // Create an instance which will fail validation + withOne := structCustomValidation{Integer: 1} + errs := validate(withOne) + + // Check that we got back non-nil errs + assert.NotNil(t, errs) + // Check that the error matches expectation + assert.Error(t, errs, "", "", "notone") +} diff --git a/gin/binding/xml.go b/gin/binding/xml.go new file mode 100644 index 0000000..4e90114 --- /dev/null +++ b/gin/binding/xml.go @@ -0,0 +1,33 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "bytes" + "encoding/xml" + "io" + "net/http" +) + +type xmlBinding struct{} + +func (xmlBinding) Name() string { + return "xml" +} + +func (xmlBinding) Bind(req *http.Request, obj interface{}) error { + return decodeXML(req.Body, obj) +} + +func (xmlBinding) BindBody(body []byte, obj interface{}) error { + return decodeXML(bytes.NewReader(body), obj) +} +func decodeXML(r io.Reader, obj interface{}) error { + decoder := xml.NewDecoder(r) + if err := decoder.Decode(obj); err != nil { + return err + } + return validate(obj) +} diff --git a/gin/binding/xml_test.go b/gin/binding/xml_test.go new file mode 100644 index 0000000..f9546c1 --- /dev/null +++ b/gin/binding/xml_test.go @@ -0,0 +1,25 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestXMLBindingBindBody(t *testing.T) { + var s struct { + Foo string `xml:"foo"` + } + xmlBody := ` + + FOO +` + err := xmlBinding{}.BindBody([]byte(xmlBody), &s) + require.NoError(t, err) + assert.Equal(t, "FOO", s.Foo) +} diff --git a/gin/binding/yaml.go b/gin/binding/yaml.go new file mode 100644 index 0000000..a2d36d6 --- /dev/null +++ b/gin/binding/yaml.go @@ -0,0 +1,35 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "bytes" + "io" + "net/http" + + "gopkg.in/yaml.v2" +) + +type yamlBinding struct{} + +func (yamlBinding) Name() string { + return "yaml" +} + +func (yamlBinding) Bind(req *http.Request, obj interface{}) error { + return decodeYAML(req.Body, obj) +} + +func (yamlBinding) BindBody(body []byte, obj interface{}) error { + return decodeYAML(bytes.NewReader(body), obj) +} + +func decodeYAML(r io.Reader, obj interface{}) error { + decoder := yaml.NewDecoder(r) + if err := decoder.Decode(obj); err != nil { + return err + } + return validate(obj) +} diff --git a/gin/binding/yaml_test.go b/gin/binding/yaml_test.go new file mode 100644 index 0000000..e66338b --- /dev/null +++ b/gin/binding/yaml_test.go @@ -0,0 +1,21 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestYAMLBindingBindBody(t *testing.T) { + var s struct { + Foo string `yaml:"foo"` + } + err := yamlBinding{}.BindBody([]byte("foo: FOO"), &s) + require.NoError(t, err) + assert.Equal(t, "FOO", s.Foo) +} diff --git a/gin/internal/bytesconv/bytesconv.go b/gin/internal/bytesconv/bytesconv.go new file mode 100644 index 0000000..7b80e33 --- /dev/null +++ b/gin/internal/bytesconv/bytesconv.go @@ -0,0 +1,23 @@ +// Copyright 2020 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package bytesconv + +import ( + "reflect" + "unsafe" +) + +// StringToBytes converts string to byte slice without a memory allocation. +func StringToBytes(s string) (b []byte) { + sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) + bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len + return b +} + +// BytesToString converts byte slice to string without a memory allocation. +func BytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} diff --git a/gin/internal/bytesconv/bytesconv_test.go b/gin/internal/bytesconv/bytesconv_test.go new file mode 100644 index 0000000..eeaad5e --- /dev/null +++ b/gin/internal/bytesconv/bytesconv_test.go @@ -0,0 +1,99 @@ +// Copyright 2020 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package bytesconv + +import ( + "bytes" + "math/rand" + "strings" + "testing" + "time" +) + +var testString = "Albert Einstein: Logic will get you from A to B. Imagination will take you everywhere." +var testBytes = []byte(testString) + +func rawBytesToStr(b []byte) string { + return string(b) +} + +func rawStrToBytes(s string) []byte { + return []byte(s) +} + +// go test -v + +func TestBytesToString(t *testing.T) { + data := make([]byte, 1024) + for i := 0; i < 100; i++ { + rand.Read(data) + if rawBytesToStr(data) != BytesToString(data) { + t.Fatal("don't match") + } + } +} + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = src.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + sb.WriteByte(letterBytes[idx]) + i-- + } + cache >>= letterIdxBits + remain-- + } + + return sb.String() +} + +func TestStringToBytes(t *testing.T) { + for i := 0; i < 100; i++ { + s := RandStringBytesMaskImprSrcSB(64) + if !bytes.Equal(rawStrToBytes(s), StringToBytes(s)) { + t.Fatal("don't match") + } + } +} + +// go test -v -run=none -bench=^BenchmarkBytesConv -benchmem=true + +func BenchmarkBytesConvBytesToStrRaw(b *testing.B) { + for i := 0; i < b.N; i++ { + rawBytesToStr(testBytes) + } +} + +func BenchmarkBytesConvBytesToStr(b *testing.B) { + for i := 0; i < b.N; i++ { + BytesToString(testBytes) + } +} + +func BenchmarkBytesConvStrToBytesRaw(b *testing.B) { + for i := 0; i < b.N; i++ { + rawStrToBytes(testString) + } +} + +func BenchmarkBytesConvStrToBytes(b *testing.B) { + for i := 0; i < b.N; i++ { + StringToBytes(testString) + } +} diff --git a/gin/internal/json/json.go b/gin/internal/json/json.go new file mode 100644 index 0000000..480e8bf --- /dev/null +++ b/gin/internal/json/json.go @@ -0,0 +1,22 @@ +// Copyright 2017 Bo-Yi Wu. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build !jsoniter + +package json + +import "encoding/json" + +var ( + // Marshal is exported by gin/json package. + Marshal = json.Marshal + // Unmarshal is exported by gin/json package. + Unmarshal = json.Unmarshal + // MarshalIndent is exported by gin/json package. + MarshalIndent = json.MarshalIndent + // NewDecoder is exported by gin/json package. + NewDecoder = json.NewDecoder + // NewEncoder is exported by gin/json package. + NewEncoder = json.NewEncoder +) diff --git a/gin/internal/json/jsoniter.go b/gin/internal/json/jsoniter.go new file mode 100644 index 0000000..649a3cd --- /dev/null +++ b/gin/internal/json/jsoniter.go @@ -0,0 +1,23 @@ +// Copyright 2017 Bo-Yi Wu. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build jsoniter + +package json + +import jsoniter "github.com/json-iterator/go" + +var ( + json = jsoniter.ConfigCompatibleWithStandardLibrary + // Marshal is exported by gin/json package. + Marshal = json.Marshal + // Unmarshal is exported by gin/json package. + Unmarshal = json.Unmarshal + // MarshalIndent is exported by gin/json package. + MarshalIndent = json.MarshalIndent + // NewDecoder is exported by gin/json package. + NewDecoder = json.NewDecoder + // NewEncoder is exported by gin/json package. + NewEncoder = json.NewEncoder +) diff --git a/go-playground/locales/.gitignore b/go-playground/locales/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/go-playground/locales/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/go-playground/locales/.travis.yml b/go-playground/locales/.travis.yml new file mode 100644 index 0000000..d50237a --- /dev/null +++ b/go-playground/locales/.travis.yml @@ -0,0 +1,26 @@ +language: go +go: + - 1.13.1 + - tip +matrix: + allow_failures: + - go: tip + +notifications: + email: + recipients: dean.karn@gmail.com + on_success: change + on_failure: always + +before_install: + - go install github.com/mattn/goveralls + +# Only clone the most recent commit. +git: + depth: 1 + +script: + - go test -v -race -covermode=atomic -coverprofile=coverage.coverprofile ./... + +after_success: | + goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN \ No newline at end of file diff --git a/go-playground/locales/LICENSE b/go-playground/locales/LICENSE new file mode 100644 index 0000000..75854ac --- /dev/null +++ b/go-playground/locales/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Go Playground + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/go-playground/locales/README.md b/go-playground/locales/README.md new file mode 100644 index 0000000..db4c4d0 --- /dev/null +++ b/go-playground/locales/README.md @@ -0,0 +1,172 @@ +## locales +![Project status](https://img.shields.io/badge/version-0.13.0-green.svg) +[![Build Status](https://travis-ci.org/go-playground/locales.svg?branch=master)](https://travis-ci.org/go-playground/locales) +[![Go Report Card](https://goreportcard.com/badge/go-playground/locales)](https://goreportcard.com/report/go-playground/locales) +[![GoDoc](https://godoc.org/go-playground/locales?status.svg)](https://godoc.org/go-playground/locales) +![License](https://img.shields.io/dub/l/vibe-d.svg) +[![Gitter](https://badges.gitter.im/go-playground/locales.svg)](https://gitter.im/go-playground/locales?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) + +Locales is a set of locales generated from the [Unicode CLDR Project](http://cldr.unicode.org/) which can be used independently or within +an i18n package; these were built for use with, but not exclusive to, [Universal Translator](https://github.com/go-playground/universal-translator). + +Features +-------- +- [x] Rules generated from the latest [CLDR](http://cldr.unicode.org/index/downloads) data, v31.0.1 +- [x] Contains Cardinal, Ordinal and Range Plural Rules +- [x] Contains Month, Weekday and Timezone translations built in +- [x] Contains Date & Time formatting functions +- [x] Contains Number, Currency, Accounting and Percent formatting functions +- [x] Supports the "Gregorian" calendar only ( my time isn't unlimited, had to draw the line somewhere ) + +Full Tests +-------------------- +I could sure use your help adding tests for every locale, it is a huge undertaking and I just don't have the free time to do it all at the moment; +any help would be **greatly appreciated!!!!** please see [issue](https://go-playground/locales/issues/1) for details. + +Installation +----------- + +Use go get + +```shell +go get go-playground/locales +``` + +NOTES +-------- +You'll notice most return types are []byte, this is because most of the time the results will be concatenated with a larger body +of text and can avoid some allocations if already appending to a byte array, otherwise just cast as string. + +Usage +------- +```go +package main + +import ( + "fmt" + "time" + + "go-playground/locales/currency" + "go-playground/locales/en_CA" +) + +func main() { + + loc, _ := time.LoadLocation("America/Toronto") + datetime := time.Date(2016, 02, 03, 9, 0, 1, 0, loc) + + l := en_CA.New() + + // Dates + fmt.Println(l.FmtDateFull(datetime)) + fmt.Println(l.FmtDateLong(datetime)) + fmt.Println(l.FmtDateMedium(datetime)) + fmt.Println(l.FmtDateShort(datetime)) + + // Times + fmt.Println(l.FmtTimeFull(datetime)) + fmt.Println(l.FmtTimeLong(datetime)) + fmt.Println(l.FmtTimeMedium(datetime)) + fmt.Println(l.FmtTimeShort(datetime)) + + // Months Wide + fmt.Println(l.MonthWide(time.January)) + fmt.Println(l.MonthWide(time.February)) + fmt.Println(l.MonthWide(time.March)) + // ... + + // Months Abbreviated + fmt.Println(l.MonthAbbreviated(time.January)) + fmt.Println(l.MonthAbbreviated(time.February)) + fmt.Println(l.MonthAbbreviated(time.March)) + // ... + + // Months Narrow + fmt.Println(l.MonthNarrow(time.January)) + fmt.Println(l.MonthNarrow(time.February)) + fmt.Println(l.MonthNarrow(time.March)) + // ... + + // Weekdays Wide + fmt.Println(l.WeekdayWide(time.Sunday)) + fmt.Println(l.WeekdayWide(time.Monday)) + fmt.Println(l.WeekdayWide(time.Tuesday)) + // ... + + // Weekdays Abbreviated + fmt.Println(l.WeekdayAbbreviated(time.Sunday)) + fmt.Println(l.WeekdayAbbreviated(time.Monday)) + fmt.Println(l.WeekdayAbbreviated(time.Tuesday)) + // ... + + // Weekdays Short + fmt.Println(l.WeekdayShort(time.Sunday)) + fmt.Println(l.WeekdayShort(time.Monday)) + fmt.Println(l.WeekdayShort(time.Tuesday)) + // ... + + // Weekdays Narrow + fmt.Println(l.WeekdayNarrow(time.Sunday)) + fmt.Println(l.WeekdayNarrow(time.Monday)) + fmt.Println(l.WeekdayNarrow(time.Tuesday)) + // ... + + var f64 float64 + + f64 = -10356.4523 + + // Number + fmt.Println(l.FmtNumber(f64, 2)) + + // Currency + fmt.Println(l.FmtCurrency(f64, 2, currency.CAD)) + fmt.Println(l.FmtCurrency(f64, 2, currency.USD)) + + // Accounting + fmt.Println(l.FmtAccounting(f64, 2, currency.CAD)) + fmt.Println(l.FmtAccounting(f64, 2, currency.USD)) + + f64 = 78.12 + + // Percent + fmt.Println(l.FmtPercent(f64, 0)) + + // Plural Rules for locale, so you know what rules you must cover + fmt.Println(l.PluralsCardinal()) + fmt.Println(l.PluralsOrdinal()) + + // Cardinal Plural Rules + fmt.Println(l.CardinalPluralRule(1, 0)) + fmt.Println(l.CardinalPluralRule(1.0, 0)) + fmt.Println(l.CardinalPluralRule(1.0, 1)) + fmt.Println(l.CardinalPluralRule(3, 0)) + + // Ordinal Plural Rules + fmt.Println(l.OrdinalPluralRule(21, 0)) // 21st + fmt.Println(l.OrdinalPluralRule(22, 0)) // 22nd + fmt.Println(l.OrdinalPluralRule(33, 0)) // 33rd + fmt.Println(l.OrdinalPluralRule(34, 0)) // 34th + + // Range Plural Rules + fmt.Println(l.RangePluralRule(1, 0, 1, 0)) // 1-1 + fmt.Println(l.RangePluralRule(1, 0, 2, 0)) // 1-2 + fmt.Println(l.RangePluralRule(5, 0, 8, 0)) // 5-8 +} +``` + +NOTES: +------- +These rules were generated from the [Unicode CLDR Project](http://cldr.unicode.org/), if you encounter any issues +I strongly encourage contributing to the CLDR project to get the locale information corrected and the next time +these locales are regenerated the fix will come with. + +I do however realize that time constraints are often important and so there are two options: + +1. Create your own locale, copy, paste and modify, and ensure it complies with the `Translator` interface. +2. Add an exception in the locale generation code directly and once regenerated, fix will be in place. + +Please to not make fixes inside the locale files, they WILL get overwritten when the locales are regenerated. + +License +------ +Distributed under MIT License, please see license file in code for more details. diff --git a/go-playground/locales/currency/currency.go b/go-playground/locales/currency/currency.go new file mode 100644 index 0000000..cdaba59 --- /dev/null +++ b/go-playground/locales/currency/currency.go @@ -0,0 +1,308 @@ +package currency + +// Type is the currency type associated with the locales currency enum +type Type int + +// locale currencies +const ( + ADP Type = iota + AED + AFA + AFN + ALK + ALL + AMD + ANG + AOA + AOK + AON + AOR + ARA + ARL + ARM + ARP + ARS + ATS + AUD + AWG + AZM + AZN + BAD + BAM + BAN + BBD + BDT + BEC + BEF + BEL + BGL + BGM + BGN + BGO + BHD + BIF + BMD + BND + BOB + BOL + BOP + BOV + BRB + BRC + BRE + BRL + BRN + BRR + BRZ + BSD + BTN + BUK + BWP + BYB + BYN + BYR + BZD + CAD + CDF + CHE + CHF + CHW + CLE + CLF + CLP + CNH + CNX + CNY + COP + COU + CRC + CSD + CSK + CUC + CUP + CVE + CYP + CZK + DDM + DEM + DJF + DKK + DOP + DZD + ECS + ECV + EEK + EGP + ERN + ESA + ESB + ESP + ETB + EUR + FIM + FJD + FKP + FRF + GBP + GEK + GEL + GHC + GHS + GIP + GMD + GNF + GNS + GQE + GRD + GTQ + GWE + GWP + GYD + HKD + HNL + HRD + HRK + HTG + HUF + IDR + IEP + ILP + ILR + ILS + INR + IQD + IRR + ISJ + ISK + ITL + JMD + JOD + JPY + KES + KGS + KHR + KMF + KPW + KRH + KRO + KRW + KWD + KYD + KZT + LAK + LBP + LKR + LRD + LSL + LTL + LTT + LUC + LUF + LUL + LVL + LVR + LYD + MAD + MAF + MCF + MDC + MDL + MGA + MGF + MKD + MKN + MLF + MMK + MNT + MOP + MRO + MTL + MTP + MUR + MVP + MVR + MWK + MXN + MXP + MXV + MYR + MZE + MZM + MZN + NAD + NGN + NIC + NIO + NLG + NOK + NPR + NZD + OMR + PAB + PEI + PEN + PES + PGK + PHP + PKR + PLN + PLZ + PTE + PYG + QAR + RHD + ROL + RON + RSD + RUB + RUR + RWF + SAR + SBD + SCR + SDD + SDG + SDP + SEK + SGD + SHP + SIT + SKK + SLL + SOS + SRD + SRG + SSP + STD + STN + SUR + SVC + SYP + SZL + THB + TJR + TJS + TMM + TMT + TND + TOP + TPE + TRL + TRY + TTD + TWD + TZS + UAH + UAK + UGS + UGX + USD + USN + USS + UYI + UYP + UYU + UZS + VEB + VEF + VND + VNN + VUV + WST + XAF + XAG + XAU + XBA + XBB + XBC + XBD + XCD + XDR + XEU + XFO + XFU + XOF + XPD + XPF + XPT + XRE + XSU + XTS + XUA + XXX + YDD + YER + YUD + YUM + YUN + YUR + ZAL + ZAR + ZMK + ZMW + ZRN + ZRZ + ZWD + ZWL + ZWR +) diff --git a/go-playground/locales/go.sum b/go-playground/locales/go.sum new file mode 100644 index 0000000..63c9200 --- /dev/null +++ b/go-playground/locales/go.sum @@ -0,0 +1,3 @@ +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/go-playground/locales/logo.png b/go-playground/locales/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3038276e6873076ecd542099e95b25037b1c0c34 GIT binary patch literal 37360 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 z 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 literal 0 HcmV?d00001 diff --git a/go-playground/locales/rules.go b/go-playground/locales/rules.go new file mode 100644 index 0000000..26f1c08 --- /dev/null +++ b/go-playground/locales/rules.go @@ -0,0 +1,293 @@ +package locales + +import ( + "strconv" + "time" + + "go-playground/locales/currency" +) + +// // ErrBadNumberValue is returned when the number passed for +// // plural rule determination cannot be parsed +// type ErrBadNumberValue struct { +// NumberValue string +// InnerError error +// } + +// // Error returns ErrBadNumberValue error string +// func (e *ErrBadNumberValue) Error() string { +// return fmt.Sprintf("Invalid Number Value '%s' %s", e.NumberValue, e.InnerError) +// } + +// var _ error = new(ErrBadNumberValue) + +// PluralRule denotes the type of plural rules +type PluralRule int + +// PluralRule's +const ( + PluralRuleUnknown PluralRule = iota + PluralRuleZero // zero + PluralRuleOne // one - singular + PluralRuleTwo // two - dual + PluralRuleFew // few - paucal + PluralRuleMany // many - also used for fractions if they have a separate class + PluralRuleOther // other - required—general plural form—also used if the language only has a single form +) + +const ( + pluralsString = "UnknownZeroOneTwoFewManyOther" +) + +// Translator encapsulates an instance of a locale +// NOTE: some values are returned as a []byte just in case the caller +// wishes to add more and can help avoid allocations; otherwise just cast as string +type Translator interface { + + // The following Functions are for overriding, debugging or developing + // with a Translator Locale + + // Locale returns the string value of the translator + Locale() string + + // returns an array of cardinal plural rules associated + // with this translator + PluralsCardinal() []PluralRule + + // returns an array of ordinal plural rules associated + // with this translator + PluralsOrdinal() []PluralRule + + // returns an array of range plural rules associated + // with this translator + PluralsRange() []PluralRule + + // returns the cardinal PluralRule given 'num' and digits/precision of 'v' for locale + CardinalPluralRule(num float64, v uint64) PluralRule + + // returns the ordinal PluralRule given 'num' and digits/precision of 'v' for locale + OrdinalPluralRule(num float64, v uint64) PluralRule + + // returns the ordinal PluralRule given 'num1', 'num2' and digits/precision of 'v1' and 'v2' for locale + RangePluralRule(num1 float64, v1 uint64, num2 float64, v2 uint64) PluralRule + + // returns the locales abbreviated month given the 'month' provided + MonthAbbreviated(month time.Month) string + + // returns the locales abbreviated months + MonthsAbbreviated() []string + + // returns the locales narrow month given the 'month' provided + MonthNarrow(month time.Month) string + + // returns the locales narrow months + MonthsNarrow() []string + + // returns the locales wide month given the 'month' provided + MonthWide(month time.Month) string + + // returns the locales wide months + MonthsWide() []string + + // returns the locales abbreviated weekday given the 'weekday' provided + WeekdayAbbreviated(weekday time.Weekday) string + + // returns the locales abbreviated weekdays + WeekdaysAbbreviated() []string + + // returns the locales narrow weekday given the 'weekday' provided + WeekdayNarrow(weekday time.Weekday) string + + // WeekdaysNarrowreturns the locales narrow weekdays + WeekdaysNarrow() []string + + // returns the locales short weekday given the 'weekday' provided + WeekdayShort(weekday time.Weekday) string + + // returns the locales short weekdays + WeekdaysShort() []string + + // returns the locales wide weekday given the 'weekday' provided + WeekdayWide(weekday time.Weekday) string + + // returns the locales wide weekdays + WeekdaysWide() []string + + // The following Functions are common Formatting functionsfor the Translator's Locale + + // returns 'num' with digits/precision of 'v' for locale and handles both Whole and Real numbers based on 'v' + FmtNumber(num float64, v uint64) string + + // returns 'num' with digits/precision of 'v' for locale and handles both Whole and Real numbers based on 'v' + // NOTE: 'num' passed into FmtPercent is assumed to be in percent already + FmtPercent(num float64, v uint64) string + + // returns the currency representation of 'num' with digits/precision of 'v' for locale + FmtCurrency(num float64, v uint64, currency currency.Type) string + + // returns the currency representation of 'num' with digits/precision of 'v' for locale + // in accounting notation. + FmtAccounting(num float64, v uint64, currency currency.Type) string + + // returns the short date representation of 't' for locale + FmtDateShort(t time.Time) string + + // returns the medium date representation of 't' for locale + FmtDateMedium(t time.Time) string + + // returns the long date representation of 't' for locale + FmtDateLong(t time.Time) string + + // returns the full date representation of 't' for locale + FmtDateFull(t time.Time) string + + // returns the short time representation of 't' for locale + FmtTimeShort(t time.Time) string + + // returns the medium time representation of 't' for locale + FmtTimeMedium(t time.Time) string + + // returns the long time representation of 't' for locale + FmtTimeLong(t time.Time) string + + // returns the full time representation of 't' for locale + FmtTimeFull(t time.Time) string +} + +// String returns the string value of PluralRule +func (p PluralRule) String() string { + + switch p { + case PluralRuleZero: + return pluralsString[7:11] + case PluralRuleOne: + return pluralsString[11:14] + case PluralRuleTwo: + return pluralsString[14:17] + case PluralRuleFew: + return pluralsString[17:20] + case PluralRuleMany: + return pluralsString[20:24] + case PluralRuleOther: + return pluralsString[24:] + default: + return pluralsString[:7] + } +} + +// +// Precision Notes: +// +// must specify a precision >= 0, and here is why https://play.golang.org/p/LyL90U0Vyh +// +// v := float64(3.141) +// i := float64(int64(v)) +// +// fmt.Println(v - i) +// +// or +// +// s := strconv.FormatFloat(v-i, 'f', -1, 64) +// fmt.Println(s) +// +// these will not print what you'd expect: 0.14100000000000001 +// and so this library requires a precision to be specified, or +// inaccurate plural rules could be applied. +// +// +// +// n - absolute value of the source number (integer and decimals). +// i - integer digits of n. +// v - number of visible fraction digits in n, with trailing zeros. +// w - number of visible fraction digits in n, without trailing zeros. +// f - visible fractional digits in n, with trailing zeros. +// t - visible fractional digits in n, without trailing zeros. +// +// +// Func(num float64, v uint64) // v = digits/precision and prevents -1 as a special case as this can lead to very unexpected behaviour, see precision note's above. +// +// n := math.Abs(num) +// i := int64(n) +// v := v +// +// +// w := strconv.FormatFloat(num-float64(i), 'f', int(v), 64) // then parse backwards on string until no more zero's.... +// f := strconv.FormatFloat(n, 'f', int(v), 64) // then turn everything after decimal into an int64 +// t := strconv.FormatFloat(n, 'f', int(v), 64) // then parse backwards on string until no more zero's.... +// +// +// +// General Inclusion Rules +// - v will always be available inherently +// - all require n +// - w requires i +// + +// W returns the number of visible fraction digits in N, without trailing zeros. +func W(n float64, v uint64) (w int64) { + + s := strconv.FormatFloat(n-float64(int64(n)), 'f', int(v), 64) + + // with either be '0' or '0.xxxx', so if 1 then w will be zero + // otherwise need to parse + if len(s) != 1 { + + s = s[2:] + end := len(s) + 1 + + for i := end; i >= 0; i-- { + if s[i] != '0' { + end = i + 1 + break + } + } + + w = int64(len(s[:end])) + } + + return +} + +// F returns the visible fractional digits in N, with trailing zeros. +func F(n float64, v uint64) (f int64) { + + s := strconv.FormatFloat(n-float64(int64(n)), 'f', int(v), 64) + + // with either be '0' or '0.xxxx', so if 1 then f will be zero + // otherwise need to parse + if len(s) != 1 { + + // ignoring error, because it can't fail as we generated + // the string internally from a real number + f, _ = strconv.ParseInt(s[2:], 10, 64) + } + + return +} + +// T returns the visible fractional digits in N, without trailing zeros. +func T(n float64, v uint64) (t int64) { + + s := strconv.FormatFloat(n-float64(int64(n)), 'f', int(v), 64) + + // with either be '0' or '0.xxxx', so if 1 then t will be zero + // otherwise need to parse + if len(s) != 1 { + + s = s[2:] + end := len(s) + 1 + + for i := end; i >= 0; i-- { + if s[i] != '0' { + end = i + 1 + break + } + } + + // ignoring error, because it can't fail as we generated + // the string internally from a real number + t, _ = strconv.ParseInt(s[:end], 10, 64) + } + + return +} diff --git a/go-playground/locales/zh/zh.go b/go-playground/locales/zh/zh.go new file mode 100644 index 0000000..726938a --- /dev/null +++ b/go-playground/locales/zh/zh.go @@ -0,0 +1,619 @@ +package zh + +import ( + "math" + "strconv" + "time" + + "go-playground/locales" + "go-playground/locales/currency" +) + +type zh struct { + locale string + pluralsCardinal []locales.PluralRule + pluralsOrdinal []locales.PluralRule + pluralsRange []locales.PluralRule + decimal string + group string + minus string + percent string + perMille string + timeSeparator string + inifinity string + currencies []string // idx = enum of currency code + monthsAbbreviated []string + monthsNarrow []string + monthsWide []string + daysAbbreviated []string + daysNarrow []string + daysShort []string + daysWide []string + periodsAbbreviated []string + periodsNarrow []string + periodsShort []string + periodsWide []string + erasAbbreviated []string + erasNarrow []string + erasWide []string + timezones map[string]string +} + +// New returns a new instance of translator for the 'zh' locale +func New() locales.Translator { + return &zh{ + locale: "zh", + pluralsCardinal: []locales.PluralRule{6}, + pluralsOrdinal: []locales.PluralRule{6}, + pluralsRange: []locales.PluralRule{6}, + decimal: ".", + group: ",", + minus: "-", + percent: "%", + perMille: "‰", + timeSeparator: ":", + inifinity: "∞", + currencies: []string{"ADP", "AED", "AFA", "AFN", "ALK", "ALL", "AMD", "ANG", "AOA", "AOK", "AON", "AOR", "ARA", "ARL", "ARM", "ARP", "ARS", "ATS", "AU$", "AWG", "AZM", "AZN", "BAD", "BAM", "BAN", "BBD", "BDT", "BEC", "BEF", "BEL", "BGL", "BGM", "BGN", "BGO", "BHD", "BIF", "BMD", "BND", "BOB", "BOL", "BOP", "BOV", "BRB", "BRC", "BRE", "R$", "BRN", "BRR", "BRZ", "BSD", "BTN", "BUK", "BWP", "BYB", "BYN", "BYR", "BZD", "CA$", "CDF", "CHE", "CHF", "CHW", "CLE", "CLF", "CLP", "CNH", "CNX", "¥", "COP", "COU", "CRC", "CSD", "CSK", "CUC", "CUP", "CVE", "CYP", "CZK", "DDM", "DEM", "DJF", "DKK", "DOP", "DZD", "ECS", "ECV", "EEK", "EGP", "ERN", "ESA", "ESB", "ESP", "ETB", "€", "FIM", "FJD", "FKP", "FRF", "£", "GEK", "GEL", "GHC", "GHS", "GIP", "GMD", "GNF", "GNS", "GQE", "GRD", "GTQ", "GWE", "GWP", "GYD", "HK$", "HNL", "HRD", "HRK", "HTG", "HUF", "IDR", "IEP", "ILP", "ILS", "₪", "₹", "IQD", "IRR", "ISJ", "ISK", "ITL", "JMD", "JOD", "JP¥", "KES", "KGS", "KHR", "KMF", "KPW", "KRH", "KRO", "₩", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LTL", "LTT", "LUC", "LUF", "LUL", "LVL", "LVR", "LYD", "MAD", "MAF", "MCF", "MDC", "MDL", "MGA", "MGF", "MKD", "MKN", "MLF", "MMK", "MNT", "MOP", "MRO", "MTL", "MTP", "MUR", "MVP", "MVR", "MWK", "MX$", "MXP", "MXV", "MYR", "MZE", "MZM", "MZN", "NAD", "NGN", "NIC", "NIO", "NLG", "NOK", "NPR", "NZ$", "OMR", "PAB", "PEI", "PEN", "PES", "PGK", "PHP", "PKR", "PLN", "PLZ", "PTE", "PYG", "QAR", "RHD", "ROL", "RON", "RSD", "RUB", "RUR", "RWF", "SAR", "SBD", "SCR", "SDD", "SDG", "SDP", "SEK", "SGD", "SHP", "SIT", "SKK", "SLL", "SOS", "SRD", "SRG", "SSP", "STD", "STN", "SUR", "SVC", "SYP", "SZL", "THB", "TJR", "TJS", "TMM", "TMT", "TND", "TOP", "TPE", "TRL", "TRY", "TTD", "NT$", "TZS", "UAH", "UAK", "UGS", "UGX", "US$", "USN", "USS", "UYI", "UYP", "UYU", "UZS", "VEB", "VEF", "₫", "VNN", "VUV", "WST", "FCFA", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "EC$", "XDR", "XEU", "XFO", "XFU", "CFA", "XPD", "CFPF", "XPT", "XRE", "XSU", "XTS", "XUA", "XXX", "YDD", "YER", "YUD", "YUM", "YUN", "YUR", "ZAL", "ZAR", "ZMK", "ZMW", "ZRN", "ZRZ", "ZWD", "ZWL", "ZWR"}, + monthsAbbreviated: []string{"", "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"}, + monthsNarrow: []string{"", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}, + monthsWide: []string{"", "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"}, + daysAbbreviated: []string{"周日", "周一", "周二", "周三", "周四", "周五", "周六"}, + daysNarrow: []string{"日", "一", "二", "三", "四", "五", "六"}, + daysShort: []string{"周日", "周一", "周二", "周三", "周四", "周五", "周六"}, + daysWide: []string{"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"}, + periodsAbbreviated: []string{"上午", "下午"}, + periodsNarrow: []string{"上午", "下午"}, + periodsWide: []string{"上午", "下午"}, + erasAbbreviated: []string{"公元前", "公元"}, + erasNarrow: []string{"公元前", "公元"}, + erasWide: []string{"公元前", "公元"}, + timezones: map[string]string{"UYST": "乌拉圭夏令时间", "HNPMX": "墨西哥太平洋标准时间", "MDT": "北美山区夏令时间", "WESZ": "西欧夏令时间", "AKST": "阿拉斯加标准时间", "ACWST": "澳大利亚中西部标准时间", "HENOMX": "墨西哥西北部夏令时间", "WIT": "印度尼西亚东部时间", "HEPMX": "墨西哥太平洋夏令时间", "JST": "日本标准时间", "SRT": "苏里南时间", "CLST": "智利夏令时间", "UYT": "乌拉圭标准时间", "AWDT": "澳大利亚西部夏令时间", "MST": "北美山区标准时间", "WAST": "西部非洲夏令时间", "NZST": "新西兰标准时间", "EAT": "东部非洲时间", "HECU": "古巴夏令时间", "BT": "不丹时间", "EDT": "北美东部夏令时间", "WARST": "阿根廷西部夏令时间", "HNPM": "圣皮埃尔和密克隆群岛标准时间", "HNCU": "古巴标准时间", "PDT": "北美太平洋夏令时间", "LHDT": "豪勋爵岛夏令时间", "CLT": "智利标准时间", "PST": "北美太平洋标准时间", "JDT": "日本夏令时间", "OEZ": "东欧标准时间", "TMT": "土库曼斯坦标准时间", "CST": "北美中部标准时间", "AWST": "澳大利亚西部标准时间", "AEST": "澳大利亚东部标准时间", "AKDT": "阿拉斯加夏令时间", "HKT": "香港标准时间", "LHST": "豪勋爵岛标准时间", "HNNOMX": "墨西哥西北部标准时间", "BOT": "玻利维亚标准时间", "HNOG": "格陵兰岛西部标准时间", "EST": "北美东部标准时间", "MESZ": "中欧夏令时间", "WITA": "印度尼西亚中部时间", "CAT": "中部非洲时间", "COST": "哥伦比亚夏令时间", "CHADT": "查坦夏令时间", "AST": "大西洋标准时间", "MYT": "马来西亚时间", "OESZ": "东欧夏令时间", "COT": "哥伦比亚标准时间", "SAST": "南非标准时间", "HEOG": "格陵兰岛西部夏令时间", "ACWDT": "澳大利亚中西部夏令时间", "MEZ": "中欧标准时间", "GYT": "圭亚那时间", "ADT": "大西洋夏令时间", "HEEG": "格陵兰岛东部夏令时间", "WART": "阿根廷西部标准时间", "VET": "委内瑞拉时间", "GMT": "格林尼治标准时间", "∅∅∅": "巴西利亚夏令时间", "SGT": "新加坡标准时间", "ACDT": "澳大利亚中部夏令时间", "HAT": "纽芬兰夏令时间", "HADT": "夏威夷-阿留申夏令时间", "CHAST": "查坦标准时间", "CDT": "北美中部夏令时间", "AEDT": "澳大利亚东部夏令时间", "WEZ": "西欧标准时间", "NZDT": "新西兰夏令时间", "ECT": "厄瓜多尔标准时间", "GFT": "法属圭亚那标准时间", "HKST": "香港夏令时间", "IST": "印度时间", "HNT": "纽芬兰标准时间", "ART": "阿根廷标准时间", "ChST": "查莫罗时间", "WAT": "西部非洲标准时间", "HNEG": "格陵兰岛东部标准时间", "HEPM": "圣皮埃尔和密克隆群岛夏令时间", "TMST": "土库曼斯坦夏令时间", "HAST": "夏威夷-阿留申标准时间", "WIB": "印度尼西亚西部时间", "ACST": "澳大利亚中部标准时间", "ARST": "阿根廷夏令时间"}, + } +} + +// Locale returns the current translators string locale +func (zh *zh) Locale() string { + return zh.locale +} + +// PluralsCardinal returns the list of cardinal plural rules associated with 'zh' +func (zh *zh) PluralsCardinal() []locales.PluralRule { + return zh.pluralsCardinal +} + +// PluralsOrdinal returns the list of ordinal plural rules associated with 'zh' +func (zh *zh) PluralsOrdinal() []locales.PluralRule { + return zh.pluralsOrdinal +} + +// PluralsRange returns the list of range plural rules associated with 'zh' +func (zh *zh) PluralsRange() []locales.PluralRule { + return zh.pluralsRange +} + +// CardinalPluralRule returns the cardinal PluralRule given 'num' and digits/precision of 'v' for 'zh' +func (zh *zh) CardinalPluralRule(num float64, v uint64) locales.PluralRule { + return locales.PluralRuleOther +} + +// OrdinalPluralRule returns the ordinal PluralRule given 'num' and digits/precision of 'v' for 'zh' +func (zh *zh) OrdinalPluralRule(num float64, v uint64) locales.PluralRule { + return locales.PluralRuleOther +} + +// RangePluralRule returns the ordinal PluralRule given 'num1', 'num2' and digits/precision of 'v1' and 'v2' for 'zh' +func (zh *zh) RangePluralRule(num1 float64, v1 uint64, num2 float64, v2 uint64) locales.PluralRule { + return locales.PluralRuleOther +} + +// MonthAbbreviated returns the locales abbreviated month given the 'month' provided +func (zh *zh) MonthAbbreviated(month time.Month) string { + return zh.monthsAbbreviated[month] +} + +// MonthsAbbreviated returns the locales abbreviated months +func (zh *zh) MonthsAbbreviated() []string { + return zh.monthsAbbreviated[1:] +} + +// MonthNarrow returns the locales narrow month given the 'month' provided +func (zh *zh) MonthNarrow(month time.Month) string { + return zh.monthsNarrow[month] +} + +// MonthsNarrow returns the locales narrow months +func (zh *zh) MonthsNarrow() []string { + return zh.monthsNarrow[1:] +} + +// MonthWide returns the locales wide month given the 'month' provided +func (zh *zh) MonthWide(month time.Month) string { + return zh.monthsWide[month] +} + +// MonthsWide returns the locales wide months +func (zh *zh) MonthsWide() []string { + return zh.monthsWide[1:] +} + +// WeekdayAbbreviated returns the locales abbreviated weekday given the 'weekday' provided +func (zh *zh) WeekdayAbbreviated(weekday time.Weekday) string { + return zh.daysAbbreviated[weekday] +} + +// WeekdaysAbbreviated returns the locales abbreviated weekdays +func (zh *zh) WeekdaysAbbreviated() []string { + return zh.daysAbbreviated +} + +// WeekdayNarrow returns the locales narrow weekday given the 'weekday' provided +func (zh *zh) WeekdayNarrow(weekday time.Weekday) string { + return zh.daysNarrow[weekday] +} + +// WeekdaysNarrow returns the locales narrow weekdays +func (zh *zh) WeekdaysNarrow() []string { + return zh.daysNarrow +} + +// WeekdayShort returns the locales short weekday given the 'weekday' provided +func (zh *zh) WeekdayShort(weekday time.Weekday) string { + return zh.daysShort[weekday] +} + +// WeekdaysShort returns the locales short weekdays +func (zh *zh) WeekdaysShort() []string { + return zh.daysShort +} + +// WeekdayWide returns the locales wide weekday given the 'weekday' provided +func (zh *zh) WeekdayWide(weekday time.Weekday) string { + return zh.daysWide[weekday] +} + +// WeekdaysWide returns the locales wide weekdays +func (zh *zh) WeekdaysWide() []string { + return zh.daysWide +} + +// Decimal returns the decimal point of number +func (zh *zh) Decimal() string { + return zh.decimal +} + +// Group returns the group of number +func (zh *zh) Group() string { + return zh.group +} + +// Group returns the minus sign of number +func (zh *zh) Minus() string { + return zh.minus +} + +// FmtNumber returns 'num' with digits/precision of 'v' for 'zh' and handles both Whole and Real numbers based on 'v' +func (zh *zh) FmtNumber(num float64, v uint64) string { + + s := strconv.FormatFloat(math.Abs(num), 'f', int(v), 64) + l := len(s) + 2 + 1*len(s[:len(s)-int(v)-1])/3 + count := 0 + inWhole := v == 0 + b := make([]byte, 0, l) + + for i := len(s) - 1; i >= 0; i-- { + + if s[i] == '.' { + b = append(b, zh.decimal[0]) + inWhole = true + continue + } + + if inWhole { + if count == 3 { + b = append(b, zh.group[0]) + count = 1 + } else { + count++ + } + } + + b = append(b, s[i]) + } + + if num < 0 { + b = append(b, zh.minus[0]) + } + + // reverse + for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 { + b[i], b[j] = b[j], b[i] + } + + return string(b) +} + +// FmtPercent returns 'num' with digits/precision of 'v' for 'zh' and handles both Whole and Real numbers based on 'v' +// NOTE: 'num' passed into FmtPercent is assumed to be in percent already +func (zh *zh) FmtPercent(num float64, v uint64) string { + s := strconv.FormatFloat(math.Abs(num), 'f', int(v), 64) + l := len(s) + 3 + b := make([]byte, 0, l) + + for i := len(s) - 1; i >= 0; i-- { + + if s[i] == '.' { + b = append(b, zh.decimal[0]) + continue + } + + b = append(b, s[i]) + } + + if num < 0 { + b = append(b, zh.minus[0]) + } + + // reverse + for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 { + b[i], b[j] = b[j], b[i] + } + + b = append(b, zh.percent...) + + return string(b) +} + +// FmtCurrency returns the currency representation of 'num' with digits/precision of 'v' for 'zh' +func (zh *zh) FmtCurrency(num float64, v uint64, currency currency.Type) string { + + s := strconv.FormatFloat(math.Abs(num), 'f', int(v), 64) + symbol := zh.currencies[currency] + l := len(s) + len(symbol) + 2 + 1*len(s[:len(s)-int(v)-1])/3 + count := 0 + inWhole := v == 0 + b := make([]byte, 0, l) + + for i := len(s) - 1; i >= 0; i-- { + + if s[i] == '.' { + b = append(b, zh.decimal[0]) + inWhole = true + continue + } + + if inWhole { + if count == 3 { + b = append(b, zh.group[0]) + count = 1 + } else { + count++ + } + } + + b = append(b, s[i]) + } + + for j := len(symbol) - 1; j >= 0; j-- { + b = append(b, symbol[j]) + } + + if num < 0 { + b = append(b, zh.minus[0]) + } + + // reverse + for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 { + b[i], b[j] = b[j], b[i] + } + + if int(v) < 2 { + + if v == 0 { + b = append(b, zh.decimal...) + } + + for i := 0; i < 2-int(v); i++ { + b = append(b, '0') + } + } + + return string(b) +} + +// FmtAccounting returns the currency representation of 'num' with digits/precision of 'v' for 'zh' +// in accounting notation. +func (zh *zh) FmtAccounting(num float64, v uint64, currency currency.Type) string { + + s := strconv.FormatFloat(math.Abs(num), 'f', int(v), 64) + symbol := zh.currencies[currency] + l := len(s) + len(symbol) + 2 + 1*len(s[:len(s)-int(v)-1])/3 + count := 0 + inWhole := v == 0 + b := make([]byte, 0, l) + + for i := len(s) - 1; i >= 0; i-- { + + if s[i] == '.' { + b = append(b, zh.decimal[0]) + inWhole = true + continue + } + + if inWhole { + if count == 3 { + b = append(b, zh.group[0]) + count = 1 + } else { + count++ + } + } + + b = append(b, s[i]) + } + + if num < 0 { + + for j := len(symbol) - 1; j >= 0; j-- { + b = append(b, symbol[j]) + } + + b = append(b, zh.minus[0]) + + } else { + + for j := len(symbol) - 1; j >= 0; j-- { + b = append(b, symbol[j]) + } + + } + + // reverse + for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 { + b[i], b[j] = b[j], b[i] + } + + if int(v) < 2 { + + if v == 0 { + b = append(b, zh.decimal...) + } + + for i := 0; i < 2-int(v); i++ { + b = append(b, '0') + } + } + + return string(b) +} + +// FmtDateShort returns the short date representation of 't' for 'zh' +func (zh *zh) FmtDateShort(t time.Time) string { + + b := make([]byte, 0, 32) + + if t.Year() > 0 { + b = strconv.AppendInt(b, int64(t.Year()), 10) + } else { + b = strconv.AppendInt(b, int64(-t.Year()), 10) + } + + b = append(b, []byte{0x2f}...) + b = strconv.AppendInt(b, int64(t.Month()), 10) + b = append(b, []byte{0x2f}...) + b = strconv.AppendInt(b, int64(t.Day()), 10) + + return string(b) +} + +// FmtDateMedium returns the medium date representation of 't' for 'zh' +func (zh *zh) FmtDateMedium(t time.Time) string { + + b := make([]byte, 0, 32) + + if t.Year() > 0 { + b = strconv.AppendInt(b, int64(t.Year()), 10) + } else { + b = strconv.AppendInt(b, int64(-t.Year()), 10) + } + + b = append(b, []byte{0xe5, 0xb9, 0xb4}...) + b = strconv.AppendInt(b, int64(t.Month()), 10) + b = append(b, []byte{0xe6, 0x9c, 0x88}...) + b = strconv.AppendInt(b, int64(t.Day()), 10) + b = append(b, []byte{0xe6, 0x97, 0xa5}...) + + return string(b) +} + +// FmtDateLong returns the long date representation of 't' for 'zh' +func (zh *zh) FmtDateLong(t time.Time) string { + + b := make([]byte, 0, 32) + + if t.Year() > 0 { + b = strconv.AppendInt(b, int64(t.Year()), 10) + } else { + b = strconv.AppendInt(b, int64(-t.Year()), 10) + } + + b = append(b, []byte{0xe5, 0xb9, 0xb4}...) + b = strconv.AppendInt(b, int64(t.Month()), 10) + b = append(b, []byte{0xe6, 0x9c, 0x88}...) + b = strconv.AppendInt(b, int64(t.Day()), 10) + b = append(b, []byte{0xe6, 0x97, 0xa5}...) + + return string(b) +} + +// FmtDateFull returns the full date representation of 't' for 'zh' +func (zh *zh) FmtDateFull(t time.Time) string { + + b := make([]byte, 0, 32) + + if t.Year() > 0 { + b = strconv.AppendInt(b, int64(t.Year()), 10) + } else { + b = strconv.AppendInt(b, int64(-t.Year()), 10) + } + + b = append(b, []byte{0xe5, 0xb9, 0xb4}...) + b = strconv.AppendInt(b, int64(t.Month()), 10) + b = append(b, []byte{0xe6, 0x9c, 0x88}...) + b = strconv.AppendInt(b, int64(t.Day()), 10) + b = append(b, []byte{0xe6, 0x97, 0xa5}...) + b = append(b, zh.daysWide[t.Weekday()]...) + + return string(b) +} + +// FmtTimeShort returns the short time representation of 't' for 'zh' +func (zh *zh) FmtTimeShort(t time.Time) string { + + b := make([]byte, 0, 32) + + if t.Hour() < 12 { + b = append(b, zh.periodsAbbreviated[0]...) + } else { + b = append(b, zh.periodsAbbreviated[1]...) + } + + h := t.Hour() + + if h > 12 { + h -= 12 + } + + b = strconv.AppendInt(b, int64(h), 10) + b = append(b, zh.timeSeparator...) + + if t.Minute() < 10 { + b = append(b, '0') + } + + b = strconv.AppendInt(b, int64(t.Minute()), 10) + + return string(b) +} + +// FmtTimeMedium returns the medium time representation of 't' for 'zh' +func (zh *zh) FmtTimeMedium(t time.Time) string { + + b := make([]byte, 0, 32) + + if t.Hour() < 12 { + b = append(b, zh.periodsAbbreviated[0]...) + } else { + b = append(b, zh.periodsAbbreviated[1]...) + } + + h := t.Hour() + + if h > 12 { + h -= 12 + } + + b = strconv.AppendInt(b, int64(h), 10) + b = append(b, zh.timeSeparator...) + + if t.Minute() < 10 { + b = append(b, '0') + } + + b = strconv.AppendInt(b, int64(t.Minute()), 10) + b = append(b, zh.timeSeparator...) + + if t.Second() < 10 { + b = append(b, '0') + } + + b = strconv.AppendInt(b, int64(t.Second()), 10) + + return string(b) +} + +// FmtTimeLong returns the long time representation of 't' for 'zh' +func (zh *zh) FmtTimeLong(t time.Time) string { + + b := make([]byte, 0, 32) + + tz, _ := t.Zone() + b = append(b, tz...) + + b = append(b, []byte{0x20}...) + + if t.Hour() < 12 { + b = append(b, zh.periodsAbbreviated[0]...) + } else { + b = append(b, zh.periodsAbbreviated[1]...) + } + + h := t.Hour() + + if h > 12 { + h -= 12 + } + + b = strconv.AppendInt(b, int64(h), 10) + b = append(b, zh.timeSeparator...) + + if t.Minute() < 10 { + b = append(b, '0') + } + + b = strconv.AppendInt(b, int64(t.Minute()), 10) + b = append(b, zh.timeSeparator...) + + if t.Second() < 10 { + b = append(b, '0') + } + + b = strconv.AppendInt(b, int64(t.Second()), 10) + + return string(b) +} + +// FmtTimeFull returns the full time representation of 't' for 'zh' +func (zh *zh) FmtTimeFull(t time.Time) string { + + b := make([]byte, 0, 32) + + tz, _ := t.Zone() + + if btz, ok := zh.timezones[tz]; ok { + b = append(b, btz...) + } else { + b = append(b, tz...) + } + + b = append(b, []byte{0x20}...) + + if t.Hour() < 12 { + b = append(b, zh.periodsAbbreviated[0]...) + } else { + b = append(b, zh.periodsAbbreviated[1]...) + } + + h := t.Hour() + + if h > 12 { + h -= 12 + } + + b = strconv.AppendInt(b, int64(h), 10) + b = append(b, zh.timeSeparator...) + + if t.Minute() < 10 { + b = append(b, '0') + } + + b = strconv.AppendInt(b, int64(t.Minute()), 10) + b = append(b, zh.timeSeparator...) + + if t.Second() < 10 { + b = append(b, '0') + } + + b = strconv.AppendInt(b, int64(t.Second()), 10) + + return string(b) +} diff --git a/go-playground/universal-translator/.gitignore b/go-playground/universal-translator/.gitignore new file mode 100644 index 0000000..bc4e07f --- /dev/null +++ b/go-playground/universal-translator/.gitignore @@ -0,0 +1,25 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +*.coverprofile \ No newline at end of file diff --git a/go-playground/universal-translator/.travis.yml b/go-playground/universal-translator/.travis.yml new file mode 100644 index 0000000..39b8b92 --- /dev/null +++ b/go-playground/universal-translator/.travis.yml @@ -0,0 +1,27 @@ +language: go +go: + - 1.13.4 + - tip +matrix: + allow_failures: + - go: tip + +notifications: + email: + recipients: dean.karn@gmail.com + on_success: change + on_failure: always + +before_install: + - go install github.com/mattn/goveralls + +# Only clone the most recent commit. +git: + depth: 1 + +script: + - go test -v -race -covermode=atomic -coverprofile=coverage.coverprofile ./... + +after_success: | + [ $TRAVIS_GO_VERSION = 1.13.4 ] && + goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN \ No newline at end of file diff --git a/go-playground/universal-translator/LICENSE b/go-playground/universal-translator/LICENSE new file mode 100644 index 0000000..8d8aba1 --- /dev/null +++ b/go-playground/universal-translator/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Go Playground + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/go-playground/universal-translator/README.md b/go-playground/universal-translator/README.md new file mode 100644 index 0000000..42f5f94 --- /dev/null +++ b/go-playground/universal-translator/README.md @@ -0,0 +1,89 @@ +## universal-translator +![Project status](https://img.shields.io/badge/version-0.17.0-green.svg) +[![Build Status](https://travis-ci.org/go-playground/universal-translator.svg?branch=master)](https://travis-ci.org/go-playground/universal-translator) +[![Coverage Status](https://coveralls.io/repos/github/go-playground/universal-translator/badge.svg)](https://coveralls.io/github/go-playground/universal-translator) +[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/universal-translator)](https://goreportcard.com/report/github.com/go-playground/universal-translator) +[![GoDoc](https://godoc.org/github.com/go-playground/universal-translator?status.svg)](https://godoc.org/github.com/go-playground/universal-translator) +![License](https://img.shields.io/dub/l/vibe-d.svg) +[![Gitter](https://badges.gitter.im/go-playground/universal-translator.svg)](https://gitter.im/go-playground/universal-translator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) + +Universal Translator is an i18n Translator for Go/Golang using CLDR data + pluralization rules + +Why another i18n library? +-------------------------- +Because none of the plural rules seem to be correct out there, including the previous implementation of this package, +so I took it upon myself to create [locales](https://go-playground/locales) for everyone to use; this package +is a thin wrapper around [locales](https://go-playground/locales) in order to store and translate text for +use in your applications. + +Features +-------- +- [x] Rules generated from the [CLDR](http://cldr.unicode.org/index/downloads) data, v30.0.3 +- [x] Contains Cardinal, Ordinal and Range Plural Rules +- [x] Contains Month, Weekday and Timezone translations built in +- [x] Contains Date & Time formatting functions +- [x] Contains Number, Currency, Accounting and Percent formatting functions +- [x] Supports the "Gregorian" calendar only ( my time isn't unlimited, had to draw the line somewhere ) +- [x] Support loading translations from files +- [x] Exporting translations to file(s), mainly for getting them professionally translated +- [ ] Code Generation for translation files -> Go code.. i.e. after it has been professionally translated +- [ ] Tests for all languages, I need help with this, please see [here](https://go-playground/locales/issues/1) + +Installation +----------- + +Use go get + +```shell +go get github.com/go-playground/universal-translator +``` + +Usage & Documentation +------- + +Please see https://godoc.org/github.com/go-playground/universal-translator for usage docs + +##### Examples: + +- [Basic](https://github.com/go-playground/universal-translator/tree/master/_examples/basic) +- [Full - no files](https://github.com/go-playground/universal-translator/tree/master/_examples/full-no-files) +- [Full - with files](https://github.com/go-playground/universal-translator/tree/master/_examples/full-with-files) + +File formatting +-------------- +All types, Plain substitution, Cardinal, Ordinal and Range translations can all be contained withing the same file(s); +they are only separated for easy viewing. + +##### Examples: + +- [Formats](https://github.com/go-playground/universal-translator/tree/master/_examples/file-formats) + +##### Basic Makeup +NOTE: not all fields are needed for all translation types, see [examples](https://github.com/go-playground/universal-translator/tree/master/_examples/file-formats) +```json +{ + "locale": "en", + "key": "days-left", + "trans": "You have {0} day left.", + "type": "Cardinal", + "rule": "One", + "override": false +} +``` +|Field|Description| +|---|---| +|locale|The locale for which the translation is for.| +|key|The translation key that will be used to store and lookup each translation; normally it is a string or integer.| +|trans|The actual translation text.| +|type|The type of translation Cardinal, Ordinal, Range or "" for a plain substitution(not required to be defined if plain used)| +|rule|The plural rule for which the translation is for eg. One, Two, Few, Many or Other.(not required to be defined if plain used)| +|override|If you wish to override an existing translation that has already been registered, set this to 'true'. 99% of the time there is no need to define it.| + +Help With Tests +--------------- +To anyone interesting in helping or contributing, I sure could use some help creating tests for each language. +Please see issue [here](https://go-playground/locales/issues/1) for details. + +License +------ +Distributed under MIT License, please see license file in code for more details. diff --git a/go-playground/universal-translator/errors.go b/go-playground/universal-translator/errors.go new file mode 100644 index 0000000..3b4649c --- /dev/null +++ b/go-playground/universal-translator/errors.go @@ -0,0 +1,148 @@ +package ut + +import ( + "errors" + "fmt" + + "gin-valid/go-playground/locales" +) + +var ( + // ErrUnknowTranslation indicates the translation could not be found + ErrUnknowTranslation = errors.New("Unknown Translation") +) + +var _ error = new(ErrConflictingTranslation) +var _ error = new(ErrRangeTranslation) +var _ error = new(ErrOrdinalTranslation) +var _ error = new(ErrCardinalTranslation) +var _ error = new(ErrMissingPluralTranslation) +var _ error = new(ErrExistingTranslator) + +// ErrExistingTranslator is the error representing a conflicting translator +type ErrExistingTranslator struct { + locale string +} + +// Error returns ErrExistingTranslator's internal error text +func (e *ErrExistingTranslator) Error() string { + return fmt.Sprintf("error: conflicting translator for locale '%s'", e.locale) +} + +// ErrConflictingTranslation is the error representing a conflicting translation +type ErrConflictingTranslation struct { + locale string + key interface{} + rule locales.PluralRule + text string +} + +// Error returns ErrConflictingTranslation's internal error text +func (e *ErrConflictingTranslation) Error() string { + + if _, ok := e.key.(string); !ok { + return fmt.Sprintf("error: conflicting key '%#v' rule '%s' with text '%s' for locale '%s', value being ignored", e.key, e.rule, e.text, e.locale) + } + + return fmt.Sprintf("error: conflicting key '%s' rule '%s' with text '%s' for locale '%s', value being ignored", e.key, e.rule, e.text, e.locale) +} + +// ErrRangeTranslation is the error representing a range translation error +type ErrRangeTranslation struct { + text string +} + +// Error returns ErrRangeTranslation's internal error text +func (e *ErrRangeTranslation) Error() string { + return e.text +} + +// ErrOrdinalTranslation is the error representing an ordinal translation error +type ErrOrdinalTranslation struct { + text string +} + +// Error returns ErrOrdinalTranslation's internal error text +func (e *ErrOrdinalTranslation) Error() string { + return e.text +} + +// ErrCardinalTranslation is the error representing a cardinal translation error +type ErrCardinalTranslation struct { + text string +} + +// Error returns ErrCardinalTranslation's internal error text +func (e *ErrCardinalTranslation) Error() string { + return e.text +} + +// ErrMissingPluralTranslation is the error signifying a missing translation given +// the locales plural rules. +type ErrMissingPluralTranslation struct { + locale string + key interface{} + rule locales.PluralRule + translationType string +} + +// Error returns ErrMissingPluralTranslation's internal error text +func (e *ErrMissingPluralTranslation) Error() string { + + if _, ok := e.key.(string); !ok { + return fmt.Sprintf("error: missing '%s' plural rule '%s' for translation with key '%#v' and locale '%s'", e.translationType, e.rule, e.key, e.locale) + } + + return fmt.Sprintf("error: missing '%s' plural rule '%s' for translation with key '%s' and locale '%s'", e.translationType, e.rule, e.key, e.locale) +} + +// ErrMissingBracket is the error representing a missing bracket in a translation +// eg. This is a {0 <-- missing ending '}' +type ErrMissingBracket struct { + locale string + key interface{} + text string +} + +// Error returns ErrMissingBracket error message +func (e *ErrMissingBracket) Error() string { + return fmt.Sprintf("error: missing bracket '{}', in translation. locale: '%s' key: '%v' text: '%s'", e.locale, e.key, e.text) +} + +// ErrBadParamSyntax is the error representing a bad parameter definition in a translation +// eg. This is a {must-be-int} +type ErrBadParamSyntax struct { + locale string + param string + key interface{} + text string +} + +// Error returns ErrBadParamSyntax error message +func (e *ErrBadParamSyntax) Error() string { + return fmt.Sprintf("error: bad parameter syntax, missing parameter '%s' in translation. locale: '%s' key: '%v' text: '%s'", e.param, e.locale, e.key, e.text) +} + +// import/export errors + +// ErrMissingLocale is the error representing an expected locale that could +// not be found aka locale not registered with the UniversalTranslator Instance +type ErrMissingLocale struct { + locale string +} + +// Error returns ErrMissingLocale's internal error text +func (e *ErrMissingLocale) Error() string { + return fmt.Sprintf("error: locale '%s' not registered.", e.locale) +} + +// ErrBadPluralDefinition is the error representing an incorrect plural definition +// usually found within translations defined within files during the import process. +type ErrBadPluralDefinition struct { + tl translation +} + +// Error returns ErrBadPluralDefinition's internal error text +func (e *ErrBadPluralDefinition) Error() string { + return fmt.Sprintf("error: bad plural definition '%#v'", e.tl) +} diff --git a/go-playground/universal-translator/go.sum b/go-playground/universal-translator/go.sum new file mode 100644 index 0000000..54b4c8a --- /dev/null +++ b/go-playground/universal-translator/go.sum @@ -0,0 +1,4 @@ +go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/go-playground/universal-translator/import_export.go b/go-playground/universal-translator/import_export.go new file mode 100644 index 0000000..839d012 --- /dev/null +++ b/go-playground/universal-translator/import_export.go @@ -0,0 +1,274 @@ +package ut + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "io" + + "go-playground/locales" +) + +type translation struct { + Locale string `json:"locale"` + Key interface{} `json:"key"` // either string or integer + Translation string `json:"trans"` + PluralType string `json:"type,omitempty"` + PluralRule string `json:"rule,omitempty"` + OverrideExisting bool `json:"override,omitempty"` +} + +const ( + cardinalType = "Cardinal" + ordinalType = "Ordinal" + rangeType = "Range" +) + +// ImportExportFormat is the format of the file import or export +type ImportExportFormat uint8 + +// supported Export Formats +const ( + FormatJSON ImportExportFormat = iota +) + +// Export writes the translations out to a file on disk. +// +// NOTE: this currently only works with string or int translations keys. +func (t *UniversalTranslator) Export(format ImportExportFormat, dirname string) error { + + _, err := os.Stat(dirname) + fmt.Println(dirname, err, os.IsNotExist(err)) + if err != nil { + + if !os.IsNotExist(err) { + return err + } + + if err = os.MkdirAll(dirname, 0744); err != nil { + return err + } + } + + // build up translations + var trans []translation + var b []byte + var ext string + + for _, locale := range t.translators { + + for k, v := range locale.(*translator).translations { + trans = append(trans, translation{ + Locale: locale.Locale(), + Key: k, + Translation: v.text, + }) + } + + for k, pluralTrans := range locale.(*translator).cardinalTanslations { + + for i, plural := range pluralTrans { + + // leave enough for all plural rules + // but not all are set for all languages. + if plural == nil { + continue + } + + trans = append(trans, translation{ + Locale: locale.Locale(), + Key: k.(string), + Translation: plural.text, + PluralType: cardinalType, + PluralRule: locales.PluralRule(i).String(), + }) + } + } + + for k, pluralTrans := range locale.(*translator).ordinalTanslations { + + for i, plural := range pluralTrans { + + // leave enough for all plural rules + // but not all are set for all languages. + if plural == nil { + continue + } + + trans = append(trans, translation{ + Locale: locale.Locale(), + Key: k.(string), + Translation: plural.text, + PluralType: ordinalType, + PluralRule: locales.PluralRule(i).String(), + }) + } + } + + for k, pluralTrans := range locale.(*translator).rangeTanslations { + + for i, plural := range pluralTrans { + + // leave enough for all plural rules + // but not all are set for all languages. + if plural == nil { + continue + } + + trans = append(trans, translation{ + Locale: locale.Locale(), + Key: k.(string), + Translation: plural.text, + PluralType: rangeType, + PluralRule: locales.PluralRule(i).String(), + }) + } + } + + switch format { + case FormatJSON: + b, err = json.MarshalIndent(trans, "", " ") + ext = ".json" + } + + if err != nil { + return err + } + + err = ioutil.WriteFile(filepath.Join(dirname, fmt.Sprintf("%s%s", locale.Locale(), ext)), b, 0644) + if err != nil { + return err + } + + trans = trans[0:0] + } + + return nil +} + +// Import reads the translations out of a file or directory on disk. +// +// NOTE: this currently only works with string or int translations keys. +func (t *UniversalTranslator) Import(format ImportExportFormat, dirnameOrFilename string) error { + + fi, err := os.Stat(dirnameOrFilename) + if err != nil { + return err + } + + processFn := func(filename string) error { + + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() + + return t.ImportByReader(format, f) + } + + if !fi.IsDir() { + return processFn(dirnameOrFilename) + } + + // recursively go through directory + walker := func(path string, info os.FileInfo, err error) error { + + if info.IsDir() { + return nil + } + + switch format { + case FormatJSON: + // skip non JSON files + if filepath.Ext(info.Name()) != ".json" { + return nil + } + } + + return processFn(path) + } + + return filepath.Walk(dirnameOrFilename, walker) +} + +// ImportByReader imports the the translations found within the contents read from the supplied reader. +// +// NOTE: generally used when assets have been embedded into the binary and are already in memory. +func (t *UniversalTranslator) ImportByReader(format ImportExportFormat, reader io.Reader) error { + + b, err := ioutil.ReadAll(reader) + if err != nil { + return err + } + + var trans []translation + + switch format { + case FormatJSON: + err = json.Unmarshal(b, &trans) + } + + if err != nil { + return err + } + + for _, tl := range trans { + + locale, found := t.FindTranslator(tl.Locale) + if !found { + return &ErrMissingLocale{locale: tl.Locale} + } + + pr := stringToPR(tl.PluralRule) + + if pr == locales.PluralRuleUnknown { + + err = locale.Add(tl.Key, tl.Translation, tl.OverrideExisting) + if err != nil { + return err + } + + continue + } + + switch tl.PluralType { + case cardinalType: + err = locale.AddCardinal(tl.Key, tl.Translation, pr, tl.OverrideExisting) + case ordinalType: + err = locale.AddOrdinal(tl.Key, tl.Translation, pr, tl.OverrideExisting) + case rangeType: + err = locale.AddRange(tl.Key, tl.Translation, pr, tl.OverrideExisting) + default: + return &ErrBadPluralDefinition{tl: tl} + } + + if err != nil { + return err + } + } + + return nil +} + +func stringToPR(s string) locales.PluralRule { + + switch s { + case "One": + return locales.PluralRuleOne + case "Two": + return locales.PluralRuleTwo + case "Few": + return locales.PluralRuleFew + case "Many": + return locales.PluralRuleMany + case "Other": + return locales.PluralRuleOther + default: + return locales.PluralRuleUnknown + } + +} diff --git a/go-playground/universal-translator/logo.png b/go-playground/universal-translator/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a37aa8c0cd0f6e1b98e0be3eb2531ebc6ac6717b GIT binary patch literal 16598 zcmV(yK*U_<=i=1Q$^7;0v#+eDrK0%p z>%6(N%f`XBv#;pk-N3xJlaY`9`tZ@r#>d0H&B?^Dtf>F`_3`TEoSK)csHK;dl)1LD z{xUb9o}0zMyZALSjf{%1BqI0!{{5jc`1SO`zPq8KpwB5MBz`@`DJHxnB>jUjr=XpP zhJ*ceGX0V>qah&tt}xmxDK3XPA9F;SA|8N$duRs?{%JJ*T{HG>5AcL8I*&O1Ju^KN z1^!Yp@h&apFDzno38}29au*Y6VF#%(B=Pd^`t{{04g#CPRH8PEL>89OH1Y4=wM`?Y+cnsCD|dZGb#-yb zPbYsY6v#U#TTlf1)HKmxCR88|TWkfi;WgJmEa>U!iH1CPzc%zlFUrZIxOpL50SAW2 zHrkRe-Bv8!+}rP-U23*Bp`mB-jzz%dHuKoF($TRq)t%&MEAPFIet}u?$D-;-NTfd? zqGBFudL7@{$H>RCmXS~SDk-e2cr(q3b}2D4;I^fdk8wf@!NHcex`pmIGxX}ykX2Zk zP#l(MXu)GF(Pvk zb5n$3uu@QfLrR3XE#bH{zmF;My}ImfZ|eE@n0GU+i65iC#kVv_JUkHP6q^ z=b4$1KRnawHnN9;XOVD+dTcU@PWtQI+{V7)vVb@}2{~W|d768ob{*KawLyATCMyBY zhk?3y?SQh*YZ(!U#KDNg9qaM-(p?CZPE$X(8z(y`Rk002FaNkl8uC7fajE|-h>jHG2b9lbevz(KOfu`z+X2)PND2t1w1WGFmB z;BZ@Tf}w8eW!d6lVUNJlhe<3w@VLAr5!hqYo=(frj?qrXhNe@r$z?o#)_BC$c#6Tn zw%ibJSrePzu1EE-o7FRWE@Bmr=0#t-W8=a(6GTqoYvcqTWN@=@*0!nMye8HIu2(T+Ak@9J5Z~>D99iw*`m8V%yP8G^_lKA3_-39D4+I+C1zz z%PxAIjtxqC6rEeEoK;TYRK5moFT3e)S;c473ltAKvc{hCWlVc;gKFP7(_^pP+TQSQ zjTnnuLBc{Sdb7KSnYy{T8Mi%*-`7X=bnva_VNVf!d<1rL=-JH6uuGF1?^y5f9AB6q zotFt+&fvKN9wTt?2px9TxG)4b)AV}%MO>l#`PSCbhc8CH z(ahGS3jt5QrHGrvLqpybTd3aT!_Ma3C?0!8iS}-@N4$|3Vjk~Odfkur_B2j4Q=ai9 zwCdFE-MgodJ$v@--@hOJdcD5T)wQs^+~2>veB#83lSsEgpFKBQ8LAYfrz?!j@VGrK zfD?mhnuhu zc6A*>`uqE-yqo0c(WA$Y&khU>p!`akr_C16;qx9(K3}Q48GMs(2|#W(U+C>U+gs?p z^6}jBk0WP#6@}XUG1|MFxifa;Gu`Gf@{^8r3D0vJ&-0ExB2Qql!KaO@u%q%RuxCa- z=nDq{@3OWa?o`}pF?bNzz;Q^nz?4c2d1Yzhk<6eIWaeu#=B8Cm810v^Rke)+e)?h zH``r9!+0zn64TyHj(U9NCw8Ir?OU83bm-%VH;0Wb>dWQso<4o4T$akBA7WgT3iz&_ zN#||w+mrl%lwSPt^Vg61UR6`~YFWHi_l{!JcULueSU zuU#7-U!wAd^F!yiGqsgYce}fpeBa&ns10GwH=8Hj)>BVDk~}BH;~@vhCnx{=D_1_h z9(z4@+Q`0r`z}a*NhgoKotri}dCcg8TMu47d)M~vT^slBr56_$zkgrMPE@N|O(WZ! zsLQ%0YpP1+LKrd6^35*)GUSB5)MzwrJS8#U4D>#RSz-pt?c=lJXO zeSaQ*)aC{sacalkE(o<+>1c~WPfGnKtu-k`2YJOl(N%r)u4fVPB7smYkR!~c!2?5` z13>TVn~)3sx0VVE3qQ8?KZ&MxkE4I%0~`Az9FB(`>c8OVKkXPppkEAHPbYRdx2|$6 zf*j~H;Ex?orIrKyj}eYAQ%S`h#fdpw@Cz<58T?>}2zx?ZT|%y(T{~Xpi=gkZTF;#e z3F=$F{`}PNFa>wR$HyM5Ztd#os_vSakjh6}Iy<|C2zX8|>ielr&R9gEe{Fo0|8gK| zkY8VlfcpEtiRi!U(S93Z-L~--2kINT8Vb8(?fP;$%dGar++*t=TX-4CqacXL~rY1d|=Z=r( zT@Q{j(+PIADfOvY)=!t^b(wRg^=0R7n8aR zGu)okchJ@@r~V1+Ujz8JBY#ZBac;u{>vE*5fRzYXDa*3>N)mKP)O|L;_+)+@W$#W_ z1HCSw0BPQbdWUK^n3k3~2){GaaiQou{J~fvivE1qGekZm14C^s_>uYe_~`Zew$6dh z&dz2jY$hilM;G9l1sq4)7Rn-P`H|OEqLr&S14g)DnOR!8zA!U0bMNinM7RaVpGDl; zY-jGh@Wtt|F$}V_tG&EfzS=j}cU7%lLF4S~p35Z7OJBR(*!bOV-xB?PmY6P)N+eR& zm~-CP8no)@pwxKED3P8YEr^+%?8q=F+vn#4EnG5nUJd7iSCx$h8STYo>M5XqKb%>U znOOtZDR(?fN4Uox1Ub*XkD$wZHf)e>(EIqv@I>f+HsGnMd7^oAVqjvh1vQ|K6RZ%6 zS(agDR(SBQc^!rdmzN8vK88rJqp)LqgEnqF@z805HGQJ@1$%=?VI znx@@zXBFMuwRP&sqPpVZvLw6xjS9oC{Zv}oug2jiu%}ZIcF@Ph^RCBI+pC^=W&^+h zj2nbIz;VS1L`O&K^(`$vP;`|5u!wLu!{jbqrQrBbaLVo`2m4qAM6qoVHq>>S?SyR` z*c}LXS5Tn6g}o`iI4?0VQ5zK%Rio1xlm=zWuo0G1 z^py0Jec*;-d4*BepMTH?oH9ZSY$|#H{6GG`^dbW~(Tbd;`3i5jm;5kK&YSMhLvC&3 zHVlp9eiusn*^WX_1XErv-9)w42ysdA?Q+H zW2CX6=ZBNdm?l$Xli3vMR5s}}x+#0!T+v)!UV9=H(9Y8J_>rsU{BkbR#TC3Ak&M^yN+acyB-!s$e-Ut;vL8ySlm4a z;eC8dRw94`tf=X%Uv6%$8=aRI9J<1TIMbMW6^gFu(4jHQl%}SF=r%{bpn^hWvR;$u# z?d{>=gnV=2*2E~n{(eP81u1Vp-=CRI+4rUIi{BUTb=|}MLKVTK$?cJhCIQZa#yajY zS(}2N?bT>;%*x8uf2KJB_nPh{ry9GsF6g#rFPyoz{6b}Ux%JHA;^J%)xNpWt!c`tW z{u(CNPsL)vuD!{O6hY2%>I8qZSX5O>PtGWEy~`Lmb<%8B>Xe$#4z(JbN}bN!q|z#P z103ko^(ZQx!H{Jz7!K^;PuL-zok!n77@PpuN3`=QIJaXNJwN>xw8No`D|!K~UHql+Qv5HWo# zB}cT^)aaC12QmqJ`hK3=ZTlv`6W5{ViSOGfIrW*|Np9;)2Kq```l9tRy)3$O^iF?{ zPUxJ75m{!I-KK-QaAxVc*K(Nd4R;SXqMht4{NEVX+)5Xg*@A@Hq<~LB?(fg+E!|uH zU3PZ%u1f#x_XH9a#ZAHrAeO}~uDGbkZnqajX~LVrJ32BtOdr;k&6(!Li-vb=RcYZo z`qr(9iCZ7x(X%SDkm@`-$X^FI#FKA=?VA8jd`GrF3{<|9@ts|UOIx>`TRl)x)i(4! zVqK1178248PoY+zy&b-K_%Oy7g$1LI+PVzSss7D$wDbGdl?MTRO~_3yx};tzTQz$r zSe*>=WM;>E0%_2DZ|vGpUk`K^A_F2WgkGGN_tjVSqN1X_;l#xEe>Y_`nRX?JRuvab z&g~`&C+yn9tq{;bzH_ItrY6OpN)iIAqhPS4RrcG0}H-ItzJ63yu82&@oOwU+_mf1X}n+g2RO*VZncK44<-BB z+G+Yq=F+7-dwvP<@5T-$&i;rIO9hgIw?BI0!*bRmde8z!{OebMCn_45k1IEu%%;5w zZ2pLs8`5bugFNbyN1`6vx%0719fnqA*1>c_p8opl;C9=-g_85G(`0|)akx&hT0i8E z*A@6EL~^dPMBnD8U_geo*8BDX#{gcTd}-NoZ9(W=)2}TPcDLmocn>*d*J+ov2jn5X zXFPA=Qd371{#Zkjvdg7$5|ZN*kx0rQ<9P{LJPXA|<1mAld=H zeF6pxh+y>)4x?&9&JdUXE;854k?u3tb-luOx$vDg{YKA04)&K8U7-bQ)+Wt*$g!p+ zd>R)h=2(eXDrI1_slN=}1S%ohp zYxPt}n<|U+ZLLX3DU^`n%%gKDue)jSGZ|@jXG+Vz=eq5l&YUz)X3nM`O}_;8bW6G= z_o!T>E7p}a=o+%+4G~%088Lh6<#L=(cY(ZrFR_KH+jR%FgZ+7u*Ku0ym53ZDlD%;M zDK00U$79jCFr3=+e>+Zd8Kbx^y8rpdw;+ePi0(rEcF{vXXUH9M*J=IbOtmtzv9U2p zZ8n=LDb51T5%B!EI7_u9F0HyYe&&bWyDz1GlxsT+qsKmW{8-WTmSZB|aUiV( zp-$K&$n7N5VfYthn(2a0mpq8WDj|Q##oe9cXkS151>`UX`LAYm5)73sDbrS_7t)At z*5MiTSA0^HksSw%*X)LU1ay357LSh~J$h6Zm8R3Ap?k~D-LOHIy{AFm-Px^**;QYa zRWHuV0((CzdpO8}4s*y3Xwud&^^FK$SLZ;dLr&F+Ft25_rg2cGMHE*=P(hwTxPLCH!Q#wALd+)uu>YY1x z@80P^ABRO1AI(Ks%hl*K+3~t~e6n@AtgJn{^)dCj`q*8&RP*TJAcw=-+1WIg>Og-! zi0R>uof>t-y6A!jjiN=dZvEf4lN?@2@VXN~XWf0N=0ImPpPrm-n>8vX&)n@Ol2R#B znbf1LwNQFh?-AAi|L(|>>jb9nKL5a# z9Oin*V%Hps8x@|4J7>=Pz-uLu-d02$AM6y*6+OQCyoDT5;^I)icc$llc4<5}_oyZ} z9q6GAFnMu9vAj6DbA5(iJ=DU~vNpz>?Ab;zfJ01KKV`b&+4+RMK#-yA2MrCG_ zjasUD`OG&}adz8*Hiir*E=5IYiqq0M(?ZkbGuepsVqLeqJEOB(2kKohauw41-dFoo zTMPU9LH@!E;jl%)zA-iRc}Di9+Jk~xPK8|TB|^jN?%g<&O~C;*1>ZTr|1HqT^~&AZ zt2|$RWm0odWs$lF-$Id8uw<=`?m#1}vaQK}T3tBj~iK{rDR^4%a1L}Fip6pKf zo^E+P(CcIO@2Z!pRH`=%LEgUw>`rv2>e~rA)5v2sH_8=VnBt-*_gW4*wPuqbAjy{F zg8t9slNhyMMJprJo3Y9#Q_FY~k2ZAkutg4!9=0@%*iP77rNsdba<2F7igPm&jID>o z#Kgo_Rqc*a9xqlw4X(wq?9avVNq zlfWb>_yT3k)!mnn1Ko?Cg6jldkn@@v0Zpnql3mf#LUJI^Vez+xlB;b|b15a~Lmi@X zJWG2YK1tUT^v0&MU$F+&){6@@u6Tr zjdB19{%^XtMFoAbLxMTGeca3`Dl2Pk)9Z^WWs<-7woPeKX~Znm6;#ut=)i4DZGI`h zOUtV7N~y{@4xQgO_dr`{X@zvA z9J2{nIpTZ?Ix9NJfxgt`Nv!vrR&C#Sx@&moL?lHYuRFE;i^icGLGYMgK=2as!*F+y zzYB7^=2C^ii(DQ062r07cvB{%yjs=fMwz`zF6@(vkk(-#is~cOIV0CsxT=)reLM7O z{`m-UPzCo=aF7%BcH;2FTvT^-HCu;GT{Si0(*uFSLtPj@?fr^guIS!|F@Y(-mgIM0 zaW8WisqhzQofDQsa1z#?_ca*OC3#HY)*TC(}?XH@?&Z-WxtL~JYjrGCdlLMQysqz8SfFa-G>$_VQx`Nh36<)yN?DnC)#7fB z!>_%4IZIsJ+?=ICB0tL7{@(1QvYaHs45Uw{tXPiEHi zq;9v>)C6A~O&ks3E?4zOPYwjEGlgLhcHN7M-5uy4zvVcsRg7q(#oprRXf3(HQ)IUJ z(u5qhYprT2kK(wXIgT4Cestix!M$rbb2e+FyM>a9riL|Pi${b zWYL!fd*tdHml<@G>e3Yo=eHL4-U>46j7L+xp|&jsyRdcav_GR z?TLwOu)`KNH6uRBVLeKlQl-)kmWV=@sXCVuBE7nXm4b>5UH&(vkkqhs7x<)`RO!=hk@B#+>Rab5%2HV z@qUDT;C_V5J-1;)dF|B@?)S7eM0aW)%WxBPyXuLse{y(u{0LS@BmIVYYLo{hWD2@W zboZVy!GJQ!6dA~Det&WKgV$KiKP2R^X|e|>@v@5htOl`G9KBCW0iDKWi+vohILtX5 z^p*J^*^es_9A7ZZoR6G~M`F*2FVBc^fIH)yC5P$^JM5o4eyi0V`;&p#WgX~oN6;>J z0et0m>jc5dfw0Ihzd--h$)=yy!QEN%TWxJW>+df)pr5|OSDf3oFFG2ZXzc}>q&b_B zMpg&^BI6+advZH0JMHW&ICIl5U)0LyHUE0eet<6}U*hm=L-*YCgZ@xG46Ceyo+d7D zd3%z`xob`i3j~46u;DJPA=%%@|0kPmUi4qEJc4B@?^XHJXFAqO`V1r1zKLtsR*;NE zNzF#Pd#CU{q(+zR9chQO!m{&Hv4xPQX#j3VXTf1#kFu$KhQ1i<)SPUJuM zj6L-LfC+56y*~W^lm#th`;^yuyU3>Y=Bs#gX zxN*THx|cicONSWMm{YK(W-A(|v90N^r041!rU`A>rg4C?uVnZw%yymDml4p z^m^gTU=MovKC&c}*us#r9&ZPCq7Nqfe2aR-pda$Z&s?U*NA?7pOv(Y%6~k)7gUQK0 zPXzxS#^$&-EO5FRvD@!aGJV(U+5=erBbz^6`zYjanHk5nd2`k^-vU{R(Sp&GUG>oM zZ5lf{)4a0iGhRQkVSen#&u&g#tE(%ltE+q2en+0jqO)y0J_P+)(#k#;`K>=vJhLjj zDUys|mm@j;KfzeA0IK^LwAwBofAX$JdD#!-1bxj)vM1(_E%vq6zP{+w){!n8mrh3; zP?>3?t*o`RwXLm94=ZA`CPfMH&zg&g9c98Cs=H{;pvT01{ONu+!|ngfTKN5O>D1Ks z9zV8_B>{)%4%Nf)K|f~~9kP!c9En^VwmOiYuRDl&ySn`A%x**wgO0!UE)4c?Ms(|g2CMy12+&Pq_u%a! ziOWhQZw%-Gfyx`CR6=wgpC=AI^yt$zfZw|H0Q}u;1(u~|^$B3tVn=nvYQ4ed2zgxS zE>vK*U!tfik_qSiodN)Tp2_W+;!qadsW~ZX1U+VRZAlf|$MhE>%)cUcmcFa5x76XP zJduPuW)*eUUzl&>!y);g^^r$>h9i^xfsWT$ICA<@{~MmBOC;O{I_0brprKJlb|J+N zgZ&Vmu|4|e&9I`IX0;XsGxg9iuu zuqIF5#P}!U6;uFJ7mT4u{r~d{{YO5J)tr*#)Qq8zwfTvo#i?CPtTX5!huvS|)--)a zw#q}{A!(XCZ|$>Mts^5NR&tLlRg*lYuGAyZt6S$YJjeGH{cYSNYBb}WgdFH;5svdY zJ~6}?^eSp<6xf?_U?H9R$5?|PZITxa^q>|(e$2hsyTr=+`}M~s1`m^+} zk2rdWpck2C5|LhA$x9JQGc#F_$6+?9IdeQ+jvIysW^EuLAuqlmuUj6mzB@0AxVyL$ z4qu-aJCQT;`sS@0+g~r-xOdA*nUt*3k`V|&2YJh}RphC_r-R6Ig#F?g!ysM&;#ElG zL7#6|M+Tbw0&44d(q-Q%l06rGzZ+>}TLX6*pw|{x?0MNM(^eHS2w6q_?+H`NP_lgzP>mx6aB=_u_k3P+) z?qe7vNjYG^WP0@gS$Q~#=Lw3FI z^kQ#lUte#p+rA4F$6!E4qNPdBj>arwkE;qgrLh@`!#X>ea{d=7=N{8m702;%aa^D< zV6csc@^TEk>mZD>DT%9=&`nn;gh)mlB7!rdB9AJME`b_S!eiwj ztdeHfL{c_bjmTcch>`u}|Hkh*x3{+)Iw$_<;Qj;ar@!axl#^7s(}uH{Y;^fAa2w@GE+?S3Ep z{Oz~%^F4tvUh2BKPS&D;Oly$Rx%c=!JlS~bVug~%%PXR^tFgEVUFh02#JbG1Hj4fs zK|iWiefYX6=Vd3lDs^3Z}T+Ya;&Ni@f!YgQA1J`fWyK%5;X(4!FEX-yLqx}StO zvq`UuOHP2T0^S!C>=UXTiWi?HgPSd*;fB$KaJ9bkRLETcolYqut+(&mx3K>6{O>(6 z(xBB&wIz3z1iCvgYY78VLd(Bg2Z+2ir8OlLRQf6^D$l010h^$|1oUdv`+2I5dGy!v zw0Uo7b($(eu6+&YXc+P65lgLzaR@ILFc;IX>f_gL+Az^7-f8e=hu$F%q3A7}estPl zClWei9YrA0N6X`Y}!prk%Okx6*BdR;WvleVA97X%R&DC&^KGs4G%(E9BPQ3WVdbTMFDE;wHAn3hB92y`<(J4RpFY&niX{j{c{56Ajx2`3ml z3fQ;c>p4Jo)B2IDr>0~gI!SKW&s+)ExP68iybs8Cn>KJ{GmNBl7`%LMaDv`qk|)q8 zHxsyH^j%19*lhfyv{Tm^M^|oc=wK(aO{nJfg)QB^NrbM+Cg#s=d3U|a7w%$7!w0WjnU;tsxcgfeU z!?dyx7KV9G__}WK*;7dg`FeRQjqo#4cN+G;pa+uVW{6GJVKT>Y@9r}s&UvEy?;{8Q z&z$ienRm*$b0Z@-4u%F5>Am6&pQ(Y?1u=tp?;Tih9fz8ispN9RXD5#@M; zUofRC52d6oL`9K%EKJWmrz2O^$de5Sa+{ki%nln=5G-1gS{}57_x38x_&7y$jFZ## zx)WV0t*op2e!iCY`o#BDyx=f6mT!;;VjCm~uFlJ6-XzA55Fs*gIpRz!<4JhY;1C}BLr<47kbpwp6LEtidvj$ud5s%uc(|KfsBujHs zu&DiuCeolf92|`Gx_Pw4UJ1D9V@qY-!+W&I#AW{29FwE2=PFt>@h#h zt83>+6l!>Rt$O|(1e@nSZo3i;tX{b|f1ug3=c{HsbI`g!Gpgm$tGA*1!O`1o2iI&x ze~V5d{Gu)+_2JOdQD)kZ&6#$XrqM%POeS3#xfef}x)b&Uawo^A)iBnHv>qa$3)#(Y zR|nLNF`uH5`Pw=Zv9=FJftcKx8M3trvP+}!MTCi;sSA~5@A^0wGD`#u@a zPY`s4>LQPxrPgNUXj3-qyEOLw<+i9bTa1v~Ku*z7&4C=f5?|kSQ)=pZSd@+)E_k?i zT4z2LVvRNH_2KEyau{9QA$#!gq;zw5EY|%xY(Y?Tu#1U(cRSU3Gql}Tr)Y!D;p>R+ zw(%-nUWEudt==&Ci0tMwJzX_LXTGT6u(Dv*>;aNd?bVmxQmIcUh_B~$Q*>zk{BFCy z%pTQYi!|7RM07$f6{4IEt}wZ~n3}pV7`B(#d)`p&b9#qEXHK?S^3&g(9w0+5GT9D{ zo{2i14x5j6bdF8;-V3=)J5(Y%&2B&sX#e?~Q^vo)NsRsGcy@L+4^NhF5{B*of!({N zr1aDBlG4(W&u9R;Zx5Bg*H2tkDNxI_=3$*$U6)m$%_z_Y_}WHCZGMPxqc+5MF~Ead zK!>mIj*LSIZ(1DGV+O=DsDIvc4_o5X4y&biF6F@NB*$Env3I157c78za&+`6tm$S$ ze7FT2*N~7?+Y>w32|7|a0vn=R8TE4rzCm92=1_5cNsWYotuwTg^)7#}SqeWW=VL&W z&F(X5U%YriMdV_JR;AL`W#y~TvkAJeX|ru=?%Td#qwm~S^`r3rdWmkw(A_ZEroDXB-mrlx2GAF?s==(t${^?w zUiWO9p;$Nv7+M5$C|z)MitbC$BcpUVD^JsFOHju4S{?LWD!ggyMk3}wPEN{}u3wi8 zkUd1N|8nb6fB#&6Z@)!7hCdi0E?dJqLVU;gxrsU|WE*0l z%F)~gbllSvYF?;4p6^DdPo+Iefy}C|^Ia#!LicZx|=kx;>#>W*m5$35-OP}~2($)sWSM+5L zV}#I2OF4kVH+~tA(W`!DFJ@7pL)Lh~OgZ6=68MPisZB)3;w5VTVLA2y%=mIR^!oVN za9o=7%<|Go>#b`m-M`^#21YutbVv5&T}nwX)w)%$9r>>m^wyho>i54Tc5$@-0|-^qv5389|oe03Z9LVZLDo@$A6KChyh0HFb zH|ekA^*^-Xq<(=~-FL`Hb?zd+to(?BY9G`5mrZ$+6K9vnVpwKiz>6mbx@hn2sV$OM zx`58#I~W^2XUVr*-@~%AlU^LLIT;BlEKM=)I%iqUKdFR$1L*kb*CV^R%J|T279Vra znbN;OkGa_B0*5r+_(=H?N}9)@vyFLnOlern>=agSth1P*!@GE9=pzJ1!2Y8(j?VDtI3JWOqCdKV*>@q}he~y2-0s-K{cKKo9a*x=?b8&f7b>u^^`# z%sPvuKfgeogGi~_NSvZWKRhb=lQR0m(e&}y`hKv*ItTLp1ie~L)Q%>VE6@SmrS>9S zop8E9eaLtW=m4kEnA=l$nS}C3RVZb_Q zLr^-sTY=1-lhBT_YrJoOi%O^Huy^;Q?o>K=1xUtEJY7?Lw5zbeUY7t>e@cVp)&9rjhRZL@fiLU zrln!fK{?De4Re>ji@tQZr)STlo*U5|+k?w_7{SVxy8+FklYbz`r^BqH6dUm7(J8rS zCC8(KT(EbX@d`PmvtZT@qCVohi>Ld*B$kaXduBN)RY5fS4+&j-@}B`Z#sT6_jg1|5 zQ#-m;e1nd%<0_h=PNX#j&POZG76Kd8g+`pxtWE^ zON@Zd>}y5+D#o}g10)b5_$AIpgIp6BNR=*F+*~lm}B~8kn%Sz?&C8hFo&_{(Bb_!y|k|xpJD$#tM6v`FKZc zQ~INxPFy{1I_G(iQ*?DcnQ5hw1GXa-K4flvSxOT1azhW<|(3Ixh;XVH5*TL2eCp0E53{WhKEG`gQ5;{d6>n1~Y%ma5 ze%K}aNC~DK+Vp_bAjJX&l~pX!HM@JUkSiCE6(PF9szj`{VmUYnXKNuyYDA-?mnbI= zsh~ZyRfU{fDpf5|C4zE^Q2q(TcV>4o9$LVOR9;-yQ7`@Y`{vD?H?#AWj`#f-Tvb8Q zUxWKlaGD_9;s-W;cL!JpYxaX9{H|njNAxrP{08)3)lS)azB%(9Qkr}U?AFTRqbo-b zTZUFDhsq_bTvp4Lx;{EpLvo`M^rK}B=xpIG%i}ZL;Rk1KDR4;S-xqIb96ccCbGjfp zVqD)Z!Ce(JzK^vBkLS;AqgJx}6XJ(3+PUn0kpH{vllyxveEH@X`^Y5bH zJ^uSU3>{aL@axuK2%L&cu2tNv0bO7hWcOYE(F<#j|L_L}`pfU;&!0?zdu64RR?CK= zOP65uu<`3BHspiAXcZJDAca~fGrg@ zXaUNypPzhv!T|cGVXbP_jO{5Sw?W=(En_)N(2?B$eXCq1IZZ<^LUX_)o>TO(4;^ez zlZuqxP(hyLE{ivC?6?#-Q@L5-8-bNImmP%IEA*pTR~CjEX8 z>A)_?&e&qn8IoWk7-CsWpnHEm4E)p9JqzF|z|j9S{59x_T{BPGjLSp#~jHQiDq zsGg;YW;(C;L2N8*xG7Ur(2`b z8bu!(CFsatG1p-T$UZs|9U086-xhmRX~Aq@w%2m8^k#3-ZFF_ndLtHP`GtGq!0v zF(NuA?-t2{4)Wg*ku4V(*eCs~(j&iZ!r?(axGLfd-NZEC>G{!N$R4mC5ZNI+Vej5I zn}=C>ihe7XZ|1j>Eu(4}O4({!B`sA-!_$vinp{q`q;;za^stn1OfTsG&iSKXRNxrc%gd^^9xB5b@^qq~|8u$i?VRV;s)e zf3rCiOXGobOKn<)Va2d;l?Zwhi&p#<);vsZwwX=Nn-#}|`{(8h+MJ%4vvZD?gEOtE0_g@+bTz-JE3tKTXm%mBT z^LK7yBdUL~7w|#fgS3hXN=v3Unl-gnBF?@MADXTjHBuFI^^8Owlkn`cr-xk~GHVT6 z#mn>-^h(q-l*t_jANT-SAV8EK@>Q9n2(Ueq}&dkrOkQyCnK;ggPO| z!yt^^*Ig{wK!&^Lanorve**{f19reCeD?G>rPnliRX8yXIZHf5&~vKoNYWQ(*fz-* z;M!rwjoWTUpPSd?b=&sliR6qt&>W%*1D%m$NS>BNOZ>WuaL+ZDw=a%ugIoqxhoO$i zPT7Ip&JOh1>l3g~w^?jVcxVAZ8$PIk`gn^Ic*!H^URYH!r*tQ3yJ=+Z`dpMqp7Gq=oE`nlZHw)q z<}8`Pyv9Ty7BW^A+L-Fd;#SWMUWE3$YX$SzC9eC9%Tsndasz)S=<92=XoJ-|7oJLw z{ubjnFh|C`N(Vmp@h9Zzu?)~L%jvq8Fy|4~qlIEtuefH^p6h8iEpuL&)dX@RGsN9F z`N);~?C60q#6BxMSIPP{#5UbJp5PceOX}Lf{8tqHgB#sJ!AgJ^y4)AOy<2b!$cE8t%=oOIDMpsA~@74TSHqZwM`NJLD9VqXm`O76C)V;u~ zRZEY*_`}3`kgxeg8yII7;h9nLG|;s=vi7XrsO$6fLN-&V7xe|#?6KXt9?95quH7GD z)A{Y2ocy^Uxcj0H_yc`-JoMifd%DEn8K5(EA=JU=ucIf&ylJtPPZ13ae}EbQrV zV26r&v4U421KtMG8GFGj7VWNxQ_yGk`{d-P)Sm!n`72G`LEb*lp9=jK8G`zrYrqR+?Y^hyzW7cvbqA4f-Sr5ueUzLvaeN$c zN4~mq=gtS4q#5s%SFUt3c+fwz)XZ9+$XWABkh2YCQFBE14o3Rk8(kf|4|{N)uJ1bFF9hp%-l;Rvb+;eJ;5k1mnn*vn zRR5`tmENP7u0(G#8I97WR;;HNeh)bt=Pb5U&1rCd;(_ZHiyEwno#Y`YP z=)oSiJ!{S?@Z$sdh<6zNiBL0eLe3l2ve3@Qxsb|0^hxP~+r$6B&T?ARtbF0Q@`N77 z;R&Z;nSQP}xQPjUTE$82e&nx658Oi1fxoe{+C1BsAngAEz5qf00MAd3Cq<=4x Z{{;$xH 0 && tarr[rule] != nil && !override { + return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text} + } + + } else { + tarr = make([]*transText, 7, 7) + t.cardinalTanslations[key] = tarr + } + + trans := &transText{ + text: text, + indexes: make([]int, 2, 2), + } + + tarr[rule] = trans + + idx := strings.Index(text, paramZero) + if idx == -1 { + tarr[rule] = nil + return &ErrCardinalTranslation{text: fmt.Sprintf("error: parameter '%s' not found, may want to use 'Add' instead of 'AddCardinal'. locale: '%s' key: '%v' text: '%s'", paramZero, t.Locale(), key, text)} + } + + trans.indexes[0] = idx + trans.indexes[1] = idx + len(paramZero) + + return nil +} + +// AddOrdinal adds an ordinal plural translation for a particular language/locale +// {0} is the only replacement type accepted and only one variable is accepted as +// multiple cannot be used for a plural rule determination, unless it is a range; +// see AddRange below. +// eg. in locale 'en' one: '{0}st day of spring' other: '{0}nd day of spring' - 1st, 2nd, 3rd... +func (t *translator) AddOrdinal(key interface{}, text string, rule locales.PluralRule, override bool) error { + + var verified bool + + // verify plural rule exists for locale + for _, pr := range t.PluralsOrdinal() { + if pr == rule { + verified = true + break + } + } + + if !verified { + return &ErrOrdinalTranslation{text: fmt.Sprintf("error: ordinal plural rule '%s' does not exist for locale '%s' key: '%v' text: '%s'", rule, t.Locale(), key, text)} + } + + tarr, ok := t.ordinalTanslations[key] + if ok { + // verify not adding a conflicting record + if len(tarr) > 0 && tarr[rule] != nil && !override { + return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text} + } + + } else { + tarr = make([]*transText, 7, 7) + t.ordinalTanslations[key] = tarr + } + + trans := &transText{ + text: text, + indexes: make([]int, 2, 2), + } + + tarr[rule] = trans + + idx := strings.Index(text, paramZero) + if idx == -1 { + tarr[rule] = nil + return &ErrOrdinalTranslation{text: fmt.Sprintf("error: parameter '%s' not found, may want to use 'Add' instead of 'AddOrdinal'. locale: '%s' key: '%v' text: '%s'", paramZero, t.Locale(), key, text)} + } + + trans.indexes[0] = idx + trans.indexes[1] = idx + len(paramZero) + + return nil +} + +// AddRange adds a range plural translation for a particular language/locale +// {0} and {1} are the only replacement types accepted and only these are accepted. +// eg. in locale 'nl' one: '{0}-{1} day left' other: '{0}-{1} days left' +func (t *translator) AddRange(key interface{}, text string, rule locales.PluralRule, override bool) error { + + var verified bool + + // verify plural rule exists for locale + for _, pr := range t.PluralsRange() { + if pr == rule { + verified = true + break + } + } + + if !verified { + return &ErrRangeTranslation{text: fmt.Sprintf("error: range plural rule '%s' does not exist for locale '%s' key: '%v' text: '%s'", rule, t.Locale(), key, text)} + } + + tarr, ok := t.rangeTanslations[key] + if ok { + // verify not adding a conflicting record + if len(tarr) > 0 && tarr[rule] != nil && !override { + return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text} + } + + } else { + tarr = make([]*transText, 7, 7) + t.rangeTanslations[key] = tarr + } + + trans := &transText{ + text: text, + indexes: make([]int, 4, 4), + } + + tarr[rule] = trans + + idx := strings.Index(text, paramZero) + if idx == -1 { + tarr[rule] = nil + return &ErrRangeTranslation{text: fmt.Sprintf("error: parameter '%s' not found, are you sure you're adding a Range Translation? locale: '%s' key: '%v' text: '%s'", paramZero, t.Locale(), key, text)} + } + + trans.indexes[0] = idx + trans.indexes[1] = idx + len(paramZero) + + idx = strings.Index(text, paramOne) + if idx == -1 { + tarr[rule] = nil + return &ErrRangeTranslation{text: fmt.Sprintf("error: parameter '%s' not found, a Range Translation requires two parameters. locale: '%s' key: '%v' text: '%s'", paramOne, t.Locale(), key, text)} + } + + trans.indexes[2] = idx + trans.indexes[3] = idx + len(paramOne) + + return nil +} + +// T creates the translation for the locale given the 'key' and params passed in +func (t *translator) T(key interface{}, params ...string) (string, error) { + + trans, ok := t.translations[key] + if !ok { + return unknownTranslation, ErrUnknowTranslation + } + + b := make([]byte, 0, 64) + + var start, end, count int + + for i := 0; i < len(trans.indexes); i++ { + end = trans.indexes[i] + b = append(b, trans.text[start:end]...) + b = append(b, params[count]...) + i++ + start = trans.indexes[i] + count++ + } + + b = append(b, trans.text[start:]...) + + return string(b), nil +} + +// C creates the cardinal translation for the locale given the 'key', 'num' and 'digit' arguments and param passed in +func (t *translator) C(key interface{}, num float64, digits uint64, param string) (string, error) { + + tarr, ok := t.cardinalTanslations[key] + if !ok { + return unknownTranslation, ErrUnknowTranslation + } + + rule := t.CardinalPluralRule(num, digits) + + trans := tarr[rule] + + b := make([]byte, 0, 64) + b = append(b, trans.text[:trans.indexes[0]]...) + b = append(b, param...) + b = append(b, trans.text[trans.indexes[1]:]...) + + return string(b), nil +} + +// O creates the ordinal translation for the locale given the 'key', 'num' and 'digit' arguments and param passed in +func (t *translator) O(key interface{}, num float64, digits uint64, param string) (string, error) { + + tarr, ok := t.ordinalTanslations[key] + if !ok { + return unknownTranslation, ErrUnknowTranslation + } + + rule := t.OrdinalPluralRule(num, digits) + + trans := tarr[rule] + + b := make([]byte, 0, 64) + b = append(b, trans.text[:trans.indexes[0]]...) + b = append(b, param...) + b = append(b, trans.text[trans.indexes[1]:]...) + + return string(b), nil +} + +// R creates the range translation for the locale given the 'key', 'num1', 'digit1', 'num2' and 'digit2' arguments +// and 'param1' and 'param2' passed in +func (t *translator) R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) (string, error) { + + tarr, ok := t.rangeTanslations[key] + if !ok { + return unknownTranslation, ErrUnknowTranslation + } + + rule := t.RangePluralRule(num1, digits1, num2, digits2) + + trans := tarr[rule] + + b := make([]byte, 0, 64) + b = append(b, trans.text[:trans.indexes[0]]...) + b = append(b, param1...) + b = append(b, trans.text[trans.indexes[1]:trans.indexes[2]]...) + b = append(b, param2...) + b = append(b, trans.text[trans.indexes[3]:]...) + + return string(b), nil +} + +// VerifyTranslations checks to ensures that no plural rules have been +// missed within the translations. +func (t *translator) VerifyTranslations() error { + + for k, v := range t.cardinalTanslations { + + for _, rule := range t.PluralsCardinal() { + + if v[rule] == nil { + return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "plural", rule: rule, key: k} + } + } + } + + for k, v := range t.ordinalTanslations { + + for _, rule := range t.PluralsOrdinal() { + + if v[rule] == nil { + return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "ordinal", rule: rule, key: k} + } + } + } + + for k, v := range t.rangeTanslations { + + for _, rule := range t.PluralsRange() { + + if v[rule] == nil { + return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "range", rule: rule, key: k} + } + } + } + + return nil +} diff --git a/go-playground/universal-translator/universal_translator.go b/go-playground/universal-translator/universal_translator.go new file mode 100644 index 0000000..32c619e --- /dev/null +++ b/go-playground/universal-translator/universal_translator.go @@ -0,0 +1,113 @@ +package ut + +import ( + "strings" + + "go-playground/locales" +) + +// UniversalTranslator holds all locale & translation data +type UniversalTranslator struct { + translators map[string]Translator + fallback Translator +} + +// New returns a new UniversalTranslator instance set with +// the fallback locale and locales it should support +func New(fallback locales.Translator, supportedLocales ...locales.Translator) *UniversalTranslator { + + t := &UniversalTranslator{ + translators: make(map[string]Translator), + } + + for _, v := range supportedLocales { + + trans := newTranslator(v) + t.translators[strings.ToLower(trans.Locale())] = trans + + if fallback.Locale() == v.Locale() { + t.fallback = trans + } + } + + if t.fallback == nil && fallback != nil { + t.fallback = newTranslator(fallback) + } + + return t +} + +// FindTranslator trys to find a Translator based on an array of locales +// and returns the first one it can find, otherwise returns the +// fallback translator. +func (t *UniversalTranslator) FindTranslator(locales ...string) (trans Translator, found bool) { + + for _, locale := range locales { + + if trans, found = t.translators[strings.ToLower(locale)]; found { + return + } + } + + return t.fallback, false +} + +// GetTranslator returns the specified translator for the given locale, +// or fallback if not found +func (t *UniversalTranslator) GetTranslator(locale string) (trans Translator, found bool) { + + if trans, found = t.translators[strings.ToLower(locale)]; found { + return + } + + return t.fallback, false +} + +// GetFallback returns the fallback locale +func (t *UniversalTranslator) GetFallback() Translator { + return t.fallback +} + +// AddTranslator adds the supplied translator, if it already exists the override param +// will be checked and if false an error will be returned, otherwise the translator will be +// overridden; if the fallback matches the supplied translator it will be overridden as well +// NOTE: this is normally only used when translator is embedded within a library +func (t *UniversalTranslator) AddTranslator(translator locales.Translator, override bool) error { + + lc := strings.ToLower(translator.Locale()) + _, ok := t.translators[lc] + if ok && !override { + return &ErrExistingTranslator{locale: translator.Locale()} + } + + trans := newTranslator(translator) + + if t.fallback.Locale() == translator.Locale() { + + // because it's optional to have a fallback, I don't impose that limitation + // don't know why you wouldn't but... + if !override { + return &ErrExistingTranslator{locale: translator.Locale()} + } + + t.fallback = trans + } + + t.translators[lc] = trans + + return nil +} + +// VerifyTranslations runs through all locales and identifies any issues +// eg. missing plural rules for a locale +func (t *UniversalTranslator) VerifyTranslations() (err error) { + + for _, trans := range t.translators { + err = trans.VerifyTranslations() + if err != nil { + return + } + } + + return +} diff --git a/go-playground/validator/v10/.github/CONTRIBUTING.md b/go-playground/validator/v10/.github/CONTRIBUTING.md new file mode 100644 index 0000000..6d3811c --- /dev/null +++ b/go-playground/validator/v10/.github/CONTRIBUTING.md @@ -0,0 +1,9 @@ +# Contribution Guidelines + +## Quality Standard + +To ensure the continued stability of this package, tests are required to be written or already exist in order for a pull request to be merged. + +## Reporting issues + +Please open an issue or join the gitter chat [![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) for any issues, questions or possible enhancements to the package. diff --git a/go-playground/validator/v10/.github/ISSUE_TEMPLATE.md b/go-playground/validator/v10/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..8afb259 --- /dev/null +++ b/go-playground/validator/v10/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,13 @@ +### Package version eg. v8, v9: + + + +### Issue, Question or Enhancement: + + + +### Code sample, to showcase or reproduce: + +```go + +``` diff --git a/go-playground/validator/v10/.github/PULL_REQUEST_TEMPLATE.md b/go-playground/validator/v10/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7bebde3 --- /dev/null +++ b/go-playground/validator/v10/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ +Fixes Or Enhances # . + +**Make sure that you've checked the boxes below before you submit PR:** +- [ ] Tests exist or have been written that cover this particular change. + +Change Details: + +- +- +- + + +@go-playground/admins \ No newline at end of file diff --git a/go-playground/validator/v10/.gitignore b/go-playground/validator/v10/.gitignore new file mode 100644 index 0000000..6e43fac --- /dev/null +++ b/go-playground/validator/v10/.gitignore @@ -0,0 +1,30 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test +bin + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +*.test +*.out +*.txt +cover.html +README.html diff --git a/go-playground/validator/v10/.travis.yml b/go-playground/validator/v10/.travis.yml new file mode 100644 index 0000000..85a7be3 --- /dev/null +++ b/go-playground/validator/v10/.travis.yml @@ -0,0 +1,29 @@ +language: go +go: + - 1.15.2 + - tip +matrix: + allow_failures: + - go: tip + +notifications: + email: + recipients: dean.karn@gmail.com + on_success: change + on_failure: always + +before_install: + - go install github.com/mattn/goveralls + - mkdir -p $GOPATH/src/gopkg.in + - ln -s $GOPATH/src/github.com/$TRAVIS_REPO_SLUG $GOPATH/src/gopkg.in/validator.v9 + +# Only clone the most recent commit. +git: + depth: 1 + +script: + - go test -v -race -covermode=atomic -coverprofile=coverage.coverprofile ./... + +after_success: | + [ $TRAVIS_GO_VERSION = 1.15.2 ] && + goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN diff --git a/go-playground/validator/v10/LICENSE b/go-playground/validator/v10/LICENSE new file mode 100644 index 0000000..6a2ae9a --- /dev/null +++ b/go-playground/validator/v10/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Dean Karn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/go-playground/validator/v10/Makefile b/go-playground/validator/v10/Makefile new file mode 100644 index 0000000..19c91ed --- /dev/null +++ b/go-playground/validator/v10/Makefile @@ -0,0 +1,18 @@ +GOCMD=GO111MODULE=on go + +linters-install: + @golangci-lint --version >/dev/null 2>&1 || { \ + echo "installing linting tools..."; \ + curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.21.0; \ + } + +lint: linters-install + $(PWD)/bin/golangci-lint run + +test: + $(GOCMD) test -cover -race ./... + +bench: + $(GOCMD) test -bench=. -benchmem ./... + +.PHONY: test lint linters-install \ No newline at end of file diff --git a/go-playground/validator/v10/README.md b/go-playground/validator/v10/README.md new file mode 100644 index 0000000..de72348 --- /dev/null +++ b/go-playground/validator/v10/README.md @@ -0,0 +1,299 @@ +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.4.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) +[![GoDoc](https://godoc.org/github.com/go-playground/validator?status.svg)](https://pkg.go.dev/gin-valid/go-playground/validator/v10) +![License](https://img.shields.io/dub/l/vibe-d.svg) + +Package validator implements value validations for structs and individual fields based on tags. + +It has the following **unique** features: + +- Cross Field and Cross Struct validations by using validation tags or custom validators. +- Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated. +- Ability to dive into both map keys and values for validation +- Handles type interface by determining it's underlying type prior to validation. +- Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29) +- Alias validation tags, which allows for mapping of several validations to a single tag for easier defining of validations on structs +- Extraction of custom defined Field Name e.g. can specify to extract the JSON name while validating and have it available in the resulting FieldError +- Customizable i18n aware error messages. +- Default validator for the [gin](https://github.com/gin-gonic/gin) web framework; upgrading from v8 to v9 in gin see [here](https://github.com/go-playground/validator/tree/master/_examples/gin-upgrading-overriding) + +Installation +------------ + +Use go get. + + go get gin-valid/go-playground/validator/v10 + +Then import the validator package into your own code. + + import "gin-valid/go-playground/validator/v10" + +Error Return Value +------- + +Validation functions return type error + +They return type error to avoid the issue discussed in the following, where err is always != nil: + +* http://stackoverflow.com/a/29138676/3158232 +* https://github.com/go-playground/validator/issues/134 + +Validator only InvalidValidationError for bad validation input, nil or ValidationErrors as type error; so, in your code all you need to do is check if the error returned is not nil, and if it's not check if error is InvalidValidationError ( if necessary, most of the time it isn't ) type cast it to type ValidationErrors like so: + +```go +err := validate.Struct(mystruct) +validationErrors := err.(validator.ValidationErrors) + ``` + +Usage and documentation +------ + +Please see https://godoc.org/github.com/go-playground/validator for detailed usage docs. + +##### Examples: + +- [Simple](https://github.com/go-playground/validator/blob/master/_examples/simple/main.go) +- [Custom Field Types](https://github.com/go-playground/validator/blob/master/_examples/custom/main.go) +- [Struct Level](https://github.com/go-playground/validator/blob/master/_examples/struct-level/main.go) +- [Translations & Custom Errors](https://github.com/go-playground/validator/blob/master/_examples/translations/main.go) +- [Gin upgrade and/or override validator](https://github.com/go-playground/validator/tree/v9/_examples/gin-upgrading-overriding) +- [wash - an example application putting it all together](https://github.com/bluesuncorp/wash) + +Baked-in Validations +------ + +### Fields: + +| Tag | Description | +| - | - | +| eqcsfield | Field Equals Another Field (relative)| +| eqfield | Field Equals Another Field | +| fieldcontains | NOT DOCUMENTED IN doc.go | +| fieldexcludes | NOT DOCUMENTED IN doc.go | +| gtcsfield | Field Greater Than Another Relative Field | +| gtecsfield | Field Greater Than or Equal To Another Relative Field | +| gtefield | Field Greater Than or Equal To Another Field | +| gtfield | Field Greater Than Another Field | +| ltcsfield | Less Than Another Relative Field | +| ltecsfield | Less Than or Equal To Another Relative Field | +| ltefield | Less Than or Equal To Another Field | +| ltfield | Less Than Another Field | +| necsfield | Field Does Not Equal Another Field (relative) | +| nefield | Field Does Not Equal Another Field | + +### Network: + +| Tag | Description | +| - | - | +| cidr | Classless Inter-Domain Routing CIDR | +| cidrv4 | Classless Inter-Domain Routing CIDRv4 | +| cidrv6 | Classless Inter-Domain Routing CIDRv6 | +| datauri | Data URL | +| fqdn | Full Qualified Domain Name (FQDN) | +| hostname | Hostname RFC 952 | +| hostname_port | HostPort | +| hostname_rfc1123 | Hostname RFC 1123 | +| ip | Internet Protocol Address IP | +| ip4_addr | Internet Protocol Address IPv4 | +| ip6_addr |Internet Protocol Address IPv6 | +| ip_addr | Internet Protocol Address IP | +| ipv4 | Internet Protocol Address IPv4 | +| ipv6 | Internet Protocol Address IPv6 | +| mac | Media Access Control Address MAC | +| tcp4_addr | Transmission Control Protocol Address TCPv4 | +| tcp6_addr | Transmission Control Protocol Address TCPv6 | +| tcp_addr | Transmission Control Protocol Address TCP | +| udp4_addr | User Datagram Protocol Address UDPv4 | +| udp6_addr | User Datagram Protocol Address UDPv6 | +| udp_addr | User Datagram Protocol Address UDP | +| unix_addr | Unix domain socket end point Address | +| uri | URI String | +| url | URL String | +| url_encoded | URL Encoded | +| urn_rfc2141 | Urn RFC 2141 String | + +### Strings: + +| Tag | Description | +| - | - | +| alpha | Alpha Only | +| alphanum | Alphanumeric | +| alphanumunicode | Alphanumeric Unicode | +| alphaunicode | Alpha Unicode | +| ascii | ASCII | +| contains | Contains | +| containsany | Contains Any | +| containsrune | Contains Rune | +| endswith | Ends With | +| lowercase | Lowercase | +| multibyte | Multi-Byte Characters | +| number | NOT DOCUMENTED IN doc.go | +| numeric | Numeric | +| printascii | Printable ASCII | +| startswith | Starts With | +| uppercase | Uppercase | + +### Format: +| Tag | Description | +| - | - | +| base64 | Base64 String | +| base64url | Base64URL String | +| btc_addr | Bitcoin Address | +| btc_addr_bech32 | Bitcoin Bech32 Address (segwit) | +| datetime | Datetime | +| e164 | e164 formatted phone number | +| email | E-mail String +| eth_addr | Ethereum Address | +| hexadecimal | Hexadecimal String | +| hexcolor | Hexcolor String | +| hsl | HSL String | +| hsla | HSLA String | +| html | HTML Tags | +| html_encoded | HTML Encoded | +| isbn | International Standard Book Number | +| isbn10 | International Standard Book Number 10 | +| isbn13 | International Standard Book Number 13 | +| json | JSON | +| latitude | Latitude | +| longitude | Longitude | +| rgb | RGB String | +| rgba | RGBA String | +| ssn | Social Security Number SSN | +| uuid | Universally Unique Identifier UUID | +| uuid3 | Universally Unique Identifier UUID v3 | +| uuid3_rfc4122 | Universally Unique Identifier UUID v3 RFC4122 | +| uuid4 | Universally Unique Identifier UUID v4 | +| uuid4_rfc4122 | Universally Unique Identifier UUID v4 RFC4122 | +| uuid5 | Universally Unique Identifier UUID v5 | +| uuid5_rfc4122 | Universally Unique Identifier UUID v5 RFC4122 | +| uuid_rfc4122 | Universally Unique Identifier UUID RFC4122 | + +### Comparisons: +| Tag | Description | +| - | - | +| eq | Equals | +| gt | Greater than| +| gte |Greater than or equal | +| lt | Less Than | +| lte | Less Than or Equal | +| ne | Not Equal | + +### Other: +| Tag | Description | +| - | - | +| dir | Directory | +| endswith | Ends With | +| excludes | Excludes | +| excludesall | Excludes All | +| excludesrune | Excludes Rune | +| file | File path | +| isdefault | Is Default | +| len | Length | +| max | Maximum | +| min | Minimum | +| oneof | One Of | +| required | Required | +| required_if | Required If | +| required_unless | Required Unless | +| required_with | Required With | +| required_with_all | Required With All | +| required_without | Required Without | +| required_without_all | Required Without All | +| excluded_with | Excluded With | +| excluded_with_all | Excluded With All | +| excluded_without | Excluded Without | +| excluded_without_all | Excluded Without All | +| unique | Unique | + +Benchmarks +------ +###### Run on MacBook Pro (15-inch, 2017) go version go1.10.2 darwin/amd64 +```go +goos: darwin +goarch: amd64 +pkg: github.com/go-playground/validator +BenchmarkFieldSuccess-8 20000000 83.6 ns/op 0 B/op 0 allocs/op +BenchmarkFieldSuccessParallel-8 50000000 26.8 ns/op 0 B/op 0 allocs/op +BenchmarkFieldFailure-8 5000000 291 ns/op 208 B/op 4 allocs/op +BenchmarkFieldFailureParallel-8 20000000 107 ns/op 208 B/op 4 allocs/op +BenchmarkFieldArrayDiveSuccess-8 2000000 623 ns/op 201 B/op 11 allocs/op +BenchmarkFieldArrayDiveSuccessParallel-8 10000000 237 ns/op 201 B/op 11 allocs/op +BenchmarkFieldArrayDiveFailure-8 2000000 859 ns/op 412 B/op 16 allocs/op +BenchmarkFieldArrayDiveFailureParallel-8 5000000 335 ns/op 413 B/op 16 allocs/op +BenchmarkFieldMapDiveSuccess-8 1000000 1292 ns/op 432 B/op 18 allocs/op +BenchmarkFieldMapDiveSuccessParallel-8 3000000 467 ns/op 432 B/op 18 allocs/op +BenchmarkFieldMapDiveFailure-8 1000000 1082 ns/op 512 B/op 16 allocs/op +BenchmarkFieldMapDiveFailureParallel-8 5000000 425 ns/op 512 B/op 16 allocs/op +BenchmarkFieldMapDiveWithKeysSuccess-8 1000000 1539 ns/op 480 B/op 21 allocs/op +BenchmarkFieldMapDiveWithKeysSuccessParallel-8 3000000 613 ns/op 480 B/op 21 allocs/op +BenchmarkFieldMapDiveWithKeysFailure-8 1000000 1413 ns/op 721 B/op 21 allocs/op +BenchmarkFieldMapDiveWithKeysFailureParallel-8 3000000 575 ns/op 721 B/op 21 allocs/op +BenchmarkFieldCustomTypeSuccess-8 10000000 216 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeSuccessParallel-8 20000000 82.2 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-8 5000000 274 ns/op 208 B/op 4 allocs/op +BenchmarkFieldCustomTypeFailureParallel-8 20000000 116 ns/op 208 B/op 4 allocs/op +BenchmarkFieldOrTagSuccess-8 2000000 740 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTagSuccessParallel-8 3000000 474 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTagFailure-8 3000000 471 ns/op 224 B/op 5 allocs/op +BenchmarkFieldOrTagFailureParallel-8 3000000 414 ns/op 224 B/op 5 allocs/op +BenchmarkStructLevelValidationSuccess-8 10000000 213 ns/op 32 B/op 2 allocs/op +BenchmarkStructLevelValidationSuccessParallel-8 20000000 91.8 ns/op 32 B/op 2 allocs/op +BenchmarkStructLevelValidationFailure-8 3000000 473 ns/op 304 B/op 8 allocs/op +BenchmarkStructLevelValidationFailureParallel-8 10000000 234 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-8 5000000 385 ns/op 32 B/op 2 allocs/op +BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 161 ns/op 32 B/op 2 allocs/op +BenchmarkStructSimpleCustomTypeFailure-8 2000000 640 ns/op 424 B/op 9 allocs/op +BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 318 ns/op 440 B/op 10 allocs/op +BenchmarkStructFilteredSuccess-8 2000000 597 ns/op 288 B/op 9 allocs/op +BenchmarkStructFilteredSuccessParallel-8 10000000 266 ns/op 288 B/op 9 allocs/op +BenchmarkStructFilteredFailure-8 3000000 454 ns/op 256 B/op 7 allocs/op +BenchmarkStructFilteredFailureParallel-8 10000000 214 ns/op 256 B/op 7 allocs/op +BenchmarkStructPartialSuccess-8 3000000 502 ns/op 256 B/op 6 allocs/op +BenchmarkStructPartialSuccessParallel-8 10000000 225 ns/op 256 B/op 6 allocs/op +BenchmarkStructPartialFailure-8 2000000 702 ns/op 480 B/op 11 allocs/op +BenchmarkStructPartialFailureParallel-8 5000000 329 ns/op 480 B/op 11 allocs/op +BenchmarkStructExceptSuccess-8 2000000 793 ns/op 496 B/op 12 allocs/op +BenchmarkStructExceptSuccessParallel-8 10000000 193 ns/op 240 B/op 5 allocs/op +BenchmarkStructExceptFailure-8 2000000 639 ns/op 464 B/op 10 allocs/op +BenchmarkStructExceptFailureParallel-8 5000000 300 ns/op 464 B/op 10 allocs/op +BenchmarkStructSimpleCrossFieldSuccess-8 3000000 417 ns/op 72 B/op 3 allocs/op +BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 163 ns/op 72 B/op 3 allocs/op +BenchmarkStructSimpleCrossFieldFailure-8 2000000 645 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCrossFieldFailureParallel-8 5000000 285 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 3000000 588 ns/op 80 B/op 4 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 10000000 221 ns/op 80 B/op 4 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2000000 868 ns/op 320 B/op 9 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 5000000 337 ns/op 320 B/op 9 allocs/op +BenchmarkStructSimpleSuccess-8 5000000 260 ns/op 0 B/op 0 allocs/op +BenchmarkStructSimpleSuccessParallel-8 20000000 90.6 ns/op 0 B/op 0 allocs/op +BenchmarkStructSimpleFailure-8 2000000 619 ns/op 424 B/op 9 allocs/op +BenchmarkStructSimpleFailureParallel-8 5000000 296 ns/op 424 B/op 9 allocs/op +BenchmarkStructComplexSuccess-8 1000000 1454 ns/op 128 B/op 8 allocs/op +BenchmarkStructComplexSuccessParallel-8 3000000 579 ns/op 128 B/op 8 allocs/op +BenchmarkStructComplexFailure-8 300000 4140 ns/op 3041 B/op 53 allocs/op +BenchmarkStructComplexFailureParallel-8 1000000 2127 ns/op 3041 B/op 53 allocs/op +BenchmarkOneof-8 10000000 140 ns/op 0 B/op 0 allocs/op +BenchmarkOneofParallel-8 20000000 70.1 ns/op 0 B/op 0 allocs/op +``` + +Complementary Software +---------------------- + +Here is a list of software that complements using this library either pre or post validation. + +* [form](https://github.com/go-playground/form) - Decodes url.Values into Go value(s) and Encodes Go value(s) into url.Values. Dual Array and Full map support. +* [mold](https://github.com/go-playground/mold) - A general library to help modify or set data within data structures and other objects + +How to Contribute +------ + +Make a pull request... + +License +------ +Distributed under MIT License, please see license file within the code for more details. diff --git a/go-playground/validator/v10/_examples/custom-validation/main.go b/go-playground/validator/v10/_examples/custom-validation/main.go new file mode 100644 index 0000000..db8822e --- /dev/null +++ b/go-playground/validator/v10/_examples/custom-validation/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + + "gin-valid/go-playground/validator/v10" +) + +// MyStruct .. +type MyStruct struct { + String string `validate:"is-awesome"` +} + +// use a single instance of Validate, it caches struct info +var validate *validator.Validate + +func main() { + + validate = validator.New() + validate.RegisterValidation("is-awesome", ValidateMyVal) + + s := MyStruct{String: "awesome"} + + err := validate.Struct(s) + if err != nil { + fmt.Printf("Err(s):\n%+v\n", err) + } + + s.String = "not awesome" + err = validate.Struct(s) + if err != nil { + fmt.Printf("Err(s):\n%+v\n", err) + } +} + +// ValidateMyVal implements validator.Func +func ValidateMyVal(fl validator.FieldLevel) bool { + return fl.Field().String() == "awesome" +} diff --git a/go-playground/validator/v10/_examples/custom/main.go b/go-playground/validator/v10/_examples/custom/main.go new file mode 100644 index 0000000..ebaa6b8 --- /dev/null +++ b/go-playground/validator/v10/_examples/custom/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "reflect" + + "gin-valid/go-playground/validator/v10" +) + +// DbBackedUser User struct +type DbBackedUser struct { + Name sql.NullString `validate:"required"` + Age sql.NullInt64 `validate:"required"` +} + +// use a single instance of Validate, it caches struct info +var validate *validator.Validate + +func main() { + + validate = validator.New() + + // register all sql.Null* types to use the ValidateValuer CustomTypeFunc + validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) + + // build object for validation + x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}} + + err := validate.Struct(x) + + if err != nil { + fmt.Printf("Err(s):\n%+v\n", err) + } +} + +// ValidateValuer implements validator.CustomTypeFunc +func ValidateValuer(field reflect.Value) interface{} { + + if valuer, ok := field.Interface().(driver.Valuer); ok { + + val, err := valuer.Value() + if err == nil { + return val + } + // handle the error how you want + } + + return nil +} diff --git a/go-playground/validator/v10/_examples/dive/main.go b/go-playground/validator/v10/_examples/dive/main.go new file mode 100644 index 0000000..f67592e --- /dev/null +++ b/go-playground/validator/v10/_examples/dive/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + + "gin-valid/go-playground/validator/v10" +) + +// Test ... +type Test struct { + Array []string `validate:"required,gt=0,dive,required"` + Map map[string]string `validate:"required,gt=0,dive,keys,keymax,endkeys,required,max=1000"` +} + +// use a single instance of Validate, it caches struct info +var validate *validator.Validate + +func main() { + + validate = validator.New() + + // registering alias so we can see the differences between + // map key, value validation errors + validate.RegisterAlias("keymax", "max=10") + + var test Test + + val(test) + + test.Array = []string{""} + test.Map = map[string]string{"test > than 10": ""} + val(test) +} + +func val(test Test) { + fmt.Println("testing") + err := validate.Struct(test) + fmt.Println(err) +} diff --git a/go-playground/validator/v10/_examples/gin-upgrading-overriding/main.go b/go-playground/validator/v10/_examples/gin-upgrading-overriding/main.go new file mode 100644 index 0000000..99a1ddf --- /dev/null +++ b/go-playground/validator/v10/_examples/gin-upgrading-overriding/main.go @@ -0,0 +1,10 @@ +package main + +import "gin-valid/gin/binding" + +func main() { + + binding.Validator = new(defaultValidator) + + // regular gin logic +} diff --git a/go-playground/validator/v10/_examples/gin-upgrading-overriding/v8_to_v9.go b/go-playground/validator/v10/_examples/gin-upgrading-overriding/v8_to_v9.go new file mode 100644 index 0000000..c049989 --- /dev/null +++ b/go-playground/validator/v10/_examples/gin-upgrading-overriding/v8_to_v9.go @@ -0,0 +1,55 @@ +package main + +import ( + "reflect" + "sync" + + "gin-valid/gin/binding" + "gin-valid/go-playground/validator/v10" +) + +type defaultValidator struct { + once sync.Once + validate *validator.Validate +} + +var _ binding.StructValidator = &defaultValidator{} + +func (v *defaultValidator) ValidateStruct(obj interface{}) error { + + if kindOfData(obj) == reflect.Struct { + + v.lazyinit() + + if err := v.validate.Struct(obj); err != nil { + return err + } + } + + return nil +} + +func (v *defaultValidator) Engine() interface{} { + v.lazyinit() + return v.validate +} + +func (v *defaultValidator) lazyinit() { + v.once.Do(func() { + v.validate = validator.New() + v.validate.SetTagName("binding") + + // add any custom validations etc. here + }) +} + +func kindOfData(data interface{}) reflect.Kind { + + value := reflect.ValueOf(data) + valueType := value.Kind() + + if valueType == reflect.Ptr { + valueType = value.Elem().Kind() + } + return valueType +} diff --git a/go-playground/validator/v10/_examples/simple/main.go b/go-playground/validator/v10/_examples/simple/main.go new file mode 100644 index 0000000..02242d8 --- /dev/null +++ b/go-playground/validator/v10/_examples/simple/main.go @@ -0,0 +1,101 @@ +package main + +import ( + "fmt" + + "gin-valid/go-playground/validator/v10" +) + +// User contains user information +type User struct { + FirstName string `validate:"required"` + LastName string `validate:"required"` + Age uint8 `validate:"gte=0,lte=130"` + Email string `validate:"required,email"` + FavouriteColor string `validate:"iscolor"` // alias for 'hexcolor|rgb|rgba|hsl|hsla' + Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage... +} + +// Address houses a users address information +type Address struct { + Street string `validate:"required"` + City string `validate:"required"` + Planet string `validate:"required"` + Phone string `validate:"required"` +} + +// use a single instance of Validate, it caches struct info +var validate *validator.Validate + +func main() { + + validate = validator.New() + + validateStruct() + validateVariable() +} + +func validateStruct() { + + address := &Address{ + Street: "Eavesdown Docks", + Planet: "Persphone", + Phone: "none", + } + + user := &User{ + FirstName: "Badger", + LastName: "Smith", + Age: 135, + Email: "Badger.Smith@gmail.com", + FavouriteColor: "#000-", + Addresses: []*Address{address}, + } + + // returns nil or ValidationErrors ( []FieldError ) + err := validate.Struct(user) + if err != nil { + + // this check is only needed when your code could produce + // an invalid value for validation such as interface with nil + // value most including myself do not usually have code like this. + if _, ok := err.(*validator.InvalidValidationError); ok { + fmt.Println(err) + return + } + + for _, err := range err.(validator.ValidationErrors) { + + fmt.Println(err.Namespace()) + fmt.Println(err.Field()) + fmt.Println(err.StructNamespace()) + fmt.Println(err.StructField()) + fmt.Println(err.Tag()) + fmt.Println(err.ActualTag()) + fmt.Println(err.Kind()) + fmt.Println(err.Type()) + fmt.Println(err.Value()) + fmt.Println(err.Param()) + fmt.Println() + } + + // from here you can create your own error messages in whatever language you wish + return + } + + // save user to database +} + +func validateVariable() { + + myEmail := "joeybloggs.gmail.com" + + errs := validate.Var(myEmail, "required,email") + + if errs != nil { + fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag + return + } + + // email ok, move on +} diff --git a/go-playground/validator/v10/_examples/struct-level/main.go b/go-playground/validator/v10/_examples/struct-level/main.go new file mode 100644 index 0000000..4472623 --- /dev/null +++ b/go-playground/validator/v10/_examples/struct-level/main.go @@ -0,0 +1,120 @@ +package main + +import ( + "fmt" + "reflect" + "strings" + + "gin-valid/go-playground/validator/v10" +) + +// User contains user information +type User struct { + FirstName string `json:"fname"` + LastName string `json:"lname"` + Age uint8 `validate:"gte=0,lte=130"` + Email string `json:"e-mail" validate:"required,email"` + FavouriteColor string `validate:"hexcolor|rgb|rgba"` + Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage... +} + +// Address houses a users address information +type Address struct { + Street string `validate:"required"` + City string `validate:"required"` + Planet string `validate:"required"` + Phone string `validate:"required"` +} + +// use a single instance of Validate, it caches struct info +var validate *validator.Validate + +func main() { + + validate = validator.New() + + // register function to get tag name from json tags. + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] + if name == "-" { + return "" + } + return name + }) + + // register validation for 'User' + // NOTE: only have to register a non-pointer type for 'User', validator + // interanlly dereferences during it's type checks. + validate.RegisterStructValidation(UserStructLevelValidation, User{}) + + // build 'User' info, normally posted data etc... + address := &Address{ + Street: "Eavesdown Docks", + Planet: "Persphone", + Phone: "none", + City: "Unknown", + } + + user := &User{ + FirstName: "", + LastName: "", + Age: 45, + Email: "Badger.Smith@gmail", + FavouriteColor: "#000", + Addresses: []*Address{address}, + } + + // returns InvalidValidationError for bad validation input, nil or ValidationErrors ( []FieldError ) + err := validate.Struct(user) + if err != nil { + + // this check is only needed when your code could produce + // an invalid value for validation such as interface with nil + // value most including myself do not usually have code like this. + if _, ok := err.(*validator.InvalidValidationError); ok { + fmt.Println(err) + return + } + + for _, err := range err.(validator.ValidationErrors) { + + fmt.Println(err.Namespace()) // can differ when a custom TagNameFunc is registered or + fmt.Println(err.Field()) // by passing alt name to ReportError like below + fmt.Println(err.StructNamespace()) + fmt.Println(err.StructField()) + fmt.Println(err.Tag()) + fmt.Println(err.ActualTag()) + fmt.Println(err.Kind()) + fmt.Println(err.Type()) + fmt.Println(err.Value()) + fmt.Println(err.Param()) + fmt.Println() + } + + // from here you can create your own error messages in whatever language you wish + return + } + + // save user to database +} + +// UserStructLevelValidation contains custom struct level validations that don't always +// make sense at the field validation level. For Example this function validates that either +// FirstName or LastName exist; could have done that with a custom field validation but then +// would have had to add it to both fields duplicating the logic + overhead, this way it's +// only validated once. +// +// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way +// hooks right into validator and you can combine with validation tags and still have a +// common error output format. +func UserStructLevelValidation(sl validator.StructLevel) { + + user := sl.Current().Interface().(User) + + if len(user.FirstName) == 0 && len(user.LastName) == 0 { + sl.ReportError(user.FirstName, "fname", "FirstName", "fnameorlname", "") + sl.ReportError(user.LastName, "lname", "LastName", "fnameorlname", "") + } + + // plus can do more, even with different tag than "fnameorlname" +} diff --git a/go-playground/validator/v10/_examples/translations/main.go b/go-playground/validator/v10/_examples/translations/main.go new file mode 100644 index 0000000..61d4f02 --- /dev/null +++ b/go-playground/validator/v10/_examples/translations/main.go @@ -0,0 +1,129 @@ +package main + +import ( + "fmt" + + "gin-valid/go-playground/locales/en" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/go-playground/validator/v10" + en_translations "gin-valid/go-playground/validator/v10/translations/en" +) + +// User contains user information +type User struct { + FirstName string `validate:"required"` + LastName string `validate:"required"` + Age uint8 `validate:"gte=0,lte=130"` + Email string `validate:"required,email"` + FavouriteColor string `validate:"iscolor"` // alias for 'hexcolor|rgb|rgba|hsl|hsla' + Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage... +} + +// Address houses a users address information +type Address struct { + Street string `validate:"required"` + City string `validate:"required"` + Planet string `validate:"required"` + Phone string `validate:"required"` +} + +// use a single instance , it caches struct info +var ( + uni *ut.UniversalTranslator + validate *validator.Validate +) + +func main() { + + // NOTE: ommitting allot of error checking for brevity + + en := en.New() + uni = ut.New(en, en) + + // this is usually know or extracted from http 'Accept-Language' header + // also see uni.FindTranslator(...) + trans, _ := uni.GetTranslator("en") + + validate = validator.New() + en_translations.RegisterDefaultTranslations(validate, trans) + + translateAll(trans) + translateIndividual(trans) + translateOverride(trans) // yep you can specify your own in whatever locale you want! +} + +func translateAll(trans ut.Translator) { + + type User struct { + Username string `validate:"required"` + Tagline string `validate:"required,lt=10"` + Tagline2 string `validate:"required,gt=1"` + } + + user := User{ + Username: "Joeybloggs", + Tagline: "This tagline is way too long.", + Tagline2: "1", + } + + err := validate.Struct(user) + if err != nil { + + // translate all error at once + errs := err.(validator.ValidationErrors) + + // returns a map with key = namespace & value = translated error + // NOTICE: 2 errors are returned and you'll see something surprising + // translations are i18n aware!!!! + // eg. '10 characters' vs '1 character' + fmt.Println(errs.Translate(trans)) + } +} + +func translateIndividual(trans ut.Translator) { + + type User struct { + Username string `validate:"required"` + } + + var user User + + err := validate.Struct(user) + if err != nil { + + errs := err.(validator.ValidationErrors) + + for _, e := range errs { + // can translate each error one at a time. + fmt.Println(e.Translate(trans)) + } + } +} + +func translateOverride(trans ut.Translator) { + + validate.RegisterTranslation("required", trans, func(ut ut.Translator) error { + return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details + }, func(ut ut.Translator, fe validator.FieldError) string { + t, _ := ut.T("required", fe.Field()) + + return t + }) + + type User struct { + Username string `validate:"required"` + } + + var user User + + err := validate.Struct(user) + if err != nil { + + errs := err.(validator.ValidationErrors) + + for _, e := range errs { + // can translate each error one at a time. + fmt.Println(e.Translate(trans)) + } + } +} diff --git a/go-playground/validator/v10/baked_in.go b/go-playground/validator/v10/baked_in.go new file mode 100644 index 0000000..8e41f62 --- /dev/null +++ b/go-playground/validator/v10/baked_in.go @@ -0,0 +1,2285 @@ +package validator + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "net" + "net/url" + "os" + "reflect" + "strconv" + "strings" + "sync" + "time" + "unicode/utf8" + + "golang.org/x/crypto/sha3" + + urn "github.com/leodido/go-urn" +) + +// Func accepts a FieldLevel interface for all validation needs. The return +// value should be true when validation succeeds. +type Func func(fl FieldLevel) bool + +// FuncCtx accepts a context.Context and FieldLevel interface for all +// validation needs. The return value should be true when validation succeeds. +type FuncCtx func(ctx context.Context, fl FieldLevel) bool + +// wrapFunc wraps noramal Func makes it compatible with FuncCtx +func wrapFunc(fn Func) FuncCtx { + if fn == nil { + return nil // be sure not to wrap a bad function. + } + return func(ctx context.Context, fl FieldLevel) bool { + return fn(fl) + } +} + +var ( + restrictedTags = map[string]struct{}{ + diveTag: {}, + keysTag: {}, + endKeysTag: {}, + structOnlyTag: {}, + omitempty: {}, + skipValidationTag: {}, + utf8HexComma: {}, + utf8Pipe: {}, + noStructLevelTag: {}, + requiredTag: {}, + isdefault: {}, + } + + // BakedInAliasValidators is a default mapping of a single validation tag that + // defines a common or complex set of validation(s) to simplify + // adding validation to structs. + bakedInAliases = map[string]string{ + "iscolor": "hexcolor|rgb|rgba|hsl|hsla", + "country_code": "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric", + } + + // BakedInValidators is the default map of ValidationFunc + // you can add, remove or even replace items to suite your needs, + // or even disregard and use your own map if so desired. + bakedInValidators = map[string]Func{ + "required": hasValue, + "required_if": requiredIf, + "required_unless": requiredUnless, + "required_with": requiredWith, + "required_with_all": requiredWithAll, + "required_without": requiredWithout, + "required_without_all": requiredWithoutAll, + "excluded_with": excludedWith, + "excluded_with_all": excludedWithAll, + "excluded_without": excludedWithout, + "excluded_without_all": excludedWithoutAll, + "isdefault": isDefault, + "len": hasLengthOf, + "min": hasMinOf, + "max": hasMaxOf, + "eq": isEq, + "ne": isNe, + "lt": isLt, + "lte": isLte, + "gt": isGt, + "gte": isGte, + "eqfield": isEqField, + "eqcsfield": isEqCrossStructField, + "necsfield": isNeCrossStructField, + "gtcsfield": isGtCrossStructField, + "gtecsfield": isGteCrossStructField, + "ltcsfield": isLtCrossStructField, + "ltecsfield": isLteCrossStructField, + "nefield": isNeField, + "gtefield": isGteField, + "gtfield": isGtField, + "ltefield": isLteField, + "ltfield": isLtField, + "fieldcontains": fieldContains, + "fieldexcludes": fieldExcludes, + "alpha": isAlpha, + "alphanum": isAlphanum, + "alphaunicode": isAlphaUnicode, + "alphanumunicode": isAlphanumUnicode, + "numeric": isNumeric, + "number": isNumber, + "hexadecimal": isHexadecimal, + "hexcolor": isHEXColor, + "rgb": isRGB, + "rgba": isRGBA, + "hsl": isHSL, + "hsla": isHSLA, + "e164": isE164, + "email": isEmail, + "url": isURL, + "uri": isURI, + "urn_rfc2141": isUrnRFC2141, // RFC 2141 + "file": isFile, + "base64": isBase64, + "base64url": isBase64URL, + "contains": contains, + "containsany": containsAny, + "containsrune": containsRune, + "excludes": excludes, + "excludesall": excludesAll, + "excludesrune": excludesRune, + "startswith": startsWith, + "endswith": endsWith, + "startsnotwith": startsNotWith, + "endsnotwith": endsNotWith, + "isbn": isISBN, + "isbn10": isISBN10, + "isbn13": isISBN13, + "eth_addr": isEthereumAddress, + "btc_addr": isBitcoinAddress, + "btc_addr_bech32": isBitcoinBech32Address, + "uuid": isUUID, + "uuid3": isUUID3, + "uuid4": isUUID4, + "uuid5": isUUID5, + "uuid_rfc4122": isUUIDRFC4122, + "uuid3_rfc4122": isUUID3RFC4122, + "uuid4_rfc4122": isUUID4RFC4122, + "uuid5_rfc4122": isUUID5RFC4122, + "ascii": isASCII, + "printascii": isPrintableASCII, + "multibyte": hasMultiByteCharacter, + "datauri": isDataURI, + "latitude": isLatitude, + "longitude": isLongitude, + "ssn": isSSN, + "ipv4": isIPv4, + "ipv6": isIPv6, + "ip": isIP, + "cidrv4": isCIDRv4, + "cidrv6": isCIDRv6, + "cidr": isCIDR, + "tcp4_addr": isTCP4AddrResolvable, + "tcp6_addr": isTCP6AddrResolvable, + "tcp_addr": isTCPAddrResolvable, + "udp4_addr": isUDP4AddrResolvable, + "udp6_addr": isUDP6AddrResolvable, + "udp_addr": isUDPAddrResolvable, + "ip4_addr": isIP4AddrResolvable, + "ip6_addr": isIP6AddrResolvable, + "ip_addr": isIPAddrResolvable, + "unix_addr": isUnixAddrResolvable, + "mac": isMAC, + "hostname": isHostnameRFC952, // RFC 952 + "hostname_rfc1123": isHostnameRFC1123, // RFC 1123 + "fqdn": isFQDN, + "unique": isUnique, + "oneof": isOneOf, + "html": isHTML, + "html_encoded": isHTMLEncoded, + "url_encoded": isURLEncoded, + "dir": isDir, + "json": isJSON, + "hostname_port": isHostnamePort, + "lowercase": isLowercase, + "uppercase": isUppercase, + "datetime": isDatetime, + "timezone": isTimeZone, + "iso3166_1_alpha2": isIso3166Alpha2, + "iso3166_1_alpha3": isIso3166Alpha3, + "iso3166_1_alpha_numeric": isIso3166AlphaNumeric, + } +) + +var oneofValsCache = map[string][]string{} +var oneofValsCacheRWLock = sync.RWMutex{} + +func parseOneOfParam2(s string) []string { + oneofValsCacheRWLock.RLock() + vals, ok := oneofValsCache[s] + oneofValsCacheRWLock.RUnlock() + if !ok { + oneofValsCacheRWLock.Lock() + vals = splitParamsRegex.FindAllString(s, -1) + for i := 0; i < len(vals); i++ { + vals[i] = strings.Replace(vals[i], "'", "", -1) + } + oneofValsCache[s] = vals + oneofValsCacheRWLock.Unlock() + } + return vals +} + +func isURLEncoded(fl FieldLevel) bool { + return uRLEncodedRegex.MatchString(fl.Field().String()) +} + +func isHTMLEncoded(fl FieldLevel) bool { + return hTMLEncodedRegex.MatchString(fl.Field().String()) +} + +func isHTML(fl FieldLevel) bool { + return hTMLRegex.MatchString(fl.Field().String()) +} + +func isOneOf(fl FieldLevel) bool { + vals := parseOneOfParam2(fl.Param()) + + field := fl.Field() + + var v string + switch field.Kind() { + case reflect.String: + v = field.String() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v = strconv.FormatInt(field.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + v = strconv.FormatUint(field.Uint(), 10) + default: + panic(fmt.Sprintf("Bad field type %T", field.Interface())) + } + for i := 0; i < len(vals); i++ { + if vals[i] == v { + return true + } + } + return false +} + +// isUnique is the validation function for validating if each array|slice|map value is unique +func isUnique(fl FieldLevel) bool { + + field := fl.Field() + param := fl.Param() + v := reflect.ValueOf(struct{}{}) + + switch field.Kind() { + case reflect.Slice, reflect.Array: + elem := field.Type().Elem() + if elem.Kind() == reflect.Ptr { + elem = elem.Elem() + } + + if param == "" { + m := reflect.MakeMap(reflect.MapOf(elem, v.Type())) + + for i := 0; i < field.Len(); i++ { + m.SetMapIndex(reflect.Indirect(field.Index(i)), v) + } + return field.Len() == m.Len() + } + + sf, ok := elem.FieldByName(param) + if !ok { + panic(fmt.Sprintf("Bad field name %s", param)) + } + + sfTyp := sf.Type + if sfTyp.Kind() == reflect.Ptr { + sfTyp = sfTyp.Elem() + } + + m := reflect.MakeMap(reflect.MapOf(sfTyp, v.Type())) + for i := 0; i < field.Len(); i++ { + m.SetMapIndex(reflect.Indirect(reflect.Indirect(field.Index(i)).FieldByName(param)), v) + } + return field.Len() == m.Len() + case reflect.Map: + m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type())) + + for _, k := range field.MapKeys() { + m.SetMapIndex(field.MapIndex(k), v) + } + return field.Len() == m.Len() + default: + panic(fmt.Sprintf("Bad field type %T", field.Interface())) + } +} + +// IsMAC is the validation function for validating if the field's value is a valid MAC address. +func isMAC(fl FieldLevel) bool { + + _, err := net.ParseMAC(fl.Field().String()) + + return err == nil +} + +// IsCIDRv4 is the validation function for validating if the field's value is a valid v4 CIDR address. +func isCIDRv4(fl FieldLevel) bool { + + ip, _, err := net.ParseCIDR(fl.Field().String()) + + return err == nil && ip.To4() != nil +} + +// IsCIDRv6 is the validation function for validating if the field's value is a valid v6 CIDR address. +func isCIDRv6(fl FieldLevel) bool { + + ip, _, err := net.ParseCIDR(fl.Field().String()) + + return err == nil && ip.To4() == nil +} + +// IsCIDR is the validation function for validating if the field's value is a valid v4 or v6 CIDR address. +func isCIDR(fl FieldLevel) bool { + + _, _, err := net.ParseCIDR(fl.Field().String()) + + return err == nil +} + +// IsIPv4 is the validation function for validating if a value is a valid v4 IP address. +func isIPv4(fl FieldLevel) bool { + + ip := net.ParseIP(fl.Field().String()) + + return ip != nil && ip.To4() != nil +} + +// IsIPv6 is the validation function for validating if the field's value is a valid v6 IP address. +func isIPv6(fl FieldLevel) bool { + + ip := net.ParseIP(fl.Field().String()) + + return ip != nil && ip.To4() == nil +} + +// IsIP is the validation function for validating if the field's value is a valid v4 or v6 IP address. +func isIP(fl FieldLevel) bool { + + ip := net.ParseIP(fl.Field().String()) + + return ip != nil +} + +// IsSSN is the validation function for validating if the field's value is a valid SSN. +func isSSN(fl FieldLevel) bool { + + field := fl.Field() + + if field.Len() != 11 { + return false + } + + return sSNRegex.MatchString(field.String()) +} + +// IsLongitude is the validation function for validating if the field's value is a valid longitude coordinate. +func isLongitude(fl FieldLevel) bool { + field := fl.Field() + + var v string + switch field.Kind() { + case reflect.String: + v = field.String() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v = strconv.FormatInt(field.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + v = strconv.FormatUint(field.Uint(), 10) + case reflect.Float32: + v = strconv.FormatFloat(field.Float(), 'f', -1, 32) + case reflect.Float64: + v = strconv.FormatFloat(field.Float(), 'f', -1, 64) + default: + panic(fmt.Sprintf("Bad field type %T", field.Interface())) + } + + return longitudeRegex.MatchString(v) +} + +// IsLatitude is the validation function for validating if the field's value is a valid latitude coordinate. +func isLatitude(fl FieldLevel) bool { + field := fl.Field() + + var v string + switch field.Kind() { + case reflect.String: + v = field.String() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v = strconv.FormatInt(field.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + v = strconv.FormatUint(field.Uint(), 10) + case reflect.Float32: + v = strconv.FormatFloat(field.Float(), 'f', -1, 32) + case reflect.Float64: + v = strconv.FormatFloat(field.Float(), 'f', -1, 64) + default: + panic(fmt.Sprintf("Bad field type %T", field.Interface())) + } + + return latitudeRegex.MatchString(v) +} + +// IsDataURI is the validation function for validating if the field's value is a valid data URI. +func isDataURI(fl FieldLevel) bool { + + uri := strings.SplitN(fl.Field().String(), ",", 2) + + if len(uri) != 2 { + return false + } + + if !dataURIRegex.MatchString(uri[0]) { + return false + } + + return base64Regex.MatchString(uri[1]) +} + +// HasMultiByteCharacter is the validation function for validating if the field's value has a multi byte character. +func hasMultiByteCharacter(fl FieldLevel) bool { + + field := fl.Field() + + if field.Len() == 0 { + return true + } + + return multibyteRegex.MatchString(field.String()) +} + +// IsPrintableASCII is the validation function for validating if the field's value is a valid printable ASCII character. +func isPrintableASCII(fl FieldLevel) bool { + return printableASCIIRegex.MatchString(fl.Field().String()) +} + +// IsASCII is the validation function for validating if the field's value is a valid ASCII character. +func isASCII(fl FieldLevel) bool { + return aSCIIRegex.MatchString(fl.Field().String()) +} + +// IsUUID5 is the validation function for validating if the field's value is a valid v5 UUID. +func isUUID5(fl FieldLevel) bool { + return uUID5Regex.MatchString(fl.Field().String()) +} + +// IsUUID4 is the validation function for validating if the field's value is a valid v4 UUID. +func isUUID4(fl FieldLevel) bool { + return uUID4Regex.MatchString(fl.Field().String()) +} + +// IsUUID3 is the validation function for validating if the field's value is a valid v3 UUID. +func isUUID3(fl FieldLevel) bool { + return uUID3Regex.MatchString(fl.Field().String()) +} + +// IsUUID is the validation function for validating if the field's value is a valid UUID of any version. +func isUUID(fl FieldLevel) bool { + return uUIDRegex.MatchString(fl.Field().String()) +} + +// IsUUID5RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v5 UUID. +func isUUID5RFC4122(fl FieldLevel) bool { + return uUID5RFC4122Regex.MatchString(fl.Field().String()) +} + +// IsUUID4RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v4 UUID. +func isUUID4RFC4122(fl FieldLevel) bool { + return uUID4RFC4122Regex.MatchString(fl.Field().String()) +} + +// IsUUID3RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v3 UUID. +func isUUID3RFC4122(fl FieldLevel) bool { + return uUID3RFC4122Regex.MatchString(fl.Field().String()) +} + +// IsUUIDRFC4122 is the validation function for validating if the field's value is a valid RFC4122 UUID of any version. +func isUUIDRFC4122(fl FieldLevel) bool { + return uUIDRFC4122Regex.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) +} + +// IsISBN13 is the validation function for validating if the field's value is a valid v13 ISBN. +func isISBN13(fl FieldLevel) bool { + + s := strings.Replace(strings.Replace(fl.Field().String(), "-", "", 4), " ", "", 4) + + if !iSBN13Regex.MatchString(s) { + return false + } + + var checksum int32 + var i int32 + + factor := []int32{1, 3} + + for i = 0; i < 12; i++ { + checksum += factor[i%2] * int32(s[i]-'0') + } + + return (int32(s[12]-'0'))-((10-(checksum%10))%10) == 0 +} + +// IsISBN10 is the validation function for validating if the field's value is a valid v10 ISBN. +func isISBN10(fl FieldLevel) bool { + + s := strings.Replace(strings.Replace(fl.Field().String(), "-", "", 3), " ", "", 3) + + if !iSBN10Regex.MatchString(s) { + return false + } + + var checksum int32 + var i int32 + + for i = 0; i < 9; i++ { + checksum += (i + 1) * int32(s[i]-'0') + } + + if s[9] == 'X' { + checksum += 10 * 10 + } else { + checksum += 10 * int32(s[9]-'0') + } + + return checksum%11 == 0 +} + +// IsEthereumAddress is the validation function for validating if the field's value is a valid Ethereum address. +func isEthereumAddress(fl FieldLevel) bool { + address := fl.Field().String() + + if !ethAddressRegex.MatchString(address) { + return false + } + + if ethaddressRegexUpper.MatchString(address) || ethAddressRegexLower.MatchString(address) { + return true + } + + // Checksum validation. Reference: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md + address = address[2:] // Skip "0x" prefix. + h := sha3.NewLegacyKeccak256() + // hash.Hash's io.Writer implementation says it never returns an error. https://golang.org/pkg/hash/#Hash + _, _ = h.Write([]byte(strings.ToLower(address))) + hash := hex.EncodeToString(h.Sum(nil)) + + for i := 0; i < len(address); i++ { + if address[i] <= '9' { // Skip 0-9 digits: they don't have upper/lower-case. + continue + } + if hash[i] > '7' && address[i] >= 'a' || hash[i] <= '7' && address[i] <= 'F' { + return false + } + } + + return true +} + +// IsBitcoinAddress is the validation function for validating if the field's value is a valid btc address +func isBitcoinAddress(fl FieldLevel) bool { + address := fl.Field().String() + + if !btcAddressRegex.MatchString(address) { + return false + } + + alphabet := []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") + + decode := [25]byte{} + + for _, n := range []byte(address) { + d := bytes.IndexByte(alphabet, n) + + for i := 24; i >= 0; i-- { + d += 58 * int(decode[i]) + decode[i] = byte(d % 256) + d /= 256 + } + } + + h := sha256.New() + _, _ = h.Write(decode[:21]) + d := h.Sum([]byte{}) + h = sha256.New() + _, _ = h.Write(d) + + validchecksum := [4]byte{} + computedchecksum := [4]byte{} + + copy(computedchecksum[:], h.Sum(d[:0])) + copy(validchecksum[:], decode[21:]) + + return validchecksum == computedchecksum +} + +// IsBitcoinBech32Address is the validation function for validating if the field's value is a valid bech32 btc address +func isBitcoinBech32Address(fl FieldLevel) bool { + address := fl.Field().String() + + if !btcLowerAddressRegexBech32.MatchString(address) && !btcUpperAddressRegexBech32.MatchString(address) { + return false + } + + am := len(address) % 8 + + if am == 0 || am == 3 || am == 5 { + return false + } + + address = strings.ToLower(address) + + alphabet := "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + + hr := []int{3, 3, 0, 2, 3} // the human readable part will always be bc + addr := address[3:] + dp := make([]int, 0, len(addr)) + + for _, c := range addr { + dp = append(dp, strings.IndexRune(alphabet, c)) + } + + ver := dp[0] + + if ver < 0 || ver > 16 { + return false + } + + if ver == 0 { + if len(address) != 42 && len(address) != 62 { + return false + } + } + + values := append(hr, dp...) + + GEN := []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} + + p := 1 + + for _, v := range values { + b := p >> 25 + p = (p&0x1ffffff)<<5 ^ v + + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + p ^= GEN[i] + } + } + } + + if p != 1 { + return false + } + + b := uint(0) + acc := 0 + mv := (1 << 5) - 1 + var sw []int + + for _, v := range dp[1 : len(dp)-6] { + acc = (acc << 5) | v + b += 5 + for b >= 8 { + b -= 8 + sw = append(sw, (acc>>b)&mv) + } + } + + if len(sw) < 2 || len(sw) > 40 { + return false + } + + return true +} + +// ExcludesRune is the validation function for validating that the field's value does not contain the rune specified within the param. +func excludesRune(fl FieldLevel) bool { + return !containsRune(fl) +} + +// ExcludesAll is the validation function for validating that the field's value does not contain any of the characters specified within the param. +func excludesAll(fl FieldLevel) bool { + return !containsAny(fl) +} + +// Excludes is the validation function for validating that the field's value does not contain the text specified within the param. +func excludes(fl FieldLevel) bool { + return !contains(fl) +} + +// ContainsRune is the validation function for validating that the field's value contains the rune specified within the param. +func containsRune(fl FieldLevel) bool { + + r, _ := utf8.DecodeRuneInString(fl.Param()) + + return strings.ContainsRune(fl.Field().String(), r) +} + +// ContainsAny is the validation function for validating that the field's value contains any of the characters specified within the param. +func containsAny(fl FieldLevel) bool { + return strings.ContainsAny(fl.Field().String(), fl.Param()) +} + +// Contains is the validation function for validating that the field's value contains the text specified within the param. +func contains(fl FieldLevel) bool { + return strings.Contains(fl.Field().String(), fl.Param()) +} + +// StartsWith is the validation function for validating that the field's value starts with the text specified within the param. +func startsWith(fl FieldLevel) bool { + return strings.HasPrefix(fl.Field().String(), fl.Param()) +} + +// EndsWith is the validation function for validating that the field's value ends with the text specified within the param. +func endsWith(fl FieldLevel) bool { + return strings.HasSuffix(fl.Field().String(), fl.Param()) +} + +// StartsNotWith is the validation function for validating that the field's value does not start with the text specified within the param. +func startsNotWith(fl FieldLevel) bool { + return !startsWith(fl) +} + +// EndsNotWith is the validation function for validating that the field's value does not end with the text specified within the param. +func endsNotWith(fl FieldLevel) bool { + return !endsWith(fl) +} + +// FieldContains is the validation function for validating if the current field's value contains the field specified by the param's value. +func fieldContains(fl FieldLevel) bool { + field := fl.Field() + + currentField, _, ok := fl.GetStructFieldOK() + + if !ok { + return false + } + + return strings.Contains(field.String(), currentField.String()) +} + +// FieldExcludes is the validation function for validating if the current field's value excludes the field specified by the param's value. +func fieldExcludes(fl FieldLevel) bool { + field := fl.Field() + + currentField, _, ok := fl.GetStructFieldOK() + if !ok { + return true + } + + return !strings.Contains(field.String(), currentField.String()) +} + +// IsNeField is the validation function for validating if the current field's value is not equal to the field specified by the param's value. +func isNeField(fl FieldLevel) bool { + + field := fl.Field() + kind := field.Kind() + + currentField, currentKind, ok := fl.GetStructFieldOK() + + if !ok || currentKind != kind { + return true + } + + switch kind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return field.Int() != currentField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return field.Uint() != currentField.Uint() + + case reflect.Float32, reflect.Float64: + return field.Float() != currentField.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(field.Len()) != int64(currentField.Len()) + + case reflect.Struct: + + fieldType := field.Type() + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return true + } + + if fieldType == timeType { + + t := currentField.Interface().(time.Time) + fieldTime := field.Interface().(time.Time) + + return !fieldTime.Equal(t) + } + + } + + // default reflect.String: + return field.String() != currentField.String() +} + +// IsNe is the validation function for validating that the field's value does not equal the provided param value. +func isNe(fl FieldLevel) bool { + return !isEq(fl) +} + +// IsLteCrossStructField is the validation function for validating if the current field's value is less than or equal to the field, within a separate struct, specified by the param's value. +func isLteCrossStructField(fl FieldLevel) bool { + + field := fl.Field() + kind := field.Kind() + + topField, topKind, ok := fl.GetStructFieldOK() + if !ok || topKind != kind { + return false + } + + switch kind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return field.Int() <= topField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return field.Uint() <= topField.Uint() + + case reflect.Float32, reflect.Float64: + return field.Float() <= topField.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(field.Len()) <= int64(topField.Len()) + + case reflect.Struct: + + fieldType := field.Type() + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } + + if fieldType == timeType { + + fieldTime := field.Interface().(time.Time) + topTime := topField.Interface().(time.Time) + + return fieldTime.Before(topTime) || fieldTime.Equal(topTime) + } + } + + // default reflect.String: + return field.String() <= topField.String() +} + +// IsLtCrossStructField is the validation function for validating if the current field's value is less than the field, within a separate struct, specified by the param's value. +// NOTE: This is exposed for use within your own custom functions and not intended to be called directly. +func isLtCrossStructField(fl FieldLevel) bool { + + field := fl.Field() + kind := field.Kind() + + topField, topKind, ok := fl.GetStructFieldOK() + if !ok || topKind != kind { + return false + } + + switch kind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return field.Int() < topField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return field.Uint() < topField.Uint() + + case reflect.Float32, reflect.Float64: + return field.Float() < topField.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(field.Len()) < int64(topField.Len()) + + case reflect.Struct: + + fieldType := field.Type() + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } + + if fieldType == timeType { + + fieldTime := field.Interface().(time.Time) + topTime := topField.Interface().(time.Time) + + return fieldTime.Before(topTime) + } + } + + // default reflect.String: + return field.String() < topField.String() +} + +// IsGteCrossStructField is the validation function for validating if the current field's value is greater than or equal to the field, within a separate struct, specified by the param's value. +func isGteCrossStructField(fl FieldLevel) bool { + + field := fl.Field() + kind := field.Kind() + + topField, topKind, ok := fl.GetStructFieldOK() + if !ok || topKind != kind { + return false + } + + switch kind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return field.Int() >= topField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return field.Uint() >= topField.Uint() + + case reflect.Float32, reflect.Float64: + return field.Float() >= topField.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(field.Len()) >= int64(topField.Len()) + + case reflect.Struct: + + fieldType := field.Type() + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } + + if fieldType == timeType { + + fieldTime := field.Interface().(time.Time) + topTime := topField.Interface().(time.Time) + + return fieldTime.After(topTime) || fieldTime.Equal(topTime) + } + } + + // default reflect.String: + return field.String() >= topField.String() +} + +// IsGtCrossStructField is the validation function for validating if the current field's value is greater than the field, within a separate struct, specified by the param's value. +func isGtCrossStructField(fl FieldLevel) bool { + + field := fl.Field() + kind := field.Kind() + + topField, topKind, ok := fl.GetStructFieldOK() + if !ok || topKind != kind { + return false + } + + switch kind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return field.Int() > topField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return field.Uint() > topField.Uint() + + case reflect.Float32, reflect.Float64: + return field.Float() > topField.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(field.Len()) > int64(topField.Len()) + + case reflect.Struct: + + fieldType := field.Type() + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } + + if fieldType == timeType { + + fieldTime := field.Interface().(time.Time) + topTime := topField.Interface().(time.Time) + + return fieldTime.After(topTime) + } + } + + // default reflect.String: + return field.String() > topField.String() +} + +// IsNeCrossStructField is the validation function for validating that the current field's value is not equal to the field, within a separate struct, specified by the param's value. +func isNeCrossStructField(fl FieldLevel) bool { + + field := fl.Field() + kind := field.Kind() + + topField, currentKind, ok := fl.GetStructFieldOK() + if !ok || currentKind != kind { + return true + } + + switch kind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return topField.Int() != field.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return topField.Uint() != field.Uint() + + case reflect.Float32, reflect.Float64: + return topField.Float() != field.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(topField.Len()) != int64(field.Len()) + + case reflect.Struct: + + fieldType := field.Type() + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return true + } + + if fieldType == timeType { + + t := field.Interface().(time.Time) + fieldTime := topField.Interface().(time.Time) + + return !fieldTime.Equal(t) + } + } + + // default reflect.String: + return topField.String() != field.String() +} + +// IsEqCrossStructField is the validation function for validating that the current field's value is equal to the field, within a separate struct, specified by the param's value. +func isEqCrossStructField(fl FieldLevel) bool { + + field := fl.Field() + kind := field.Kind() + + topField, topKind, ok := fl.GetStructFieldOK() + if !ok || topKind != kind { + return false + } + + switch kind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return topField.Int() == field.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return topField.Uint() == field.Uint() + + case reflect.Float32, reflect.Float64: + return topField.Float() == field.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(topField.Len()) == int64(field.Len()) + + case reflect.Struct: + + fieldType := field.Type() + + // Not Same underlying type i.e. struct and time + if fieldType != topField.Type() { + return false + } + + if fieldType == timeType { + + t := field.Interface().(time.Time) + fieldTime := topField.Interface().(time.Time) + + return fieldTime.Equal(t) + } + } + + // default reflect.String: + return topField.String() == field.String() +} + +// IsEqField is the validation function for validating if the current field's value is equal to the field specified by the param's value. +func isEqField(fl FieldLevel) bool { + + field := fl.Field() + kind := field.Kind() + + currentField, currentKind, ok := fl.GetStructFieldOK() + if !ok || currentKind != kind { + return false + } + + switch kind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return field.Int() == currentField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return field.Uint() == currentField.Uint() + + case reflect.Float32, reflect.Float64: + return field.Float() == currentField.Float() + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(field.Len()) == int64(currentField.Len()) + + case reflect.Struct: + + fieldType := field.Type() + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } + + if fieldType == timeType { + + t := currentField.Interface().(time.Time) + fieldTime := field.Interface().(time.Time) + + return fieldTime.Equal(t) + } + + } + + // default reflect.String: + return field.String() == currentField.String() +} + +// IsEq is the validation function for validating if the current field's value is equal to the param's value. +func isEq(fl FieldLevel) bool { + + field := fl.Field() + param := fl.Param() + + switch field.Kind() { + + case reflect.String: + return field.String() == param + + case reflect.Slice, reflect.Map, reflect.Array: + p := asInt(param) + + return int64(field.Len()) == p + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + p := asIntFromType(field.Type(), param) + + return field.Int() == p + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + p := asUint(param) + + return field.Uint() == p + + case reflect.Float32, reflect.Float64: + p := asFloat(param) + + return field.Float() == p + + case reflect.Bool: + p := asBool(param) + + return field.Bool() == p + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// IsBase64 is the validation function for validating if the current field's value is a valid base 64. +func isBase64(fl FieldLevel) bool { + return base64Regex.MatchString(fl.Field().String()) +} + +// IsBase64URL is the validation function for validating if the current field's value is a valid base64 URL safe string. +func isBase64URL(fl FieldLevel) bool { + return base64URLRegex.MatchString(fl.Field().String()) +} + +// IsURI is the validation function for validating if the current field's value is a valid URI. +func isURI(fl FieldLevel) bool { + + field := fl.Field() + + switch field.Kind() { + + case reflect.String: + + s := field.String() + + // checks needed as of Go 1.6 because of change https://github.com/golang/go/commit/617c93ce740c3c3cc28cdd1a0d712be183d0b328#diff-6c2d018290e298803c0c9419d8739885L195 + // emulate browser and strip the '#' suffix prior to validation. see issue-#237 + if i := strings.Index(s, "#"); i > -1 { + s = s[:i] + } + + if len(s) == 0 { + return false + } + + _, err := url.ParseRequestURI(s) + + return err == nil + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// IsURL is the validation function for validating if the current field's value is a valid URL. +func isURL(fl FieldLevel) bool { + + field := fl.Field() + + switch field.Kind() { + + case reflect.String: + + var i int + s := field.String() + + // checks needed as of Go 1.6 because of change https://github.com/golang/go/commit/617c93ce740c3c3cc28cdd1a0d712be183d0b328#diff-6c2d018290e298803c0c9419d8739885L195 + // emulate browser and strip the '#' suffix prior to validation. see issue-#237 + if i = strings.Index(s, "#"); i > -1 { + s = s[:i] + } + + if len(s) == 0 { + return false + } + + url, err := url.ParseRequestURI(s) + + if err != nil || url.Scheme == "" { + return false + } + + return true + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// isUrnRFC2141 is the validation function for validating if the current field's value is a valid URN as per RFC 2141. +func isUrnRFC2141(fl FieldLevel) bool { + field := fl.Field() + + switch field.Kind() { + + case reflect.String: + + str := field.String() + + _, match := urn.Parse([]byte(str)) + + return match + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// IsFile is the validation function for validating if the current field's value is a valid file path. +func isFile(fl FieldLevel) bool { + field := fl.Field() + + switch field.Kind() { + case reflect.String: + fileInfo, err := os.Stat(field.String()) + if err != nil { + return false + } + + return !fileInfo.IsDir() + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// IsE164 is the validation function for validating if the current field's value is a valid e.164 formatted phone number. +func isE164(fl FieldLevel) bool { + return e164Regex.MatchString(fl.Field().String()) +} + +// IsEmail is the validation function for validating if the current field's value is a valid email address. +func isEmail(fl FieldLevel) bool { + return emailRegex.MatchString(fl.Field().String()) +} + +// IsHSLA is the validation function for validating if the current field's value is a valid HSLA color. +func isHSLA(fl FieldLevel) bool { + return hslaRegex.MatchString(fl.Field().String()) +} + +// IsHSL is the validation function for validating if the current field's value is a valid HSL color. +func isHSL(fl FieldLevel) bool { + return hslRegex.MatchString(fl.Field().String()) +} + +// IsRGBA is the validation function for validating if the current field's value is a valid RGBA color. +func isRGBA(fl FieldLevel) bool { + return rgbaRegex.MatchString(fl.Field().String()) +} + +// IsRGB is the validation function for validating if the current field's value is a valid RGB color. +func isRGB(fl FieldLevel) bool { + return rgbRegex.MatchString(fl.Field().String()) +} + +// IsHEXColor is the validation function for validating if the current field's value is a valid HEX color. +func isHEXColor(fl FieldLevel) bool { + return hexcolorRegex.MatchString(fl.Field().String()) +} + +// IsHexadecimal is the validation function for validating if the current field's value is a valid hexadecimal. +func isHexadecimal(fl FieldLevel) bool { + return hexadecimalRegex.MatchString(fl.Field().String()) +} + +// IsNumber is the validation function for validating if the current field's value is a valid number. +func isNumber(fl FieldLevel) bool { + switch fl.Field().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64: + return true + default: + return numberRegex.MatchString(fl.Field().String()) + } +} + +// IsNumeric is the validation function for validating if the current field's value is a valid numeric value. +func isNumeric(fl FieldLevel) bool { + switch fl.Field().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64: + return true + default: + return numericRegex.MatchString(fl.Field().String()) + } +} + +// IsAlphanum is the validation function for validating if the current field's value is a valid alphanumeric value. +func isAlphanum(fl FieldLevel) bool { + return alphaNumericRegex.MatchString(fl.Field().String()) +} + +// IsAlpha is the validation function for validating if the current field's value is a valid alpha value. +func isAlpha(fl FieldLevel) bool { + return alphaRegex.MatchString(fl.Field().String()) +} + +// IsAlphanumUnicode is the validation function for validating if the current field's value is a valid alphanumeric unicode value. +func isAlphanumUnicode(fl FieldLevel) bool { + return alphaUnicodeNumericRegex.MatchString(fl.Field().String()) +} + +// IsAlphaUnicode is the validation function for validating if the current field's value is a valid alpha unicode value. +func isAlphaUnicode(fl FieldLevel) bool { + return alphaUnicodeRegex.MatchString(fl.Field().String()) +} + +// isDefault is the opposite of required aka hasValue +func isDefault(fl FieldLevel) bool { + return !hasValue(fl) +} + +// HasValue is the validation function for validating if the current field's value is not the default static value. +func hasValue(fl FieldLevel) bool { + field := fl.Field() + switch field.Kind() { + case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: + return !field.IsNil() + default: + if fl.(*validate).fldIsPointer && field.Interface() != nil { + return true + } + return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface() + } +} + +// requireCheckField is a func for check field kind +func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue bool) bool { + field := fl.Field() + kind := field.Kind() + var nullable, found bool + if len(param) > 0 { + field, kind, nullable, found = fl.GetStructFieldOKAdvanced2(fl.Parent(), param) + if !found { + return defaultNotFoundValue + } + } + switch kind { + case reflect.Invalid: + return defaultNotFoundValue + case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: + return field.IsNil() + default: + if nullable && field.Interface() != nil { + return false + } + return field.IsValid() && field.Interface() == reflect.Zero(field.Type()).Interface() + } +} + +// requireCheckFieldValue is a func for check field value +func requireCheckFieldValue(fl FieldLevel, param string, value string, defaultNotFoundValue bool) bool { + field, kind, _, found := fl.GetStructFieldOKAdvanced2(fl.Parent(), param) + if !found { + return defaultNotFoundValue + } + + switch kind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return field.Int() == asInt(value) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return field.Uint() == asUint(value) + + case reflect.Float32, reflect.Float64: + return field.Float() == asFloat(value) + + case reflect.Slice, reflect.Map, reflect.Array: + return int64(field.Len()) == asInt(value) + } + + // default reflect.String: + return field.String() == value +} + +// requiredIf is the validation function +// The field under validation must be present and not empty only if all the other specified fields are equal to the value following with the specified field. +func requiredIf(fl FieldLevel) bool { + params := parseOneOfParam2(fl.Param()) + if len(params)%2 != 0 { + panic(fmt.Sprintf("Bad param number for required_if %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) +} + +// 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 { + params := parseOneOfParam2(fl.Param()) + if len(params)%2 != 0 { + panic(fmt.Sprintf("Bad param number for required_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 { + params := parseOneOfParam2(fl.Param()) + for _, param := range params { + if !requireCheckFieldKind(fl, param, true) { + return !hasValue(fl) + } + } + return true +} + +// RequiredWith is the validation function +// The field under validation must be present and not empty only if any of the other specified fields are present. +func requiredWith(fl FieldLevel) bool { + params := parseOneOfParam2(fl.Param()) + for _, param := range params { + if !requireCheckFieldKind(fl, param, true) { + return hasValue(fl) + } + } + return true +} + +// ExcludedWithAll is the validation function +// The field under validation must not be present or is empty if all of the other specified fields are present. +func excludedWithAll(fl FieldLevel) bool { + params := parseOneOfParam2(fl.Param()) + for _, param := range params { + if requireCheckFieldKind(fl, param, true) { + return true + } + } + return !hasValue(fl) +} + +// RequiredWithAll is the validation function +// The field under validation must be present and not empty only if all of the other specified fields are present. +func requiredWithAll(fl FieldLevel) bool { + params := parseOneOfParam2(fl.Param()) + for _, param := range params { + if requireCheckFieldKind(fl, param, true) { + return true + } + } + return hasValue(fl) +} + +// ExcludedWithout is the validation function +// The field under validation must not be present or is empty when any of the other specified fields are not present. +func excludedWithout(fl FieldLevel) bool { + if requireCheckFieldKind(fl, strings.TrimSpace(fl.Param()), true) { + return !hasValue(fl) + } + return true +} + +// RequiredWithout is the validation function +// The field under validation must be present and not empty only when any of the other specified fields are not present. +func requiredWithout(fl FieldLevel) bool { + if requireCheckFieldKind(fl, strings.TrimSpace(fl.Param()), true) { + return hasValue(fl) + } + return true +} + +// RequiredWithoutAll is the validation function +// The field under validation must not be present or is empty when all of the other specified fields are not present. +func excludedWithoutAll(fl FieldLevel) bool { + params := parseOneOfParam2(fl.Param()) + for _, param := range params { + if !requireCheckFieldKind(fl, param, true) { + return true + } + } + return !hasValue(fl) +} + +// RequiredWithoutAll is the validation function +// The field under validation must be present and not empty only when all of the other specified fields are not present. +func requiredWithoutAll(fl FieldLevel) bool { + params := parseOneOfParam2(fl.Param()) + for _, param := range params { + if !requireCheckFieldKind(fl, param, true) { + return true + } + } + return hasValue(fl) +} + +// IsGteField is the validation function for validating if the current field's value is greater than or equal to the field specified by the param's value. +func isGteField(fl FieldLevel) bool { + + field := fl.Field() + kind := field.Kind() + + currentField, currentKind, ok := fl.GetStructFieldOK() + if !ok || currentKind != kind { + return false + } + + switch kind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + + return field.Int() >= currentField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + + return field.Uint() >= currentField.Uint() + + case reflect.Float32, reflect.Float64: + + return field.Float() >= currentField.Float() + + case reflect.Struct: + + fieldType := field.Type() + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } + + if fieldType == timeType { + + t := currentField.Interface().(time.Time) + fieldTime := field.Interface().(time.Time) + + return fieldTime.After(t) || fieldTime.Equal(t) + } + } + + // default reflect.String + return len(field.String()) >= len(currentField.String()) +} + +// IsGtField is the validation function for validating if the current field's value is greater than the field specified by the param's value. +func isGtField(fl FieldLevel) bool { + + field := fl.Field() + kind := field.Kind() + + currentField, currentKind, ok := fl.GetStructFieldOK() + if !ok || currentKind != kind { // 首先会 比较找到的 field 和现在的类型作比较 + return false + } + + switch kind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + + return field.Int() > currentField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + + return field.Uint() > currentField.Uint() + + case reflect.Float32, reflect.Float64: + + return field.Float() > currentField.Float() + + case reflect.Struct: + + fieldType := field.Type() + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } + + if fieldType == timeType { + + t := currentField.Interface().(time.Time) + fieldTime := field.Interface().(time.Time) + + return fieldTime.After(t) + } + } + + // default reflect.String + return len(field.String()) > len(currentField.String()) +} + +// IsGte is the validation function for validating if the current field's value is greater than or equal to the param's value. +func isGte(fl FieldLevel) bool { + + field := fl.Field() + param := fl.Param() + + switch field.Kind() { + + case reflect.String: + p := asInt(param) + + return int64(utf8.RuneCountInString(field.String())) >= p + + case reflect.Slice, reflect.Map, reflect.Array: + p := asInt(param) + + return int64(field.Len()) >= p + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + p := asIntFromType(field.Type(), param) + + return field.Int() >= p + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + p := asUint(param) + + return field.Uint() >= p + + case reflect.Float32, reflect.Float64: + p := asFloat(param) + + return field.Float() >= p + + case reflect.Struct: + + if field.Type() == timeType { + + now := time.Now().UTC() + t := field.Interface().(time.Time) + + return t.After(now) || t.Equal(now) + } + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// IsGt is the validation function for validating if the current field's value is greater than the param's value. +func isGt(fl FieldLevel) bool { + + field := fl.Field() + param := fl.Param() + + switch field.Kind() { + + case reflect.String: + p := asInt(param) + + return int64(utf8.RuneCountInString(field.String())) > p + + case reflect.Slice, reflect.Map, reflect.Array: + p := asInt(param) + + return int64(field.Len()) > p + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + p := asIntFromType(field.Type(), param) + + return field.Int() > p + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + p := asUint(param) + + return field.Uint() > p + + case reflect.Float32, reflect.Float64: + p := asFloat(param) + + return field.Float() > p + case reflect.Struct: + + if field.Type() == timeType { + + return field.Interface().(time.Time).After(time.Now().UTC()) + } + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// HasLengthOf is the validation function for validating if the current field's value is equal to the param's value. +func hasLengthOf(fl FieldLevel) bool { + + field := fl.Field() + param := fl.Param() + + switch field.Kind() { + + case reflect.String: + p := asInt(param) + + return int64(utf8.RuneCountInString(field.String())) == p + + case reflect.Slice, reflect.Map, reflect.Array: + p := asInt(param) + + return int64(field.Len()) == p + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + p := asIntFromType(field.Type(), param) + + return field.Int() == p + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + p := asUint(param) + + return field.Uint() == p + + case reflect.Float32, reflect.Float64: + p := asFloat(param) + + return field.Float() == p + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// HasMinOf is the validation function for validating if the current field's value is greater than or equal to the param's value. +func hasMinOf(fl FieldLevel) bool { + return isGte(fl) +} + +// IsLteField is the validation function for validating if the current field's value is less than or equal to the field specified by the param's value. +func isLteField(fl FieldLevel) bool { + + field := fl.Field() + kind := field.Kind() + + currentField, currentKind, ok := fl.GetStructFieldOK() + if !ok || currentKind != kind { + return false + } + + switch kind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + + return field.Int() <= currentField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + + return field.Uint() <= currentField.Uint() + + case reflect.Float32, reflect.Float64: + + return field.Float() <= currentField.Float() + + case reflect.Struct: + + fieldType := field.Type() + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } + + if fieldType == timeType { + + t := currentField.Interface().(time.Time) + fieldTime := field.Interface().(time.Time) + + return fieldTime.Before(t) || fieldTime.Equal(t) + } + } + + // default reflect.String + return len(field.String()) <= len(currentField.String()) +} + +// IsLtField is the validation function for validating if the current field's value is less than the field specified by the param's value. +func isLtField(fl FieldLevel) bool { + + field := fl.Field() + kind := field.Kind() + + currentField, currentKind, ok := fl.GetStructFieldOK() + if !ok || currentKind != kind { + return false + } + + switch kind { + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + + return field.Int() < currentField.Int() + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + + return field.Uint() < currentField.Uint() + + case reflect.Float32, reflect.Float64: + + return field.Float() < currentField.Float() + + case reflect.Struct: + + fieldType := field.Type() + + // Not Same underlying type i.e. struct and time + if fieldType != currentField.Type() { + return false + } + + if fieldType == timeType { + + t := currentField.Interface().(time.Time) + fieldTime := field.Interface().(time.Time) + + return fieldTime.Before(t) + } + } + + // default reflect.String + return len(field.String()) < len(currentField.String()) +} + +// IsLte is the validation function for validating if the current field's value is less than or equal to the param's value. +func isLte(fl FieldLevel) bool { + + field := fl.Field() + param := fl.Param() + + switch field.Kind() { + + case reflect.String: + p := asInt(param) + + return int64(utf8.RuneCountInString(field.String())) <= p + + case reflect.Slice, reflect.Map, reflect.Array: + p := asInt(param) + + return int64(field.Len()) <= p + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + p := asIntFromType(field.Type(), param) + + return field.Int() <= p + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + p := asUint(param) + + return field.Uint() <= p + + case reflect.Float32, reflect.Float64: + p := asFloat(param) + + return field.Float() <= p + + case reflect.Struct: + + if field.Type() == timeType { + + now := time.Now().UTC() + t := field.Interface().(time.Time) + + return t.Before(now) || t.Equal(now) + } + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// IsLt is the validation function for validating if the current field's value is less than the param's value. +func isLt(fl FieldLevel) bool { + + field := fl.Field() + param := fl.Param() + + switch field.Kind() { + + case reflect.String: + p := asInt(param) + + return int64(utf8.RuneCountInString(field.String())) < p + + case reflect.Slice, reflect.Map, reflect.Array: + p := asInt(param) + + return int64(field.Len()) < p + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + p := asIntFromType(field.Type(), param) + + return field.Int() < p + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + p := asUint(param) + + return field.Uint() < p + + case reflect.Float32, reflect.Float64: + p := asFloat(param) + + return field.Float() < p + + case reflect.Struct: + + if field.Type() == timeType { + + return field.Interface().(time.Time).Before(time.Now().UTC()) + } + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// HasMaxOf is the validation function for validating if the current field's value is less than or equal to the param's value. +func hasMaxOf(fl FieldLevel) bool { + return isLte(fl) +} + +// IsTCP4AddrResolvable is the validation function for validating if the field's value is a resolvable tcp4 address. +func isTCP4AddrResolvable(fl FieldLevel) bool { + + if !isIP4Addr(fl) { + return false + } + + _, err := net.ResolveTCPAddr("tcp4", fl.Field().String()) + return err == nil +} + +// IsTCP6AddrResolvable is the validation function for validating if the field's value is a resolvable tcp6 address. +func isTCP6AddrResolvable(fl FieldLevel) bool { + + if !isIP6Addr(fl) { + return false + } + + _, err := net.ResolveTCPAddr("tcp6", fl.Field().String()) + + return err == nil +} + +// IsTCPAddrResolvable is the validation function for validating if the field's value is a resolvable tcp address. +func isTCPAddrResolvable(fl FieldLevel) bool { + + if !isIP4Addr(fl) && !isIP6Addr(fl) { + return false + } + + _, err := net.ResolveTCPAddr("tcp", fl.Field().String()) + + return err == nil +} + +// IsUDP4AddrResolvable is the validation function for validating if the field's value is a resolvable udp4 address. +func isUDP4AddrResolvable(fl FieldLevel) bool { + + if !isIP4Addr(fl) { + return false + } + + _, err := net.ResolveUDPAddr("udp4", fl.Field().String()) + + return err == nil +} + +// IsUDP6AddrResolvable is the validation function for validating if the field's value is a resolvable udp6 address. +func isUDP6AddrResolvable(fl FieldLevel) bool { + + if !isIP6Addr(fl) { + return false + } + + _, err := net.ResolveUDPAddr("udp6", fl.Field().String()) + + return err == nil +} + +// IsUDPAddrResolvable is the validation function for validating if the field's value is a resolvable udp address. +func isUDPAddrResolvable(fl FieldLevel) bool { + + if !isIP4Addr(fl) && !isIP6Addr(fl) { + return false + } + + _, err := net.ResolveUDPAddr("udp", fl.Field().String()) + + return err == nil +} + +// IsIP4AddrResolvable is the validation function for validating if the field's value is a resolvable ip4 address. +func isIP4AddrResolvable(fl FieldLevel) bool { + + if !isIPv4(fl) { + return false + } + + _, err := net.ResolveIPAddr("ip4", fl.Field().String()) + + return err == nil +} + +// IsIP6AddrResolvable is the validation function for validating if the field's value is a resolvable ip6 address. +func isIP6AddrResolvable(fl FieldLevel) bool { + + if !isIPv6(fl) { + return false + } + + _, err := net.ResolveIPAddr("ip6", fl.Field().String()) + + return err == nil +} + +// IsIPAddrResolvable is the validation function for validating if the field's value is a resolvable ip address. +func isIPAddrResolvable(fl FieldLevel) bool { + + if !isIP(fl) { + return false + } + + _, err := net.ResolveIPAddr("ip", fl.Field().String()) + + return err == nil +} + +// IsUnixAddrResolvable is the validation function for validating if the field's value is a resolvable unix address. +func isUnixAddrResolvable(fl FieldLevel) bool { + + _, err := net.ResolveUnixAddr("unix", fl.Field().String()) + + return err == nil +} + +func isIP4Addr(fl FieldLevel) bool { + + val := fl.Field().String() + + if idx := strings.LastIndex(val, ":"); idx != -1 { + val = val[0:idx] + } + + ip := net.ParseIP(val) + + return ip != nil && ip.To4() != nil +} + +func isIP6Addr(fl FieldLevel) bool { + + val := fl.Field().String() + + if idx := strings.LastIndex(val, ":"); idx != -1 { + if idx != 0 && val[idx-1:idx] == "]" { + val = val[1 : idx-1] + } + } + + ip := net.ParseIP(val) + + return ip != nil && ip.To4() == nil +} + +func isHostnameRFC952(fl FieldLevel) bool { + return hostnameRegexRFC952.MatchString(fl.Field().String()) +} + +func isHostnameRFC1123(fl FieldLevel) bool { + return hostnameRegexRFC1123.MatchString(fl.Field().String()) +} + +func isFQDN(fl FieldLevel) bool { + val := fl.Field().String() + + if val == "" { + return false + } + + return fqdnRegexRFC1123.MatchString(val) +} + +// IsDir is the validation function for validating if the current field's value is a valid directory. +func isDir(fl FieldLevel) bool { + field := fl.Field() + + if field.Kind() == reflect.String { + fileInfo, err := os.Stat(field.String()) + if err != nil { + return false + } + + return fileInfo.IsDir() + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// isJSON is the validation function for validating if the current field's value is a valid json string. +func isJSON(fl FieldLevel) bool { + field := fl.Field() + + if field.Kind() == reflect.String { + val := field.String() + return json.Valid([]byte(val)) + } + + 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 +} + +// 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 { + if field.String() == "" { + return false + } + 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 { + if field.String() == "" { + return false + } + return field.String() == strings.ToUpper(field.String()) + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// isDatetime is the validation function for validating if the current field's value is a valid datetime string. +func isDatetime(fl FieldLevel) bool { + field := fl.Field() + param := fl.Param() + + if field.Kind() == reflect.String { + _, err := time.Parse(param, field.String()) + + return err == nil + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// isTimeZone is the validation function for validating if the current field's value is a valid time zone string. +func isTimeZone(fl FieldLevel) bool { + field := fl.Field() + + if field.Kind() == reflect.String { + // empty value is converted to UTC by time.LoadLocation but disallow it as it is not a valid time zone name + if field.String() == "" { + return false + } + + // Local value is converted to the current system time zone by time.LoadLocation but disallow it as it is not a valid time zone name + if strings.ToLower(field.String()) == "local" { + return false + } + + _, err := time.LoadLocation(field.String()) + return err == nil + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + +// isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-2 country code. +func isIso3166Alpha2(fl FieldLevel) bool { + val := fl.Field().String() + return iso3166_1_alpha2[val] +} + +// isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-3 country code. +func isIso3166Alpha3(fl FieldLevel) bool { + val := fl.Field().String() + return iso3166_1_alpha3[val] +} + +// isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-numeric country code. +func isIso3166AlphaNumeric(fl FieldLevel) bool { + field := fl.Field() + + var code int + switch field.Kind() { + 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: + code = int(field.Uint() % 1000) + default: + panic(fmt.Sprintf("Bad field type %T", field.Interface())) + } + return iso3166_1_alpha_numeric[code] +} diff --git a/go-playground/validator/v10/benchmarks_test.go b/go-playground/validator/v10/benchmarks_test.go new file mode 100644 index 0000000..ee70f95 --- /dev/null +++ b/go-playground/validator/v10/benchmarks_test.go @@ -0,0 +1,1099 @@ +package validator + +import ( + "bytes" + sql "database/sql/driver" + "testing" + "time" +) + +func BenchmarkFieldSuccess(b *testing.B) { + validate := New() + s := "1" + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Var(&s, "len=1") + } +} + +func BenchmarkFieldSuccessParallel(b *testing.B) { + validate := New() + s := "1" + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Var(&s, "len=1") + } + }) +} + +func BenchmarkFieldFailure(b *testing.B) { + validate := New() + s := "12" + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Var(&s, "len=1") + } +} + +func BenchmarkFieldFailureParallel(b *testing.B) { + validate := New() + s := "12" + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Var(&s, "len=1") + } + }) +} + +func BenchmarkFieldArrayDiveSuccess(b *testing.B) { + validate := New() + m := []string{"val1", "val2", "val3"} + + b.ResetTimer() + + for n := 0; n < b.N; n++ { + _ = validate.Var(m, "required,dive,required") + } +} + +func BenchmarkFieldArrayDiveSuccessParallel(b *testing.B) { + validate := New() + m := []string{"val1", "val2", "val3"} + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Var(m, "required,dive,required") + } + }) +} + +func BenchmarkFieldArrayDiveFailure(b *testing.B) { + validate := New() + m := []string{"val1", "", "val3"} + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Var(m, "required,dive,required") + } +} + +func BenchmarkFieldArrayDiveFailureParallel(b *testing.B) { + validate := New() + m := []string{"val1", "", "val3"} + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Var(m, "required,dive,required") + } + }) +} + +func BenchmarkFieldMapDiveSuccess(b *testing.B) { + validate := New() + m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"} + + b.ResetTimer() + + for n := 0; n < b.N; n++ { + _ = validate.Var(m, "required,dive,required") + } +} + +func BenchmarkFieldMapDiveSuccessParallel(b *testing.B) { + validate := New() + m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"} + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Var(m, "required,dive,required") + } + }) +} + +func BenchmarkFieldMapDiveFailure(b *testing.B) { + validate := New() + m := map[string]string{"": "", "val3": "val3"} + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Var(m, "required,dive,required") + } +} + +func BenchmarkFieldMapDiveFailureParallel(b *testing.B) { + validate := New() + m := map[string]string{"": "", "val3": "val3"} + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Var(m, "required,dive,required") + } + }) +} + +func BenchmarkFieldMapDiveWithKeysSuccess(b *testing.B) { + validate := New() + m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"} + + b.ResetTimer() + + for n := 0; n < b.N; n++ { + _ = validate.Var(m, "required,dive,keys,required,endkeys,required") + } +} + +func BenchmarkFieldMapDiveWithKeysSuccessParallel(b *testing.B) { + validate := New() + m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"} + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Var(m, "required,dive,keys,required,endkeys,required") + } + }) +} + +func BenchmarkFieldMapDiveWithKeysFailure(b *testing.B) { + validate := New() + m := map[string]string{"": "", "val3": "val3"} + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Var(m, "required,dive,keys,required,endkeys,required") + } +} + +func BenchmarkFieldMapDiveWithKeysFailureParallel(b *testing.B) { + validate := New() + m := map[string]string{"": "", "val3": "val3"} + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Var(m, "required,dive,keys,required,endkeys,required") + } + }) +} + +func BenchmarkFieldCustomTypeSuccess(b *testing.B) { + validate := New() + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) + val := valuer{ + Name: "1", + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Var(val, "len=1") + } +} + +func BenchmarkFieldCustomTypeSuccessParallel(b *testing.B) { + validate := New() + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) + val := valuer{ + Name: "1", + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Var(val, "len=1") + } + }) +} + +func BenchmarkFieldCustomTypeFailure(b *testing.B) { + validate := New() + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) + val := valuer{} + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Var(val, "len=1") + } +} + +func BenchmarkFieldCustomTypeFailureParallel(b *testing.B) { + validate := New() + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) + val := valuer{} + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Var(val, "len=1") + } + }) +} + +func BenchmarkFieldOrTagSuccess(b *testing.B) { + validate := New() + s := "rgba(0,0,0,1)" + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Var(s, "rgb|rgba") + } +} + +func BenchmarkFieldOrTagSuccessParallel(b *testing.B) { + validate := New() + s := "rgba(0,0,0,1)" + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Var(s, "rgb|rgba") + } + }) +} + +func BenchmarkFieldOrTagFailure(b *testing.B) { + validate := New() + s := "#000" + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Var(s, "rgb|rgba") + } +} + +func BenchmarkFieldOrTagFailureParallel(b *testing.B) { + validate := New() + s := "#000" + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Var(s, "rgb|rgba") + } + }) +} + +func BenchmarkStructLevelValidationSuccess(b *testing.B) { + validate := New() + validate.RegisterStructValidation(StructValidationTestStructSuccess, TestStruct{}) + + tst := TestStruct{ + String: "good value", + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Struct(tst) + } +} + +func BenchmarkStructLevelValidationSuccessParallel(b *testing.B) { + validate := New() + validate.RegisterStructValidation(StructValidationTestStructSuccess, TestStruct{}) + + tst := TestStruct{ + String: "good value", + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Struct(tst) + } + }) +} + +func BenchmarkStructLevelValidationFailure(b *testing.B) { + validate := New() + validate.RegisterStructValidation(StructValidationTestStruct, TestStruct{}) + + tst := TestStruct{ + String: "good value", + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Struct(tst) + } +} + +func BenchmarkStructLevelValidationFailureParallel(b *testing.B) { + validate := New() + validate.RegisterStructValidation(StructValidationTestStruct, TestStruct{}) + + tst := TestStruct{ + String: "good value", + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Struct(tst) + } + }) +} + +func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { + validate := New() + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) + + val := valuer{ + Name: "1", + } + + type Foo struct { + Valuer valuer `validate:"len=1"` + IntValue int `validate:"min=5,max=10"` + } + + validFoo := &Foo{Valuer: val, IntValue: 7} + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Struct(validFoo) + } +} + +func BenchmarkStructSimpleCustomTypeSuccessParallel(b *testing.B) { + validate := New() + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) + val := valuer{ + Name: "1", + } + + type Foo struct { + Valuer valuer `validate:"len=1"` + IntValue int `validate:"min=5,max=10"` + } + validFoo := &Foo{Valuer: val, IntValue: 7} + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Struct(validFoo) + } + }) +} + +func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) { + validate := New() + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) + + val := valuer{} + + type Foo struct { + Valuer valuer `validate:"len=1"` + IntValue int `validate:"min=5,max=10"` + } + validFoo := &Foo{Valuer: val, IntValue: 3} + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Struct(validFoo) + } +} + +func BenchmarkStructSimpleCustomTypeFailureParallel(b *testing.B) { + validate := New() + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) + + val := valuer{} + + type Foo struct { + Valuer valuer `validate:"len=1"` + IntValue int `validate:"min=5,max=10"` + } + validFoo := &Foo{Valuer: val, IntValue: 3} + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Struct(validate.Struct(validFoo)) + } + }) +} + +func BenchmarkStructFilteredSuccess(b *testing.B) { + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + byts := []byte("Name") + fn := func(ns []byte) bool { + return !bytes.HasSuffix(ns, byts) + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.StructFiltered(test, fn) + } +} + +func BenchmarkStructFilteredSuccessParallel(b *testing.B) { + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + byts := []byte("Name") + fn := func(ns []byte) bool { + return !bytes.HasSuffix(ns, byts) + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.StructFiltered(test, fn) + } + }) +} + +func BenchmarkStructFilteredFailure(b *testing.B) { + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + byts := []byte("NickName") + + fn := func(ns []byte) bool { + return !bytes.HasSuffix(ns, byts) + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.StructFiltered(test, fn) + } +} + +func BenchmarkStructFilteredFailureParallel(b *testing.B) { + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + byts := []byte("NickName") + fn := func(ns []byte) bool { + return !bytes.HasSuffix(ns, byts) + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.StructFiltered(test, fn) + } + }) +} + +func BenchmarkStructPartialSuccess(b *testing.B) { + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.StructPartial(test, "Name") + } +} + +func BenchmarkStructPartialSuccessParallel(b *testing.B) { + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.StructPartial(test, "Name") + } + }) +} + +func BenchmarkStructPartialFailure(b *testing.B) { + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.StructPartial(test, "NickName") + } +} + +func BenchmarkStructPartialFailureParallel(b *testing.B) { + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.StructPartial(test, "NickName") + } + }) +} + +func BenchmarkStructExceptSuccess(b *testing.B) { + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.StructExcept(test, "Nickname") + } +} + +func BenchmarkStructExceptSuccessParallel(b *testing.B) { + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.StructExcept(test, "NickName") + } + }) +} + +func BenchmarkStructExceptFailure(b *testing.B) { + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.StructExcept(test, "Name") + } +} + +func BenchmarkStructExceptFailureParallel(b *testing.B) { + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.StructExcept(test, "Name") + } + }) +} + +func BenchmarkStructSimpleCrossFieldSuccess(b *testing.B) { + validate := New() + + type Test struct { + Start time.Time + End time.Time `validate:"gtfield=Start"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * 5) + test := &Test{ + Start: now, + End: then, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Struct(test) + } +} + +func BenchmarkStructSimpleCrossFieldSuccessParallel(b *testing.B) { + validate := New() + + type Test struct { + Start time.Time + End time.Time `validate:"gtfield=Start"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * 5) + test := &Test{ + Start: now, + End: then, + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Struct(test) + } + }) +} + +func BenchmarkStructSimpleCrossFieldFailure(b *testing.B) { + validate := New() + + type Test struct { + Start time.Time + End time.Time `validate:"gtfield=Start"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * -5) + + test := &Test{ + Start: now, + End: then, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Struct(test) + } +} + +func BenchmarkStructSimpleCrossFieldFailureParallel(b *testing.B) { + validate := New() + + type Test struct { + Start time.Time + End time.Time `validate:"gtfield=Start"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * -5) + test := &Test{ + Start: now, + End: then, + } + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Struct(test) + } + }) +} + +func BenchmarkStructSimpleCrossStructCrossFieldSuccess(b *testing.B) { + validate := New() + + type Inner struct { + Start time.Time + } + + type Outer struct { + Inner *Inner + CreatedAt time.Time `validate:"eqcsfield=Inner.Start"` + } + + now := time.Now().UTC() + inner := &Inner{ + Start: now, + } + outer := &Outer{ + Inner: inner, + CreatedAt: now, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Struct(outer) + } +} + +func BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel(b *testing.B) { + validate := New() + + type Inner struct { + Start time.Time + } + + type Outer struct { + Inner *Inner + CreatedAt time.Time `validate:"eqcsfield=Inner.Start"` + } + + now := time.Now().UTC() + inner := &Inner{ + Start: now, + } + outer := &Outer{ + Inner: inner, + CreatedAt: now, + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Struct(outer) + } + }) +} + +func BenchmarkStructSimpleCrossStructCrossFieldFailure(b *testing.B) { + validate := New() + type Inner struct { + Start time.Time + } + + type Outer struct { + Inner *Inner + CreatedAt time.Time `validate:"eqcsfield=Inner.Start"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * 5) + + inner := &Inner{ + Start: then, + } + + outer := &Outer{ + Inner: inner, + CreatedAt: now, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Struct(outer) + } +} + +func BenchmarkStructSimpleCrossStructCrossFieldFailureParallel(b *testing.B) { + validate := New() + + type Inner struct { + Start time.Time + } + + type Outer struct { + Inner *Inner + CreatedAt time.Time `validate:"eqcsfield=Inner.Start"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * 5) + + inner := &Inner{ + Start: then, + } + + outer := &Outer{ + Inner: inner, + CreatedAt: now, + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Struct(outer) + } + }) +} + +func BenchmarkStructSimpleSuccess(b *testing.B) { + validate := New() + type Foo struct { + StringValue string `validate:"min=5,max=10"` + IntValue int `validate:"min=5,max=10"` + } + + validFoo := &Foo{StringValue: "Foobar", IntValue: 7} + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Struct(validFoo) + } +} + +func BenchmarkStructSimpleSuccessParallel(b *testing.B) { + validate := New() + type Foo struct { + StringValue string `validate:"min=5,max=10"` + IntValue int `validate:"min=5,max=10"` + } + validFoo := &Foo{StringValue: "Foobar", IntValue: 7} + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Struct(validFoo) + } + }) +} + +func BenchmarkStructSimpleFailure(b *testing.B) { + validate := New() + type Foo struct { + StringValue string `validate:"min=5,max=10"` + IntValue int `validate:"min=5,max=10"` + } + + invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Struct(invalidFoo) + } +} + +func BenchmarkStructSimpleFailureParallel(b *testing.B) { + validate := New() + type Foo struct { + StringValue string `validate:"min=5,max=10"` + IntValue int `validate:"min=5,max=10"` + } + + invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Struct(invalidFoo) + } + }) +} + +func BenchmarkStructComplexSuccess(b *testing.B) { + validate := New() + tSuccess := &TestString{ + Required: "Required", + Len: "length==10", + Min: "min=1", + Max: "1234567890", + MinMax: "12345", + Lt: "012345678", + Lte: "0123456789", + Gt: "01234567890", + Gte: "0123456789", + OmitEmpty: "", + Sub: &SubTest{ + Test: "1", + }, + SubIgnore: &SubTest{ + Test: "", + }, + Anonymous: struct { + A string `validate:"required"` + }{ + A: "1", + }, + Iface: &Impl{ + F: "123", + }, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Struct(tSuccess) + } +} + +func BenchmarkStructComplexSuccessParallel(b *testing.B) { + validate := New() + tSuccess := &TestString{ + Required: "Required", + Len: "length==10", + Min: "min=1", + Max: "1234567890", + MinMax: "12345", + Lt: "012345678", + Lte: "0123456789", + Gt: "01234567890", + Gte: "0123456789", + OmitEmpty: "", + Sub: &SubTest{ + Test: "1", + }, + SubIgnore: &SubTest{ + Test: "", + }, + Anonymous: struct { + A string `validate:"required"` + }{ + A: "1", + }, + Iface: &Impl{ + F: "123", + }, + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Struct(tSuccess) + } + }) +} + +func BenchmarkStructComplexFailure(b *testing.B) { + validate := New() + tFail := &TestString{ + Required: "", + Len: "", + Min: "", + Max: "12345678901", + MinMax: "", + Lt: "0123456789", + Lte: "01234567890", + Gt: "1", + Gte: "1", + OmitEmpty: "12345678901", + Sub: &SubTest{ + Test: "", + }, + Anonymous: struct { + A string `validate:"required"` + }{ + A: "", + }, + Iface: &Impl{ + F: "12", + }, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + _ = validate.Struct(tFail) + } +} + +func BenchmarkStructComplexFailureParallel(b *testing.B) { + validate := New() + tFail := &TestString{ + Required: "", + Len: "", + Min: "", + Max: "12345678901", + MinMax: "", + Lt: "0123456789", + Lte: "01234567890", + Gt: "1", + Gte: "1", + OmitEmpty: "12345678901", + Sub: &SubTest{ + Test: "", + }, + Anonymous: struct { + A string `validate:"required"` + }{ + A: "", + }, + Iface: &Impl{ + F: "12", + }, + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = validate.Struct(tFail) + } + }) +} + +type TestOneof struct { + Color string `validate:"oneof=red green"` +} + +func BenchmarkOneof(b *testing.B) { + w := &TestOneof{Color: "green"} + val := New() + for i := 0; i < b.N; i++ { + _ = val.Struct(w) + } +} + +func BenchmarkOneofParallel(b *testing.B) { + w := &TestOneof{Color: "green"} + val := New() + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _ = val.Struct(w) + } + }) +} diff --git a/go-playground/validator/v10/cache.go b/go-playground/validator/v10/cache.go new file mode 100644 index 0000000..df48ce1 --- /dev/null +++ b/go-playground/validator/v10/cache.go @@ -0,0 +1,322 @@ +package validator + +import ( + "fmt" + "reflect" + "strings" + "sync" + "sync/atomic" +) + +type tagType uint8 + +const ( + typeDefault tagType = iota + typeOmitEmpty + typeIsDefault + typeNoStructLevel + typeStructOnly + typeDive + typeOr + typeKeys + typeEndKeys +) + +const ( + invalidValidation = "Invalid validation tag on field '%s'" + undefinedValidation = "Undefined validation function '%s' on field '%s'" + keysTagNotDefined = "'" + endKeysTag + "' tag encountered without a corresponding '" + keysTag + "' tag" +) + +type structCache struct { + lock sync.Mutex + m atomic.Value // map[reflect.Type]*cStruct +} + +func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) { + c, found = sc.m.Load().(map[reflect.Type]*cStruct)[key] //有一个断言的封装 + return +} + +func (sc *structCache) Set(key reflect.Type, value *cStruct) { + m := sc.m.Load().(map[reflect.Type]*cStruct) + nm := make(map[reflect.Type]*cStruct, len(m)+1) + for k, v := range m { + nm[k] = v + } + nm[key] = value + sc.m.Store(nm) +} + +type tagCache struct { + lock sync.Mutex + m atomic.Value // map[string]*cTag +} + +func (tc *tagCache) Get(key string) (c *cTag, found bool) { + c, found = tc.m.Load().(map[string]*cTag)[key] + return +} + +func (tc *tagCache) Set(key string, value *cTag) { + m := tc.m.Load().(map[string]*cTag) + nm := make(map[string]*cTag, len(m)+1) + for k, v := range m { + nm[k] = v + } + nm[key] = value + tc.m.Store(nm) +} + +type cStruct struct { + name string + fields []*cField + fn StructLevelFuncCtx +} + +type cField struct { + idx int + name string // field.name + altName string // 如果没有自定义 tagNameFunc,就是field.name, 如果有,比如可以按 json_tag中的, 报错的时候就会按照这个 报错 + namesEqual bool // field.name 和 altName是否相同 + cTags *cTag //以链表的形式存在 +} + +type cTag struct { + tag string // (gte=10) 实际是 gte,不带参数 + aliasTag string // 应该是别名, 不过在没有别名的时候等同于 tag + actualAliasTag string // 实际名字,而不是在func *Validate.parseFieldTagsRecursive 解析后的 别名 + param string + keys *cTag // only populated when using tag's 'keys' and 'endkeys' for map key validation + next *cTag + fn FuncCtx + typeof tagType + hasTag bool + hasAlias bool + hasParam bool // true if parameter used eg. eq= where the equal sign has been set + isBlockEnd bool // indicates the current tag represents the last validation in the block , 表示当前field的最后一个,比如 `require,gte=10`,那么到gte就是true + runValidationWhenNil bool +} + +func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct { + v.structCache.lock.Lock() + defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise! + + typ := current.Type() + + // could have been multiple trying to access, but once first is done this ensures struct + // isn't parsed again. + cs, ok := v.structCache.Get(typ) + if ok { + return cs + } + + cs = &cStruct{name: sName, fields: make([]*cField, 0), fn: v.structLevelFuncs[typ]} + + numFields := current.NumField() + + var ctag *cTag + var fld reflect.StructField + var tag string + var customName string + + for i := 0; i < numFields; i++ { + + fld = typ.Field(i) + + if !fld.Anonymous && len(fld.PkgPath) > 0 { // 如果不是 嵌套field 且是 未导出字段, (大写的字段 pakPath为空) + continue + } + + tag = fld.Tag.Get(v.tagName) + + if tag == skipValidationTag { + continue + } + + customName = fld.Name + + if v.hasTagNameFunc { // 自定义的获取 名字的func + name := v.tagNameFunc(fld) + if len(name) > 0 { + customName = name + } + } + + // NOTE: cannot use shared tag cache, because tags may be equal, but things like alias may be different + // and so only struct level caching can be used instead of combined with Field tag caching + + if len(tag) > 0 { + ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, "", false) //返回链头 + } else { + // even if field doesn't have validations need cTag for traversing to potential inner/nested + // elements of the field. + ctag = new(cTag) + } + + cs.fields = append(cs.fields, &cField{ + idx: i, + name: fld.Name, + altName: customName, + cTags: ctag, + namesEqual: fld.Name == customName, + }) + } + v.structCache.Set(typ, cs) + return cs +} + +func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) { + var t string + noAlias := len(alias) == 0 + tags := strings.Split(tag, tagSeparator) + + for i := 0; i < len(tags); i++ { + t = tags[i] + if noAlias { + alias = t + } + + // check map for alias and process new tags, otherwise process as usual + if tagsVal, found := v.aliases[t]; found { + if i == 0 { + firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true) + } else { + next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true) + current.next, current = next, curr + + } + continue + } + + var prevTag tagType + + if i == 0 { + current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true, typeof: typeDefault} + firstCtag = current + } else { + prevTag = current.typeof // 此刻之前current 还是 上一个的状态 + current.next = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true} + current = current.next + } + + switch t { + case diveTag: + current.typeof = typeDive + continue + + case keysTag: + current.typeof = typeKeys + + if i == 0 || prevTag != typeDive { + panic(fmt.Sprintf("'%s' tag must be immediately preceded by the '%s' tag", keysTag, diveTag)) + } + + current.typeof = typeKeys + + // need to pass along only keys tag + // need to increment i to skip over the keys tags + b := make([]byte, 0, 64) + + i++ + + for ; i < len(tags); i++ { + + b = append(b, tags[i]...) + b = append(b, ',') + + if tags[i] == endKeysTag { + break + } + } + + current.keys, _ = v.parseFieldTagsRecursive(string(b[:len(b)-1]), fieldName, "", false) + continue + + case endKeysTag: + current.typeof = typeEndKeys + + // if there are more in tags then there was no keysTag defined + // and an error should be thrown + if i != len(tags)-1 { + panic(keysTagNotDefined) + } + return + + case omitempty: + current.typeof = typeOmitEmpty + continue + + case structOnlyTag: + current.typeof = typeStructOnly + continue + + case noStructLevelTag: + current.typeof = typeNoStructLevel + continue + + default: + if t == isdefault { + current.typeof = typeIsDefault + } + // if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C" + orVals := strings.Split(t, orSeparator) + + for j := 0; j < len(orVals); j++ { + vals := strings.SplitN(orVals[j], tagKeySeparator, 2) // binding:"gte=10" 的 = + if noAlias { + alias = vals[0] //如果没有别名就 gte , 前面的是 gte=10 + current.aliasTag = alias + } else { + current.actualAliasTag = t // 原始名字 gte=10 + } + + if j > 0 { //这里的current.next 不应该是 .pre 前一个么,不过前一个也不对啊,后面的赋值还是给 current赋值啊 + current.next = &cTag{aliasTag: alias, actualAliasTag: current.actualAliasTag, hasAlias: hasAlias, hasTag: true} + current = current.next + } + current.hasParam = len(vals) > 1 + + current.tag = vals[0] + if len(current.tag) == 0 { + panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName))) + } + + if wrapper, ok := v.validations[current.tag]; ok { + current.fn = wrapper.fn + current.runValidationWhenNil = wrapper.runValidatinOnNil + } else { + panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, current.tag, fieldName))) + } + + if len(orVals) > 1 { + current.typeof = typeOr + } + + if len(vals) > 1 { + current.param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1) + } + } + current.isBlockEnd = true + } + } + return +} + +func (v *Validate) fetchCacheTag(tag string) *cTag { + // find cached tag + ctag, found := v.tagCache.Get(tag) + if !found { + v.tagCache.lock.Lock() + defer v.tagCache.lock.Unlock() + + // could have been multiple trying to access, but once first is done this ensures tag + // isn't parsed again. + ctag, found = v.tagCache.Get(tag) + if !found { + ctag, _ = v.parseFieldTagsRecursive(tag, "", "", false) + v.tagCache.Set(tag, ctag) + } + } + return ctag +} diff --git a/go-playground/validator/v10/country_codes.go b/go-playground/validator/v10/country_codes.go new file mode 100644 index 0000000..ef81ead --- /dev/null +++ b/go-playground/validator/v10/country_codes.go @@ -0,0 +1,162 @@ +package validator + +var iso3166_1_alpha2 = map[string]bool{ + // see: https://www.iso.org/iso-3166-country-codes.html + "AF": true, "AX": true, "AL": true, "DZ": true, "AS": true, + "AD": true, "AO": true, "AI": true, "AQ": true, "AG": true, + "AR": true, "AM": true, "AW": true, "AU": true, "AT": true, + "AZ": true, "BS": true, "BH": true, "BD": true, "BB": true, + "BY": true, "BE": true, "BZ": true, "BJ": true, "BM": true, + "BT": true, "BO": true, "BQ": true, "BA": true, "BW": true, + "BV": true, "BR": true, "IO": true, "BN": true, "BG": true, + "BF": true, "BI": true, "KH": true, "CM": true, "CA": true, + "CV": true, "KY": true, "CF": true, "TD": true, "CL": true, + "CN": true, "CX": true, "CC": true, "CO": true, "KM": true, + "CG": true, "CD": true, "CK": true, "CR": true, "CI": true, + "HR": true, "CU": true, "CW": true, "CY": true, "CZ": true, + "DK": true, "DJ": true, "DM": true, "DO": true, "EC": true, + "EG": true, "SV": true, "GQ": true, "ER": true, "EE": true, + "ET": true, "FK": true, "FO": true, "FJ": true, "FI": true, + "FR": true, "GF": true, "PF": true, "TF": true, "GA": true, + "GM": true, "GE": true, "DE": true, "GH": true, "GI": true, + "GR": true, "GL": true, "GD": true, "GP": true, "GU": true, + "GT": true, "GG": true, "GN": true, "GW": true, "GY": true, + "HT": true, "HM": true, "VA": true, "HN": true, "HK": true, + "HU": true, "IS": true, "IN": true, "ID": true, "IR": true, + "IQ": true, "IE": true, "IM": true, "IL": true, "IT": true, + "JM": true, "JP": true, "JE": true, "JO": true, "KZ": true, + "KE": true, "KI": true, "KP": true, "KR": true, "KW": true, + "KG": true, "LA": true, "LV": true, "LB": true, "LS": true, + "LR": true, "LY": true, "LI": true, "LT": true, "LU": true, + "MO": true, "MK": true, "MG": true, "MW": true, "MY": true, + "MV": true, "ML": true, "MT": true, "MH": true, "MQ": true, + "MR": true, "MU": true, "YT": true, "MX": true, "FM": true, + "MD": true, "MC": true, "MN": true, "ME": true, "MS": true, + "MA": true, "MZ": true, "MM": true, "NA": true, "NR": true, + "NP": true, "NL": true, "NC": true, "NZ": true, "NI": true, + "NE": true, "NG": true, "NU": true, "NF": true, "MP": true, + "NO": true, "OM": true, "PK": true, "PW": true, "PS": true, + "PA": true, "PG": true, "PY": true, "PE": true, "PH": true, + "PN": true, "PL": true, "PT": true, "PR": true, "QA": true, + "RE": true, "RO": true, "RU": true, "RW": true, "BL": true, + "SH": true, "KN": true, "LC": true, "MF": true, "PM": true, + "VC": true, "WS": true, "SM": true, "ST": true, "SA": true, + "SN": true, "RS": true, "SC": true, "SL": true, "SG": true, + "SX": true, "SK": true, "SI": true, "SB": true, "SO": true, + "ZA": true, "GS": true, "SS": true, "ES": true, "LK": true, + "SD": true, "SR": true, "SJ": true, "SZ": true, "SE": true, + "CH": true, "SY": true, "TW": true, "TJ": true, "TZ": true, + "TH": true, "TL": true, "TG": true, "TK": true, "TO": true, + "TT": true, "TN": true, "TR": true, "TM": true, "TC": true, + "TV": true, "UG": true, "UA": true, "AE": true, "GB": true, + "US": true, "UM": true, "UY": true, "UZ": true, "VU": true, + "VE": true, "VN": true, "VG": true, "VI": true, "WF": true, + "EH": true, "YE": true, "ZM": true, "ZW": true, +} + +var iso3166_1_alpha3 = map[string]bool{ + // see: https://www.iso.org/iso-3166-country-codes.html + "AFG": true, "ALB": true, "DZA": true, "ASM": true, "AND": true, + "AGO": true, "AIA": true, "ATA": true, "ATG": true, "ARG": true, + "ARM": true, "ABW": true, "AUS": true, "AUT": true, "AZE": true, + "BHS": true, "BHR": true, "BGD": true, "BRB": true, "BLR": true, + "BEL": true, "BLZ": true, "BEN": true, "BMU": true, "BTN": true, + "BOL": true, "BES": true, "BIH": true, "BWA": true, "BVT": true, + "BRA": true, "IOT": true, "BRN": true, "BGR": true, "BFA": true, + "BDI": true, "CPV": true, "KHM": true, "CMR": true, "CAN": true, + "CYM": true, "CAF": true, "TCD": true, "CHL": true, "CHN": true, + "CXR": true, "CCK": true, "COL": true, "COM": true, "COD": true, + "COG": true, "COK": true, "CRI": true, "HRV": true, "CUB": true, + "CUW": true, "CYP": true, "CZE": true, "CIV": true, "DNK": true, + "DJI": true, "DMA": true, "DOM": true, "ECU": true, "EGY": true, + "SLV": true, "GNQ": true, "ERI": true, "EST": true, "SWZ": true, + "ETH": true, "FLK": true, "FRO": true, "FJI": true, "FIN": true, + "FRA": true, "GUF": true, "PYF": true, "ATF": true, "GAB": true, + "GMB": true, "GEO": true, "DEU": true, "GHA": true, "GIB": true, + "GRC": true, "GRL": true, "GRD": true, "GLP": true, "GUM": true, + "GTM": true, "GGY": true, "GIN": true, "GNB": true, "GUY": true, + "HTI": true, "HMD": true, "VAT": true, "HND": true, "HKG": true, + "HUN": true, "ISL": true, "IND": true, "IDN": true, "IRN": true, + "IRQ": true, "IRL": true, "IMN": true, "ISR": true, "ITA": true, + "JAM": true, "JPN": true, "JEY": true, "JOR": true, "KAZ": true, + "KEN": true, "KIR": true, "PRK": true, "KOR": true, "KWT": true, + "KGZ": true, "LAO": true, "LVA": true, "LBN": true, "LSO": true, + "LBR": true, "LBY": true, "LIE": true, "LTU": true, "LUX": true, + "MAC": true, "MDG": true, "MWI": true, "MYS": true, "MDV": true, + "MLI": true, "MLT": true, "MHL": true, "MTQ": true, "MRT": true, + "MUS": true, "MYT": true, "MEX": true, "FSM": true, "MDA": true, + "MCO": true, "MNG": true, "MNE": true, "MSR": true, "MAR": true, + "MOZ": true, "MMR": true, "NAM": true, "NRU": true, "NPL": true, + "NLD": true, "NCL": true, "NZL": true, "NIC": true, "NER": true, + "NGA": true, "NIU": true, "NFK": true, "MKD": true, "MNP": true, + "NOR": true, "OMN": true, "PAK": true, "PLW": true, "PSE": true, + "PAN": true, "PNG": true, "PRY": true, "PER": true, "PHL": true, + "PCN": true, "POL": true, "PRT": true, "PRI": true, "QAT": true, + "ROU": true, "RUS": true, "RWA": true, "REU": true, "BLM": true, + "SHN": true, "KNA": true, "LCA": true, "MAF": true, "SPM": true, + "VCT": true, "WSM": true, "SMR": true, "STP": true, "SAU": true, + "SEN": true, "SRB": true, "SYC": true, "SLE": true, "SGP": true, + "SXM": true, "SVK": true, "SVN": true, "SLB": true, "SOM": true, + "ZAF": true, "SGS": true, "SSD": true, "ESP": true, "LKA": true, + "SDN": true, "SUR": true, "SJM": true, "SWE": true, "CHE": true, + "SYR": true, "TWN": true, "TJK": true, "TZA": true, "THA": true, + "TLS": true, "TGO": true, "TKL": true, "TON": true, "TTO": true, + "TUN": true, "TUR": true, "TKM": true, "TCA": true, "TUV": true, + "UGA": true, "UKR": true, "ARE": true, "GBR": true, "UMI": true, + "USA": true, "URY": true, "UZB": true, "VUT": true, "VEN": true, + "VNM": true, "VGB": true, "VIR": true, "WLF": true, "ESH": true, + "YEM": true, "ZMB": true, "ZWE": true, "ALA": true, +} +var iso3166_1_alpha_numeric = map[int]bool{ + // see: https://www.iso.org/iso-3166-country-codes.html + 4: true, 8: true, 12: true, 16: true, 20: true, + 24: true, 660: true, 10: true, 28: true, 32: true, + 51: true, 533: true, 36: true, 40: true, 31: true, + 44: true, 48: true, 50: true, 52: true, 112: true, + 56: true, 84: true, 204: true, 60: true, 64: true, + 68: true, 535: true, 70: true, 72: true, 74: true, + 76: true, 86: true, 96: true, 100: true, 854: true, + 108: true, 132: true, 116: true, 120: true, 124: true, + 136: true, 140: true, 148: true, 152: true, 156: true, + 162: true, 166: true, 170: true, 174: true, 180: true, + 178: true, 184: true, 188: true, 191: true, 192: true, + 531: true, 196: true, 203: true, 384: true, 208: true, + 262: true, 212: true, 214: true, 218: true, 818: true, + 222: true, 226: true, 232: true, 233: true, 748: true, + 231: true, 238: true, 234: true, 242: true, 246: true, + 250: true, 254: true, 258: true, 260: true, 266: true, + 270: true, 268: true, 276: true, 288: true, 292: true, + 300: true, 304: true, 308: true, 312: true, 316: true, + 320: true, 831: true, 324: true, 624: true, 328: true, + 332: true, 334: true, 336: true, 340: true, 344: true, + 348: true, 352: true, 356: true, 360: true, 364: true, + 368: true, 372: true, 833: true, 376: true, 380: true, + 388: true, 392: true, 832: true, 400: true, 398: true, + 404: true, 296: true, 408: true, 410: true, 414: true, + 417: true, 418: true, 428: true, 422: true, 426: true, + 430: true, 434: true, 438: true, 440: true, 442: true, + 446: true, 450: true, 454: true, 458: true, 462: true, + 466: true, 470: true, 584: true, 474: true, 478: true, + 480: true, 175: true, 484: true, 583: true, 498: true, + 492: true, 496: true, 499: true, 500: true, 504: true, + 508: true, 104: true, 516: true, 520: true, 524: true, + 528: true, 540: true, 554: true, 558: true, 562: true, + 566: true, 570: true, 574: true, 807: true, 580: true, + 578: true, 512: true, 586: true, 585: true, 275: true, + 591: true, 598: true, 600: true, 604: true, 608: true, + 612: true, 616: true, 620: true, 630: true, 634: true, + 642: true, 643: true, 646: true, 638: true, 652: true, + 654: true, 659: true, 662: true, 663: true, 666: true, + 670: true, 882: true, 674: true, 678: true, 682: true, + 686: true, 688: true, 690: true, 694: true, 702: true, + 534: true, 703: true, 705: true, 90: true, 706: true, + 710: true, 239: true, 728: true, 724: true, 144: true, + 729: true, 740: true, 744: true, 752: true, 756: true, + 760: true, 158: true, 762: true, 834: true, 764: true, + 626: true, 768: true, 772: true, 776: true, 780: true, + 788: true, 792: true, 795: true, 796: true, 798: true, + 800: true, 804: true, 784: true, 826: true, 581: true, + 840: true, 858: true, 860: true, 548: true, 862: true, + 704: true, 92: true, 850: true, 876: true, 732: true, + 887: true, 894: true, 716: true, 248: true, +} diff --git a/go-playground/validator/v10/doc.go b/go-playground/validator/v10/doc.go new file mode 100644 index 0000000..34f22ad --- /dev/null +++ b/go-playground/validator/v10/doc.go @@ -0,0 +1,1308 @@ +/* +Package validator implements value validations for structs and individual fields +based on tags. + +It can also handle Cross-Field and Cross-Struct validation for nested structs +and has the ability to dive into arrays and maps of any type. + +see more examples https://github.com/go-playground/validator/tree/master/_examples + +Validation Functions Return Type error + +Doing things this way is actually the way the standard library does, see the +file.Open method here: + + https://golang.org/pkg/os/#Open. + +The authors return type "error" to avoid the issue discussed in the following, +where err is always != nil: + + http://stackoverflow.com/a/29138676/3158232 + https://github.com/go-playground/validator/issues/134 + +Validator only InvalidValidationError for bad validation input, nil or +ValidationErrors as type error; so, in your code all you need to do is check +if the error returned is not nil, and if it's not check if error is +InvalidValidationError ( if necessary, most of the time it isn't ) type cast +it to type ValidationErrors like so err.(validator.ValidationErrors). + +Custom Validation Functions + +Custom Validation functions can be added. Example: + + // Structure + func customFunc(fl validator.FieldLevel) bool { + + if fl.Field().String() == "invalid" { + return false + } + + return true + } + + validate.RegisterValidation("custom tag name", customFunc) + // NOTES: using the same tag name as an existing function + // will overwrite the existing one + +Cross-Field Validation + +Cross-Field Validation can be done via the following tags: + - eqfield + - nefield + - gtfield + - gtefield + - ltfield + - ltefield + - eqcsfield + - necsfield + - gtcsfield + - gtecsfield + - ltcsfield + - ltecsfield + +If, however, some custom cross-field validation is required, it can be done +using a custom validation. + +Why not just have cross-fields validation tags (i.e. only eqcsfield and not +eqfield)? + +The reason is efficiency. If you want to check a field within the same struct +"eqfield" only has to find the field on the same struct (1 level). But, if we +used "eqcsfield" it could be multiple levels down. Example: + + type Inner struct { + StartDate time.Time + } + + type Outer struct { + InnerStructField *Inner + CreatedAt time.Time `validate:"ltecsfield=InnerStructField.StartDate"` + } + + now := time.Now() + + inner := &Inner{ + StartDate: now, + } + + outer := &Outer{ + InnerStructField: inner, + CreatedAt: now, + } + + errs := validate.Struct(outer) + + // NOTE: when calling validate.Struct(val) topStruct will be the top level struct passed + // into the function + // when calling validate.VarWithValue(val, field, tag) val will be + // whatever you pass, struct, field... + // when calling validate.Field(field, tag) val will be nil + +Multiple Validators + +Multiple validators on a field will process in the order defined. Example: + + type Test struct { + Field `validate:"max=10,min=1"` + } + + // max will be checked then min + +Bad Validator definitions are not handled by the library. Example: + + type Test struct { + Field `validate:"min=10,max=0"` + } + + // this definition of min max will never succeed + +Using Validator Tags + +Baked In Cross-Field validation only compares fields on the same struct. +If Cross-Field + Cross-Struct validation is needed you should implement your +own custom validator. + +Comma (",") is the default separator of validation tags. If you wish to +have a comma included within the parameter (i.e. excludesall=,) you will need to +use the UTF-8 hex representation 0x2C, which is replaced in the code as a comma, +so the above will become excludesall=0x2C. + + type Test struct { + Field `validate:"excludesall=,"` // BAD! Do not include a comma. + Field `validate:"excludesall=0x2C"` // GOOD! Use the UTF-8 hex representation. + } + +Pipe ("|") is the 'or' validation tags deparator. If you wish to +have a pipe included within the parameter i.e. excludesall=| you will need to +use the UTF-8 hex representation 0x7C, which is replaced in the code as a pipe, +so the above will become excludesall=0x7C + + type Test struct { + Field `validate:"excludesall=|"` // BAD! Do not include a a pipe! + Field `validate:"excludesall=0x7C"` // GOOD! Use the UTF-8 hex representation. + } + + +Baked In Validators and Tags + +Here is a list of the current built in validators: + + +Skip Field + +Tells the validation to skip this struct field; this is particularly +handy in ignoring embedded structs from being validated. (Usage: -) + Usage: - + + +Or Operator + +This is the 'or' operator allowing multiple validators to be used and +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) + + Usage: | + +StructOnly + +When a field that is a nested struct is encountered, and contains this flag +any validation on the nested struct will be run, but none of the nested +struct fields will be validated. This is useful if inside of your program +you know the struct will be valid, but need to verify it has been assigned. +NOTE: only "required" and "omitempty" can be used on a struct itself. + + Usage: structonly + +NoStructLevel + +Same as structonly tag except that any struct level validations will not run. + + Usage: nostructlevel + +Omit Empty + +Allows conditional validation, for example if a field is not set with +a value (Determined by the "required" validator) then other validation +such as min or max won't run, but if a value is set validation will run. + + Usage: omitempty + +Dive + +This tells the validator to dive into a slice, array or map and validate that +level of the slice, array or map with the validation tags that follow. +Multidimensional nesting is also supported, each level you wish to dive will +require another dive tag. dive has some sub-tags, 'keys' & 'endkeys', please see +the Keys & EndKeys section just below. + + Usage: dive + +Example #1 + + [][]string with validation tag "gt=0,dive,len=1,dive,required" + // gt=0 will be applied to [] + // len=1 will be applied to []string + // required will be applied to string + +Example #2 + + [][]string with validation tag "gt=0,dive,dive,required" + // gt=0 will be applied to [] + // []string will be spared validation + // required will be applied to string + +Keys & EndKeys + +These are to be used together directly after the dive tag and tells the validator +that anything between 'keys' and 'endkeys' applies to the keys of a map and not the +values; think of it like the 'dive' tag, but for map keys instead of values. +Multidimensional nesting is also supported, each level you wish to validate will +require another 'keys' and 'endkeys' tag. These tags are only valid for maps. + + Usage: dive,keys,othertagvalidation(s),endkeys,valuevalidationtags + +Example #1 + + map[string]string with validation tag "gt=0,dive,keys,eg=1|eq=2,endkeys,required" + // gt=0 will be applied to the map itself + // eg=1|eq=2 will be applied to the map keys + // required will be applied to map values + +Example #2 + + map[[2]string]string with validation tag "gt=0,dive,keys,dive,eq=1|eq=2,endkeys,required" + // gt=0 will be applied to the map itself + // eg=1|eq=2 will be applied to each array element in the the map keys + // required will be applied to map values + +Required + +This validates that the value is not the data types default zero value. +For numbers ensures value is not zero. For strings ensures value is +not "". For slices, maps, pointers, interfaces, channels and functions +ensures the value is not nil. + + Usage: required + +Required If + +The field under validation must be present and 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: required_if + +Examples: + + // require the field if the Field1 is equal to the parameter given: + Usage: required_if=Field1 foobar + + // require the field if the Field1 and Field2 is equal to the value respectively: + Usage: required_if=Field1 foo Field2 bar + +Required Unless + +The field under validation must be present and not 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: required_unless + +Examples: + + // require the field unless the Field1 is equal to the parameter given: + Usage: required_unless=Field1 foobar + + // require the field unless the Field1 and Field2 is equal to the value respectively: + Usage: required_unless=Field1 foo Field2 bar + +Required With + +The field under validation must be present and not empty only if any +of the other specified fields are present. For strings ensures value is +not "". For slices, maps, pointers, interfaces, channels and functions +ensures the value is not nil. + + Usage: required_with + +Examples: + + // require the field if the Field1 is present: + Usage: required_with=Field1 + + // require the field if the Field1 or Field2 is present: + Usage: required_with=Field1 Field2 + +Required With All + +The field under validation must be present and not empty only if all +of the other specified fields are present. For strings ensures value is +not "". For slices, maps, pointers, interfaces, channels and functions +ensures the value is not nil. + + Usage: required_with_all + +Example: + + // require the field if the Field1 and Field2 is present: + Usage: required_with_all=Field1 Field2 + +Required Without + +The field under validation must be present and not empty only when any +of the other specified fields are not present. For strings ensures value is +not "". For slices, maps, pointers, interfaces, channels and functions +ensures the value is not nil. + + Usage: required_without + +Examples: + + // require the field if the Field1 is not present: + Usage: required_without=Field1 + + // require the field if the Field1 or Field2 is not present: + Usage: required_without=Field1 Field2 + +Required Without All + +The field under validation must be present and not empty only when all +of the other specified fields are not present. For strings ensures value is +not "". For slices, maps, pointers, interfaces, channels and functions +ensures the value is not nil. + + Usage: required_without_all + +Example: + + // require the field if the Field1 and Field2 is not present: + Usage: required_without_all=Field1 Field2 + +Is Default + +This validates that the value is the default value and is almost the +opposite of required. + + Usage: isdefault + +Length + +For numbers, length will ensure that the value is +equal to the parameter given. For strings, it checks that +the string length is exactly that number of characters. For slices, +arrays, and maps, validates the number of items. + +Example #1 + + Usage: len=10 + +Example #2 (time.Duration) + +For time.Duration, len will ensure that the value is equal to the duration given +in the parameter. + + Usage: len=1h30m + +Maximum + +For numbers, max will ensure that the value is +less than or equal to the parameter given. For strings, it checks +that the string length is at most that number of characters. For +slices, arrays, and maps, validates the number of items. + +Example #1 + + Usage: max=10 + +Example #2 (time.Duration) + +For time.Duration, max will ensure that the value is less than or equal to the +duration given in the parameter. + + Usage: max=1h30m + +Minimum + +For numbers, min will ensure that the value is +greater or equal to the parameter given. For strings, it checks that +the string length is at least that number of characters. For slices, +arrays, and maps, validates the number of items. + +Example #1 + + Usage: min=10 + +Example #2 (time.Duration) + +For time.Duration, min will ensure that the value is greater than or equal to +the duration given in the parameter. + + Usage: min=1h30m + +Equals + +For strings & numbers, eq will ensure that the value is +equal to the parameter given. For slices, arrays, and maps, +validates the number of items. + +Example #1 + + Usage: eq=10 + +Example #2 (time.Duration) + +For time.Duration, eq will ensure that the value is equal to the duration given +in the parameter. + + Usage: eq=1h30m + +Not Equal + +For strings & numbers, ne will ensure that the value is not +equal to the parameter given. For slices, arrays, and maps, +validates the number of items. + +Example #1 + + Usage: ne=10 + +Example #2 (time.Duration) + +For time.Duration, ne will ensure that the value is not equal to the duration +given in the parameter. + + Usage: ne=1h30m + +One Of + +For strings, ints, and uints, oneof will ensure that the value +is one of the values in the parameter. The parameter should be +a list of values separated by whitespace. Values may be +strings or numbers. To match strings with spaces in them, include +the target string between single quotes. + + Usage: oneof=red green + oneof='red green' 'blue yellow' + oneof=5 7 9 + +Greater Than + +For numbers, this will ensure that the value is greater than the +parameter given. For strings, it checks that the string length +is greater than that number of characters. For slices, arrays +and maps it validates the number of items. + +Example #1 + + Usage: gt=10 + +Example #2 (time.Time) + +For time.Time ensures the time value is greater than time.Now.UTC(). + + Usage: gt + +Example #3 (time.Duration) + +For time.Duration, gt will ensure that the value is greater than the duration +given in the parameter. + + Usage: gt=1h30m + +Greater Than or Equal + +Same as 'min' above. Kept both to make terminology with 'len' easier. + +Example #1 + + Usage: gte=10 + +Example #2 (time.Time) + +For time.Time ensures the time value is greater than or equal to time.Now.UTC(). + + Usage: gte + +Example #3 (time.Duration) + +For time.Duration, gte will ensure that the value is greater than or equal to +the duration given in the parameter. + + Usage: gte=1h30m + +Less Than + +For numbers, this will ensure that the value is less than the parameter given. +For strings, it checks that the string length is less than that number of +characters. For slices, arrays, and maps it validates the number of items. + +Example #1 + + Usage: lt=10 + +Example #2 (time.Time) + +For time.Time ensures the time value is less than time.Now.UTC(). + + Usage: lt + +Example #3 (time.Duration) + +For time.Duration, lt will ensure that the value is less than the duration given +in the parameter. + + Usage: lt=1h30m + +Less Than or Equal + +Same as 'max' above. Kept both to make terminology with 'len' easier. + +Example #1 + + Usage: lte=10 + +Example #2 (time.Time) + +For time.Time ensures the time value is less than or equal to time.Now.UTC(). + + Usage: lte + +Example #3 (time.Duration) + +For time.Duration, lte will ensure that the value is less than or equal to the +duration given in the parameter. + + Usage: lte=1h30m + +Field Equals Another Field + +This will validate the field value against another fields value either within +a struct or passed in field. + +Example #1: + + // Validation on Password field using: + Usage: eqfield=ConfirmPassword + +Example #2: + + // Validating by field: + validate.VarWithValue(password, confirmpassword, "eqfield") + +Field Equals Another Field (relative) + +This does the same as eqfield except that it validates the field provided relative +to the top level struct. + + Usage: eqcsfield=InnerStructField.Field) + +Field Does Not Equal Another Field + +This will validate the field value against another fields value either within +a struct or passed in field. + +Examples: + + // Confirm two colors are not the same: + // + // Validation on Color field: + Usage: nefield=Color2 + + // Validating by field: + validate.VarWithValue(color1, color2, "nefield") + +Field Does Not Equal Another Field (relative) + +This does the same as nefield except that it validates the field provided +relative to the top level struct. + + Usage: necsfield=InnerStructField.Field + +Field Greater Than Another Field + +Only valid for Numbers, time.Duration and time.Time types, this will validate +the field value against another fields value either within a struct or passed in +field. usage examples are for validation of a Start and End date: + +Example #1: + + // Validation on End field using: + validate.Struct Usage(gtfield=Start) + +Example #2: + + // Validating by field: + validate.VarWithValue(start, end, "gtfield") + +Field Greater Than Another Relative Field + +This does the same as gtfield except that it validates the field provided +relative to the top level struct. + + Usage: gtcsfield=InnerStructField.Field + +Field Greater Than or Equal To Another Field + +Only valid for Numbers, time.Duration and time.Time types, this will validate +the field value against another fields value either within a struct or passed in +field. usage examples are for validation of a Start and End date: + +Example #1: + + // Validation on End field using: + validate.Struct Usage(gtefield=Start) + +Example #2: + + // Validating by field: + validate.VarWithValue(start, end, "gtefield") + +Field Greater Than or Equal To Another Relative Field + +This does the same as gtefield except that it validates the field provided relative +to the top level struct. + + Usage: gtecsfield=InnerStructField.Field + +Less Than Another Field + +Only valid for Numbers, time.Duration and time.Time types, this will validate +the field value against another fields value either within a struct or passed in +field. usage examples are for validation of a Start and End date: + +Example #1: + + // Validation on End field using: + validate.Struct Usage(ltfield=Start) + +Example #2: + + // Validating by field: + validate.VarWithValue(start, end, "ltfield") + +Less Than Another Relative Field + +This does the same as ltfield except that it validates the field provided relative +to the top level struct. + + Usage: ltcsfield=InnerStructField.Field + +Less Than or Equal To Another Field + +Only valid for Numbers, time.Duration and time.Time types, this will validate +the field value against another fields value either within a struct or passed in +field. usage examples are for validation of a Start and End date: + +Example #1: + + // Validation on End field using: + validate.Struct Usage(ltefield=Start) + +Example #2: + + // Validating by field: + validate.VarWithValue(start, end, "ltefield") + +Less Than or Equal To Another Relative Field + +This does the same as ltefield except that it validates the field provided relative +to the top level struct. + + Usage: ltecsfield=InnerStructField.Field + +Field Contains Another Field + +This does the same as contains except for struct fields. It should only be used +with string types. See the behavior of reflect.Value.String() for behavior on +other types. + + Usage: containsfield=InnerStructField.Field + +Field Excludes Another Field + +This does the same as excludes except for struct fields. It should only be used +with string types. See the behavior of reflect.Value.String() for behavior on +other types. + + Usage: excludesfield=InnerStructField.Field + +Unique + +For arrays & slices, unique will ensure that there are no duplicates. +For maps, unique will ensure that there are no duplicate values. +For slices of struct, unique will ensure that there are no duplicate values +in a field of the struct specified via a parameter. + + // For arrays, slices, and maps: + Usage: unique + + // For slices of struct: + Usage: unique=field + +Alpha Only + +This validates that a string value contains ASCII alpha characters only + + Usage: alpha + +Alphanumeric + +This validates that a string value contains ASCII alphanumeric characters only + + Usage: alphanum + +Alpha Unicode + +This validates that a string value contains unicode alpha characters only + + Usage: alphaunicode + +Alphanumeric Unicode + +This validates that a string value contains unicode alphanumeric characters only + + Usage: alphanumunicode + +Number + +This validates that a string value contains number values only. +For integers or float it returns true. + + Usage: number + +Numeric + +This validates that a string value contains a basic numeric value. +basic excludes exponents etc... +for integers or float it returns true. + + Usage: numeric + +Hexadecimal String + +This validates that a string value contains a valid hexadecimal. + + Usage: hexadecimal + +Hexcolor String + +This validates that a string value contains a valid hex color including +hashtag (#) + + Usage: hexcolor + +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 not a valid uppercase string. + + Usage: uppercase + +RGB String + +This validates that a string value contains a valid rgb color + + Usage: rgb + +RGBA String + +This validates that a string value contains a valid rgba color + + Usage: rgba + +HSL String + +This validates that a string value contains a valid hsl color + + Usage: hsl + +HSLA String + +This validates that a string value contains a valid hsla color + + Usage: hsla + +E.164 Phone Number String + +This validates that a string value contains a valid E.164 Phone number +https://en.wikipedia.org/wiki/E.164 (ex. +1123456789) + + Usage: e164 + +E-mail String + +This validates that a string value contains a valid email +This may not conform to all possibilities of any rfc standard, but neither +does any email provider accept all possibilities. + + Usage: email + +JSON String + +This validates that a string value is valid JSON + + Usage: json + +File path + +This validates that a string value contains a valid file path and that +the file exists on the machine. +This is done using os.Stat, which is a platform independent function. + + Usage: file + +URL String + +This validates that a string value contains a valid url +This will accept any url the golang request uri accepts but must contain +a schema for example http:// or rtmp:// + + Usage: url + +URI String + +This validates that a string value contains a valid uri +This will accept any uri the golang request uri accepts + + Usage: uri + +Urn RFC 2141 String + +This validataes that a string value contains a valid URN +according to the RFC 2141 spec. + + Usage: urn_rfc2141 + +Base64 String + +This validates that a string value contains a valid base64 value. +Although an empty string is valid base64 this will report an empty string +as an error, if you wish to accept an empty string as valid you can use +this with the omitempty tag. + + Usage: base64 + +Base64URL String + +This validates that a string value contains a valid base64 URL safe value +according the the RFC4648 spec. +Although an empty string is a valid base64 URL safe value, this will report +an empty string as an error, if you wish to accept an empty string as valid +you can use this with the omitempty tag. + + Usage: base64url + +Bitcoin Address + +This validates that a string value contains a valid bitcoin address. +The format of the string is checked to ensure it matches one of the three formats +P2PKH, P2SH and performs checksum validation. + + Usage: btc_addr + +Bitcoin Bech32 Address (segwit) + +This validates that a string value contains a valid bitcoin Bech32 address as defined +by bip-0173 (https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) +Special thanks to Pieter Wuille for providng reference implementations. + + Usage: btc_addr_bech32 + +Ethereum Address + +This validates that a string value contains a valid ethereum address. +The format of the string is checked to ensure it matches the standard Ethereum address format. + + Usage: eth_addr + +Contains + +This validates that a string value contains the substring value. + + Usage: contains=@ + +Contains Any + +This validates that a string value contains any Unicode code points +in the substring value. + + Usage: containsany=!@#? + +Contains Rune + +This validates that a string value contains the supplied rune value. + + Usage: containsrune=@ + +Excludes + +This validates that a string value does not contain the substring value. + + Usage: excludes=@ + +Excludes All + +This validates that a string value does not contain any Unicode code +points in the substring value. + + Usage: excludesall=!@#? + +Excludes Rune + +This validates that a string value does not contain the supplied rune value. + + Usage: excludesrune=@ + +Starts With + +This validates that a string value starts with the supplied string value + + Usage: startswith=hello + +Ends With + +This validates that a string value ends with the supplied string value + + Usage: endswith=goodbye + +Does Not Start With + +This validates that a string value does not start with the supplied string value + + Usage: startsnotwith=hello + +Does Not End With + +This validates that a string value does not end with the supplied string value + + Usage: endsnotwith=goodbye + +International Standard Book Number + +This validates that a string value contains a valid isbn10 or isbn13 value. + + Usage: isbn + +International Standard Book Number 10 + +This validates that a string value contains a valid isbn10 value. + + Usage: isbn10 + +International Standard Book Number 13 + +This validates that a string value contains a valid isbn13 value. + + Usage: isbn13 + +Universally Unique Identifier UUID + +This validates that a string value contains a valid UUID. Uppercase UUID values will not pass - use `uuid_rfc4122` instead. + + Usage: uuid + +Universally Unique Identifier UUID v3 + +This validates that a string value contains a valid version 3 UUID. Uppercase UUID values will not pass - use `uuid3_rfc4122` instead. + + Usage: uuid3 + +Universally Unique Identifier UUID v4 + +This validates that a string value contains a valid version 4 UUID. Uppercase UUID values will not pass - use `uuid4_rfc4122` instead. + + Usage: uuid4 + +Universally Unique Identifier UUID v5 + +This validates that a string value contains a valid version 5 UUID. Uppercase UUID values will not pass - use `uuid5_rfc4122` instead. + + Usage: uuid5 + +ASCII + +This validates that a string value contains only ASCII characters. +NOTE: if the string is blank, this validates as true. + + Usage: ascii + +Printable ASCII + +This validates that a string value contains only printable ASCII characters. +NOTE: if the string is blank, this validates as true. + + Usage: printascii + +Multi-Byte Characters + +This validates that a string value contains one or more multibyte characters. +NOTE: if the string is blank, this validates as true. + + Usage: multibyte + +Data URL + +This validates that a string value contains a valid DataURI. +NOTE: this will also validate that the data portion is valid base64 + + Usage: datauri + +Latitude + +This validates that a string value contains a valid latitude. + + Usage: latitude + +Longitude + +This validates that a string value contains a valid longitude. + + Usage: longitude + +Social Security Number SSN + +This validates that a string value contains a valid U.S. Social Security Number. + + Usage: ssn + +Internet Protocol Address IP + +This validates that a string value contains a valid IP Address. + + Usage: ip + +Internet Protocol Address IPv4 + +This validates that a string value contains a valid v4 IP Address. + + Usage: ipv4 + +Internet Protocol Address IPv6 + +This validates that a string value contains a valid v6 IP Address. + + Usage: ipv6 + +Classless Inter-Domain Routing CIDR + +This validates that a string value contains a valid CIDR Address. + + Usage: cidr + +Classless Inter-Domain Routing CIDRv4 + +This validates that a string value contains a valid v4 CIDR Address. + + Usage: cidrv4 + +Classless Inter-Domain Routing CIDRv6 + +This validates that a string value contains a valid v6 CIDR Address. + + Usage: cidrv6 + +Transmission Control Protocol Address TCP + +This validates that a string value contains a valid resolvable TCP Address. + + Usage: tcp_addr + +Transmission Control Protocol Address TCPv4 + +This validates that a string value contains a valid resolvable v4 TCP Address. + + Usage: tcp4_addr + +Transmission Control Protocol Address TCPv6 + +This validates that a string value contains a valid resolvable v6 TCP Address. + + Usage: tcp6_addr + +User Datagram Protocol Address UDP + +This validates that a string value contains a valid resolvable UDP Address. + + Usage: udp_addr + +User Datagram Protocol Address UDPv4 + +This validates that a string value contains a valid resolvable v4 UDP Address. + + Usage: udp4_addr + +User Datagram Protocol Address UDPv6 + +This validates that a string value contains a valid resolvable v6 UDP Address. + + Usage: udp6_addr + +Internet Protocol Address IP + +This validates that a string value contains a valid resolvable IP Address. + + Usage: ip_addr + +Internet Protocol Address IPv4 + +This validates that a string value contains a valid resolvable v4 IP Address. + + Usage: ip4_addr + +Internet Protocol Address IPv6 + +This validates that a string value contains a valid resolvable v6 IP Address. + + Usage: ip6_addr + +Unix domain socket end point Address + +This validates that a string value contains a valid Unix Address. + + Usage: unix_addr + +Media Access Control Address MAC + +This validates that a string value contains a valid MAC Address. + + Usage: mac + +Note: See Go's ParseMAC for accepted formats and types: + + http://golang.org/src/net/mac.go?s=866:918#L29 + +Hostname RFC 952 + +This validates that a string value is a valid Hostname according to RFC 952 https://tools.ietf.org/html/rfc952 + + Usage: hostname + +Hostname RFC 1123 + +This validates that a string value is a valid Hostname according to RFC 1123 https://tools.ietf.org/html/rfc1123 + + Usage: hostname_rfc1123 or if you want to continue to use 'hostname' in your tags, create an alias. + +Full Qualified Domain Name (FQDN) + +This validates that a string value contains a valid FQDN. + + Usage: fqdn + +HTML Tags + +This validates that a string value appears to be an HTML element tag +including those described at https://developer.mozilla.org/en-US/docs/Web/HTML/Element + + Usage: html + +HTML Encoded + +This validates that a string value is a proper character reference in decimal +or hexadecimal format + + Usage: html_encoded + +URL Encoded + +This validates that a string value is percent-encoded (URL encoded) according +to https://tools.ietf.org/html/rfc3986#section-2.1 + + Usage: url_encoded + +Directory + +This validates that a string value contains a valid directory and that +it exists on the machine. +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 + +Datetime + +This validates that a string value is a valid datetime based on the supplied datetime format. +Supplied format must match the official Go time format layout as documented in https://golang.org/pkg/time/ + + Usage: datetime=2006-01-02 + +Iso3166-1 alpha-2 + +This validates that a string value is a valid country code based on iso3166-1 alpha-2 standard. +see: https://www.iso.org/iso-3166-country-codes.html + + Usage: iso3166_1_alpha2 + +Iso3166-1 alpha-3 + +This validates that a string value is a valid country code based on iso3166-1 alpha-3 standard. +see: https://www.iso.org/iso-3166-country-codes.html + + Usage: iso3166_1_alpha3 + +Iso3166-1 alpha-numeric + +This validates that a string value is a valid country code based on iso3166-1 alpha-numeric standard. +see: https://www.iso.org/iso-3166-country-codes.html + + Usage: iso3166_1_alpha3 + +TimeZone + +This validates that a string value is a valid time zone based on the time zone database present on the system. +Although empty value and Local value are allowed by time.LoadLocation golang function, they are not allowed by this validator. +More information on https://golang.org/pkg/time/#LoadLocation + + Usage: timezone + + +Alias Validators and Tags + +NOTE: When returning an error, the tag returned in "FieldError" will be +the alias tag unless the dive tag is part of the alias. Everything after the +dive tag is not reported as the alias tag. Also, the "ActualTag" in the before +case will be the actual tag within the alias that failed. + +Here is a list of the current built in alias tags: + + "iscolor" + alias is "hexcolor|rgb|rgba|hsl|hsla" (Usage: iscolor) + "country_code" + alias is "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric" (Usage: country_code) + +Validator notes: + + regex + a regex validator won't be added because commas and = signs can be part + of a regex which conflict with the validation definitions. Although + workarounds can be made, they take away from using pure regex's. + Furthermore it's quick and dirty but the regex's become harder to + maintain and are not reusable, so it's as much a programming philosophy + as anything. + + In place of this new validator functions should be created; a regex can + be used within the validator function and even be precompiled for better + efficiency within regexes.go. + + And the best reason, you can submit a pull request and we can keep on + adding to the validation library of this package! + +Non standard validators + +A collection of validation rules that are frequently needed but are more +complex than the ones found in the baked in validators. +A non standard validator must be registered manually like you would +with your own custom validation functions. + +Example of registration and use: + + type Test struct { + TestField string `validate:"yourtag"` + } + + t := &Test{ + TestField: "Test" + } + + validate := validator.New() + validate.RegisterValidation("yourtag", validators.NotBlank) + +Here is a list of the current non standard validators: + + NotBlank + This validates that the value is not blank or with length zero. + For strings ensures they do not contain only spaces. For channels, maps, slices and arrays + ensures they don't have zero length. For others, a non empty value is required. + + Usage: notblank + +Panics + +This package panics when bad input is provided, this is by design, bad code like +that should not make it to production. + + type Test struct { + TestField string `validate:"nonexistantfunction=1"` + } + + t := &Test{ + TestField: "Test" + } + + validate.Struct(t) // this will panic +*/ +package validator diff --git a/go-playground/validator/v10/errors.go b/go-playground/validator/v10/errors.go new file mode 100644 index 0000000..776029c --- /dev/null +++ b/go-playground/validator/v10/errors.go @@ -0,0 +1,295 @@ +package validator + +import ( + "bytes" + "fmt" + "reflect" + "strings" + + ut "gin-valid/go-playground/universal-translator" +) + +const ( + fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag" +) + +// ValidationErrorsTranslations is the translation return type +type ValidationErrorsTranslations map[string]string + +// InvalidValidationError describes an invalid argument passed to +// `Struct`, `StructExcept`, StructPartial` or `Field` +type InvalidValidationError struct { + Type reflect.Type +} + +// Error returns InvalidValidationError message +func (e *InvalidValidationError) Error() string { + + if e.Type == nil { + return "validator: (nil)" + } + + return "validator: (nil " + e.Type.String() + ")" +} + +// ValidationErrors is an array of FieldError's +// for use in custom error messages post validation. +type ValidationErrors []FieldError + +// Error is intended for use in development + debugging and not intended to be a production error message. +// It allows ValidationErrors to subscribe to the Error interface. +// All information to create an error message specific to your application is contained within +// the FieldError found within the ValidationErrors array +func (ve ValidationErrors) Error() string { + + buff := bytes.NewBufferString("") + + var fe *fieldError + + for i := 0; i < len(ve); i++ { + + fe = ve[i].(*fieldError) + buff.WriteString(fe.Error()) + buff.WriteString("\n") + } + + return strings.TrimSpace(buff.String()) +} + +// yang 修改 +// Translate translates all of the ValidationErrors +//func (ve ValidationErrors) Translate(ut ut.Translator) ValidationErrorsTranslations { +// +// trans := make(ValidationErrorsTranslations) +// +// var fe *fieldError +// +// for i := 0; i < len(ve); i++ { +// fe = ve[i].(*fieldError) +// +// // // in case an Anonymous struct was used, ensure that the key +// // // would be 'Username' instead of ".Username" +// // if len(fe.ns) > 0 && fe.ns[:1] == "." { +// // trans[fe.ns[1:]] = fe.Translate(ut) +// // continue +// // } +// +// trans[fe.ns] = fe.Translate(ut) +// } +// +// return trans +//} +type TransValidError struct { + ErrorString string +} + +func (e TransValidError) Error() string { + return e.ErrorString +} +func (ve ValidationErrors) Translate(ut ut.Translator) TransValidError { + var result TransValidError + var fe *fieldError + if len(ve) == 0 { + return result + } + fe = ve[0].(*fieldError) + result.ErrorString = fe.Translate(ut) + return result +} + +// yang 修改结束 + +// FieldError contains all functions to get error details +type FieldError interface { + + // returns the validation tag that failed. if the + // validation was an alias, this will return the + // alias name and not the underlying tag that failed. + // + // eg. alias "iscolor": "hexcolor|rgb|rgba|hsl|hsla" + // will return "iscolor" + Tag() string + + // returns the validation tag that failed, even if an + // alias the actual tag within the alias will be returned. + // If an 'or' validation fails the entire or will be returned. + // + // eg. alias "iscolor": "hexcolor|rgb|rgba|hsl|hsla" + // will return "hexcolor|rgb|rgba|hsl|hsla" + ActualTag() string + + // returns the namespace for the field error, with the tag + // name taking precedence over the field's actual name. + // + // eg. JSON name "User.fname" + // + // See StructNamespace() for a version that returns actual names. + // + // NOTE: this field can be blank when validating a single primitive field + // using validate.Field(...) as there is no way to extract it's name + Namespace() string + + // returns the namespace for the field error, with the field's + // actual name. + // + // eq. "User.FirstName" see Namespace for comparison + // + // NOTE: this field can be blank when validating a single primitive field + // using validate.Field(...) as there is no way to extract its name + StructNamespace() string + + // returns the fields name with the tag name taking precedence over the + // field's actual name. + // + // eq. JSON name "fname" + // see StructField for comparison + Field() string + + // returns the field's actual name from the struct, when able to determine. + // + // eq. "FirstName" + // see Field for comparison + StructField() string + + // returns the actual field's value in case needed for creating the error + // message + Value() interface{} + + // returns the param value, in string form for comparison; this will also + // help with generating an error message + Param() string + + // Kind returns the Field's reflect Kind + // + // eg. time.Time's kind is a struct + Kind() reflect.Kind + + // Type returns the Field's reflect Type + // + // // eg. time.Time's type is time.Time + Type() reflect.Type + + // returns the FieldError's translated error + // from the provided 'ut.Translator' and registered 'TranslationFunc' + // + // NOTE: if no registered translator can be found it returns the same as + // calling fe.Error() + Translate(ut ut.Translator) string + + // Error returns the FieldError's message + Error() string +} + +// compile time interface checks +var _ FieldError = new(fieldError) +var _ error = new(fieldError) + +// fieldError contains a single field's validation error along +// with other properties that may be needed for error message creation +// it complies with the FieldError interface +type fieldError struct { + v *Validate + tag string + actualTag string + ns string + structNs string + fieldLen uint8 + structfieldLen uint8 + value interface{} + param string + kind reflect.Kind + typ reflect.Type +} + +// Tag returns the validation tag that failed. +func (fe *fieldError) Tag() string { + return fe.tag +} + +// ActualTag returns the validation tag that failed, even if an +// alias the actual tag within the alias will be returned. +func (fe *fieldError) ActualTag() string { + return fe.actualTag +} + +// Namespace returns the namespace for the field error, with the tag +// name taking precedence over the field's actual name. +func (fe *fieldError) Namespace() string { + return fe.ns +} + +// StructNamespace returns the namespace for the field error, with the field's +// actual name. +func (fe *fieldError) StructNamespace() string { + return fe.structNs +} + +// Field returns the field's name with the tag name taking precedence over the +// field's actual name. +func (fe *fieldError) Field() string { + + return fe.ns[len(fe.ns)-int(fe.fieldLen):] + // // return fe.field + // fld := fe.ns[len(fe.ns)-int(fe.fieldLen):] + + // log.Println("FLD:", fld) + + // if len(fld) > 0 && fld[:1] == "." { + // return fld[1:] + // } + + // return fld +} + +// returns the field's actual name from the struct, when able to determine. +func (fe *fieldError) StructField() string { + // return fe.structField + return fe.structNs[len(fe.structNs)-int(fe.structfieldLen):] +} + +// Value returns the actual field's value in case needed for creating the error +// message +func (fe *fieldError) Value() interface{} { + return fe.value +} + +// Param returns the param value, in string form for comparison; this will +// also help with generating an error message +func (fe *fieldError) Param() string { + return fe.param +} + +// Kind returns the Field's reflect Kind +func (fe *fieldError) Kind() reflect.Kind { + return fe.kind +} + +// Type returns the Field's reflect Type +func (fe *fieldError) Type() reflect.Type { + return fe.typ +} + +// Error returns the fieldError's error message +func (fe *fieldError) Error() string { + return fmt.Sprintf(fieldErrMsg, fe.ns, fe.Field(), fe.tag) +} + +// Translate returns the FieldError's translated error +// from the provided 'ut.Translator' and registered 'TranslationFunc' +// +// NOTE: if no registered translation can be found, it returns the original +// untranslated error message. +func (fe *fieldError) Translate(ut ut.Translator) string { + + m, ok := fe.v.transTagFunc[ut] + if !ok { + return fe.Error() + } + + fn, ok := m[fe.tag] + if !ok { + return fe.Error() + } + + return fn(ut, fe) +} diff --git a/go-playground/validator/v10/field_level.go b/go-playground/validator/v10/field_level.go new file mode 100644 index 0000000..f0e2a9a --- /dev/null +++ b/go-playground/validator/v10/field_level.go @@ -0,0 +1,119 @@ +package validator + +import "reflect" + +// FieldLevel contains all the information and helper functions +// to validate a field +type FieldLevel interface { + // returns the top level struct, if any + Top() reflect.Value + + // returns the current fields parent struct, if any or + // the comparison value if called 'VarWithValue' + Parent() reflect.Value + + // returns current field for validation + Field() reflect.Value + + // returns the field's name with the tag + // name taking precedence over the fields actual name. + FieldName() string + + // returns the struct field's name + StructFieldName() string + + // returns param for validation against current field + Param() string + + // GetTag returns the current validations tag name + GetTag() string + + // ExtractType gets the actual underlying type of field value. + // It will dive into pointers, customTypes and return you the + // underlying value and it's kind. + ExtractType(field reflect.Value) (value reflect.Value, kind reflect.Kind, nullable bool) + + // traverses the parent struct to retrieve a specific field denoted by the provided namespace + // in the param and returns the field, field kind and whether is was successful in retrieving + // the field at all. + // + // NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field + // could not be retrieved because it didn't exist. + // + // Deprecated: Use GetStructFieldOK2() instead which also return if the value is nullable. + GetStructFieldOK() (reflect.Value, reflect.Kind, bool) + + // GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for + // the field and namespace allowing more extensibility for validators. + // + // Deprecated: Use GetStructFieldOKAdvanced2() instead which also return if the value is nullable. + GetStructFieldOKAdvanced(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) + + // traverses the parent struct to retrieve a specific field denoted by the provided namespace + // in the param and returns the field, field kind, if it's a nullable type and whether is was successful in retrieving + // the field at all. + // + // NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field + // could not be retrieved because it didn't exist. + GetStructFieldOK2() (reflect.Value, reflect.Kind, bool, bool) + + // GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for + // the field and namespace allowing more extensibility for validators. + GetStructFieldOKAdvanced2(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool, bool) +} + +var _ FieldLevel = new(validate) + +// Field returns current field for validation +func (v *validate) Field() reflect.Value { + return v.flField +} + +// FieldName returns the field's name with the tag +// name taking precedence over the fields actual name. +func (v *validate) FieldName() string { + return v.cf.altName +} + +// GetTag returns the current validations tag name +func (v *validate) GetTag() string { + return v.ct.tag +} + +// StructFieldName returns the struct field's name +func (v *validate) StructFieldName() string { + return v.cf.name +} + +// Param returns param for validation against current field +func (v *validate) Param() string { + return v.ct.param +} + +// GetStructFieldOK returns Param returns param for validation against current field +// +// Deprecated: Use GetStructFieldOK2() instead which also return if the value is nullable. +func (v *validate) GetStructFieldOK() (reflect.Value, reflect.Kind, bool) { + current, kind, _, found := v.getStructFieldOKInternal(v.slflParent, v.ct.param) + return current, kind, found +} + +// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for +// the field and namespace allowing more extensibility for validators. +// +// Deprecated: Use GetStructFieldOKAdvanced2() instead which also return if the value is nullable. +func (v *validate) GetStructFieldOKAdvanced(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) { + current, kind, _, found := v.GetStructFieldOKAdvanced2(val, namespace) + return current, kind, found +} + +// GetStructFieldOK returns Param returns param for validation against current field +func (v *validate) GetStructFieldOK2() (reflect.Value, reflect.Kind, bool, bool) { + return v.getStructFieldOKInternal(v.slflParent, v.ct.param) +} + +// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for +// the field and namespace allowing more extensibility for validators. +func (v *validate) GetStructFieldOKAdvanced2(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool, bool) { + return v.getStructFieldOKInternal(val, namespace) +} diff --git a/go-playground/validator/v10/go.sum b/go-playground/validator/v10/go.sum new file mode 100644 index 0000000..3dc4e15 --- /dev/null +++ b/go-playground/validator/v10/go.sum @@ -0,0 +1,48 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +gin-valid/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +gin-valid/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +gin-valid/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +gin-valid/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +gin-valid/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/go-playground/validator/v10/logo.png b/go-playground/validator/v10/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..355000f5247d50e979cf5db5de38188ef4649a34 GIT binary patch literal 13443 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 V^Rh-@K?jDw 0 + case reflect.Chan, reflect.Map, reflect.Slice, reflect.Array: + return field.Len() > 0 + case reflect.Ptr, reflect.Interface, reflect.Func: + return !field.IsNil() + default: + return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface() + } +} diff --git a/go-playground/validator/v10/non-standard/validators/notblank_test.go b/go-playground/validator/v10/non-standard/validators/notblank_test.go new file mode 100644 index 0000000..5ef53dd --- /dev/null +++ b/go-playground/validator/v10/non-standard/validators/notblank_test.go @@ -0,0 +1,65 @@ +package validators + +import ( + "testing" + + "gin-valid/go-playground/validator/v10" + "github.com/go-playground/assert/v2" +) + +type test struct { + String string `validate:"notblank"` + Array []int `validate:"notblank"` + Pointer *int `validate:"notblank"` + Number int `validate:"notblank"` + Interface interface{} `validate:"notblank"` + Func func() `validate:"notblank"` +} + +func TestNotBlank(t *testing.T) { + v := validator.New() + err := v.RegisterValidation("notblank", NotBlank) + assert.Equal(t, nil, err) + + // Errors + var x *int + invalid := test{ + String: " ", + Array: []int{}, + Pointer: x, + Number: 0, + Interface: nil, + Func: nil, + } + fieldsWithError := []string{ + "String", + "Array", + "Pointer", + "Number", + "Interface", + "Func", + } + + errors := v.Struct(invalid).(validator.ValidationErrors) + var fields []string + for _, err := range errors { + fields = append(fields, err.Field()) + } + + assert.Equal(t, fieldsWithError, fields) + + // No errors + y := 1 + x = &y + valid := test{ + String: "str", + Array: []int{1}, + Pointer: x, + Number: 1, + Interface: "value", + Func: func() {}, + } + + err = v.Struct(valid) + assert.Equal(t, nil, err) +} diff --git a/go-playground/validator/v10/regexes.go b/go-playground/validator/v10/regexes.go new file mode 100644 index 0000000..b741f4e --- /dev/null +++ b/go-playground/validator/v10/regexes.go @@ -0,0 +1,101 @@ +package validator + +import "regexp" + +const ( + alphaRegexString = "^[a-zA-Z]+$" + alphaNumericRegexString = "^[a-zA-Z0-9]+$" + alphaUnicodeRegexString = "^[\\p{L}]+$" + alphaUnicodeNumericRegexString = "^[\\p{L}\\p{N}]+$" + 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})$" + 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*\\)$" + hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$" + emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" + e164RegexString = "^\\+[1-9]?[0-9]{7,14}$" + base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" + base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$" + iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$" + iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$" + uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" + uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + uUIDRegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + uUID3RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-3[0-9a-fA-F]{3}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + 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}$" + aSCIIRegexString = "^[\x00-\x7F]*$" + printableASCIIRegexString = "^[\x20-\x7E]*$" + multibyteRegexString = "[^\x00-\x7F]" + dataURIRegexString = `^data:((?:\w+\/(?:([^;]|;[^;]).)+)?)` + 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 + ethAddressRegexString = `^0x[0-9a-fA-F]{40}$` + ethAddressUpperRegexString = `^0x[0-9A-F]{40}$` + ethAddressLowerRegexString = `^0x[0-9a-f]{40}$` + uRLEncodedRegexString = `(%[A-Fa-f0-9]{2})` + hTMLEncodedRegexString = `&#[x]?([0-9a-fA-F]{2})|(>)|(<)|(")|(&)+[;]?` + hTMLRegexString = `<[/]?([a-zA-Z]+).*?>` + splitParamsRegexString = `'[^']*'|\S+` +) + +var ( + alphaRegex = regexp.MustCompile(alphaRegexString) + alphaNumericRegex = regexp.MustCompile(alphaNumericRegexString) + alphaUnicodeRegex = regexp.MustCompile(alphaUnicodeRegexString) + alphaUnicodeNumericRegex = regexp.MustCompile(alphaUnicodeNumericRegexString) + numericRegex = regexp.MustCompile(numericRegexString) + numberRegex = regexp.MustCompile(numberRegexString) + hexadecimalRegex = regexp.MustCompile(hexadecimalRegexString) + hexcolorRegex = regexp.MustCompile(hexcolorRegexString) + rgbRegex = regexp.MustCompile(rgbRegexString) + rgbaRegex = regexp.MustCompile(rgbaRegexString) + hslRegex = regexp.MustCompile(hslRegexString) + hslaRegex = regexp.MustCompile(hslaRegexString) + e164Regex = regexp.MustCompile(e164RegexString) + emailRegex = regexp.MustCompile(emailRegexString) + base64Regex = regexp.MustCompile(base64RegexString) + base64URLRegex = regexp.MustCompile(base64URLRegexString) + iSBN10Regex = regexp.MustCompile(iSBN10RegexString) + iSBN13Regex = regexp.MustCompile(iSBN13RegexString) + uUID3Regex = regexp.MustCompile(uUID3RegexString) + uUID4Regex = regexp.MustCompile(uUID4RegexString) + uUID5Regex = regexp.MustCompile(uUID5RegexString) + uUIDRegex = regexp.MustCompile(uUIDRegexString) + uUID3RFC4122Regex = regexp.MustCompile(uUID3RFC4122RegexString) + uUID4RFC4122Regex = regexp.MustCompile(uUID4RFC4122RegexString) + uUID5RFC4122Regex = regexp.MustCompile(uUID5RFC4122RegexString) + uUIDRFC4122Regex = regexp.MustCompile(uUIDRFC4122RegexString) + aSCIIRegex = regexp.MustCompile(aSCIIRegexString) + printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString) + multibyteRegex = regexp.MustCompile(multibyteRegexString) + dataURIRegex = regexp.MustCompile(dataURIRegexString) + latitudeRegex = regexp.MustCompile(latitudeRegexString) + longitudeRegex = regexp.MustCompile(longitudeRegexString) + sSNRegex = regexp.MustCompile(sSNRegexString) + hostnameRegexRFC952 = regexp.MustCompile(hostnameRegexStringRFC952) + hostnameRegexRFC1123 = regexp.MustCompile(hostnameRegexStringRFC1123) + fqdnRegexRFC1123 = regexp.MustCompile(fqdnRegexStringRFC1123) + btcAddressRegex = regexp.MustCompile(btcAddressRegexString) + btcUpperAddressRegexBech32 = regexp.MustCompile(btcAddressUpperRegexStringBech32) + btcLowerAddressRegexBech32 = regexp.MustCompile(btcAddressLowerRegexStringBech32) + ethAddressRegex = regexp.MustCompile(ethAddressRegexString) + ethaddressRegexUpper = regexp.MustCompile(ethAddressUpperRegexString) + ethAddressRegexLower = regexp.MustCompile(ethAddressLowerRegexString) + uRLEncodedRegex = regexp.MustCompile(uRLEncodedRegexString) + hTMLEncodedRegex = regexp.MustCompile(hTMLEncodedRegexString) + hTMLRegex = regexp.MustCompile(hTMLRegexString) + splitParamsRegex = regexp.MustCompile(splitParamsRegexString) +) diff --git a/go-playground/validator/v10/struct_level.go b/go-playground/validator/v10/struct_level.go new file mode 100644 index 0000000..57691ee --- /dev/null +++ b/go-playground/validator/v10/struct_level.go @@ -0,0 +1,175 @@ +package validator + +import ( + "context" + "reflect" +) + +// StructLevelFunc accepts all values needed for struct level validation +type StructLevelFunc func(sl StructLevel) + +// StructLevelFuncCtx accepts all values needed for struct level validation +// but also allows passing of contextual validation information via context.Context. +type StructLevelFuncCtx func(ctx context.Context, sl StructLevel) + +// wrapStructLevelFunc wraps normal StructLevelFunc makes it compatible with StructLevelFuncCtx +func wrapStructLevelFunc(fn StructLevelFunc) StructLevelFuncCtx { + return func(ctx context.Context, sl StructLevel) { + fn(sl) + } +} + +// StructLevel contains all the information and helper functions +// to validate a struct +type StructLevel interface { + + // returns the main validation object, in case one wants to call validations internally. + // this is so you don't have to use anonymous functions to get access to the validate + // instance. + Validator() *Validate + + // returns the top level struct, if any + Top() reflect.Value + + // returns the current fields parent struct, if any + Parent() reflect.Value + + // returns the current struct. + Current() reflect.Value + + // ExtractType gets the actual underlying type of field value. + // It will dive into pointers, customTypes and return you the + // underlying value and its kind. + ExtractType(field reflect.Value) (value reflect.Value, kind reflect.Kind, nullable bool) + + // reports an error just by passing the field and tag information + // + // NOTES: + // + // fieldName and altName get appended to the existing namespace that + // validator is on. e.g. pass 'FirstName' or 'Names[0]' depending + // on the nesting + // + // tag can be an existing validation tag or just something you make up + // and process on the flip side it's up to you. + ReportError(field interface{}, fieldName, structFieldName string, tag, param string) + + // reports an error just by passing ValidationErrors + // + // NOTES: + // + // relativeNamespace and relativeActualNamespace get appended to the + // existing namespace that validator is on. + // e.g. pass 'User.FirstName' or 'Users[0].FirstName' depending + // on the nesting. most of the time they will be blank, unless you validate + // at a level lower the the current field depth + ReportValidationErrors(relativeNamespace, relativeActualNamespace string, errs ValidationErrors) +} + +var _ StructLevel = new(validate) + +// Top returns the top level struct +// +// NOTE: this can be the same as the current struct being validated +// if not is a nested struct. +// +// this is only called when within Struct and Field Level validation and +// should not be relied upon for an acurate value otherwise. +func (v *validate) Top() reflect.Value { + return v.top +} + +// Parent returns the current structs parent +// +// NOTE: this can be the same as the current struct being validated +// if not is a nested struct. +// +// this is only called when within Struct and Field Level validation and +// should not be relied upon for an acurate value otherwise. +func (v *validate) Parent() reflect.Value { + return v.slflParent +} + +// Current returns the current struct. +func (v *validate) Current() reflect.Value { + return v.slCurrent +} + +// Validator returns the main validation object, in case one want to call validations internally. +func (v *validate) Validator() *Validate { + return v.v +} + +// ExtractType gets the actual underlying type of field value. +func (v *validate) ExtractType(field reflect.Value) (reflect.Value, reflect.Kind, bool) { + return v.extractTypeInternal(field, false) +} + +// ReportError reports an error just by passing the field and tag information +func (v *validate) ReportError(field interface{}, fieldName, structFieldName, tag, param string) { + + fv, kind, _ := v.extractTypeInternal(reflect.ValueOf(field), false) + + if len(structFieldName) == 0 { + structFieldName = fieldName + } + + v.str1 = string(append(v.ns, fieldName...)) + + if v.v.hasTagNameFunc || fieldName != structFieldName { + v.str2 = string(append(v.actualNs, structFieldName...)) + } else { + v.str2 = v.str1 + } + + if kind == reflect.Invalid { + + v.errs = append(v.errs, + &fieldError{ + v: v.v, + tag: tag, + actualTag: tag, + ns: v.str1, + structNs: v.str2, + fieldLen: uint8(len(fieldName)), + structfieldLen: uint8(len(structFieldName)), + param: param, + kind: kind, + }, + ) + return + } + + v.errs = append(v.errs, + &fieldError{ + v: v.v, + tag: tag, + actualTag: tag, + ns: v.str1, + structNs: v.str2, + fieldLen: uint8(len(fieldName)), + structfieldLen: uint8(len(structFieldName)), + value: fv.Interface(), + param: param, + kind: kind, + typ: fv.Type(), + }, + ) +} + +// ReportValidationErrors reports ValidationErrors obtained from running validations within the Struct Level validation. +// +// NOTE: this function prepends the current namespace to the relative ones. +func (v *validate) ReportValidationErrors(relativeNamespace, relativeStructNamespace string, errs ValidationErrors) { + + var err *fieldError + + for i := 0; i < len(errs); i++ { + + err = errs[i].(*fieldError) + err.ns = string(append(append(v.ns, relativeNamespace...), err.ns...)) + err.structNs = string(append(append(v.actualNs, relativeStructNamespace...), err.structNs...)) + + v.errs = append(v.errs, err) + } +} diff --git a/go-playground/validator/v10/testdata/a.go b/go-playground/validator/v10/testdata/a.go new file mode 100644 index 0000000..69d29d3 --- /dev/null +++ b/go-playground/validator/v10/testdata/a.go @@ -0,0 +1 @@ +package testdata diff --git a/go-playground/validator/v10/translations.go b/go-playground/validator/v10/translations.go new file mode 100644 index 0000000..1ea55ac --- /dev/null +++ b/go-playground/validator/v10/translations.go @@ -0,0 +1,11 @@ +package validator + +import ut "gin-valid/go-playground/universal-translator" + +// TranslationFunc is the function type used to register or override +// custom translations +type TranslationFunc func(ut ut.Translator, fe FieldError) string + +// RegisterTranslationsFunc allows for registering of translations +// for a 'ut.Translator' for use within the 'TranslationFunc' +type RegisterTranslationsFunc func(ut ut.Translator) error diff --git a/go-playground/validator/v10/translations/en/en.go b/go-playground/validator/v10/translations/en/en.go new file mode 100644 index 0000000..c56466b --- /dev/null +++ b/go-playground/validator/v10/translations/en/en.go @@ -0,0 +1,1405 @@ +package en + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "gin-valid/go-playground/locales" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/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} is a required field", + override: false, + }, + { + tag: "len", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("len-string", "{0} must be {1} in length", false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} character", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} characters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("len-number", "{0} must be equal to {1}", false); err != nil { + return + } + + if err = ut.Add("len-items", "{0} must contain {1}", false); err != nil { + return + } + if err = ut.AddCardinal("len-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-items-item", "{0} items", 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} must be at least {1} in length", false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} character", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} characters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("min-number", "{0} must be {1} or greater", false); err != nil { + return + } + + if err = ut.Add("min-items", "{0} must contain at least {1}", false); err != nil { + return + } + if err = ut.AddCardinal("min-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-items-item", "{0} items", 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} must be a maximum of {1} in length", false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} character", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} characters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("max-number", "{0} must be {1} or less", false); err != nil { + return + } + + if err = ut.Add("max-items", "{0} must contain at maximum {1}", false); err != nil { + return + } + if err = ut.AddCardinal("max-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-items-item", "{0} items", 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} is not equal to {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} should not be equal to {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} must be less than {1} in length", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} character", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} characters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-number", "{0} must be less than {1}", false); err != nil { + return + } + + if err = ut.Add("lt-items", "{0} must contain less than {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} items", 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} must be at maximum {1} in length", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} character", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} characters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-number", "{0} must be {1} or less", false); err != nil { + return + } + + if err = ut.Add("lte-items", "{0} must contain at maximum {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} items", 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} must be greater than {1} in length", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} character", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} characters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-number", "{0} must be greater than {1}", false); err != nil { + return + } + + if err = ut.Add("gt-items", "{0} must contain more than {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-datetime", "{0} must be greater 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("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} must be at least {1} in length", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} character", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} characters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-number", "{0} must be {1} or greater", false); err != nil { + return + } + + if err = ut.Add("gte-items", "{0} must contain at least {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-datetime", "{0} must be greater 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("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} must be equal to {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} must be equal to {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} cannot be equal to {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} must be greater than {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} must be greater than or equal to {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} must be less than {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} must be less than or equal to {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} cannot be equal to {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} must be greater than {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} must be greater than or equal to {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} must be less than {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} must be less than or equal to {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} can only contain alphabetic characters", + override: false, + }, + { + tag: "alphanum", + translation: "{0} can only contain alphanumeric characters", + override: false, + }, + { + tag: "numeric", + translation: "{0} must be a valid numeric value", + override: false, + }, + { + tag: "number", + translation: "{0} must be a valid number", + override: false, + }, + { + tag: "hexadecimal", + translation: "{0} must be a valid hexadecimal", + override: false, + }, + { + tag: "hexcolor", + translation: "{0} must be a valid HEX color", + override: false, + }, + { + tag: "rgb", + translation: "{0} must be a valid RGB color", + override: false, + }, + { + tag: "rgba", + translation: "{0} must be a valid RGBA color", + override: false, + }, + { + tag: "hsl", + translation: "{0} must be a valid HSL color", + override: false, + }, + { + tag: "hsla", + translation: "{0} must be a valid HSLA color", + override: false, + }, + { + tag: "e164", + translation: "{0} must be a valid E.164 formatted phone number", + override: false, + }, + { + tag: "email", + translation: "{0} must be a valid email address", + override: false, + }, + { + tag: "url", + translation: "{0} must be a valid URL", + override: false, + }, + { + tag: "uri", + translation: "{0} must be a valid URI", + override: false, + }, + { + tag: "base64", + translation: "{0} must be a valid Base64 string", + override: false, + }, + { + tag: "contains", + translation: "{0} must contain the text '{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} must contain at least one of the following characters '{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} cannot contain the text '{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} cannot contain any of the following characters '{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} cannot contain the following '{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} must be a valid ISBN number", + override: false, + }, + { + tag: "isbn10", + translation: "{0} must be a valid ISBN-10 number", + override: false, + }, + { + tag: "isbn13", + translation: "{0} must be a valid ISBN-13 number", + override: false, + }, + { + tag: "uuid", + translation: "{0} must be a valid UUID", + override: false, + }, + { + tag: "uuid3", + translation: "{0} must be a valid version 3 UUID", + override: false, + }, + { + tag: "uuid4", + translation: "{0} must be a valid version 4 UUID", + override: false, + }, + { + tag: "uuid5", + translation: "{0} must be a valid version 5 UUID", + override: false, + }, + { + tag: "ascii", + translation: "{0} must contain only ascii characters", + override: false, + }, + { + tag: "printascii", + translation: "{0} must contain only printable ascii characters", + override: false, + }, + { + tag: "multibyte", + translation: "{0} must contain multibyte characters", + override: false, + }, + { + tag: "datauri", + translation: "{0} must contain a valid Data URI", + override: false, + }, + { + tag: "latitude", + translation: "{0} must contain valid latitude coordinates", + override: false, + }, + { + tag: "longitude", + translation: "{0} must contain a valid longitude coordinates", + override: false, + }, + { + tag: "ssn", + translation: "{0} must be a valid SSN number", + override: false, + }, + { + tag: "ipv4", + translation: "{0} must be a valid IPv4 address", + override: false, + }, + { + tag: "ipv6", + translation: "{0} must be a valid IPv6 address", + override: false, + }, + { + tag: "ip", + translation: "{0} must be a valid IP address", + override: false, + }, + { + tag: "cidr", + translation: "{0} must contain a valid CIDR notation", + override: false, + }, + { + tag: "cidrv4", + translation: "{0} must contain a valid CIDR notation for an IPv4 address", + override: false, + }, + { + tag: "cidrv6", + translation: "{0} must contain a valid CIDR notation for an IPv6 address", + override: false, + }, + { + tag: "tcp_addr", + translation: "{0} must be a valid TCP address", + override: false, + }, + { + tag: "tcp4_addr", + translation: "{0} must be a valid IPv4 TCP address", + override: false, + }, + { + tag: "tcp6_addr", + translation: "{0} must be a valid IPv6 TCP address", + override: false, + }, + { + tag: "udp_addr", + translation: "{0} must be a valid UDP address", + override: false, + }, + { + tag: "udp4_addr", + translation: "{0} must be a valid IPv4 UDP address", + override: false, + }, + { + tag: "udp6_addr", + translation: "{0} must be a valid IPv6 UDP address", + override: false, + }, + { + tag: "ip_addr", + translation: "{0} must be a resolvable IP address", + override: false, + }, + { + tag: "ip4_addr", + translation: "{0} must be a resolvable IPv4 address", + override: false, + }, + { + tag: "ip6_addr", + translation: "{0} must be a resolvable IPv6 address", + override: false, + }, + { + tag: "unix_addr", + translation: "{0} must be a resolvable UNIX address", + override: false, + }, + { + tag: "mac", + translation: "{0} must contain a valid MAC address", + override: false, + }, + { + tag: "unique", + translation: "{0} must contain unique values", + override: false, + }, + { + tag: "iscolor", + translation: "{0} must be a valid color", + override: false, + }, + { + tag: "oneof", + translation: "{0} must be one of [{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 + }, + }, + { + tag: "json", + translation: "{0} must be a valid json string", + override: false, + }, + { + tag: "lowercase", + translation: "{0} must be a lowercase string", + override: false, + }, + { + tag: "uppercase", + translation: "{0} must be an uppercase string", + override: false, + }, + { + tag: "datetime", + translation: "{0} does not match the {1} format", + override: false, + customTransFunc: func(ut ut.Translator, fe validator.FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field(), fe.Param()) + if err != nil { + log.Printf("warning: error translating FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + } + + for _, t := range translations { + + 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/go-playground/validator/v10/translations/en/en_test.go b/go-playground/validator/v10/translations/en/en_test.go new file mode 100644 index 0000000..9b6be95 --- /dev/null +++ b/go-playground/validator/v10/translations/en/en_test.go @@ -0,0 +1,676 @@ +package en + +import ( + "testing" + "time" + + english "gin-valid/go-playground/locales/en" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/go-playground/validator/v10" + . "github.com/go-playground/assert/v2" +) + +func TestTranslations(t *testing.T) { + + eng := english.New() + uni := ut.New(eng, eng) + 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"` + 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"` + JSONString string `validate:"json"` + LowercaseString string `validate:"lowercase"` + UppercaseString string `validate:"uppercase"` + Datetime string `validate:"datetime=2006-01-02"` + } + + 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" + + 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 must be a valid color", + }, + { + ns: "Test.MAC", + expected: "MAC must contain a valid MAC address", + }, + { + ns: "Test.IPAddr", + expected: "IPAddr must be a resolvable IP address", + }, + { + ns: "Test.IPAddrv4", + expected: "IPAddrv4 must be a resolvable IPv4 address", + }, + { + ns: "Test.IPAddrv6", + expected: "IPAddrv6 must be a resolvable IPv6 address", + }, + { + ns: "Test.UDPAddr", + expected: "UDPAddr must be a valid UDP address", + }, + { + ns: "Test.UDPAddrv4", + expected: "UDPAddrv4 must be a valid IPv4 UDP address", + }, + { + ns: "Test.UDPAddrv6", + expected: "UDPAddrv6 must be a valid IPv6 UDP address", + }, + { + ns: "Test.TCPAddr", + expected: "TCPAddr must be a valid TCP address", + }, + { + ns: "Test.TCPAddrv4", + expected: "TCPAddrv4 must be a valid IPv4 TCP address", + }, + { + ns: "Test.TCPAddrv6", + expected: "TCPAddrv6 must be a valid IPv6 TCP address", + }, + { + ns: "Test.CIDR", + expected: "CIDR must contain a valid CIDR notation", + }, + { + ns: "Test.CIDRv4", + expected: "CIDRv4 must contain a valid CIDR notation for an IPv4 address", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6 must contain a valid CIDR notation for an IPv6 address", + }, + { + ns: "Test.SSN", + expected: "SSN must be a valid SSN number", + }, + { + ns: "Test.IP", + expected: "IP must be a valid IP address", + }, + { + ns: "Test.IPv4", + expected: "IPv4 must be a valid IPv4 address", + }, + { + ns: "Test.IPv6", + expected: "IPv6 must be a valid IPv6 address", + }, + { + ns: "Test.DataURI", + expected: "DataURI must contain a valid Data URI", + }, + { + ns: "Test.Latitude", + expected: "Latitude must contain valid latitude coordinates", + }, + { + ns: "Test.Longitude", + expected: "Longitude must contain a valid longitude coordinates", + }, + { + ns: "Test.MultiByte", + expected: "MultiByte must contain multibyte characters", + }, + { + ns: "Test.ASCII", + expected: "ASCII must contain only ascii characters", + }, + { + ns: "Test.PrintableASCII", + expected: "PrintableASCII must contain only printable ascii characters", + }, + { + ns: "Test.UUID", + expected: "UUID must be a valid UUID", + }, + { + ns: "Test.UUID3", + expected: "UUID3 must be a valid version 3 UUID", + }, + { + ns: "Test.UUID4", + expected: "UUID4 must be a valid version 4 UUID", + }, + { + ns: "Test.UUID5", + expected: "UUID5 must be a valid version 5 UUID", + }, + { + ns: "Test.ISBN", + expected: "ISBN must be a valid ISBN number", + }, + { + ns: "Test.ISBN10", + expected: "ISBN10 must be a valid ISBN-10 number", + }, + { + ns: "Test.ISBN13", + expected: "ISBN13 must be a valid ISBN-13 number", + }, + { + ns: "Test.Excludes", + expected: "Excludes cannot contain the text 'text'", + }, + { + ns: "Test.ExcludesAll", + expected: "ExcludesAll cannot contain any of the following characters '!@#$'", + }, + { + ns: "Test.ExcludesRune", + expected: "ExcludesRune cannot contain the following '☻'", + }, + { + ns: "Test.ContainsAny", + expected: "ContainsAny must contain at least one of the following characters '!@#$'", + }, + { + ns: "Test.Contains", + expected: "Contains must contain the text 'purpose'", + }, + { + ns: "Test.Base64", + expected: "Base64 must be a valid Base64 string", + }, + { + ns: "Test.Email", + expected: "Email must be a valid email address", + }, + { + ns: "Test.URL", + expected: "URL must be a valid URL", + }, + { + ns: "Test.URI", + expected: "URI must be a valid URI", + }, + { + ns: "Test.RGBColorString", + expected: "RGBColorString must be a valid RGB color", + }, + { + ns: "Test.RGBAColorString", + expected: "RGBAColorString must be a valid RGBA color", + }, + { + ns: "Test.HSLColorString", + expected: "HSLColorString must be a valid HSL color", + }, + { + ns: "Test.HSLAColorString", + expected: "HSLAColorString must be a valid HSLA color", + }, + { + ns: "Test.HexadecimalString", + expected: "HexadecimalString must be a valid hexadecimal", + }, + { + ns: "Test.HexColorString", + expected: "HexColorString must be a valid HEX color", + }, + { + ns: "Test.NumberString", + expected: "NumberString must be a valid number", + }, + { + ns: "Test.NumericString", + expected: "NumericString must be a valid numeric value", + }, + { + ns: "Test.AlphanumString", + expected: "AlphanumString can only contain alphanumeric characters", + }, + { + ns: "Test.AlphaString", + expected: "AlphaString can only contain alphabetic characters", + }, + { + ns: "Test.LtFieldString", + expected: "LtFieldString must be less than MaxString", + }, + { + ns: "Test.LteFieldString", + expected: "LteFieldString must be less than or equal to MaxString", + }, + { + ns: "Test.GtFieldString", + expected: "GtFieldString must be greater than MaxString", + }, + { + ns: "Test.GteFieldString", + expected: "GteFieldString must be greater than or equal to MaxString", + }, + { + ns: "Test.NeFieldString", + expected: "NeFieldString cannot be equal to EqFieldString", + }, + { + ns: "Test.LtCSFieldString", + expected: "LtCSFieldString must be less than Inner.LtCSFieldString", + }, + { + ns: "Test.LteCSFieldString", + expected: "LteCSFieldString must be less than or equal to Inner.LteCSFieldString", + }, + { + ns: "Test.GtCSFieldString", + expected: "GtCSFieldString must be greater than Inner.GtCSFieldString", + }, + { + ns: "Test.GteCSFieldString", + expected: "GteCSFieldString must be greater than or equal to Inner.GteCSFieldString", + }, + { + ns: "Test.NeCSFieldString", + expected: "NeCSFieldString cannot be equal to Inner.NeCSFieldString", + }, + { + ns: "Test.EqCSFieldString", + expected: "EqCSFieldString must be equal to Inner.EqCSFieldString", + }, + { + ns: "Test.EqFieldString", + expected: "EqFieldString must be equal to MaxString", + }, + { + ns: "Test.GteString", + expected: "GteString must be at least 3 characters in length", + }, + { + ns: "Test.GteNumber", + expected: "GteNumber must be 5.56 or greater", + }, + { + ns: "Test.GteMultiple", + expected: "GteMultiple must contain at least 2 items", + }, + { + ns: "Test.GteTime", + expected: "GteTime must be greater than or equal to the current Date & Time", + }, + { + ns: "Test.GtString", + expected: "GtString must be greater than 3 characters in length", + }, + { + ns: "Test.GtNumber", + expected: "GtNumber must be greater than 5.56", + }, + { + ns: "Test.GtMultiple", + expected: "GtMultiple must contain more than 2 items", + }, + { + ns: "Test.GtTime", + expected: "GtTime must be greater than the current Date & Time", + }, + { + ns: "Test.LteString", + expected: "LteString must be at maximum 3 characters in length", + }, + { + ns: "Test.LteNumber", + expected: "LteNumber must be 5.56 or less", + }, + { + ns: "Test.LteMultiple", + expected: "LteMultiple must contain at maximum 2 items", + }, + { + ns: "Test.LteTime", + expected: "LteTime must be less than or equal to the current Date & Time", + }, + { + ns: "Test.LtString", + expected: "LtString must be less than 3 characters in length", + }, + { + ns: "Test.LtNumber", + expected: "LtNumber must be less than 5.56", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultiple must contain less than 2 items", + }, + { + ns: "Test.LtTime", + expected: "LtTime must be less than the current Date & Time", + }, + { + ns: "Test.NeString", + expected: "NeString should not be equal to ", + }, + { + ns: "Test.NeNumber", + expected: "NeNumber should not be equal to 0.00", + }, + { + ns: "Test.NeMultiple", + expected: "NeMultiple should not be equal to 0", + }, + { + ns: "Test.EqString", + expected: "EqString is not equal to 3", + }, + { + ns: "Test.EqNumber", + expected: "EqNumber is not equal to 2.33", + }, + { + ns: "Test.EqMultiple", + expected: "EqMultiple is not equal to 7", + }, + { + ns: "Test.MaxString", + expected: "MaxString must be a maximum of 3 characters in length", + }, + { + ns: "Test.MaxNumber", + expected: "MaxNumber must be 1,113.00 or less", + }, + { + ns: "Test.MaxMultiple", + expected: "MaxMultiple must contain at maximum 7 items", + }, + { + ns: "Test.MinString", + expected: "MinString must be at least 1 character in length", + }, + { + ns: "Test.MinNumber", + expected: "MinNumber must be 1,113.00 or greater", + }, + { + ns: "Test.MinMultiple", + expected: "MinMultiple must contain at least 7 items", + }, + { + ns: "Test.LenString", + expected: "LenString must be 1 character in length", + }, + { + ns: "Test.LenNumber", + expected: "LenNumber must be equal to 1,113.00", + }, + { + ns: "Test.LenMultiple", + expected: "LenMultiple must contain 7 items", + }, + { + ns: "Test.RequiredString", + expected: "RequiredString is a required field", + }, + { + ns: "Test.RequiredNumber", + expected: "RequiredNumber is a required field", + }, + { + ns: "Test.RequiredMultiple", + expected: "RequiredMultiple is a required field", + }, + { + ns: "Test.StrPtrMinLen", + expected: "StrPtrMinLen must be at least 10 characters in length", + }, + { + ns: "Test.StrPtrMaxLen", + expected: "StrPtrMaxLen must be a maximum of 1 character in length", + }, + { + ns: "Test.StrPtrLen", + expected: "StrPtrLen must be 2 characters in length", + }, + { + ns: "Test.StrPtrLt", + expected: "StrPtrLt must be less than 1 character in length", + }, + { + ns: "Test.StrPtrLte", + expected: "StrPtrLte must be at maximum 1 character in length", + }, + { + ns: "Test.StrPtrGt", + expected: "StrPtrGt must be greater than 10 characters in length", + }, + { + ns: "Test.StrPtrGte", + expected: "StrPtrGte must be at least 10 characters in length", + }, + { + ns: "Test.OneOfString", + expected: "OneOfString must be one of [red green]", + }, + { + ns: "Test.OneOfInt", + expected: "OneOfInt must be one of [5 63]", + }, + { + ns: "Test.UniqueSlice", + expected: "UniqueSlice must contain unique values", + }, + { + ns: "Test.UniqueArray", + expected: "UniqueArray must contain unique values", + }, + { + ns: "Test.UniqueMap", + expected: "UniqueMap must contain unique values", + }, + { + ns: "Test.JSONString", + expected: "JSONString must be a valid json string", + }, + { + ns: "Test.LowercaseString", + expected: "LowercaseString must be a lowercase string", + }, + { + ns: "Test.UppercaseString", + expected: "UppercaseString must be an uppercase string", + }, + { + ns: "Test.Datetime", + expected: "Datetime does not match the 2006-01-02 format", + }, + } + + for _, tt := range tests { + + 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)) + } + +} diff --git a/go-playground/validator/v10/translations/es/es.go b/go-playground/validator/v10/translations/es/es.go new file mode 100644 index 0000000..7722aac --- /dev/null +++ b/go-playground/validator/v10/translations/es/es.go @@ -0,0 +1,1375 @@ +package es + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "gin-valid/go-playground/locales" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/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} es un campo requerido", + override: false, + }, + { + tag: "len", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("len-string", "{0} debe tener {1} de longitud", false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} carácter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("len-number", "{0} debe ser igual a {1}", false); err != nil { + return + } + + if err = ut.Add("len-items", "{0} debe contener {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} elementos", 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} debe tener al menos {1} de longitud", false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} carácter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("min-number", "{0} debe ser {1} o más", false); err != nil { + return + } + + if err = ut.Add("min-items", "{0} debe contener al menos {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} elementos", 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} debe tener un máximo de {1} de longitud", false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} carácter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("max-number", "{0} debe ser {1} o menos", false); err != nil { + return + } + + if err = ut.Add("max-items", "{0} debe contener como máximo {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} elementos", 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} no es igual a {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} no debería ser igual a {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} debe tener menos de {1} de longitud", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} carácter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-number", "{0} debe ser menos de {1}", false); err != nil { + return + } + + if err = ut.Add("lt-items", "{0} debe contener menos de {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} elementos", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-datetime", "{0} debe ser antes de la fecha y hora actual", 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} debe tener un máximo de {1} de longitud", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} carácter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-number", "{0} debe ser {1} o menos", false); err != nil { + return + } + + if err = ut.Add("lte-items", "{0} debe contener como máximo {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} elementos", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-datetime", "{0} debe ser antes o durante la fecha y hora actual", 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} debe ser mayor que {1} en longitud", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} carácter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-number", "{0} debe ser mayor que {1}", false); err != nil { + return + } + + if err = ut.Add("gt-items", "{0} debe contener más de {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} elementos", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-datetime", "{0} debe ser después de la fecha y hora actual", 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} debe tener al menos {1} de longitud", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} carácter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-number", "{0} debe ser {1} o mayor", false); err != nil { + return + } + + if err = ut.Add("gte-items", "{0} debe contener al menos {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} elementos", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-datetime", "{0} debe ser después o durante la fecha y hora actuales", 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} debe ser igual a {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} debe ser igual a {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} no puede ser igual a {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} debe ser mayor que {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} debe ser mayor o igual a {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} debe ser menor que {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} debe ser menor o igual a {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} no puede ser igual a {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} debe ser mayor que {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} debe ser mayor o igual a {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} debe ser menor que {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} debe ser menor o igual a {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} sólo puede contener caracteres alfabéticos", + override: false, + }, + { + tag: "alphanum", + translation: "{0} sólo puede contener caracteres alfanuméricos", + override: false, + }, + { + tag: "numeric", + translation: "{0} debe ser un valor numérico válido", + override: false, + }, + { + tag: "number", + translation: "{0} debe ser un número válido", + override: false, + }, + { + tag: "hexadecimal", + translation: "{0} debe ser un hexadecimal válido", + override: false, + }, + { + tag: "hexcolor", + translation: "{0} debe ser un color HEX válido", + override: false, + }, + { + tag: "rgb", + translation: "{0} debe ser un color RGB válido", + override: false, + }, + { + tag: "rgba", + translation: "{0} debe ser un color RGBA válido", + override: false, + }, + { + tag: "hsl", + translation: "{0} debe ser un color HSL válido", + override: false, + }, + { + tag: "hsla", + translation: "{0} debe ser un color HSL válido", + override: false, + }, + { + tag: "e164", + translation: "{0} debe ser un número de teléfono válido con formato E.164", + override: false, + }, + { + tag: "email", + translation: "{0} debe ser una dirección de correo electrónico válida", + override: false, + }, + { + tag: "url", + translation: "{0} debe ser un URL válido", + override: false, + }, + { + tag: "uri", + translation: "{0} debe ser una URI válida", + override: false, + }, + { + tag: "base64", + translation: "{0} debe ser una cadena de Base64 válida", + override: false, + }, + { + tag: "contains", + translation: "{0} debe contener el texto '{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} debe contener al menos uno de los siguientes caracteres '{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} no puede contener el texto '{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} no puede contener ninguno de los siguientes caracteres '{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} no puede contener lo siguiente '{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} debe ser un número ISBN válido", + override: false, + }, + { + tag: "isbn10", + translation: "{0} debe ser un número ISBN-10 válido", + override: false, + }, + { + tag: "isbn13", + translation: "{0} debe ser un número ISBN-13 válido", + override: false, + }, + { + tag: "uuid", + translation: "{0} debe ser un UUID válido", + override: false, + }, + { + tag: "uuid3", + translation: "{0} debe ser una versión válida 3 UUID", + override: false, + }, + { + tag: "uuid4", + translation: "{0} debe ser una versión válida 4 UUID", + override: false, + }, + { + tag: "uuid5", + translation: "{0} debe ser una versión válida 5 UUID", + override: false, + }, + { + tag: "ascii", + translation: "{0} debe contener sólo caracteres ascii", + override: false, + }, + { + tag: "printascii", + translation: "{0} debe contener sólo caracteres ASCII imprimibles", + override: false, + }, + { + tag: "multibyte", + translation: "{0} debe contener caracteres multibyte", + override: false, + }, + { + tag: "datauri", + translation: "{0} debe contener un URI de datos válido", + override: false, + }, + { + tag: "latitude", + translation: "{0} debe contener coordenadas de latitud válidas", + override: false, + }, + { + tag: "longitude", + translation: "{0} debe contener unas coordenadas de longitud válidas", + override: false, + }, + { + tag: "ssn", + translation: "{0} debe ser un número válido de SSN", + override: false, + }, + { + tag: "ipv4", + translation: "{0} debe ser una dirección IPv4 válida", + override: false, + }, + { + tag: "ipv6", + translation: "{0} debe ser una dirección IPv6 válida", + override: false, + }, + { + tag: "ip", + translation: "{0} debe ser una dirección IP válida", + override: false, + }, + { + tag: "cidr", + translation: "{0} debe contener una anotación válida del CIDR", + override: false, + }, + { + tag: "cidrv4", + translation: "{0} debe contener una notación CIDR válida para una dirección IPv4", + override: false, + }, + { + tag: "cidrv6", + translation: "{0} debe contener una notación CIDR válida para una dirección IPv6", + override: false, + }, + { + tag: "tcp_addr", + translation: "{0} debe ser una dirección TCP válida", + override: false, + }, + { + tag: "tcp4_addr", + translation: "{0} debe ser una dirección IPv4 TCP válida", + override: false, + }, + { + tag: "tcp6_addr", + translation: "{0} debe ser una dirección IPv6 TCP válida", + override: false, + }, + { + tag: "udp_addr", + translation: "{0} debe ser una dirección UDP válida", + override: false, + }, + { + tag: "udp4_addr", + translation: "{0} debe ser una dirección IPv4 UDP válida", + override: false, + }, + { + tag: "udp6_addr", + translation: "{0} debe ser una dirección IPv6 UDP válida", + override: false, + }, + { + tag: "ip_addr", + translation: "{0} debe ser una dirección IP resoluble", + override: false, + }, + { + tag: "ip4_addr", + translation: "{0} debe ser una dirección IPv4 resoluble", + override: false, + }, + { + tag: "ip6_addr", + translation: "{0} debe ser una dirección IPv6 resoluble", + override: false, + }, + { + tag: "unix_addr", + translation: "{0} debe ser una dirección UNIX resoluble", + override: false, + }, + { + tag: "mac", + translation: "{0} debe contener una dirección MAC válida", + override: false, + }, + { + tag: "unique", + translation: "{0} debe contener valores únicos", + override: false, + }, + { + tag: "iscolor", + translation: "{0} debe ser un color válido", + override: false, + }, + { + tag: "oneof", + translation: "{0} debe ser uno de [{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/go-playground/validator/v10/translations/es/es_test.go b/go-playground/validator/v10/translations/es/es_test.go new file mode 100644 index 0000000..5985c38 --- /dev/null +++ b/go-playground/validator/v10/translations/es/es_test.go @@ -0,0 +1,652 @@ +package es + +import ( + "testing" + "time" + + spanish "gin-valid/go-playground/locales/es" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/go-playground/validator/v10" + . "github.com/go-playground/assert/v2" +) + +func TestTranslations(t *testing.T) { + + spa := spanish.New() + uni := ut.New(spa, spa) + trans, _ := uni.GetTranslator("es") + + 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 debe ser un color válido", + }, + { + ns: "Test.MAC", + expected: "MAC debe contener una dirección MAC válida", + }, + { + ns: "Test.IPAddr", + expected: "IPAddr debe ser una dirección IP resoluble", + }, + { + ns: "Test.IPAddrv4", + expected: "IPAddrv4 debe ser una dirección IPv4 resoluble", + }, + { + ns: "Test.IPAddrv6", + expected: "IPAddrv6 debe ser una dirección IPv6 resoluble", + }, + { + ns: "Test.UDPAddr", + expected: "UDPAddr debe ser una dirección UDP válida", + }, + { + ns: "Test.UDPAddrv4", + expected: "UDPAddrv4 debe ser una dirección IPv4 UDP válida", + }, + { + ns: "Test.UDPAddrv6", + expected: "UDPAddrv6 debe ser una dirección IPv6 UDP válida", + }, + { + ns: "Test.TCPAddr", + expected: "TCPAddr debe ser una dirección TCP válida", + }, + { + ns: "Test.TCPAddrv4", + expected: "TCPAddrv4 debe ser una dirección IPv4 TCP válida", + }, + { + ns: "Test.TCPAddrv6", + expected: "TCPAddrv6 debe ser una dirección IPv6 TCP válida", + }, + { + ns: "Test.CIDR", + expected: "CIDR debe contener una anotación válida del CIDR", + }, + { + ns: "Test.CIDRv4", + expected: "CIDRv4 debe contener una notación CIDR válida para una dirección IPv4", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6 debe contener una notación CIDR válida para una dirección IPv6", + }, + { + ns: "Test.SSN", + expected: "SSN debe ser un número válido de SSN", + }, + { + ns: "Test.IP", + expected: "IP debe ser una dirección IP válida", + }, + { + ns: "Test.IPv4", + expected: "IPv4 debe ser una dirección IPv4 válida", + }, + { + ns: "Test.IPv6", + expected: "IPv6 debe ser una dirección IPv6 válida", + }, + { + ns: "Test.DataURI", + expected: "DataURI debe contener un URI de datos válido", + }, + { + ns: "Test.Latitude", + expected: "Latitude debe contener coordenadas de latitud válidas", + }, + { + ns: "Test.Longitude", + expected: "Longitude debe contener unas coordenadas de longitud válidas", + }, + { + ns: "Test.MultiByte", + expected: "MultiByte debe contener caracteres multibyte", + }, + { + ns: "Test.ASCII", + expected: "ASCII debe contener sólo caracteres ascii", + }, + { + ns: "Test.PrintableASCII", + expected: "PrintableASCII debe contener sólo caracteres ASCII imprimibles", + }, + { + ns: "Test.UUID", + expected: "UUID debe ser un UUID válido", + }, + { + ns: "Test.UUID3", + expected: "UUID3 debe ser una versión válida 3 UUID", + }, + { + ns: "Test.UUID4", + expected: "UUID4 debe ser una versión válida 4 UUID", + }, + { + ns: "Test.UUID5", + expected: "UUID5 debe ser una versión válida 5 UUID", + }, + { + ns: "Test.ISBN", + expected: "ISBN debe ser un número ISBN válido", + }, + { + ns: "Test.ISBN10", + expected: "ISBN10 debe ser un número ISBN-10 válido", + }, + { + ns: "Test.ISBN13", + expected: "ISBN13 debe ser un número ISBN-13 válido", + }, + { + ns: "Test.Excludes", + expected: "Excludes no puede contener el texto 'text'", + }, + { + ns: "Test.ExcludesAll", + expected: "ExcludesAll no puede contener ninguno de los siguientes caracteres '!@#$'", + }, + { + ns: "Test.ExcludesRune", + expected: "ExcludesRune no puede contener lo siguiente '☻'", + }, + { + ns: "Test.ContainsAny", + expected: "ContainsAny debe contener al menos uno de los siguientes caracteres '!@#$'", + }, + { + ns: "Test.Contains", + expected: "Contains debe contener el texto 'purpose'", + }, + { + ns: "Test.Base64", + expected: "Base64 debe ser una cadena de Base64 válida", + }, + { + ns: "Test.Email", + expected: "Email debe ser una dirección de correo electrónico válida", + }, + { + ns: "Test.URL", + expected: "URL debe ser un URL válido", + }, + { + ns: "Test.URI", + expected: "URI debe ser una URI válida", + }, + { + ns: "Test.RGBColorString", + expected: "RGBColorString debe ser un color RGB válido", + }, + { + ns: "Test.RGBAColorString", + expected: "RGBAColorString debe ser un color RGBA válido", + }, + { + ns: "Test.HSLColorString", + expected: "HSLColorString debe ser un color HSL válido", + }, + { + ns: "Test.HSLAColorString", + expected: "HSLAColorString debe ser un color HSL válido", + }, + { + ns: "Test.HexadecimalString", + expected: "HexadecimalString debe ser un hexadecimal válido", + }, + { + ns: "Test.HexColorString", + expected: "HexColorString debe ser un color HEX válido", + }, + { + ns: "Test.NumberString", + expected: "NumberString debe ser un número válido", + }, + { + ns: "Test.NumericString", + expected: "NumericString debe ser un valor numérico válido", + }, + { + ns: "Test.AlphanumString", + expected: "AlphanumString sólo puede contener caracteres alfanuméricos", + }, + { + ns: "Test.AlphaString", + expected: "AlphaString sólo puede contener caracteres alfabéticos", + }, + { + ns: "Test.LtFieldString", + expected: "LtFieldString debe ser menor que MaxString", + }, + { + ns: "Test.LteFieldString", + expected: "LteFieldString debe ser menor o igual a MaxString", + }, + { + ns: "Test.GtFieldString", + expected: "GtFieldString debe ser mayor que MaxString", + }, + { + ns: "Test.GteFieldString", + expected: "GteFieldString debe ser mayor o igual a MaxString", + }, + { + ns: "Test.NeFieldString", + expected: "NeFieldString no puede ser igual a EqFieldString", + }, + { + ns: "Test.LtCSFieldString", + expected: "LtCSFieldString debe ser menor que Inner.LtCSFieldString", + }, + { + ns: "Test.LteCSFieldString", + expected: "LteCSFieldString debe ser menor o igual a Inner.LteCSFieldString", + }, + { + ns: "Test.GtCSFieldString", + expected: "GtCSFieldString debe ser mayor que Inner.GtCSFieldString", + }, + { + ns: "Test.GteCSFieldString", + expected: "GteCSFieldString debe ser mayor o igual a Inner.GteCSFieldString", + }, + { + ns: "Test.NeCSFieldString", + expected: "NeCSFieldString no puede ser igual a Inner.NeCSFieldString", + }, + { + ns: "Test.EqCSFieldString", + expected: "EqCSFieldString debe ser igual a Inner.EqCSFieldString", + }, + { + ns: "Test.EqFieldString", + expected: "EqFieldString debe ser igual a MaxString", + }, + { + ns: "Test.GteString", + expected: "GteString debe tener al menos 3 caracteres de longitud", + }, + { + ns: "Test.GteNumber", + expected: "GteNumber debe ser 5,56 o mayor", + }, + { + ns: "Test.GteMultiple", + expected: "GteMultiple debe contener al menos 2 elementos", + }, + { + ns: "Test.GteTime", + expected: "GteTime debe ser después o durante la fecha y hora actuales", + }, + { + ns: "Test.GtString", + expected: "GtString debe ser mayor que 3 caracteres en longitud", + }, + { + ns: "Test.GtNumber", + expected: "GtNumber debe ser mayor que 5,56", + }, + { + ns: "Test.GtMultiple", + expected: "GtMultiple debe contener más de 2 elementos", + }, + { + ns: "Test.GtTime", + expected: "GtTime debe ser después de la fecha y hora actual", + }, + { + ns: "Test.LteString", + expected: "LteString debe tener un máximo de 3 caracteres de longitud", + }, + { + ns: "Test.LteNumber", + expected: "LteNumber debe ser 5,56 o menos", + }, + { + ns: "Test.LteMultiple", + expected: "LteMultiple debe contener como máximo 2 elementos", + }, + { + ns: "Test.LteTime", + expected: "LteTime debe ser antes o durante la fecha y hora actual", + }, + { + ns: "Test.LtString", + expected: "LtString debe tener menos de 3 caracteres de longitud", + }, + { + ns: "Test.LtNumber", + expected: "LtNumber debe ser menos de 5,56", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultiple debe contener menos de 2 elementos", + }, + { + ns: "Test.LtTime", + expected: "LtTime debe ser antes de la fecha y hora actual", + }, + { + ns: "Test.NeString", + expected: "NeString no debería ser igual a ", + }, + { + ns: "Test.NeNumber", + expected: "NeNumber no debería ser igual a 0.00", + }, + { + ns: "Test.NeMultiple", + expected: "NeMultiple no debería ser igual a 0", + }, + { + ns: "Test.EqString", + expected: "EqString no es igual a 3", + }, + { + ns: "Test.EqNumber", + expected: "EqNumber no es igual a 2.33", + }, + { + ns: "Test.EqMultiple", + expected: "EqMultiple no es igual a 7", + }, + { + ns: "Test.MaxString", + expected: "MaxString debe tener un máximo de 3 caracteres de longitud", + }, + { + ns: "Test.MaxNumber", + expected: "MaxNumber debe ser 1.113,00 o menos", + }, + { + ns: "Test.MaxMultiple", + expected: "MaxMultiple debe contener como máximo 7 elementos", + }, + { + ns: "Test.MinString", + expected: "MinString debe tener al menos 1 carácter de longitud", + }, + { + ns: "Test.MinNumber", + expected: "MinNumber debe ser 1.113,00 o más", + }, + { + ns: "Test.MinMultiple", + expected: "MinMultiple debe contener al menos 7 elementos", + }, + { + ns: "Test.LenString", + expected: "LenString debe tener 1 carácter de longitud", + }, + { + ns: "Test.LenNumber", + expected: "LenNumber debe ser igual a 1.113,00", + }, + { + ns: "Test.LenMultiple", + expected: "LenMultiple debe contener 7 elementos", + }, + { + ns: "Test.RequiredString", + expected: "RequiredString es un campo requerido", + }, + { + ns: "Test.RequiredNumber", + expected: "RequiredNumber es un campo requerido", + }, + { + ns: "Test.RequiredMultiple", + expected: "RequiredMultiple es un campo requerido", + }, + { + ns: "Test.StrPtrMinLen", + expected: "StrPtrMinLen debe tener al menos 10 caracteres de longitud", + }, + { + ns: "Test.StrPtrMaxLen", + expected: "StrPtrMaxLen debe tener un máximo de 1 carácter de longitud", + }, + { + ns: "Test.StrPtrLen", + expected: "StrPtrLen debe tener 2 caracteres de longitud", + }, + { + ns: "Test.StrPtrLt", + expected: "StrPtrLt debe tener menos de 1 carácter de longitud", + }, + { + ns: "Test.StrPtrLte", + expected: "StrPtrLte debe tener un máximo de 1 carácter de longitud", + }, + { + ns: "Test.StrPtrGt", + expected: "StrPtrGt debe ser mayor que 10 caracteres en longitud", + }, + { + ns: "Test.StrPtrGte", + expected: "StrPtrGte debe tener al menos 10 caracteres de longitud", + }, + { + ns: "Test.OneOfString", + expected: "OneOfString debe ser uno de [red green]", + }, + { + ns: "Test.OneOfInt", + expected: "OneOfInt debe ser uno de [5 63]", + }, + { + ns: "Test.UniqueSlice", + expected: "UniqueSlice debe contener valores únicos", + }, + { + ns: "Test.UniqueArray", + expected: "UniqueArray debe contener valores únicos", + }, + { + ns: "Test.UniqueMap", + expected: "UniqueMap debe contener valores únicos", + }, + } + + 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)) + } + +} diff --git a/go-playground/validator/v10/translations/fr/fr.go b/go-playground/validator/v10/translations/fr/fr.go new file mode 100644 index 0000000..82dbacf --- /dev/null +++ b/go-playground/validator/v10/translations/fr/fr.go @@ -0,0 +1,1365 @@ +package fr + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "gin-valid/go-playground/locales" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/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} est un champ obligatoire", + override: false, + }, + { + tag: "len", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("len-string", "{0} doit faire une taille de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} caractère", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} caractères", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("len-number", "{0} doit être égal à {1}", false); err != nil { + return + } + + if err = ut.Add("len-elements", "{0} doit contenir {1}", false); err != nil { + return + } + if err = ut.AddCardinal("len-elements-element", "{0} element", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-elements-element", "{0} elements", 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-elements-element", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("len-elements", 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} doit faire une taille minimum de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} caractère", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} caractères", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("min-number", "{0} doit être égal à {1} ou plus", false); err != nil { + return + } + + if err = ut.Add("min-elements", "{0} doit contenir au moins {1}", false); err != nil { + return + } + if err = ut.AddCardinal("min-elements-element", "{0} element", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-elements-element", "{0} elements", 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-elements-element", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("min-elements", 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} doit faire une taille maximum de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} caractère", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} caractères", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("max-number", "{0} doit être égal à {1} ou moins", false); err != nil { + return + } + + if err = ut.Add("max-elements", "{0} doit contenir au maximum {1}", false); err != nil { + return + } + if err = ut.AddCardinal("max-elements-element", "{0} element", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-elements-element", "{0} elements", 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-elements-element", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("max-elements", 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} n'est pas égal à {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} ne doit pas être égal à {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} doit avoir une taille inférieure à {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} caractère", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} caractères", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-number", "{0} doit être inférieur à {1}", false); err != nil { + return + } + + if err = ut.Add("lt-elements", "{0} doit contenir mois de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-elements-element", "{0} element", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-elements-element", "{0} elements", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-datetime", "{0} doit être avant la date et l'heure actuelle", 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-elements-element", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("lt-elements", 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} doit faire une taille maximum de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} caractère", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} caractères", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-number", "{0} doit faire {1} ou moins", false); err != nil { + return + } + + if err = ut.Add("lte-elements", "{0} doit contenir un maximum de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-elements-element", "{0} element", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-elements-element", "{0} elements", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-datetime", "{0} doit être avant ou pendant la date et l'heure actuelle", 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-elements-element", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("lte-elements", 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} doit avoir une taille supérieur à {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} caractère", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} caractères", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-number", "{0} doit être supérieur à {1}", false); err != nil { + return + } + + if err = ut.Add("gt-elements", "{0} doit contenir plus de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-elements-element", "{0} element", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-elements-element", "{0} elements", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-datetime", "{0} doit être après la date et l'heure actuelle", 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-elements-element", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("gt-elements", 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} doit faire une taille d'au moins {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} caractère", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} caractères", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-number", "{0} doit être {1} ou plus", false); err != nil { + return + } + + if err = ut.Add("gte-elements", "{0} doit contenir au moins {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-elements-element", "{0} element", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-elements-element", "{0} elements", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-datetime", "{0} doit être après ou pendant la date et l'heure actuelle", 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-elements-element", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + + t, err = ut.T("gte-elements", 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} doit être égal à {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} doit être égal à {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} ne doit pas être égal à {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} doit être supérieur à {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} doit être supérieur ou égal à {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} doit être inférieur à {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} doit être inférieur ou égal à {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} ne doit pas être égal à {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} doit être supérieur à {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} doit être supérieur ou égal à {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} doit être inférieur à {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} doit être inférieur ou égal à {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} ne doit contenir que des caractères alphabétiques", + override: false, + }, + { + tag: "alphanum", + translation: "{0} ne doit contenir que des caractères alphanumériques", + override: false, + }, + { + tag: "numeric", + translation: "{0} doit être une valeur numérique valide", + override: false, + }, + { + tag: "number", + translation: "{0} doit être un nombre valid", + override: false, + }, + { + tag: "hexadecimal", + translation: "{0} doit être une chaîne de caractères au format hexadécimal valide", + override: false, + }, + { + tag: "hexcolor", + translation: "{0} doit être une couleur au format HEX valide", + override: false, + }, + { + tag: "rgb", + translation: "{0} doit être une couleur au format RGB valide", + override: false, + }, + { + tag: "rgba", + translation: "{0} doit être une couleur au format RGBA valide", + override: false, + }, + { + tag: "hsl", + translation: "{0} doit être une couleur au format HSL valide", + override: false, + }, + { + tag: "hsla", + translation: "{0} doit être une couleur au format HSLA valide", + override: false, + }, + { + tag: "email", + translation: "{0} doit être une adresse email valide", + override: false, + }, + { + tag: "url", + translation: "{0} doit être une URL valide", + override: false, + }, + { + tag: "uri", + translation: "{0} doit être une URI valide", + override: false, + }, + { + tag: "base64", + translation: "{0} doit être une chaîne de caractères au format Base64 valide", + override: false, + }, + { + tag: "contains", + translation: "{0} doit contenir le texte '{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} doit contenir au moins l' un des caractères suivants '{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} ne doit pas contenir le texte '{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} ne doit pas contenir l'un des caractères suivants '{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} ne doit pas contenir ce qui suit '{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} doit être un numéro ISBN valid", + override: false, + }, + { + tag: "isbn10", + translation: "{0} doit être un numéro ISBN-10 valid", + override: false, + }, + { + tag: "isbn13", + translation: "{0} doit être un numéro ISBN-13 valid", + override: false, + }, + { + tag: "uuid", + translation: "{0} doit être un UUID valid", + override: false, + }, + { + tag: "uuid3", + translation: "{0} doit être un UUID version 3 valid", + override: false, + }, + { + tag: "uuid4", + translation: "{0} doit être un UUID version 4 valid", + override: false, + }, + { + tag: "uuid5", + translation: "{0} doit être un UUID version 5 valid", + override: false, + }, + { + tag: "ascii", + translation: "{0} ne doit contenir que des caractères ascii", + override: false, + }, + { + tag: "printascii", + translation: "{0} ne doit contenir que des caractères ascii affichables", + override: false, + }, + { + tag: "multibyte", + translation: "{0} doit contenir des caractères multioctets", + override: false, + }, + { + tag: "datauri", + translation: "{0} doit contenir une URI data valide", + override: false, + }, + { + tag: "latitude", + translation: "{0} doit contenir des coordonnées latitude valides", + override: false, + }, + { + tag: "longitude", + translation: "{0} doit contenir des coordonnées longitudes valides", + override: false, + }, + { + tag: "ssn", + translation: "{0} doit être un numéro SSN valide", + override: false, + }, + { + tag: "ipv4", + translation: "{0} doit être une adressse IPv4 valide", + override: false, + }, + { + tag: "ipv6", + translation: "{0} doit être une adressse IPv6 valide", + override: false, + }, + { + tag: "ip", + translation: "{0} doit être une adressse IP valide", + override: false, + }, + { + tag: "cidr", + translation: "{0} doit contenir une notation CIDR valide", + override: false, + }, + { + tag: "cidrv4", + translation: "{0} doit contenir une notation CIDR valide pour une adresse IPv4", + override: false, + }, + { + tag: "cidrv6", + translation: "{0} doit contenir une notation CIDR valide pour une adresse IPv6", + override: false, + }, + { + tag: "tcp_addr", + translation: "{0} doit être une adressse TCP valide", + override: false, + }, + { + tag: "tcp4_addr", + translation: "{0} doit être une adressse IPv4 TCP valide", + override: false, + }, + { + tag: "tcp6_addr", + translation: "{0} doit être une adressse IPv6 TCP valide", + override: false, + }, + { + tag: "udp_addr", + translation: "{0} doit être une adressse UDP valide", + override: false, + }, + { + tag: "udp4_addr", + translation: "{0} doit être une adressse IPv4 UDP valide", + override: false, + }, + { + tag: "udp6_addr", + translation: "{0} doit être une adressse IPv6 UDP valide", + override: false, + }, + { + tag: "ip_addr", + translation: "{0} doit être une adresse IP résolvable", + override: false, + }, + { + tag: "ip4_addr", + translation: "{0} doit être une adresse IPv4 résolvable", + override: false, + }, + { + tag: "ip6_addr", + translation: "{0} doit être une adresse IPv6 résolvable", + override: false, + }, + { + tag: "unix_addr", + translation: "{0} doit être une adresse UNIX résolvable", + override: false, + }, + { + tag: "mac", + translation: "{0} doit contenir une adresse MAC valide", + override: false, + }, + { + tag: "iscolor", + translation: "{0} doit être une couleur valide", + override: false, + }, + { + tag: "oneof", + translation: "{0} doit être l'un des choix suivants [{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/go-playground/validator/v10/translations/fr/fr_test.go b/go-playground/validator/v10/translations/fr/fr_test.go new file mode 100644 index 0000000..08c8a63 --- /dev/null +++ b/go-playground/validator/v10/translations/fr/fr_test.go @@ -0,0 +1,634 @@ +package fr + +import ( + "testing" + "time" + + french "gin-valid/go-playground/locales/fr" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/go-playground/validator/v10" + . "github.com/go-playground/assert/v2" +) + +func TestTranslations(t *testing.T) { + + fre := french.New() + uni := ut.New(fre, fre) + trans, _ := uni.GetTranslator("fr") + + 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"` + } + + 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 + + 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 doit être une couleur valide", + }, + { + ns: "Test.MAC", + expected: "MAC doit contenir une adresse MAC valide", + }, + { + ns: "Test.IPAddr", + expected: "IPAddr doit être une adresse IP résolvable", + }, + { + ns: "Test.IPAddrv4", + expected: "IPAddrv4 doit être une adresse IPv4 résolvable", + }, + { + ns: "Test.IPAddrv6", + expected: "IPAddrv6 doit être une adresse IPv6 résolvable", + }, + { + ns: "Test.UDPAddr", + expected: "UDPAddr doit être une adressse UDP valide", + }, + { + ns: "Test.UDPAddrv4", + expected: "UDPAddrv4 doit être une adressse IPv4 UDP valide", + }, + { + ns: "Test.UDPAddrv6", + expected: "UDPAddrv6 doit être une adressse IPv6 UDP valide", + }, + { + ns: "Test.TCPAddr", + expected: "TCPAddr doit être une adressse TCP valide", + }, + { + ns: "Test.TCPAddrv4", + expected: "TCPAddrv4 doit être une adressse IPv4 TCP valide", + }, + { + ns: "Test.TCPAddrv6", + expected: "TCPAddrv6 doit être une adressse IPv6 TCP valide", + }, + { + ns: "Test.CIDR", + expected: "CIDR doit contenir une notation CIDR valide", + }, + { + ns: "Test.CIDRv4", + expected: "CIDRv4 doit contenir une notation CIDR valide pour une adresse IPv4", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6 doit contenir une notation CIDR valide pour une adresse IPv6", + }, + { + ns: "Test.SSN", + expected: "SSN doit être un numéro SSN valide", + }, + { + ns: "Test.IP", + expected: "IP doit être une adressse IP valide", + }, + { + ns: "Test.IPv4", + expected: "IPv4 doit être une adressse IPv4 valide", + }, + { + ns: "Test.IPv6", + expected: "IPv6 doit être une adressse IPv6 valide", + }, + { + ns: "Test.DataURI", + expected: "DataURI doit contenir une URI data valide", + }, + { + ns: "Test.Latitude", + expected: "Latitude doit contenir des coordonnées latitude valides", + }, + { + ns: "Test.Longitude", + expected: "Longitude doit contenir des coordonnées longitudes valides", + }, + { + ns: "Test.MultiByte", + expected: "MultiByte doit contenir des caractères multioctets", + }, + { + ns: "Test.ASCII", + expected: "ASCII ne doit contenir que des caractères ascii", + }, + { + ns: "Test.PrintableASCII", + expected: "PrintableASCII ne doit contenir que des caractères ascii affichables", + }, + { + ns: "Test.UUID", + expected: "UUID doit être un UUID valid", + }, + { + ns: "Test.UUID3", + expected: "UUID3 doit être un UUID version 3 valid", + }, + { + ns: "Test.UUID4", + expected: "UUID4 doit être un UUID version 4 valid", + }, + { + ns: "Test.UUID5", + expected: "UUID5 doit être un UUID version 5 valid", + }, + { + ns: "Test.ISBN", + expected: "ISBN doit être un numéro ISBN valid", + }, + { + ns: "Test.ISBN10", + expected: "ISBN10 doit être un numéro ISBN-10 valid", + }, + { + ns: "Test.ISBN13", + expected: "ISBN13 doit être un numéro ISBN-13 valid", + }, + { + ns: "Test.Excludes", + expected: "Excludes ne doit pas contenir le texte 'text'", + }, + { + ns: "Test.ExcludesAll", + expected: "ExcludesAll ne doit pas contenir l'un des caractères suivants '!@#$'", + }, + { + ns: "Test.ExcludesRune", + expected: "ExcludesRune ne doit pas contenir ce qui suit '☻'", + }, + { + ns: "Test.ContainsAny", + expected: "ContainsAny doit contenir au moins l' un des caractères suivants '!@#$'", + }, + { + ns: "Test.Contains", + expected: "Contains doit contenir le texte 'purpose'", + }, + { + ns: "Test.Base64", + expected: "Base64 doit être une chaîne de caractères au format Base64 valide", + }, + { + ns: "Test.Email", + expected: "Email doit être une adresse email valide", + }, + { + ns: "Test.URL", + expected: "URL doit être une URL valide", + }, + { + ns: "Test.URI", + expected: "URI doit être une URI valide", + }, + { + ns: "Test.RGBColorString", + expected: "RGBColorString doit être une couleur au format RGB valide", + }, + { + ns: "Test.RGBAColorString", + expected: "RGBAColorString doit être une couleur au format RGBA valide", + }, + { + ns: "Test.HSLColorString", + expected: "HSLColorString doit être une couleur au format HSL valide", + }, + { + ns: "Test.HSLAColorString", + expected: "HSLAColorString doit être une couleur au format HSLA valide", + }, + { + ns: "Test.HexadecimalString", + expected: "HexadecimalString doit être une chaîne de caractères au format hexadécimal valide", + }, + { + ns: "Test.HexColorString", + expected: "HexColorString doit être une couleur au format HEX valide", + }, + { + ns: "Test.NumberString", + expected: "NumberString doit être un nombre valid", + }, + { + ns: "Test.NumericString", + expected: "NumericString doit être une valeur numérique valide", + }, + { + ns: "Test.AlphanumString", + expected: "AlphanumString ne doit contenir que des caractères alphanumériques", + }, + { + ns: "Test.AlphaString", + expected: "AlphaString ne doit contenir que des caractères alphabétiques", + }, + { + ns: "Test.LtFieldString", + expected: "LtFieldString doit être inférieur à MaxString", + }, + { + ns: "Test.LteFieldString", + expected: "LteFieldString doit être inférieur ou égal à MaxString", + }, + { + ns: "Test.GtFieldString", + expected: "GtFieldString doit être supérieur à MaxString", + }, + { + ns: "Test.GteFieldString", + expected: "GteFieldString doit être supérieur ou égal à MaxString", + }, + { + ns: "Test.NeFieldString", + expected: "NeFieldString ne doit pas être égal à EqFieldString", + }, + { + ns: "Test.LtCSFieldString", + expected: "LtCSFieldString doit être inférieur à Inner.LtCSFieldString", + }, + { + ns: "Test.LteCSFieldString", + expected: "LteCSFieldString doit être inférieur ou égal à Inner.LteCSFieldString", + }, + { + ns: "Test.GtCSFieldString", + expected: "GtCSFieldString doit être supérieur à Inner.GtCSFieldString", + }, + { + ns: "Test.GteCSFieldString", + expected: "GteCSFieldString doit être supérieur ou égal à Inner.GteCSFieldString", + }, + { + ns: "Test.NeCSFieldString", + expected: "NeCSFieldString ne doit pas être égal à Inner.NeCSFieldString", + }, + { + ns: "Test.EqCSFieldString", + expected: "EqCSFieldString doit être égal à Inner.EqCSFieldString", + }, + { + ns: "Test.EqFieldString", + expected: "EqFieldString doit être égal à MaxString", + }, + { + ns: "Test.GteString", + expected: "GteString doit faire une taille d'au moins 3 caractères", + }, + { + ns: "Test.GteNumber", + expected: "GteNumber doit être 5,56 ou plus", + }, + { + ns: "Test.GteMultiple", + expected: "GteMultiple doit contenir au moins 2 elements", + }, + { + ns: "Test.GteTime", + expected: "GteTime doit être après ou pendant la date et l'heure actuelle", + }, + { + ns: "Test.GtString", + expected: "GtString doit avoir une taille supérieur à 3 caractères", + }, + { + ns: "Test.GtNumber", + expected: "GtNumber doit être supérieur à 5,56", + }, + { + ns: "Test.GtMultiple", + expected: "GtMultiple doit contenir plus de 2 elements", + }, + { + ns: "Test.GtTime", + expected: "GtTime doit être après la date et l'heure actuelle", + }, + { + ns: "Test.LteString", + expected: "LteString doit faire une taille maximum de 3 caractères", + }, + { + ns: "Test.LteNumber", + expected: "LteNumber doit faire 5,56 ou moins", + }, + { + ns: "Test.LteMultiple", + expected: "LteMultiple doit contenir un maximum de 2 elements", + }, + { + ns: "Test.LteTime", + expected: "LteTime doit être avant ou pendant la date et l'heure actuelle", + }, + { + ns: "Test.LtString", + expected: "LtString doit avoir une taille inférieure à 3 caractères", + }, + { + ns: "Test.LtNumber", + expected: "LtNumber doit être inférieur à 5,56", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultiple doit contenir mois de 2 elements", + }, + { + ns: "Test.LtTime", + expected: "LtTime doit être avant la date et l'heure actuelle", + }, + { + ns: "Test.NeString", + expected: "NeString ne doit pas être égal à ", + }, + { + ns: "Test.NeNumber", + expected: "NeNumber ne doit pas être égal à 0.00", + }, + { + ns: "Test.NeMultiple", + expected: "NeMultiple ne doit pas être égal à 0", + }, + { + ns: "Test.EqString", + expected: "EqString n'est pas égal à 3", + }, + { + ns: "Test.EqNumber", + expected: "EqNumber n'est pas égal à 2.33", + }, + { + ns: "Test.EqMultiple", + expected: "EqMultiple n'est pas égal à 7", + }, + { + ns: "Test.MaxString", + expected: "MaxString doit faire une taille maximum de 3 caractères", + }, + { + ns: "Test.MaxNumber", + expected: "MaxNumber doit être égal à 1 113,00 ou moins", + }, + { + ns: "Test.MaxMultiple", + expected: "MaxMultiple doit contenir au maximum 7 elements", + }, + { + ns: "Test.MinString", + expected: "MinString doit faire une taille minimum de 1 caractère", + }, + { + ns: "Test.MinNumber", + expected: "MinNumber doit être égal à 1 113,00 ou plus", + }, + { + ns: "Test.MinMultiple", + expected: "MinMultiple doit contenir au moins 7 elements", + }, + { + ns: "Test.LenString", + expected: "LenString doit faire une taille de 1 caractère", + }, + { + ns: "Test.LenNumber", + expected: "LenNumber doit être égal à 1 113,00", + }, + { + ns: "Test.LenMultiple", + expected: "LenMultiple doit contenir 7 elements", + }, + { + ns: "Test.RequiredString", + expected: "RequiredString est un champ obligatoire", + }, + { + ns: "Test.RequiredNumber", + expected: "RequiredNumber est un champ obligatoire", + }, + { + ns: "Test.RequiredMultiple", + expected: "RequiredMultiple est un champ obligatoire", + }, + { + ns: "Test.StrPtrMinLen", + expected: "StrPtrMinLen doit faire une taille minimum de 10 caractères", + }, + { + ns: "Test.StrPtrMaxLen", + expected: "StrPtrMaxLen doit faire une taille maximum de 1 caractère", + }, + { + ns: "Test.StrPtrLen", + expected: "StrPtrLen doit faire une taille de 2 caractères", + }, + { + ns: "Test.StrPtrLt", + expected: "StrPtrLt doit avoir une taille inférieure à 1 caractère", + }, + { + ns: "Test.StrPtrLte", + expected: "StrPtrLte doit faire une taille maximum de 1 caractère", + }, + { + ns: "Test.StrPtrGt", + expected: "StrPtrGt doit avoir une taille supérieur à 10 caractères", + }, + { + ns: "Test.StrPtrGte", + expected: "StrPtrGte doit faire une taille d'au moins 10 caractères", + }, + { + ns: "Test.OneOfString", + expected: "OneOfString doit être l'un des choix suivants [red green]", + }, + { + ns: "Test.OneOfInt", + expected: "OneOfInt doit être l'un des choix suivants [5 63]", + }, + } + + 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)) + } + +} diff --git a/go-playground/validator/v10/translations/id/id.go b/go-playground/validator/v10/translations/id/id.go new file mode 100644 index 0000000..e98b61b --- /dev/null +++ b/go-playground/validator/v10/translations/id/id.go @@ -0,0 +1,1365 @@ +package id + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "gin-valid/go-playground/locales" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/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} wajib diisi", + override: false, + }, + { + tag: "len", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("len-string", "panjang {0} harus {1}", false); err != nil { + return + } + + // if err = ut.AddCardinal("len-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("len-string-character", "{0} karakter", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("len-number", "{0} harus sama dengan {1}", false); err != nil { + return + } + + if err = ut.Add("len-items", "{0} harus berisi {1}", false); err != nil { + return + } + // if err = ut.AddCardinal("len-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("len-items-item", "{0} item", 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", "panjang minimal {0} adalah {1}", false); err != nil { + return + } + + // if err = ut.AddCardinal("min-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("min-string-character", "{0} karakter", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("min-number", "{0} harus {1} atau lebih besar", false); err != nil { + return + } + + if err = ut.Add("min-items", "panjang minimal {0} adalah {1}", false); err != nil { + return + } + // if err = ut.AddCardinal("min-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("min-items-item", "{0} item", 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", "panjang maksimal {0} adalah {1}", false); err != nil { + return + } + + // if err = ut.AddCardinal("max-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("max-string-character", "{0} karakter", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("max-number", "{0} harus {1} atau kurang", false); err != nil { + return + } + + if err = ut.Add("max-items", "{0} harus berisi maksimal {1}", false); err != nil { + return + } + // if err = ut.AddCardinal("max-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("max-items-item", "{0} item", 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} tidak sama dengan {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} tidak sama dengan {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", "panjang {0} harus kurang dari {1}", false); err != nil { + return + } + + // if err = ut.AddCardinal("lt-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("lt-string-character", "{0} karakter", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-number", "{0} harus kurang dari {1}", false); err != nil { + return + } + + if err = ut.Add("lt-items", "{0} harus berisi kurang dari {1}", false); err != nil { + return + } + + // if err = ut.AddCardinal("lt-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("lt-items-item", "{0} item", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-datetime", "{0} harus kurang dari tanggal & waktu saat ini", 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", "panjang maksimal {0} adalah {1}", false); err != nil { + return + } + + // if err = ut.AddCardinal("lte-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("lte-string-character", "{0} karakter", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-number", "{0} harus {1} atau kurang", false); err != nil { + return + } + + if err = ut.Add("lte-items", "{0} harus berisi maksimal {1}", false); err != nil { + return + } + + // if err = ut.AddCardinal("lte-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("lte-items-item", "{0} item", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-datetime", "{0} harus kurang dari atau sama dengan tanggal & waktu saat ini", 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", "panjang {0} harus lebih dari {1}", false); err != nil { + return + } + + // if err = ut.AddCardinal("gt-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("gt-string-character", "{0} karakter", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-number", "{0} harus lebih besar dari {1}", false); err != nil { + return + } + + if err = ut.Add("gt-items", "{0} harus berisi lebih dari {1}", false); err != nil { + return + } + + // if err = ut.AddCardinal("gt-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("gt-items-item", "{0} item", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-datetime", "{0} harus lebih besar dari tanggal & waktu saat ini", 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", "panjang minimal {0} adalah {1}", false); err != nil { + return + } + + // if err = ut.AddCardinal("gte-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("gte-string-character", "{0} karakter", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-number", "{0} harus {1} atau lebih besar", false); err != nil { + return + } + + if err = ut.Add("gte-items", "{0} harus berisi setidaknya {1}", false); err != nil { + return + } + + // if err = ut.AddCardinal("gte-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("gte-items-item", "{0} item", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-datetime", "{0} harus lebih besar dari atau sama dengan tanggal & waktu saat ini", 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} harus sama dengan {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} harus sama dengan {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} tidak sama dengan {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} harus lebih besar dari {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} harus lebih besar dari atau sama dengan {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} harus kurang dari {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} harus kurang dari atau sama dengan {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} tidak sama dengan {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} harus lebih besar dari {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} harus lebih besar dari atau sama dengan {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} harus kurang dari {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} harus kurang dari atau sama dengan {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} hanya dapat berisi karakter abjad", + override: false, + }, + { + tag: "alphanum", + translation: "{0} hanya dapat berisi karakter alfanumerik", + override: false, + }, + { + tag: "numeric", + translation: "{0} harus berupa nilai numerik yang valid", + override: false, + }, + { + tag: "number", + translation: "{0} harus berupa angka yang valid", + override: false, + }, + { + tag: "hexadecimal", + translation: "{0} harus berupa heksadesimal yang valid", + override: false, + }, + { + tag: "hexcolor", + translation: "{0} harus berupa warna HEX yang valid", + override: false, + }, + { + tag: "rgb", + translation: "{0} harus berupa warna RGB yang valid", + override: false, + }, + { + tag: "rgba", + translation: "{0} harus berupa warna RGBA yang valid", + override: false, + }, + { + tag: "hsl", + translation: "{0} harus berupa warna HSL yang valid", + override: false, + }, + { + tag: "hsla", + translation: "{0} harus berupa warna HSLA yang valid", + override: false, + }, + { + tag: "email", + translation: "{0} harus berupa alamat email yang valid", + override: false, + }, + { + tag: "url", + translation: "{0} harus berupa URL yang valid", + override: false, + }, + { + tag: "uri", + translation: "{0} harus berupa URI yang valid", + override: false, + }, + { + tag: "base64", + translation: "{0} harus berupa string Base64 yang valid", + override: false, + }, + { + tag: "contains", + translation: "{0} harus berisi teks '{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} harus berisi setidaknya salah satu karakter berikut '{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} tidak boleh berisi teks '{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} tidak boleh berisi salah satu karakter berikut '{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} tidak boleh berisi '{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} harus berupa nomor ISBN yang valid", + override: false, + }, + { + tag: "isbn10", + translation: "{0} harus berupa nomor ISBN-10 yang valid", + override: false, + }, + { + tag: "isbn13", + translation: "{0} harus berupa nomor ISBN-13 yang valid", + override: false, + }, + { + tag: "uuid", + translation: "{0} harus berupa UUID yang valid", + override: false, + }, + { + tag: "uuid3", + translation: "{0} harus berupa UUID versi 3 yang valid", + override: false, + }, + { + tag: "uuid4", + translation: "{0} harus berupa UUID versi 4 yang valid", + override: false, + }, + { + tag: "uuid5", + translation: "{0} harus berupa UUID versi 5 yang valid", + override: false, + }, + { + tag: "ascii", + translation: "{0} hanya boleh berisi karakter ascii", + override: false, + }, + { + tag: "printascii", + translation: "{0} hanya boleh berisi karakter ascii yang dapat dicetak", + override: false, + }, + { + tag: "multibyte", + translation: "{0} harus berisi karakter multibyte", + override: false, + }, + { + tag: "datauri", + translation: "{0} harus berisi URI Data yang valid", + override: false, + }, + { + tag: "latitude", + translation: "{0} harus berisi koordinat lintang yang valid", + override: false, + }, + { + tag: "longitude", + translation: "{0} harus berisi koordinat bujur yang valid", + override: false, + }, + { + tag: "ssn", + translation: "{0} harus berupa nomor SSN yang valid", + override: false, + }, + { + tag: "ipv4", + translation: "{0} harus berupa alamat IPv4 yang valid", + override: false, + }, + { + tag: "ipv6", + translation: "{0} harus berupa alamat IPv6 yang valid", + override: false, + }, + { + tag: "ip", + translation: "{0} harus berupa alamat IP yang valid", + override: false, + }, + { + tag: "cidr", + translation: "{0} harus berisi notasi CIDR yang valid", + override: false, + }, + { + tag: "cidrv4", + translation: "{0} harus berisi notasi CIDR yang valid untuk alamat IPv4", + override: false, + }, + { + tag: "cidrv6", + translation: "{0} harus berisi notasi CIDR yang valid untuk alamat IPv6", + override: false, + }, + { + tag: "tcp_addr", + translation: "{0} harus berupa alamat TCP yang valid", + override: false, + }, + { + tag: "tcp4_addr", + translation: "{0} harus berupa alamat TCP IPv4 yang valid", + override: false, + }, + { + tag: "tcp6_addr", + translation: "{0} harus berupa alamat TCP IPv6 yang valid", + override: false, + }, + { + tag: "udp_addr", + translation: "{0} harus berupa alamat UDP yang valid", + override: false, + }, + { + tag: "udp4_addr", + translation: "{0} harus berupa alamat IPv4 UDP yang valid", + override: false, + }, + { + tag: "udp6_addr", + translation: "{0} harus berupa alamat IPv6 UDP yang valid", + override: false, + }, + { + tag: "ip_addr", + translation: "{0} harus berupa alamat IP yang dapat dipecahkan", + override: false, + }, + { + tag: "ip4_addr", + translation: "{0} harus berupa alamat IPv4 yang dapat diatasi", + override: false, + }, + { + tag: "ip6_addr", + translation: "{0} harus berupa alamat IPv6 yang dapat diatasi", + override: false, + }, + { + tag: "unix_addr", + translation: "{0} harus berupa alamat UNIX yang dapat diatasi", + override: false, + }, + { + tag: "mac", + translation: "{0} harus berisi alamat MAC yang valid", + override: false, + }, + { + tag: "iscolor", + translation: "{0} harus berupa warna yang valid", + override: false, + }, + { + tag: "oneof", + translation: "{0} harus berupa salah satu dari [{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/go-playground/validator/v10/translations/id/id_test.go b/go-playground/validator/v10/translations/id/id_test.go new file mode 100644 index 0000000..1305ff6 --- /dev/null +++ b/go-playground/validator/v10/translations/id/id_test.go @@ -0,0 +1,634 @@ +package id + +import ( + "testing" + "time" + + indonesia "gin-valid/go-playground/locales/id" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/go-playground/validator/v10" + . "github.com/go-playground/assert/v2" +) + +func TestTranslations(t *testing.T) { + + idn := indonesia.New() + uni := ut.New(idn, idn) + trans, _ := uni.GetTranslator("id") + + 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=tujuan"` + 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=merah hijau"` + OneOfInt int `validate:"oneof=5 63"` + } + + 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 + + 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 harus berupa warna yang valid", + }, + { + ns: "Test.MAC", + expected: "MAC harus berisi alamat MAC yang valid", + }, + { + ns: "Test.IPAddr", + expected: "IPAddr harus berupa alamat IP yang dapat dipecahkan", + }, + { + ns: "Test.IPAddrv4", + expected: "IPAddrv4 harus berupa alamat IPv4 yang dapat diatasi", + }, + { + ns: "Test.IPAddrv6", + expected: "IPAddrv6 harus berupa alamat IPv6 yang dapat diatasi", + }, + { + ns: "Test.UDPAddr", + expected: "UDPAddr harus berupa alamat UDP yang valid", + }, + { + ns: "Test.UDPAddrv4", + expected: "UDPAddrv4 harus berupa alamat IPv4 UDP yang valid", + }, + { + ns: "Test.UDPAddrv6", + expected: "UDPAddrv6 harus berupa alamat IPv6 UDP yang valid", + }, + { + ns: "Test.TCPAddr", + expected: "TCPAddr harus berupa alamat TCP yang valid", + }, + { + ns: "Test.TCPAddrv4", + expected: "TCPAddrv4 harus berupa alamat TCP IPv4 yang valid", + }, + { + ns: "Test.TCPAddrv6", + expected: "TCPAddrv6 harus berupa alamat TCP IPv6 yang valid", + }, + { + ns: "Test.CIDR", + expected: "CIDR harus berisi notasi CIDR yang valid", + }, + { + ns: "Test.CIDRv4", + expected: "CIDRv4 harus berisi notasi CIDR yang valid untuk alamat IPv4", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6 harus berisi notasi CIDR yang valid untuk alamat IPv6", + }, + { + ns: "Test.SSN", + expected: "SSN harus berupa nomor SSN yang valid", + }, + { + ns: "Test.IP", + expected: "IP harus berupa alamat IP yang valid", + }, + { + ns: "Test.IPv4", + expected: "IPv4 harus berupa alamat IPv4 yang valid", + }, + { + ns: "Test.IPv6", + expected: "IPv6 harus berupa alamat IPv6 yang valid", + }, + { + ns: "Test.DataURI", + expected: "DataURI harus berisi URI Data yang valid", + }, + { + ns: "Test.Latitude", + expected: "Latitude harus berisi koordinat lintang yang valid", + }, + { + ns: "Test.Longitude", + expected: "Longitude harus berisi koordinat bujur yang valid", + }, + { + ns: "Test.MultiByte", + expected: "MultiByte harus berisi karakter multibyte", + }, + { + ns: "Test.ASCII", + expected: "ASCII hanya boleh berisi karakter ascii", + }, + { + ns: "Test.PrintableASCII", + expected: "PrintableASCII hanya boleh berisi karakter ascii yang dapat dicetak", + }, + { + ns: "Test.UUID", + expected: "UUID harus berupa UUID yang valid", + }, + { + ns: "Test.UUID3", + expected: "UUID3 harus berupa UUID versi 3 yang valid", + }, + { + ns: "Test.UUID4", + expected: "UUID4 harus berupa UUID versi 4 yang valid", + }, + { + ns: "Test.UUID5", + expected: "UUID5 harus berupa UUID versi 5 yang valid", + }, + { + ns: "Test.ISBN", + expected: "ISBN harus berupa nomor ISBN yang valid", + }, + { + ns: "Test.ISBN10", + expected: "ISBN10 harus berupa nomor ISBN-10 yang valid", + }, + { + ns: "Test.ISBN13", + expected: "ISBN13 harus berupa nomor ISBN-13 yang valid", + }, + { + ns: "Test.Excludes", + expected: "Excludes tidak boleh berisi teks 'text'", + }, + { + ns: "Test.ExcludesAll", + expected: "ExcludesAll tidak boleh berisi salah satu karakter berikut '!@#$'", + }, + { + ns: "Test.ExcludesRune", + expected: "ExcludesRune tidak boleh berisi '☻'", + }, + { + ns: "Test.ContainsAny", + expected: "ContainsAny harus berisi setidaknya salah satu karakter berikut '!@#$'", + }, + { + ns: "Test.Contains", + expected: "Contains harus berisi teks 'tujuan'", + }, + { + ns: "Test.Base64", + expected: "Base64 harus berupa string Base64 yang valid", + }, + { + ns: "Test.Email", + expected: "Email harus berupa alamat email yang valid", + }, + { + ns: "Test.URL", + expected: "URL harus berupa URL yang valid", + }, + { + ns: "Test.URI", + expected: "URI harus berupa URI yang valid", + }, + { + ns: "Test.RGBColorString", + expected: "RGBColorString harus berupa warna RGB yang valid", + }, + { + ns: "Test.RGBAColorString", + expected: "RGBAColorString harus berupa warna RGBA yang valid", + }, + { + ns: "Test.HSLColorString", + expected: "HSLColorString harus berupa warna HSL yang valid", + }, + { + ns: "Test.HSLAColorString", + expected: "HSLAColorString harus berupa warna HSLA yang valid", + }, + { + ns: "Test.HexadecimalString", + expected: "HexadecimalString harus berupa heksadesimal yang valid", + }, + { + ns: "Test.HexColorString", + expected: "HexColorString harus berupa warna HEX yang valid", + }, + { + ns: "Test.NumberString", + expected: "NumberString harus berupa angka yang valid", + }, + { + ns: "Test.NumericString", + expected: "NumericString harus berupa nilai numerik yang valid", + }, + { + ns: "Test.AlphanumString", + expected: "AlphanumString hanya dapat berisi karakter alfanumerik", + }, + { + ns: "Test.AlphaString", + expected: "AlphaString hanya dapat berisi karakter abjad", + }, + { + ns: "Test.LtFieldString", + expected: "LtFieldString harus kurang dari MaxString", + }, + { + ns: "Test.LteFieldString", + expected: "LteFieldString harus kurang dari atau sama dengan MaxString", + }, + { + ns: "Test.GtFieldString", + expected: "GtFieldString harus lebih besar dari MaxString", + }, + { + ns: "Test.GteFieldString", + expected: "GteFieldString harus lebih besar dari atau sama dengan MaxString", + }, + { + ns: "Test.NeFieldString", + expected: "NeFieldString tidak sama dengan EqFieldString", + }, + { + ns: "Test.LtCSFieldString", + expected: "LtCSFieldString harus kurang dari Inner.LtCSFieldString", + }, + { + ns: "Test.LteCSFieldString", + expected: "LteCSFieldString harus kurang dari atau sama dengan Inner.LteCSFieldString", + }, + { + ns: "Test.GtCSFieldString", + expected: "GtCSFieldString harus lebih besar dari Inner.GtCSFieldString", + }, + { + ns: "Test.GteCSFieldString", + expected: "GteCSFieldString harus lebih besar dari atau sama dengan Inner.GteCSFieldString", + }, + { + ns: "Test.NeCSFieldString", + expected: "NeCSFieldString tidak sama dengan Inner.NeCSFieldString", + }, + { + ns: "Test.EqCSFieldString", + expected: "EqCSFieldString harus sama dengan Inner.EqCSFieldString", + }, + { + ns: "Test.EqFieldString", + expected: "EqFieldString harus sama dengan MaxString", + }, + { + ns: "Test.GteString", + expected: "panjang minimal GteString adalah 3 karakter", + }, + { + ns: "Test.GteNumber", + expected: "GteNumber harus 5,56 atau lebih besar", + }, + { + ns: "Test.GteMultiple", + expected: "GteMultiple harus berisi setidaknya 2 item", + }, + { + ns: "Test.GteTime", + expected: "GteTime harus lebih besar dari atau sama dengan tanggal & waktu saat ini", + }, + { + ns: "Test.GtString", + expected: "panjang GtString harus lebih dari 3 karakter", + }, + { + ns: "Test.GtNumber", + expected: "GtNumber harus lebih besar dari 5,56", + }, + { + ns: "Test.GtMultiple", + expected: "GtMultiple harus berisi lebih dari 2 item", + }, + { + ns: "Test.GtTime", + expected: "GtTime harus lebih besar dari tanggal & waktu saat ini", + }, + { + ns: "Test.LteString", + expected: "panjang maksimal LteString adalah 3 karakter", + }, + { + ns: "Test.LteNumber", + expected: "LteNumber harus 5,56 atau kurang", + }, + { + ns: "Test.LteMultiple", + expected: "LteMultiple harus berisi maksimal 2 item", + }, + { + ns: "Test.LteTime", + expected: "LteTime harus kurang dari atau sama dengan tanggal & waktu saat ini", + }, + { + ns: "Test.LtString", + expected: "panjang LtString harus kurang dari 3 karakter", + }, + { + ns: "Test.LtNumber", + expected: "LtNumber harus kurang dari 5,56", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultiple harus berisi kurang dari 2 item", + }, + { + ns: "Test.LtTime", + expected: "LtTime harus kurang dari tanggal & waktu saat ini", + }, + { + ns: "Test.NeString", + expected: "NeString tidak sama dengan ", + }, + { + ns: "Test.NeNumber", + expected: "NeNumber tidak sama dengan 0.00", + }, + { + ns: "Test.NeMultiple", + expected: "NeMultiple tidak sama dengan 0", + }, + { + ns: "Test.EqString", + expected: "EqString tidak sama dengan 3", + }, + { + ns: "Test.EqNumber", + expected: "EqNumber tidak sama dengan 2.33", + }, + { + ns: "Test.EqMultiple", + expected: "EqMultiple tidak sama dengan 7", + }, + { + ns: "Test.MaxString", + expected: "panjang maksimal MaxString adalah 3 karakter", + }, + { + ns: "Test.MaxNumber", + expected: "MaxNumber harus 1.113,00 atau kurang", + }, + { + ns: "Test.MaxMultiple", + expected: "MaxMultiple harus berisi maksimal 7 item", + }, + { + ns: "Test.MinString", + expected: "panjang minimal MinString adalah 1 karakter", + }, + { + ns: "Test.MinNumber", + expected: "MinNumber harus 1.113,00 atau lebih besar", + }, + { + ns: "Test.MinMultiple", + expected: "panjang minimal MinMultiple adalah 7 item", + }, + { + ns: "Test.LenString", + expected: "panjang LenString harus 1 karakter", + }, + { + ns: "Test.LenNumber", + expected: "LenNumber harus sama dengan 1.113,00", + }, + { + ns: "Test.LenMultiple", + expected: "LenMultiple harus berisi 7 item", + }, + { + ns: "Test.RequiredString", + expected: "RequiredString wajib diisi", + }, + { + ns: "Test.RequiredNumber", + expected: "RequiredNumber wajib diisi", + }, + { + ns: "Test.RequiredMultiple", + expected: "RequiredMultiple wajib diisi", + }, + { + ns: "Test.StrPtrMinLen", + expected: "panjang minimal StrPtrMinLen adalah 10 karakter", + }, + { + ns: "Test.StrPtrMaxLen", + expected: "panjang maksimal StrPtrMaxLen adalah 1 karakter", + }, + { + ns: "Test.StrPtrLen", + expected: "panjang StrPtrLen harus 2 karakter", + }, + { + ns: "Test.StrPtrLt", + expected: "panjang StrPtrLt harus kurang dari 1 karakter", + }, + { + ns: "Test.StrPtrLte", + expected: "panjang maksimal StrPtrLte adalah 1 karakter", + }, + { + ns: "Test.StrPtrGt", + expected: "panjang StrPtrGt harus lebih dari 10 karakter", + }, + { + ns: "Test.StrPtrGte", + expected: "panjang minimal StrPtrGte adalah 10 karakter", + }, + { + ns: "Test.OneOfString", + expected: "OneOfString harus berupa salah satu dari [merah hijau]", + }, + { + ns: "Test.OneOfInt", + expected: "OneOfInt harus berupa salah satu dari [5 63]", + }, + } + + 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)) + } + +} diff --git a/go-playground/validator/v10/translations/ja/ja.go b/go-playground/validator/v10/translations/ja/ja.go new file mode 100644 index 0000000..2de8803 --- /dev/null +++ b/go-playground/validator/v10/translations/ja/ja.go @@ -0,0 +1,1421 @@ +package ja + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "gin-valid/go-playground/locales" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/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", + customRegisFunc: func(ut ut.Translator) (err error) { + if err = ut.Add("ne-items", "{0}の項目の数は{1}と異ならなければなりません", false); err != nil { + fmt.Printf("ne customRegisFunc #1 error because of %v\n", err) + return + } + // if err = ut.AddCardinal("ne-items-item", "{0}個", locales.PluralRuleOne, false); err != nil { + // return + // } + + if err = ut.AddCardinal("ne-items-item", "{0}個", locales.PluralRuleOther, false); err != nil { + return + } + if err = ut.Add("ne", "{0}は{1}と異ならなければなりません", false); err != nil { + fmt.Printf("ne customRegisFunc #2 error because of %v\n", err) + 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.Slice: + var c string + err = fn() + if err != nil { + goto END + } + + c, err = ut.C("ne-items-item", f64, digits, ut.FmtNumber(f64, digits)) + if err != nil { + goto END + } + t, err = ut.T("ne-items", fe.Field(), c) + default: + t, err = ut.T("ne", fe.Field(), fe.Param()) + } + + END: + if err != nil { + fmt.Printf("warning: error translating FieldError: %s", err) + 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}は現時刻よりも前でなければなりません", 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}は現時刻以前でなければなりません", 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}は正しい16進表記でなければなりません", + 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: "email", + translation: "{0}は正しいメールアドレスでなければなりません", + 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}'の少なくとも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}はバージョンが3の正しいUUIDでなければなりません", + override: false, + }, + { + tag: "uuid4", + translation: "{0}はバージョンが4の正しいUUIDでなければなりません", + override: false, + }, + { + tag: "uuid5", + translation: "{0}はバージョンが4の正しいUUIDでなければなりません", + 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}は正しいデータURIを含まなければなりません", + override: false, + }, + { + tag: "latitude", + translation: "{0}は正しい緯度の座標を含まなければなりません", + override: false, + }, + { + tag: "longitude", + translation: "{0}は正しい経度の座標を含まなければなりません", + override: false, + }, + { + tag: "ssn", + translation: "{0}は正しい社会保障番号でなければなりません", + 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}はIPv4アドレスの正しいCIDR表記を含まなければなりません", + override: false, + }, + { + tag: "cidrv6", + translation: "{0}はIPv6アドレスの正しいCIDR表記を含まなければなりません", + 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: "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/go-playground/validator/v10/translations/ja/ja_test.go b/go-playground/validator/v10/translations/ja/ja_test.go new file mode 100644 index 0000000..d47fe34 --- /dev/null +++ b/go-playground/validator/v10/translations/ja/ja_test.go @@ -0,0 +1,634 @@ +package ja + +import ( + "testing" + "time" + + ja_locale "gin-valid/go-playground/locales/ja" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/go-playground/validator/v10" + . "github.com/go-playground/assert/v2" +) + +func TestTranslations(t *testing.T) { + + japanese := ja_locale.New() + uni := ut.New(japanese, japanese) + trans, _ := uni.GetTranslator("ja") + + 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"` + } + + 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 + + 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はIPv4アドレスの正しいCIDR表記を含まなければなりません", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6はIPv6アドレスの正しいCIDR表記を含まなければなりません", + }, + { + ns: "Test.SSN", + expected: "SSNは正しい社会保障番号でなければなりません", + }, + { + ns: "Test.IP", + expected: "IPは正しいIPアドレスでなければなりません", + }, + { + ns: "Test.IPv4", + expected: "IPv4は正しいIPv4アドレスでなければなりません", + }, + { + ns: "Test.IPv6", + expected: "IPv6は正しいIPv6アドレスでなければなりません", + }, + { + ns: "Test.DataURI", + expected: "DataURIは正しいデータ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はバージョンが3の正しいUUIDでなければなりません", + }, + { + ns: "Test.UUID4", + expected: "UUID4はバージョンが4の正しいUUIDでなければなりません", + }, + { + ns: "Test.UUID5", + expected: "UUID5はバージョンが4の正しいUUIDでなければなりません", + }, + { + 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は'!@#$'の少なくとも1つを含まなければなりません", + }, + { + ns: "Test.Contains", + expected: "Containsは'purpose'を含まなければなりません", + }, + { + ns: "Test.Base64", + expected: "Base64は正しいBase64文字列でなければなりません", + }, + { + ns: "Test.Email", + expected: "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は正しい16進表記でなければなりません", + }, + { + 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は現時刻以前でなければなりません", + }, + { + ns: "Test.LtString", + expected: "LtStringの長さは3文字よりも少なくなければなりません", + }, + { + ns: "Test.LtNumber", + expected: "LtNumberは5.56よりも小さくなければなりません", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultipleは2つの項目よりも少ない項目を含まなければなりません", + }, + { + ns: "Test.LtTime", + expected: "LtTimeは現時刻よりも前でなければなりません", + }, + { + 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]のうちのいずれかでなければなりません", + }, + } + + 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)) + } + +} diff --git a/go-playground/validator/v10/translations/nl/nl.go b/go-playground/validator/v10/translations/nl/nl.go new file mode 100644 index 0000000..bb9c544 --- /dev/null +++ b/go-playground/validator/v10/translations/nl/nl.go @@ -0,0 +1,1365 @@ +package nl + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "gin-valid/go-playground/locales" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/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} is een verplicht veld", + override: false, + }, + { + tag: "len", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("len-string", "{0} moet {1} lang zijn", false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} karakters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("len-number", "{0} moet gelijk zijn aan {1}", false); err != nil { + return + } + + if err = ut.Add("len-items", "{0} moet {1} bevatten", false); err != nil { + return + } + if err = ut.AddCardinal("len-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-items-item", "{0} items", 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} moet tenminste {1} lang zijn", false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} karakters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("min-number", "{0} moet {1} of groter zijn", false); err != nil { + return + } + + if err = ut.Add("min-items", "{0} moet tenminste {1} bevatten", false); err != nil { + return + } + if err = ut.AddCardinal("min-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-items-item", "{0} items", 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} mag maximaal {1} lang zijn", false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} karakters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("max-number", "{0} moet {1} of kleiner zijn", false); err != nil { + return + } + + if err = ut.Add("max-items", "{0} mag maximaal {1} bevatten", false); err != nil { + return + } + if err = ut.AddCardinal("max-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-items-item", "{0} items", 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} is niet gelijk aan {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} mag niet gelijk zijn aan {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} moet minder dan {1} lang zijn", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} karakters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-number", "{0} moet kleiner zijn dan {1}", false); err != nil { + return + } + + if err = ut.Add("lt-items", "{0} moet minder dan {1} bevatten", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-datetime", "{0} moet kleiner zijn dan de huidige datum & tijd", 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} mag maximaal {1} lang zijn", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} karakters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-number", "{0} moet {1} of minder zijn", false); err != nil { + return + } + + if err = ut.Add("lte-items", "{0} mag maximaal {1} bevatten", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-datetime", "{0} moet kleiner dan of gelijk aan de huidige datum & tijd zijn", 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} moet langer dan {1} zijn", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} karakters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-number", "{0} moet groter zijn dan {1}", false); err != nil { + return + } + + if err = ut.Add("gt-items", "{0} moet meer dan {1} bevatten", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-datetime", "{0} moet groter zijn dan de huidige datum & tijd", 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} moet tenminste {1} lang zijn", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} karakters", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-number", "{0} moet {1} of groter zijn", false); err != nil { + return + } + + if err = ut.Add("gte-items", "{0} moet tenminste {1} bevatten", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-datetime", "{0} moet groter dan of gelijk zijn aan de huidige datum & tijd", 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} moet gelijk zijn aan {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} moet gelijk zijn aan {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} mag niet gelijk zijn aan {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} moet groter zijn dan {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} moet groter dan of gelijk aan {1} zijn", + 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} moet kleiner zijn dan {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} moet kleiner dan of gelijk aan {1} zijn", + 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} mag niet gelijk zijn aan {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} moet groter zijn dan {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} moet groter dan of gelijk aan {1} zijn", + 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} moet kleiner zijn dan {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} moet kleiner dan of gelijk aan {1} zijn", + 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} mag alleen alfabetische karakters bevatten", + override: false, + }, + { + tag: "alphanum", + translation: "{0} mag alleen alfanumerieke karakters bevatten", + override: false, + }, + { + tag: "numeric", + translation: "{0} moet een geldige numerieke waarde zijn", + override: false, + }, + { + tag: "number", + translation: "{0} moet een geldig getal zijn", + override: false, + }, + { + tag: "hexadecimal", + translation: "{0} moet een geldig hexadecimaal getal zijn", + override: false, + }, + { + tag: "hexcolor", + translation: "{0} moet een geldige HEX kleur zijn", + override: false, + }, + { + tag: "rgb", + translation: "{0} moet een geldige RGB kleur zijn", + override: false, + }, + { + tag: "rgba", + translation: "{0} moet een geldige RGBA kleur zijn", + override: false, + }, + { + tag: "hsl", + translation: "{0} moet een geldige HSL kleur zijn", + override: false, + }, + { + tag: "hsla", + translation: "{0} moet een geldige HSLA kleur zijn", + override: false, + }, + { + tag: "email", + translation: "{0} moet een geldig email adres zijn", + override: false, + }, + { + tag: "url", + translation: "{0} moet een geldige URL zijn", + override: false, + }, + { + tag: "uri", + translation: "{0} moet een geldige URI zijn", + override: false, + }, + { + tag: "base64", + translation: "{0} moet een geldige Base64 string zijn", + override: false, + }, + { + tag: "contains", + translation: "{0} moet de tekst '{1}' bevatten", + 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} moet tenminste een van de volgende karakters bevatten '{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} mag niet de tekst '{1}' bevatten", + 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} mag niet een van de volgende karakters bevatten '{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} mag niet het volgende bevatten '{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} moet een geldig ISBN nummer zijn", + override: false, + }, + { + tag: "isbn10", + translation: "{0} moet een geldig ISBN-10 nummer zijn", + override: false, + }, + { + tag: "isbn13", + translation: "{0} moet een geldig ISBN-13 nummer zijn", + override: false, + }, + { + tag: "uuid", + translation: "{0} moet een geldige UUID zijn", + override: false, + }, + { + tag: "uuid3", + translation: "{0} moet een geldige versie 3 UUID zijn", + override: false, + }, + { + tag: "uuid4", + translation: "{0} moet een geldige versie 4 UUID zijn", + override: false, + }, + { + tag: "uuid5", + translation: "{0} moet een geldige versie 5 UUID zijn", + override: false, + }, + { + tag: "ascii", + translation: "{0} mag alleen ascii karakters bevatten", + override: false, + }, + { + tag: "printascii", + translation: "{0} mag alleen afdrukbare ascii karakters bevatten", + override: false, + }, + { + tag: "multibyte", + translation: "{0} moet multibyte karakters bevatten", + override: false, + }, + { + tag: "datauri", + translation: "{0} moet een geldige Data URI bevatten", + override: false, + }, + { + tag: "latitude", + translation: "{0} moet geldige breedtegraadcoördinaten bevatten", + override: false, + }, + { + tag: "longitude", + translation: "{0} moet geldige lengtegraadcoördinaten bevatten", + override: false, + }, + { + tag: "ssn", + translation: "{0} moet een geldig SSN nummer zijn", + override: false, + }, + { + tag: "ipv4", + translation: "{0} moet een geldig IPv4 adres zijn", + override: false, + }, + { + tag: "ipv6", + translation: "{0} moet een geldig IPv6 adres zijn", + override: false, + }, + { + tag: "ip", + translation: "{0} moet een geldig IP adres zijn", + override: false, + }, + { + tag: "cidr", + translation: "{0} moet een geldige CIDR notatie bevatten", + override: false, + }, + { + tag: "cidrv4", + translation: "{0} moet een geldige CIDR notatie voor een IPv4 adres bevatten", + override: false, + }, + { + tag: "cidrv6", + translation: "{0} moet een geldige CIDR notatie voor een IPv6 adres bevatten", + override: false, + }, + { + tag: "tcp_addr", + translation: "{0} moet een geldig TCP adres zijn", + override: false, + }, + { + tag: "tcp4_addr", + translation: "{0} moet een geldig IPv4 TCP adres zijn", + override: false, + }, + { + tag: "tcp6_addr", + translation: "{0} moet een geldig IPv6 TCP adres zijn", + override: false, + }, + { + tag: "udp_addr", + translation: "{0} moet een geldig UDP adres zijn", + override: false, + }, + { + tag: "udp4_addr", + translation: "{0} moet een geldig IPv4 UDP adres zijn", + override: false, + }, + { + tag: "udp6_addr", + translation: "{0} moet een geldig IPv6 UDP adres zijn", + override: false, + }, + { + tag: "ip_addr", + translation: "{0} moet een oplosbaar IP adres zijn", + override: false, + }, + { + tag: "ip4_addr", + translation: "{0} moet een oplosbaar IPv4 adres zijn", + override: false, + }, + { + tag: "ip6_addr", + translation: "{0} moet een oplosbaar IPv6 adres zijn", + override: false, + }, + { + tag: "unix_addr", + translation: "{0} moet een oplosbaar UNIX adres zijn", + override: false, + }, + { + tag: "mac", + translation: "{0} moet een geldig MAC adres bevatten", + override: false, + }, + { + tag: "iscolor", + translation: "{0} moet een geldige kleur zijn", + override: false, + }, + { + tag: "oneof", + translation: "{0} moet een van de volgende zijn [{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/go-playground/validator/v10/translations/nl/nl_test.go b/go-playground/validator/v10/translations/nl/nl_test.go new file mode 100644 index 0000000..4ec720e --- /dev/null +++ b/go-playground/validator/v10/translations/nl/nl_test.go @@ -0,0 +1,634 @@ +package nl + +import ( + "testing" + "time" + + english "gin-valid/go-playground/locales/en" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/go-playground/validator/v10" + . "github.com/go-playground/assert/v2" +) + +func TestTranslations(t *testing.T) { + + eng := english.New() + uni := ut.New(eng, eng) + 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"` + 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"` + } + + 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 + + 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 moet een geldige kleur zijn", + }, + { + ns: "Test.MAC", + expected: "MAC moet een geldig MAC adres bevatten", + }, + { + ns: "Test.IPAddr", + expected: "IPAddr moet een oplosbaar IP adres zijn", + }, + { + ns: "Test.IPAddrv4", + expected: "IPAddrv4 moet een oplosbaar IPv4 adres zijn", + }, + { + ns: "Test.IPAddrv6", + expected: "IPAddrv6 moet een oplosbaar IPv6 adres zijn", + }, + { + ns: "Test.UDPAddr", + expected: "UDPAddr moet een geldig UDP adres zijn", + }, + { + ns: "Test.UDPAddrv4", + expected: "UDPAddrv4 moet een geldig IPv4 UDP adres zijn", + }, + { + ns: "Test.UDPAddrv6", + expected: "UDPAddrv6 moet een geldig IPv6 UDP adres zijn", + }, + { + ns: "Test.TCPAddr", + expected: "TCPAddr moet een geldig TCP adres zijn", + }, + { + ns: "Test.TCPAddrv4", + expected: "TCPAddrv4 moet een geldig IPv4 TCP adres zijn", + }, + { + ns: "Test.TCPAddrv6", + expected: "TCPAddrv6 moet een geldig IPv6 TCP adres zijn", + }, + { + ns: "Test.CIDR", + expected: "CIDR moet een geldige CIDR notatie bevatten", + }, + { + ns: "Test.CIDRv4", + expected: "CIDRv4 moet een geldige CIDR notatie voor een IPv4 adres bevatten", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6 moet een geldige CIDR notatie voor een IPv6 adres bevatten", + }, + { + ns: "Test.SSN", + expected: "SSN moet een geldig SSN nummer zijn", + }, + { + ns: "Test.IP", + expected: "IP moet een geldig IP adres zijn", + }, + { + ns: "Test.IPv4", + expected: "IPv4 moet een geldig IPv4 adres zijn", + }, + { + ns: "Test.IPv6", + expected: "IPv6 moet een geldig IPv6 adres zijn", + }, + { + ns: "Test.DataURI", + expected: "DataURI moet een geldige Data URI bevatten", + }, + { + ns: "Test.Latitude", + expected: "Latitude moet geldige breedtegraadcoördinaten bevatten", + }, + { + ns: "Test.Longitude", + expected: "Longitude moet geldige lengtegraadcoördinaten bevatten", + }, + { + ns: "Test.MultiByte", + expected: "MultiByte moet multibyte karakters bevatten", + }, + { + ns: "Test.ASCII", + expected: "ASCII mag alleen ascii karakters bevatten", + }, + { + ns: "Test.PrintableASCII", + expected: "PrintableASCII mag alleen afdrukbare ascii karakters bevatten", + }, + { + ns: "Test.UUID", + expected: "UUID moet een geldige UUID zijn", + }, + { + ns: "Test.UUID3", + expected: "UUID3 moet een geldige versie 3 UUID zijn", + }, + { + ns: "Test.UUID4", + expected: "UUID4 moet een geldige versie 4 UUID zijn", + }, + { + ns: "Test.UUID5", + expected: "UUID5 moet een geldige versie 5 UUID zijn", + }, + { + ns: "Test.ISBN", + expected: "ISBN moet een geldig ISBN nummer zijn", + }, + { + ns: "Test.ISBN10", + expected: "ISBN10 moet een geldig ISBN-10 nummer zijn", + }, + { + ns: "Test.ISBN13", + expected: "ISBN13 moet een geldig ISBN-13 nummer zijn", + }, + { + ns: "Test.Excludes", + expected: "Excludes mag niet de tekst 'text' bevatten", + }, + { + ns: "Test.ExcludesAll", + expected: "ExcludesAll mag niet een van de volgende karakters bevatten '!@#$'", + }, + { + ns: "Test.ExcludesRune", + expected: "ExcludesRune mag niet het volgende bevatten '☻'", + }, + { + ns: "Test.ContainsAny", + expected: "ContainsAny moet tenminste een van de volgende karakters bevatten '!@#$'", + }, + { + ns: "Test.Contains", + expected: "Contains moet de tekst 'purpose' bevatten", + }, + { + ns: "Test.Base64", + expected: "Base64 moet een geldige Base64 string zijn", + }, + { + ns: "Test.Email", + expected: "Email moet een geldig email adres zijn", + }, + { + ns: "Test.URL", + expected: "URL moet een geldige URL zijn", + }, + { + ns: "Test.URI", + expected: "URI moet een geldige URI zijn", + }, + { + ns: "Test.RGBColorString", + expected: "RGBColorString moet een geldige RGB kleur zijn", + }, + { + ns: "Test.RGBAColorString", + expected: "RGBAColorString moet een geldige RGBA kleur zijn", + }, + { + ns: "Test.HSLColorString", + expected: "HSLColorString moet een geldige HSL kleur zijn", + }, + { + ns: "Test.HSLAColorString", + expected: "HSLAColorString moet een geldige HSLA kleur zijn", + }, + { + ns: "Test.HexadecimalString", + expected: "HexadecimalString moet een geldig hexadecimaal getal zijn", + }, + { + ns: "Test.HexColorString", + expected: "HexColorString moet een geldige HEX kleur zijn", + }, + { + ns: "Test.NumberString", + expected: "NumberString moet een geldig getal zijn", + }, + { + ns: "Test.NumericString", + expected: "NumericString moet een geldige numerieke waarde zijn", + }, + { + ns: "Test.AlphanumString", + expected: "AlphanumString mag alleen alfanumerieke karakters bevatten", + }, + { + ns: "Test.AlphaString", + expected: "AlphaString mag alleen alfabetische karakters bevatten", + }, + { + ns: "Test.LtFieldString", + expected: "LtFieldString moet kleiner zijn dan MaxString", + }, + { + ns: "Test.LteFieldString", + expected: "LteFieldString moet kleiner dan of gelijk aan MaxString zijn", + }, + { + ns: "Test.GtFieldString", + expected: "GtFieldString moet groter zijn dan MaxString", + }, + { + ns: "Test.GteFieldString", + expected: "GteFieldString moet groter dan of gelijk aan MaxString zijn", + }, + { + ns: "Test.NeFieldString", + expected: "NeFieldString mag niet gelijk zijn aan EqFieldString", + }, + { + ns: "Test.LtCSFieldString", + expected: "LtCSFieldString moet kleiner zijn dan Inner.LtCSFieldString", + }, + { + ns: "Test.LteCSFieldString", + expected: "LteCSFieldString moet kleiner dan of gelijk aan Inner.LteCSFieldString zijn", + }, + { + ns: "Test.GtCSFieldString", + expected: "GtCSFieldString moet groter zijn dan Inner.GtCSFieldString", + }, + { + ns: "Test.GteCSFieldString", + expected: "GteCSFieldString moet groter dan of gelijk aan Inner.GteCSFieldString zijn", + }, + { + ns: "Test.NeCSFieldString", + expected: "NeCSFieldString mag niet gelijk zijn aan Inner.NeCSFieldString", + }, + { + ns: "Test.EqCSFieldString", + expected: "EqCSFieldString moet gelijk zijn aan Inner.EqCSFieldString", + }, + { + ns: "Test.EqFieldString", + expected: "EqFieldString moet gelijk zijn aan MaxString", + }, + { + ns: "Test.GteString", + expected: "GteString moet tenminste 3 karakters lang zijn", + }, + { + ns: "Test.GteNumber", + expected: "GteNumber moet 5.56 of groter zijn", + }, + { + ns: "Test.GteMultiple", + expected: "GteMultiple moet tenminste 2 items bevatten", + }, + { + ns: "Test.GteTime", + expected: "GteTime moet groter dan of gelijk zijn aan de huidige datum & tijd", + }, + { + ns: "Test.GtString", + expected: "GtString moet langer dan 3 karakters zijn", + }, + { + ns: "Test.GtNumber", + expected: "GtNumber moet groter zijn dan 5.56", + }, + { + ns: "Test.GtMultiple", + expected: "GtMultiple moet meer dan 2 items bevatten", + }, + { + ns: "Test.GtTime", + expected: "GtTime moet groter zijn dan de huidige datum & tijd", + }, + { + ns: "Test.LteString", + expected: "LteString mag maximaal 3 karakters lang zijn", + }, + { + ns: "Test.LteNumber", + expected: "LteNumber moet 5.56 of minder zijn", + }, + { + ns: "Test.LteMultiple", + expected: "LteMultiple mag maximaal 2 items bevatten", + }, + { + ns: "Test.LteTime", + expected: "LteTime moet kleiner dan of gelijk aan de huidige datum & tijd zijn", + }, + { + ns: "Test.LtString", + expected: "LtString moet minder dan 3 karakters lang zijn", + }, + { + ns: "Test.LtNumber", + expected: "LtNumber moet kleiner zijn dan 5.56", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultiple moet minder dan 2 items bevatten", + }, + { + ns: "Test.LtTime", + expected: "LtTime moet kleiner zijn dan de huidige datum & tijd", + }, + { + ns: "Test.NeString", + expected: "NeString mag niet gelijk zijn aan ", + }, + { + ns: "Test.NeNumber", + expected: "NeNumber mag niet gelijk zijn aan 0.00", + }, + { + ns: "Test.NeMultiple", + expected: "NeMultiple mag niet gelijk zijn aan 0", + }, + { + ns: "Test.EqString", + expected: "EqString is niet gelijk aan 3", + }, + { + ns: "Test.EqNumber", + expected: "EqNumber is niet gelijk aan 2.33", + }, + { + ns: "Test.EqMultiple", + expected: "EqMultiple is niet gelijk aan 7", + }, + { + ns: "Test.MaxString", + expected: "MaxString mag maximaal 3 karakters lang zijn", + }, + { + ns: "Test.MaxNumber", + expected: "MaxNumber moet 1,113.00 of kleiner zijn", + }, + { + ns: "Test.MaxMultiple", + expected: "MaxMultiple mag maximaal 7 items bevatten", + }, + { + ns: "Test.MinString", + expected: "MinString moet tenminste 1 karakter lang zijn", + }, + { + ns: "Test.MinNumber", + expected: "MinNumber moet 1,113.00 of groter zijn", + }, + { + ns: "Test.MinMultiple", + expected: "MinMultiple moet tenminste 7 items bevatten", + }, + { + ns: "Test.LenString", + expected: "LenString moet 1 karakter lang zijn", + }, + { + ns: "Test.LenNumber", + expected: "LenNumber moet gelijk zijn aan 1,113.00", + }, + { + ns: "Test.LenMultiple", + expected: "LenMultiple moet 7 items bevatten", + }, + { + ns: "Test.RequiredString", + expected: "RequiredString is een verplicht veld", + }, + { + ns: "Test.RequiredNumber", + expected: "RequiredNumber is een verplicht veld", + }, + { + ns: "Test.RequiredMultiple", + expected: "RequiredMultiple is een verplicht veld", + }, + { + ns: "Test.StrPtrMinLen", + expected: "StrPtrMinLen moet tenminste 10 karakters lang zijn", + }, + { + ns: "Test.StrPtrMaxLen", + expected: "StrPtrMaxLen mag maximaal 1 karakter lang zijn", + }, + { + ns: "Test.StrPtrLen", + expected: "StrPtrLen moet 2 karakters lang zijn", + }, + { + ns: "Test.StrPtrLt", + expected: "StrPtrLt moet minder dan 1 karakter lang zijn", + }, + { + ns: "Test.StrPtrLte", + expected: "StrPtrLte mag maximaal 1 karakter lang zijn", + }, + { + ns: "Test.StrPtrGt", + expected: "StrPtrGt moet langer dan 10 karakters zijn", + }, + { + ns: "Test.StrPtrGte", + expected: "StrPtrGte moet tenminste 10 karakters lang zijn", + }, + { + ns: "Test.OneOfString", + expected: "OneOfString moet een van de volgende zijn [red green]", + }, + { + ns: "Test.OneOfInt", + expected: "OneOfInt moet een van de volgende zijn [5 63]", + }, + } + + 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)) + } + +} diff --git a/go-playground/validator/v10/translations/pt/pt.go b/go-playground/validator/v10/translations/pt/pt.go new file mode 100644 index 0000000..e3d24a9 --- /dev/null +++ b/go-playground/validator/v10/translations/pt/pt.go @@ -0,0 +1,1405 @@ +package pt + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "gin-valid/go-playground/locales" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/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} é obrigatório", + override: false, + }, + { + tag: "len", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("len-string", "{0} deve ter {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} caractere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("len-number", "{0} deve ser igual a {1}", false); err != nil { + return + } + + if err = ut.Add("len-items", "{0} deve conter {1}", false); err != nil { + return + } + if err = ut.AddCardinal("len-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-items-item", "{0} items", 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 ter pelo menos {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} caractere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("min-number", "{0} deve ser {1} ou superior", false); err != nil { + return + } + + if err = ut.Add("min-items", "{0} deve conter pelo menos {1}", false); err != nil { + return + } + if err = ut.AddCardinal("min-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-items-item", "{0} items", 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 ter no máximo {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} caractere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("max-number", "{0} deve ser {1} ou menos", false); err != nil { + return + } + + if err = ut.Add("max-items", "{0} deve conter no máximo {1}", false); err != nil { + return + } + if err = ut.AddCardinal("max-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-items-item", "{0} items", 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} não é igual a {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} não deve ser igual a {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} deve ter menos de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} caractere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-number", "{0} deve ser menor que {1}", false); err != nil { + return + } + + if err = ut.Add("lt-items", "{0} deve conter menos de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-datetime", "{0} deve ser anterior à data / hora atual", 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 ter no máximo {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} caractere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-number", "{0} deve ser menor ou igual a {1}", false); err != nil { + return + } + + if err = ut.Add("lte-items", "{0} deve conter no máximo {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-datetime", "{0} deve ser anterior ou igual à data/hora atual", 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 conter mais de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} caractere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-number", "{0} deve ser maior que {1}", false); err != nil { + return + } + + if err = ut.Add("gt-items", "{0} deve conter mais de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-datetime", "{0} deve ser posterior à data/hora atual", 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 ter pelo menos {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} caractere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-number", "{0} deve ser maior ou igual a {1}", false); err != nil { + return + } + + if err = ut.Add("gte-items", "{0} deve conter pelo menos {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} items", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-datetime", "{0} deve ser posterior ou igual à data/hora atual", 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 ser igual a {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} deve ser igual a {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} não deve ser igual a {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} deve ser maior que {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} deve ser maior ou igual que {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} deve ser menor que {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} deve ser menor ou igual que {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} não deve ser igual a {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} deve ser maior que {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} deve ser maior ou igual que {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} deve ser menor que {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} deve ser menor ou igual que {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} deve conter apenas caracteres alfabéticos", + override: false, + }, + { + tag: "alphanum", + translation: "{0} deve conter apenas caracteres alfanuméricos", + override: false, + }, + { + tag: "numeric", + translation: "{0} deve ser um valor numérico válido", + override: false, + }, + { + tag: "number", + translation: "{0} deve ser um número válido", + override: false, + }, + { + tag: "hexadecimal", + translation: "{0} deve ser um hexadecimal válido", + override: false, + }, + { + tag: "hexcolor", + translation: "{0} deve ser uma cor HEX válida", + override: false, + }, + { + tag: "rgb", + translation: "{0} deve ser uma cor RGB válida", + override: false, + }, + { + tag: "rgba", + translation: "{0} deve ser uma cor RGBA válida", + override: false, + }, + { + tag: "hsl", + translation: "{0} deve ser uma cor HSL válida", + override: false, + }, + { + tag: "hsla", + translation: "{0} deve ser uma cor HSLA válida", + override: false, + }, + { + tag: "e164", + translation: "{0} deve ser um número de telefone válido no formato E.164", + override: false, + }, + { + tag: "email", + translation: "{0} deve ser um endereço de e-mail válido", + override: false, + }, + { + tag: "url", + translation: "{0} deve ser um URL válido", + override: false, + }, + { + tag: "uri", + translation: "{0} deve ser um URI válido", + override: false, + }, + { + tag: "base64", + translation: "{0} deve ser uma string Base64 válida", + override: false, + }, + { + tag: "contains", + translation: "{0} deve conter o texto '{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} deve conter pelo menos um dos seguintes caracteres '{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} não deve conter o texto '{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} não deve conter os seguintes caracteres '{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} não pode conter o seguinte '{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} deve ser um número de ISBN válido", + override: false, + }, + { + tag: "isbn10", + translation: "{0} deve ser um número ISBN-10 válido", + override: false, + }, + { + tag: "isbn13", + translation: "{0} deve ser um número ISBN-13 válido", + override: false, + }, + { + tag: "uuid", + translation: "{0} deve ser um UUID válido", + override: false, + }, + { + tag: "uuid3", + translation: "{0} deve ser um UUID versão 3 válido", + override: false, + }, + { + tag: "uuid4", + translation: "{0} deve ser um UUID versão 4 válido", + override: false, + }, + { + tag: "uuid5", + translation: "{0} deve ser um UUID versão 5 válido", + override: false, + }, + { + tag: "ascii", + translation: "{0} deve conter apenas caracteres ascii", + override: false, + }, + { + tag: "printascii", + translation: "{0} deve conter apenas caracteres ascii imprimíveis", + override: false, + }, + { + tag: "multibyte", + translation: "{0} deve conter caracteres multibyte", + override: false, + }, + { + tag: "datauri", + translation: "{0} deve conter um Data URI válido", + override: false, + }, + { + tag: "latitude", + translation: "{0} deve conter uma coordenada de latitude válida", + override: false, + }, + { + tag: "longitude", + translation: "{0} deve conter uma coordenada de longitude válida", + override: false, + }, + { + tag: "ssn", + translation: "{0} deve ser um número SSN válido", + override: false, + }, + { + tag: "ipv4", + translation: "{0} deve ser um endereço IPv4 válido", + override: false, + }, + { + tag: "ipv6", + translation: "{0} deve ser um endereço IPv6 válido", + override: false, + }, + { + tag: "ip", + translation: "{0} deve ser um endereço IP válido", + override: false, + }, + { + tag: "cidr", + translation: "{0} não respeita a notação CIDR", + override: false, + }, + { + tag: "cidrv4", + translation: "{0} não respeita a notação CIDR para um endereço IPv4", + override: false, + }, + { + tag: "cidrv6", + translation: "{0} não respeita a notação CIDR para um endereço IPv6", + override: false, + }, + { + tag: "tcp_addr", + translation: "{0} deve ser um endereço TCP válido", + override: false, + }, + { + tag: "tcp4_addr", + translation: "{0} deve ser um endereço TCP IPv4 válido", + override: false, + }, + { + tag: "tcp6_addr", + translation: "{0} deve ser um endereço TCP IPv6 válido", + override: false, + }, + { + tag: "udp_addr", + translation: "{0} deve ser um endereço UDP válido", + override: false, + }, + { + tag: "udp4_addr", + translation: "{0} deve ser um endereço UDP IPv4 válido", + override: false, + }, + { + tag: "udp6_addr", + translation: "{0} deve ser um endereço UDP IPv6 válido", + override: false, + }, + { + tag: "ip_addr", + translation: "{0} deve ser um endereço IP resolvível", + override: false, + }, + { + tag: "ip4_addr", + translation: "{0} deve ser um endereço IPv4 resolvível", + override: false, + }, + { + tag: "ip6_addr", + translation: "{0} deve ser um endereço IPv6 resolvível", + override: false, + }, + { + tag: "unix_addr", + translation: "{0} deve ser um endereço UNIX resolvível", + override: false, + }, + { + tag: "mac", + translation: "{0} deve conter um endereço MAC válido", + override: false, + }, + { + tag: "unique", + translation: "{0} deve conter valores únicos", + override: false, + }, + { + tag: "iscolor", + translation: "{0} deve ser uma cor válida", + override: false, + }, + { + tag: "oneof", + translation: "{0} deve ser um de [{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 + }, + }, + { + tag: "json", + translation: "{0} deve ser uma string json válida", + override: false, + }, + { + tag: "lowercase", + translation: "{0} deve estar em minuscúlas", + override: false, + }, + { + tag: "uppercase", + translation: "{0} deve estar em maiúsculas", + override: false, + }, + { + tag: "datetime", + translation: "{0} não está no formato {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 + }, + }, + } + + 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/go-playground/validator/v10/translations/pt/pt_test.go b/go-playground/validator/v10/translations/pt/pt_test.go new file mode 100644 index 0000000..f8a9c7e --- /dev/null +++ b/go-playground/validator/v10/translations/pt/pt_test.go @@ -0,0 +1,677 @@ +package pt + +import ( + "testing" + "time" + + "gin-valid/go-playground/locales/pt" + ut "gin-valid/go-playground/universal-translator" + . "github.com/go-playground/assert/v2" + + "gin-valid/go-playground/validator/v10" +) + +func TestTranslations(t *testing.T) { + + pt := pt.New() + uni := ut.New(pt, pt) + trans, _ := uni.GetTranslator("pt") + + 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"` + JSONString string `validate:"json"` + LowercaseString string `validate:"lowercase"` + UppercaseString string `validate:"uppercase"` + Datetime string `validate:"datetime=2006-01-02"` + } + + 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" + + 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 ser uma cor válida", + }, + { + ns: "Test.MAC", + expected: "MAC deve conter um endereço MAC válido", + }, + { + ns: "Test.IPAddr", + expected: "IPAddr deve ser um endereço IP resolvível", + }, + { + ns: "Test.IPAddrv4", + expected: "IPAddrv4 deve ser um endereço IPv4 resolvível", + }, + { + ns: "Test.IPAddrv6", + expected: "IPAddrv6 deve ser um endereço IPv6 resolvível", + }, + { + ns: "Test.UDPAddr", + expected: "UDPAddr deve ser um endereço UDP válido", + }, + { + ns: "Test.UDPAddrv4", + expected: "UDPAddrv4 deve ser um endereço UDP IPv4 válido", + }, + { + ns: "Test.UDPAddrv6", + expected: "UDPAddrv6 deve ser um endereço UDP IPv6 válido", + }, + { + ns: "Test.TCPAddr", + expected: "TCPAddr deve ser um endereço TCP válido", + }, + { + ns: "Test.TCPAddrv4", + expected: "TCPAddrv4 deve ser um endereço TCP IPv4 válido", + }, + { + ns: "Test.TCPAddrv6", + expected: "TCPAddrv6 deve ser um endereço TCP IPv6 válido", + }, + { + ns: "Test.CIDR", + expected: "CIDR não respeita a notação CIDR", + }, + { + ns: "Test.CIDRv4", + expected: "CIDRv4 não respeita a notação CIDR para um endereço IPv4", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6 não respeita a notação CIDR para um endereço IPv6", + }, + { + ns: "Test.SSN", + expected: "SSN deve ser um número SSN válido", + }, + { + ns: "Test.IP", + expected: "IP deve ser um endereço IP válido", + }, + { + ns: "Test.IPv4", + expected: "IPv4 deve ser um endereço IPv4 válido", + }, + { + ns: "Test.IPv6", + expected: "IPv6 deve ser um endereço IPv6 válido", + }, + { + ns: "Test.DataURI", + expected: "DataURI deve conter um Data URI válido", + }, + { + ns: "Test.Latitude", + expected: "Latitude deve conter uma coordenada de latitude válida", + }, + { + ns: "Test.Longitude", + expected: "Longitude deve conter uma coordenada de longitude válida", + }, + { + ns: "Test.MultiByte", + expected: "MultiByte deve conter caracteres multibyte", + }, + { + ns: "Test.ASCII", + expected: "ASCII deve conter apenas caracteres ascii", + }, + { + ns: "Test.PrintableASCII", + expected: "PrintableASCII deve conter apenas caracteres ascii imprimíveis", + }, + { + ns: "Test.UUID", + expected: "UUID deve ser um UUID válido", + }, + { + ns: "Test.UUID3", + expected: "UUID3 deve ser um UUID versão 3 válido", + }, + { + ns: "Test.UUID4", + expected: "UUID4 deve ser um UUID versão 4 válido", + }, + { + ns: "Test.UUID5", + expected: "UUID5 deve ser um UUID versão 5 válido", + }, + { + ns: "Test.ISBN", + expected: "ISBN deve ser um número de ISBN válido", + }, + { + ns: "Test.ISBN10", + expected: "ISBN10 deve ser um número ISBN-10 válido", + }, + { + ns: "Test.ISBN13", + expected: "ISBN13 deve ser um número ISBN-13 válido", + }, + { + ns: "Test.Excludes", + expected: "Excludes não deve conter o texto 'text'", + }, + { + ns: "Test.ExcludesAll", + expected: "ExcludesAll não deve conter os seguintes caracteres '!@#$'", + }, + { + ns: "Test.ExcludesRune", + expected: "ExcludesRune não pode conter o seguinte '☻'", + }, + { + ns: "Test.ContainsAny", + expected: "ContainsAny deve conter pelo menos um dos seguintes caracteres '!@#$'", + }, + { + ns: "Test.Contains", + expected: "Contains deve conter o texto 'purpose'", + }, + { + ns: "Test.Base64", + expected: "Base64 deve ser uma string Base64 válida", + }, + { + ns: "Test.Email", + expected: "Email deve ser um endereço de e-mail válido", + }, + { + ns: "Test.URL", + expected: "URL deve ser um URL válido", + }, + { + ns: "Test.URI", + expected: "URI deve ser um URI válido", + }, + { + ns: "Test.RGBColorString", + expected: "RGBColorString deve ser uma cor RGB válida", + }, + { + ns: "Test.RGBAColorString", + expected: "RGBAColorString deve ser uma cor RGBA válida", + }, + { + ns: "Test.HSLColorString", + expected: "HSLColorString deve ser uma cor HSL válida", + }, + { + ns: "Test.HSLAColorString", + expected: "HSLAColorString deve ser uma cor HSLA válida", + }, + { + ns: "Test.HexadecimalString", + expected: "HexadecimalString deve ser um hexadecimal válido", + }, + { + ns: "Test.HexColorString", + expected: "HexColorString deve ser uma cor HEX válida", + }, + { + ns: "Test.NumberString", + expected: "NumberString deve ser um número válido", + }, + { + ns: "Test.NumericString", + expected: "NumericString deve ser um valor numérico válido", + }, + { + ns: "Test.AlphanumString", + expected: "AlphanumString deve conter apenas caracteres alfanuméricos", + }, + { + ns: "Test.AlphaString", + expected: "AlphaString deve conter apenas caracteres alfabéticos", + }, + { + ns: "Test.LtFieldString", + expected: "LtFieldString deve ser menor que MaxString", + }, + { + ns: "Test.LteFieldString", + expected: "LteFieldString deve ser menor ou igual que MaxString", + }, + { + ns: "Test.GtFieldString", + expected: "GtFieldString deve ser maior que MaxString", + }, + { + ns: "Test.GteFieldString", + expected: "GteFieldString deve ser maior ou igual que MaxString", + }, + { + ns: "Test.NeFieldString", + expected: "NeFieldString não deve ser igual a EqFieldString", + }, + { + ns: "Test.LtCSFieldString", + expected: "LtCSFieldString deve ser menor que Inner.LtCSFieldString", + }, + { + ns: "Test.LteCSFieldString", + expected: "LteCSFieldString deve ser menor ou igual que Inner.LteCSFieldString", + }, + { + ns: "Test.GtCSFieldString", + expected: "GtCSFieldString deve ser maior que Inner.GtCSFieldString", + }, + { + ns: "Test.GteCSFieldString", + expected: "GteCSFieldString deve ser maior ou igual que Inner.GteCSFieldString", + }, + { + ns: "Test.NeCSFieldString", + expected: "NeCSFieldString não deve ser igual a Inner.NeCSFieldString", + }, + { + ns: "Test.EqCSFieldString", + expected: "EqCSFieldString deve ser igual a Inner.EqCSFieldString", + }, + { + ns: "Test.EqFieldString", + expected: "EqFieldString deve ser igual a MaxString", + }, + { + ns: "Test.GteString", + expected: "GteString deve ter pelo menos 3 caracteres", + }, + { + ns: "Test.GteNumber", + expected: "GteNumber deve ser maior ou igual a 5,56", + }, + { + ns: "Test.GteMultiple", + expected: "GteMultiple deve conter pelo menos 2 items", + }, + { + ns: "Test.GteTime", + expected: "GteTime deve ser posterior ou igual à data/hora atual", + }, + { + ns: "Test.GtString", + expected: "GtString deve conter mais de 3 caracteres", + }, + { + ns: "Test.GtNumber", + expected: "GtNumber deve ser maior que 5,56", + }, + { + ns: "Test.GtMultiple", + expected: "GtMultiple deve conter mais de 2 items", + }, + { + ns: "Test.GtTime", + expected: "GtTime deve ser posterior à data/hora atual", + }, + { + ns: "Test.LteString", + expected: "LteString deve ter no máximo 3 caracteres", + }, + { + ns: "Test.LteNumber", + expected: "LteNumber deve ser menor ou igual a 5,56", + }, + { + ns: "Test.LteMultiple", + expected: "LteMultiple deve conter no máximo 2 items", + }, + { + ns: "Test.LteTime", + expected: "LteTime deve ser anterior ou igual à data/hora atual", + }, + { + ns: "Test.LtString", + expected: "LtString deve ter menos de 3 caracteres", + }, + { + ns: "Test.LtNumber", + expected: "LtNumber deve ser menor que 5,56", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultiple deve conter menos de 2 items", + }, + { + ns: "Test.LtTime", + expected: "LtTime deve ser anterior à data / hora atual", + }, + { + ns: "Test.NeString", + expected: "NeString não deve ser igual a ", + }, + { + ns: "Test.NeNumber", + expected: "NeNumber não deve ser igual a 0.00", + }, + { + ns: "Test.NeMultiple", + expected: "NeMultiple não deve ser igual a 0", + }, + { + ns: "Test.EqString", + expected: "EqString não é igual a 3", + }, + { + ns: "Test.EqNumber", + expected: "EqNumber não é igual a 2.33", + }, + { + ns: "Test.EqMultiple", + expected: "EqMultiple não é igual a 7", + }, + { + ns: "Test.MaxString", + expected: "MaxString deve ter no máximo 3 caracteres", + }, + { + ns: "Test.MaxNumber", + expected: "MaxNumber deve ser 1.113,00 ou menos", + }, + { + ns: "Test.MaxMultiple", + expected: "MaxMultiple deve conter no máximo 7 items", + }, + { + ns: "Test.MinString", + expected: "MinString deve ter pelo menos 1 caractere", + }, + { + ns: "Test.MinNumber", + expected: "MinNumber deve ser 1.113,00 ou superior", + }, + { + ns: "Test.MinMultiple", + expected: "MinMultiple deve conter pelo menos 7 items", + }, + { + ns: "Test.LenString", + expected: "LenString deve ter 1 caractere", + }, + { + ns: "Test.LenNumber", + expected: "LenNumber deve ser igual a 1.113,00", + }, + { + ns: "Test.LenMultiple", + expected: "LenMultiple deve conter 7 items", + }, + { + ns: "Test.RequiredString", + expected: "RequiredString é obrigatório", + }, + { + ns: "Test.RequiredNumber", + expected: "RequiredNumber é obrigatório", + }, + { + ns: "Test.RequiredMultiple", + expected: "RequiredMultiple é obrigatório", + }, + { + ns: "Test.StrPtrMinLen", + expected: "StrPtrMinLen deve ter pelo menos 10 caracteres", + }, + { + ns: "Test.StrPtrMaxLen", + expected: "StrPtrMaxLen deve ter no máximo 1 caractere", + }, + { + ns: "Test.StrPtrLen", + expected: "StrPtrLen deve ter 2 caracteres", + }, + { + ns: "Test.StrPtrLt", + expected: "StrPtrLt deve ter menos de 1 caractere", + }, + { + ns: "Test.StrPtrLte", + expected: "StrPtrLte deve ter no máximo 1 caractere", + }, + { + ns: "Test.StrPtrGt", + expected: "StrPtrGt deve conter mais de 10 caracteres", + }, + { + ns: "Test.StrPtrGte", + expected: "StrPtrGte deve ter pelo menos 10 caracteres", + }, + { + ns: "Test.OneOfString", + expected: "OneOfString deve ser um de [red green]", + }, + { + ns: "Test.OneOfInt", + expected: "OneOfInt deve ser um de [5 63]", + }, + { + ns: "Test.UniqueSlice", + expected: "UniqueSlice deve conter valores únicos", + }, + { + ns: "Test.UniqueArray", + expected: "UniqueArray deve conter valores únicos", + }, + { + ns: "Test.UniqueMap", + expected: "UniqueMap deve conter valores únicos", + }, + { + ns: "Test.JSONString", + expected: "JSONString deve ser uma string json válida", + }, + { + ns: "Test.LowercaseString", + expected: "LowercaseString deve estar em minuscúlas", + }, + { + ns: "Test.UppercaseString", + expected: "UppercaseString deve estar em maiúsculas", + }, + { + ns: "Test.Datetime", + expected: "Datetime não está no formato 2006-01-02", + }, + } + + 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)) + } + +} diff --git a/go-playground/validator/v10/translations/pt_BR/pt_BR.go b/go-playground/validator/v10/translations/pt_BR/pt_BR.go new file mode 100644 index 0000000..eb06572 --- /dev/null +++ b/go-playground/validator/v10/translations/pt_BR/pt_BR.go @@ -0,0 +1,1365 @@ +package pt_BR + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "gin-valid/go-playground/locales" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/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} é um campo requerido", + override: false, + }, + { + tag: "len", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("len-string", "{0} deve ter {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} caractere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("len-number", "{0} deve ser igual a {1}", false); err != nil { + return + } + + if err = ut.Add("len-items", "{0} deve conter {1}", false); err != nil { + return + } + if err = ut.AddCardinal("len-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-items-item", "{0} itens", 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("alerta: erro na tradução 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 ter pelo menos {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} caractere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("min-number", "{0} deve ser {1} ou superior", false); err != nil { + return + } + + if err = ut.Add("min-items", "{0} deve conter pelo menos {1}", false); err != nil { + return + } + if err = ut.AddCardinal("min-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-items-item", "{0} itens", 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("alerta: erro na tradução 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 ter no máximo {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} caractere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("max-number", "{0} deve ser {1} ou menor", false); err != nil { + return + } + + if err = ut.Add("max-items", "{0} deve conter no máximo {1}", false); err != nil { + return + } + if err = ut.AddCardinal("max-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-items-item", "{0} itens", 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("alerta: erro na tradução FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "eq", + translation: "{0} não é igual a {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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ne", + translation: "{0} não deve ser igual a {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("alerta: erro na tradução 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} deve ter menos de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} caractere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-number", "{0} deve ser menor que {1}", false); err != nil { + return + } + + if err = ut.Add("lt-items", "{0} deve conter menos de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} itens", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-datetime", "{0} deve ser inferior à Data e Hora atual", 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("a tag '%s' não pode ser usada em uma 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("alerta: erro na tradução 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 ter no máximo {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} caractere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-number", "{0} deve ser {1} ou menor", false); err != nil { + return + } + + if err = ut.Add("lte-items", "{0} deve conter no máximo {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} itens", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-datetime", "{0} deve ser menor ou igual à Data e Hora atual", 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("a tag '%s' não pode ser usado em uma 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("alerta: erro na tradução 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 ter mais de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} caractere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-number", "{0} deve ser maior do que {1}", false); err != nil { + return + } + + if err = ut.Add("gt-items", "{0} deve conter mais de {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} itens", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-datetime", "{0} deve ser maior que a Data e Hora atual", 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("a tag '%s' não pode ser usado em uma 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("alerta: erro na tradução 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 ter pelo menos {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} caractere", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} caracteres", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-number", "{0} deve ser {1} ou superior", false); err != nil { + return + } + + if err = ut.Add("gte-items", "{0} deve conter pelo menos {1}", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} item", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} itens", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-datetime", "{0} deve ser maior ou igual à Data e Hora atual", 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("a tag '%s' não pode ser usado em uma 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("alerta: erro na tradução FieldError: %s", err) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "eqfield", + translation: "{0} deve ser igual a {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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "eqcsfield", + translation: "{0} deve ser igual a {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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "necsfield", + translation: "{0} não deve ser igual a {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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gtcsfield", + translation: "{0} deve ser maior do que {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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gtecsfield", + translation: "{0} deve ser maior ou igual a {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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ltcsfield", + translation: "{0} deve ser menor que {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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ltecsfield", + translation: "{0} deve ser menor ou igual a {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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "nefield", + translation: "{0} não deve ser igual a {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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gtfield", + translation: "{0} deve ser maior do que {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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "gtefield", + translation: "{0} deve ser maior ou igual a {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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ltfield", + translation: "{0} deve ser menor que {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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "ltefield", + translation: "{0} deve ser menor ou igual a {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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "alpha", + translation: "{0} deve conter caracteres alfabéticos", + override: false, + }, + { + tag: "alphanum", + translation: "{0} deve conter caracteres alfanuméricos", + override: false, + }, + { + tag: "numeric", + translation: "{0} deve ser um valor numérico válido", + override: false, + }, + { + tag: "number", + translation: "{0} deve ser um número válido", + override: false, + }, + { + tag: "hexadecimal", + translation: "{0} deve ser um hexadecimal válido", + override: false, + }, + { + tag: "hexcolor", + translation: "{0} deve ser uma cor HEX válida", + override: false, + }, + { + tag: "rgb", + translation: "{0} deve ser uma cor RGB válida", + override: false, + }, + { + tag: "rgba", + translation: "{0} deve ser uma cor RGBA válida", + override: false, + }, + { + tag: "hsl", + translation: "{0} deve ser uma cor HSL válida", + override: false, + }, + { + tag: "hsla", + translation: "{0} deve ser uma cor HSLA válida", + override: false, + }, + { + tag: "email", + translation: "{0} deve ser um endereço de e-mail válido", + override: false, + }, + { + tag: "url", + translation: "{0} deve ser uma URL válida", + override: false, + }, + { + tag: "uri", + translation: "{0} deve ser uma URI válida", + override: false, + }, + { + tag: "base64", + translation: "{0} deve ser uma string Base64 válida", + override: false, + }, + { + tag: "contains", + translation: "{0} deve conter o texto '{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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "containsany", + translation: "{0} deve conter pelo menos um dos caracteres '{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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "excludes", + translation: "{0} não deve conter o texto '{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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "excludesall", + translation: "{0} não deve conter nenhum dos caracteres '{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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "excludesrune", + translation: "{0} não deve conter '{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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + { + tag: "isbn", + translation: "{0} deve ser um número ISBN válido", + override: false, + }, + { + tag: "isbn10", + translation: "{0} deve ser um número ISBN-10 válido", + override: false, + }, + { + tag: "isbn13", + translation: "{0} deve ser um número ISBN-13 válido", + override: false, + }, + { + tag: "uuid", + translation: "{0} deve ser um UUID válido", + override: false, + }, + { + tag: "uuid3", + translation: "{0} deve ser um UUID versão 3 válido", + override: false, + }, + { + tag: "uuid4", + translation: "{0} deve ser um UUID versão 4 válido", + override: false, + }, + { + tag: "uuid5", + translation: "{0} deve ser um UUID versão 5 válido", + override: false, + }, + { + tag: "ascii", + translation: "{0} deve conter apenas caracteres ascii", + override: false, + }, + { + tag: "printascii", + translation: "{0} deve conter apenas caracteres ascii imprimíveis", + override: false, + }, + { + tag: "multibyte", + translation: "{0} deve conter caracteres multibyte", + override: false, + }, + { + tag: "datauri", + translation: "{0} deve conter um URI data válido", + override: false, + }, + { + tag: "latitude", + translation: "{0} deve conter uma coordenada de latitude válida", + override: false, + }, + { + tag: "longitude", + translation: "{0} deve conter uma coordenada de longitude válida", + override: false, + }, + { + tag: "ssn", + translation: "{0} deve ser um número SSN válido", + override: false, + }, + { + tag: "ipv4", + translation: "{0} deve ser um endereço IPv4 válido", + override: false, + }, + { + tag: "ipv6", + translation: "{0} deve ser um endereço IPv6 válido", + override: false, + }, + { + tag: "ip", + translation: "{0} deve ser um endereço de IP válido", + override: false, + }, + { + tag: "cidr", + translation: "{0} deve conter uma notação CIDR válida", + override: false, + }, + { + tag: "cidrv4", + translation: "{0} deve conter uma notação CIDR válida para um endereço IPv4", + override: false, + }, + { + tag: "cidrv6", + translation: "{0} deve conter uma notação CIDR válida para um endereço IPv6", + override: false, + }, + { + tag: "tcp_addr", + translation: "{0} deve ser um endereço TCP válido", + override: false, + }, + { + tag: "tcp4_addr", + translation: "{0} deve ser um endereço IPv4 TCP válido", + override: false, + }, + { + tag: "tcp6_addr", + translation: "{0} deve ser um endereço IPv6 TCP válido", + override: false, + }, + { + tag: "udp_addr", + translation: "{0} deve ser um endereço UDP válido", + override: false, + }, + { + tag: "udp4_addr", + translation: "{0} deve ser um endereço IPv4 UDP válido", + override: false, + }, + { + tag: "udp6_addr", + translation: "{0} deve ser um endereço IPv6 UDP válido", + override: false, + }, + { + tag: "ip_addr", + translation: "{0} deve ser um endereço IP resolvível", + override: false, + }, + { + tag: "ip4_addr", + translation: "{0} deve ser um endereço IPv4 resolvível", + override: false, + }, + { + tag: "ip6_addr", + translation: "{0} deve ser um endereço IPv6 resolvível", + override: false, + }, + { + tag: "unix_addr", + translation: "{0} deve ser um endereço UNIX resolvível", + override: false, + }, + { + tag: "mac", + translation: "{0} deve conter um endereço MAC válido", + override: false, + }, + { + tag: "iscolor", + translation: "{0} deve ser uma cor válida", + override: false, + }, + { + tag: "oneof", + translation: "{0} deve ser um de [{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("alerta: erro na tradução 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("alerta: erro na tradução FieldError: %#v", fe) + return fe.(error).Error() + } + + return t +} diff --git a/go-playground/validator/v10/translations/pt_BR/pt_BR_test.go b/go-playground/validator/v10/translations/pt_BR/pt_BR_test.go new file mode 100644 index 0000000..8877e95 --- /dev/null +++ b/go-playground/validator/v10/translations/pt_BR/pt_BR_test.go @@ -0,0 +1,634 @@ +package pt_BR + +import ( + "testing" + "time" + + brazilian_portuguese "gin-valid/go-playground/locales/pt_BR" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/go-playground/validator/v10" + . "github.com/go-playground/assert/v2" +) + +func TestTranslations(t *testing.T) { + + ptbr := brazilian_portuguese.New() + uni := ut.New(ptbr, ptbr) + trans, _ := uni.GetTranslator("pt_BR") + + 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"` + } + + 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 = "este é um texto de teste" + test.ExcludesAll = "Isso é Ótimo!" + test.ExcludesRune = "Amo isso ☻" + + test.ASCII = "カタカナ" + test.PrintableASCII = "カタカナ" + + test.MultiByte = "1234feerf" + + s := "toolong" + test.StrPtrMaxLen = &s + test.StrPtrLen = &s + + 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 ser uma cor válida", + }, + { + ns: "Test.MAC", + expected: "MAC deve conter um endereço MAC válido", + }, + { + ns: "Test.IPAddr", + expected: "IPAddr deve ser um endereço IP resolvível", + }, + { + ns: "Test.IPAddrv4", + expected: "IPAddrv4 deve ser um endereço IPv4 resolvível", + }, + { + ns: "Test.IPAddrv6", + expected: "IPAddrv6 deve ser um endereço IPv6 resolvível", + }, + { + ns: "Test.UDPAddr", + expected: "UDPAddr deve ser um endereço UDP válido", + }, + { + ns: "Test.UDPAddrv4", + expected: "UDPAddrv4 deve ser um endereço IPv4 UDP válido", + }, + { + ns: "Test.UDPAddrv6", + expected: "UDPAddrv6 deve ser um endereço IPv6 UDP válido", + }, + { + ns: "Test.TCPAddr", + expected: "TCPAddr deve ser um endereço TCP válido", + }, + { + ns: "Test.TCPAddrv4", + expected: "TCPAddrv4 deve ser um endereço IPv4 TCP válido", + }, + { + ns: "Test.TCPAddrv6", + expected: "TCPAddrv6 deve ser um endereço IPv6 TCP válido", + }, + { + ns: "Test.CIDR", + expected: "CIDR deve conter uma notação CIDR válida", + }, + { + ns: "Test.CIDRv4", + expected: "CIDRv4 deve conter uma notação CIDR válida para um endereço IPv4", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6 deve conter uma notação CIDR válida para um endereço IPv6", + }, + { + ns: "Test.SSN", + expected: "SSN deve ser um número SSN válido", + }, + { + ns: "Test.IP", + expected: "IP deve ser um endereço de IP válido", + }, + { + ns: "Test.IPv4", + expected: "IPv4 deve ser um endereço IPv4 válido", + }, + { + ns: "Test.IPv6", + expected: "IPv6 deve ser um endereço IPv6 válido", + }, + { + ns: "Test.DataURI", + expected: "DataURI deve conter um URI data válido", + }, + { + ns: "Test.Latitude", + expected: "Latitude deve conter uma coordenada de latitude válida", + }, + { + ns: "Test.Longitude", + expected: "Longitude deve conter uma coordenada de longitude válida", + }, + { + ns: "Test.MultiByte", + expected: "MultiByte deve conter caracteres multibyte", + }, + { + ns: "Test.ASCII", + expected: "ASCII deve conter apenas caracteres ascii", + }, + { + ns: "Test.PrintableASCII", + expected: "PrintableASCII deve conter apenas caracteres ascii imprimíveis", + }, + { + ns: "Test.UUID", + expected: "UUID deve ser um UUID válido", + }, + { + ns: "Test.UUID3", + expected: "UUID3 deve ser um UUID versão 3 válido", + }, + { + ns: "Test.UUID4", + expected: "UUID4 deve ser um UUID versão 4 válido", + }, + { + ns: "Test.UUID5", + expected: "UUID5 deve ser um UUID versão 5 válido", + }, + { + ns: "Test.ISBN", + expected: "ISBN deve ser um número ISBN válido", + }, + { + ns: "Test.ISBN10", + expected: "ISBN10 deve ser um número ISBN-10 válido", + }, + { + ns: "Test.ISBN13", + expected: "ISBN13 deve ser um número ISBN-13 válido", + }, + { + ns: "Test.Excludes", + expected: "Excludes não deve conter o texto 'text'", + }, + { + ns: "Test.ExcludesAll", + expected: "ExcludesAll não deve conter nenhum dos caracteres '!@#$'", + }, + { + ns: "Test.ExcludesRune", + expected: "ExcludesRune não deve conter '☻'", + }, + { + ns: "Test.ContainsAny", + expected: "ContainsAny deve conter pelo menos um dos caracteres '!@#$'", + }, + { + ns: "Test.Contains", + expected: "Contains deve conter o texto 'purpose'", + }, + { + ns: "Test.Base64", + expected: "Base64 deve ser uma string Base64 válida", + }, + { + ns: "Test.Email", + expected: "Email deve ser um endereço de e-mail válido", + }, + { + ns: "Test.URL", + expected: "URL deve ser uma URL válida", + }, + { + ns: "Test.URI", + expected: "URI deve ser uma URI válida", + }, + { + ns: "Test.RGBColorString", + expected: "RGBColorString deve ser uma cor RGB válida", + }, + { + ns: "Test.RGBAColorString", + expected: "RGBAColorString deve ser uma cor RGBA válida", + }, + { + ns: "Test.HSLColorString", + expected: "HSLColorString deve ser uma cor HSL válida", + }, + { + ns: "Test.HSLAColorString", + expected: "HSLAColorString deve ser uma cor HSLA válida", + }, + { + ns: "Test.HexadecimalString", + expected: "HexadecimalString deve ser um hexadecimal válido", + }, + { + ns: "Test.HexColorString", + expected: "HexColorString deve ser uma cor HEX válida", + }, + { + ns: "Test.NumberString", + expected: "NumberString deve ser um número válido", + }, + { + ns: "Test.NumericString", + expected: "NumericString deve ser um valor numérico válido", + }, + { + ns: "Test.AlphanumString", + expected: "AlphanumString deve conter caracteres alfanuméricos", + }, + { + ns: "Test.AlphaString", + expected: "AlphaString deve conter caracteres alfabéticos", + }, + { + ns: "Test.LtFieldString", + expected: "LtFieldString deve ser menor que MaxString", + }, + { + ns: "Test.LteFieldString", + expected: "LteFieldString deve ser menor ou igual a MaxString", + }, + { + ns: "Test.GtFieldString", + expected: "GtFieldString deve ser maior do que MaxString", + }, + { + ns: "Test.GteFieldString", + expected: "GteFieldString deve ser maior ou igual a MaxString", + }, + { + ns: "Test.NeFieldString", + expected: "NeFieldString não deve ser igual a EqFieldString", + }, + { + ns: "Test.LtCSFieldString", + expected: "LtCSFieldString deve ser menor que Inner.LtCSFieldString", + }, + { + ns: "Test.LteCSFieldString", + expected: "LteCSFieldString deve ser menor ou igual a Inner.LteCSFieldString", + }, + { + ns: "Test.GtCSFieldString", + expected: "GtCSFieldString deve ser maior do que Inner.GtCSFieldString", + }, + { + ns: "Test.GteCSFieldString", + expected: "GteCSFieldString deve ser maior ou igual a Inner.GteCSFieldString", + }, + { + ns: "Test.NeCSFieldString", + expected: "NeCSFieldString não deve ser igual a Inner.NeCSFieldString", + }, + { + ns: "Test.EqCSFieldString", + expected: "EqCSFieldString deve ser igual a Inner.EqCSFieldString", + }, + { + ns: "Test.EqFieldString", + expected: "EqFieldString deve ser igual a MaxString", + }, + { + ns: "Test.GteString", + expected: "GteString deve ter pelo menos 3 caracteres", + }, + { + ns: "Test.GteNumber", + expected: "GteNumber deve ser 5,56 ou superior", + }, + { + ns: "Test.GteMultiple", + expected: "GteMultiple deve conter pelo menos 2 itens", + }, + { + ns: "Test.GteTime", + expected: "GteTime deve ser maior ou igual à Data e Hora atual", + }, + { + ns: "Test.GtString", + expected: "GtString deve ter mais de 3 caracteres", + }, + { + ns: "Test.GtNumber", + expected: "GtNumber deve ser maior do que 5,56", + }, + { + ns: "Test.GtMultiple", + expected: "GtMultiple deve conter mais de 2 itens", + }, + { + ns: "Test.GtTime", + expected: "GtTime deve ser maior que a Data e Hora atual", + }, + { + ns: "Test.LteString", + expected: "LteString deve ter no máximo 3 caracteres", + }, + { + ns: "Test.LteNumber", + expected: "LteNumber deve ser 5,56 ou menor", + }, + { + ns: "Test.LteMultiple", + expected: "LteMultiple deve conter no máximo 2 itens", + }, + { + ns: "Test.LteTime", + expected: "LteTime deve ser menor ou igual à Data e Hora atual", + }, + { + ns: "Test.LtString", + expected: "LtString deve ter menos de 3 caracteres", + }, + { + ns: "Test.LtNumber", + expected: "LtNumber deve ser menor que 5,56", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultiple deve conter menos de 2 itens", + }, + { + ns: "Test.LtTime", + expected: "LtTime deve ser inferior à Data e Hora atual", + }, + { + ns: "Test.NeString", + expected: "NeString não deve ser igual a ", + }, + { + ns: "Test.NeNumber", + expected: "NeNumber não deve ser igual a 0.00", + }, + { + ns: "Test.NeMultiple", + expected: "NeMultiple não deve ser igual a 0", + }, + { + ns: "Test.EqString", + expected: "EqString não é igual a 3", + }, + { + ns: "Test.EqNumber", + expected: "EqNumber não é igual a 2.33", + }, + { + ns: "Test.EqMultiple", + expected: "EqMultiple não é igual a 7", + }, + { + ns: "Test.MaxString", + expected: "MaxString deve ter no máximo 3 caracteres", + }, + { + ns: "Test.MaxNumber", + expected: "MaxNumber deve ser 1.113,00 ou menor", + }, + { + ns: "Test.MaxMultiple", + expected: "MaxMultiple deve conter no máximo 7 itens", + }, + { + ns: "Test.MinString", + expected: "MinString deve ter pelo menos 1 caractere", + }, + { + ns: "Test.MinNumber", + expected: "MinNumber deve ser 1.113,00 ou superior", + }, + { + ns: "Test.MinMultiple", + expected: "MinMultiple deve conter pelo menos 7 itens", + }, + { + ns: "Test.LenString", + expected: "LenString deve ter 1 caractere", + }, + { + ns: "Test.LenNumber", + expected: "LenNumber deve ser igual a 1.113,00", + }, + { + ns: "Test.LenMultiple", + expected: "LenMultiple deve conter 7 itens", + }, + { + ns: "Test.RequiredString", + expected: "RequiredString é um campo requerido", + }, + { + ns: "Test.RequiredNumber", + expected: "RequiredNumber é um campo requerido", + }, + { + ns: "Test.RequiredMultiple", + expected: "RequiredMultiple é um campo requerido", + }, + { + ns: "Test.StrPtrMinLen", + expected: "StrPtrMinLen deve ter pelo menos 10 caracteres", + }, + { + ns: "Test.StrPtrMaxLen", + expected: "StrPtrMaxLen deve ter no máximo 1 caractere", + }, + { + ns: "Test.StrPtrLen", + expected: "StrPtrLen deve ter 2 caracteres", + }, + { + ns: "Test.StrPtrLt", + expected: "StrPtrLt deve ter menos de 1 caractere", + }, + { + ns: "Test.StrPtrLte", + expected: "StrPtrLte deve ter no máximo 1 caractere", + }, + { + ns: "Test.StrPtrGt", + expected: "StrPtrGt deve ter mais de 10 caracteres", + }, + { + ns: "Test.StrPtrGte", + expected: "StrPtrGte deve ter pelo menos 10 caracteres", + }, + { + ns: "Test.OneOfString", + expected: "OneOfString deve ser um de [red green]", + }, + { + ns: "Test.OneOfInt", + expected: "OneOfInt deve ser um de [5 63]", + }, + } + + 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)) + } + +} diff --git a/go-playground/validator/v10/translations/ru/ru.go b/go-playground/validator/v10/translations/ru/ru.go new file mode 100644 index 0000000..53a335a --- /dev/null +++ b/go-playground/validator/v10/translations/ru/ru.go @@ -0,0 +1,1375 @@ +package ru + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "gin-valid/go-playground/locales" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/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/go-playground/validator/v10/translations/ru/ru_test.go b/go-playground/validator/v10/translations/ru/ru_test.go new file mode 100644 index 0000000..493c1f4 --- /dev/null +++ b/go-playground/validator/v10/translations/ru/ru_test.go @@ -0,0 +1,656 @@ +package ru + +import ( + "log" + //"github.com/rustery/validator" + "testing" + "time" + + russian "gin-valid/go-playground/locales/en" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/go-playground/validator/v10" + . "github.com/go-playground/assert/v2" +) + +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)) + } + +} diff --git a/go-playground/validator/v10/translations/tr/tr.go b/go-playground/validator/v10/translations/tr/tr.go new file mode 100644 index 0000000..72706ed --- /dev/null +++ b/go-playground/validator/v10/translations/tr/tr.go @@ -0,0 +1,1370 @@ +package tr + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "gin-valid/go-playground/locales" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/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} zorunlu bir alandır", + override: false, + }, + { + tag: "len", + customRegisFunc: func(ut ut.Translator) (err error) { + + if err = ut.Add("len-string", "{0} uzunluğu {1} olmalıdır", false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-string-character", "{0} karakter", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("len-number", "{0}, {1} değerine eşit olmalıdır", false); err != nil { + return + } + + if err = ut.Add("len-items", "{0}, {1} içermelidir", false); err != nil { + return + } + if err = ut.AddCardinal("len-items-item", "{0} öğe", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("len-items-item", "{0} öğe", 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} en az {1} uzunluğunda olmalıdır", false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-string-character", "{0} karakter", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("min-number", "{0}, {1} veya daha büyük olmalıdır", false); err != nil { + return + } + + if err = ut.Add("min-items", "{0} en az {1} içermelidir", false); err != nil { + return + } + if err = ut.AddCardinal("min-items-item", "{0} öğe", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("min-items-item", "{0} öğe", 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} uzunluğu en fazla {1} olmalıdır", false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-string-character", "{0} karakter", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("max-number", "{0}, {1} veya daha az olmalıdır", false); err != nil { + return + } + + if err = ut.Add("max-items", "{0} maksimum {1} içermelidir", false); err != nil { + return + } + if err = ut.AddCardinal("max-items-item", "{0} öğe", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("max-items-item", "{0} öğe", 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} değerine eşit değil", + 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} değerine eşit olmamalıdır", + 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} uzunluğundan daha az olmalıdır", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-string-character", "{0} karakter", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-number", "{0}, {1} değerinden küçük olmalıdır", false); err != nil { + return + } + + if err = ut.Add("lt-items", "{0}, {1}den daha az içermelidir", false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} öğe", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lt-items-item", "{0} öğe", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lt-datetime", "{0} geçerli Tarih ve Saatten daha az olmalıdır", 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} en fazla {1} uzunluğunda olmalıdır", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-string-character", "{0} karakter", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-number", "{0}, {1} veya daha az olmalıdır", false); err != nil { + return + } + + if err = ut.Add("lte-items", "{0}, maksimum {1} içermelidir", false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} öğe", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("lte-items-item", "{0} öğe", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("lte-datetime", "{0} geçerli Tarih ve Saate eşit veya daha küçük olmalıdır", 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} uzunluğundan fazla olmalıdır", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-string-character", "{0} karakter", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-number", "{0}, {1} değerinden büyük olmalıdır", false); err != nil { + return + } + + if err = ut.Add("gt-items", "{0}, {1}den daha fazla içermelidir", false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} öğe", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gt-items-item", "{0} öğe", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gt-datetime", "{0} geçerli Tarih ve Saatten büyük olmalıdır", 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} en az {1} uzunluğunda olmalıdır", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} karakter", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-string-character", "{0} karakter", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-number", "{0}, {1} veya daha büyük olmalıdır", false); err != nil { + return + } + + if err = ut.Add("gte-items", "{0} en az {1} içermelidir", false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} öğe", locales.PluralRuleOne, false); err != nil { + return + } + + if err = ut.AddCardinal("gte-items-item", "{0} öğe", locales.PluralRuleOther, false); err != nil { + return + } + + if err = ut.Add("gte-datetime", "{0} geçerli Tarih ve Saatten büyük veya ona eşit olmalıdır", 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} değerine eşit olmalıdır", + 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} değerine eşit olmalıdır", + 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} değerine eşit olmamalıdır", + 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} değerinden büyük olmalıdır", + 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} değerinden küçük veya ona eşit olmalıdır", + 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} değerinden küçük olmalıdır", + 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} değerinden küçük veya ona eşit olmalıdır", + 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} değerine eşit olmamalıdır", + 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} değerinden büyük olmalıdır", + 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} değerinden büyük veya ona eşit olmalıdır", + 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} değerinden küçük olmalıdır", + 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} değerinden küçük veya ona eşit olmalıdır", + 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} yalnızca alfabetik karakterler içerebilir", + override: false, + }, + { + tag: "alphanum", + translation: "{0} yalnızca alfanümerik karakterler içerebilir", + override: false, + }, + { + tag: "numeric", + translation: "{0} geçerli bir sayısal değer olmalıdır", + override: false, + }, + { + tag: "number", + translation: "{0} geçerli bir sayı olmalıdır", + override: false, + }, + { + tag: "hexadecimal", + translation: "{0} geçerli bir onaltılık olmalıdır", + override: false, + }, + { + tag: "hexcolor", + translation: "{0} geçerli bir HEX rengi olmalıdır", + override: false, + }, + { + tag: "rgb", + translation: "{0} geçerli bir RGB rengi olmalıdır", + override: false, + }, + { + tag: "rgba", + translation: "{0} geçerli bir RGBA rengi olmalıdır", + override: false, + }, + { + tag: "hsl", + translation: "{0} geçerli bir HSL rengi olmalıdır", + override: false, + }, + { + tag: "hsla", + translation: "{0} geçerli bir HSLA rengi olmalıdır", + override: false, + }, + { + tag: "email", + translation: "{0} geçerli bir e-posta adresi olmalıdır", + override: false, + }, + { + tag: "url", + translation: "{0} geçerli bir URL olmalıdır", + override: false, + }, + { + tag: "uri", + translation: "{0} geçerli bir URI olmalıdır", + override: false, + }, + { + tag: "base64", + translation: "{0} geçerli bir Base64 karakter dizesi olmalıdır", + override: false, + }, + { + tag: "contains", + translation: "{0}, '{1}' metnini içermelidir", + 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}' karakterlerinden en az birini içermelidir", + 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}' metnini içeremez", + 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}' karakterlerinden hiçbirini içeremez", + 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}' ifadesini içeremez", + 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} geçerli bir ISBN numarası olmalıdır", + override: false, + }, + { + tag: "isbn10", + translation: "{0} geçerli bir ISBN-10 numarası olmalıdır", + override: false, + }, + { + tag: "isbn13", + translation: "{0} geçerli bir ISBN-13 numarası olmalıdır", + override: false, + }, + { + tag: "uuid", + translation: "{0} geçerli bir UUID olmalıdır", + override: false, + }, + { + tag: "uuid3", + translation: "{0} geçerli bir sürüm 3 UUID olmalıdır", + override: false, + }, + { + tag: "uuid4", + translation: "{0} geçerli bir sürüm 4 UUID olmalıdır", + override: false, + }, + { + tag: "uuid5", + translation: "{0} geçerli bir sürüm 5 UUID olmalıdır", + override: false, + }, + { + tag: "ascii", + translation: "{0} yalnızca ascii karakterler içermelidir", + override: false, + }, + { + tag: "printascii", + translation: "{0} yalnızca yazdırılabilir ascii karakterleri içermelidir", + override: false, + }, + { + tag: "multibyte", + translation: "{0} çok baytlı karakterler içermelidir", + override: false, + }, + { + tag: "datauri", + translation: "{0} geçerli bir Veri URI içermelidir", + override: false, + }, + { + tag: "latitude", + translation: "{0} geçerli bir enlem koordinatı içermelidir", + override: false, + }, + { + tag: "longitude", + translation: "{0} geçerli bir boylam koordinatı içermelidir", + override: false, + }, + { + tag: "ssn", + translation: "{0} geçerli bir SSN numarası olmalıdır", + override: false, + }, + { + tag: "ipv4", + translation: "{0} geçerli bir IPv4 adresi olmalıdır", + override: false, + }, + { + tag: "ipv6", + translation: "{0} geçerli bir IPv6 adresi olmalıdır", + override: false, + }, + { + tag: "ip", + translation: "{0} geçerli bir IP adresi olmalıdır", + override: false, + }, + { + tag: "cidr", + translation: "{0} geçerli bir CIDR gösterimi içermelidir", + override: false, + }, + { + tag: "cidrv4", + translation: "{0} bir IPv4 adresi için geçerli bir CIDR gösterimi içermelidir", + override: false, + }, + { + tag: "cidrv6", + translation: "{0} bir IPv6 adresi için geçerli bir CIDR gösterimi içermelidir", + override: false, + }, + { + tag: "tcp_addr", + translation: "{0} geçerli bir TCP adresi olmalıdır", + override: false, + }, + { + tag: "tcp4_addr", + translation: "{0} geçerli bir IPv4 TCP adresi olmalıdır", + override: false, + }, + { + tag: "tcp6_addr", + translation: "{0} geçerli bir IPv6 TCP adresi olmalıdır", + override: false, + }, + { + tag: "udp_addr", + translation: "{0} geçerli bir UDP adresi olmalıdır", + override: false, + }, + { + tag: "udp4_addr", + translation: "{0} geçerli bir IPv4 UDP adresi olmalıdır", + override: false, + }, + { + tag: "udp6_addr", + translation: "{0} geçerli bir IPv6 UDP adresi olmalıdır", + override: false, + }, + { + tag: "ip_addr", + translation: "{0} çözülebilir bir IP adresi olmalıdır", + override: false, + }, + { + tag: "ip4_addr", + translation: "{0} çözülebilir bir IPv4 adresi olmalıdır", + override: false, + }, + { + tag: "ip6_addr", + translation: "{0} çözülebilir bir IPv6 adresi olmalıdır", + override: false, + }, + { + tag: "unix_addr", + translation: "{0} çözülebilir bir UNIX adresi olmalıdır", + override: false, + }, + { + tag: "mac", + translation: "{0} geçerli bir MAC adresi içermelidir", + override: false, + }, + { + tag: "unique", + translation: "{0} benzersiz değerler içermelidir", + override: false, + }, + { + tag: "iscolor", + translation: "{0} geçerli bir renk olmalıdır", + override: false, + }, + { + tag: "oneof", + translation: "{0}, [{1}]'dan biri olmalıdır", + 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/go-playground/validator/v10/translations/tr/tr_test.go b/go-playground/validator/v10/translations/tr/tr_test.go new file mode 100644 index 0000000..07ee1f7 --- /dev/null +++ b/go-playground/validator/v10/translations/tr/tr_test.go @@ -0,0 +1,652 @@ +package tr + +import ( + "testing" + "time" + + turkish "gin-valid/go-playground/locales/tr" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/go-playground/validator/v10" + . "github.com/go-playground/assert/v2" +) + +func TestTranslations(t *testing.T) { + + tr := turkish.New() + uni := ut.New(tr, tr) + trans, _ := uni.GetTranslator("tr") + + 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 geçerli bir renk olmalıdır", + }, + { + ns: "Test.MAC", + expected: "MAC geçerli bir MAC adresi içermelidir", + }, + { + ns: "Test.IPAddr", + expected: "IPAddr çözülebilir bir IP adresi olmalıdır", + }, + { + ns: "Test.IPAddrv4", + expected: "IPAddrv4 çözülebilir bir IPv4 adresi olmalıdır", + }, + { + ns: "Test.IPAddrv6", + expected: "IPAddrv6 çözülebilir bir IPv6 adresi olmalıdır", + }, + { + ns: "Test.UDPAddr", + expected: "UDPAddr geçerli bir UDP adresi olmalıdır", + }, + { + ns: "Test.UDPAddrv4", + expected: "UDPAddrv4 geçerli bir IPv4 UDP adresi olmalıdır", + }, + { + ns: "Test.UDPAddrv6", + expected: "UDPAddrv6 geçerli bir IPv6 UDP adresi olmalıdır", + }, + { + ns: "Test.TCPAddr", + expected: "TCPAddr geçerli bir TCP adresi olmalıdır", + }, + { + ns: "Test.TCPAddrv4", + expected: "TCPAddrv4 geçerli bir IPv4 TCP adresi olmalıdır", + }, + { + ns: "Test.TCPAddrv6", + expected: "TCPAddrv6 geçerli bir IPv6 TCP adresi olmalıdır", + }, + { + ns: "Test.CIDR", + expected: "CIDR geçerli bir CIDR gösterimi içermelidir", + }, + { + ns: "Test.CIDRv4", + expected: "CIDRv4 bir IPv4 adresi için geçerli bir CIDR gösterimi içermelidir", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6 bir IPv6 adresi için geçerli bir CIDR gösterimi içermelidir", + }, + { + ns: "Test.SSN", + expected: "SSN geçerli bir SSN numarası olmalıdır", + }, + { + ns: "Test.IP", + expected: "IP geçerli bir IP adresi olmalıdır", + }, + { + ns: "Test.IPv4", + expected: "IPv4 geçerli bir IPv4 adresi olmalıdır", + }, + { + ns: "Test.IPv6", + expected: "IPv6 geçerli bir IPv6 adresi olmalıdır", + }, + { + ns: "Test.DataURI", + expected: "DataURI geçerli bir Veri URI içermelidir", + }, + { + ns: "Test.Latitude", + expected: "Latitude geçerli bir enlem koordinatı içermelidir", + }, + { + ns: "Test.Longitude", + expected: "Longitude geçerli bir boylam koordinatı içermelidir", + }, + { + ns: "Test.MultiByte", + expected: "MultiByte çok baytlı karakterler içermelidir", + }, + { + ns: "Test.ASCII", + expected: "ASCII yalnızca ascii karakterler içermelidir", + }, + { + ns: "Test.PrintableASCII", + expected: "PrintableASCII yalnızca yazdırılabilir ascii karakterleri içermelidir", + }, + { + ns: "Test.UUID", + expected: "UUID geçerli bir UUID olmalıdır", + }, + { + ns: "Test.UUID3", + expected: "UUID3 geçerli bir sürüm 3 UUID olmalıdır", + }, + { + ns: "Test.UUID4", + expected: "UUID4 geçerli bir sürüm 4 UUID olmalıdır", + }, + { + ns: "Test.UUID5", + expected: "UUID5 geçerli bir sürüm 5 UUID olmalıdır", + }, + { + ns: "Test.ISBN", + expected: "ISBN geçerli bir ISBN numarası olmalıdır", + }, + { + ns: "Test.ISBN10", + expected: "ISBN10 geçerli bir ISBN-10 numarası olmalıdır", + }, + { + ns: "Test.ISBN13", + expected: "ISBN13 geçerli bir ISBN-13 numarası olmalıdır", + }, + { + ns: "Test.Excludes", + expected: "Excludes, 'text' metnini içeremez", + }, + { + ns: "Test.ExcludesAll", + expected: "ExcludesAll, '!@#$' karakterlerinden hiçbirini içeremez", + }, + { + ns: "Test.ExcludesRune", + expected: "ExcludesRune, '☻' ifadesini içeremez", + }, + { + ns: "Test.ContainsAny", + expected: "ContainsAny, '!@#$' karakterlerinden en az birini içermelidir", + }, + { + ns: "Test.Contains", + expected: "Contains, 'purpose' metnini içermelidir", + }, + { + ns: "Test.Base64", + expected: "Base64 geçerli bir Base64 karakter dizesi olmalıdır", + }, + { + ns: "Test.Email", + expected: "Email geçerli bir e-posta adresi olmalıdır", + }, + { + ns: "Test.URL", + expected: "URL geçerli bir URL olmalıdır", + }, + { + ns: "Test.URI", + expected: "URI geçerli bir URI olmalıdır", + }, + { + ns: "Test.RGBColorString", + expected: "RGBColorString geçerli bir RGB rengi olmalıdır", + }, + { + ns: "Test.RGBAColorString", + expected: "RGBAColorString geçerli bir RGBA rengi olmalıdır", + }, + { + ns: "Test.HSLColorString", + expected: "HSLColorString geçerli bir HSL rengi olmalıdır", + }, + { + ns: "Test.HSLAColorString", + expected: "HSLAColorString geçerli bir HSLA rengi olmalıdır", + }, + { + ns: "Test.HexadecimalString", + expected: "HexadecimalString geçerli bir onaltılık olmalıdır", + }, + { + ns: "Test.HexColorString", + expected: "HexColorString geçerli bir HEX rengi olmalıdır", + }, + { + ns: "Test.NumberString", + expected: "NumberString geçerli bir sayı olmalıdır", + }, + { + ns: "Test.NumericString", + expected: "NumericString geçerli bir sayısal değer olmalıdır", + }, + { + ns: "Test.AlphanumString", + expected: "AlphanumString yalnızca alfanümerik karakterler içerebilir", + }, + { + ns: "Test.AlphaString", + expected: "AlphaString yalnızca alfabetik karakterler içerebilir", + }, + { + ns: "Test.LtFieldString", + expected: "LtFieldString, MaxString değerinden küçük olmalıdır", + }, + { + ns: "Test.LteFieldString", + expected: "LteFieldString, MaxString değerinden küçük veya ona eşit olmalıdır", + }, + { + ns: "Test.GtFieldString", + expected: "GtFieldString, MaxString değerinden büyük olmalıdır", + }, + { + ns: "Test.GteFieldString", + expected: "GteFieldString, MaxString değerinden büyük veya ona eşit olmalıdır", + }, + { + ns: "Test.NeFieldString", + expected: "NeFieldString, EqFieldString değerine eşit olmamalıdır", + }, + { + ns: "Test.LtCSFieldString", + expected: "LtCSFieldString, Inner.LtCSFieldString değerinden küçük olmalıdır", + }, + { + ns: "Test.LteCSFieldString", + expected: "LteCSFieldString, Inner.LteCSFieldString değerinden küçük veya ona eşit olmalıdır", + }, + { + ns: "Test.GtCSFieldString", + expected: "GtCSFieldString, Inner.GtCSFieldString değerinden büyük olmalıdır", + }, + { + ns: "Test.GteCSFieldString", + expected: "GteCSFieldString, Inner.GteCSFieldString değerinden küçük veya ona eşit olmalıdır", + }, + { + ns: "Test.NeCSFieldString", + expected: "NeCSFieldString, Inner.NeCSFieldString değerine eşit olmamalıdır", + }, + { + ns: "Test.EqCSFieldString", + expected: "EqCSFieldString, Inner.EqCSFieldString değerine eşit olmalıdır", + }, + { + ns: "Test.EqFieldString", + expected: "EqFieldString, MaxString değerine eşit olmalıdır", + }, + { + ns: "Test.GteString", + expected: "GteString en az 3 karakter uzunluğunda olmalıdır", + }, + { + ns: "Test.GteNumber", + expected: "GteNumber, 5,56 veya daha büyük olmalıdır", + }, + { + ns: "Test.GteMultiple", + expected: "GteMultiple en az 2 öğe içermelidir", + }, + { + ns: "Test.GteTime", + expected: "GteTime geçerli Tarih ve Saatten büyük veya ona eşit olmalıdır", + }, + { + ns: "Test.GtString", + expected: "GtString, 3 karakter uzunluğundan fazla olmalıdır", + }, + { + ns: "Test.GtNumber", + expected: "GtNumber, 5,56 değerinden büyük olmalıdır", + }, + { + ns: "Test.GtMultiple", + expected: "GtMultiple, 2 öğeden daha fazla içermelidir", + }, + { + ns: "Test.GtTime", + expected: "GtTime geçerli Tarih ve Saatten büyük olmalıdır", + }, + { + ns: "Test.LteString", + expected: "LteString en fazla 3 karakter uzunluğunda olmalıdır", + }, + { + ns: "Test.LteNumber", + expected: "LteNumber, 5,56 veya daha az olmalıdır", + }, + { + ns: "Test.LteMultiple", + expected: "LteMultiple, maksimum 2 öğe içermelidir", + }, + { + ns: "Test.LteTime", + expected: "LteTime geçerli Tarih ve Saate eşit veya daha küçük olmalıdır", + }, + { + ns: "Test.LtString", + expected: "LtString, 3 karakter uzunluğundan daha az olmalıdır", + }, + { + ns: "Test.LtNumber", + expected: "LtNumber, 5,56 değerinden küçük olmalıdır", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultiple, 2 öğeden daha az içermelidir", + }, + { + ns: "Test.LtTime", + expected: "LtTime geçerli Tarih ve Saatten daha az olmalıdır", + }, + { + ns: "Test.NeString", + expected: "NeString, değerine eşit olmamalıdır", + }, + { + ns: "Test.NeNumber", + expected: "NeNumber, 0.00 değerine eşit olmamalıdır", + }, + { + ns: "Test.NeMultiple", + expected: "NeMultiple, 0 değerine eşit olmamalıdır", + }, + { + ns: "Test.EqString", + expected: "EqString, 3 değerine eşit değil", + }, + { + ns: "Test.EqNumber", + expected: "EqNumber, 2.33 değerine eşit değil", + }, + { + ns: "Test.EqMultiple", + expected: "EqMultiple, 7 değerine eşit değil", + }, + { + ns: "Test.MaxString", + expected: "MaxString uzunluğu en fazla 3 karakter olmalıdır", + }, + { + ns: "Test.MaxNumber", + expected: "MaxNumber, 1.113,00 veya daha az olmalıdır", + }, + { + ns: "Test.MaxMultiple", + expected: "MaxMultiple maksimum 7 öğe içermelidir", + }, + { + ns: "Test.MinString", + expected: "MinString en az 1 karakter uzunluğunda olmalıdır", + }, + { + ns: "Test.MinNumber", + expected: "MinNumber, 1.113,00 veya daha büyük olmalıdır", + }, + { + ns: "Test.MinMultiple", + expected: "MinMultiple en az 7 öğe içermelidir", + }, + { + ns: "Test.LenString", + expected: "LenString uzunluğu 1 karakter olmalıdır", + }, + { + ns: "Test.LenNumber", + expected: "LenNumber, 1.113,00 değerine eşit olmalıdır", + }, + { + ns: "Test.LenMultiple", + expected: "LenMultiple, 7 öğe içermelidir", + }, + { + ns: "Test.RequiredString", + expected: "RequiredString zorunlu bir alandır", + }, + { + ns: "Test.RequiredNumber", + expected: "RequiredNumber zorunlu bir alandır", + }, + { + ns: "Test.RequiredMultiple", + expected: "RequiredMultiple zorunlu bir alandır", + }, + { + ns: "Test.StrPtrMinLen", + expected: "StrPtrMinLen en az 10 karakter uzunluğunda olmalıdır", + }, + { + ns: "Test.StrPtrMaxLen", + expected: "StrPtrMaxLen uzunluğu en fazla 1 karakter olmalıdır", + }, + { + ns: "Test.StrPtrLen", + expected: "StrPtrLen uzunluğu 2 karakter olmalıdır", + }, + { + ns: "Test.StrPtrLt", + expected: "StrPtrLt, 1 karakter uzunluğundan daha az olmalıdır", + }, + { + ns: "Test.StrPtrLte", + expected: "StrPtrLte en fazla 1 karakter uzunluğunda olmalıdır", + }, + { + ns: "Test.StrPtrGt", + expected: "StrPtrGt, 10 karakter uzunluğundan fazla olmalıdır", + }, + { + ns: "Test.StrPtrGte", + expected: "StrPtrGte en az 10 karakter uzunluğunda olmalıdır", + }, + { + ns: "Test.OneOfString", + expected: "OneOfString, [red green]'dan biri olmalıdır", + }, + { + ns: "Test.OneOfInt", + expected: "OneOfInt, [5 63]'dan biri olmalıdır", + }, + { + ns: "Test.UniqueSlice", + expected: "UniqueSlice benzersiz değerler içermelidir", + }, + { + ns: "Test.UniqueArray", + expected: "UniqueArray benzersiz değerler içermelidir", + }, + { + ns: "Test.UniqueMap", + expected: "UniqueMap benzersiz değerler içermelidir", + }, + } + + 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)) + } + +} diff --git a/go-playground/validator/v10/translations/zh/zh.go b/go-playground/validator/v10/translations/zh/zh.go new file mode 100644 index 0000000..f681812 --- /dev/null +++ b/go-playground/validator/v10/translations/zh/zh.go @@ -0,0 +1,1392 @@ +package zh + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "gin-valid/go-playground/locales" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/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("警告: 翻译字段错误: %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("警告: 翻译字段错误: %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("警告: 翻译字段错误: %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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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}必须小于当前日期和时间", 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'不能用于struct类型.", fe.Tag()) + } else { + 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("警告: 翻译字段错误: %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} character", 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} item", 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}必须小于或等于当前日期和时间", 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'不能用于struct类型.", fe.Tag()) + } else { + 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("警告: 翻译字段错误: %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'不能用于struct类型.", fe.Tag()) + } else { + + 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("警告: 翻译字段错误: %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'不能用于struct类型.", fe.Tag()) + } else { + 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("警告: 翻译字段错误: %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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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}必须是一个有效的十六进制颜色", + 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: "email", + translation: "{0}必须是一个有效的邮箱", + 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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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("警告: 翻译字段错误: %#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}必须是一个有效的V3 UUID", + override: false, + }, + { + tag: "uuid4", + translation: "{0}必须是一个有效的V4 UUID", + override: false, + }, + { + tag: "uuid5", + translation: "{0}必须是一个有效的V5 UUID", + 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}必须包含有效的数据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}必须是一个包含IPv4地址的有效无类别域间路由(CIDR)", + override: false, + }, + { + tag: "cidrv6", + translation: "{0}必须是一个包含IPv6地址的有效无类别域间路由(CIDR)", + 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: "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("警告: 翻译字段错误: %#v", fe) + return fe.(error).Error() + } + return s + }, + }, + { + tag: "json", + translation: "{0}必须是一个JSON字符串", + override: false, + }, + { + tag: "lowercase", + translation: "{0}必须是小写字母", + override: false, + }, + { + tag: "uppercase", + translation: "{0}必须是大写字母", + override: false, + }, + { + tag: "datetime", + 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("警告: 翻译字段错误: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + } + + 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("警告: 翻译字段错误: %#v", fe) + return fe.(error).Error() + } + + return t +} diff --git a/go-playground/validator/v10/translations/zh/zh_test.go b/go-playground/validator/v10/translations/zh/zh_test.go new file mode 100644 index 0000000..af7ca7a --- /dev/null +++ b/go-playground/validator/v10/translations/zh/zh_test.go @@ -0,0 +1,661 @@ +package zh + +import ( + "testing" + "time" + + zhongwen "gin-valid/go-playground/locales/zh" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/go-playground/validator/v10" + . "github.com/go-playground/assert/v2" +) + +func TestTranslations(t *testing.T) { + + zh := zhongwen.New() + uni := ut.New(zh, zh) + trans, _ := uni.GetTranslator("zh") + + 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"` + JsonString string `validate:"json"` + LowercaseString string `validate:"lowercase"` + UppercaseString string `validate:"uppercase"` + Datetime string `validate:"datetime=2006-01-02"` + } + + 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.JsonString = "{\"foo\":\"bar\",}" + + test.LowercaseString = "ABCDEFG" + test.UppercaseString = "abcdefg" + + test.Datetime = "20060102" + + 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必须是一个包含IPv4地址的有效无类别域间路由(CIDR)", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6必须是一个包含IPv6地址的有效无类别域间路由(CIDR)", + }, + { + 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必须包含有效的数据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必须是一个有效的V3 UUID", + }, + { + ns: "Test.UUID4", + expected: "UUID4必须是一个有效的V4 UUID", + }, + { + ns: "Test.UUID5", + expected: "UUID5必须是一个有效的V5 UUID", + }, + { + 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必须是一个有效的邮箱", + }, + { + 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必须是一个有效的十六进制颜色", + }, + { + 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必须小于或等于当前日期和时间", + }, + { + ns: "Test.LtString", + expected: "LtString长度必须小于3个字符", + }, + { + ns: "Test.LtNumber", + expected: "LtNumber必须小于5.56", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultiple必须包含少于2项", + }, + { + ns: "Test.LtTime", + expected: "LtTime必须小于当前日期和时间", + }, + { + 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.JsonString", + expected: "JsonString必须是一个JSON字符串", + }, + { + ns: "Test.LowercaseString", + expected: "LowercaseString必须是小写字母", + }, + { + ns: "Test.UppercaseString", + expected: "UppercaseString必须是大写字母", + }, + { + ns: "Test.Datetime", + expected: "Datetime的格式必须是2006-01-02", + }, + } + + 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)) + } + +} diff --git a/go-playground/validator/v10/translations/zh_tw/zh_tw.go b/go-playground/validator/v10/translations/zh_tw/zh_tw.go new file mode 100644 index 0000000..1e49efb --- /dev/null +++ b/go-playground/validator/v10/translations/zh_tw/zh_tw.go @@ -0,0 +1,1373 @@ +package zh_tw + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + "time" + + "gin-valid/go-playground/locales" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/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("警告: 翻譯欄位錯誤: %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("警告: 翻譯欄位錯誤: %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("警告: 翻譯欄位錯誤: %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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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}必須小於目前日期和時間", 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'不能用於struct類型.", fe.Tag()) + } else { + 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("警告: 翻譯欄位錯誤: %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} character", 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} item", 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}必須小於或等於目前日期和時間", 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'不能用於struct類型.", fe.Tag()) + } else { + 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("警告: 翻譯欄位錯誤: %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'不能用於struct類型.", fe.Tag()) + } else { + 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("警告: 翻譯欄位錯誤: %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'不能用於struct類型.", fe.Tag()) + } else { + 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("警告: 翻譯欄位錯誤: %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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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}必須是一個有效的十六進制顏色", + 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: "email", + translation: "{0}必須是一個有效的信箱", + 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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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("警告: 翻譯欄位錯誤: %#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}必須是一個有效的V3 UUID", + override: false, + }, + { + tag: "uuid4", + translation: "{0}必須是一個有效的V4 UUID", + override: false, + }, + { + tag: "uuid5", + translation: "{0}必須是一個有效的V5 UUID", + 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}必須包含有效的數據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}必須是一个包含IPv4地址的有效無類別域間路由(CIDR)", + override: false, + }, + { + tag: "cidrv6", + translation: "{0}必須是一个包含IPv6地址的有效無類別域間路由(CIDR)", + 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: "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("警告: 翻譯欄位錯誤: %#v", fe) + return fe.(error).Error() + } + return s + }, + }, + { + tag: "datetime", + 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("警告: 翻譯欄位錯誤: %#v", fe) + return fe.(error).Error() + } + + return t + }, + }, + } + + 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("警告: 翻譯欄位錯誤: %#v", fe) + return fe.(error).Error() + } + + return t +} diff --git a/go-playground/validator/v10/translations/zh_tw/zh_tw_test.go b/go-playground/validator/v10/translations/zh_tw/zh_tw_test.go new file mode 100644 index 0000000..b011ae7 --- /dev/null +++ b/go-playground/validator/v10/translations/zh_tw/zh_tw_test.go @@ -0,0 +1,641 @@ +package zh_tw + +import ( + "testing" + "time" + + zhongwen "gin-valid/go-playground/locales/zh_Hant_TW" + ut "gin-valid/go-playground/universal-translator" + "gin-valid/go-playground/validator/v10" + . "github.com/go-playground/assert/v2" +) + +func TestTranslations(t *testing.T) { + + zh := zhongwen.New() + uni := ut.New(zh, zh) + trans, _ := uni.GetTranslator("zh") + + 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"` + Datetime string `validate:"datetime=2006-01-02"` + } + + 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.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必須是一個有效的顏色", + }, + { + 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必須是一个包含IPv4地址的有效無類別域間路由(CIDR)", + }, + { + ns: "Test.CIDRv6", + expected: "CIDRv6必須是一个包含IPv6地址的有效無類別域間路由(CIDR)", + }, + { + 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必須包含有效的數據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必須是一個有效的V3 UUID", + }, + { + ns: "Test.UUID4", + expected: "UUID4必須是一個有效的V4 UUID", + }, + { + ns: "Test.UUID5", + expected: "UUID5必須是一個有效的V5 UUID", + }, + { + 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必須是一個有效的信箱", + }, + { + 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必須是一個有效的十六進制顏色", + }, + { + 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必須小於或等於目前日期和時間", + }, + { + ns: "Test.LtString", + expected: "LtString長度必須小於3個字元", + }, + { + ns: "Test.LtNumber", + expected: "LtNumber必須小於5.56", + }, + { + ns: "Test.LtMultiple", + expected: "LtMultiple必須包含少於2項", + }, + { + ns: "Test.LtTime", + expected: "LtTime必須小於目前日期和時間", + }, + { + 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.Datetime", + expected: "Datetime與2006-01-02格式不匹配", + }, + } + + 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)) + } + +} diff --git a/go-playground/validator/v10/util.go b/go-playground/validator/v10/util.go new file mode 100644 index 0000000..cb564e3 --- /dev/null +++ b/go-playground/validator/v10/util.go @@ -0,0 +1,288 @@ +package validator + +import ( + "reflect" + "strconv" + "strings" + "time" +) + +// extractTypeInternal gets the actual underlying type of field value. +// It will dive into pointers, customTypes and return you the +// underlying value and it's kind. +func (v *validate) extractTypeInternal(current reflect.Value, nullable bool) (reflect.Value, reflect.Kind, bool) { + +BEGIN: + switch current.Kind() { + case reflect.Ptr: + + nullable = true + + if current.IsNil() { + return current, reflect.Ptr, nullable + } + + current = current.Elem() + goto BEGIN + + case reflect.Interface: + + nullable = true + + if current.IsNil() { + return current, reflect.Interface, nullable + } + + current = current.Elem() + goto BEGIN + + case reflect.Invalid: + return current, reflect.Invalid, nullable + + default: + + if v.v.hasCustomFuncs { + + if fn, ok := v.v.customFuncs[current.Type()]; ok { + current = reflect.ValueOf(fn(current)) + goto BEGIN + } + } + + return current, current.Kind(), nullable + } +} + +// getStructFieldOKInternal traverses a struct to retrieve a specific field denoted by the provided namespace and +// returns the field, field kind and whether is was successful in retrieving the field at all. +// +// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field +// could not be retrieved because it didn't exist. +func (v *validate) getStructFieldOKInternal(val reflect.Value, namespace string) (current reflect.Value, kind reflect.Kind, nullable bool, found bool) { + // val 是结构体, 如果 gtfield=filed1 这种field的比较,那 namespace就是入参 filed1 +BEGIN: + current, kind, nullable = v.ExtractType(val) + if kind == reflect.Invalid { + return + } + + if namespace == "" { + found = true + return + } + + switch kind { + + case reflect.Ptr, reflect.Interface: + return + + case reflect.Struct: + + typ := current.Type() + fld := namespace + var ns string + + if typ != timeType { + + idx := strings.Index(namespace, namespaceSeparator) + + if idx != -1 { + fld = namespace[:idx] + ns = namespace[idx+1:] + } else { + ns = "" + } + + bracketIdx := strings.Index(fld, leftBracket) + if bracketIdx != -1 { + fld = fld[:bracketIdx] + + ns = namespace[bracketIdx:] + } + + val = current.FieldByName(fld) + namespace = ns + goto BEGIN + } + + case reflect.Array, reflect.Slice: + idx := strings.Index(namespace, leftBracket) + idx2 := strings.Index(namespace, rightBracket) + + arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2]) + + if arrIdx >= current.Len() { + return + } + + startIdx := idx2 + 1 + + if startIdx < len(namespace) { + if namespace[startIdx:startIdx+1] == namespaceSeparator { + startIdx++ + } + } + + val = current.Index(arrIdx) + namespace = namespace[startIdx:] + goto BEGIN + + case reflect.Map: + idx := strings.Index(namespace, leftBracket) + 1 + idx2 := strings.Index(namespace, rightBracket) + + endIdx := idx2 + + if endIdx+1 < len(namespace) { + if namespace[endIdx+1:endIdx+2] == namespaceSeparator { + endIdx++ + } + } + + key := namespace[idx:idx2] + + switch current.Type().Key().Kind() { + case reflect.Int: + i, _ := strconv.Atoi(key) + val = current.MapIndex(reflect.ValueOf(i)) + namespace = namespace[endIdx+1:] + + case reflect.Int8: + i, _ := strconv.ParseInt(key, 10, 8) + val = current.MapIndex(reflect.ValueOf(int8(i))) + namespace = namespace[endIdx+1:] + + case reflect.Int16: + i, _ := strconv.ParseInt(key, 10, 16) + val = current.MapIndex(reflect.ValueOf(int16(i))) + namespace = namespace[endIdx+1:] + + case reflect.Int32: + i, _ := strconv.ParseInt(key, 10, 32) + val = current.MapIndex(reflect.ValueOf(int32(i))) + namespace = namespace[endIdx+1:] + + case reflect.Int64: + i, _ := strconv.ParseInt(key, 10, 64) + val = current.MapIndex(reflect.ValueOf(i)) + namespace = namespace[endIdx+1:] + + case reflect.Uint: + i, _ := strconv.ParseUint(key, 10, 0) + val = current.MapIndex(reflect.ValueOf(uint(i))) + namespace = namespace[endIdx+1:] + + case reflect.Uint8: + i, _ := strconv.ParseUint(key, 10, 8) + val = current.MapIndex(reflect.ValueOf(uint8(i))) + namespace = namespace[endIdx+1:] + + case reflect.Uint16: + i, _ := strconv.ParseUint(key, 10, 16) + val = current.MapIndex(reflect.ValueOf(uint16(i))) + namespace = namespace[endIdx+1:] + + case reflect.Uint32: + i, _ := strconv.ParseUint(key, 10, 32) + val = current.MapIndex(reflect.ValueOf(uint32(i))) + namespace = namespace[endIdx+1:] + + case reflect.Uint64: + i, _ := strconv.ParseUint(key, 10, 64) + val = current.MapIndex(reflect.ValueOf(i)) + namespace = namespace[endIdx+1:] + + case reflect.Float32: + f, _ := strconv.ParseFloat(key, 32) + val = current.MapIndex(reflect.ValueOf(float32(f))) + namespace = namespace[endIdx+1:] + + case reflect.Float64: + f, _ := strconv.ParseFloat(key, 64) + val = current.MapIndex(reflect.ValueOf(f)) + namespace = namespace[endIdx+1:] + + case reflect.Bool: + b, _ := strconv.ParseBool(key) + val = current.MapIndex(reflect.ValueOf(b)) + namespace = namespace[endIdx+1:] + + // reflect.Type = string + default: + val = current.MapIndex(reflect.ValueOf(key)) + namespace = namespace[endIdx+1:] + } + + goto BEGIN + } + + // if got here there was more namespace, cannot go any deeper + panic("Invalid field namespace") +} + +// asInt returns the parameter as a int64 +// or panics if it can't convert +func asInt(param string) int64 { + i, err := strconv.ParseInt(param, 0, 64) + panicIf(err) + + return i +} + +// asIntFromTimeDuration parses param as time.Duration and returns it as int64 +// or panics on error. +func asIntFromTimeDuration(param string) int64 { + d, err := time.ParseDuration(param) + if err != nil { + // attempt parsing as an an integer assuming nanosecond precision + return asInt(param) + } + return int64(d) +} + +// asIntFromType calls the proper function to parse param as int64, +// given a field's Type t. +func asIntFromType(t reflect.Type, param string) int64 { + switch t { + case timeDurationType: + return asIntFromTimeDuration(param) + default: + return asInt(param) + } +} + +// asUint returns the parameter as a uint64 +// or panics if it can't convert +func asUint(param string) uint64 { + + i, err := strconv.ParseUint(param, 0, 64) + panicIf(err) + + return i +} + +// asFloat returns the parameter as a float64 +// or panics if it can't convert +func asFloat(param string) float64 { + + i, err := strconv.ParseFloat(param, 64) + panicIf(err) + + return i +} + +// asBool returns the parameter as a bool +// or panics if it can't convert +func asBool(param string) bool { + + i, err := strconv.ParseBool(param) + panicIf(err) + + return i +} + +func panicIf(err error) { + if err != nil { + panic(err.Error()) + } +} diff --git a/go-playground/validator/v10/validator.go b/go-playground/validator/v10/validator.go new file mode 100644 index 0000000..5baacfe --- /dev/null +++ b/go-playground/validator/v10/validator.go @@ -0,0 +1,482 @@ +package validator + +import ( + "context" + "fmt" + "reflect" + "strconv" +) + +// per validate construct +type validate struct { + v *Validate + top reflect.Value + ns []byte // ns是命名空间,见 + actualNs []byte + // 如果想修改 错误信息的打印, 直接改这个错误的 .Error()方法应该就可以了, 不对不对,还有一个翻译在执行 + errs ValidationErrors //校验失败的 记录错误的 结构体的 field[] , 依次验证field,所以可以加个判断,只要有一个错误,就停下来 //每次用完都会清零,在put回pool + includeExclude map[string]struct{} // reset only if StructPartial or StructExcept are called, no need otherwise + ffn FilterFunc + slflParent reflect.Value // StructLevel & FieldLevel + slCurrent reflect.Value // StructLevel & FieldLevel + flField reflect.Value // StructLevel & FieldLevel + cf *cField // StructLevel & FieldLevel + ct *cTag // StructLevel & FieldLevel + misc []byte // misc reusable + str1 string // misc reusable // 真正显示的 错误,有别名就是别名 + str2 string // misc reusable // 失败的field信息,如 User.Name (结构体名称+field真名称),如果没有别名,就是str1 + fldIsPointer bool // StructLevel & FieldLevel + isPartial bool + hasExcludes bool +} + +// parent and current will be the same the first run of validateStruct +func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) { + + cs, ok := v.v.structCache.Get(typ) + if !ok { + cs = v.v.extractStructCache(current, typ.Name()) + } + + if len(ns) == 0 && len(cs.name) != 0 { + // 但实际上,`:` 左边的我都不需要,所以还是改 翻译 比较好 , 而且可能会影响到 未知的错误 + ns = append(ns, cs.name...) // 去掉这里的ns的内容,出现错误时,应该就就不会输出 struct.field:"err", 而是 field:"err", + ns = append(ns, '.') + + structNs = append(structNs, cs.name...) + structNs = append(structNs, '.') + } + + // ct is nil on top level struct, and structs as fields that have no tag info + // so if nil or if not nil and the structonly tag isn't present + if ct == nil || ct.typeof != typeStructOnly { + + var f *cField + + for i := 0; i < len(cs.fields); i++ { + // yang 加个判断, 有一个验证不通过就退出 + if len(v.errs) != 0 { + break + } + // yang 修改分割线 + f = cs.fields[i] + + if v.isPartial { + + if v.ffn != nil { + // used with StructFiltered + if v.ffn(append(structNs, f.name...)) { + continue + } + + } else { + // used with StructPartial & StructExcept + _, ok = v.includeExclude[string(append(structNs, f.name...))] + + if (ok && v.hasExcludes) || (!ok && !v.hasExcludes) { + continue + } + } + } + + v.traverseField(ctx, parent, current.Field(f.idx), ns, structNs, f, f.cTags) + } + } + + // check if any struct level validations, after all field validations already checked. + // first iteration will have no info about nostructlevel tag, and is checked prior to + // calling the next iteration of validateStruct called from traverseField. + if cs.fn != nil { + + v.slflParent = parent + v.slCurrent = current + v.ns = ns + v.actualNs = structNs + + cs.fn(ctx, v) + } +} + +// traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options +func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) { + var typ reflect.Type + var kind reflect.Kind + + current, kind, v.fldIsPointer = v.extractTypeInternal(current, false) + + switch kind { + case reflect.Ptr, reflect.Interface, reflect.Invalid: + + if ct == nil { + return + } + + if ct.typeof == typeOmitEmpty || ct.typeof == typeIsDefault { + return + } + + if ct.hasTag { + if kind == reflect.Invalid { + v.str1 = string(append(ns, cf.altName...)) + if v.v.hasTagNameFunc { + v.str2 = string(append(structNs, cf.name...)) + } else { + v.str2 = v.str1 + } + v.errs = append(v.errs, + &fieldError{ + v: v.v, + tag: ct.aliasTag, + actualTag: ct.tag, + ns: v.str1, + structNs: v.str2, + fieldLen: uint8(len(cf.altName)), + structfieldLen: uint8(len(cf.name)), + param: ct.param, + kind: kind, + }, + ) + return + } + + v.str1 = string(append(ns, cf.altName...)) + if v.v.hasTagNameFunc { + v.str2 = string(append(structNs, cf.name...)) + } else { + v.str2 = v.str1 + } + if !ct.runValidationWhenNil { + v.errs = append(v.errs, + &fieldError{ + v: v.v, + tag: ct.aliasTag, + actualTag: ct.tag, + ns: v.str1, + structNs: v.str2, + fieldLen: uint8(len(cf.altName)), + structfieldLen: uint8(len(cf.name)), + value: current.Interface(), + param: ct.param, + kind: kind, + typ: current.Type(), + }, + ) + return + } + } + + case reflect.Struct: + + typ = current.Type() + + if typ != timeType { + + if ct != nil { + + if ct.typeof == typeStructOnly { + goto CONTINUE + } else if ct.typeof == typeIsDefault { + // set Field Level fields + v.slflParent = parent + v.flField = current + v.cf = cf + v.ct = ct + + if !ct.fn(ctx, v) { + v.str1 = string(append(ns, cf.altName...)) + + if v.v.hasTagNameFunc { + v.str2 = string(append(structNs, cf.name...)) + } else { + v.str2 = v.str1 + } + + v.errs = append(v.errs, + &fieldError{ + v: v.v, + tag: ct.aliasTag, + actualTag: ct.tag, + ns: v.str1, + structNs: v.str2, + fieldLen: uint8(len(cf.altName)), + structfieldLen: uint8(len(cf.name)), + value: current.Interface(), + param: ct.param, + kind: kind, + typ: typ, + }, + ) + return + } + } + + ct = ct.next + } + + if ct != nil && ct.typeof == typeNoStructLevel { + return + } + + CONTINUE: + // if len == 0 then validating using 'Var' or 'VarWithValue' + // Var - doesn't make much sense to do it that way, should call 'Struct', but no harm... + // VarWithField - this allows for validating against each field within the struct against a specific value + // pretty handy in certain situations + if len(cf.name) > 0 { + ns = append(append(ns, cf.altName...), '.') + structNs = append(append(structNs, cf.name...), '.') + } + + v.validateStruct(ctx, current, current, typ, ns, structNs, ct) + return + } + } + + if !ct.hasTag { + return + } + + typ = current.Type() + +OUTER: + for { + if ct == nil { + return + } + + switch ct.typeof { + + case typeOmitEmpty: + + // set Field Level fields + v.slflParent = parent + v.flField = current + v.cf = cf + v.ct = ct + + if !hasValue(v) { + return + } + + ct = ct.next + continue + + case typeEndKeys: + return + + case typeDive: + + ct = ct.next + + // traverse slice or map here + // or panic ;) + switch kind { + case reflect.Slice, reflect.Array: + + var i64 int64 + reusableCF := &cField{} + + for i := 0; i < current.Len(); i++ { + + i64 = int64(i) + + v.misc = append(v.misc[0:0], cf.name...) + v.misc = append(v.misc, '[') + v.misc = strconv.AppendInt(v.misc, i64, 10) + v.misc = append(v.misc, ']') + + reusableCF.name = string(v.misc) + + if cf.namesEqual { + reusableCF.altName = reusableCF.name + } else { + + v.misc = append(v.misc[0:0], cf.altName...) + v.misc = append(v.misc, '[') + v.misc = strconv.AppendInt(v.misc, i64, 10) + v.misc = append(v.misc, ']') + + reusableCF.altName = string(v.misc) + } + v.traverseField(ctx, parent, current.Index(i), ns, structNs, reusableCF, ct) + } + + case reflect.Map: + + var pv string + reusableCF := &cField{} + + for _, key := range current.MapKeys() { + + pv = fmt.Sprintf("%v", key.Interface()) + + v.misc = append(v.misc[0:0], cf.name...) + v.misc = append(v.misc, '[') + v.misc = append(v.misc, pv...) + v.misc = append(v.misc, ']') + + reusableCF.name = string(v.misc) + + if cf.namesEqual { + reusableCF.altName = reusableCF.name + } else { + v.misc = append(v.misc[0:0], cf.altName...) + v.misc = append(v.misc, '[') + v.misc = append(v.misc, pv...) + v.misc = append(v.misc, ']') + + reusableCF.altName = string(v.misc) + } + + if ct != nil && ct.typeof == typeKeys && ct.keys != nil { + v.traverseField(ctx, parent, key, ns, structNs, reusableCF, ct.keys) + // can be nil when just keys being validated + if ct.next != nil { + v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct.next) + } + } else { + v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct) + } + } + + default: + // throw error, if not a slice or map then should not have gotten here + // bad dive tag + panic("dive error! can't dive on a non slice or map") + } + + return + + case typeOr: + + v.misc = v.misc[0:0] + + for { + + // set Field Level fields + v.slflParent = parent + v.flField = current + v.cf = cf + v.ct = ct + + if ct.fn(ctx, v) { + + // drain rest of the 'or' values, then continue or leave + for { + + ct = ct.next + + if ct == nil { + return + } + + if ct.typeof != typeOr { + continue OUTER + } + } + } + + v.misc = append(v.misc, '|') + v.misc = append(v.misc, ct.tag...) + + if ct.hasParam { + v.misc = append(v.misc, '=') + v.misc = append(v.misc, ct.param...) + } + + if ct.isBlockEnd || ct.next == nil { + // if we get here, no valid 'or' value and no more tags + v.str1 = string(append(ns, cf.altName...)) + + if v.v.hasTagNameFunc { + v.str2 = string(append(structNs, cf.name...)) + } else { + v.str2 = v.str1 + } + + if ct.hasAlias { + + v.errs = append(v.errs, + &fieldError{ + v: v.v, + tag: ct.aliasTag, + actualTag: ct.actualAliasTag, + ns: v.str1, + structNs: v.str2, + fieldLen: uint8(len(cf.altName)), + structfieldLen: uint8(len(cf.name)), + value: current.Interface(), + param: ct.param, + kind: kind, + typ: typ, + }, + ) + + } else { + + tVal := string(v.misc)[1:] + + v.errs = append(v.errs, + &fieldError{ + v: v.v, + tag: tVal, + actualTag: tVal, + ns: v.str1, + structNs: v.str2, + fieldLen: uint8(len(cf.altName)), + structfieldLen: uint8(len(cf.name)), + value: current.Interface(), + param: ct.param, + kind: kind, + typ: typ, + }, + ) + } + + return + } + + ct = ct.next + } + + default: + + // set Field Level fields + v.slflParent = parent + v.flField = current + v.cf = cf + v.ct = ct + + if !ct.fn(ctx, v) { // 这是 验证函数的进行校验, 不成功就会记录下来 + + v.str1 = string(append(ns, cf.altName...)) //如果不想要 命名空间,就不要拼接 ns + + if v.v.hasTagNameFunc { + v.str2 = string(append(structNs, cf.name...)) + } else { + v.str2 = v.str1 + } + + v.errs = append(v.errs, + &fieldError{ + v: v.v, + tag: ct.aliasTag, + actualTag: ct.tag, + ns: v.str1, + structNs: v.str2, + fieldLen: uint8(len(cf.altName)), + structfieldLen: uint8(len(cf.name)), + value: current.Interface(), + param: ct.param, + kind: kind, + typ: typ, + }, + ) + + return + } + ct = ct.next + } + } + +} diff --git a/go-playground/validator/v10/validator_instance.go b/go-playground/validator/v10/validator_instance.go new file mode 100644 index 0000000..1855290 --- /dev/null +++ b/go-playground/validator/v10/validator_instance.go @@ -0,0 +1,619 @@ +package validator + +import ( + "context" + "errors" + "fmt" + "reflect" + "strings" + "sync" + "time" + + ut "gin-valid/go-playground/universal-translator" +) + +const ( + defaultTagName = "validate" + utf8HexComma = "0x2C" + utf8Pipe = "0x7C" + tagSeparator = "," // tag参数 的分离符号, + orSeparator = "|" // 或者 的分离符号 + tagKeySeparator = "=" + structOnlyTag = "structonly" + noStructLevelTag = "nostructlevel" + omitempty = "omitempty" + isdefault = "isdefault" + requiredWithoutAllTag = "required_without_all" + requiredWithoutTag = "required_without" + requiredWithTag = "required_with" + requiredWithAllTag = "required_with_all" + requiredIfTag = "required_if" + requiredUnlessTag = "required_unless" + skipValidationTag = "-" + diveTag = "dive" + keysTag = "keys" + endKeysTag = "endkeys" + requiredTag = "required" + namespaceSeparator = "." + leftBracket = "[" + rightBracket = "]" + restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}" + restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation" + restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation" +) + +var ( + timeDurationType = reflect.TypeOf(time.Duration(0)) + timeType = reflect.TypeOf(time.Time{}) + + defaultCField = &cField{namesEqual: true} +) + +// FilterFunc is the type used to filter fields using +// StructFiltered(...) function. +// returning true results in the field being filtered/skiped from +// validation +type FilterFunc func(ns []byte) bool + +// CustomTypeFunc allows for overriding or adding custom field type handler functions +// field = field value of the type to return a value to be validated +// example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29 +type CustomTypeFunc func(field reflect.Value) interface{} + +// TagNameFunc allows for adding of a custom tag name parser +type TagNameFunc func(field reflect.StructField) string + +type internalValidationFuncWrapper struct { + fn FuncCtx + runValidatinOnNil bool +} + +// Validate contains the validator settings and cache +type Validate struct { + tagName string + pool *sync.Pool + hasCustomFuncs bool + hasTagNameFunc bool + tagNameFunc TagNameFunc + structLevelFuncs map[reflect.Type]StructLevelFuncCtx + customFuncs map[reflect.Type]CustomTypeFunc + aliases map[string]string + validations map[string]internalValidationFuncWrapper // 每个 tag如 gte 都会对应一个func处理, 如果这个tag没有函数处理会报错 + transTagFunc map[ut.Translator]map[string]TranslationFunc // map[]map[]TranslationFunc + tagCache *tagCache + structCache *structCache +} + +// New returns a new instance of 'validate' with sane defaults. +func New() *Validate { + + tc := new(tagCache) + tc.m.Store(make(map[string]*cTag)) + + sc := new(structCache) + sc.m.Store(make(map[reflect.Type]*cStruct)) + + v := &Validate{ + tagName: defaultTagName, + aliases: make(map[string]string, len(bakedInAliases)), + validations: make(map[string]internalValidationFuncWrapper, len(bakedInValidators)), + tagCache: tc, + structCache: sc, + } + + // must copy alias validators for separate validations to be used in each validator instance + for k, val := range bakedInAliases { + v.RegisterAlias(k, val) + } + + // must copy validators for separate validations to be used in each instance + for k, val := range bakedInValidators { + + 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: + _ = v.registerValidation(k, wrapFunc(val), true, true) + default: + // no need to error check here, baked in will always be valid + _ = v.registerValidation(k, wrapFunc(val), true, false) + } + } + + v.pool = &sync.Pool{ + New: func() interface{} { + return &validate{ + v: v, + ns: make([]byte, 0, 64), + actualNs: make([]byte, 0, 64), + misc: make([]byte, 32), + } + }, + } + + return v +} + +// SetTagName allows for changing of the default tag name of 'validate' +func (v *Validate) SetTagName(name string) { + v.tagName = name +} + +// RegisterTagNameFunc registers a function to get alternate names for StructFields. +// +// eg. to use the names which have been specified for JSON representations of structs, rather than normal Go field names: +// +// validate.RegisterTagNameFunc(func(fld reflect.StructField) string { +// name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] +// if name == "-" { +// return "" +// } +// return name +// }) +func (v *Validate) RegisterTagNameFunc(fn TagNameFunc) { + v.tagNameFunc = fn + v.hasTagNameFunc = true +} + +// RegisterValidation adds a validation with the given tag +// +// NOTES: +// - if the key already exists, the previous validation function will be replaced. +// - this method is not thread-safe it is intended that these all be registered prior to any validation +func (v *Validate) RegisterValidation(tag string, fn Func, callValidationEvenIfNull ...bool) error { + return v.RegisterValidationCtx(tag, wrapFunc(fn), callValidationEvenIfNull...) +} + +// RegisterValidationCtx does the same as RegisterValidation on accepts a FuncCtx validation +// allowing context.Context validation support. +func (v *Validate) RegisterValidationCtx(tag string, fn FuncCtx, callValidationEvenIfNull ...bool) error { + var nilCheckable bool + if len(callValidationEvenIfNull) > 0 { + nilCheckable = callValidationEvenIfNull[0] + } + return v.registerValidation(tag, fn, false, nilCheckable) +} + +func (v *Validate) registerValidation(tag string, fn FuncCtx, bakedIn bool, nilCheckable bool) error { + if len(tag) == 0 { + return errors.New("Function Key cannot be empty") + } + + if fn == nil { + return errors.New("Function cannot be empty") + } + + _, ok := restrictedTags[tag] + if !bakedIn && (ok || strings.ContainsAny(tag, restrictedTagChars)) { + panic(fmt.Sprintf(restrictedTagErr, tag)) + } + v.validations[tag] = internalValidationFuncWrapper{fn: fn, runValidatinOnNil: nilCheckable} + return nil +} + +// RegisterAlias registers a mapping of a single validation tag that +// defines a common or complex set of validation(s) to simplify adding validation +// to structs. +// +// NOTE: this function is not thread-safe it is intended that these all be registered prior to any validation +func (v *Validate) RegisterAlias(alias, tags string) { + + _, ok := restrictedTags[alias] + + if ok || strings.ContainsAny(alias, restrictedTagChars) { + panic(fmt.Sprintf(restrictedAliasErr, alias)) + } + + v.aliases[alias] = tags +} + +// RegisterStructValidation registers a StructLevelFunc against a number of types. +// +// NOTE: +// - this method is not thread-safe it is intended that these all be registered prior to any validation +func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interface{}) { + v.RegisterStructValidationCtx(wrapStructLevelFunc(fn), types...) +} + +// RegisterStructValidationCtx registers a StructLevelFuncCtx against a number of types and allows passing +// of contextual validation information via context.Context. +// +// NOTE: +// - this method is not thread-safe it is intended that these all be registered prior to any validation +func (v *Validate) RegisterStructValidationCtx(fn StructLevelFuncCtx, types ...interface{}) { + + if v.structLevelFuncs == nil { + v.structLevelFuncs = make(map[reflect.Type]StructLevelFuncCtx) + } + + for _, t := range types { + tv := reflect.ValueOf(t) + if tv.Kind() == reflect.Ptr { + t = reflect.Indirect(tv).Interface() + } + + v.structLevelFuncs[reflect.TypeOf(t)] = fn + } +} + +// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types +// +// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation +func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) { + + if v.customFuncs == nil { + v.customFuncs = make(map[reflect.Type]CustomTypeFunc) + } + + for _, t := range types { + v.customFuncs[reflect.TypeOf(t)] = fn + } + + v.hasCustomFuncs = true +} + +// RegisterTranslation registers translations against the provided tag. +func (v *Validate) RegisterTranslation(tag string, trans ut.Translator, registerFn RegisterTranslationsFunc, translationFn TranslationFunc) (err error) { + + if v.transTagFunc == nil { + v.transTagFunc = make(map[ut.Translator]map[string]TranslationFunc) + } + + if err = registerFn(trans); err != nil { + return + } + + m, ok := v.transTagFunc[trans] + if !ok { + m = make(map[string]TranslationFunc) + v.transTagFunc[trans] = m + } + + m[tag] = translationFn + + return +} + +// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified. +// +// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. +// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. +func (v *Validate) Struct(s interface{}) error { + return v.StructCtx(context.Background(), s) +} + +// StructCtx validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified +// and also allows passing of context.Context for contextual validation information. +// +// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. +// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. +func (v *Validate) StructCtx(ctx context.Context, s interface{}) (err error) { + + val := reflect.ValueOf(s) + top := val + + if val.Kind() == reflect.Ptr && !val.IsNil() { + val = val.Elem() + } + + if val.Kind() != reflect.Struct || val.Type() == timeType { + return &InvalidValidationError{Type: reflect.TypeOf(s)} + } + + // good to validate + vd := v.pool.Get().(*validate) + vd.top = top + vd.isPartial = false + // vd.hasExcludes = false // only need to reset in StructPartial and StructExcept + + vd.validateStruct(ctx, top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil) + + if len(vd.errs) > 0 { + err = vd.errs + vd.errs = nil + } + + v.pool.Put(vd) + + return +} + +// StructFiltered validates a structs exposed fields, that pass the FilterFunc check and automatically validates +// nested structs, unless otherwise specified. +// +// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. +// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. +func (v *Validate) StructFiltered(s interface{}, fn FilterFunc) error { + return v.StructFilteredCtx(context.Background(), s, fn) +} + +// StructFilteredCtx validates a structs exposed fields, that pass the FilterFunc check and automatically validates +// nested structs, unless otherwise specified and also allows passing of contextual validation information via +// context.Context +// +// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. +// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. +func (v *Validate) StructFilteredCtx(ctx context.Context, s interface{}, fn FilterFunc) (err error) { + val := reflect.ValueOf(s) + top := val + + if val.Kind() == reflect.Ptr && !val.IsNil() { + val = val.Elem() + } + + if val.Kind() != reflect.Struct || val.Type() == timeType { + return &InvalidValidationError{Type: reflect.TypeOf(s)} + } + + // good to validate + vd := v.pool.Get().(*validate) + vd.top = top + vd.isPartial = true + vd.ffn = fn + // vd.hasExcludes = false // only need to reset in StructPartial and StructExcept + + vd.validateStruct(ctx, top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil) + + if len(vd.errs) > 0 { + err = vd.errs + vd.errs = nil + } + + v.pool.Put(vd) + + return +} + +// StructPartial validates the fields passed in only, ignoring all others. +// Fields may be provided in a namespaced fashion relative to the struct provided +// eg. NestedStruct.Field or NestedArrayField[0].Struct.Name +// +// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. +// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. +func (v *Validate) StructPartial(s interface{}, fields ...string) error { + return v.StructPartialCtx(context.Background(), s, fields...) +} + +// StructPartialCtx validates the fields passed in only, ignoring all others and allows passing of contextual +// validation validation information via context.Context +// Fields may be provided in a namespaced fashion relative to the struct provided +// eg. NestedStruct.Field or NestedArrayField[0].Struct.Name +// +// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. +// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. +func (v *Validate) StructPartialCtx(ctx context.Context, s interface{}, fields ...string) (err error) { + val := reflect.ValueOf(s) + top := val + + if val.Kind() == reflect.Ptr && !val.IsNil() { + val = val.Elem() + } + + if val.Kind() != reflect.Struct || val.Type() == timeType { + return &InvalidValidationError{Type: reflect.TypeOf(s)} + } + + // good to validate + vd := v.pool.Get().(*validate) + vd.top = top + vd.isPartial = true + vd.ffn = nil + vd.hasExcludes = false + vd.includeExclude = make(map[string]struct{}) + + typ := val.Type() + name := typ.Name() + + for _, k := range fields { + + flds := strings.Split(k, namespaceSeparator) + if len(flds) > 0 { + + vd.misc = append(vd.misc[0:0], name...) + vd.misc = append(vd.misc, '.') + + for _, s := range flds { + + idx := strings.Index(s, leftBracket) + + if idx != -1 { + for idx != -1 { + vd.misc = append(vd.misc, s[:idx]...) + vd.includeExclude[string(vd.misc)] = struct{}{} + + idx2 := strings.Index(s, rightBracket) + idx2++ + vd.misc = append(vd.misc, s[idx:idx2]...) + vd.includeExclude[string(vd.misc)] = struct{}{} + s = s[idx2:] + idx = strings.Index(s, leftBracket) + } + } else { + + vd.misc = append(vd.misc, s...) + vd.includeExclude[string(vd.misc)] = struct{}{} + } + + vd.misc = append(vd.misc, '.') + } + } + } + + vd.validateStruct(ctx, top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) + + if len(vd.errs) > 0 { + err = vd.errs + vd.errs = nil + } + + v.pool.Put(vd) + + return +} + +// StructExcept validates all fields except the ones passed in. +// Fields may be provided in a namespaced fashion relative to the struct provided +// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name +// +// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. +// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. +func (v *Validate) StructExcept(s interface{}, fields ...string) error { + return v.StructExceptCtx(context.Background(), s, fields...) +} + +// StructExceptCtx validates all fields except the ones passed in and allows passing of contextual +// validation validation information via context.Context +// Fields may be provided in a namespaced fashion relative to the struct provided +// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name +// +// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. +// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. +func (v *Validate) StructExceptCtx(ctx context.Context, s interface{}, fields ...string) (err error) { + val := reflect.ValueOf(s) + top := val + + if val.Kind() == reflect.Ptr && !val.IsNil() { + val = val.Elem() + } + + if val.Kind() != reflect.Struct || val.Type() == timeType { + return &InvalidValidationError{Type: reflect.TypeOf(s)} + } + + // good to validate + vd := v.pool.Get().(*validate) + vd.top = top + vd.isPartial = true + vd.ffn = nil + vd.hasExcludes = true + vd.includeExclude = make(map[string]struct{}) + + typ := val.Type() + name := typ.Name() + + for _, key := range fields { + + vd.misc = vd.misc[0:0] + + if len(name) > 0 { + vd.misc = append(vd.misc, name...) + vd.misc = append(vd.misc, '.') + } + + vd.misc = append(vd.misc, key...) + vd.includeExclude[string(vd.misc)] = struct{}{} + } + + vd.validateStruct(ctx, top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) + + if len(vd.errs) > 0 { + err = vd.errs + vd.errs = nil + } + + v.pool.Put(vd) + + return +} + +// Var validates a single variable using tag style validation. +// eg. +// var i int +// validate.Var(i, "gt=1,lt=10") +// +// WARNING: a struct can be passed for validation eg. time.Time is a struct or +// if you have a custom type and have registered a custom type handler, so must +// allow it; however unforeseen validations will occur if trying to validate a +// struct that is meant to be passed to 'validate.Struct' +// +// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. +// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. +// validate Array, Slice and maps fields which may contain more than one error +func (v *Validate) Var(field interface{}, tag string) error { + return v.VarCtx(context.Background(), field, tag) +} + +// VarCtx validates a single variable using tag style validation and allows passing of contextual +// validation validation information via context.Context. +// eg. +// var i int +// validate.Var(i, "gt=1,lt=10") +// +// WARNING: a struct can be passed for validation eg. time.Time is a struct or +// if you have a custom type and have registered a custom type handler, so must +// allow it; however unforeseen validations will occur if trying to validate a +// struct that is meant to be passed to 'validate.Struct' +// +// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. +// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. +// validate Array, Slice and maps fields which may contain more than one error +func (v *Validate) VarCtx(ctx context.Context, field interface{}, tag string) (err error) { + if len(tag) == 0 || tag == skipValidationTag { + return nil + } + + ctag := v.fetchCacheTag(tag) + val := reflect.ValueOf(field) + vd := v.pool.Get().(*validate) + vd.top = val + vd.isPartial = false + vd.traverseField(ctx, val, val, vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag) + + if len(vd.errs) > 0 { + err = vd.errs + vd.errs = nil + } + v.pool.Put(vd) + return +} + +// VarWithValue validates a single variable, against another variable/field's value using tag style validation +// eg. +// s1 := "abcd" +// s2 := "abcd" +// validate.VarWithValue(s1, s2, "eqcsfield") // returns true +// +// WARNING: a struct can be passed for validation eg. time.Time is a struct or +// if you have a custom type and have registered a custom type handler, so must +// allow it; however unforeseen validations will occur if trying to validate a +// struct that is meant to be passed to 'validate.Struct' +// +// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. +// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. +// validate Array, Slice and maps fields which may contain more than one error +func (v *Validate) VarWithValue(field interface{}, other interface{}, tag string) error { + return v.VarWithValueCtx(context.Background(), field, other, tag) +} + +// VarWithValueCtx validates a single variable, against another variable/field's value using tag style validation and +// allows passing of contextual validation validation information via context.Context. +// eg. +// s1 := "abcd" +// s2 := "abcd" +// validate.VarWithValue(s1, s2, "eqcsfield") // returns true +// +// WARNING: a struct can be passed for validation eg. time.Time is a struct or +// if you have a custom type and have registered a custom type handler, so must +// allow it; however unforeseen validations will occur if trying to validate a +// struct that is meant to be passed to 'validate.Struct' +// +// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. +// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. +// validate Array, Slice and maps fields which may contain more than one error +func (v *Validate) VarWithValueCtx(ctx context.Context, field interface{}, other interface{}, tag string) (err error) { + if len(tag) == 0 || tag == skipValidationTag { + return nil + } + ctag := v.fetchCacheTag(tag) + otherVal := reflect.ValueOf(other) + vd := v.pool.Get().(*validate) + vd.top = otherVal + vd.isPartial = false + vd.traverseField(ctx, otherVal, reflect.ValueOf(field), vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag) + + if len(vd.errs) > 0 { + err = vd.errs + vd.errs = nil + } + v.pool.Put(vd) + return +} diff --git a/go-playground/validator/v10/validator_test.go b/go-playground/validator/v10/validator_test.go new file mode 100644 index 0000000..6b1f392 --- /dev/null +++ b/go-playground/validator/v10/validator_test.go @@ -0,0 +1,11035 @@ +package validator + +import ( + "bytes" + "context" + "database/sql" + "database/sql/driver" + "encoding/base64" + "encoding/json" + "fmt" + "path/filepath" + "reflect" + "strings" + "testing" + "time" + + "gin-valid/go-playground/locales/en" + "gin-valid/go-playground/locales/fr" + "gin-valid/go-playground/locales/nl" + ut "gin-valid/go-playground/universal-translator" + . "github.com/go-playground/assert/v2" +) + +// NOTES: +// - Run "go test" to run tests +// - Run "gocov test | gocov report" to report on test converage by file +// - Run "gocov test | gocov annotate -" to report on all code and functions, those ,marked with "MISS" were never called +// +// or +// +// -- may be a good idea to change to output path to somewherelike /tmp +// go test -coverprofile cover.out && go tool cover -html=cover.out -o cover.html +// +// +// go test -cpuprofile cpu.out +// ./validator.test -test.bench=. -test.cpuprofile=cpu.prof +// go tool pprof validator.test cpu.prof +// +// +// go test -memprofile mem.out + +type I interface { + Foo() string +} + +type Impl struct { + F string `validate:"len=3"` +} + +func (i *Impl) Foo() string { + return i.F +} + +type SubTest struct { + Test string `validate:"required"` +} + +type TestInterface struct { + Iface I +} + +type TestString struct { + BlankTag string `validate:""` + Required string `validate:"required"` + Len string `validate:"len=10"` + Min string `validate:"min=1"` + Max string `validate:"max=10"` + MinMax string `validate:"min=1,max=10"` + Lt string `validate:"lt=10"` + Lte string `validate:"lte=10"` + Gt string `validate:"gt=10"` + Gte string `validate:"gte=10"` + OmitEmpty string `validate:"omitempty,min=1,max=10"` + Sub *SubTest + SubIgnore *SubTest `validate:"-"` + Anonymous struct { + A string `validate:"required"` + } + Iface I +} + +type TestUint64 struct { + Required uint64 `validate:"required"` + Len uint64 `validate:"len=10"` + Min uint64 `validate:"min=1"` + Max uint64 `validate:"max=10"` + MinMax uint64 `validate:"min=1,max=10"` + OmitEmpty uint64 `validate:"omitempty,min=1,max=10"` +} + +type TestFloat64 struct { + Required float64 `validate:"required"` + Len float64 `validate:"len=10"` + Min float64 `validate:"min=1"` + Max float64 `validate:"max=10"` + MinMax float64 `validate:"min=1,max=10"` + Lte float64 `validate:"lte=10"` + OmitEmpty float64 `validate:"omitempty,min=1,max=10"` +} + +type TestSlice struct { + Required []int `validate:"required"` + Len []int `validate:"len=10"` + Min []int `validate:"min=1"` + Max []int `validate:"max=10"` + MinMax []int `validate:"min=1,max=10"` + OmitEmpty []int `validate:"omitempty,min=1,max=10"` +} + +func AssertError(t *testing.T, err error, nsKey, structNsKey, field, structField, expectedTag string) { + + errs := err.(ValidationErrors) + + found := false + var fe FieldError + + for i := 0; i < len(errs); i++ { + if errs[i].Namespace() == nsKey && errs[i].StructNamespace() == structNsKey { + found = true + fe = errs[i] + break + } + } + + EqualSkip(t, 2, found, true) + NotEqualSkip(t, 2, fe, nil) + EqualSkip(t, 2, fe.Field(), field) + EqualSkip(t, 2, fe.StructField(), structField) + EqualSkip(t, 2, fe.Tag(), expectedTag) +} + +func AssertDeepError(t *testing.T, err error, nsKey, structNsKey, field, structField, expectedTag, actualTag string) { + errs := err.(ValidationErrors) + + found := false + var fe FieldError + + for i := 0; i < len(errs); i++ { + if errs[i].Namespace() == nsKey && errs[i].StructNamespace() == structNsKey && errs[i].Tag() == expectedTag && errs[i].ActualTag() == actualTag { + found = true + fe = errs[i] + break + } + } + + EqualSkip(t, 2, found, true) + NotEqualSkip(t, 2, fe, nil) + EqualSkip(t, 2, fe.Field(), field) + EqualSkip(t, 2, fe.StructField(), structField) +} + +func getError(err error, nsKey, structNsKey string) FieldError { + + errs := err.(ValidationErrors) + + var fe FieldError + + for i := 0; i < len(errs); i++ { + if errs[i].Namespace() == nsKey && errs[i].StructNamespace() == structNsKey { + fe = errs[i] + break + } + } + + return fe +} + +type valuer struct { + Name string +} + +func (v valuer) Value() (driver.Value, error) { + + if v.Name == "errorme" { + panic("SQL Driver Valuer error: some kind of error") + // return nil, errors.New("some kind of error") + } + + if len(v.Name) == 0 { + return nil, nil + } + + return v.Name, nil +} + +type MadeUpCustomType struct { + FirstName string + LastName string +} + +func ValidateCustomType(field reflect.Value) interface{} { + + if cust, ok := field.Interface().(MadeUpCustomType); ok { + + if len(cust.FirstName) == 0 || len(cust.LastName) == 0 { + return "" + } + + return cust.FirstName + " " + cust.LastName + } + + return "" +} + +func OverrideIntTypeForSomeReason(field reflect.Value) interface{} { + + if i, ok := field.Interface().(int); ok { + if i == 1 { + return "1" + } + + if i == 2 { + return "12" + } + } + + return "" +} + +type CustomMadeUpStruct struct { + MadeUp MadeUpCustomType `validate:"required"` + OverriddenInt int `validate:"gt=1"` +} + +func ValidateValuerType(field reflect.Value) interface{} { + + if valuer, ok := field.Interface().(driver.Valuer); ok { + + val, err := valuer.Value() + if err != nil { + // handle the error how you want + return nil + } + + return val + } + + return nil +} + +type TestPartial struct { + NoTag string + BlankTag string `validate:""` + Required string `validate:"required"` + SubSlice []*SubTest `validate:"required,dive"` + Sub *SubTest + SubIgnore *SubTest `validate:"-"` + Anonymous struct { + A string `validate:"required"` + ASubSlice []*SubTest `validate:"required,dive"` + + SubAnonStruct []struct { + Test string `validate:"required"` + OtherTest string `validate:"required"` + } `validate:"required,dive"` + } +} + +type TestStruct struct { + String string `validate:"required" json:"StringVal"` +} + +func StructValidationTestStructSuccess(sl StructLevel) { + + st := sl.Current().Interface().(TestStruct) + + if st.String != "good value" { + sl.ReportError(st.String, "StringVal", "String", "badvalueteststruct", "good value") + } +} + +func StructValidationTestStruct(sl StructLevel) { + + st := sl.Current().Interface().(TestStruct) + + if st.String != "bad value" { + sl.ReportError(st.String, "StringVal", "String", "badvalueteststruct", "bad value") + } +} + +func StructValidationNoTestStructCustomName(sl StructLevel) { + + st := sl.Current().Interface().(TestStruct) + + if st.String != "bad value" { + sl.ReportError(st.String, "String", "", "badvalueteststruct", "bad value") + } +} + +func StructValidationTestStructInvalid(sl StructLevel) { + + st := sl.Current().Interface().(TestStruct) + + if st.String != "bad value" { + sl.ReportError(nil, "StringVal", "String", "badvalueteststruct", "bad value") + } +} + +func StructValidationTestStructReturnValidationErrors(sl StructLevel) { + + s := sl.Current().Interface().(TestStructReturnValidationErrors) + + errs := sl.Validator().Struct(s.Inner1.Inner2) + if errs == nil { + return + } + + sl.ReportValidationErrors("Inner1.", "Inner1.", errs.(ValidationErrors)) +} + +func StructValidationTestStructReturnValidationErrors2(sl StructLevel) { + + s := sl.Current().Interface().(TestStructReturnValidationErrors) + + errs := sl.Validator().Struct(s.Inner1.Inner2) + if errs == nil { + return + } + + sl.ReportValidationErrors("Inner1JSON.", "Inner1.", errs.(ValidationErrors)) +} + +type TestStructReturnValidationErrorsInner2 struct { + String string `validate:"required" json:"JSONString"` +} + +type TestStructReturnValidationErrorsInner1 struct { + Inner2 *TestStructReturnValidationErrorsInner2 +} + +type TestStructReturnValidationErrors struct { + Inner1 *TestStructReturnValidationErrorsInner1 `json:"Inner1JSON"` +} + +type StructLevelInvalidErr struct { + Value string +} + +func StructLevelInvalidError(sl StructLevel) { + + top := sl.Top().Interface().(StructLevelInvalidErr) + s := sl.Current().Interface().(StructLevelInvalidErr) + + if top.Value == s.Value { + sl.ReportError(nil, "Value", "Value", "required", "") + } +} + +func stringPtr(v string) *string { + return &v +} + +func intPtr(v int) *int { + return &v +} + +func float64Ptr(v float64) *float64 { + return &v +} + +func TestStructLevelInvalidError(t *testing.T) { + + validate := New() + validate.RegisterStructValidation(StructLevelInvalidError, StructLevelInvalidErr{}) + + var test StructLevelInvalidErr + + err := validate.Struct(test) + NotEqual(t, err, nil) + + errs, ok := err.(ValidationErrors) + Equal(t, ok, true) + + fe := errs[0] + Equal(t, fe.Field(), "Value") + Equal(t, fe.StructField(), "Value") + Equal(t, fe.Namespace(), "StructLevelInvalidErr.Value") + Equal(t, fe.StructNamespace(), "StructLevelInvalidErr.Value") + Equal(t, fe.Tag(), "required") + Equal(t, fe.ActualTag(), "required") + Equal(t, fe.Kind(), reflect.Invalid) + Equal(t, fe.Type(), reflect.TypeOf(nil)) +} + +func TestNameNamespace(t *testing.T) { + + type Inner2Namespace struct { + String []string `validate:"dive,required" json:"JSONString"` + } + + type Inner1Namespace struct { + Inner2 *Inner2Namespace `json:"Inner2JSON"` + } + + type Namespace struct { + Inner1 *Inner1Namespace `json:"Inner1JSON"` + } + + validate := New() + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] + + if name == "-" { + return "" + } + + return name + }) + + i2 := &Inner2Namespace{String: []string{"ok", "ok", "ok"}} + i1 := &Inner1Namespace{Inner2: i2} + ns := &Namespace{Inner1: i1} + + errs := validate.Struct(ns) + Equal(t, errs, nil) + + i2.String[1] = "" + + errs = validate.Struct(ns) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 1) + AssertError(t, errs, "Namespace.Inner1JSON.Inner2JSON.JSONString[1]", "Namespace.Inner1.Inner2.String[1]", "JSONString[1]", "String[1]", "required") + + fe := getError(ve, "Namespace.Inner1JSON.Inner2JSON.JSONString[1]", "Namespace.Inner1.Inner2.String[1]") + NotEqual(t, fe, nil) + Equal(t, fe.Field(), "JSONString[1]") + Equal(t, fe.StructField(), "String[1]") + Equal(t, fe.Namespace(), "Namespace.Inner1JSON.Inner2JSON.JSONString[1]") + Equal(t, fe.StructNamespace(), "Namespace.Inner1.Inner2.String[1]") +} + +func TestAnonymous(t *testing.T) { + + validate := New() + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] + + if name == "-" { + return "" + } + + return name + }) + + type Test struct { + Anonymous struct { + A string `validate:"required" json:"EH"` + } + AnonymousB struct { + B string `validate:"required" json:"BEE"` + } + anonymousC struct { + c string `validate:"required"` + } + } + + tst := &Test{ + Anonymous: struct { + A string `validate:"required" json:"EH"` + }{ + A: "1", + }, + AnonymousB: struct { + B string `validate:"required" json:"BEE"` + }{ + B: "", + }, + anonymousC: struct { + c string `validate:"required"` + }{ + c: "", + }, + } + + Equal(t, tst.anonymousC.c, "") + + err := validate.Struct(tst) + NotEqual(t, err, nil) + + errs := err.(ValidationErrors) + + Equal(t, len(errs), 1) + AssertError(t, errs, "Test.AnonymousB.BEE", "Test.AnonymousB.B", "BEE", "B", "required") + + fe := getError(errs, "Test.AnonymousB.BEE", "Test.AnonymousB.B") + NotEqual(t, fe, nil) + Equal(t, fe.Field(), "BEE") + Equal(t, fe.StructField(), "B") + + s := struct { + c string `validate:"required"` + }{ + c: "", + } + + err = validate.Struct(s) + Equal(t, err, nil) +} + +func TestAnonymousSameStructDifferentTags(t *testing.T) { + + validate := New() + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] + + if name == "-" { + return "" + } + + return name + }) + + type Test struct { + A interface{} + } + + tst := &Test{ + A: struct { + A string `validate:"required"` + }{ + A: "", + }, + } + + err := validate.Struct(tst) + NotEqual(t, err, nil) + + errs := err.(ValidationErrors) + + Equal(t, len(errs), 1) + AssertError(t, errs, "Test.A.A", "Test.A.A", "A", "A", "required") + + tst = &Test{ + A: struct { + A string `validate:"omitempty,required"` + }{ + A: "", + }, + } + + err = validate.Struct(tst) + Equal(t, err, nil) +} + +func TestStructLevelReturnValidationErrors(t *testing.T) { + + validate := New() + validate.RegisterStructValidation(StructValidationTestStructReturnValidationErrors, TestStructReturnValidationErrors{}) + + inner2 := &TestStructReturnValidationErrorsInner2{ + String: "I'm HERE", + } + + inner1 := &TestStructReturnValidationErrorsInner1{ + Inner2: inner2, + } + + val := &TestStructReturnValidationErrors{ + Inner1: inner1, + } + + errs := validate.Struct(val) + Equal(t, errs, nil) + + inner2.String = "" + + errs = validate.Struct(val) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 2) + AssertError(t, errs, "TestStructReturnValidationErrors.Inner1.Inner2.String", "TestStructReturnValidationErrors.Inner1.Inner2.String", "String", "String", "required") + // this is an extra error reported from struct validation + AssertError(t, errs, "TestStructReturnValidationErrors.Inner1.TestStructReturnValidationErrorsInner2.String", "TestStructReturnValidationErrors.Inner1.TestStructReturnValidationErrorsInner2.String", "String", "String", "required") +} + +func TestStructLevelReturnValidationErrorsWithJSON(t *testing.T) { + + validate := New() + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] + + if name == "-" { + return "" + } + + return name + }) + validate.RegisterStructValidation(StructValidationTestStructReturnValidationErrors2, TestStructReturnValidationErrors{}) + + inner2 := &TestStructReturnValidationErrorsInner2{ + String: "I'm HERE", + } + + inner1 := &TestStructReturnValidationErrorsInner1{ + Inner2: inner2, + } + + val := &TestStructReturnValidationErrors{ + Inner1: inner1, + } + + errs := validate.Struct(val) + Equal(t, errs, nil) + + inner2.String = "" + + errs = validate.Struct(val) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 2) + AssertError(t, errs, "TestStructReturnValidationErrors.Inner1JSON.Inner2.JSONString", "TestStructReturnValidationErrors.Inner1.Inner2.String", "JSONString", "String", "required") + // this is an extra error reported from struct validation, it's a badly formatted one, but on purpose + AssertError(t, errs, "TestStructReturnValidationErrors.Inner1JSON.TestStructReturnValidationErrorsInner2.JSONString", "TestStructReturnValidationErrors.Inner1.TestStructReturnValidationErrorsInner2.String", "JSONString", "String", "required") + + fe := getError(errs, "TestStructReturnValidationErrors.Inner1JSON.Inner2.JSONString", "TestStructReturnValidationErrors.Inner1.Inner2.String") + NotEqual(t, fe, nil) + + // check for proper JSON namespace + Equal(t, fe.Field(), "JSONString") + Equal(t, fe.StructField(), "String") + Equal(t, fe.Namespace(), "TestStructReturnValidationErrors.Inner1JSON.Inner2.JSONString") + Equal(t, fe.StructNamespace(), "TestStructReturnValidationErrors.Inner1.Inner2.String") + + fe = getError(errs, "TestStructReturnValidationErrors.Inner1JSON.TestStructReturnValidationErrorsInner2.JSONString", "TestStructReturnValidationErrors.Inner1.TestStructReturnValidationErrorsInner2.String") + NotEqual(t, fe, nil) + + // check for proper JSON namespace + Equal(t, fe.Field(), "JSONString") + Equal(t, fe.StructField(), "String") + Equal(t, fe.Namespace(), "TestStructReturnValidationErrors.Inner1JSON.TestStructReturnValidationErrorsInner2.JSONString") + Equal(t, fe.StructNamespace(), "TestStructReturnValidationErrors.Inner1.TestStructReturnValidationErrorsInner2.String") +} + +func TestStructLevelValidations(t *testing.T) { + + v1 := New() + v1.RegisterStructValidation(StructValidationTestStruct, TestStruct{}) + + tst := &TestStruct{ + String: "good value", + } + + errs := v1.Struct(tst) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestStruct.StringVal", "TestStruct.String", "StringVal", "String", "badvalueteststruct") + + v2 := New() + v2.RegisterStructValidation(StructValidationNoTestStructCustomName, TestStruct{}) + + errs = v2.Struct(tst) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestStruct.String", "TestStruct.String", "String", "String", "badvalueteststruct") + + v3 := New() + v3.RegisterStructValidation(StructValidationTestStructInvalid, TestStruct{}) + + errs = v3.Struct(tst) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestStruct.StringVal", "TestStruct.String", "StringVal", "String", "badvalueteststruct") + + v4 := New() + v4.RegisterStructValidation(StructValidationTestStructSuccess, TestStruct{}) + + errs = v4.Struct(tst) + Equal(t, errs, nil) +} + +func TestAliasTags(t *testing.T) { + + validate := New() + validate.RegisterAlias("iscoloralias", "hexcolor|rgb|rgba|hsl|hsla") + + s := "rgb(255,255,255)" + errs := validate.Var(s, "iscoloralias") + Equal(t, errs, nil) + + s = "" + errs = validate.Var(s, "omitempty,iscoloralias") + Equal(t, errs, nil) + + s = "rgb(255,255,0)" + errs = validate.Var(s, "iscoloralias,len=5") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "len") + + type Test struct { + Color string `validate:"iscoloralias"` + } + + tst := &Test{ + Color: "#000", + } + + errs = validate.Struct(tst) + Equal(t, errs, nil) + + tst.Color = "cfvre" + errs = validate.Struct(tst) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.Color", "Test.Color", "Color", "Color", "iscoloralias") + + fe := getError(errs, "Test.Color", "Test.Color") + NotEqual(t, fe, nil) + Equal(t, fe.ActualTag(), "hexcolor|rgb|rgba|hsl|hsla") + + validate.RegisterAlias("req", "required,dive,iscoloralias") + arr := []string{"val1", "#fff", "#000"} + + errs = validate.Var(arr, "req") + NotEqual(t, errs, nil) + AssertError(t, errs, "[0]", "[0]", "[0]", "[0]", "iscoloralias") + + PanicMatches(t, func() { validate.RegisterAlias("exists!", "gt=5,lt=10") }, "Alias 'exists!' either contains restricted characters or is the same as a restricted tag needed for normal operation") +} + +func TestNilValidator(t *testing.T) { + + type TestStruct struct { + Test string `validate:"required"` + } + + ts := TestStruct{} + + var val *Validate + + fn := func(fl FieldLevel) bool { + + return fl.Parent().String() == fl.Field().String() + } + + PanicMatches(t, func() { val.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) }, "runtime error: invalid memory address or nil pointer dereference") + PanicMatches(t, func() { _ = val.RegisterValidation("something", fn) }, "runtime error: invalid memory address or nil pointer dereference") + PanicMatches(t, func() { _ = val.Var(ts.Test, "required") }, "runtime error: invalid memory address or nil pointer dereference") + PanicMatches(t, func() { _ = val.VarWithValue("test", ts.Test, "required") }, "runtime error: invalid memory address or nil pointer dereference") + PanicMatches(t, func() { _ = val.Struct(ts) }, "runtime error: invalid memory address or nil pointer dereference") + PanicMatches(t, func() { _ = val.StructExcept(ts, "Test") }, "runtime error: invalid memory address or nil pointer dereference") + PanicMatches(t, func() { _ = val.StructPartial(ts, "Test") }, "runtime error: invalid memory address or nil pointer dereference") +} + +func TestStructPartial(t *testing.T) { + p1 := []string{ + "NoTag", + "Required", + } + + p2 := []string{ + "SubSlice[0].Test", + "Sub", + "SubIgnore", + "Anonymous.A", + } + + p3 := []string{ + "SubTest.Test", + } + + p4 := []string{ + "A", + } + + tPartial := &TestPartial{ + NoTag: "NoTag", + Required: "Required", + + SubSlice: []*SubTest{ + { + + Test: "Required", + }, + { + + Test: "Required", + }, + }, + + Sub: &SubTest{ + Test: "1", + }, + SubIgnore: &SubTest{ + Test: "", + }, + Anonymous: struct { + A string `validate:"required"` + ASubSlice []*SubTest `validate:"required,dive"` + SubAnonStruct []struct { + Test string `validate:"required"` + OtherTest string `validate:"required"` + } `validate:"required,dive"` + }{ + A: "1", + ASubSlice: []*SubTest{ + { + Test: "Required", + }, + { + Test: "Required", + }, + }, + + SubAnonStruct: []struct { + Test string `validate:"required"` + OtherTest string `validate:"required"` + }{ + {"Required", "RequiredOther"}, + {"Required", "RequiredOther"}, + }, + }, + } + + validate := New() + + // the following should all return no errors as everything is valid in + // the default state + errs := validate.StructPartialCtx(context.Background(), tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructPartial(tPartial, p2...) + Equal(t, errs, nil) + + // this isn't really a robust test, but is ment to illustrate the ANON CASE below + errs = validate.StructPartial(tPartial.SubSlice[0], p3...) + Equal(t, errs, nil) + + errs = validate.StructExceptCtx(context.Background(), tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructExcept(tPartial, p2...) + Equal(t, errs, nil) + + // mod tParial for required feild and re-test making sure invalid fields are NOT required: + tPartial.Required = "" + + errs = validate.StructExcept(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructPartial(tPartial, p2...) + Equal(t, errs, nil) + + // inversion and retesting Partial to generate failures: + errs = validate.StructPartial(tPartial, p1...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Required", "TestPartial.Required", "Required", "Required", "required") + + errs = validate.StructExcept(tPartial, p2...) + AssertError(t, errs, "TestPartial.Required", "TestPartial.Required", "Required", "Required", "required") + + // reset Required field, and set nested struct + tPartial.Required = "Required" + tPartial.Anonymous.A = "" + + // will pass as unset feilds is not going to be tested + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructExcept(tPartial, p2...) + Equal(t, errs, nil) + + // ANON CASE the response here is strange, it clearly does what it is being told to + errs = validate.StructExcept(tPartial.Anonymous, p4...) + Equal(t, errs, nil) + + // will fail as unset feild is tested + errs = validate.StructPartial(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Anonymous.A", "TestPartial.Anonymous.A", "A", "A", "required") + + errs = validate.StructExcept(tPartial, p1...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Anonymous.A", "TestPartial.Anonymous.A", "A", "A", "required") + + // reset nested struct and unset struct in slice + tPartial.Anonymous.A = "Required" + tPartial.SubSlice[0].Test = "" + + // these will pass as unset item is NOT tested + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructExcept(tPartial, p2...) + Equal(t, errs, nil) + + // these will fail as unset item IS tested + errs = validate.StructExcept(tPartial, p1...) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "TestPartial.SubSlice[0].Test", "Test", "Test", "required") + Equal(t, len(errs.(ValidationErrors)), 1) + + errs = validate.StructPartial(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "TestPartial.SubSlice[0].Test", "Test", "Test", "required") + Equal(t, len(errs.(ValidationErrors)), 1) + + // Unset second slice member concurrently to test dive behavior: + tPartial.SubSlice[1].Test = "" + + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + // NOTE: When specifying nested items, it is still the users responsibility + // to specify the dive tag, the library does not override this. + errs = validate.StructExcept(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.SubSlice[1].Test", "TestPartial.SubSlice[1].Test", "Test", "Test", "required") + + errs = validate.StructExcept(tPartial, p1...) + Equal(t, len(errs.(ValidationErrors)), 2) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "TestPartial.SubSlice[0].Test", "Test", "Test", "required") + AssertError(t, errs, "TestPartial.SubSlice[1].Test", "TestPartial.SubSlice[1].Test", "Test", "Test", "required") + + errs = validate.StructPartial(tPartial, p2...) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "TestPartial.SubSlice[0].Test", "Test", "Test", "required") + + // reset struct in slice, and unset struct in slice in unset posistion + tPartial.SubSlice[0].Test = "Required" + + // these will pass as the unset item is NOT tested + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructPartial(tPartial, p2...) + Equal(t, errs, nil) + + // testing for missing item by exception, yes it dives and fails + errs = validate.StructExcept(tPartial, p1...) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "TestPartial.SubSlice[1].Test", "TestPartial.SubSlice[1].Test", "Test", "Test", "required") + + errs = validate.StructExcept(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.SubSlice[1].Test", "TestPartial.SubSlice[1].Test", "Test", "Test", "required") + + tPartial.SubSlice[1].Test = "Required" + + tPartial.Anonymous.SubAnonStruct[0].Test = "" + // these will pass as the unset item is NOT tested + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructPartial(tPartial, p2...) + Equal(t, errs, nil) + + errs = validate.StructExcept(tPartial, p1...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Anonymous.SubAnonStruct[0].Test", "TestPartial.Anonymous.SubAnonStruct[0].Test", "Test", "Test", "required") + + errs = validate.StructExcept(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Anonymous.SubAnonStruct[0].Test", "TestPartial.Anonymous.SubAnonStruct[0].Test", "Test", "Test", "required") + +} + +func TestCrossStructLteFieldValidation(t *testing.T) { + var errs error + validate := New() + + type Inner struct { + CreatedAt *time.Time + String string + Int int + Uint uint + Float float64 + Array []string + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time `validate:"ltecsfield=Inner.CreatedAt"` + String string `validate:"ltecsfield=Inner.String"` + Int int `validate:"ltecsfield=Inner.Int"` + Uint uint `validate:"ltecsfield=Inner.Uint"` + Float float64 `validate:"ltecsfield=Inner.Float"` + Array []string `validate:"ltecsfield=Inner.Array"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * 5) + + inner := &Inner{ + CreatedAt: &then, + String: "abcd", + Int: 13, + Uint: 13, + Float: 1.13, + Array: []string{"val1", "val2"}, + } + + test := &Test{ + Inner: inner, + CreatedAt: &now, + String: "abc", + Int: 12, + Uint: 12, + Float: 1.12, + Array: []string{"val1"}, + } + + errs = validate.Struct(test) + Equal(t, errs, nil) + + test.CreatedAt = &then + test.String = "abcd" + test.Int = 13 + test.Uint = 13 + test.Float = 1.13 + test.Array = []string{"val1", "val2"} + + errs = validate.Struct(test) + Equal(t, errs, nil) + + after := now.Add(time.Hour * 10) + + test.CreatedAt = &after + test.String = "abce" + test.Int = 14 + test.Uint = 14 + test.Float = 1.14 + test.Array = []string{"val1", "val2", "val3"} + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "ltecsfield") + AssertError(t, errs, "Test.String", "Test.String", "String", "String", "ltecsfield") + AssertError(t, errs, "Test.Int", "Test.Int", "Int", "Int", "ltecsfield") + AssertError(t, errs, "Test.Uint", "Test.Uint", "Uint", "Uint", "ltecsfield") + AssertError(t, errs, "Test.Float", "Test.Float", "Float", "Float", "ltecsfield") + AssertError(t, errs, "Test.Array", "Test.Array", "Array", "Array", "ltecsfield") + + errs = validate.VarWithValueCtx(context.Background(), 1, "", "ltecsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltecsfield") + + // this test is for the WARNING about unforeseen validation issues. + errs = validate.VarWithValue(test, now, "ltecsfield") + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 6) + AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "ltecsfield") + AssertError(t, errs, "Test.String", "Test.String", "String", "String", "ltecsfield") + AssertError(t, errs, "Test.Int", "Test.Int", "Int", "Int", "ltecsfield") + AssertError(t, errs, "Test.Uint", "Test.Uint", "Uint", "Uint", "ltecsfield") + AssertError(t, errs, "Test.Float", "Test.Float", "Float", "Float", "ltecsfield") + AssertError(t, errs, "Test.Array", "Test.Array", "Array", "Array", "ltecsfield") + + type Other struct { + Value string + } + + type Test2 struct { + Value Other + Time time.Time `validate:"ltecsfield=Value"` + } + + tst := Test2{ + Value: Other{Value: "StringVal"}, + Time: then, + } + + errs = validate.Struct(tst) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test2.Time", "Test2.Time", "Time", "Time", "ltecsfield") + + // Tests for time.Duration type. + + // -- Validations for variables of time.Duration type. + + errs = validate.VarWithValue(time.Hour, time.Hour+time.Minute, "ltecsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour, "ltecsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour-time.Minute, "ltecsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltecsfield") + + errs = validate.VarWithValue(time.Duration(0), -time.Minute, "omitempty,ltecsfield") + Equal(t, errs, nil) + + // -- Validations for a struct and an inner struct with time.Duration type fields. + + type TimeDurationInner struct { + Duration time.Duration + } + var timeDurationInner *TimeDurationInner + + type TimeDurationTest struct { + Inner *TimeDurationInner + Duration time.Duration `validate:"ltecsfield=Inner.Duration"` + } + var timeDurationTest *TimeDurationTest + + timeDurationInner = &TimeDurationInner{time.Hour + time.Minute} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationInner = &TimeDurationInner{time.Hour} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationInner = &TimeDurationInner{time.Hour - time.Minute} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "ltecsfield") + + type TimeDurationOmitemptyTest struct { + Inner *TimeDurationInner + Duration time.Duration `validate:"omitempty,ltecsfield=Inner.Duration"` + } + var timeDurationOmitemptyTest *TimeDurationOmitemptyTest + + timeDurationInner = &TimeDurationInner{-time.Minute} + timeDurationOmitemptyTest = &TimeDurationOmitemptyTest{timeDurationInner, time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestCrossStructLtFieldValidation(t *testing.T) { + var errs error + validate := New() + + type Inner struct { + CreatedAt *time.Time + String string + Int int + Uint uint + Float float64 + Array []string + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time `validate:"ltcsfield=Inner.CreatedAt"` + String string `validate:"ltcsfield=Inner.String"` + Int int `validate:"ltcsfield=Inner.Int"` + Uint uint `validate:"ltcsfield=Inner.Uint"` + Float float64 `validate:"ltcsfield=Inner.Float"` + Array []string `validate:"ltcsfield=Inner.Array"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * 5) + + inner := &Inner{ + CreatedAt: &then, + String: "abcd", + Int: 13, + Uint: 13, + Float: 1.13, + Array: []string{"val1", "val2"}, + } + + test := &Test{ + Inner: inner, + CreatedAt: &now, + String: "abc", + Int: 12, + Uint: 12, + Float: 1.12, + Array: []string{"val1"}, + } + + errs = validate.Struct(test) + Equal(t, errs, nil) + + test.CreatedAt = &then + test.String = "abcd" + test.Int = 13 + test.Uint = 13 + test.Float = 1.13 + test.Array = []string{"val1", "val2"} + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "ltcsfield") + AssertError(t, errs, "Test.String", "Test.String", "String", "String", "ltcsfield") + AssertError(t, errs, "Test.Int", "Test.Int", "Int", "Int", "ltcsfield") + AssertError(t, errs, "Test.Uint", "Test.Uint", "Uint", "Uint", "ltcsfield") + AssertError(t, errs, "Test.Float", "Test.Float", "Float", "Float", "ltcsfield") + AssertError(t, errs, "Test.Array", "Test.Array", "Array", "Array", "ltcsfield") + + errs = validate.VarWithValue(1, "", "ltcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltcsfield") + + // this test is for the WARNING about unforeseen validation issues. + errs = validate.VarWithValue(test, now, "ltcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "ltcsfield") + AssertError(t, errs, "Test.String", "Test.String", "String", "String", "ltcsfield") + AssertError(t, errs, "Test.Int", "Test.Int", "Int", "Int", "ltcsfield") + AssertError(t, errs, "Test.Uint", "Test.Uint", "Uint", "Uint", "ltcsfield") + AssertError(t, errs, "Test.Float", "Test.Float", "Float", "Float", "ltcsfield") + AssertError(t, errs, "Test.Array", "Test.Array", "Array", "Array", "ltcsfield") + + type Other struct { + Value string + } + + type Test2 struct { + Value Other + Time time.Time `validate:"ltcsfield=Value"` + } + + tst := Test2{ + Value: Other{Value: "StringVal"}, + Time: then, + } + + errs = validate.Struct(tst) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test2.Time", "Test2.Time", "Time", "Time", "ltcsfield") + + // Tests for time.Duration type. + + // -- Validations for variables of time.Duration type. + + errs = validate.VarWithValue(time.Hour, time.Hour+time.Minute, "ltcsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour, "ltcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltcsfield") + + errs = validate.VarWithValue(time.Hour, time.Hour-time.Minute, "ltcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltcsfield") + + errs = validate.VarWithValue(time.Duration(0), -time.Minute, "omitempty,ltcsfield") + Equal(t, errs, nil) + + // -- Validations for a struct and an inner struct with time.Duration type fields. + + type TimeDurationInner struct { + Duration time.Duration + } + var timeDurationInner *TimeDurationInner + + type TimeDurationTest struct { + Inner *TimeDurationInner + Duration time.Duration `validate:"ltcsfield=Inner.Duration"` + } + var timeDurationTest *TimeDurationTest + + timeDurationInner = &TimeDurationInner{time.Hour + time.Minute} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationInner = &TimeDurationInner{time.Hour} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "ltcsfield") + + timeDurationInner = &TimeDurationInner{time.Hour - time.Minute} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "ltcsfield") + + type TimeDurationOmitemptyTest struct { + Inner *TimeDurationInner + Duration time.Duration `validate:"omitempty,ltcsfield=Inner.Duration"` + } + var timeDurationOmitemptyTest *TimeDurationOmitemptyTest + + timeDurationInner = &TimeDurationInner{-time.Minute} + timeDurationOmitemptyTest = &TimeDurationOmitemptyTest{timeDurationInner, time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestCrossStructGteFieldValidation(t *testing.T) { + var errs error + validate := New() + + type Inner struct { + CreatedAt *time.Time + String string + Int int + Uint uint + Float float64 + Array []string + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time `validate:"gtecsfield=Inner.CreatedAt"` + String string `validate:"gtecsfield=Inner.String"` + Int int `validate:"gtecsfield=Inner.Int"` + Uint uint `validate:"gtecsfield=Inner.Uint"` + Float float64 `validate:"gtecsfield=Inner.Float"` + Array []string `validate:"gtecsfield=Inner.Array"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * -5) + + inner := &Inner{ + CreatedAt: &then, + String: "abcd", + Int: 13, + Uint: 13, + Float: 1.13, + Array: []string{"val1", "val2"}, + } + + test := &Test{ + Inner: inner, + CreatedAt: &now, + String: "abcde", + Int: 14, + Uint: 14, + Float: 1.14, + Array: []string{"val1", "val2", "val3"}, + } + + errs = validate.Struct(test) + Equal(t, errs, nil) + + test.CreatedAt = &then + test.String = "abcd" + test.Int = 13 + test.Uint = 13 + test.Float = 1.13 + test.Array = []string{"val1", "val2"} + + errs = validate.Struct(test) + Equal(t, errs, nil) + + before := now.Add(time.Hour * -10) + + test.CreatedAt = &before + test.String = "abc" + test.Int = 12 + test.Uint = 12 + test.Float = 1.12 + test.Array = []string{"val1"} + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "gtecsfield") + AssertError(t, errs, "Test.String", "Test.String", "String", "String", "gtecsfield") + AssertError(t, errs, "Test.Int", "Test.Int", "Int", "Int", "gtecsfield") + AssertError(t, errs, "Test.Uint", "Test.Uint", "Uint", "Uint", "gtecsfield") + AssertError(t, errs, "Test.Float", "Test.Float", "Float", "Float", "gtecsfield") + AssertError(t, errs, "Test.Array", "Test.Array", "Array", "Array", "gtecsfield") + + errs = validate.VarWithValue(1, "", "gtecsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtecsfield") + + // this test is for the WARNING about unforeseen validation issues. + errs = validate.VarWithValue(test, now, "gtecsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "gtecsfield") + AssertError(t, errs, "Test.String", "Test.String", "String", "String", "gtecsfield") + AssertError(t, errs, "Test.Int", "Test.Int", "Int", "Int", "gtecsfield") + AssertError(t, errs, "Test.Uint", "Test.Uint", "Uint", "Uint", "gtecsfield") + AssertError(t, errs, "Test.Float", "Test.Float", "Float", "Float", "gtecsfield") + AssertError(t, errs, "Test.Array", "Test.Array", "Array", "Array", "gtecsfield") + + type Other struct { + Value string + } + + type Test2 struct { + Value Other + Time time.Time `validate:"gtecsfield=Value"` + } + + tst := Test2{ + Value: Other{Value: "StringVal"}, + Time: then, + } + + errs = validate.Struct(tst) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test2.Time", "Test2.Time", "Time", "Time", "gtecsfield") + + // Tests for time.Duration type. + + // -- Validations for variables of time.Duration type. + + errs = validate.VarWithValue(time.Hour, time.Hour-time.Minute, "gtecsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour, "gtecsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour+time.Minute, "gtecsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtecsfield") + + errs = validate.VarWithValue(time.Duration(0), time.Hour, "omitempty,gtecsfield") + Equal(t, errs, nil) + + // -- Validations for a struct and an inner struct with time.Duration type fields. + + type TimeDurationInner struct { + Duration time.Duration + } + var timeDurationInner *TimeDurationInner + + type TimeDurationTest struct { + Inner *TimeDurationInner + Duration time.Duration `validate:"gtecsfield=Inner.Duration"` + } + var timeDurationTest *TimeDurationTest + + timeDurationInner = &TimeDurationInner{time.Hour - time.Minute} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationInner = &TimeDurationInner{time.Hour} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationInner = &TimeDurationInner{time.Hour + time.Minute} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "gtecsfield") + + type TimeDurationOmitemptyTest struct { + Inner *TimeDurationInner + Duration time.Duration `validate:"omitempty,gtecsfield=Inner.Duration"` + } + var timeDurationOmitemptyTest *TimeDurationOmitemptyTest + + timeDurationInner = &TimeDurationInner{time.Hour} + timeDurationOmitemptyTest = &TimeDurationOmitemptyTest{timeDurationInner, time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestCrossStructGtFieldValidation(t *testing.T) { + var errs error + validate := New() + + type Inner struct { + CreatedAt *time.Time + String string + Int int + Uint uint + Float float64 + Array []string + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time `validate:"gtcsfield=Inner.CreatedAt"` + String string `validate:"gtcsfield=Inner.String"` + Int int `validate:"gtcsfield=Inner.Int"` + Uint uint `validate:"gtcsfield=Inner.Uint"` + Float float64 `validate:"gtcsfield=Inner.Float"` + Array []string `validate:"gtcsfield=Inner.Array"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * -5) + + inner := &Inner{ + CreatedAt: &then, + String: "abcd", + Int: 13, + Uint: 13, + Float: 1.13, + Array: []string{"val1", "val2"}, + } + + test := &Test{ + Inner: inner, + CreatedAt: &now, + String: "abcde", + Int: 14, + Uint: 14, + Float: 1.14, + Array: []string{"val1", "val2", "val3"}, + } + + errs = validate.Struct(test) + Equal(t, errs, nil) + + test.CreatedAt = &then + test.String = "abcd" + test.Int = 13 + test.Uint = 13 + test.Float = 1.13 + test.Array = []string{"val1", "val2"} + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "gtcsfield") + AssertError(t, errs, "Test.String", "Test.String", "String", "String", "gtcsfield") + AssertError(t, errs, "Test.Int", "Test.Int", "Int", "Int", "gtcsfield") + AssertError(t, errs, "Test.Uint", "Test.Uint", "Uint", "Uint", "gtcsfield") + AssertError(t, errs, "Test.Float", "Test.Float", "Float", "Float", "gtcsfield") + AssertError(t, errs, "Test.Array", "Test.Array", "Array", "Array", "gtcsfield") + + errs = validate.VarWithValue(1, "", "gtcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtcsfield") + + // this test is for the WARNING about unforeseen validation issues. + errs = validate.VarWithValue(test, now, "gtcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "gtcsfield") + AssertError(t, errs, "Test.String", "Test.String", "String", "String", "gtcsfield") + AssertError(t, errs, "Test.Int", "Test.Int", "Int", "Int", "gtcsfield") + AssertError(t, errs, "Test.Uint", "Test.Uint", "Uint", "Uint", "gtcsfield") + AssertError(t, errs, "Test.Float", "Test.Float", "Float", "Float", "gtcsfield") + AssertError(t, errs, "Test.Array", "Test.Array", "Array", "Array", "gtcsfield") + + type Other struct { + Value string + } + + type Test2 struct { + Value Other + Time time.Time `validate:"gtcsfield=Value"` + } + + tst := Test2{ + Value: Other{Value: "StringVal"}, + Time: then, + } + + errs = validate.Struct(tst) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test2.Time", "Test2.Time", "Time", "Time", "gtcsfield") + + // Tests for time.Duration type. + + // -- Validations for variables of time.Duration type. + + errs = validate.VarWithValue(time.Hour, time.Hour-time.Minute, "gtcsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour, "gtcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtcsfield") + + errs = validate.VarWithValue(time.Hour, time.Hour+time.Minute, "gtcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtcsfield") + + errs = validate.VarWithValue(time.Duration(0), time.Hour, "omitempty,gtcsfield") + Equal(t, errs, nil) + + // -- Validations for a struct and an inner struct with time.Duration type fields. + + type TimeDurationInner struct { + Duration time.Duration + } + var timeDurationInner *TimeDurationInner + + type TimeDurationTest struct { + Inner *TimeDurationInner + Duration time.Duration `validate:"gtcsfield=Inner.Duration"` + } + var timeDurationTest *TimeDurationTest + + timeDurationInner = &TimeDurationInner{time.Hour - time.Minute} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationInner = &TimeDurationInner{time.Hour} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "gtcsfield") + + timeDurationInner = &TimeDurationInner{time.Hour + time.Minute} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "gtcsfield") + + type TimeDurationOmitemptyTest struct { + Inner *TimeDurationInner + Duration time.Duration `validate:"omitempty,gtcsfield=Inner.Duration"` + } + var timeDurationOmitemptyTest *TimeDurationOmitemptyTest + + timeDurationInner = &TimeDurationInner{time.Hour} + timeDurationOmitemptyTest = &TimeDurationOmitemptyTest{timeDurationInner, time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestCrossStructNeFieldValidation(t *testing.T) { + var errs error + validate := New() + + type Inner struct { + CreatedAt *time.Time + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time `validate:"necsfield=Inner.CreatedAt"` + } + + now := time.Now().UTC() + then := now.Add(time.Hour * 5) + + inner := &Inner{ + CreatedAt: &then, + } + + test := &Test{ + Inner: inner, + CreatedAt: &now, + } + + errs = validate.Struct(test) + Equal(t, errs, nil) + + test.CreatedAt = &then + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "necsfield") + + var j uint64 + var k float64 + var j2 uint64 + var k2 float64 + s := "abcd" + i := 1 + j = 1 + k = 1.543 + arr := []string{"test"} + + s2 := "abcd" + i2 := 1 + j2 = 1 + k2 = 1.543 + arr2 := []string{"test"} + arr3 := []string{"test", "test2"} + now2 := now + + errs = validate.VarWithValue(s, s2, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "necsfield") + + errs = validate.VarWithValue(i2, i, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "necsfield") + + errs = validate.VarWithValue(j2, j, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "necsfield") + + errs = validate.VarWithValue(k2, k, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "necsfield") + + errs = validate.VarWithValue(arr2, arr, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "necsfield") + + errs = validate.VarWithValue(now2, now, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "necsfield") + + errs = validate.VarWithValue(arr3, arr, "necsfield") + Equal(t, errs, nil) + + type SInner struct { + Name string + } + + type TStruct struct { + Inner *SInner + CreatedAt *time.Time `validate:"necsfield=Inner"` + } + + sinner := &SInner{ + Name: "NAME", + } + + test2 := &TStruct{ + Inner: sinner, + CreatedAt: &now, + } + + errs = validate.Struct(test2) + Equal(t, errs, nil) + + test2.Inner = nil + errs = validate.Struct(test2) + Equal(t, errs, nil) + + errs = validate.VarWithValue(nil, 1, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "necsfield") + + // Tests for time.Duration type. + + // -- Validations for variables of time.Duration type. + + errs = validate.VarWithValue(time.Hour, time.Hour-time.Minute, "necsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour+time.Minute, "necsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour, "necsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "necsfield") + + errs = validate.VarWithValue(time.Duration(0), time.Duration(0), "omitempty,necsfield") + Equal(t, errs, nil) + + // -- Validations for a struct and an inner struct with time.Duration type fields. + + type TimeDurationInner struct { + Duration time.Duration + } + var timeDurationInner *TimeDurationInner + + type TimeDurationTest struct { + Inner *TimeDurationInner + Duration time.Duration `validate:"necsfield=Inner.Duration"` + } + var timeDurationTest *TimeDurationTest + + timeDurationInner = &TimeDurationInner{time.Hour - time.Minute} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationInner = &TimeDurationInner{time.Hour + time.Minute} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationInner = &TimeDurationInner{time.Hour} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "necsfield") + + type TimeDurationOmitemptyTest struct { + Inner *TimeDurationInner + Duration time.Duration `validate:"omitempty,necsfield=Inner.Duration"` + } + var timeDurationOmitemptyTest *TimeDurationOmitemptyTest + + timeDurationInner = &TimeDurationInner{time.Duration(0)} + timeDurationOmitemptyTest = &TimeDurationOmitemptyTest{timeDurationInner, time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestCrossStructEqFieldValidation(t *testing.T) { + var errs error + validate := New() + + type Inner struct { + CreatedAt *time.Time + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time `validate:"eqcsfield=Inner.CreatedAt"` + } + + now := time.Now().UTC() + + inner := &Inner{ + CreatedAt: &now, + } + + test := &Test{ + Inner: inner, + CreatedAt: &now, + } + + errs = validate.Struct(test) + Equal(t, errs, nil) + + newTime := time.Now().UTC() + test.CreatedAt = &newTime + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.CreatedAt", "Test.CreatedAt", "CreatedAt", "CreatedAt", "eqcsfield") + + var j uint64 + var k float64 + s := "abcd" + i := 1 + j = 1 + k = 1.543 + arr := []string{"test"} + + var j2 uint64 + var k2 float64 + s2 := "abcd" + i2 := 1 + j2 = 1 + k2 = 1.543 + arr2 := []string{"test"} + arr3 := []string{"test", "test2"} + now2 := now + + errs = validate.VarWithValue(s, s2, "eqcsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(i2, i, "eqcsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(j2, j, "eqcsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(k2, k, "eqcsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(arr2, arr, "eqcsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(now2, now, "eqcsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(arr3, arr, "eqcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "eqcsfield") + + type SInner struct { + Name string + } + + type TStruct struct { + Inner *SInner + CreatedAt *time.Time `validate:"eqcsfield=Inner"` + } + + sinner := &SInner{ + Name: "NAME", + } + + test2 := &TStruct{ + Inner: sinner, + CreatedAt: &now, + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TStruct.CreatedAt", "TStruct.CreatedAt", "CreatedAt", "CreatedAt", "eqcsfield") + + test2.Inner = nil + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TStruct.CreatedAt", "TStruct.CreatedAt", "CreatedAt", "CreatedAt", "eqcsfield") + + errs = validate.VarWithValue(nil, 1, "eqcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "eqcsfield") + + // Tests for time.Duration type. + + // -- Validations for variables of time.Duration type. + + errs = validate.VarWithValue(time.Hour, time.Hour, "eqcsfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour-time.Minute, "eqcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "eqcsfield") + + errs = validate.VarWithValue(time.Hour, time.Hour+time.Minute, "eqcsfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "eqcsfield") + + errs = validate.VarWithValue(time.Duration(0), time.Hour, "omitempty,eqcsfield") + Equal(t, errs, nil) + + // -- Validations for a struct and an inner struct with time.Duration type fields. + + type TimeDurationInner struct { + Duration time.Duration + } + var timeDurationInner *TimeDurationInner + + type TimeDurationTest struct { + Inner *TimeDurationInner + Duration time.Duration `validate:"eqcsfield=Inner.Duration"` + } + var timeDurationTest *TimeDurationTest + + timeDurationInner = &TimeDurationInner{time.Hour} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationInner = &TimeDurationInner{time.Hour - time.Minute} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "eqcsfield") + + timeDurationInner = &TimeDurationInner{time.Hour + time.Minute} + timeDurationTest = &TimeDurationTest{timeDurationInner, time.Hour} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "eqcsfield") + + type TimeDurationOmitemptyTest struct { + Inner *TimeDurationInner + Duration time.Duration `validate:"omitempty,eqcsfield=Inner.Duration"` + } + var timeDurationOmitemptyTest *TimeDurationOmitemptyTest + + timeDurationInner = &TimeDurationInner{time.Hour} + timeDurationOmitemptyTest = &TimeDurationOmitemptyTest{timeDurationInner, time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestCrossNamespaceFieldValidation(t *testing.T) { + + type SliceStruct struct { + Name string + } + + type Inner struct { + CreatedAt *time.Time + Slice []string + SliceStructs []*SliceStruct + SliceSlice [][]string + SliceSliceStruct [][]*SliceStruct + SliceMap []map[string]string + Map map[string]string + MapMap map[string]map[string]string + MapStructs map[string]*SliceStruct + MapMapStruct map[string]map[string]*SliceStruct + MapSlice map[string][]string + MapInt map[int]string + MapInt8 map[int8]string + MapInt16 map[int16]string + MapInt32 map[int32]string + MapInt64 map[int64]string + MapUint map[uint]string + MapUint8 map[uint8]string + MapUint16 map[uint16]string + MapUint32 map[uint32]string + MapUint64 map[uint64]string + MapFloat32 map[float32]string + MapFloat64 map[float64]string + MapBool map[bool]string + } + + type Test struct { + Inner *Inner + CreatedAt *time.Time + } + + now := time.Now() + + inner := &Inner{ + CreatedAt: &now, + Slice: []string{"val1", "val2", "val3"}, + SliceStructs: []*SliceStruct{{Name: "name1"}, {Name: "name2"}, {Name: "name3"}}, + SliceSlice: [][]string{{"1", "2", "3"}, {"4", "5", "6"}, {"7", "8", "9"}}, + SliceSliceStruct: [][]*SliceStruct{{{Name: "name1"}, {Name: "name2"}, {Name: "name3"}}, {{Name: "name4"}, {Name: "name5"}, {Name: "name6"}}, {{Name: "name7"}, {Name: "name8"}, {Name: "name9"}}}, + SliceMap: []map[string]string{{"key1": "val1", "key2": "val2", "key3": "val3"}, {"key4": "val4", "key5": "val5", "key6": "val6"}}, + Map: map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"}, + MapStructs: map[string]*SliceStruct{"key1": {Name: "name1"}, "key2": {Name: "name2"}, "key3": {Name: "name3"}}, + MapMap: map[string]map[string]string{"key1": {"key1-1": "val1"}, "key2": {"key2-1": "val2"}, "key3": {"key3-1": "val3"}}, + MapMapStruct: map[string]map[string]*SliceStruct{"key1": {"key1-1": {Name: "name1"}}, "key2": {"key2-1": {Name: "name2"}}, "key3": {"key3-1": {Name: "name3"}}}, + MapSlice: map[string][]string{"key1": {"1", "2", "3"}, "key2": {"4", "5", "6"}, "key3": {"7", "8", "9"}}, + MapInt: map[int]string{1: "val1", 2: "val2", 3: "val3"}, + MapInt8: map[int8]string{1: "val1", 2: "val2", 3: "val3"}, + MapInt16: map[int16]string{1: "val1", 2: "val2", 3: "val3"}, + MapInt32: map[int32]string{1: "val1", 2: "val2", 3: "val3"}, + MapInt64: map[int64]string{1: "val1", 2: "val2", 3: "val3"}, + MapUint: map[uint]string{1: "val1", 2: "val2", 3: "val3"}, + MapUint8: map[uint8]string{1: "val1", 2: "val2", 3: "val3"}, + MapUint16: map[uint16]string{1: "val1", 2: "val2", 3: "val3"}, + MapUint32: map[uint32]string{1: "val1", 2: "val2", 3: "val3"}, + MapUint64: map[uint64]string{1: "val1", 2: "val2", 3: "val3"}, + MapFloat32: map[float32]string{1.01: "val1", 2.02: "val2", 3.03: "val3"}, + MapFloat64: map[float64]string{1.01: "val1", 2.02: "val2", 3.03: "val3"}, + MapBool: map[bool]string{true: "val1", false: "val2"}, + } + + test := &Test{ + Inner: inner, + CreatedAt: &now, + } + + val := reflect.ValueOf(test) + + vd := New() + v := &validate{ + v: vd, + } + + current, kind, _, ok := v.getStructFieldOKInternal(val, "Inner.CreatedAt") + Equal(t, ok, true) + Equal(t, kind, reflect.Struct) + tm, ok := current.Interface().(time.Time) + Equal(t, ok, true) + Equal(t, tm, now) + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.Slice[1]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, _, _, ok = v.getStructFieldOKInternal(val, "Inner.CrazyNonExistantField") + Equal(t, ok, false) + + current, _, _, ok = v.getStructFieldOKInternal(val, "Inner.Slice[101]") + Equal(t, ok, false) + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.Map[key3]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val3") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapMap[key2][key2-1]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapStructs[key2].Name") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "name2") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapMapStruct[key3][key3-1].Name") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "name3") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceSlice[2][0]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "7") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceSliceStruct[2][1].Name") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "name8") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceMap[1][key5]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val5") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapSlice[key3][2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "9") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt8[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt16[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt32[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapInt64[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint8[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint16[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint32[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapUint64[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapFloat32[3.03]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val3") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapFloat64[2.02]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val2") + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.MapBool[true]") + Equal(t, ok, true) + Equal(t, kind, reflect.String) + Equal(t, current.String(), "val1") + + inner = &Inner{ + CreatedAt: &now, + Slice: []string{"val1", "val2", "val3"}, + SliceStructs: []*SliceStruct{{Name: "name1"}, {Name: "name2"}, nil}, + SliceSlice: [][]string{{"1", "2", "3"}, {"4", "5", "6"}, {"7", "8", "9"}}, + SliceSliceStruct: [][]*SliceStruct{{{Name: "name1"}, {Name: "name2"}, {Name: "name3"}}, {{Name: "name4"}, {Name: "name5"}, {Name: "name6"}}, {{Name: "name7"}, {Name: "name8"}, {Name: "name9"}}}, + SliceMap: []map[string]string{{"key1": "val1", "key2": "val2", "key3": "val3"}, {"key4": "val4", "key5": "val5", "key6": "val6"}}, + Map: map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"}, + MapStructs: map[string]*SliceStruct{"key1": {Name: "name1"}, "key2": {Name: "name2"}, "key3": {Name: "name3"}}, + MapMap: map[string]map[string]string{"key1": {"key1-1": "val1"}, "key2": {"key2-1": "val2"}, "key3": {"key3-1": "val3"}}, + MapMapStruct: map[string]map[string]*SliceStruct{"key1": {"key1-1": {Name: "name1"}}, "key2": {"key2-1": {Name: "name2"}}, "key3": {"key3-1": {Name: "name3"}}}, + MapSlice: map[string][]string{"key1": {"1", "2", "3"}, "key2": {"4", "5", "6"}, "key3": {"7", "8", "9"}}, + } + + test = &Test{ + Inner: inner, + CreatedAt: nil, + } + + val = reflect.ValueOf(test) + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceStructs[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.Ptr) + Equal(t, current.String(), "<*validator.SliceStruct Value>") + Equal(t, current.IsNil(), true) + + current, kind, _, ok = v.getStructFieldOKInternal(val, "Inner.SliceStructs[2].Name") + Equal(t, ok, false) + Equal(t, kind, reflect.Ptr) + Equal(t, current.String(), "<*validator.SliceStruct Value>") + Equal(t, current.IsNil(), true) + + PanicMatches(t, func() { v.getStructFieldOKInternal(reflect.ValueOf(1), "crazyinput") }, "Invalid field namespace") +} + +func TestExistsValidation(t *testing.T) { + + jsonText := "{ \"truthiness2\": true }" + + type Thing struct { + Truthiness *bool `json:"truthiness" validate:"required"` + } + + var ting Thing + + err := json.Unmarshal([]byte(jsonText), &ting) + Equal(t, err, nil) + NotEqual(t, ting, nil) + Equal(t, ting.Truthiness, nil) + + validate := New() + errs := validate.Struct(ting) + NotEqual(t, errs, nil) + AssertError(t, errs, "Thing.Truthiness", "Thing.Truthiness", "Truthiness", "Truthiness", "required") + + jsonText = "{ \"truthiness\": true }" + + err = json.Unmarshal([]byte(jsonText), &ting) + Equal(t, err, nil) + NotEqual(t, ting, nil) + Equal(t, ting.Truthiness, true) + + errs = validate.Struct(ting) + Equal(t, errs, nil) +} + +func TestSQLValue2Validation(t *testing.T) { + + validate := New() + validate.RegisterCustomTypeFunc(ValidateValuerType, valuer{}, (*driver.Valuer)(nil), sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}) + validate.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) + validate.RegisterCustomTypeFunc(OverrideIntTypeForSomeReason, 1) + + val := valuer{ + Name: "", + } + + errs := validate.Var(val, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "required") + + val.Name = "Valid Name" + errs = validate.VarCtx(context.Background(), val, "required") + Equal(t, errs, nil) + + val.Name = "errorme" + + PanicMatches(t, func() { _ = validate.Var(val, "required") }, "SQL Driver Valuer error: some kind of error") + + myVal := valuer{ + Name: "", + } + + errs = validate.Var(myVal, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "required") + + cust := MadeUpCustomType{ + FirstName: "Joey", + LastName: "Bloggs", + } + + c := CustomMadeUpStruct{MadeUp: cust, OverriddenInt: 2} + + errs = validate.Struct(c) + Equal(t, errs, nil) + + c.MadeUp.FirstName = "" + c.OverriddenInt = 1 + + errs = validate.Struct(c) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 2) + AssertError(t, errs, "CustomMadeUpStruct.MadeUp", "CustomMadeUpStruct.MadeUp", "MadeUp", "MadeUp", "required") + AssertError(t, errs, "CustomMadeUpStruct.OverriddenInt", "CustomMadeUpStruct.OverriddenInt", "OverriddenInt", "OverriddenInt", "gt") +} + +func TestSQLValueValidation(t *testing.T) { + + validate := New() + validate.RegisterCustomTypeFunc(ValidateValuerType, (*driver.Valuer)(nil), valuer{}) + validate.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) + validate.RegisterCustomTypeFunc(OverrideIntTypeForSomeReason, 1) + + val := valuer{ + Name: "", + } + + errs := validate.Var(val, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "required") + + val.Name = "Valid Name" + errs = validate.Var(val, "required") + Equal(t, errs, nil) + + val.Name = "errorme" + + PanicMatches(t, func() { errs = validate.Var(val, "required") }, "SQL Driver Valuer error: some kind of error") + + myVal := valuer{ + Name: "", + } + + errs = validate.Var(myVal, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "required") + + cust := MadeUpCustomType{ + FirstName: "Joey", + LastName: "Bloggs", + } + + c := CustomMadeUpStruct{MadeUp: cust, OverriddenInt: 2} + + errs = validate.Struct(c) + Equal(t, errs, nil) + + c.MadeUp.FirstName = "" + c.OverriddenInt = 1 + + errs = validate.Struct(c) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 2) + AssertError(t, errs, "CustomMadeUpStruct.MadeUp", "CustomMadeUpStruct.MadeUp", "MadeUp", "MadeUp", "required") + AssertError(t, errs, "CustomMadeUpStruct.OverriddenInt", "CustomMadeUpStruct.OverriddenInt", "OverriddenInt", "OverriddenInt", "gt") +} + +func TestMACValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"3D:F2:C9:A6:B3:4F", true}, + {"3D-F2-C9-A6-B3:4F", false}, + {"123", false}, + {"", false}, + {"abacaba", false}, + {"00:25:96:FF:FE:12:34:56", true}, + {"0025:96FF:FE12:3456", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "mac") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d mac failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d mac failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "mac" { + t.Fatalf("Index: %d mac failed Error: %s", i, errs) + } + } + } + } +} + +func TestIPValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"10.0.0.1", true}, + {"172.16.0.1", true}, + {"192.168.0.1", true}, + {"192.168.255.254", true}, + {"192.168.255.256", false}, + {"172.16.255.254", true}, + {"172.16.256.255", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652", true}, + {"2001:cdba:0:0:0:0:3257:9652", true}, + {"2001:cdba::3257:9652", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "ip") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ip failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ip failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "ip" { + t.Fatalf("Index: %d ip failed Error: %s", i, errs) + } + } + } + } +} + +func TestIPv6Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"10.0.0.1", false}, + {"172.16.0.1", false}, + {"192.168.0.1", false}, + {"192.168.255.254", false}, + {"192.168.255.256", false}, + {"172.16.255.254", false}, + {"172.16.256.255", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652", true}, + {"2001:cdba:0:0:0:0:3257:9652", true}, + {"2001:cdba::3257:9652", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "ipv6") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "ipv6" { + t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) + } + } + } + } +} + +func TestIPv4Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"10.0.0.1", true}, + {"172.16.0.1", true}, + {"192.168.0.1", true}, + {"192.168.255.254", true}, + {"192.168.255.256", false}, + {"172.16.255.254", true}, + {"172.16.256.255", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652", false}, + {"2001:cdba:0:0:0:0:3257:9652", false}, + {"2001:cdba::3257:9652", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "ipv4") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "ipv4" { + t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) + } + } + } + } +} + +func TestCIDRValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"10.0.0.0/0", true}, + {"10.0.0.1/8", true}, + {"172.16.0.1/16", true}, + {"192.168.0.1/24", true}, + {"192.168.255.254/24", true}, + {"192.168.255.254/48", false}, + {"192.168.255.256/24", false}, + {"172.16.255.254/16", true}, + {"172.16.256.255/16", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652/64", true}, + {"2001:cdba:0000:0000:0000:0000:3257:9652/256", false}, + {"2001:cdba:0:0:0:0:3257:9652/32", true}, + {"2001:cdba::3257:9652/16", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "cidr") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d cidr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d cidr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "cidr" { + t.Fatalf("Index: %d cidr failed Error: %s", i, errs) + } + } + } + } +} + +func TestCIDRv6Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"10.0.0.0/0", false}, + {"10.0.0.1/8", false}, + {"172.16.0.1/16", false}, + {"192.168.0.1/24", false}, + {"192.168.255.254/24", false}, + {"192.168.255.254/48", false}, + {"192.168.255.256/24", false}, + {"172.16.255.254/16", false}, + {"172.16.256.255/16", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652/64", true}, + {"2001:cdba:0000:0000:0000:0000:3257:9652/256", false}, + {"2001:cdba:0:0:0:0:3257:9652/32", true}, + {"2001:cdba::3257:9652/16", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "cidrv6") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d cidrv6 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d cidrv6 failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "cidrv6" { + t.Fatalf("Index: %d cidrv6 failed Error: %s", i, errs) + } + } + } + } +} + +func TestCIDRv4Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"10.0.0.0/0", true}, + {"10.0.0.1/8", true}, + {"172.16.0.1/16", true}, + {"192.168.0.1/24", true}, + {"192.168.255.254/24", true}, + {"192.168.255.254/48", false}, + {"192.168.255.256/24", false}, + {"172.16.255.254/16", true}, + {"172.16.256.255/16", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652/64", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652/256", false}, + {"2001:cdba:0:0:0:0:3257:9652/32", false}, + {"2001:cdba::3257:9652/16", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "cidrv4") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d cidrv4 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d cidrv4 failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "cidrv4" { + t.Fatalf("Index: %d cidrv4 failed Error: %s", i, errs) + } + } + } + } +} + +func TestTCPAddrValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {":80", false}, + {"127.0.0.1:80", true}, + {"[::1]:80", true}, + {"256.0.0.0:1", false}, + {"[::1]", false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.param, "tcp_addr") + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d tcp_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d tcp_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "tcp_addr" { + t.Fatalf("Index: %d tcp_addr failed Error: %s", i, errs) + } + } + } + } +} + +func TestTCP6AddrValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {":80", false}, + {"127.0.0.1:80", false}, + {"[::1]:80", true}, + {"256.0.0.0:1", false}, + {"[::1]", false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.param, "tcp6_addr") + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d tcp6_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d tcp6_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "tcp6_addr" { + t.Fatalf("Index: %d tcp6_addr failed Error: %s", i, errs) + } + } + } + } +} + +func TestTCP4AddrValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {":80", false}, + {"127.0.0.1:80", true}, + {"[::1]:80", false}, // https://github.com/golang/go/issues/14037 + {"256.0.0.0:1", false}, + {"[::1]", false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.param, "tcp4_addr") + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d tcp4_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Log(test.param, IsEqual(errs, nil)) + t.Fatalf("Index: %d tcp4_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "tcp4_addr" { + t.Fatalf("Index: %d tcp4_addr failed Error: %s", i, errs) + } + } + } + } +} + +func TestUDPAddrValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {":80", false}, + {"127.0.0.1:80", true}, + {"[::1]:80", true}, + {"256.0.0.0:1", false}, + {"[::1]", false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.param, "udp_addr") + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d udp_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d udp_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "udp_addr" { + t.Fatalf("Index: %d udp_addr failed Error: %s", i, errs) + } + } + } + } +} + +func TestUDP6AddrValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {":80", false}, + {"127.0.0.1:80", false}, + {"[::1]:80", true}, + {"256.0.0.0:1", false}, + {"[::1]", false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.param, "udp6_addr") + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d udp6_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d udp6_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "udp6_addr" { + t.Fatalf("Index: %d udp6_addr failed Error: %s", i, errs) + } + } + } + } +} + +func TestUDP4AddrValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {":80", false}, + {"127.0.0.1:80", true}, + {"[::1]:80", false}, // https://github.com/golang/go/issues/14037 + {"256.0.0.0:1", false}, + {"[::1]", false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.param, "udp4_addr") + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d udp4_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Log(test.param, IsEqual(errs, nil)) + t.Fatalf("Index: %d udp4_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "udp4_addr" { + t.Fatalf("Index: %d udp4_addr failed Error: %s", i, errs) + } + } + } + } +} + +func TestIPAddrValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"127.0.0.1", true}, + {"127.0.0.1:80", false}, + {"::1", true}, + {"256.0.0.0", false}, + {"localhost", false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.param, "ip_addr") + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ip_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ip_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "ip_addr" { + t.Fatalf("Index: %d ip_addr failed Error: %s", i, errs) + } + } + } + } +} + +func TestIP6AddrValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"127.0.0.1", false}, // https://github.com/golang/go/issues/14037 + {"127.0.0.1:80", false}, + {"::1", true}, + {"0:0:0:0:0:0:0:1", true}, + {"256.0.0.0", false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.param, "ip6_addr") + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ip6_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ip6_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "ip6_addr" { + t.Fatalf("Index: %d ip6_addr failed Error: %s", i, errs) + } + } + } + } +} + +func TestIP4AddrValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"127.0.0.1", true}, + {"127.0.0.1:80", false}, + {"::1", false}, // https://github.com/golang/go/issues/14037 + {"256.0.0.0", false}, + {"localhost", false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.param, "ip4_addr") + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ip4_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Log(test.param, IsEqual(errs, nil)) + t.Fatalf("Index: %d ip4_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "ip4_addr" { + t.Fatalf("Index: %d ip4_addr failed Error: %s", i, errs) + } + } + } + } +} + +func TestUnixAddrValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", true}, + {"v.sock", true}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.param, "unix_addr") + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d unix_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Log(test.param, IsEqual(errs, nil)) + t.Fatalf("Index: %d unix_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "unix_addr" { + t.Fatalf("Index: %d unix_addr failed Error: %s", i, errs) + } + } + } + } +} + +func TestSliceMapArrayChanFuncPtrInterfaceRequiredValidation(t *testing.T) { + + validate := New() + + var m map[string]string + + errs := validate.Var(m, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "required") + + m = map[string]string{} + errs = validate.Var(m, "required") + Equal(t, errs, nil) + + var arr [5]string + errs = validate.Var(arr, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "required") + + arr[0] = "ok" + errs = validate.Var(arr, "required") + Equal(t, errs, nil) + + var s []string + errs = validate.Var(s, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "required") + + s = []string{} + errs = validate.Var(s, "required") + Equal(t, errs, nil) + + var c chan string + errs = validate.Var(c, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "required") + + c = make(chan string) + errs = validate.Var(c, "required") + Equal(t, errs, nil) + + var tst *int + errs = validate.Var(tst, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "required") + + one := 1 + tst = &one + errs = validate.Var(tst, "required") + Equal(t, errs, nil) + + var iface interface{} + + errs = validate.Var(iface, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "required") + + errs = validate.Var(iface, "omitempty,required") + Equal(t, errs, nil) + + errs = validate.Var(iface, "") + Equal(t, errs, nil) + + errs = validate.VarWithValue(nil, iface, "") + Equal(t, errs, nil) + + var f func(string) + + errs = validate.Var(f, "required") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "required") + + f = func(name string) {} + + errs = validate.Var(f, "required") + Equal(t, errs, nil) +} + +func TestDatePtrValidationIssueValidation(t *testing.T) { + + type Test struct { + LastViewed *time.Time + Reminder *time.Time + } + + test := &Test{} + + validate := New() + errs := validate.Struct(test) + Equal(t, errs, nil) +} + +func TestCommaAndPipeObfuscationValidation(t *testing.T) { + s := "My Name Is, |joeybloggs|" + + validate := New() + + errs := validate.Var(s, "excludesall=0x2C") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "excludesall") + + errs = validate.Var(s, "excludesall=0x7C") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "excludesall") +} + +func TestBadKeyValidation(t *testing.T) { + type Test struct { + Name string `validate:"required, "` + } + + tst := &Test{ + Name: "test", + } + + validate := New() + + PanicMatches(t, func() { _ = validate.Struct(tst) }, "Undefined validation function ' ' on field 'Name'") + + type Test2 struct { + Name string `validate:"required,,len=2"` + } + + tst2 := &Test2{ + Name: "test", + } + + PanicMatches(t, func() { _ = validate.Struct(tst2) }, "Invalid validation tag on field 'Name'") +} + +func TestInterfaceErrValidation(t *testing.T) { + + var v2 interface{} = 1 + var v1 interface{} = v2 + + validate := New() + errs := validate.Var(v1, "len=1") + Equal(t, errs, nil) + + errs = validate.Var(v2, "len=1") + Equal(t, errs, nil) + + type ExternalCMD struct { + Userid string `json:"userid"` + Action uint32 `json:"action"` + Data interface{} `json:"data,omitempty" validate:"required"` + } + + s := &ExternalCMD{ + Userid: "123456", + Action: 10000, + // Data: 1, + } + + errs = validate.Struct(s) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "ExternalCMD.Data", "ExternalCMD.Data", "Data", "Data", "required") + + type ExternalCMD2 struct { + Userid string `json:"userid"` + Action uint32 `json:"action"` + Data interface{} `json:"data,omitempty" validate:"len=1"` + } + + s2 := &ExternalCMD2{ + Userid: "123456", + Action: 10000, + // Data: 1, + } + + errs = validate.Struct(s2) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "ExternalCMD2.Data", "ExternalCMD2.Data", "Data", "Data", "len") + + s3 := &ExternalCMD2{ + Userid: "123456", + Action: 10000, + Data: 2, + } + + errs = validate.Struct(s3) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "ExternalCMD2.Data", "ExternalCMD2.Data", "Data", "Data", "len") + + type Inner struct { + Name string `validate:"required"` + } + + inner := &Inner{ + Name: "", + } + + s4 := &ExternalCMD{ + Userid: "123456", + Action: 10000, + Data: inner, + } + + errs = validate.Struct(s4) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "ExternalCMD.Data.Name", "ExternalCMD.Data.Name", "Name", "Name", "required") + + type TestMapStructPtr struct { + Errs map[int]interface{} `validate:"gt=0,dive,len=2"` + } + + mip := map[int]interface{}{0: &Inner{"ok"}, 3: nil, 4: &Inner{"ok"}} + + msp := &TestMapStructPtr{ + Errs: mip, + } + + errs = validate.Struct(msp) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "TestMapStructPtr.Errs[3]", "TestMapStructPtr.Errs[3]", "Errs[3]", "Errs[3]", "len") + + type TestMultiDimensionalStructs struct { + Errs [][]interface{} `validate:"gt=0,dive,dive"` + } + + var errStructArray [][]interface{} + + errStructArray = append(errStructArray, []interface{}{&Inner{"ok"}, &Inner{""}, &Inner{""}}) + errStructArray = append(errStructArray, []interface{}{&Inner{"ok"}, &Inner{""}, &Inner{""}}) + + tms := &TestMultiDimensionalStructs{ + Errs: errStructArray, + } + + errs = validate.Struct(tms) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 4) + AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][1].Name", "TestMultiDimensionalStructs.Errs[0][1].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][2].Name", "TestMultiDimensionalStructs.Errs[0][2].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructs.Errs[1][1].Name", "TestMultiDimensionalStructs.Errs[1][1].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructs.Errs[1][2].Name", "TestMultiDimensionalStructs.Errs[1][2].Name", "Name", "Name", "required") + + type TestMultiDimensionalStructsPtr2 struct { + Errs [][]*Inner `validate:"gt=0,dive,dive,required"` + } + + var errStructPtr2Array [][]*Inner + + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, nil}) + + tmsp2 := &TestMultiDimensionalStructsPtr2{ + Errs: errStructPtr2Array, + } + + errs = validate.Struct(tmsp2) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 6) + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][1].Name", "TestMultiDimensionalStructsPtr2.Errs[0][1].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][2].Name", "TestMultiDimensionalStructsPtr2.Errs[0][2].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[1][1].Name", "TestMultiDimensionalStructsPtr2.Errs[1][1].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[1][2].Name", "TestMultiDimensionalStructsPtr2.Errs[1][2].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[2][1].Name", "TestMultiDimensionalStructsPtr2.Errs[2][1].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[2][2]", "TestMultiDimensionalStructsPtr2.Errs[2][2]", "Errs[2][2]", "Errs[2][2]", "required") + + m := map[int]interface{}{0: "ok", 3: "", 4: "ok"} + + errs = validate.Var(m, "len=3,dive,len=2") + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "[3]", "[3]", "[3]", "[3]", "len") + + errs = validate.Var(m, "len=2,dive,required") + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "", "", "", "", "len") + + arr := []interface{}{"ok", "", "ok"} + + errs = validate.Var(arr, "len=3,dive,len=2") + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "[1]", "[1]", "[1]", "[1]", "len") + + errs = validate.Var(arr, "len=2,dive,required") + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "", "", "", "", "len") + + type MyStruct struct { + A, B string + C interface{} + } + + var a MyStruct + + a.A = "value" + a.C = "nu" + + errs = validate.Struct(a) + Equal(t, errs, nil) +} + +func TestMapDiveValidation(t *testing.T) { + + validate := New() + + n := map[int]interface{}{0: nil} + errs := validate.Var(n, "omitempty,required") + Equal(t, errs, nil) + + m := map[int]string{0: "ok", 3: "", 4: "ok"} + + errs = validate.Var(m, "len=3,dive,required") + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "[3]", "[3]", "[3]", "[3]", "required") + + errs = validate.Var(m, "len=2,dive,required") + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "", "", "", "", "len") + + type Inner struct { + Name string `validate:"required"` + } + + type TestMapStruct struct { + Errs map[int]Inner `validate:"gt=0,dive"` + } + + mi := map[int]Inner{0: {"ok"}, 3: {""}, 4: {"ok"}} + + ms := &TestMapStruct{ + Errs: mi, + } + + errs = validate.Struct(ms) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "TestMapStruct.Errs[3].Name", "TestMapStruct.Errs[3].Name", "Name", "Name", "required") + + // for full test coverage + s := fmt.Sprint(errs.Error()) + NotEqual(t, s, "") + + type TestMapTimeStruct struct { + Errs map[int]*time.Time `validate:"gt=0,dive,required"` + } + + t1 := time.Now().UTC() + + mta := map[int]*time.Time{0: &t1, 3: nil, 4: nil} + + mt := &TestMapTimeStruct{ + Errs: mta, + } + + errs = validate.Struct(mt) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 2) + AssertError(t, errs, "TestMapTimeStruct.Errs[3]", "TestMapTimeStruct.Errs[3]", "Errs[3]", "Errs[3]", "required") + AssertError(t, errs, "TestMapTimeStruct.Errs[4]", "TestMapTimeStruct.Errs[4]", "Errs[4]", "Errs[4]", "required") + + type TestMapStructPtr struct { + Errs map[int]*Inner `validate:"gt=0,dive,required"` + } + + mip := map[int]*Inner{0: {"ok"}, 3: nil, 4: {"ok"}} + + msp := &TestMapStructPtr{ + Errs: mip, + } + + errs = validate.Struct(msp) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "TestMapStructPtr.Errs[3]", "TestMapStructPtr.Errs[3]", "Errs[3]", "Errs[3]", "required") + + type TestMapStructPtr2 struct { + Errs map[int]*Inner `validate:"gt=0,dive,omitempty,required"` + } + + mip2 := map[int]*Inner{0: {"ok"}, 3: nil, 4: {"ok"}} + + msp2 := &TestMapStructPtr2{ + Errs: mip2, + } + + errs = validate.Struct(msp2) + Equal(t, errs, nil) + + v2 := New() + v2.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] + + if name == "-" { + return "" + } + + return name + }) + + type MapDiveJSONTest struct { + Map map[string]string `validate:"required,gte=1,dive,gte=1" json:"MyName"` + } + + mdjt := &MapDiveJSONTest{ + Map: map[string]string{ + "Key1": "Value1", + "Key2": "", + }, + } + + err := v2.Struct(mdjt) + NotEqual(t, err, nil) + + errs = err.(ValidationErrors) + fe := getError(errs, "MapDiveJSONTest.MyName[Key2]", "MapDiveJSONTest.Map[Key2]") + NotEqual(t, fe, nil) + Equal(t, fe.Tag(), "gte") + Equal(t, fe.ActualTag(), "gte") + Equal(t, fe.Field(), "MyName[Key2]") + Equal(t, fe.StructField(), "Map[Key2]") +} + +func TestArrayDiveValidation(t *testing.T) { + + validate := New() + + arr := []string{"ok", "", "ok"} + + errs := validate.Var(arr, "len=3,dive,required") + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "[1]", "[1]", "[1]", "[1]", "required") + + errs = validate.Var(arr, "len=2,dive,required") + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "", "", "", "", "len") + + type BadDive struct { + Name string `validate:"dive"` + } + + bd := &BadDive{ + Name: "TEST", + } + + PanicMatches(t, func() { _ = validate.Struct(bd) }, "dive error! can't dive on a non slice or map") + + type Test struct { + Errs []string `validate:"gt=0,dive,required"` + } + + test := &Test{ + Errs: []string{"ok", "", "ok"}, + } + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "Test.Errs[1]", "Test.Errs[1]", "Errs[1]", "Errs[1]", "required") + + test = &Test{ + Errs: []string{"ok", "ok", ""}, + } + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "Test.Errs[2]", "Test.Errs[2]", "Errs[2]", "Errs[2]", "required") + + type TestMultiDimensional struct { + Errs [][]string `validate:"gt=0,dive,dive,required"` + } + + var errArray [][]string + + errArray = append(errArray, []string{"ok", "", ""}) + errArray = append(errArray, []string{"ok", "", ""}) + + tm := &TestMultiDimensional{ + Errs: errArray, + } + + errs = validate.Struct(tm) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 4) + AssertError(t, errs, "TestMultiDimensional.Errs[0][1]", "TestMultiDimensional.Errs[0][1]", "Errs[0][1]", "Errs[0][1]", "required") + AssertError(t, errs, "TestMultiDimensional.Errs[0][2]", "TestMultiDimensional.Errs[0][2]", "Errs[0][2]", "Errs[0][2]", "required") + AssertError(t, errs, "TestMultiDimensional.Errs[1][1]", "TestMultiDimensional.Errs[1][1]", "Errs[1][1]", "Errs[1][1]", "required") + AssertError(t, errs, "TestMultiDimensional.Errs[1][2]", "TestMultiDimensional.Errs[1][2]", "Errs[1][2]", "Errs[1][2]", "required") + + type Inner struct { + Name string `validate:"required"` + } + + type TestMultiDimensionalStructs struct { + Errs [][]Inner `validate:"gt=0,dive,dive"` + } + + var errStructArray [][]Inner + + errStructArray = append(errStructArray, []Inner{{"ok"}, {""}, {""}}) + errStructArray = append(errStructArray, []Inner{{"ok"}, {""}, {""}}) + + tms := &TestMultiDimensionalStructs{ + Errs: errStructArray, + } + + errs = validate.Struct(tms) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 4) + AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][1].Name", "TestMultiDimensionalStructs.Errs[0][1].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][2].Name", "TestMultiDimensionalStructs.Errs[0][2].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructs.Errs[1][1].Name", "TestMultiDimensionalStructs.Errs[1][1].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructs.Errs[1][2].Name", "TestMultiDimensionalStructs.Errs[1][2].Name", "Name", "Name", "required") + + type TestMultiDimensionalStructsPtr struct { + Errs [][]*Inner `validate:"gt=0,dive,dive"` + } + + var errStructPtrArray [][]*Inner + + errStructPtrArray = append(errStructPtrArray, []*Inner{{"ok"}, {""}, {""}}) + errStructPtrArray = append(errStructPtrArray, []*Inner{{"ok"}, {""}, {""}}) + errStructPtrArray = append(errStructPtrArray, []*Inner{{"ok"}, {""}, nil}) + + tmsp := &TestMultiDimensionalStructsPtr{ + Errs: errStructPtrArray, + } + + errs = validate.Struct(tmsp) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 5) + AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[0][1].Name", "TestMultiDimensionalStructsPtr.Errs[0][1].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[0][2].Name", "TestMultiDimensionalStructsPtr.Errs[0][2].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[1][1].Name", "TestMultiDimensionalStructsPtr.Errs[1][1].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[1][2].Name", "TestMultiDimensionalStructsPtr.Errs[1][2].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[2][1].Name", "TestMultiDimensionalStructsPtr.Errs[2][1].Name", "Name", "Name", "required") + + // for full test coverage + s := fmt.Sprint(errs.Error()) + NotEqual(t, s, "") + + type TestMultiDimensionalStructsPtr2 struct { + Errs [][]*Inner `validate:"gt=0,dive,dive,required"` + } + + var errStructPtr2Array [][]*Inner + + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr2Array = append(errStructPtr2Array, []*Inner{{"ok"}, {""}, nil}) + + tmsp2 := &TestMultiDimensionalStructsPtr2{ + Errs: errStructPtr2Array, + } + + errs = validate.Struct(tmsp2) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 6) + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][1].Name", "TestMultiDimensionalStructsPtr2.Errs[0][1].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][2].Name", "TestMultiDimensionalStructsPtr2.Errs[0][2].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[1][1].Name", "TestMultiDimensionalStructsPtr2.Errs[1][1].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[1][2].Name", "TestMultiDimensionalStructsPtr2.Errs[1][2].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[2][1].Name", "TestMultiDimensionalStructsPtr2.Errs[2][1].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[2][2]", "TestMultiDimensionalStructsPtr2.Errs[2][2]", "Errs[2][2]", "Errs[2][2]", "required") + + type TestMultiDimensionalStructsPtr3 struct { + Errs [][]*Inner `validate:"gt=0,dive,dive,omitempty"` + } + + var errStructPtr3Array [][]*Inner + + errStructPtr3Array = append(errStructPtr3Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr3Array = append(errStructPtr3Array, []*Inner{{"ok"}, {""}, {""}}) + errStructPtr3Array = append(errStructPtr3Array, []*Inner{{"ok"}, {""}, nil}) + + tmsp3 := &TestMultiDimensionalStructsPtr3{ + Errs: errStructPtr3Array, + } + + errs = validate.Struct(tmsp3) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 5) + AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[0][1].Name", "TestMultiDimensionalStructsPtr3.Errs[0][1].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[0][2].Name", "TestMultiDimensionalStructsPtr3.Errs[0][2].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[1][1].Name", "TestMultiDimensionalStructsPtr3.Errs[1][1].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[1][2].Name", "TestMultiDimensionalStructsPtr3.Errs[1][2].Name", "Name", "Name", "required") + AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[2][1].Name", "TestMultiDimensionalStructsPtr3.Errs[2][1].Name", "Name", "Name", "required") + + type TestMultiDimensionalTimeTime struct { + Errs [][]*time.Time `validate:"gt=0,dive,dive,required"` + } + + var errTimePtr3Array [][]*time.Time + + t1 := time.Now().UTC() + t2 := time.Now().UTC() + t3 := time.Now().UTC().Add(time.Hour * 24) + + errTimePtr3Array = append(errTimePtr3Array, []*time.Time{&t1, &t2, &t3}) + errTimePtr3Array = append(errTimePtr3Array, []*time.Time{&t1, &t2, nil}) + errTimePtr3Array = append(errTimePtr3Array, []*time.Time{&t1, nil, nil}) + + tmtp3 := &TestMultiDimensionalTimeTime{ + Errs: errTimePtr3Array, + } + + errs = validate.Struct(tmtp3) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 3) + AssertError(t, errs, "TestMultiDimensionalTimeTime.Errs[1][2]", "TestMultiDimensionalTimeTime.Errs[1][2]", "Errs[1][2]", "Errs[1][2]", "required") + AssertError(t, errs, "TestMultiDimensionalTimeTime.Errs[2][1]", "TestMultiDimensionalTimeTime.Errs[2][1]", "Errs[2][1]", "Errs[2][1]", "required") + AssertError(t, errs, "TestMultiDimensionalTimeTime.Errs[2][2]", "TestMultiDimensionalTimeTime.Errs[2][2]", "Errs[2][2]", "Errs[2][2]", "required") + + type TestMultiDimensionalTimeTime2 struct { + Errs [][]*time.Time `validate:"gt=0,dive,dive,required"` + } + + var errTimeArray [][]*time.Time + + t1 = time.Now().UTC() + t2 = time.Now().UTC() + t3 = time.Now().UTC().Add(time.Hour * 24) + + errTimeArray = append(errTimeArray, []*time.Time{&t1, &t2, &t3}) + errTimeArray = append(errTimeArray, []*time.Time{&t1, &t2, nil}) + errTimeArray = append(errTimeArray, []*time.Time{&t1, nil, nil}) + + tmtp := &TestMultiDimensionalTimeTime2{ + Errs: errTimeArray, + } + + errs = validate.Struct(tmtp) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 3) + AssertError(t, errs, "TestMultiDimensionalTimeTime2.Errs[1][2]", "TestMultiDimensionalTimeTime2.Errs[1][2]", "Errs[1][2]", "Errs[1][2]", "required") + AssertError(t, errs, "TestMultiDimensionalTimeTime2.Errs[2][1]", "TestMultiDimensionalTimeTime2.Errs[2][1]", "Errs[2][1]", "Errs[2][1]", "required") + AssertError(t, errs, "TestMultiDimensionalTimeTime2.Errs[2][2]", "TestMultiDimensionalTimeTime2.Errs[2][2]", "Errs[2][2]", "Errs[2][2]", "required") +} + +func TestNilStructPointerValidation(t *testing.T) { + type Inner struct { + Data string + } + + type Outer struct { + Inner *Inner `validate:"omitempty"` + } + + inner := &Inner{ + Data: "test", + } + + outer := &Outer{ + Inner: inner, + } + + validate := New() + errs := validate.Struct(outer) + Equal(t, errs, nil) + + outer = &Outer{ + Inner: nil, + } + + errs = validate.Struct(outer) + Equal(t, errs, nil) + + type Inner2 struct { + Data string + } + + type Outer2 struct { + Inner2 *Inner2 `validate:"required"` + } + + inner2 := &Inner2{ + Data: "test", + } + + outer2 := &Outer2{ + Inner2: inner2, + } + + errs = validate.Struct(outer2) + Equal(t, errs, nil) + + outer2 = &Outer2{ + Inner2: nil, + } + + errs = validate.Struct(outer2) + NotEqual(t, errs, nil) + AssertError(t, errs, "Outer2.Inner2", "Outer2.Inner2", "Inner2", "Inner2", "required") + + type Inner3 struct { + Data string + } + + type Outer3 struct { + Inner3 *Inner3 + } + + inner3 := &Inner3{ + Data: "test", + } + + outer3 := &Outer3{ + Inner3: inner3, + } + + errs = validate.Struct(outer3) + Equal(t, errs, nil) + + type Inner4 struct { + Data string + } + + type Outer4 struct { + Inner4 *Inner4 `validate:"-"` + } + + inner4 := &Inner4{ + Data: "test", + } + + outer4 := &Outer4{ + Inner4: inner4, + } + + errs = validate.Struct(outer4) + Equal(t, errs, nil) +} + +func TestSSNValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"00-90-8787", false}, + {"66690-76", false}, + {"191 60 2869", true}, + {"191-60-2869", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "ssn") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d SSN failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d SSN failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "ssn" { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + } + } + } + } +} + +func TestLongitudeValidation(t *testing.T) { + tests := []struct { + param interface{} + expected bool + }{ + {"", false}, + {"-180.000", true}, + {"180.1", false}, + {"+73.234", true}, + {"+382.3811", false}, + {"23.11111111", true}, + {uint(180), true}, + {float32(-180.0), true}, + {-180, true}, + {180.1, false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "longitude") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d Longitude failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d Longitude failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "longitude" { + t.Fatalf("Index: %d Longitude failed Error: %s", i, errs) + } + } + } + } + + PanicMatches(t, func() { _ = validate.Var(true, "longitude") }, "Bad field type bool") +} + +func TestLatitudeValidation(t *testing.T) { + tests := []struct { + param interface{} + expected bool + }{ + {"", false}, + {"-90.000", true}, + {"+90", true}, + {"47.1231231", true}, + {"+99.9", false}, + {"108", false}, + {uint(90), true}, + {float32(-90.0), true}, + {-90, true}, + {90.1, false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "latitude") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "latitude" { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + } + } + } + } + + PanicMatches(t, func() { _ = validate.Var(true, "latitude") }, "Bad field type bool") +} + +func TestDataURIValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"data:image/png;base64,TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4=", true}, + {"data:text/plain;base64,Vml2YW11cyBmZXJtZW50dW0gc2VtcGVyIHBvcnRhLg==", true}, + {"image/gif;base64,U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==", false}, + {"data:image/gif;base64,MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMPNS1Ufof9EW/M98FNw" + + "UAKrwflsqVxaxQjBQnHQmiI7Vac40t8x7pIb8gLGV6wL7sBTJiPovJ0V7y7oc0Ye" + + "rhKh0Rm4skP2z/jHwwZICgGzBvA0rH8xlhUiTvcwDCJ0kc+fh35hNt8srZQM4619" + + "FTgB66Xmp4EtVyhpQV+t02g6NzK72oZI0vnAvqhpkxLeLiMCyrI416wHm5Tkukhx" + + "QmcL2a6hNOyu0ixX/x2kSFXApEnVrJ+/IxGyfyw8kf4N2IZpW5nEP847lpfj0SZZ" + + "Fwrd1mnfnDbYohX2zRptLy2ZUn06Qo9pkG5ntvFEPo9bfZeULtjYzIl6K8gJ2uGZ" + "HQIDAQAB", true}, + {"data:image/png;base64,12345", false}, + {"", false}, + {"data:text,:;base85,U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==", false}, + {"data:image/jpeg;key=value;base64,UEsDBBQAAAAI", true}, + {"data:image/jpeg;key=value,UEsDBBQAAAAI", true}, + {"data:;base64;sdfgsdfgsdfasdfa=s,UEsDBBQAAAAI", true}, + {"data:,UEsDBBQAAAAI", true}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.param, "datauri") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "datauri" { + t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) + } + } + } + } +} + +func TestMultibyteValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", true}, + {"abc", false}, + {"123", false}, + {"<>@;.-=", false}, + {"ひらがな・カタカナ、.漢字", true}, + {"あいうえお foobar", true}, + {"test@example.com", true}, + {"test@example.com", true}, + {"1234abcDExyz", true}, + {"カタカナ", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "multibyte") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "multibyte" { + t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) + } + } + } + } +} + +func TestPrintableASCIIValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", true}, + {"foobar", false}, + {"xyz098", false}, + {"123456", false}, + {"カタカナ", false}, + {"foobar", true}, + {"0987654321", true}, + {"test@example.com", true}, + {"1234abcDEF", true}, + {"newline\n", false}, + {"\x19test\x7F", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "printascii") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "printascii" { + t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) + } + } + } + } +} + +func TestASCIIValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", true}, + {"foobar", false}, + {"xyz098", false}, + {"123456", false}, + {"カタカナ", false}, + {"foobar", true}, + {"0987654321", true}, + {"test@example.com", true}, + {"1234abcDEF", true}, + {"", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "ascii") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "ascii" { + t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) + } + } + } + } +} + +func TestUUID5Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + + {"", false}, + {"xxxa987fbc9-4bed-3078-cf07-9141ba07c9f3", false}, + {"9c858901-8a57-4791-81fe-4c455b099bc9", false}, + {"a987fbc9-4bed-3078-cf07-9141ba07c9f3", false}, + {"987fbc97-4bed-5078-af07-9141ba07c9f3", true}, + {"987fbc97-4bed-5078-9f07-9141ba07c9f3", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "uuid5") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "uuid5" { + t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) + } + } + } + } +} + +func TestUUID4Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"xxxa987fbc9-4bed-3078-cf07-9141ba07c9f3", false}, + {"a987fbc9-4bed-5078-af07-9141ba07c9f3", false}, + {"934859", false}, + {"57b73598-8764-4ad0-a76a-679bb6640eb1", true}, + {"625e63f3-58f5-40b7-83a1-a72ad31acffb", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "uuid4") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "uuid4" { + t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) + } + } + } + } +} + +func TestUUID3Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"412452646", false}, + {"xxxa987fbc9-4bed-3078-cf07-9141ba07c9f3", false}, + {"a987fbc9-4bed-4078-8f07-9141ba07c9f3", false}, + {"a987fbc9-4bed-3078-cf07-9141ba07c9f3", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "uuid3") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "uuid3" { + t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) + } + } + } + } +} + +func TestUUIDValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"xxxa987fbc9-4bed-3078-cf07-9141ba07c9f3", false}, + {"a987fbc9-4bed-3078-cf07-9141ba07c9f3xxx", false}, + {"a987fbc94bed3078cf079141ba07c9f3", false}, + {"934859", false}, + {"987fbc9-4bed-3078-cf07a-9141ba07c9f3", false}, + {"aaaaaaaa-1111-1111-aaag-111111111111", false}, + {"a987fbc9-4bed-3078-cf07-9141ba07c9f3", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "uuid") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d UUID failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d UUID failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "uuid" { + t.Fatalf("Index: %d UUID failed Error: %s", i, errs) + } + } + } + } +} + +func TestUUID5RFC4122Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + + {"", false}, + {"xxxa987Fbc9-4bed-3078-cf07-9141ba07c9f3", false}, + {"9c858901-8a57-4791-81Fe-4c455b099bc9", false}, + {"a987Fbc9-4bed-3078-cf07-9141ba07c9f3", false}, + {"987Fbc97-4bed-5078-af07-9141ba07c9f3", true}, + {"987Fbc97-4bed-5078-9f07-9141ba07c9f3", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "uuid5_rfc4122") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d UUID5RFC4122 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d UUID5RFC4122 failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "uuid5_rfc4122" { + t.Fatalf("Index: %d UUID5RFC4122 failed Error: %s", i, errs) + } + } + } + } +} + +func TestUUID4RFC4122Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"xxxa987fbc9-4bed-3078-cf07-9141ba07c9F3", false}, + {"a987fbc9-4bed-5078-af07-9141ba07c9F3", false}, + {"934859", false}, + {"57b73598-8764-4ad0-a76A-679bb6640eb1", true}, + {"625e63f3-58f5-40b7-83a1-a72ad31acFfb", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "uuid4_rfc4122") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d UUID4RFC4122 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d UUID4RFC4122 failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "uuid4_rfc4122" { + t.Fatalf("Index: %d UUID4RFC4122 failed Error: %s", i, errs) + } + } + } + } +} + +func TestUUID3RFC4122Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"412452646", false}, + {"xxxa987fbc9-4bed-3078-cf07-9141ba07c9F3", false}, + {"a987fbc9-4bed-4078-8f07-9141ba07c9F3", false}, + {"a987fbc9-4bed-3078-cf07-9141ba07c9F3", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "uuid3_rfc4122") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d UUID3RFC4122 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d UUID3RFC4122 failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "uuid3_rfc4122" { + t.Fatalf("Index: %d UUID3RFC4122 failed Error: %s", i, errs) + } + } + } + } +} + +func TestUUIDRFC4122Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"xxxa987Fbc9-4bed-3078-cf07-9141ba07c9f3", false}, + {"a987Fbc9-4bed-3078-cf07-9141ba07c9f3xxx", false}, + {"a987Fbc94bed3078cf079141ba07c9f3", false}, + {"934859", false}, + {"987fbc9-4bed-3078-cf07a-9141ba07c9F3", false}, + {"aaaaaaaa-1111-1111-aaaG-111111111111", false}, + {"a987Fbc9-4bed-3078-cf07-9141ba07c9f3", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "uuid_rfc4122") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d UUIDRFC4122 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d UUIDRFC4122 failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "uuid_rfc4122" { + t.Fatalf("Index: %d UUIDRFC4122 failed Error: %s", i, errs) + } + } + } + } +} + +func TestISBNValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"foo", false}, + {"3836221195", true}, + {"1-61729-085-8", true}, + {"3 423 21412 0", true}, + {"3 401 01319 X", true}, + {"9784873113685", true}, + {"978-4-87311-368-5", true}, + {"978 3401013190", true}, + {"978-3-8362-2119-1", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "isbn") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "isbn" { + t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) + } + } + } + } +} + +func TestISBN13Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"foo", false}, + {"3-8362-2119-5", false}, + {"01234567890ab", false}, + {"978 3 8362 2119 0", false}, + {"9784873113685", true}, + {"978-4-87311-368-5", true}, + {"978 3401013190", true}, + {"978-3-8362-2119-1", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "isbn13") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "isbn13" { + t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) + } + } + } + } +} + +func TestISBN10Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"foo", false}, + {"3423214121", false}, + {"978-3836221191", false}, + {"3-423-21412-1", false}, + {"3 423 21412 1", false}, + {"3836221195", true}, + {"1-61729-085-8", true}, + {"3 423 21412 0", true}, + {"3 401 01319 X", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "isbn10") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "isbn10" { + t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) + } + } + } + } +} + +func TestExcludesRuneValidation(t *testing.T) { + + tests := []struct { + Value string `validate:"excludesrune=☻"` + Tag string + ExpectedNil bool + }{ + {Value: "a☺b☻c☹d", Tag: "excludesrune=☻", ExpectedNil: false}, + {Value: "abcd", Tag: "excludesrune=☻", ExpectedNil: true}, + } + + validate := New() + + for i, s := range tests { + errs := validate.Var(s.Value, s.Tag) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + + errs = validate.Struct(s) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + } +} + +func TestExcludesAllValidation(t *testing.T) { + + tests := []struct { + Value string `validate:"excludesall=@!{}[]"` + Tag string + ExpectedNil bool + }{ + {Value: "abcd@!jfk", Tag: "excludesall=@!{}[]", ExpectedNil: false}, + {Value: "abcdefg", Tag: "excludesall=@!{}[]", ExpectedNil: true}, + } + + validate := New() + + for i, s := range tests { + errs := validate.Var(s.Value, s.Tag) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + + errs = validate.Struct(s) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + } + + username := "joeybloggs " + + errs := validate.Var(username, "excludesall=@ ") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "excludesall") + + excluded := "," + + errs = validate.Var(excluded, "excludesall=!@#$%^&*()_+.0x2C?") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "excludesall") + + excluded = "=" + + errs = validate.Var(excluded, "excludesall=!@#$%^&*()_+.0x2C=?") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "excludesall") +} + +func TestExcludesValidation(t *testing.T) { + + tests := []struct { + Value string `validate:"excludes=@"` + Tag string + ExpectedNil bool + }{ + {Value: "abcd@!jfk", Tag: "excludes=@", ExpectedNil: false}, + {Value: "abcdq!jfk", Tag: "excludes=@", ExpectedNil: true}, + } + + validate := New() + + for i, s := range tests { + errs := validate.Var(s.Value, s.Tag) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + + errs = validate.Struct(s) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + } +} + +func TestContainsRuneValidation(t *testing.T) { + + tests := []struct { + Value string `validate:"containsrune=☻"` + Tag string + ExpectedNil bool + }{ + {Value: "a☺b☻c☹d", Tag: "containsrune=☻", ExpectedNil: true}, + {Value: "abcd", Tag: "containsrune=☻", ExpectedNil: false}, + } + + validate := New() + + for i, s := range tests { + errs := validate.Var(s.Value, s.Tag) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + + errs = validate.Struct(s) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + } +} + +func TestContainsAnyValidation(t *testing.T) { + + tests := []struct { + Value string `validate:"containsany=@!{}[]"` + Tag string + ExpectedNil bool + }{ + {Value: "abcd@!jfk", Tag: "containsany=@!{}[]", ExpectedNil: true}, + {Value: "abcdefg", Tag: "containsany=@!{}[]", ExpectedNil: false}, + } + + validate := New() + + for i, s := range tests { + errs := validate.Var(s.Value, s.Tag) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + + errs = validate.Struct(s) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + } +} + +func TestContainsValidation(t *testing.T) { + + tests := []struct { + Value string `validate:"contains=@"` + Tag string + ExpectedNil bool + }{ + {Value: "abcd@!jfk", Tag: "contains=@", ExpectedNil: true}, + {Value: "abcdq!jfk", Tag: "contains=@", ExpectedNil: false}, + } + + validate := New() + + for i, s := range tests { + errs := validate.Var(s.Value, s.Tag) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + + errs = validate.Struct(s) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + } +} + +func TestIsNeFieldValidation(t *testing.T) { + var errs error + validate := New() + + var j uint64 + var k float64 + s := "abcd" + i := 1 + j = 1 + k = 1.543 + arr := []string{"test"} + now := time.Now().UTC() + + var j2 uint64 + var k2 float64 + s2 := "abcdef" + i2 := 3 + j2 = 2 + k2 = 1.5434456 + arr2 := []string{"test", "test2"} + arr3 := []string{"test"} + now2 := now + + errs = validate.VarWithValue(s, s2, "nefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(i2, i, "nefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(j2, j, "nefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(k2, k, "nefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(arr2, arr, "nefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(now2, now, "nefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "nefield") + + errs = validate.VarWithValue(arr3, arr, "nefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "nefield") + + type Test struct { + Start *time.Time `validate:"nefield=End"` + End *time.Time + } + + sv := &Test{ + Start: &now, + End: &now, + } + + errs = validate.Struct(sv) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.Start", "Test.Start", "Start", "Start", "nefield") + + now3 := time.Now().UTC() + + sv = &Test{ + Start: &now, + End: &now3, + } + + errs = validate.Struct(sv) + Equal(t, errs, nil) + + errs = validate.VarWithValue(nil, 1, "nefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "nefield") + + errs = validate.VarWithValue(sv, now, "nefield") + Equal(t, errs, nil) + + type Test2 struct { + Start *time.Time `validate:"nefield=NonExistantField"` + End *time.Time + } + + sv2 := &Test2{ + Start: &now, + End: &now, + } + + errs = validate.Struct(sv2) + Equal(t, errs, nil) + + type Other struct { + Value string + } + + type Test3 struct { + Value Other + Time time.Time `validate:"nefield=Value"` + } + + tst := Test3{ + Value: Other{Value: "StringVal"}, + Time: now, + } + + errs = validate.Struct(tst) + Equal(t, errs, nil) + + // Tests for time.Duration type. + + // -- Validations for variables of time.Duration type. + + errs = validate.VarWithValue(time.Hour, time.Hour-time.Minute, "nefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour+time.Minute, "nefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour, "nefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "nefield") + + errs = validate.VarWithValue(time.Duration(0), time.Duration(0), "omitempty,nefield") + Equal(t, errs, nil) + + // -- Validations for a struct with time.Duration type fields. + + type TimeDurationTest struct { + First time.Duration `validate:"nefield=Second"` + Second time.Duration + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour - time.Minute} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour + time.Minute} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.First", "TimeDurationTest.First", "First", "First", "nefield") + + type TimeDurationOmitemptyTest struct { + First time.Duration `validate:"omitempty,nefield=Second"` + Second time.Duration + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0), time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestIsNeValidation(t *testing.T) { + var errs error + validate := New() + + var j uint64 + var k float64 + s := "abcdef" + i := 3 + j = 2 + k = 1.5434 + arr := []string{"test"} + now := time.Now().UTC() + + errs = validate.Var(s, "ne=abcd") + Equal(t, errs, nil) + + errs = validate.Var(i, "ne=1") + Equal(t, errs, nil) + + errs = validate.Var(j, "ne=1") + Equal(t, errs, nil) + + errs = validate.Var(k, "ne=1.543") + Equal(t, errs, nil) + + errs = validate.Var(arr, "ne=2") + Equal(t, errs, nil) + + errs = validate.Var(arr, "ne=1") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ne") + + PanicMatches(t, func() { _ = validate.Var(now, "ne=now") }, "Bad field type time.Time") + + // Tests for time.Duration type. + + // -- Validations for a variable of time.Duration type. + + errs = validate.Var(time.Hour-time.Minute, "ne=1h") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour+time.Minute, "ne=1h") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour, "ne=1h") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ne") + + errs = validate.Var(time.Duration(0), "omitempty,ne=0") + Equal(t, errs, nil) + + // -- Validations for a struct with a time.Duration type field. + + type TimeDurationTest struct { + Duration time.Duration `validate:"ne=1h"` + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour - time.Minute} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour + time.Minute} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "ne") + + type TimeDurationOmitemptyTest struct { + Duration time.Duration `validate:"omitempty,ne=0"` + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestIsEqFieldValidation(t *testing.T) { + var errs error + validate := New() + + var j uint64 + var k float64 + s := "abcd" + i := 1 + j = 1 + k = 1.543 + arr := []string{"test"} + now := time.Now().UTC() + + var j2 uint64 + var k2 float64 + s2 := "abcd" + i2 := 1 + j2 = 1 + k2 = 1.543 + arr2 := []string{"test"} + arr3 := []string{"test", "test2"} + now2 := now + + errs = validate.VarWithValue(s, s2, "eqfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(i2, i, "eqfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(j2, j, "eqfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(k2, k, "eqfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(arr2, arr, "eqfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(now2, now, "eqfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(arr3, arr, "eqfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "eqfield") + + type Test struct { + Start *time.Time `validate:"eqfield=End"` + End *time.Time + } + + sv := &Test{ + Start: &now, + End: &now, + } + + errs = validate.Struct(sv) + Equal(t, errs, nil) + + now3 := time.Now().UTC() + + sv = &Test{ + Start: &now, + End: &now3, + } + + errs = validate.Struct(sv) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.Start", "Test.Start", "Start", "Start", "eqfield") + + errs = validate.VarWithValue(nil, 1, "eqfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "eqfield") + + channel := make(chan string) + errs = validate.VarWithValue(5, channel, "eqfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "eqfield") + + errs = validate.VarWithValue(5, now, "eqfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "eqfield") + + type Test2 struct { + Start *time.Time `validate:"eqfield=NonExistantField"` + End *time.Time + } + + sv2 := &Test2{ + Start: &now, + End: &now, + } + + errs = validate.Struct(sv2) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test2.Start", "Test2.Start", "Start", "Start", "eqfield") + + type Inner struct { + Name string + } + + type TStruct struct { + Inner *Inner + CreatedAt *time.Time `validate:"eqfield=Inner"` + } + + inner := &Inner{ + Name: "NAME", + } + + test := &TStruct{ + Inner: inner, + CreatedAt: &now, + } + + errs = validate.Struct(test) + NotEqual(t, errs, nil) + AssertError(t, errs, "TStruct.CreatedAt", "TStruct.CreatedAt", "CreatedAt", "CreatedAt", "eqfield") + + // Tests for time.Duration type. + + // -- Validations for variables of time.Duration type. + + errs = validate.VarWithValue(time.Hour, time.Hour, "eqfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour-time.Minute, "eqfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "eqfield") + + errs = validate.VarWithValue(time.Hour, time.Hour+time.Minute, "eqfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "eqfield") + + errs = validate.VarWithValue(time.Duration(0), time.Hour, "omitempty,eqfield") + Equal(t, errs, nil) + + // -- Validations for a struct with time.Duration type fields. + + type TimeDurationTest struct { + First time.Duration `validate:"eqfield=Second"` + Second time.Duration + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour - time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.First", "TimeDurationTest.First", "First", "First", "eqfield") + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour + time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.First", "TimeDurationTest.First", "First", "First", "eqfield") + + type TimeDurationOmitemptyTest struct { + First time.Duration `validate:"omitempty,eqfield=Second"` + Second time.Duration + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0), time.Hour} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestIsEqValidation(t *testing.T) { + var errs error + validate := New() + + var j uint64 + var k float64 + s := "abcd" + i := 1 + j = 1 + k = 1.543 + arr := []string{"test"} + now := time.Now().UTC() + + errs = validate.Var(s, "eq=abcd") + Equal(t, errs, nil) + + errs = validate.Var(i, "eq=1") + Equal(t, errs, nil) + + errs = validate.Var(j, "eq=1") + Equal(t, errs, nil) + + errs = validate.Var(k, "eq=1.543") + Equal(t, errs, nil) + + errs = validate.Var(arr, "eq=1") + Equal(t, errs, nil) + + errs = validate.Var(arr, "eq=2") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "eq") + + PanicMatches(t, func() { _ = validate.Var(now, "eq=now") }, "Bad field type time.Time") + + // Tests for time.Duration type. + + // -- Validations for a variable of time.Duration type. + + errs = validate.Var(time.Hour, "eq=1h") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour-time.Minute, "eq=1h") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "eq") + + errs = validate.Var(time.Hour+time.Minute, "eq=1h") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "eq") + + errs = validate.Var(time.Duration(0), "omitempty,eq=1h") + Equal(t, errs, nil) + + // -- Validations for a struct with a time.Duration type field. + + type TimeDurationTest struct { + Duration time.Duration `validate:"eq=1h"` + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour - time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "eq") + + timeDurationTest = &TimeDurationTest{time.Hour + time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "eq") + + type TimeDurationOmitemptyTest struct { + Duration time.Duration `validate:"omitempty,eq=1h"` + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestOneOfValidation(t *testing.T) { + validate := New() + + passSpecs := []struct { + f interface{} + t string + }{ + {f: "red", t: "oneof=red green"}, + {f: "green", t: "oneof=red green"}, + {f: "red green", t: "oneof='red green' blue"}, + {f: "blue", t: "oneof='red green' blue"}, + {f: 5, t: "oneof=5 6"}, + {f: 6, t: "oneof=5 6"}, + {f: int8(6), t: "oneof=5 6"}, + {f: int16(6), t: "oneof=5 6"}, + {f: int32(6), t: "oneof=5 6"}, + {f: int64(6), t: "oneof=5 6"}, + {f: uint(6), t: "oneof=5 6"}, + {f: uint8(6), t: "oneof=5 6"}, + {f: uint16(6), t: "oneof=5 6"}, + {f: uint32(6), t: "oneof=5 6"}, + {f: uint64(6), t: "oneof=5 6"}, + } + + for _, spec := range passSpecs { + t.Logf("%#v", spec) + errs := validate.Var(spec.f, spec.t) + Equal(t, errs, nil) + } + + failSpecs := []struct { + f interface{} + t string + }{ + {f: "", t: "oneof=red green"}, + {f: "yellow", t: "oneof=red green"}, + {f: "green", t: "oneof='red green' blue"}, + {f: 5, t: "oneof=red green"}, + {f: 6, t: "oneof=red green"}, + {f: 6, t: "oneof=7"}, + {f: uint(6), t: "oneof=7"}, + {f: int8(5), t: "oneof=red green"}, + {f: int16(5), t: "oneof=red green"}, + {f: int32(5), t: "oneof=red green"}, + {f: int64(5), t: "oneof=red green"}, + {f: uint(5), t: "oneof=red green"}, + {f: uint8(5), t: "oneof=red green"}, + {f: uint16(5), t: "oneof=red green"}, + {f: uint32(5), t: "oneof=red green"}, + {f: uint64(5), t: "oneof=red green"}, + } + + for _, spec := range failSpecs { + t.Logf("%#v", spec) + errs := validate.Var(spec.f, spec.t) + AssertError(t, errs, "", "", "", "", "oneof") + } + + PanicMatches(t, func() { + _ = validate.Var(3.14, "oneof=red green") + }, "Bad field type float64") +} + +func TestBase64Validation(t *testing.T) { + + validate := New() + + s := "dW5pY29ybg==" + + errs := validate.Var(s, "base64") + Equal(t, errs, nil) + + s = "dGhpIGlzIGEgdGVzdCBiYXNlNjQ=" + errs = validate.Var(s, "base64") + Equal(t, errs, nil) + + s = "" + errs = validate.Var(s, "base64") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "base64") + + s = "dW5pY29ybg== foo bar" + errs = validate.Var(s, "base64") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "base64") +} + +func TestBase64URLValidation(t *testing.T) { + validate := New() + + testCases := []struct { + decoded, encoded string + success bool + }{ + // empty string, although a valid base64 string, should fail + {"", "", false}, + // invalid length + {"", "a", false}, + // base64 with padding + {"f", "Zg==", true}, + {"fo", "Zm8=", true}, + // base64 without padding + {"foo", "Zm9v", true}, + {"", "Zg", false}, + {"", "Zm8", false}, + // base64 URL safe encoding with invalid, special characters '+' and '/' + {"\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l+", false}, + {"\x14\xfb\x9c\x03\xf9\x73", "FPucA/lz", false}, + // base64 URL safe encoding with valid, special characters '-' and '_' + {"\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l-", true}, + {"\x14\xfb\x9c\x03\xf9\x73", "FPucA_lz", true}, + // non base64 characters + {"", "@mc=", false}, + {"", "Zm 9", false}, + } + for _, tc := range testCases { + err := validate.Var(tc.encoded, "base64url") + if tc.success { + Equal(t, err, nil) + // make sure encoded value is decoded back to the expected value + d, innerErr := base64.URLEncoding.DecodeString(tc.encoded) + Equal(t, innerErr, nil) + Equal(t, tc.decoded, string(d)) + } else { + NotEqual(t, err, nil) + if len(tc.encoded) > 0 { + // make sure that indeed the encoded value was faulty + _, err := base64.URLEncoding.DecodeString(tc.encoded) + NotEqual(t, err, nil) + } + } + } +} + +func TestFileValidation(t *testing.T) { + validate := New() + + tests := []struct { + title string + param string + expected bool + }{ + {"empty path", "", false}, + {"regular file", filepath.Join("testdata", "a.go"), true}, + {"missing file", filepath.Join("testdata", "no.go"), false}, + {"directory, not a file", "testdata", false}, + } + + for _, test := range tests { + errs := validate.Var(test.param, "file") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Test: '%s' failed Error: %s", test.title, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Test: '%s' failed Error: %s", test.title, errs) + } + } + } + + PanicMatches(t, func() { + _ = validate.Var(6, "file") + }, "Bad field type int") +} + +func TestEthereumAddressValidation(t *testing.T) { + + validate := New() + + tests := []struct { + param string + expected bool + }{ + // All caps. + {"0x52908400098527886E0F7030069857D2E4169EE7", true}, + {"0x8617E340B3D01FA5F11F306F4090FD50E238070D", true}, + + // All lower. + {"0xde709f2102306220921060314715629080e2fb77", true}, + {"0x27b1fdb04752bbc536007a920d24acb045561c26", true}, + {"0x123f681646d4a755815f9cb19e1acc8565a0c2ac", true}, + + // Mixed case: runs checksum validation. + {"0x02F9AE5f22EA3fA88F05780B30385bECFacbf130", true}, + {"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", true}, + {"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true}, + {"0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true}, + {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true}, + {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDB", false}, // Invalid checksum. + + // Other. + {"", false}, + {"D1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", false}, // Missing "0x" prefix. + {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDbc", false}, // More than 40 hex digits. + {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aD", false}, // Less than 40 hex digits. + {"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDw", false}, // Invalid hex digit "w". + } + + for i, test := range tests { + + errs := validate.Var(test.param, "eth_addr") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d eth_addr failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d eth_addr failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "eth_addr" { + t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) + } + } + } + } +} + +func TestBitcoinAddressValidation(t *testing.T) { + + validate := New() + + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"x", false}, + {"0x02F9AE5f22EA3fA88F05780B30385bEC", false}, + {"1A1zP1ePQGefi2DMPTifTL5SLmv7DivfNa", false}, + {"1P9RQEr2XeE3PEb44ZE35sfZRRW1JH8Uqx", false}, + {"3P14159I73E4gFr7JterCCQh9QjiTjiZrG", false}, + {"3P141597f3E4gFr7JterCCQh9QjiTjiZrG", false}, + {"37qgekLpCCHrQuSjvX3fs496FWTGsHFHizjJAs6NPcR47aefnnCWECAhHV6E3g4YN7u7Yuwod5Y", false}, + {"dzb7VV1Ui55BARxv7ATxAtCUeJsANKovDGWFVgpTbhq9gvPqP3yv", false}, + {"MuNu7ZAEDFiHthiunm7dPjwKqrVNCM3mAz6rP9zFveQu14YA8CxExSJTHcVP9DErn6u84E6Ej7S", false}, + {"rPpQpYknyNQ5AEHuY6H8ijJJrYc2nDKKk9jjmKEXsWzyAQcFGpDLU2Zvsmoi8JLR7hAwoy3RQWf", false}, + {"4Uc3FmN6NQ6zLBK5QQBXRBUREaaHwCZYsGCueHauuDmJpZKn6jkEskMB2Zi2CNgtb5r6epWEFfUJq", false}, + {"7aQgR5DFQ25vyXmqZAWmnVCjL3PkBcdVkBUpjrjMTcghHx3E8wb", false}, + {"17QpPprjeg69fW1DV8DcYYCKvWjYhXvWkov6MJ1iTTvMFj6weAqW7wybZeH57WTNxXVCRH4veVs", false}, + {"KxuACDviz8Xvpn1xAh9MfopySZNuyajYMZWz16Dv2mHHryznWUp3", false}, + {"7nK3GSmqdXJQtdohvGfJ7KsSmn3TmGqExug49583bDAL91pVSGq5xS9SHoAYL3Wv3ijKTit65th", false}, + {"cTivdBmq7bay3RFGEBBuNfMh2P1pDCgRYN2Wbxmgwr4ki3jNUL2va", false}, + {"gjMV4vjNjyMrna4fsAr8bWxAbwtmMUBXJS3zL4NJt5qjozpbQLmAfK1uA3CquSqsZQMpoD1g2nk", false}, + {"emXm1naBMoVzPjbk7xpeTVMFy4oDEe25UmoyGgKEB1gGWsK8kRGs", false}, + {"7VThQnNRj1o3Zyvc7XHPRrjDf8j2oivPTeDXnRPYWeYGE4pXeRJDZgf28ppti5hsHWXS2GSobdqyo", false}, + {"1G9u6oCVCPh2o8m3t55ACiYvG1y5BHewUkDSdiQarDcYXXhFHYdzMdYfUAhfxn5vNZBwpgUNpso", false}, + {"31QQ7ZMLkScDiB4VyZjuptr7AEc9j1SjstF7pRoLhHTGkW4Q2y9XELobQmhhWxeRvqcukGd1XCq", false}, + {"DHqKSnpxa8ZdQyH8keAhvLTrfkyBMQxqngcQA5N8LQ9KVt25kmGN", false}, + {"2LUHcJPbwLCy9GLH1qXmfmAwvadWw4bp4PCpDfduLqV17s6iDcy1imUwhQJhAoNoN1XNmweiJP4i", false}, + {"7USRzBXAnmck8fX9HmW7RAb4qt92VFX6soCnts9s74wxm4gguVhtG5of8fZGbNPJA83irHVY6bCos", false}, + {"1DGezo7BfVebZxAbNT3XGujdeHyNNBF3vnficYoTSp4PfK2QaML9bHzAMxke3wdKdHYWmsMTJVu", false}, + {"2D12DqDZKwCxxkzs1ZATJWvgJGhQ4cFi3WrizQ5zLAyhN5HxuAJ1yMYaJp8GuYsTLLxTAz6otCfb", false}, + {"8AFJzuTujXjw1Z6M3fWhQ1ujDW7zsV4ePeVjVo7D1egERqSW9nZ", false}, + {"163Q17qLbTCue8YY3AvjpUhotuaodLm2uqMhpYirsKjVqnxJRWTEoywMVY3NbBAHuhAJ2cF9GAZ", false}, + {"2MnmgiRH4eGLyLc9eAqStzk7dFgBjFtUCtu", false}, + {"461QQ2sYWxU7H2PV4oBwJGNch8XVTYYbZxU", false}, + {"2UCtv53VttmQYkVU4VMtXB31REvQg4ABzs41AEKZ8UcB7DAfVzdkV9JDErwGwyj5AUHLkmgZeobs", false}, + {"cSNjAsnhgtiFMi6MtfvgscMB2Cbhn2v1FUYfviJ1CdjfidvmeW6mn", false}, + {"gmsow2Y6EWAFDFE1CE4Hd3Tpu2BvfmBfG1SXsuRARbnt1WjkZnFh1qGTiptWWbjsq2Q6qvpgJVj", false}, + {"nksUKSkzS76v8EsSgozXGMoQFiCoCHzCVajFKAXqzK5on9ZJYVHMD5CKwgmX3S3c7M1U3xabUny", false}, + {"L3favK1UzFGgdzYBF2oBT5tbayCo4vtVBLJhg2iYuMeePxWG8SQc", false}, + {"7VxLxGGtYT6N99GdEfi6xz56xdQ8nP2dG1CavuXx7Rf2PrvNMTBNevjkfgs9JmkcGm6EXpj8ipyPZ ", false}, + {"2mbZwFXF6cxShaCo2czTRB62WTx9LxhTtpP", false}, + {"dB7cwYdcPSgiyAwKWL3JwCVwSk6epU2txw", false}, + {"HPhFUhUAh8ZQQisH8QQWafAxtQYju3SFTX", false}, + {"4ctAH6AkHzq5ioiM1m9T3E2hiYEev5mTsB", false}, + {"31uEbMgunupShBVTewXjtqbBv5MndwfXhb", false}, + {"175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W", false}, + {"Hn1uFi4dNexWrqARpjMqgT6cX1UsNPuV3cHdGg9ExyXw8HTKadbktRDtdeVmY3M1BxJStiL4vjJ", false}, + {"Sq3fDbvutABmnAHHExJDgPLQn44KnNC7UsXuT7KZecpaYDMU9Txs", false}, + {"6TqWyrqdgUEYDQU1aChMuFMMEimHX44qHFzCUgGfqxGgZNMUVWJ", false}, + {"giqJo7oWqFxNKWyrgcBxAVHXnjJ1t6cGoEffce5Y1y7u649Noj5wJ4mmiUAKEVVrYAGg2KPB3Y4", false}, + {"cNzHY5e8vcmM3QVJUcjCyiKMYfeYvyueq5qCMV3kqcySoLyGLYUK", false}, + {"37uTe568EYc9WLoHEd9jXEvUiWbq5LFLscNyqvAzLU5vBArUJA6eydkLmnMwJDjkL5kXc2VK7ig", false}, + {"EsYbG4tWWWY45G31nox838qNdzksbPySWc", false}, + {"nbuzhfwMoNzA3PaFnyLcRxE9bTJPDkjZ6Rf6Y6o2ckXZfzZzXBT", false}, + {"cQN9PoxZeCWK1x56xnz6QYAsvR11XAce3Ehp3gMUdfSQ53Y2mPzx", false}, + {"1Gm3N3rkef6iMbx4voBzaxtXcmmiMTqZPhcuAepRzYUJQW4qRpEnHvMojzof42hjFRf8PE2jPde", false}, + {"2TAq2tuN6x6m233bpT7yqdYQPELdTDJn1eU", false}, + {"ntEtnnGhqPii4joABvBtSEJG6BxjT2tUZqE8PcVYgk3RHpgxgHDCQxNbLJf7ardf1dDk2oCQ7Cf", false}, + {"Ky1YjoZNgQ196HJV3HpdkecfhRBmRZdMJk89Hi5KGfpfPwS2bUbfd", false}, + {"2A1q1YsMZowabbvta7kTy2Fd6qN4r5ZCeG3qLpvZBMzCixMUdkN2Y4dHB1wPsZAeVXUGD83MfRED", false}, + {"1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", true}, + {"1Ax4gZtb7gAit2TivwejZHYtNNLT18PUXJ", true}, + {"1C5bSj1iEGUgSTbziymG7Cn18ENQuT36vv", true}, + {"1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu", true}, + {"1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4", true}, + {"19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r", true}, + {"13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE", true}, + {"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", true}, + {"3P14159f73E4gFr7JterCCQh9QjiTjiZrG", true}, + {"3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou", true}, + {"3QjYXhTkvuj8qPaXHTTWb5wjXhdsLAAWVy", true}, + {"3AnNxabYGoTxYiTEZwFEnerUoeFXK2Zoks", true}, + {"33vt8ViH5jsr115AGkW6cEmEz9MpvJSwDk", true}, + {"3QCzvfL4ZRvmJFiWWBVwxfdaNBT8EtxB5y", true}, + {"37Sp6Rv3y4kVd1nQ1JV5pfqXccHNyZm1x3", true}, + {"3ALJH9Y951VCGcVZYAdpA3KchoP9McEj1G", true}, + {"12KYrjTdVGjFMtaxERSk3gphreJ5US8aUP", true}, + {"12QeMLzSrB8XH8FvEzPMVoRxVAzTr5XM2y", true}, + {"1oNLrsHnBcR6dpaBpwz3LSwutbUNkNSjs", true}, + {"1SQHtwR5oJRKLfiWQ2APsAd9miUc4k2ez", true}, + {"116CGDLddrZhMrTwhCVJXtXQpxygTT1kHd", true}, + {"3NJZLcZEEYBpxYEUGewU4knsQRn1WM5Fkt", true}, + } + + for i, test := range tests { + + errs := validate.Var(test.param, "btc_addr") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d btc_addr failed with Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d btc_addr failed with Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "btc_addr" { + t.Fatalf("Index: %d Latitude failed with Error: %s", i, errs) + } + } + } + } +} + +func TestBitcoinBech32AddressValidation(t *testing.T) { + + validate := New() + + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"bc1rw5uspcuh", false}, + {"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", false}, + {"BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", false}, + {"qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", false}, + {"bc1rw5uspcuh", false}, + {"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false}, + {"BC1QW508d6QEJxTDG4y5R3ZArVARY0C5XW7KV8F3T4", false}, + {"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", false}, + {"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", false}, + {"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", false}, + {"bc1pw508d6qejxtdg4y5r3zarqfsj6c3", false}, + {"bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", false}, + {"bc1gmk9yu", false}, + {"bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", false}, + {"BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", true}, + {"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", true}, + {"bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", true}, + {"BC1SW50QA3JX3S", true}, + {"bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", true}, + } + + for i, test := range tests { + + errs := validate.Var(test.param, "btc_addr_bech32") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d btc_addr_bech32 failed with Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d btc_addr_bech32 failed with Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "btc_addr_bech32" { + t.Fatalf("Index: %d Latitude failed with Error: %s", i, errs) + } + } + } + } +} + +func TestNoStructLevelValidation(t *testing.T) { + + type Inner struct { + Test string `validate:"len=5"` + } + + type Outer struct { + InnerStruct *Inner `validate:"required,nostructlevel"` + } + + outer := &Outer{ + InnerStruct: nil, + } + + validate := New() + + errs := validate.Struct(outer) + NotEqual(t, errs, nil) + AssertError(t, errs, "Outer.InnerStruct", "Outer.InnerStruct", "InnerStruct", "InnerStruct", "required") + + inner := &Inner{ + Test: "1234", + } + + outer = &Outer{ + InnerStruct: inner, + } + + errs = validate.Struct(outer) + Equal(t, errs, nil) +} + +func TestStructOnlyValidation(t *testing.T) { + + type Inner struct { + Test string `validate:"len=5"` + } + + type Outer struct { + InnerStruct *Inner `validate:"required,structonly"` + } + + outer := &Outer{ + InnerStruct: nil, + } + + validate := New() + + errs := validate.Struct(outer) + NotEqual(t, errs, nil) + AssertError(t, errs, "Outer.InnerStruct", "Outer.InnerStruct", "InnerStruct", "InnerStruct", "required") + + inner := &Inner{ + Test: "1234", + } + + outer = &Outer{ + InnerStruct: inner, + } + + errs = validate.Struct(outer) + Equal(t, errs, nil) + + // Address houses a users address information + type Address struct { + Street string `validate:"required"` + City string `validate:"required"` + Planet string `validate:"required"` + Phone string `validate:"required"` + } + + type User struct { + FirstName string `json:"fname"` + LastName string `json:"lname"` + Age uint8 `validate:"gte=0,lte=130"` + Number string `validate:"required,e164"` + Email string `validate:"required,email"` + FavouriteColor string `validate:"hexcolor|rgb|rgba"` + Addresses []*Address `validate:"required"` // a person can have a home and cottage... + Address Address `validate:"structonly"` // a person can have a home and cottage... + } + + address := &Address{ + Street: "Eavesdown Docks", + Planet: "Persphone", + Phone: "none", + City: "Unknown", + } + + user := &User{ + FirstName: "", + LastName: "", + Age: 45, + Number: "+1123456789", + Email: "Badger.Smith@gmail.com", + FavouriteColor: "#000", + Addresses: []*Address{address}, + Address: Address{ + // Street: "Eavesdown Docks", + Planet: "Persphone", + Phone: "none", + City: "Unknown", + }, + } + + errs = validate.Struct(user) + Equal(t, errs, nil) +} + +func TestGtField(t *testing.T) { + var errs error + validate := New() + + type TimeTest struct { + Start *time.Time `validate:"required,gt"` + End *time.Time `validate:"required,gt,gtfield=Start"` + } + + now := time.Now() + start := now.Add(time.Hour * 24) + end := start.Add(time.Hour * 24) + + timeTest := &TimeTest{ + Start: &start, + End: &end, + } + + errs = validate.Struct(timeTest) + Equal(t, errs, nil) + + timeTest = &TimeTest{ + Start: &end, + End: &start, + } + + errs = validate.Struct(timeTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest.End", "TimeTest.End", "End", "End", "gtfield") + + errs = validate.VarWithValue(&end, &start, "gtfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(&start, &end, "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtfield") + + errs = validate.VarWithValue(&end, &start, "gtfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(&timeTest, &end, "gtfield") + NotEqual(t, errs, nil) + + errs = validate.VarWithValue("test bigger", "test", "gtfield") + Equal(t, errs, nil) + + // Tests for time.Duration type. + + // -- Validations for variables of time.Duration type. + + errs = validate.VarWithValue(time.Hour, time.Hour-time.Minute, "gtfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour, "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtfield") + + errs = validate.VarWithValue(time.Hour, time.Hour+time.Minute, "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtfield") + + errs = validate.VarWithValue(time.Duration(0), time.Hour, "omitempty,gtfield") + Equal(t, errs, nil) + + // -- Validations for a struct with time.Duration type fields. + + type TimeDurationTest struct { + First time.Duration `validate:"gtfield=Second"` + Second time.Duration + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour - time.Minute} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.First", "TimeDurationTest.First", "First", "First", "gtfield") + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour + time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.First", "TimeDurationTest.First", "First", "First", "gtfield") + + type TimeDurationOmitemptyTest struct { + First time.Duration `validate:"omitempty,gtfield=Second"` + Second time.Duration + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0), time.Hour} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) + + // Tests for Ints types. + + type IntTest struct { + Val1 int `validate:"required"` + Val2 int `validate:"required,gtfield=Val1"` + } + + intTest := &IntTest{ + Val1: 1, + Val2: 5, + } + + errs = validate.Struct(intTest) + Equal(t, errs, nil) + + intTest = &IntTest{ + Val1: 5, + Val2: 1, + } + + errs = validate.Struct(intTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "IntTest.Val2", "IntTest.Val2", "Val2", "Val2", "gtfield") + + errs = validate.VarWithValue(int(5), int(1), "gtfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(int(1), int(5), "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtfield") + + type UIntTest struct { + Val1 uint `validate:"required"` + Val2 uint `validate:"required,gtfield=Val1"` + } + + uIntTest := &UIntTest{ + Val1: 1, + Val2: 5, + } + + errs = validate.Struct(uIntTest) + Equal(t, errs, nil) + + uIntTest = &UIntTest{ + Val1: 5, + Val2: 1, + } + + errs = validate.Struct(uIntTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "UIntTest.Val2", "UIntTest.Val2", "Val2", "Val2", "gtfield") + + errs = validate.VarWithValue(uint(5), uint(1), "gtfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(uint(1), uint(5), "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtfield") + + type FloatTest struct { + Val1 float64 `validate:"required"` + Val2 float64 `validate:"required,gtfield=Val1"` + } + + floatTest := &FloatTest{ + Val1: 1, + Val2: 5, + } + + errs = validate.Struct(floatTest) + Equal(t, errs, nil) + + floatTest = &FloatTest{ + Val1: 5, + Val2: 1, + } + + errs = validate.Struct(floatTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "FloatTest.Val2", "FloatTest.Val2", "Val2", "Val2", "gtfield") + + errs = validate.VarWithValue(float32(5), float32(1), "gtfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(float32(1), float32(5), "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtfield") + + errs = validate.VarWithValue(nil, 1, "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtfield") + + errs = validate.VarWithValue(5, "T", "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtfield") + + errs = validate.VarWithValue(5, start, "gtfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtfield") + + type TimeTest2 struct { + Start *time.Time `validate:"required"` + End *time.Time `validate:"required,gtfield=NonExistantField"` + } + + timeTest2 := &TimeTest2{ + Start: &start, + End: &end, + } + + errs = validate.Struct(timeTest2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest2.End", "TimeTest2.End", "End", "End", "gtfield") + + type Other struct { + Value string + } + + type Test struct { + Value Other + Time time.Time `validate:"gtfield=Value"` + } + + tst := Test{ + Value: Other{Value: "StringVal"}, + Time: end, + } + + errs = validate.Struct(tst) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.Time", "Test.Time", "Time", "Time", "gtfield") +} + +func TestLtField(t *testing.T) { + var errs error + validate := New() + + type TimeTest struct { + Start *time.Time `validate:"required,lt,ltfield=End"` + End *time.Time `validate:"required,lt"` + } + + now := time.Now() + start := now.Add(time.Hour * 24 * -1 * 2) + end := start.Add(time.Hour * 24) + + timeTest := &TimeTest{ + Start: &start, + End: &end, + } + + errs = validate.Struct(timeTest) + Equal(t, errs, nil) + + timeTest = &TimeTest{ + Start: &end, + End: &start, + } + + errs = validate.Struct(timeTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest.Start", "TimeTest.Start", "Start", "Start", "ltfield") + + errs = validate.VarWithValue(&start, &end, "ltfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(&end, &start, "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltfield") + + errs = validate.VarWithValue(&end, timeTest, "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltfield") + + errs = validate.VarWithValue("tes", "test", "ltfield") + Equal(t, errs, nil) + + // Tests for time.Duration type. + + // -- Validations for variables of time.Duration type. + + errs = validate.VarWithValue(time.Hour, time.Hour+time.Minute, "ltfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour, "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltfield") + + errs = validate.VarWithValue(time.Hour, time.Hour-time.Minute, "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltfield") + + errs = validate.VarWithValue(time.Duration(0), -time.Minute, "omitempty,ltfield") + Equal(t, errs, nil) + + // -- Validations for a struct with time.Duration type fields. + + type TimeDurationTest struct { + First time.Duration `validate:"ltfield=Second"` + Second time.Duration + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour + time.Minute} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.First", "TimeDurationTest.First", "First", "First", "ltfield") + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour - time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.First", "TimeDurationTest.First", "First", "First", "ltfield") + + type TimeDurationOmitemptyTest struct { + First time.Duration `validate:"omitempty,ltfield=Second"` + Second time.Duration + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0), -time.Minute} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) + + // Tests for Ints types. + + type IntTest struct { + Val1 int `validate:"required"` + Val2 int `validate:"required,ltfield=Val1"` + } + + intTest := &IntTest{ + Val1: 5, + Val2: 1, + } + + errs = validate.Struct(intTest) + Equal(t, errs, nil) + + intTest = &IntTest{ + Val1: 1, + Val2: 5, + } + + errs = validate.Struct(intTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "IntTest.Val2", "IntTest.Val2", "Val2", "Val2", "ltfield") + + errs = validate.VarWithValue(int(1), int(5), "ltfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(int(5), int(1), "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltfield") + + type UIntTest struct { + Val1 uint `validate:"required"` + Val2 uint `validate:"required,ltfield=Val1"` + } + + uIntTest := &UIntTest{ + Val1: 5, + Val2: 1, + } + + errs = validate.Struct(uIntTest) + Equal(t, errs, nil) + + uIntTest = &UIntTest{ + Val1: 1, + Val2: 5, + } + + errs = validate.Struct(uIntTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "UIntTest.Val2", "UIntTest.Val2", "Val2", "Val2", "ltfield") + + errs = validate.VarWithValue(uint(1), uint(5), "ltfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(uint(5), uint(1), "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltfield") + + type FloatTest struct { + Val1 float64 `validate:"required"` + Val2 float64 `validate:"required,ltfield=Val1"` + } + + floatTest := &FloatTest{ + Val1: 5, + Val2: 1, + } + + errs = validate.Struct(floatTest) + Equal(t, errs, nil) + + floatTest = &FloatTest{ + Val1: 1, + Val2: 5, + } + + errs = validate.Struct(floatTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "FloatTest.Val2", "FloatTest.Val2", "Val2", "Val2", "ltfield") + + errs = validate.VarWithValue(float32(1), float32(5), "ltfield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(float32(5), float32(1), "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltfield") + + errs = validate.VarWithValue(nil, 5, "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltfield") + + errs = validate.VarWithValue(1, "T", "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltfield") + + errs = validate.VarWithValue(1, end, "ltfield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltfield") + + type TimeTest2 struct { + Start *time.Time `validate:"required"` + End *time.Time `validate:"required,ltfield=NonExistantField"` + } + + timeTest2 := &TimeTest2{ + Start: &end, + End: &start, + } + + errs = validate.Struct(timeTest2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest2.End", "TimeTest2.End", "End", "End", "ltfield") +} + +func TestFieldContains(t *testing.T) { + validate := New() + + type StringTest struct { + Foo string `validate:"fieldcontains=Bar"` + Bar string + } + + stringTest := &StringTest{ + Foo: "foobar", + Bar: "bar", + } + + errs := validate.Struct(stringTest) + Equal(t, errs, nil) + + stringTest = &StringTest{ + Foo: "foo", + Bar: "bar", + } + + errs = validate.Struct(stringTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "StringTest.Foo", "StringTest.Foo", "Foo", "Foo", "fieldcontains") + + errs = validate.VarWithValue("foo", "bar", "fieldcontains") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "fieldcontains") + + errs = validate.VarWithValue("bar", "foobarfoo", "fieldcontains") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "fieldcontains") + + errs = validate.VarWithValue("foobarfoo", "bar", "fieldcontains") + Equal(t, errs, nil) + + type StringTestMissingField struct { + Foo string `validate:"fieldcontains=Bar"` + } + + stringTestMissingField := &StringTestMissingField{ + Foo: "foo", + } + + errs = validate.Struct(stringTestMissingField) + NotEqual(t, errs, nil) + AssertError(t, errs, "StringTestMissingField.Foo", "StringTestMissingField.Foo", "Foo", "Foo", "fieldcontains") +} + +func TestFieldExcludes(t *testing.T) { + validate := New() + + type StringTest struct { + Foo string `validate:"fieldexcludes=Bar"` + Bar string + } + + stringTest := &StringTest{ + Foo: "foobar", + Bar: "bar", + } + + errs := validate.Struct(stringTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "StringTest.Foo", "StringTest.Foo", "Foo", "Foo", "fieldexcludes") + + stringTest = &StringTest{ + Foo: "foo", + Bar: "bar", + } + + errs = validate.Struct(stringTest) + Equal(t, errs, nil) + + errs = validate.VarWithValue("foo", "bar", "fieldexcludes") + Equal(t, errs, nil) + + errs = validate.VarWithValue("bar", "foobarfoo", "fieldexcludes") + Equal(t, errs, nil) + + errs = validate.VarWithValue("foobarfoo", "bar", "fieldexcludes") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "fieldexcludes") + + type StringTestMissingField struct { + Foo string `validate:"fieldexcludes=Bar"` + } + + stringTestMissingField := &StringTestMissingField{ + Foo: "foo", + } + + errs = validate.Struct(stringTestMissingField) + Equal(t, errs, nil) +} + +func TestContainsAndExcludes(t *testing.T) { + validate := New() + + type ImpossibleStringTest struct { + Foo string `validate:"fieldcontains=Bar"` + Bar string `validate:"fieldexcludes=Foo"` + } + + impossibleStringTest := &ImpossibleStringTest{ + Foo: "foo", + Bar: "bar", + } + + errs := validate.Struct(impossibleStringTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "ImpossibleStringTest.Foo", "ImpossibleStringTest.Foo", "Foo", "Foo", "fieldcontains") + + impossibleStringTest = &ImpossibleStringTest{ + Foo: "bar", + Bar: "foo", + } + + errs = validate.Struct(impossibleStringTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "ImpossibleStringTest.Foo", "ImpossibleStringTest.Foo", "Foo", "Foo", "fieldcontains") +} + +func TestLteField(t *testing.T) { + var errs error + validate := New() + + type TimeTest struct { + Start *time.Time `validate:"required,lte,ltefield=End"` + End *time.Time `validate:"required,lte"` + } + + now := time.Now() + start := now.Add(time.Hour * 24 * -1 * 2) + end := start.Add(time.Hour * 24) + + timeTest := &TimeTest{ + Start: &start, + End: &end, + } + + errs = validate.Struct(timeTest) + Equal(t, errs, nil) + + timeTest = &TimeTest{ + Start: &end, + End: &start, + } + + errs = validate.Struct(timeTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest.Start", "TimeTest.Start", "Start", "Start", "ltefield") + + errs = validate.VarWithValue(&start, &end, "ltefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(&end, &start, "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltefield") + + errs = validate.VarWithValue(&end, timeTest, "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltefield") + + errs = validate.VarWithValue("tes", "test", "ltefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue("test", "test", "ltefield") + Equal(t, errs, nil) + + // Tests for time.Duration type. + + // -- Validations for variables of time.Duration type. + + errs = validate.VarWithValue(time.Hour, time.Hour+time.Minute, "ltefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour, "ltefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour-time.Minute, "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltefield") + + errs = validate.VarWithValue(time.Duration(0), -time.Minute, "omitempty,ltefield") + Equal(t, errs, nil) + + // -- Validations for a struct with time.Duration type fields. + + type TimeDurationTest struct { + First time.Duration `validate:"ltefield=Second"` + Second time.Duration + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour + time.Minute} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour - time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.First", "TimeDurationTest.First", "First", "First", "ltefield") + + type TimeDurationOmitemptyTest struct { + First time.Duration `validate:"omitempty,ltefield=Second"` + Second time.Duration + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0), -time.Minute} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) + + // Tests for Ints types. + + type IntTest struct { + Val1 int `validate:"required"` + Val2 int `validate:"required,ltefield=Val1"` + } + + intTest := &IntTest{ + Val1: 5, + Val2: 1, + } + + errs = validate.Struct(intTest) + Equal(t, errs, nil) + + intTest = &IntTest{ + Val1: 1, + Val2: 5, + } + + errs = validate.Struct(intTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "IntTest.Val2", "IntTest.Val2", "Val2", "Val2", "ltefield") + + errs = validate.VarWithValue(int(1), int(5), "ltefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(int(5), int(1), "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltefield") + + type UIntTest struct { + Val1 uint `validate:"required"` + Val2 uint `validate:"required,ltefield=Val1"` + } + + uIntTest := &UIntTest{ + Val1: 5, + Val2: 1, + } + + errs = validate.Struct(uIntTest) + Equal(t, errs, nil) + + uIntTest = &UIntTest{ + Val1: 1, + Val2: 5, + } + + errs = validate.Struct(uIntTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "UIntTest.Val2", "UIntTest.Val2", "Val2", "Val2", "ltefield") + + errs = validate.VarWithValue(uint(1), uint(5), "ltefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(uint(5), uint(1), "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltefield") + + type FloatTest struct { + Val1 float64 `validate:"required"` + Val2 float64 `validate:"required,ltefield=Val1"` + } + + floatTest := &FloatTest{ + Val1: 5, + Val2: 1, + } + + errs = validate.Struct(floatTest) + Equal(t, errs, nil) + + floatTest = &FloatTest{ + Val1: 1, + Val2: 5, + } + + errs = validate.Struct(floatTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "FloatTest.Val2", "FloatTest.Val2", "Val2", "Val2", "ltefield") + + errs = validate.VarWithValue(float32(1), float32(5), "ltefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(float32(5), float32(1), "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltefield") + + errs = validate.VarWithValue(nil, 5, "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltefield") + + errs = validate.VarWithValue(1, "T", "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltefield") + + errs = validate.VarWithValue(1, end, "ltefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "ltefield") + + type TimeTest2 struct { + Start *time.Time `validate:"required"` + End *time.Time `validate:"required,ltefield=NonExistantField"` + } + + timeTest2 := &TimeTest2{ + Start: &end, + End: &start, + } + + errs = validate.Struct(timeTest2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest2.End", "TimeTest2.End", "End", "End", "ltefield") +} + +func TestGteField(t *testing.T) { + var errs error + validate := New() + + type TimeTest struct { + Start *time.Time `validate:"required,gte"` + End *time.Time `validate:"required,gte,gtefield=Start"` + } + + now := time.Now() + start := now.Add(time.Hour * 24) + end := start.Add(time.Hour * 24) + + timeTest := &TimeTest{ + Start: &start, + End: &end, + } + + errs = validate.Struct(timeTest) + Equal(t, errs, nil) + + timeTest = &TimeTest{ + Start: &end, + End: &start, + } + + errs = validate.Struct(timeTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest.End", "TimeTest.End", "End", "End", "gtefield") + + errs = validate.VarWithValue(&end, &start, "gtefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(&start, &end, "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtefield") + + errs = validate.VarWithValue(&start, timeTest, "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtefield") + + errs = validate.VarWithValue("test", "test", "gtefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue("test bigger", "test", "gtefield") + Equal(t, errs, nil) + + // Tests for time.Duration type. + + // -- Validations for variables of time.Duration type. + + errs = validate.VarWithValue(time.Hour, time.Hour-time.Minute, "gtefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour, "gtefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(time.Hour, time.Hour+time.Minute, "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtefield") + + errs = validate.VarWithValue(time.Duration(0), time.Hour, "omitempty,gtefield") + Equal(t, errs, nil) + + // -- Validations for a struct with time.Duration type fields. + + type TimeDurationTest struct { + First time.Duration `validate:"gtefield=Second"` + Second time.Duration + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour - time.Minute} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour, time.Hour + time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.First", "TimeDurationTest.First", "First", "First", "gtefield") + + type TimeDurationOmitemptyTest struct { + First time.Duration `validate:"omitempty,gtefield=Second"` + Second time.Duration + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0), time.Hour} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) + + // Tests for Ints types. + + type IntTest struct { + Val1 int `validate:"required"` + Val2 int `validate:"required,gtefield=Val1"` + } + + intTest := &IntTest{ + Val1: 1, + Val2: 5, + } + + errs = validate.Struct(intTest) + Equal(t, errs, nil) + + intTest = &IntTest{ + Val1: 5, + Val2: 1, + } + + errs = validate.Struct(intTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "IntTest.Val2", "IntTest.Val2", "Val2", "Val2", "gtefield") + + errs = validate.VarWithValue(int(5), int(1), "gtefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(int(1), int(5), "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtefield") + + type UIntTest struct { + Val1 uint `validate:"required"` + Val2 uint `validate:"required,gtefield=Val1"` + } + + uIntTest := &UIntTest{ + Val1: 1, + Val2: 5, + } + + errs = validate.Struct(uIntTest) + Equal(t, errs, nil) + + uIntTest = &UIntTest{ + Val1: 5, + Val2: 1, + } + + errs = validate.Struct(uIntTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "UIntTest.Val2", "UIntTest.Val2", "Val2", "Val2", "gtefield") + + errs = validate.VarWithValue(uint(5), uint(1), "gtefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(uint(1), uint(5), "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtefield") + + type FloatTest struct { + Val1 float64 `validate:"required"` + Val2 float64 `validate:"required,gtefield=Val1"` + } + + floatTest := &FloatTest{ + Val1: 1, + Val2: 5, + } + + errs = validate.Struct(floatTest) + Equal(t, errs, nil) + + floatTest = &FloatTest{ + Val1: 5, + Val2: 1, + } + + errs = validate.Struct(floatTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "FloatTest.Val2", "FloatTest.Val2", "Val2", "Val2", "gtefield") + + errs = validate.VarWithValue(float32(5), float32(1), "gtefield") + Equal(t, errs, nil) + + errs = validate.VarWithValue(float32(1), float32(5), "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtefield") + + errs = validate.VarWithValue(nil, 1, "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtefield") + + errs = validate.VarWithValue(5, "T", "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtefield") + + errs = validate.VarWithValue(5, start, "gtefield") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gtefield") + + type TimeTest2 struct { + Start *time.Time `validate:"required"` + End *time.Time `validate:"required,gtefield=NonExistantField"` + } + + timeTest2 := &TimeTest2{ + Start: &start, + End: &end, + } + + errs = validate.Struct(timeTest2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeTest2.End", "TimeTest2.End", "End", "End", "gtefield") +} + +func TestValidateByTagAndValue(t *testing.T) { + validate := New() + + val := "test" + field := "test" + errs := validate.VarWithValue(val, field, "required") + Equal(t, errs, nil) + + fn := func(fl FieldLevel) bool { + + return fl.Parent().String() == fl.Field().String() + } + + errs = validate.RegisterValidation("isequaltestfunc", fn) + Equal(t, errs, nil) + + errs = validate.VarWithValue(val, field, "isequaltestfunc") + Equal(t, errs, nil) + + val = "unequal" + + errs = validate.VarWithValue(val, field, "isequaltestfunc") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "isequaltestfunc") +} + +func TestAddFunctions(t *testing.T) { + + fn := func(fl FieldLevel) bool { + + return true + } + + fnCtx := func(ctx context.Context, fl FieldLevel) bool { + return true + } + + validate := New() + + errs := validate.RegisterValidation("new", fn) + Equal(t, errs, nil) + + errs = validate.RegisterValidation("", fn) + NotEqual(t, errs, nil) + + errs = validate.RegisterValidation("new", nil) + NotEqual(t, errs, nil) + + errs = validate.RegisterValidation("new", fn) + Equal(t, errs, nil) + + errs = validate.RegisterValidationCtx("new", fnCtx) + Equal(t, errs, nil) + + PanicMatches(t, func() { _ = validate.RegisterValidation("dive", fn) }, "Tag 'dive' either contains restricted characters or is the same as a restricted tag needed for normal operation") +} + +func TestChangeTag(t *testing.T) { + + validate := New() + validate.SetTagName("val") + + type Test struct { + Name string `val:"len=4"` + } + s := &Test{ + Name: "TEST", + } + + errs := validate.Struct(s) + Equal(t, errs, nil) + + s.Name = "" + + errs = validate.Struct(s) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.Name", "Test.Name", "Name", "Name", "len") +} + +func TestUnexposedStruct(t *testing.T) { + validate := New() + + type Test struct { + Name string + unexposed struct { + A string `validate:"required"` + } + } + + s := &Test{ + Name: "TEST", + } + Equal(t, s.unexposed.A, "") + + errs := validate.Struct(s) + Equal(t, errs, nil) +} + +func TestBadParams(t *testing.T) { + validate := New() + i := 1 + errs := validate.Var(i, "-") + Equal(t, errs, nil) + + PanicMatches(t, func() { _ = validate.Var(i, "len=a") }, "strconv.ParseInt: parsing \"a\": invalid syntax") + PanicMatches(t, func() { _ = validate.Var(i, "len=a") }, "strconv.ParseInt: parsing \"a\": invalid syntax") + + var ui uint = 1 + PanicMatches(t, func() { _ = validate.Var(ui, "len=a") }, "strconv.ParseUint: parsing \"a\": invalid syntax") + + f := 1.23 + PanicMatches(t, func() { _ = validate.Var(f, "len=a") }, "strconv.ParseFloat: parsing \"a\": invalid syntax") +} + +func TestLength(t *testing.T) { + validate := New() + i := true + PanicMatches(t, func() { _ = validate.Var(i, "len") }, "Bad field type bool") +} + +func TestIsGt(t *testing.T) { + var errs error + validate := New() + + myMap := map[string]string{} + errs = validate.Var(myMap, "gt=0") + NotEqual(t, errs, nil) + + f := 1.23 + errs = validate.Var(f, "gt=5") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gt") + + var ui uint = 5 + errs = validate.Var(ui, "gt=10") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gt") + + i := true + PanicMatches(t, func() { _ = validate.Var(i, "gt") }, "Bad field type bool") + + tm := time.Now().UTC() + tm = tm.Add(time.Hour * 24) + + errs = validate.Var(tm, "gt") + Equal(t, errs, nil) + + t2 := time.Now().UTC().Add(-time.Hour) + + errs = validate.Var(t2, "gt") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gt") + + type Test struct { + Now *time.Time `validate:"gt"` + } + s := &Test{ + Now: &tm, + } + + errs = validate.Struct(s) + Equal(t, errs, nil) + + s = &Test{ + Now: &t2, + } + + errs = validate.Struct(s) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.Now", "Test.Now", "Now", "Now", "gt") + + // Tests for time.Duration type. + + // -- Validations for a variable of time.Duration type. + + errs = validate.Var(time.Hour, "gt=59m") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour-time.Minute, "gt=59m") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gt") + + errs = validate.Var(time.Hour-2*time.Minute, "gt=59m") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gt") + + errs = validate.Var(time.Duration(0), "omitempty,gt=59m") + Equal(t, errs, nil) + + // -- Validations for a struct with a time.Duration type field. + + type TimeDurationTest struct { + Duration time.Duration `validate:"gt=59m"` + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour - time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "gt") + + timeDurationTest = &TimeDurationTest{time.Hour - 2*time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "gt") + + type TimeDurationOmitemptyTest struct { + Duration time.Duration `validate:"omitempty,gt=59m"` + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestIsGte(t *testing.T) { + var errs error + validate := New() + + i := true + PanicMatches(t, func() { _ = validate.Var(i, "gte") }, "Bad field type bool") + + t1 := time.Now().UTC() + t1 = t1.Add(time.Hour * 24) + + errs = validate.Var(t1, "gte") + Equal(t, errs, nil) + + t2 := time.Now().UTC().Add(-time.Hour) + + errs = validate.Var(t2, "gte") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gte") + + type Test struct { + Now *time.Time `validate:"gte"` + } + s := &Test{ + Now: &t1, + } + + errs = validate.Struct(s) + Equal(t, errs, nil) + + s = &Test{ + Now: &t2, + } + + errs = validate.Struct(s) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.Now", "Test.Now", "Now", "Now", "gte") + + // Tests for time.Duration type. + + // -- Validations for a variable of time.Duration type. + + errs = validate.Var(time.Hour, "gte=59m") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour-time.Minute, "gte=59m") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour-2*time.Minute, "gte=59m") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "gte") + + errs = validate.Var(time.Duration(0), "omitempty,gte=59m") + Equal(t, errs, nil) + + // -- Validations for a struct with a time.Duration type field. + + type TimeDurationTest struct { + Duration time.Duration `validate:"gte=59m"` + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour - time.Minute} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour - 2*time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "gte") + + type TimeDurationOmitemptyTest struct { + Duration time.Duration `validate:"omitempty,gte=59m"` + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestMinValidation(t *testing.T) { + var errs error + validate := New() + + // Tests for time.Duration type. + + // -- Validations for a variable of time.Duration type. + + errs = validate.Var(time.Hour, "min=59m") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour-time.Minute, "min=59m") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour-2*time.Minute, "min=59m") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "min") + + errs = validate.Var(time.Duration(0), "omitempty,min=59m") + Equal(t, errs, nil) + + // -- Validations for a struct with a time.Duration type field. + + type TimeDurationTest struct { + Duration time.Duration `validate:"min=59m"` + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour - time.Minute} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour - 2*time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "min") + + type TimeDurationOmitemptyTest struct { + Duration time.Duration `validate:"omitempty,min=59m"` + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestMaxValidation(t *testing.T) { + var errs error + validate := New() + + // Tests for time.Duration type. + + // -- Validations for a variable of time.Duration type. + + errs = validate.Var(time.Hour, "max=1h1m") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour+time.Minute, "max=1h1m") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour+2*time.Minute, "max=1h1m") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "max") + + errs = validate.Var(time.Duration(0), "omitempty,max=-1s") + Equal(t, errs, nil) + + // -- Validations for a struct with a time.Duration type field. + + type TimeDurationTest struct { + Duration time.Duration `validate:"max=1h1m"` + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour + time.Minute} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour + 2*time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "max") + + type TimeDurationOmitemptyTest struct { + Duration time.Duration `validate:"omitempty,max=-1s"` + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestMinMaxValidation(t *testing.T) { + var errs error + validate := New() + + // Tests for time.Duration type. + + // -- Validations for a variable of time.Duration type. + + errs = validate.Var(time.Hour, "min=59m,max=1h1m") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour-time.Minute, "min=59m,max=1h1m") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour+time.Minute, "min=59m,max=1h1m") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour-2*time.Minute, "min=59m,max=1h1m") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "min") + + errs = validate.Var(time.Hour+2*time.Minute, "min=59m,max=1h1m") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "max") + + errs = validate.Var(time.Duration(0), "omitempty,min=59m,max=1h1m") + Equal(t, errs, nil) + + // -- Validations for a struct with a time.Duration type field. + + type TimeDurationTest struct { + Duration time.Duration `validate:"min=59m,max=1h1m"` + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour - time.Minute} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour + time.Minute} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour - 2*time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "min") + + timeDurationTest = &TimeDurationTest{time.Hour + 2*time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "max") + + type TimeDurationOmitemptyTest struct { + Duration time.Duration `validate:"omitempty,min=59m,max=1h1m"` + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestLenValidation(t *testing.T) { + var errs error + validate := New() + + // Tests for time.Duration type. + + // -- Validations for a variable of time.Duration type. + + errs = validate.Var(time.Hour, "len=1h") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour-time.Minute, "len=1h") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "len") + + errs = validate.Var(time.Hour+time.Minute, "len=1h") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "len") + + errs = validate.Var(time.Duration(0), "omitempty,len=1h") + Equal(t, errs, nil) + + // -- Validations for a struct with a time.Duration type field. + + type TimeDurationTest struct { + Duration time.Duration `validate:"len=1h"` + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour - time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "len") + + timeDurationTest = &TimeDurationTest{time.Hour + time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "len") + + type TimeDurationOmitemptyTest struct { + Duration time.Duration `validate:"omitempty,len=1h"` + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestIsLt(t *testing.T) { + var errs error + validate := New() + + myMap := map[string]string{} + errs = validate.Var(myMap, "lt=0") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "lt") + + f := 1.23 + errs = validate.Var(f, "lt=0") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "lt") + + var ui uint = 5 + errs = validate.Var(ui, "lt=0") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "lt") + + i := true + PanicMatches(t, func() { _ = validate.Var(i, "lt") }, "Bad field type bool") + + t1 := time.Now().UTC().Add(-time.Hour) + + errs = validate.Var(t1, "lt") + Equal(t, errs, nil) + + t2 := time.Now().UTC() + t2 = t2.Add(time.Hour * 24) + + errs = validate.Var(t2, "lt") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "lt") + + type Test struct { + Now *time.Time `validate:"lt"` + } + + s := &Test{ + Now: &t1, + } + + errs = validate.Struct(s) + Equal(t, errs, nil) + + s = &Test{ + Now: &t2, + } + + errs = validate.Struct(s) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.Now", "Test.Now", "Now", "Now", "lt") + + // Tests for time.Duration type. + + // -- Validations for a variable of time.Duration type. + + errs = validate.Var(time.Hour, "lt=1h1m") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour+time.Minute, "lt=1h1m") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "lt") + + errs = validate.Var(time.Hour+2*time.Minute, "lt=1h1m") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "lt") + + errs = validate.Var(time.Duration(0), "omitempty,lt=0") + Equal(t, errs, nil) + + // -- Validations for a struct with a time.Duration type field. + + type TimeDurationTest struct { + Duration time.Duration `validate:"lt=1h1m"` + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour + time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "lt") + + timeDurationTest = &TimeDurationTest{time.Hour + 2*time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "lt") + + type TimeDurationOmitemptyTest struct { + Duration time.Duration `validate:"omitempty,lt=0"` + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestIsLte(t *testing.T) { + var errs error + validate := New() + + i := true + PanicMatches(t, func() { _ = validate.Var(i, "lte") }, "Bad field type bool") + + t1 := time.Now().UTC().Add(-time.Hour) + + errs = validate.Var(t1, "lte") + Equal(t, errs, nil) + + t2 := time.Now().UTC() + t2 = t2.Add(time.Hour * 24) + + errs = validate.Var(t2, "lte") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "lte") + + type Test struct { + Now *time.Time `validate:"lte"` + } + + s := &Test{ + Now: &t1, + } + + errs = validate.Struct(s) + Equal(t, errs, nil) + + s = &Test{ + Now: &t2, + } + + errs = validate.Struct(s) + NotEqual(t, errs, nil) + + // Tests for time.Duration type. + + // -- Validations for a variable of time.Duration type. + + errs = validate.Var(time.Hour, "lte=1h1m") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour+time.Minute, "lte=1h1m") + Equal(t, errs, nil) + + errs = validate.Var(time.Hour+2*time.Minute, "lte=1h1m") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "lte") + + errs = validate.Var(time.Duration(0), "omitempty,lte=-1s") + Equal(t, errs, nil) + + // -- Validations for a struct with a time.Duration type field. + + type TimeDurationTest struct { + Duration time.Duration `validate:"lte=1h1m"` + } + var timeDurationTest *TimeDurationTest + + timeDurationTest = &TimeDurationTest{time.Hour} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour + time.Minute} + errs = validate.Struct(timeDurationTest) + Equal(t, errs, nil) + + timeDurationTest = &TimeDurationTest{time.Hour + 2*time.Minute} + errs = validate.Struct(timeDurationTest) + NotEqual(t, errs, nil) + AssertError(t, errs, "TimeDurationTest.Duration", "TimeDurationTest.Duration", "Duration", "Duration", "lte") + + type TimeDurationOmitemptyTest struct { + Duration time.Duration `validate:"omitempty,lte=-1s"` + } + + timeDurationOmitemptyTest := &TimeDurationOmitemptyTest{time.Duration(0)} + errs = validate.Struct(timeDurationOmitemptyTest) + Equal(t, errs, nil) +} + +func TestUrnRFC2141(t *testing.T) { + + var tests = []struct { + param string + expected bool + }{ + {"urn:a:b", true}, + {"urn:a::", true}, + {"urn:a:-", true}, + {"URN:simple:simple", true}, + {"urn:urna:simple", true}, + {"urn:burnout:nss", true}, + {"urn:burn:nss", true}, + {"urn:urnurnurn:x", true}, + {"urn:abcdefghilmnopqrstuvzabcdefghilm:x", true}, + {"URN:123:x", true}, + {"URN:abcd-:x", true}, + {"URN:abcd-abcd:x", true}, + {"urn:urnx:urn", true}, + {"urn:ciao:a:b:c", true}, + {"urn:aaa:x:y:", true}, + {"urn:ciao:-", true}, + {"urn:colon:::::nss", true}, + {"urn:ciao:@!=%2C(xyz)+a,b.*@g=$_'", true}, + {"URN:hexes:%25", true}, + {"URN:x:abc%1Dz%2F%3az", true}, + {"URN:foo:a123,456", true}, + {"urn:foo:a123,456", true}, + {"urn:FOO:a123,456", true}, + {"urn:foo:A123,456", true}, + {"urn:foo:a123%2C456", true}, + {"URN:FOO:a123%2c456", true}, + {"URN:FOO:ABC%FFabc123%2c456", true}, + {"URN:FOO:ABC%FFabc123%2C456%9A", true}, + {"urn:ietf:params:scim:schemas:core:2.0:User", true}, + {"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:meta.lastModified", true}, + {"URN:-xxx:x", false}, + {"urn::colon:nss", false}, + {"urn:abcdefghilmnopqrstuvzabcdefghilmn:specificstring", false}, + {"URN:a!?:x", false}, + {"URN:#,:x", false}, + {"urn:urn:NSS", false}, + {"urn:URN:NSS", false}, + {"urn:white space:NSS", false}, + {"urn:concat:no spaces", false}, + {"urn:a:%", false}, + {"urn:", false}, + } + + tag := "urn_rfc2141" + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, tag) + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d URN failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d URN failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != tag { + t.Fatalf("Index: %d URN failed Error: %s", i, errs) + } + } + } + } + + i := 1 + PanicMatches(t, func() { _ = validate.Var(i, tag) }, "Bad field type int") +} + +func TestUrl(t *testing.T) { + + var tests = []struct { + param string + expected bool + }{ + {"http://foo.bar#com", true}, + {"http://foobar.com", true}, + {"https://foobar.com", true}, + {"foobar.com", false}, + {"http://foobar.coffee/", true}, + {"http://foobar.中文网/", true}, + {"http://foobar.org/", true}, + {"http://foobar.org:8080/", true}, + {"ftp://foobar.ru/", true}, + {"http://user:pass@www.foobar.com/", true}, + {"http://127.0.0.1/", true}, + {"http://duckduckgo.com/?q=%2F", true}, + {"http://localhost:3000/", true}, + {"http://foobar.com/?foo=bar#baz=qux", true}, + {"http://foobar.com?foo=bar", true}, + {"http://www.xn--froschgrn-x9a.net/", true}, + {"", false}, + {"xyz://foobar.com", true}, + {"invalid.", false}, + {".com", false}, + {"rtmp://foobar.com", true}, + {"http://www.foo_bar.com/", true}, + {"http://localhost:3000/", true}, + {"http://foobar.com/#baz", true}, + {"http://foobar.com#baz=qux", true}, + {"http://foobar.com/t$-_.+!*\\'(),", true}, + {"http://www.foobar.com/~foobar", true}, + {"http://www.-foobar.com/", true}, + {"http://www.foo---bar.com/", true}, + {"mailto:someone@example.com", true}, + {"irc://irc.server.org/channel", true}, + {"irc://#channel@network", true}, + {"/abs/test/dir", false}, + {"./rel/test/dir", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "url") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d URL failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d URL failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "url" { + t.Fatalf("Index: %d URL failed Error: %s", i, errs) + } + } + } + } + + i := 1 + PanicMatches(t, func() { _ = validate.Var(i, "url") }, "Bad field type int") +} + +func TestUri(t *testing.T) { + + var tests = []struct { + param string + expected bool + }{ + {"http://foo.bar#com", true}, + {"http://foobar.com", true}, + {"https://foobar.com", true}, + {"foobar.com", false}, + {"http://foobar.coffee/", true}, + {"http://foobar.中文网/", true}, + {"http://foobar.org/", true}, + {"http://foobar.org:8080/", true}, + {"ftp://foobar.ru/", true}, + {"http://user:pass@www.foobar.com/", true}, + {"http://127.0.0.1/", true}, + {"http://duckduckgo.com/?q=%2F", true}, + {"http://localhost:3000/", true}, + {"http://foobar.com/?foo=bar#baz=qux", true}, + {"http://foobar.com?foo=bar", true}, + {"http://www.xn--froschgrn-x9a.net/", true}, + {"", false}, + {"xyz://foobar.com", true}, + {"invalid.", false}, + {".com", false}, + {"rtmp://foobar.com", true}, + {"http://www.foo_bar.com/", true}, + {"http://localhost:3000/", true}, + {"http://foobar.com#baz=qux", true}, + {"http://foobar.com/t$-_.+!*\\'(),", true}, + {"http://www.foobar.com/~foobar", true}, + {"http://www.-foobar.com/", true}, + {"http://www.foo---bar.com/", true}, + {"mailto:someone@example.com", true}, + {"irc://irc.server.org/channel", true}, + {"irc://#channel@network", true}, + {"/abs/test/dir", true}, + {"./rel/test/dir", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "uri") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d URI failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d URI failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "uri" { + t.Fatalf("Index: %d URI failed Error: %s", i, errs) + } + } + } + } + + i := 1 + PanicMatches(t, func() { _ = validate.Var(i, "uri") }, "Bad field type int") +} + +func TestOrTag(t *testing.T) { + + validate := New() + + s := "rgba(0,31,255,0.5)" + errs := validate.Var(s, "rgb|rgba") + Equal(t, errs, nil) + + s = "rgba(0,31,255,0.5)" + errs = validate.Var(s, "rgb|rgba|len=18") + Equal(t, errs, nil) + + s = "this ain't right" + errs = validate.Var(s, "rgb|rgba") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "rgb|rgba") + + s = "this ain't right" + errs = validate.Var(s, "rgb|rgba|len=10") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "rgb|rgba|len=10") + + s = "this is right" + errs = validate.Var(s, "rgb|rgba|len=13") + Equal(t, errs, nil) + + s = "" + errs = validate.Var(s, "omitempty,rgb|rgba") + Equal(t, errs, nil) + + s = "green" + errs = validate.Var(s, "eq=|eq=blue,rgb|rgba") //should fail on first validation block + NotEqual(t, errs, nil) + ve := errs.(ValidationErrors) + Equal(t, len(ve), 1) + Equal(t, ve[0].Tag(), "eq=|eq=blue") + + s = "this is right, but a blank or isn't" + + PanicMatches(t, func() { _ = validate.Var(s, "rgb||len=13") }, "Invalid validation tag on field ''") + PanicMatches(t, func() { _ = validate.Var(s, "rgb|rgbaa|len=13") }, "Undefined validation function 'rgbaa' on field ''") + + v2 := New() + v2.RegisterTagNameFunc(func(fld reflect.StructField) string { + + name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] + + if name == "-" { + return "" + } + + return name + }) + + type Colors struct { + Fav string `validate:"rgb|rgba" json:"fc"` + } + + c := Colors{Fav: "this ain't right"} + + err := v2.Struct(c) + NotEqual(t, err, nil) + + errs = err.(ValidationErrors) + fe := getError(errs, "Colors.fc", "Colors.Fav") + NotEqual(t, fe, nil) +} + +func TestHsla(t *testing.T) { + + validate := New() + + s := "hsla(360,100%,100%,1)" + errs := validate.Var(s, "hsla") + Equal(t, errs, nil) + + s = "hsla(360,100%,100%,0.5)" + errs = validate.Var(s, "hsla") + Equal(t, errs, nil) + + s = "hsla(0,0%,0%, 0)" + errs = validate.Var(s, "hsla") + Equal(t, errs, nil) + + s = "hsl(361,100%,50%,1)" + errs = validate.Var(s, "hsla") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hsla") + + s = "hsl(361,100%,50%)" + errs = validate.Var(s, "hsla") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hsla") + + s = "hsla(361,100%,50%)" + errs = validate.Var(s, "hsla") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hsla") + + s = "hsla(360,101%,50%)" + errs = validate.Var(s, "hsla") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hsla") + + s = "hsla(360,100%,101%)" + errs = validate.Var(s, "hsla") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hsla") + + i := 1 + errs = validate.Var(i, "hsla") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hsla") +} + +func TestHsl(t *testing.T) { + + validate := New() + + s := "hsl(360,100%,50%)" + errs := validate.Var(s, "hsl") + Equal(t, errs, nil) + + s = "hsl(0,0%,0%)" + errs = validate.Var(s, "hsl") + Equal(t, errs, nil) + + s = "hsl(361,100%,50%)" + errs = validate.Var(s, "hsl") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hsl") + + s = "hsl(361,101%,50%)" + errs = validate.Var(s, "hsl") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hsl") + + s = "hsl(361,100%,101%)" + errs = validate.Var(s, "hsl") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hsl") + + s = "hsl(-10,100%,100%)" + errs = validate.Var(s, "hsl") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hsl") + + i := 1 + errs = validate.Var(i, "hsl") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hsl") +} + +func TestRgba(t *testing.T) { + + validate := New() + + s := "rgba(0,31,255,0.5)" + errs := validate.Var(s, "rgba") + Equal(t, errs, nil) + + s = "rgba(0,31,255,0.12)" + errs = validate.Var(s, "rgba") + Equal(t, errs, nil) + + s = "rgba(12%,55%,100%,0.12)" + errs = validate.Var(s, "rgba") + Equal(t, errs, nil) + + s = "rgba( 0, 31, 255, 0.5)" + errs = validate.Var(s, "rgba") + Equal(t, errs, nil) + + s = "rgba(12%,55,100%,0.12)" + errs = validate.Var(s, "rgba") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "rgba") + + s = "rgb(0, 31, 255)" + errs = validate.Var(s, "rgba") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "rgba") + + s = "rgb(1,349,275,0.5)" + errs = validate.Var(s, "rgba") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "rgba") + + s = "rgb(01,31,255,0.5)" + errs = validate.Var(s, "rgba") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "rgba") + + i := 1 + errs = validate.Var(i, "rgba") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "rgba") +} + +func TestRgb(t *testing.T) { + + validate := New() + + s := "rgb(0,31,255)" + errs := validate.Var(s, "rgb") + Equal(t, errs, nil) + + s = "rgb(0, 31, 255)" + errs = validate.Var(s, "rgb") + Equal(t, errs, nil) + + s = "rgb(10%, 50%, 100%)" + errs = validate.Var(s, "rgb") + Equal(t, errs, nil) + + s = "rgb(10%, 50%, 55)" + errs = validate.Var(s, "rgb") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "rgb") + + s = "rgb(1,349,275)" + errs = validate.Var(s, "rgb") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "rgb") + + s = "rgb(01,31,255)" + errs = validate.Var(s, "rgb") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "rgb") + + s = "rgba(0,31,255)" + errs = validate.Var(s, "rgb") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "rgb") + + i := 1 + errs = validate.Var(i, "rgb") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "rgb") +} + +func TestEmail(t *testing.T) { + + validate := New() + + s := "test@mail.com" + errs := validate.Var(s, "email") + Equal(t, errs, nil) + + s = "Dörte@Sörensen.example.com" + errs = validate.Var(s, "email") + Equal(t, errs, nil) + + s = "θσερ@εχαμπλε.ψομ" + errs = validate.Var(s, "email") + Equal(t, errs, nil) + + s = "юзер@екзампл.ком" + errs = validate.Var(s, "email") + Equal(t, errs, nil) + + s = "उपयोगकर्ता@उदाहरण.कॉम" + errs = validate.Var(s, "email") + Equal(t, errs, nil) + + s = "用户@例子.广告" + errs = validate.Var(s, "email") + Equal(t, errs, nil) + + s = "mail@domain_with_underscores.org" + errs = validate.Var(s, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "email") + + s = "" + errs = validate.Var(s, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "email") + + s = "test@email" + errs = validate.Var(s, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "email") + + s = "test@email." + errs = validate.Var(s, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "email") + + s = "@email.com" + errs = validate.Var(s, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "email") + + s = `"test test"@email.com` + errs = validate.Var(s, "email") + Equal(t, errs, nil) + + s = `"@email.com` + errs = validate.Var(s, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "email") + + i := true + errs = validate.Var(i, "email") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "email") +} + +func TestHexColor(t *testing.T) { + + validate := New() + + s := "#fff" + errs := validate.Var(s, "hexcolor") + Equal(t, errs, nil) + + s = "#c2c2c2" + errs = validate.Var(s, "hexcolor") + Equal(t, errs, nil) + + s = "fff" + errs = validate.Var(s, "hexcolor") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hexcolor") + + s = "fffFF" + errs = validate.Var(s, "hexcolor") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hexcolor") + + i := true + errs = validate.Var(i, "hexcolor") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hexcolor") +} + +func TestHexadecimal(t *testing.T) { + + validate := New() + + s := "ff0044" + errs := validate.Var(s, "hexadecimal") + Equal(t, errs, nil) + + s = "0xff0044" + errs = validate.Var(s, "hexadecimal") + Equal(t, errs, nil) + + s = "0Xff0044" + errs = validate.Var(s, "hexadecimal") + Equal(t, errs, nil) + + s = "abcdefg" + errs = validate.Var(s, "hexadecimal") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hexadecimal") + + i := true + errs = validate.Var(i, "hexadecimal") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "hexadecimal") +} + +func TestNumber(t *testing.T) { + + validate := New() + + s := "1" + errs := validate.Var(s, "number") + Equal(t, errs, nil) + + s = "+1" + errs = validate.Var(s, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "number") + + s = "-1" + errs = validate.Var(s, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "number") + + s = "1.12" + errs = validate.Var(s, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "number") + + s = "+1.12" + errs = validate.Var(s, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "number") + + s = "-1.12" + errs = validate.Var(s, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "number") + + s = "1." + errs = validate.Var(s, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "number") + + s = "1.o" + errs = validate.Var(s, "number") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "number") + + i := 1 + errs = validate.Var(i, "number") + Equal(t, errs, nil) +} + +func TestNumeric(t *testing.T) { + + validate := New() + + s := "1" + errs := validate.Var(s, "numeric") + Equal(t, errs, nil) + + s = "+1" + errs = validate.Var(s, "numeric") + Equal(t, errs, nil) + + s = "-1" + errs = validate.Var(s, "numeric") + Equal(t, errs, nil) + + s = "1.12" + errs = validate.Var(s, "numeric") + Equal(t, errs, nil) + + s = "+1.12" + errs = validate.Var(s, "numeric") + Equal(t, errs, nil) + + s = "-1.12" + errs = validate.Var(s, "numeric") + Equal(t, errs, nil) + + s = "1." + errs = validate.Var(s, "numeric") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "numeric") + + s = "1.o" + errs = validate.Var(s, "numeric") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "numeric") + + i := 1 + errs = validate.Var(i, "numeric") + Equal(t, errs, nil) +} + +func TestAlphaNumeric(t *testing.T) { + + validate := New() + + s := "abcd123" + errs := validate.Var(s, "alphanum") + Equal(t, errs, nil) + + s = "abc!23" + errs = validate.Var(s, "alphanum") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "alphanum") + + errs = validate.Var(1, "alphanum") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "alphanum") +} + +func TestAlpha(t *testing.T) { + + validate := New() + + s := "abcd" + errs := validate.Var(s, "alpha") + Equal(t, errs, nil) + + s = "abc®" + errs = validate.Var(s, "alpha") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "alpha") + + s = "abc÷" + errs = validate.Var(s, "alpha") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "alpha") + + s = "abc1" + errs = validate.Var(s, "alpha") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "alpha") + + s = "this is a test string" + errs = validate.Var(s, "alpha") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "alpha") + + errs = validate.Var(1, "alpha") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "alpha") + +} + +func TestStructStringValidation(t *testing.T) { + + validate := New() + + tSuccess := &TestString{ + Required: "Required", + Len: "length==10", + Min: "min=1", + Max: "1234567890", + MinMax: "12345", + Lt: "012345678", + Lte: "0123456789", + Gt: "01234567890", + Gte: "0123456789", + OmitEmpty: "", + Sub: &SubTest{ + Test: "1", + }, + SubIgnore: &SubTest{ + Test: "", + }, + Anonymous: struct { + A string `validate:"required"` + }{ + A: "1", + }, + Iface: &Impl{ + F: "123", + }, + } + + errs := validate.Struct(tSuccess) + Equal(t, errs, nil) + + tFail := &TestString{ + Required: "", + Len: "", + Min: "", + Max: "12345678901", + MinMax: "", + Lt: "0123456789", + Lte: "01234567890", + Gt: "1", + Gte: "1", + OmitEmpty: "12345678901", + Sub: &SubTest{ + Test: "", + }, + Anonymous: struct { + A string `validate:"required"` + }{ + A: "", + }, + Iface: &Impl{ + F: "12", + }, + } + + errs = validate.Struct(tFail) + + // Assert Top Level + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 13) + + // Assert Fields + AssertError(t, errs, "TestString.Required", "TestString.Required", "Required", "Required", "required") + AssertError(t, errs, "TestString.Len", "TestString.Len", "Len", "Len", "len") + AssertError(t, errs, "TestString.Min", "TestString.Min", "Min", "Min", "min") + AssertError(t, errs, "TestString.Max", "TestString.Max", "Max", "Max", "max") + AssertError(t, errs, "TestString.MinMax", "TestString.MinMax", "MinMax", "MinMax", "min") + AssertError(t, errs, "TestString.Lt", "TestString.Lt", "Lt", "Lt", "lt") + AssertError(t, errs, "TestString.Lte", "TestString.Lte", "Lte", "Lte", "lte") + AssertError(t, errs, "TestString.Gt", "TestString.Gt", "Gt", "Gt", "gt") + AssertError(t, errs, "TestString.Gte", "TestString.Gte", "Gte", "Gte", "gte") + AssertError(t, errs, "TestString.OmitEmpty", "TestString.OmitEmpty", "OmitEmpty", "OmitEmpty", "max") + + // Nested Struct Field Errs + AssertError(t, errs, "TestString.Anonymous.A", "TestString.Anonymous.A", "A", "A", "required") + AssertError(t, errs, "TestString.Sub.Test", "TestString.Sub.Test", "Test", "Test", "required") + AssertError(t, errs, "TestString.Iface.F", "TestString.Iface.F", "F", "F", "len") +} + +func TestStructInt32Validation(t *testing.T) { + + type TestInt32 struct { + Required int `validate:"required"` + Len int `validate:"len=10"` + Min int `validate:"min=1"` + Max int `validate:"max=10"` + MinMax int `validate:"min=1,max=10"` + Lt int `validate:"lt=10"` + Lte int `validate:"lte=10"` + Gt int `validate:"gt=10"` + Gte int `validate:"gte=10"` + OmitEmpty int `validate:"omitempty,min=1,max=10"` + } + + tSuccess := &TestInt32{ + Required: 1, + Len: 10, + Min: 1, + Max: 10, + MinMax: 5, + Lt: 9, + Lte: 10, + Gt: 11, + Gte: 10, + OmitEmpty: 0, + } + + validate := New() + errs := validate.Struct(tSuccess) + Equal(t, errs, nil) + + tFail := &TestInt32{ + Required: 0, + Len: 11, + Min: -1, + Max: 11, + MinMax: -1, + Lt: 10, + Lte: 11, + Gt: 10, + Gte: 9, + OmitEmpty: 11, + } + + errs = validate.Struct(tFail) + + // Assert Top Level + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 10) + + // Assert Fields + AssertError(t, errs, "TestInt32.Required", "TestInt32.Required", "Required", "Required", "required") + AssertError(t, errs, "TestInt32.Len", "TestInt32.Len", "Len", "Len", "len") + AssertError(t, errs, "TestInt32.Min", "TestInt32.Min", "Min", "Min", "min") + AssertError(t, errs, "TestInt32.Max", "TestInt32.Max", "Max", "Max", "max") + AssertError(t, errs, "TestInt32.MinMax", "TestInt32.MinMax", "MinMax", "MinMax", "min") + AssertError(t, errs, "TestInt32.Lt", "TestInt32.Lt", "Lt", "Lt", "lt") + AssertError(t, errs, "TestInt32.Lte", "TestInt32.Lte", "Lte", "Lte", "lte") + AssertError(t, errs, "TestInt32.Gt", "TestInt32.Gt", "Gt", "Gt", "gt") + AssertError(t, errs, "TestInt32.Gte", "TestInt32.Gte", "Gte", "Gte", "gte") + AssertError(t, errs, "TestInt32.OmitEmpty", "TestInt32.OmitEmpty", "OmitEmpty", "OmitEmpty", "max") +} + +func TestStructUint64Validation(t *testing.T) { + + validate := New() + + tSuccess := &TestUint64{ + Required: 1, + Len: 10, + Min: 1, + Max: 10, + MinMax: 5, + OmitEmpty: 0, + } + + errs := validate.Struct(tSuccess) + Equal(t, errs, nil) + + tFail := &TestUint64{ + Required: 0, + Len: 11, + Min: 0, + Max: 11, + MinMax: 0, + OmitEmpty: 11, + } + + errs = validate.Struct(tFail) + + // Assert Top Level + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 6) + + // Assert Fields + AssertError(t, errs, "TestUint64.Required", "TestUint64.Required", "Required", "Required", "required") + AssertError(t, errs, "TestUint64.Len", "TestUint64.Len", "Len", "Len", "len") + AssertError(t, errs, "TestUint64.Min", "TestUint64.Min", "Min", "Min", "min") + AssertError(t, errs, "TestUint64.Max", "TestUint64.Max", "Max", "Max", "max") + AssertError(t, errs, "TestUint64.MinMax", "TestUint64.MinMax", "MinMax", "MinMax", "min") + AssertError(t, errs, "TestUint64.OmitEmpty", "TestUint64.OmitEmpty", "OmitEmpty", "OmitEmpty", "max") +} + +func TestStructFloat64Validation(t *testing.T) { + + validate := New() + + tSuccess := &TestFloat64{ + Required: 1, + Len: 10, + Min: 1, + Max: 10, + MinMax: 5, + OmitEmpty: 0, + } + + errs := validate.Struct(tSuccess) + Equal(t, errs, nil) + + tFail := &TestFloat64{ + Required: 0, + Len: 11, + Min: 0, + Max: 11, + MinMax: 0, + OmitEmpty: 11, + } + + errs = validate.Struct(tFail) + + // Assert Top Level + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 6) + + // Assert Fields + AssertError(t, errs, "TestFloat64.Required", "TestFloat64.Required", "Required", "Required", "required") + AssertError(t, errs, "TestFloat64.Len", "TestFloat64.Len", "Len", "Len", "len") + AssertError(t, errs, "TestFloat64.Min", "TestFloat64.Min", "Min", "Min", "min") + AssertError(t, errs, "TestFloat64.Max", "TestFloat64.Max", "Max", "Max", "max") + AssertError(t, errs, "TestFloat64.MinMax", "TestFloat64.MinMax", "MinMax", "MinMax", "min") + AssertError(t, errs, "TestFloat64.OmitEmpty", "TestFloat64.OmitEmpty", "OmitEmpty", "OmitEmpty", "max") +} + +func TestStructSliceValidation(t *testing.T) { + + validate := New() + + tSuccess := &TestSlice{ + Required: []int{1}, + Len: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, + Min: []int{1, 2}, + Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, + MinMax: []int{1, 2, 3, 4, 5}, + OmitEmpty: nil, + } + + errs := validate.Struct(tSuccess) + Equal(t, errs, nil) + + tFail := &TestSlice{ + Required: nil, + Len: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1}, + Min: []int{}, + Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1}, + MinMax: []int{}, + OmitEmpty: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1}, + } + + errs = validate.Struct(tFail) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 6) + + // Assert Field Errors + AssertError(t, errs, "TestSlice.Required", "TestSlice.Required", "Required", "Required", "required") + AssertError(t, errs, "TestSlice.Len", "TestSlice.Len", "Len", "Len", "len") + AssertError(t, errs, "TestSlice.Min", "TestSlice.Min", "Min", "Min", "min") + AssertError(t, errs, "TestSlice.Max", "TestSlice.Max", "Max", "Max", "max") + AssertError(t, errs, "TestSlice.MinMax", "TestSlice.MinMax", "MinMax", "MinMax", "min") + AssertError(t, errs, "TestSlice.OmitEmpty", "TestSlice.OmitEmpty", "OmitEmpty", "OmitEmpty", "max") + + fe := getError(errs, "TestSlice.Len", "TestSlice.Len") + NotEqual(t, fe, nil) + Equal(t, fe.Field(), "Len") + Equal(t, fe.StructField(), "Len") + Equal(t, fe.Namespace(), "TestSlice.Len") + Equal(t, fe.StructNamespace(), "TestSlice.Len") + Equal(t, fe.Tag(), "len") + Equal(t, fe.ActualTag(), "len") + Equal(t, fe.Param(), "10") + Equal(t, fe.Kind(), reflect.Slice) + Equal(t, fe.Type(), reflect.TypeOf([]int{})) + + _, ok := fe.Value().([]int) + Equal(t, ok, true) + +} + +func TestInvalidStruct(t *testing.T) { + + validate := New() + + s := &SubTest{ + Test: "1", + } + + err := validate.Struct(s.Test) + NotEqual(t, err, nil) + Equal(t, err.Error(), "validator: (nil string)") + + err = validate.Struct(nil) + NotEqual(t, err, nil) + Equal(t, err.Error(), "validator: (nil)") + + err = validate.StructPartial(nil, "SubTest.Test") + NotEqual(t, err, nil) + Equal(t, err.Error(), "validator: (nil)") + + err = validate.StructExcept(nil, "SubTest.Test") + NotEqual(t, err, nil) + Equal(t, err.Error(), "validator: (nil)") +} + +func TestInvalidValidatorFunction(t *testing.T) { + + validate := New() + + s := &SubTest{ + Test: "1", + } + + PanicMatches(t, func() { _ = validate.Var(s.Test, "zzxxBadFunction") }, "Undefined validation function 'zzxxBadFunction' on field ''") +} + +func TestCustomFieldName(t *testing.T) { + + validate := New() + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("schema"), ",", 2)[0] + + if name == "-" { + return "" + } + + return name + }) + + type A struct { + B string `schema:"b" validate:"required"` + C string `schema:"c" validate:"required"` + D []bool `schema:"d" validate:"required"` + E string `schema:"-" validate:"required"` + } + + a := &A{} + + err := validate.Struct(a) + NotEqual(t, err, nil) + + errs := err.(ValidationErrors) + Equal(t, len(errs), 4) + Equal(t, getError(errs, "A.b", "A.B").Field(), "b") + Equal(t, getError(errs, "A.c", "A.C").Field(), "c") + Equal(t, getError(errs, "A.d", "A.D").Field(), "d") + Equal(t, getError(errs, "A.E", "A.E").Field(), "E") + + v2 := New() + err = v2.Struct(a) + NotEqual(t, err, nil) + + errs = err.(ValidationErrors) + Equal(t, len(errs), 4) + Equal(t, getError(errs, "A.B", "A.B").Field(), "B") + Equal(t, getError(errs, "A.C", "A.C").Field(), "C") + Equal(t, getError(errs, "A.D", "A.D").Field(), "D") + Equal(t, getError(errs, "A.E", "A.E").Field(), "E") +} + +func TestMutipleRecursiveExtractStructCache(t *testing.T) { + + validate := New() + + type Recursive struct { + Field *string `validate:"required,len=5,ne=string"` + } + + var test Recursive + + current := reflect.ValueOf(test) + name := "Recursive" + proceed := make(chan struct{}) + + sc := validate.extractStructCache(current, name) + ptr := fmt.Sprintf("%p", sc) + + for i := 0; i < 100; i++ { + + go func() { + <-proceed + sc := validate.extractStructCache(current, name) + Equal(t, ptr, fmt.Sprintf("%p", sc)) + }() + } + + close(proceed) +} + +// Thanks @robbrockbank, see https://github.com/go-playground/validator/issues/249 +func TestPointerAndOmitEmpty(t *testing.T) { + + validate := New() + + type Test struct { + MyInt *int `validate:"omitempty,gte=2,lte=255"` + } + + val1 := 0 + val2 := 256 + + t1 := Test{MyInt: &val1} // This should fail validation on gte because value is 0 + t2 := Test{MyInt: &val2} // This should fail validate on lte because value is 256 + t3 := Test{MyInt: nil} // This should succeed validation because pointer is nil + + errs := validate.Struct(t1) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.MyInt", "Test.MyInt", "MyInt", "MyInt", "gte") + + errs = validate.Struct(t2) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.MyInt", "Test.MyInt", "MyInt", "MyInt", "lte") + + errs = validate.Struct(t3) + Equal(t, errs, nil) + + type TestIface struct { + MyInt interface{} `validate:"omitempty,gte=2,lte=255"` + } + + ti1 := TestIface{MyInt: &val1} // This should fail validation on gte because value is 0 + ti2 := TestIface{MyInt: &val2} // This should fail validate on lte because value is 256 + ti3 := TestIface{MyInt: nil} // This should succeed validation because pointer is nil + + errs = validate.Struct(ti1) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestIface.MyInt", "TestIface.MyInt", "MyInt", "MyInt", "gte") + + errs = validate.Struct(ti2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestIface.MyInt", "TestIface.MyInt", "MyInt", "MyInt", "lte") + + errs = validate.Struct(ti3) + Equal(t, errs, nil) +} + +func TestRequired(t *testing.T) { + + validate := New() + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] + + if name == "-" { + return "" + } + + return name + }) + + type Test struct { + Value interface{} `validate:"required"` + } + + var test Test + + err := validate.Struct(test) + NotEqual(t, err, nil) + AssertError(t, err.(ValidationErrors), "Test.Value", "Test.Value", "Value", "Value", "required") +} + +func TestBoolEqual(t *testing.T) { + + validate := New() + + type Test struct { + Value bool `validate:"eq=true"` + } + + var test Test + + err := validate.Struct(test) + NotEqual(t, err, nil) + AssertError(t, err.(ValidationErrors), "Test.Value", "Test.Value", "Value", "Value", "eq") + + test.Value = true + err = validate.Struct(test) + Equal(t, err, nil) +} + +func TestTranslations(t *testing.T) { + en := en.New() + uni := ut.New(en, en, fr.New()) + + trans, _ := uni.GetTranslator("en") + fr, _ := uni.GetTranslator("fr") + + validate := New() + err := validate.RegisterTranslation("required", trans, + func(ut ut.Translator) (err error) { + + // using this stype because multiple translation may have to be added for the full translation + if err = ut.Add("required", "{0} is a required field", false); err != nil { + return + } + + return + + }, func(ut ut.Translator, fe FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field()) + if err != nil { + fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError)) + return fe.(*fieldError).Error() + } + + return t + }) + Equal(t, err, nil) + + err = validate.RegisterTranslation("required", fr, + func(ut ut.Translator) (err error) { + + // using this stype because multiple translation may have to be added for the full translation + if err = ut.Add("required", "{0} est un champ obligatoire", false); err != nil { + return + } + + return + + }, func(ut ut.Translator, fe FieldError) string { + + t, transErr := ut.T(fe.Tag(), fe.Field()) + if transErr != nil { + fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError)) + return fe.(*fieldError).Error() + } + + return t + }) + + Equal(t, err, nil) + + type Test struct { + Value interface{} `validate:"required"` + } + + var test Test + + err = validate.Struct(test) + NotEqual(t, err, nil) + + errs := err.(ValidationErrors) + Equal(t, len(errs), 1) + + fe := errs[0] + Equal(t, fe.Tag(), "required") + Equal(t, fe.Namespace(), "Test.Value") + Equal(t, fe.Translate(trans), fmt.Sprintf("%s is a required field", fe.Field())) + Equal(t, fe.Translate(fr), fmt.Sprintf("%s est un champ obligatoire", fe.Field())) + + nl := nl.New() + uni2 := ut.New(nl, nl) + trans2, _ := uni2.GetTranslator("nl") + Equal(t, fe.Translate(trans2), "Key: 'Test.Value' Error:Field validation for 'Value' failed on the 'required' tag") + + terrs := errs.Translate(trans) + Equal(t, len(terrs), 1) + + v, ok := terrs["Test.Value"] + Equal(t, ok, true) + Equal(t, v, fmt.Sprintf("%s is a required field", fe.Field())) + + terrs = errs.Translate(fr) + Equal(t, len(terrs), 1) + + v, ok = terrs["Test.Value"] + Equal(t, ok, true) + Equal(t, v, fmt.Sprintf("%s est un champ obligatoire", fe.Field())) + + type Test2 struct { + Value string `validate:"gt=1"` + } + + var t2 Test2 + + err = validate.Struct(t2) + NotEqual(t, err, nil) + + errs = err.(ValidationErrors) + Equal(t, len(errs), 1) + + fe = errs[0] + Equal(t, fe.Tag(), "gt") + Equal(t, fe.Namespace(), "Test2.Value") + Equal(t, fe.Translate(trans), "Key: 'Test2.Value' Error:Field validation for 'Value' failed on the 'gt' tag") +} + +func TestTranslationErrors(t *testing.T) { + en := en.New() + uni := ut.New(en, en, fr.New()) + + trans, _ := uni.GetTranslator("en") + err := trans.Add("required", "{0} is a required field", false) // using translator outside of validator also + Equal(t, err, nil) + + validate := New() + err = validate.RegisterTranslation("required", trans, + func(ut ut.Translator) (err error) { + + // using this stype because multiple translation may have to be added for the full translation + if err = ut.Add("required", "{0} is a required field", false); err != nil { + return + } + + return + + }, func(ut ut.Translator, fe FieldError) string { + + t, err := ut.T(fe.Tag(), fe.Field()) + if err != nil { + fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError)) + return fe.(*fieldError).Error() + } + + return t + }) + + NotEqual(t, err, nil) + Equal(t, err.Error(), "error: conflicting key 'required' rule 'Unknown' with text '{0} is a required field' for locale 'en', value being ignored") +} + +func TestStructFiltered(t *testing.T) { + + p1 := func(ns []byte) bool { + if bytes.HasSuffix(ns, []byte("NoTag")) || bytes.HasSuffix(ns, []byte("Required")) { + return false + } + + return true + } + + p2 := func(ns []byte) bool { + if bytes.HasSuffix(ns, []byte("SubSlice[0].Test")) || + bytes.HasSuffix(ns, []byte("SubSlice[0]")) || + bytes.HasSuffix(ns, []byte("SubSlice")) || + bytes.HasSuffix(ns, []byte("Sub")) || + bytes.HasSuffix(ns, []byte("SubIgnore")) || + bytes.HasSuffix(ns, []byte("Anonymous")) || + bytes.HasSuffix(ns, []byte("Anonymous.A")) { + return false + } + + return true + } + + p3 := func(ns []byte) bool { + return !bytes.HasSuffix(ns, []byte("SubTest.Test")) + } + + // p4 := []string{ + // "A", + // } + + tPartial := &TestPartial{ + NoTag: "NoTag", + Required: "Required", + + SubSlice: []*SubTest{ + { + + Test: "Required", + }, + { + + Test: "Required", + }, + }, + + Sub: &SubTest{ + Test: "1", + }, + SubIgnore: &SubTest{ + Test: "", + }, + Anonymous: struct { + A string `validate:"required"` + ASubSlice []*SubTest `validate:"required,dive"` + SubAnonStruct []struct { + Test string `validate:"required"` + OtherTest string `validate:"required"` + } `validate:"required,dive"` + }{ + A: "1", + ASubSlice: []*SubTest{ + { + Test: "Required", + }, + { + Test: "Required", + }, + }, + + SubAnonStruct: []struct { + Test string `validate:"required"` + OtherTest string `validate:"required"` + }{ + {"Required", "RequiredOther"}, + {"Required", "RequiredOther"}, + }, + }, + } + + validate := New() + + // the following should all return no errors as everything is valid in + // the default state + errs := validate.StructFilteredCtx(context.Background(), tPartial, p1) + Equal(t, errs, nil) + + errs = validate.StructFiltered(tPartial, p2) + Equal(t, errs, nil) + + // this isn't really a robust test, but is ment to illustrate the ANON CASE below + errs = validate.StructFiltered(tPartial.SubSlice[0], p3) + Equal(t, errs, nil) + + // mod tParial for required feild and re-test making sure invalid fields are NOT required: + tPartial.Required = "" + + // inversion and retesting Partial to generate failures: + errs = validate.StructFiltered(tPartial, p1) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Required", "TestPartial.Required", "Required", "Required", "required") + + // reset Required field, and set nested struct + tPartial.Required = "Required" + tPartial.Anonymous.A = "" + + // will pass as unset feilds is not going to be tested + errs = validate.StructFiltered(tPartial, p1) + Equal(t, errs, nil) + + // will fail as unset feild is tested + errs = validate.StructFiltered(tPartial, p2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Anonymous.A", "TestPartial.Anonymous.A", "A", "A", "required") + + // reset nested struct and unset struct in slice + tPartial.Anonymous.A = "Required" + tPartial.SubSlice[0].Test = "" + + // these will pass as unset item is NOT tested + errs = validate.StructFiltered(tPartial, p1) + Equal(t, errs, nil) + + errs = validate.StructFiltered(tPartial, p2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "TestPartial.SubSlice[0].Test", "Test", "Test", "required") + Equal(t, len(errs.(ValidationErrors)), 1) + + // Unset second slice member concurrently to test dive behavior: + tPartial.SubSlice[1].Test = "" + + errs = validate.StructFiltered(tPartial, p1) + Equal(t, errs, nil) + + errs = validate.StructFiltered(tPartial, p2) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "TestPartial.SubSlice[0].Test", "Test", "Test", "required") + + // reset struct in slice, and unset struct in slice in unset posistion + tPartial.SubSlice[0].Test = "Required" + + // these will pass as the unset item is NOT tested + errs = validate.StructFiltered(tPartial, p1) + Equal(t, errs, nil) + + errs = validate.StructFiltered(tPartial, p2) + Equal(t, errs, nil) + + tPartial.SubSlice[1].Test = "Required" + tPartial.Anonymous.SubAnonStruct[0].Test = "" + + // these will pass as the unset item is NOT tested + errs = validate.StructFiltered(tPartial, p1) + Equal(t, errs, nil) + + errs = validate.StructFiltered(tPartial, p2) + Equal(t, errs, nil) + + dt := time.Now() + err := validate.StructFiltered(&dt, func(ns []byte) bool { return true }) + NotEqual(t, err, nil) + Equal(t, err.Error(), "validator: (nil *time.Time)") +} + +func TestRequiredPtr(t *testing.T) { + + type Test struct { + Bool *bool `validate:"required"` + } + + validate := New() + + f := false + + test := Test{ + Bool: &f, + } + + err := validate.Struct(test) + Equal(t, err, nil) + + tr := true + + test.Bool = &tr + + err = validate.Struct(test) + Equal(t, err, nil) + + test.Bool = nil + + err = validate.Struct(test) + NotEqual(t, err, nil) + + errs, ok := err.(ValidationErrors) + Equal(t, ok, true) + Equal(t, len(errs), 1) + AssertError(t, errs, "Test.Bool", "Test.Bool", "Bool", "Bool", "required") + + type Test2 struct { + Bool bool `validate:"required"` + } + + var test2 Test2 + + err = validate.Struct(test2) + NotEqual(t, err, nil) + + errs, ok = err.(ValidationErrors) + Equal(t, ok, true) + Equal(t, len(errs), 1) + AssertError(t, errs, "Test2.Bool", "Test2.Bool", "Bool", "Bool", "required") + + test2.Bool = true + + err = validate.Struct(test2) + Equal(t, err, nil) + + type Test3 struct { + Arr []string `validate:"required"` + } + + var test3 Test3 + + err = validate.Struct(test3) + NotEqual(t, err, nil) + + errs, ok = err.(ValidationErrors) + Equal(t, ok, true) + Equal(t, len(errs), 1) + AssertError(t, errs, "Test3.Arr", "Test3.Arr", "Arr", "Arr", "required") + + test3.Arr = make([]string, 0) + + err = validate.Struct(test3) + Equal(t, err, nil) + + type Test4 struct { + Arr *[]string `validate:"required"` // I know I know pointer to array, just making sure validation works as expected... + } + + var test4 Test4 + + err = validate.Struct(test4) + NotEqual(t, err, nil) + + errs, ok = err.(ValidationErrors) + Equal(t, ok, true) + Equal(t, len(errs), 1) + AssertError(t, errs, "Test4.Arr", "Test4.Arr", "Arr", "Arr", "required") + + arr := make([]string, 0) + test4.Arr = &arr + + err = validate.Struct(test4) + Equal(t, err, nil) +} + +func TestAlphaUnicodeValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"abc", true}, + {"this is a test string", false}, + {"这是一个测试字符串", true}, + {"123", false}, + {"<>@;.-=", false}, + {"ひらがな・カタカナ、.漢字", false}, + {"あいうえおfoobar", true}, + {"test@example.com", false}, + {"1234abcDE", false}, + {"カタカナ", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "alphaunicode") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d Alpha Unicode failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d Alpha Unicode failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "alphaunicode" { + t.Fatalf("Index: %d Alpha Unicode failed Error: %s", i, errs) + } + } + } + } +} + +func TestAlphanumericUnicodeValidation(t *testing.T) { + + tests := []struct { + param string + expected bool + }{ + {"", false}, + {"abc", true}, + {"this is a test string", false}, + {"这是一个测试字符串", true}, + {"\u0031\u0032\u0033", true}, // unicode 5 + {"123", true}, + {"<>@;.-=", false}, + {"ひらがな・カタカナ、.漢字", false}, + {"あいうえおfoobar", true}, + {"test@example.com", false}, + {"1234abcDE", true}, + {"カタカナ", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "alphanumunicode") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d Alphanum Unicode failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d Alphanum Unicode failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "alphanumunicode" { + t.Fatalf("Index: %d Alphanum Unicode failed Error: %s", i, errs) + } + } + } + } +} + +func TestArrayStructNamespace(t *testing.T) { + + validate := New() + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] + + if name == "-" { + return "" + } + + return name + }) + + type child struct { + Name string `json:"name" validate:"required"` + } + var input struct { + Children []child `json:"children" validate:"required,gt=0,dive"` + } + input.Children = []child{{"ok"}, {""}} + + errs := validate.Struct(input) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 1) + AssertError(t, errs, "children[1].name", "Children[1].Name", "name", "Name", "required") +} + +func TestMapStructNamespace(t *testing.T) { + + validate := New() + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] + + if name == "-" { + return "" + } + + return name + }) + + type child struct { + Name string `json:"name" validate:"required"` + } + var input struct { + Children map[int]child `json:"children" validate:"required,gt=0,dive"` + } + input.Children = map[int]child{ + 0: {Name: "ok"}, + 1: {Name: ""}, + } + + errs := validate.Struct(input) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 1) + AssertError(t, errs, "children[1].name", "Children[1].Name", "name", "Name", "required") +} + +func TestFieldLevelName(t *testing.T) { + type Test struct { + String string `validate:"custom1" json:"json1"` + Array []string `validate:"dive,custom2" json:"json2"` + Map map[string]string `validate:"dive,custom3" json:"json3"` + Array2 []string `validate:"custom4" json:"json4"` + Map2 map[string]string `validate:"custom5" json:"json5"` + } + + var res1, res2, res3, res4, res5, alt1, alt2, alt3, alt4, alt5 string + validate := New() + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] + + if name == "-" { + return "" + } + + return name + }) + err := validate.RegisterValidation("custom1", func(fl FieldLevel) bool { + res1 = fl.FieldName() + alt1 = fl.StructFieldName() + return true + }) + Equal(t, err, nil) + + err = validate.RegisterValidation("custom2", func(fl FieldLevel) bool { + res2 = fl.FieldName() + alt2 = fl.StructFieldName() + return true + }) + Equal(t, err, nil) + + err = validate.RegisterValidation("custom3", func(fl FieldLevel) bool { + res3 = fl.FieldName() + alt3 = fl.StructFieldName() + return true + }) + Equal(t, err, nil) + + err = validate.RegisterValidation("custom4", func(fl FieldLevel) bool { + res4 = fl.FieldName() + alt4 = fl.StructFieldName() + return true + }) + Equal(t, err, nil) + + err = validate.RegisterValidation("custom5", func(fl FieldLevel) bool { + res5 = fl.FieldName() + alt5 = fl.StructFieldName() + return true + }) + Equal(t, err, nil) + + test := Test{ + String: "test", + Array: []string{"1"}, + Map: map[string]string{"test": "test"}, + } + + errs := validate.Struct(test) + Equal(t, errs, nil) + Equal(t, res1, "json1") + Equal(t, alt1, "String") + Equal(t, res2, "json2[0]") + Equal(t, alt2, "Array[0]") + Equal(t, res3, "json3[test]") + Equal(t, alt3, "Map[test]") + Equal(t, res4, "json4") + Equal(t, alt4, "Array2") + Equal(t, res5, "json5") + Equal(t, alt5, "Map2") +} + +func TestValidateStructRegisterCtx(t *testing.T) { + var ctxVal string + + fnCtx := func(ctx context.Context, fl FieldLevel) bool { + ctxVal = ctx.Value(&ctxVal).(string) + return true + } + + var ctxSlVal string + slFn := func(ctx context.Context, sl StructLevel) { + ctxSlVal = ctx.Value(&ctxSlVal).(string) + } + + type Test struct { + Field string `validate:"val"` + } + + var tst Test + + validate := New() + err := validate.RegisterValidationCtx("val", fnCtx) + Equal(t, err, nil) + + validate.RegisterStructValidationCtx(slFn, Test{}) + + ctx := context.WithValue(context.Background(), &ctxVal, "testval") + ctx = context.WithValue(ctx, &ctxSlVal, "slVal") + errs := validate.StructCtx(ctx, tst) + Equal(t, errs, nil) + Equal(t, ctxVal, "testval") + Equal(t, ctxSlVal, "slVal") +} + +func TestHostnameRFC952Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"test.example.com", true}, + {"example.com", true}, + {"example24.com", true}, + {"test.example24.com", true}, + {"test24.example24.com", true}, + {"example", true}, + {"EXAMPLE", true}, + {"1.foo.com", false}, + {"test.example.com.", false}, + {"example.com.", false}, + {"example24.com.", false}, + {"test.example24.com.", false}, + {"test24.example24.com.", false}, + {"example.", false}, + {"192.168.0.1", false}, + {"email@example.com", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652", false}, + {"2001:cdba:0:0:0:0:3257:9652", false}, + {"2001:cdba::3257:9652", false}, + {"example..........com", false}, + {"1234", false}, + {"abc1234", true}, + {"example. com", false}, + {"ex ample.com", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "hostname") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d hostname failed Error: %v", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d hostname failed Error: %v", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "hostname" { + t.Fatalf("Index: %d hostname failed Error: %v", i, errs) + } + } + } + } +} + +func TestHostnameRFC1123Validation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"test.example.com", true}, + {"example.com", true}, + {"example24.com", true}, + {"test.example24.com", true}, + {"test24.example24.com", true}, + {"example", true}, + {"1.foo.com", true}, + {"test.example.com.", false}, + {"example.com.", false}, + {"example24.com.", false}, + {"test.example24.com.", false}, + {"test24.example24.com.", false}, + {"example.", false}, + {"192.168.0.1", true}, + {"email@example.com", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652", false}, + {"2001:cdba:0:0:0:0:3257:9652", false}, + {"2001:cdba::3257:9652", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "hostname_rfc1123") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Hostname: %v failed Error: %v", test, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Hostname: %v failed Error: %v", test, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "hostname_rfc1123" { + t.Fatalf("Hostname: %v failed Error: %v", i, errs) + } + } + } + } +} + +func TestHostnameRFC1123AliasValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"test.example.com", true}, + {"example.com", true}, + {"example24.com", true}, + {"test.example24.com", true}, + {"test24.example24.com", true}, + {"example", true}, + {"1.foo.com", true}, + {"test.example.com.", false}, + {"example.com.", false}, + {"example24.com.", false}, + {"test.example24.com.", false}, + {"test24.example24.com.", false}, + {"example.", false}, + {"192.168.0.1", true}, + {"email@example.com", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652", false}, + {"2001:cdba:0:0:0:0:3257:9652", false}, + {"2001:cdba::3257:9652", false}, + } + + validate := New() + validate.RegisterAlias("hostname", "hostname_rfc1123") + + for i, test := range tests { + + errs := validate.Var(test.param, "hostname") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d hostname failed Error: %v", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d hostname failed Error: %v", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "hostname" { + t.Fatalf("Index: %d hostname failed Error: %v", i, errs) + } + } + } + } +} + +func TestFQDNValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"test.example.com", true}, + {"example.com", true}, + {"example24.com", true}, + {"test.example24.com", true}, + {"test24.example24.com", true}, + {"test.example.com.", true}, + {"example.com.", true}, + {"example24.com.", true}, + {"test.example24.com.", true}, + {"test24.example24.com.", true}, + {"24.example24.com", true}, + {"test.24.example.com", true}, + {"test24.example24.com..", false}, + {"example", false}, + {"192.168.0.1", false}, + {"email@example.com", false}, + {"2001:cdba:0000:0000:0000:0000:3257:9652", false}, + {"2001:cdba:0:0:0:0:3257:9652", false}, + {"2001:cdba::3257:9652", false}, + {"", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "fqdn") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d fqdn failed Error: %v", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d fqdn failed Error: %v", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "fqdn" { + t.Fatalf("Index: %d fqdn failed Error: %v", i, errs) + } + } + } + } +} + +func TestIsDefault(t *testing.T) { + validate := New() + + type Inner struct { + String string `validate:"isdefault"` + } + type Test struct { + String string `validate:"isdefault"` + Inner *Inner `validate:"isdefault"` + } + + var tt Test + + errs := validate.Struct(tt) + Equal(t, errs, nil) + + tt.Inner = &Inner{String: ""} + errs = validate.Struct(tt) + NotEqual(t, errs, nil) + + fe := errs.(ValidationErrors)[0] + Equal(t, fe.Field(), "Inner") + Equal(t, fe.Namespace(), "Test.Inner") + Equal(t, fe.Tag(), "isdefault") + + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] + if name == "-" { + return "" + } + return name + }) + + type Inner2 struct { + String string `validate:"isdefault"` + } + + type Test2 struct { + Inner Inner2 `validate:"isdefault" json:"inner"` + } + + var t2 Test2 + errs = validate.Struct(t2) + Equal(t, errs, nil) + + t2.Inner.String = "Changed" + errs = validate.Struct(t2) + NotEqual(t, errs, nil) + + fe = errs.(ValidationErrors)[0] + Equal(t, fe.Field(), "inner") + Equal(t, fe.Namespace(), "Test2.inner") + Equal(t, fe.Tag(), "isdefault") +} + +func TestUniqueValidation(t *testing.T) { + tests := []struct { + param interface{} + expected bool + }{ + // Arrays + {[2]string{"a", "b"}, true}, + {[2]int{1, 2}, true}, + {[2]float64{1, 2}, true}, + {[2]interface{}{"a", "b"}, true}, + {[2]interface{}{"a", 1}, true}, + {[2]float64{1, 1}, false}, + {[2]int{1, 1}, false}, + {[2]string{"a", "a"}, false}, + {[2]interface{}{"a", "a"}, false}, + {[4]interface{}{"a", 1, "b", 1}, false}, + {[2]*string{stringPtr("a"), stringPtr("b")}, true}, + {[2]*int{intPtr(1), intPtr(2)}, true}, + {[2]*float64{float64Ptr(1), float64Ptr(2)}, true}, + {[2]*string{stringPtr("a"), stringPtr("a")}, false}, + {[2]*float64{float64Ptr(1), float64Ptr(1)}, false}, + {[2]*int{intPtr(1), intPtr(1)}, false}, + // Slices + {[]string{"a", "b"}, true}, + {[]int{1, 2}, true}, + {[]float64{1, 2}, true}, + {[]interface{}{"a", "b"}, true}, + {[]interface{}{"a", 1}, true}, + {[]float64{1, 1}, false}, + {[]int{1, 1}, false}, + {[]string{"a", "a"}, false}, + {[]interface{}{"a", "a"}, false}, + {[]interface{}{"a", 1, "b", 1}, false}, + {[]*string{stringPtr("a"), stringPtr("b")}, true}, + {[]*int{intPtr(1), intPtr(2)}, true}, + {[]*float64{float64Ptr(1), float64Ptr(2)}, true}, + {[]*string{stringPtr("a"), stringPtr("a")}, false}, + {[]*float64{float64Ptr(1), float64Ptr(1)}, false}, + {[]*int{intPtr(1), intPtr(1)}, false}, + // Maps + {map[string]string{"one": "a", "two": "b"}, true}, + {map[string]int{"one": 1, "two": 2}, true}, + {map[string]float64{"one": 1, "two": 2}, true}, + {map[string]interface{}{"one": "a", "two": "b"}, true}, + {map[string]interface{}{"one": "a", "two": 1}, true}, + {map[string]float64{"one": 1, "two": 1}, false}, + {map[string]int{"one": 1, "two": 1}, false}, + {map[string]string{"one": "a", "two": "a"}, false}, + {map[string]interface{}{"one": "a", "two": "a"}, false}, + {map[string]interface{}{"one": "a", "two": 1, "three": "b", "four": 1}, false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "unique") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d unique failed Error: %v", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d unique failed Error: %v", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "unique" { + t.Fatalf("Index: %d unique failed Error: %v", i, errs) + } + } + } + } + PanicMatches(t, func() { _ = validate.Var(1.0, "unique") }, "Bad field type float64") +} + +func TestUniqueValidationStructSlice(t *testing.T) { + testStructs := []struct { + A string + B string + }{ + {A: "one", B: "two"}, + {A: "one", B: "three"}, + } + + tests := []struct { + target interface{} + param string + expected bool + }{ + {testStructs, "unique", true}, + {testStructs, "unique=A", false}, + {testStructs, "unique=B", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.target, test.param) + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d unique failed Error: %v", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d unique failed Error: %v", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "unique" { + t.Fatalf("Index: %d unique failed Error: %v", i, errs) + } + } + } + } + PanicMatches(t, func() { _ = validate.Var(testStructs, "unique=C") }, "Bad field name C") +} + +func TestUniqueValidationStructPtrSlice(t *testing.T) { + testStructs := []*struct { + A *string + B *string + }{ + {A: stringPtr("one"), B: stringPtr("two")}, + {A: stringPtr("one"), B: stringPtr("three")}, + } + + tests := []struct { + target interface{} + param string + expected bool + }{ + {testStructs, "unique", true}, + {testStructs, "unique=A", false}, + {testStructs, "unique=B", true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.target, test.param) + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d unique failed Error: %v", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d unique failed Error: %v", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "unique" { + t.Fatalf("Index: %d unique failed Error: %v", i, errs) + } + } + } + } + PanicMatches(t, func() { _ = validate.Var(testStructs, "unique=C") }, "Bad field name C") +} + +func TestHTMLValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"", true}, + {"", true}, + {"", false}, + {"<123nonsense>", false}, + {"test", false}, + {"&example", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "html") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d html failed Error: %v", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d html failed Error: %v", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "html" { + t.Fatalf("Index: %d html failed Error: %v", i, errs) + } + } + } + } +} + +func TestHTMLEncodedValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"<", true}, + {"¯", true}, + {"�", true}, + {"ð", true}, + {"<", true}, + {"¯", true}, + {"�", true}, + {"ð", true}, + {"&#ab", true}, + {"<", true}, + {">", true}, + {""", true}, + {"&", true}, + {"#x0a", false}, + {"&x00", false}, + {"z", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "html_encoded") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d html_encoded failed Error: %v", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d html_enocded failed Error: %v", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "html_encoded" { + t.Fatalf("Index: %d html_encoded failed Error: %v", i, errs) + } + } + } + } +} + +func TestURLEncodedValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"%20", true}, + {"%af", true}, + {"%ff", true}, + {"<%az", false}, + {"%test%", false}, + {"a%b", false}, + {"1%2", false}, + {"%%a%%", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "url_encoded") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d url_encoded failed Error: %v", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d url_enocded failed Error: %v", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "url_encoded" { + t.Fatalf("Index: %d url_encoded failed Error: %v", i, errs) + } + } + } + } +} + +func TestKeys(t *testing.T) { + + type Test struct { + Test1 map[string]string `validate:"gt=0,dive,keys,eq=testkey,endkeys,eq=testval" json:"test1"` + Test2 map[int]int `validate:"gt=0,dive,keys,eq=3,endkeys,eq=4" json:"test2"` + Test3 map[int]int `validate:"gt=0,dive,keys,eq=3,endkeys" json:"test3"` + } + + var tst Test + + validate := New() + err := validate.Struct(tst) + NotEqual(t, err, nil) + Equal(t, len(err.(ValidationErrors)), 3) + AssertError(t, err.(ValidationErrors), "Test.Test1", "Test.Test1", "Test1", "Test1", "gt") + AssertError(t, err.(ValidationErrors), "Test.Test2", "Test.Test2", "Test2", "Test2", "gt") + AssertError(t, err.(ValidationErrors), "Test.Test3", "Test.Test3", "Test3", "Test3", "gt") + + tst.Test1 = map[string]string{ + "testkey": "testval", + } + + tst.Test2 = map[int]int{ + 3: 4, + } + + tst.Test3 = map[int]int{ + 3: 4, + } + + err = validate.Struct(tst) + Equal(t, err, nil) + + tst.Test1["badtestkey"] = "badtestvalue" + tst.Test2[10] = 11 + + err = validate.Struct(tst) + NotEqual(t, err, nil) + + errs := err.(ValidationErrors) + + Equal(t, len(errs), 4) + + AssertDeepError(t, errs, "Test.Test1[badtestkey]", "Test.Test1[badtestkey]", "Test1[badtestkey]", "Test1[badtestkey]", "eq", "eq") + AssertDeepError(t, errs, "Test.Test1[badtestkey]", "Test.Test1[badtestkey]", "Test1[badtestkey]", "Test1[badtestkey]", "eq", "eq") + AssertDeepError(t, errs, "Test.Test2[10]", "Test.Test2[10]", "Test2[10]", "Test2[10]", "eq", "eq") + AssertDeepError(t, errs, "Test.Test2[10]", "Test.Test2[10]", "Test2[10]", "Test2[10]", "eq", "eq") + + type Test2 struct { + NestedKeys map[[1]string]string `validate:"gt=0,dive,keys,dive,eq=innertestkey,endkeys,eq=outertestval"` + } + + var tst2 Test2 + + err = validate.Struct(tst2) + NotEqual(t, err, nil) + Equal(t, len(err.(ValidationErrors)), 1) + AssertError(t, err.(ValidationErrors), "Test2.NestedKeys", "Test2.NestedKeys", "NestedKeys", "NestedKeys", "gt") + + tst2.NestedKeys = map[[1]string]string{ + [1]string{"innertestkey"}: "outertestval", + } + + err = validate.Struct(tst2) + Equal(t, err, nil) + + tst2.NestedKeys[[1]string{"badtestkey"}] = "badtestvalue" + + err = validate.Struct(tst2) + NotEqual(t, err, nil) + + errs = err.(ValidationErrors) + + Equal(t, len(errs), 2) + AssertDeepError(t, errs, "Test2.NestedKeys[[badtestkey]][0]", "Test2.NestedKeys[[badtestkey]][0]", "NestedKeys[[badtestkey]][0]", "NestedKeys[[badtestkey]][0]", "eq", "eq") + AssertDeepError(t, errs, "Test2.NestedKeys[[badtestkey]]", "Test2.NestedKeys[[badtestkey]]", "NestedKeys[[badtestkey]]", "NestedKeys[[badtestkey]]", "eq", "eq") + + // test bad tag definitions + + PanicMatches(t, func() { _ = validate.Var(map[string]string{"key": "val"}, "endkeys,dive,eq=val") }, "'endkeys' tag encountered without a corresponding 'keys' tag") + PanicMatches(t, func() { _ = validate.Var(1, "keys,eq=1,endkeys") }, "'keys' tag must be immediately preceded by the 'dive' tag") + + // test custom tag name + validate = New() + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] + + if name == "-" { + return "" + } + return name + }) + + err = validate.Struct(tst) + NotEqual(t, err, nil) + + errs = err.(ValidationErrors) + + Equal(t, len(errs), 4) + + AssertDeepError(t, errs, "Test.test1[badtestkey]", "Test.Test1[badtestkey]", "test1[badtestkey]", "Test1[badtestkey]", "eq", "eq") + AssertDeepError(t, errs, "Test.test1[badtestkey]", "Test.Test1[badtestkey]", "test1[badtestkey]", "Test1[badtestkey]", "eq", "eq") + AssertDeepError(t, errs, "Test.test2[10]", "Test.Test2[10]", "test2[10]", "Test2[10]", "eq", "eq") + AssertDeepError(t, errs, "Test.test2[10]", "Test.Test2[10]", "test2[10]", "Test2[10]", "eq", "eq") +} + +// Thanks @adrian-sgn specific test for your specific scenario +func TestKeysCustomValidation(t *testing.T) { + type LangCode string + type Label map[LangCode]string + + type TestMapStructPtr struct { + Label Label `validate:"dive,keys,lang_code,endkeys,required"` + } + + validate := New() + err := validate.RegisterValidation("lang_code", func(fl FieldLevel) bool { + validLangCodes := map[LangCode]struct{}{ + "en": {}, + "es": {}, + "pt": {}, + } + + _, ok := validLangCodes[fl.Field().Interface().(LangCode)] + return ok + }) + Equal(t, err, nil) + + label := Label{ + "en": "Good morning!", + "pt": "", + "es": "¡Buenos días!", + "xx": "Bad key", + "xxx": "", + } + + err = validate.Struct(TestMapStructPtr{label}) + NotEqual(t, err, nil) + + errs := err.(ValidationErrors) + Equal(t, len(errs), 4) + + AssertDeepError(t, errs, "TestMapStructPtr.Label[xx]", "TestMapStructPtr.Label[xx]", "Label[xx]", "Label[xx]", "lang_code", "lang_code") + AssertDeepError(t, errs, "TestMapStructPtr.Label[pt]", "TestMapStructPtr.Label[pt]", "Label[pt]", "Label[pt]", "required", "required") + AssertDeepError(t, errs, "TestMapStructPtr.Label[xxx]", "TestMapStructPtr.Label[xxx]", "Label[xxx]", "Label[xxx]", "lang_code", "lang_code") + AssertDeepError(t, errs, "TestMapStructPtr.Label[xxx]", "TestMapStructPtr.Label[xxx]", "Label[xxx]", "Label[xxx]", "required", "required") + + // find specific error + + var e FieldError + for _, e = range errs { + if e.Namespace() == "TestMapStructPtr.Label[xxx]" { + break + } + } + + Equal(t, e.Param(), "") + Equal(t, e.Value().(LangCode), LangCode("xxx")) + + for _, e = range errs { + if e.Namespace() == "TestMapStructPtr.Label[xxx]" && e.Tag() == "required" { + break + } + } + + Equal(t, e.Param(), "") + Equal(t, e.Value().(string), "") +} + +func TestKeyOrs(t *testing.T) { + + type Test struct { + Test1 map[string]string `validate:"gt=0,dive,keys,eq=testkey|eq=testkeyok,endkeys,eq=testval" json:"test1"` + } + + var tst Test + + validate := New() + err := validate.Struct(tst) + NotEqual(t, err, nil) + Equal(t, len(err.(ValidationErrors)), 1) + AssertError(t, err.(ValidationErrors), "Test.Test1", "Test.Test1", "Test1", "Test1", "gt") + + tst.Test1 = map[string]string{ + "testkey": "testval", + } + + err = validate.Struct(tst) + Equal(t, err, nil) + + tst.Test1["badtestkey"] = "badtestval" + + err = validate.Struct(tst) + NotEqual(t, err, nil) + + errs := err.(ValidationErrors) + + Equal(t, len(errs), 2) + + AssertDeepError(t, errs, "Test.Test1[badtestkey]", "Test.Test1[badtestkey]", "Test1[badtestkey]", "Test1[badtestkey]", "eq=testkey|eq=testkeyok", "eq=testkey|eq=testkeyok") + AssertDeepError(t, errs, "Test.Test1[badtestkey]", "Test.Test1[badtestkey]", "Test1[badtestkey]", "Test1[badtestkey]", "eq", "eq") + + validate.RegisterAlias("okkey", "eq=testkey|eq=testkeyok") + + type Test2 struct { + Test1 map[string]string `validate:"gt=0,dive,keys,okkey,endkeys,eq=testval" json:"test1"` + } + + var tst2 Test2 + + err = validate.Struct(tst2) + NotEqual(t, err, nil) + Equal(t, len(err.(ValidationErrors)), 1) + AssertError(t, err.(ValidationErrors), "Test2.Test1", "Test2.Test1", "Test1", "Test1", "gt") + + tst2.Test1 = map[string]string{ + "testkey": "testval", + } + + err = validate.Struct(tst2) + Equal(t, err, nil) + + tst2.Test1["badtestkey"] = "badtestval" + + err = validate.Struct(tst2) + NotEqual(t, err, nil) + + errs = err.(ValidationErrors) + + Equal(t, len(errs), 2) + + AssertDeepError(t, errs, "Test2.Test1[badtestkey]", "Test2.Test1[badtestkey]", "Test1[badtestkey]", "Test1[badtestkey]", "okkey", "eq=testkey|eq=testkeyok") + AssertDeepError(t, errs, "Test2.Test1[badtestkey]", "Test2.Test1[badtestkey]", "Test1[badtestkey]", "Test1[badtestkey]", "eq", "eq") +} + +func TestStructLevelValidationsPointerPassing(t *testing.T) { + v1 := New() + v1.RegisterStructValidation(StructValidationTestStruct, &TestStruct{}) + + tst := &TestStruct{ + String: "good value", + } + + errs := v1.Struct(tst) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestStruct.StringVal", "TestStruct.String", "StringVal", "String", "badvalueteststruct") +} + +func TestDirValidation(t *testing.T) { + validate := New() + + tests := []struct { + title string + param string + expected bool + }{ + {"existing dir", "testdata", true}, + {"existing self dir", ".", true}, + {"existing parent dir", "..", true}, + {"empty dir", "", false}, + {"missing dir", "non_existing_testdata", false}, + {"a file not a directory", filepath.Join("testdata", "a.go"), false}, + } + + for _, test := range tests { + errs := validate.Var(test.param, "dir") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Test: '%s' failed Error: %s", test.title, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Test: '%s' failed Error: %s", test.title, errs) + } + } + } + + PanicMatches(t, func() { + _ = validate.Var(2, "dir") + }, "Bad field type int") +} + +func TestStartsWithValidation(t *testing.T) { + tests := []struct { + Value string `validate:"startswith=(/^ヮ^)/*:・゚✧"` + Tag string + ExpectedNil bool + }{ + {Value: "(/^ヮ^)/*:・゚✧ glitter", Tag: "startswith=(/^ヮ^)/*:・゚✧", ExpectedNil: true}, + {Value: "abcd", Tag: "startswith=(/^ヮ^)/*:・゚✧", ExpectedNil: false}, + } + + validate := New() + + for i, s := range tests { + errs := validate.Var(s.Value, s.Tag) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + + errs = validate.Struct(s) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + } +} + +func TestEndsWithValidation(t *testing.T) { + tests := []struct { + Value string `validate:"endswith=(/^ヮ^)/*:・゚✧"` + Tag string + ExpectedNil bool + }{ + {Value: "glitter (/^ヮ^)/*:・゚✧", Tag: "endswith=(/^ヮ^)/*:・゚✧", ExpectedNil: true}, + {Value: "(/^ヮ^)/*:・゚✧ glitter", Tag: "endswith=(/^ヮ^)/*:・゚✧", ExpectedNil: false}, + } + + validate := New() + + for i, s := range tests { + errs := validate.Var(s.Value, s.Tag) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + + errs = validate.Struct(s) + + if (s.ExpectedNil && errs != nil) || (!s.ExpectedNil && errs == nil) { + t.Fatalf("Index: %d failed Error: %s", i, errs) + } + } +} + +func TestRequiredIf(t *testing.T) { + type Inner struct { + Field *string + } + + fieldVal := "test" + test := struct { + Inner *Inner + FieldE string `validate:"omitempty" json:"field_e"` + FieldER string `validate:"required_if=FieldE test" json:"field_er"` + Field1 string `validate:"omitempty" json:"field_1"` + Field2 *string `validate:"required_if=Field1 test" json:"field_2"` + Field3 map[string]string `validate:"required_if=Field2 test" json:"field_3"` + Field4 interface{} `validate:"required_if=Field3 1" json:"field_4"` + Field5 int `validate:"required_if=Inner.Field test" json:"field_5"` + Field6 uint `validate:"required_if=Field5 1" json:"field_6"` + Field7 float32 `validate:"required_if=Field6 1" json:"field_7"` + Field8 float64 `validate:"required_if=Field7 1.0" json:"field_8"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: 2, + } + + validate := New() + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + Inner *Inner + Inner2 *Inner + FieldE string `validate:"omitempty" json:"field_e"` + FieldER string `validate:"required_if=FieldE test" json:"field_er"` + Field1 string `validate:"omitempty" json:"field_1"` + Field2 *string `validate:"required_if=Field1 test" json:"field_2"` + Field3 map[string]string `validate:"required_if=Field2 test" json:"field_3"` + Field4 interface{} `validate:"required_if=Field2 test" json:"field_4"` + Field5 string `validate:"required_if=Field3 1" json:"field_5"` + Field6 string `validate:"required_if=Inner.Field test" json:"field_6"` + Field7 string `validate:"required_if=Inner2.Field test" json:"field_7"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field2: &fieldVal, + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 3) + AssertError(t, errs, "Field3", "Field3", "Field3", "Field3", "required_if") + AssertError(t, errs, "Field4", "Field4", "Field4", "Field4", "required_if") + AssertError(t, errs, "Field6", "Field6", "Field6", "Field6", "required_if") + + defer func() { + if r := recover(); r == nil { + t.Errorf("test3 should have panicked!") + } + }() + + test3 := struct { + Inner *Inner + Field1 string `validate:"required_if=Inner.Field" json:"field_1"` + }{ + Inner: &Inner{Field: &fieldVal}, + } + _ = validate.Struct(test3) +} + +func TestRequiredUnless(t *testing.T) { + type Inner struct { + Field *string + } + + fieldVal := "test" + test := struct { + Inner *Inner + FieldE string `validate:"omitempty" json:"field_e"` + FieldER string `validate:"required_unless=FieldE test" json:"field_er"` + Field1 string `validate:"omitempty" json:"field_1"` + Field2 *string `validate:"required_unless=Field1 test" json:"field_2"` + Field3 map[string]string `validate:"required_unless=Field2 test" json:"field_3"` + Field4 interface{} `validate:"required_unless=Field3 1" json:"field_4"` + Field5 int `validate:"required_unless=Inner.Field test" json:"field_5"` + Field6 uint `validate:"required_unless=Field5 2" json:"field_6"` + Field7 float32 `validate:"required_unless=Field6 0" json:"field_7"` + Field8 float64 `validate:"required_unless=Field7 0.0" json:"field_8"` + }{ + FieldE: "test", + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: 2, + } + + validate := New() + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + Inner *Inner + Inner2 *Inner + FieldE string `validate:"omitempty" json:"field_e"` + FieldER string `validate:"required_unless=FieldE test" json:"field_er"` + Field1 string `validate:"omitempty" json:"field_1"` + Field2 *string `validate:"required_unless=Field1 test" json:"field_2"` + Field3 map[string]string `validate:"required_unless=Field2 test" json:"field_3"` + Field4 interface{} `validate:"required_unless=Field2 test" json:"field_4"` + Field5 string `validate:"required_unless=Field3 0" json:"field_5"` + Field6 string `validate:"required_unless=Inner.Field test" json:"field_6"` + Field7 string `validate:"required_unless=Inner2.Field test" json:"field_7"` + }{ + Inner: &Inner{Field: &fieldVal}, + FieldE: "test", + Field1: "test", + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 3) + AssertError(t, errs, "Field3", "Field3", "Field3", "Field3", "required_unless") + AssertError(t, errs, "Field4", "Field4", "Field4", "Field4", "required_unless") + AssertError(t, errs, "Field7", "Field7", "Field7", "Field7", "required_unless") + + defer func() { + if r := recover(); r == nil { + t.Errorf("test3 should have panicked!") + } + }() + + test3 := struct { + Inner *Inner + Field1 string `validate:"required_unless=Inner.Field" json:"field_1"` + }{ + Inner: &Inner{Field: &fieldVal}, + } + _ = validate.Struct(test3) +} + +func TestRequiredWith(t *testing.T) { + type Inner struct { + Field *string + } + + fieldVal := "test" + test := struct { + Inner *Inner + FieldE string `validate:"omitempty" json:"field_e"` + FieldER string `validate:"required_with=FieldE" json:"field_er"` + Field1 string `validate:"omitempty" json:"field_1"` + Field2 *string `validate:"required_with=Field1" json:"field_2"` + Field3 map[string]string `validate:"required_with=Field2" json:"field_3"` + Field4 interface{} `validate:"required_with=Field3" json:"field_4"` + Field5 string `validate:"required_with=Inner.Field" json:"field_5"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + } + + validate := New() + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + Inner *Inner + Inner2 *Inner + FieldE string `validate:"omitempty" json:"field_e"` + FieldER string `validate:"required_with=FieldE" json:"field_er"` + Field1 string `validate:"omitempty" json:"field_1"` + Field2 *string `validate:"required_with=Field1" json:"field_2"` + Field3 map[string]string `validate:"required_with=Field2" json:"field_3"` + Field4 interface{} `validate:"required_with=Field2" json:"field_4"` + Field5 string `validate:"required_with=Field3" json:"field_5"` + Field6 string `validate:"required_with=Inner.Field" json:"field_6"` + Field7 string `validate:"required_with=Inner2.Field" json:"field_7"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field2: &fieldVal, + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 3) + AssertError(t, errs, "Field3", "Field3", "Field3", "Field3", "required_with") + AssertError(t, errs, "Field4", "Field4", "Field4", "Field4", "required_with") + AssertError(t, errs, "Field6", "Field6", "Field6", "Field6", "required_with") +} + +func TestExcludedWith(t *testing.T) { + type Inner struct { + FieldE string + Field *string + } + + fieldVal := "test" + test := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_with=FieldE" json:"field_1"` + Field2 *string `validate:"excluded_with=FieldE" json:"field_2"` + Field3 map[string]string `validate:"excluded_with=FieldE" json:"field_3"` + Field4 interface{} `validate:"excluded_with=FieldE" json:"field_4"` + Field5 string `validate:"excluded_with=Inner.FieldE" json:"field_5"` + Field6 string `validate:"excluded_with=Inner2.FieldE" json:"field_6"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + Field6: "test", + } + + validate := New() + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_with=Field" json:"field_1"` + Field2 *string `validate:"excluded_with=Field" json:"field_2"` + Field3 map[string]string `validate:"excluded_with=Field" json:"field_3"` + Field4 interface{} `validate:"excluded_with=Field" json:"field_4"` + Field5 string `validate:"excluded_with=Inner.Field" json:"field_5"` + Field6 string `validate:"excluded_with=Inner2.Field" json:"field_6"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field: "populated", + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + Field6: "test", + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 5) + for i := 1; i <= 5; i++ { + name := fmt.Sprintf("Field%d", i) + AssertError(t, errs, name, name, name, name, "excluded_with") + } +} + +func TestExcludedWithout(t *testing.T) { + type Inner struct { + FieldE string + Field *string + } + + fieldVal := "test" + test := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_without=Field" json:"field_1"` + Field2 *string `validate:"excluded_without=Field" json:"field_2"` + Field3 map[string]string `validate:"excluded_without=Field" json:"field_3"` + Field4 interface{} `validate:"excluded_without=Field" json:"field_4"` + Field5 string `validate:"excluded_without=Inner.Field" json:"field_5"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field: "populated", + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + } + + validate := New() + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_without=FieldE" json:"field_1"` + Field2 *string `validate:"excluded_without=FieldE" json:"field_2"` + Field3 map[string]string `validate:"excluded_without=FieldE" json:"field_3"` + Field4 interface{} `validate:"excluded_without=FieldE" json:"field_4"` + Field5 string `validate:"excluded_without=Inner.FieldE" json:"field_5"` + Field6 string `validate:"excluded_without=Inner2.FieldE" json:"field_6"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + Field6: "test", + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 6) + for i := 1; i <= 6; i++ { + name := fmt.Sprintf("Field%d", i) + AssertError(t, errs, name, name, name, name, "excluded_without") + } +} + +func TestExcludedWithAll(t *testing.T) { + type Inner struct { + FieldE string + Field *string + } + + fieldVal := "test" + test := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_with_all=FieldE Field" json:"field_1"` + Field2 *string `validate:"excluded_with_all=FieldE Field" json:"field_2"` + Field3 map[string]string `validate:"excluded_with_all=FieldE Field" json:"field_3"` + Field4 interface{} `validate:"excluded_with_all=FieldE Field" json:"field_4"` + Field5 string `validate:"excluded_with_all=Inner.FieldE" json:"field_5"` + Field6 string `validate:"excluded_with_all=Inner2.FieldE" json:"field_6"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field: fieldVal, + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + Field6: "test", + } + + validate := New() + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_with_all=Field FieldE" json:"field_1"` + Field2 *string `validate:"excluded_with_all=Field FieldE" json:"field_2"` + Field3 map[string]string `validate:"excluded_with_all=Field FieldE" json:"field_3"` + Field4 interface{} `validate:"excluded_with_all=Field FieldE" json:"field_4"` + Field5 string `validate:"excluded_with_all=Inner.Field" json:"field_5"` + Field6 string `validate:"excluded_with_all=Inner2.Field" json:"field_6"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field: "populated", + FieldE: "populated", + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + Field6: "test", + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 5) + for i := 1; i <= 5; i++ { + name := fmt.Sprintf("Field%d", i) + AssertError(t, errs, name, name, name, name, "excluded_with_all") + } +} + +func TestExcludedWithoutAll(t *testing.T) { + type Inner struct { + FieldE string + Field *string + } + + fieldVal := "test" + test := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_without_all=Field FieldE" json:"field_1"` + Field2 *string `validate:"excluded_without_all=Field FieldE" json:"field_2"` + Field3 map[string]string `validate:"excluded_without_all=Field FieldE" json:"field_3"` + Field4 interface{} `validate:"excluded_without_all=Field FieldE" json:"field_4"` + Field5 string `validate:"excluded_without_all=Inner.Field Inner.Field2" json:"field_5"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field: "populated", + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + } + + validate := New() + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + Inner *Inner + Inner2 *Inner + Field string `validate:"omitempty" json:"field"` + FieldE string `validate:"omitempty" json:"field_e"` + Field1 string `validate:"excluded_without_all=FieldE Field" json:"field_1"` + Field2 *string `validate:"excluded_without_all=FieldE Field" json:"field_2"` + Field3 map[string]string `validate:"excluded_without_all=FieldE Field" json:"field_3"` + Field4 interface{} `validate:"excluded_without_all=FieldE Field" json:"field_4"` + Field5 string `validate:"excluded_without_all=Inner.FieldE" json:"field_5"` + Field6 string `validate:"excluded_without_all=Inner2.FieldE" json:"field_6"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field1: fieldVal, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + Field6: "test", + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 6) + for i := 1; i <= 6; i++ { + name := fmt.Sprintf("Field%d", i) + AssertError(t, errs, name, name, name, name, "excluded_without_all") + } +} + +func TestRequiredWithAll(t *testing.T) { + type Inner struct { + Field *string + } + + fieldVal := "test" + test := struct { + Inner *Inner + FieldE string `validate:"omitempty" json:"field_e"` + FieldER string `validate:"required_with_all=FieldE" json:"field_er"` + Field1 string `validate:"omitempty" json:"field_1"` + Field2 *string `validate:"required_with_all=Field1" json:"field_2"` + Field3 map[string]string `validate:"required_with_all=Field2" json:"field_3"` + Field4 interface{} `validate:"required_with_all=Field3" json:"field_4"` + Field5 string `validate:"required_with_all=Inner.Field" json:"field_5"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field1: "test_field1", + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + } + + validate := New() + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + Inner *Inner + Inner2 *Inner + FieldE string `validate:"omitempty" json:"field_e"` + FieldER string `validate:"required_with_all=FieldE" json:"field_er"` + Field1 string `validate:"omitempty" json:"field_1"` + Field2 *string `validate:"required_with_all=Field1" json:"field_2"` + Field3 map[string]string `validate:"required_with_all=Field2" json:"field_3"` + Field4 interface{} `validate:"required_with_all=Field1 FieldE" json:"field_4"` + Field5 string `validate:"required_with_all=Inner.Field Field2" json:"field_5"` + Field6 string `validate:"required_with_all=Inner2.Field Field2" json:"field_6"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field2: &fieldVal, + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 2) + AssertError(t, errs, "Field3", "Field3", "Field3", "Field3", "required_with_all") + AssertError(t, errs, "Field5", "Field5", "Field5", "Field5", "required_with_all") +} + +func TestRequiredWithout(t *testing.T) { + + type Inner struct { + Field *string + } + + fieldVal := "test" + test := struct { + Inner *Inner + Field1 string `validate:"omitempty" json:"field_1"` + Field2 *string `validate:"required_without=Field1" json:"field_2"` + Field3 map[string]string `validate:"required_without=Field2" json:"field_3"` + Field4 interface{} `validate:"required_without=Field3" json:"field_4"` + Field5 string `validate:"required_without=Field3" json:"field_5"` + }{ + Inner: &Inner{Field: &fieldVal}, + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + } + + validate := New() + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + Inner *Inner + Inner2 *Inner + Field1 string `json:"field_1"` + Field2 *string `validate:"required_without=Field1" json:"field_2"` + Field3 map[string]string `validate:"required_without=Field2" json:"field_3"` + Field4 interface{} `validate:"required_without=Field3" json:"field_4"` + Field5 string `validate:"required_without=Field3" json:"field_5"` + Field6 string `validate:"required_without=Field1" json:"field_6"` + Field7 string `validate:"required_without=Inner.Field" json:"field_7"` + Field8 string `validate:"required_without=Inner.Field" json:"field_8"` + }{ + Inner: &Inner{}, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + } + + errs = validate.Struct(&test2) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 4) + AssertError(t, errs, "Field2", "Field2", "Field2", "Field2", "required_without") + AssertError(t, errs, "Field6", "Field6", "Field6", "Field6", "required_without") + AssertError(t, errs, "Field7", "Field7", "Field7", "Field7", "required_without") + AssertError(t, errs, "Field8", "Field8", "Field8", "Field8", "required_without") + + test3 := struct { + Field1 *string `validate:"required_without=Field2,omitempty,min=1" json:"field_1"` + Field2 *string `validate:"required_without=Field1,omitempty,min=1" json:"field_2"` + }{ + Field1: &fieldVal, + } + + errs = validate.Struct(&test3) + Equal(t, errs, nil) +} + +func TestRequiredWithoutAll(t *testing.T) { + + fieldVal := "test" + test := struct { + Field1 string `validate:"omitempty" json:"field_1"` + Field2 *string `validate:"required_without_all=Field1" json:"field_2"` + Field3 map[string]string `validate:"required_without_all=Field2" json:"field_3"` + Field4 interface{} `validate:"required_without_all=Field3" json:"field_4"` + Field5 string `validate:"required_without_all=Field3" json:"field_5"` + }{ + Field1: "", + Field2: &fieldVal, + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + } + + validate := New() + + errs := validate.Struct(test) + Equal(t, errs, nil) + + test2 := struct { + Field1 string `validate:"omitempty" json:"field_1"` + Field2 *string `validate:"required_without_all=Field1" json:"field_2"` + Field3 map[string]string `validate:"required_without_all=Field2" json:"field_3"` + Field4 interface{} `validate:"required_without_all=Field3" json:"field_4"` + Field5 string `validate:"required_without_all=Field3" json:"field_5"` + Field6 string `validate:"required_without_all=Field1 Field3" json:"field_6"` + }{ + Field3: map[string]string{"key": "val"}, + Field4: "test", + Field5: "test", + } + + errs = validate.Struct(test2) + NotEqual(t, errs, nil) + + ve := errs.(ValidationErrors) + Equal(t, len(ve), 1) + AssertError(t, errs, "Field2", "Field2", "Field2", "Field2", "required_without_all") +} + +func TestLookup(t *testing.T) { + type Lookup struct { + FieldA *string `json:"fieldA,omitempty" validate:"required_without=FieldB"` + FieldB *string `json:"fieldB,omitempty" validate:"required_without=FieldA"` + } + + fieldAValue := "1232" + lookup := Lookup{ + FieldA: &fieldAValue, + FieldB: nil, + } + Equal(t, New().Struct(lookup), nil) +} + +func TestAbilityToValidateNils(t *testing.T) { + + type TestStruct struct { + Test *string `validate:"nil"` + } + + ts := TestStruct{} + val := New() + fn := func(fl FieldLevel) bool { + return fl.Field().Kind() == reflect.Ptr && fl.Field().IsNil() + } + + err := val.RegisterValidation("nil", fn, true) + Equal(t, err, nil) + + errs := val.Struct(ts) + Equal(t, errs, nil) + + str := "string" + ts.Test = &str + + errs = val.Struct(ts) + NotEqual(t, errs, nil) +} + +func TestRequiredWithoutPointers(t *testing.T) { + type Lookup struct { + FieldA *bool `json:"fieldA,omitempty" validate:"required_without=FieldB"` + FieldB *bool `json:"fieldB,omitempty" validate:"required_without=FieldA"` + } + + b := true + lookup := Lookup{ + FieldA: &b, + FieldB: nil, + } + + val := New() + errs := val.Struct(lookup) + Equal(t, errs, nil) + + b = false + lookup = Lookup{ + FieldA: &b, + FieldB: nil, + } + errs = val.Struct(lookup) + Equal(t, errs, nil) +} + +func TestRequiredWithoutAllPointers(t *testing.T) { + type Lookup struct { + FieldA *bool `json:"fieldA,omitempty" validate:"required_without_all=FieldB"` + FieldB *bool `json:"fieldB,omitempty" validate:"required_without_all=FieldA"` + } + + b := true + lookup := Lookup{ + FieldA: &b, + FieldB: nil, + } + + val := New() + errs := val.Struct(lookup) + Equal(t, errs, nil) + + b = false + lookup = Lookup{ + FieldA: &b, + FieldB: nil, + } + errs = val.Struct(lookup) + Equal(t, errs, nil) +} + +func TestGetTag(t *testing.T) { + var tag string + + type Test struct { + String string `validate:"mytag"` + } + + val := New() + _ = val.RegisterValidation("mytag", func(fl FieldLevel) bool { + tag = fl.GetTag() + return true + }) + + var test Test + errs := val.Struct(test) + Equal(t, errs, nil) + Equal(t, tag, "mytag") +} + +func TestJSONValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {`foo`, false}, + {`}{`, false}, + {`{]`, false}, + {`{}`, true}, + {`{"foo":"bar"}`, true}, + {`{"foo":"bar","bar":{"baz":["qux"]}}`, true}, + {`{"foo": 3 "bar": 4}`, false}, + {`{"foo": 3 ,"bar": 4`, false}, + {`{foo": 3, "bar": 4}`, false}, + {`foo`, false}, + {`1`, true}, + {`true`, true}, + {`null`, true}, + {`"null"`, true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "json") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d json failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d json failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "json" { + t.Fatalf("Index: %d json failed Error: %s", i, errs) + } + } + } + } + + PanicMatches(t, func() { + _ = validate.Var(2, "json") + }, "Bad field type int") +} + +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) + } + } +} + +func TestLowercaseValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {`abcdefg`, true}, + {`Abcdefg`, false}, + {"", false}, + } + + 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}, + {"", false}, + } + + 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") + +} + +func TestDatetimeValidation(t *testing.T) { + tests := []struct { + value string `validate:"datetime=2006-01-02"` + tag string + expected bool + }{ + {"2008-02-01", `datetime=2006-01-02`, true}, + {"2008-Feb-01", `datetime=2006-01-02`, false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.value, test.tag) + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d datetime failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d datetime failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "datetime" { + t.Fatalf("Index: %d datetime failed Error: %s", i, errs) + } + } + } + } + + PanicMatches(t, func() { + _ = validate.Var(2, "datetime") + }, "Bad field type int") +} + +func TestIsIso3166Alpha2Validation(t *testing.T) { + tests := []struct { + value string `validate:"iso3166_1_alpha2"` + expected bool + }{ + {"PL", true}, + {"POL", false}, + {"AA", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.value, "iso3166_1_alpha2") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d iso3166_1_alpha2 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d iso3166_1_alpha2 failed Error: %s", i, errs) + } + } + } +} + +func TestIsIso3166Alpha3Validation(t *testing.T) { + tests := []struct { + value string `validate:"iso3166_1_alpha3"` + expected bool + }{ + {"POL", true}, + {"PL", false}, + {"AAA", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.value, "iso3166_1_alpha3") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d iso3166_1_alpha3 failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d iso3166_1_alpha3 failed Error: %s", i, errs) + } + } + } +} + +func TestIsIso3166AlphaNumericValidation(t *testing.T) { + tests := []struct { + value int + expected bool + }{ + {248, true}, + {0, false}, + {1, false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.value, "iso3166_1_alpha_numeric") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d iso3166_1_alpha_numeric failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d iso3166_1_alpha_numeric failed Error: %s", i, errs) + } + } + } + + PanicMatches(t, func() { + _ = validate.Var("1", "iso3166_1_alpha_numeric") + }, "Bad field type string") +} + +func TestTimeZoneValidation(t *testing.T) { + tests := []struct { + value string `validate:"timezone"` + tag string + expected bool + }{ + // systems may have different time zone database, some systems time zone are case insensitive + {"America/New_York", `timezone`, true}, + {"UTC", `timezone`, true}, + {"", `timezone`, false}, + {"Local", `timezone`, false}, + {"Unknown", `timezone`, 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 time zone failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d time zone failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "timezone" { + t.Fatalf("Index: %d time zone failed Error: %s", i, errs) + } + } + } + } + + PanicMatches(t, func() { + _ = validate.Var(2, "timezone") + }, "Bad field type int") +} + +func TestDurationType(t *testing.T) { + tests := []struct { + name string + s interface{} // struct + success bool + }{ + { + name: "valid duration string pass", + s: struct { + Value time.Duration `validate:"gte=500ns"` + }{ + Value: time.Second, + }, + success: true, + }, + { + name: "valid duration int pass", + s: struct { + Value time.Duration `validate:"gte=500"` + }{ + Value: time.Second, + }, + success: true, + }, + } + + validate := New() + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + errs := validate.Struct(tc.s) + if tc.success { + Equal(t, errs, nil) + return + } + NotEqual(t, errs, nil) + }) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c32083b --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module gin-valid + +go 1.13 + +require ( + github.com/go-playground/assert/v2 v2.0.1 // indirect + github.com/go-playground/universal-translator v0.17.0 + github.com/golang/protobuf v1.4.3 + github.com/json-iterator/go v1.1.10 + github.com/leodido/go-urn v1.2.0 + github.com/stretchr/testify v1.6.1 + github.com/ugorji/go/codec v1.2.0 + golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 + gopkg.in/yaml.v2 v2.4.0 +) diff --git a/main.go b/main.go new file mode 100644 index 0000000..e0b87a2 --- /dev/null +++ b/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + return +}