initial translation/custom error code

pull/256/head
Dean Karn 8 years ago
parent 6f98212623
commit 532878b008
  1. 52
      errors.go
  2. 2
      struct_level.go
  3. 11
      translations.go
  4. 5
      validator.go
  5. 24
      validator_instance.go
  6. 147
      validator_test.go

@ -5,12 +5,16 @@ import (
"fmt"
"reflect"
"strings"
"github.com/go-playground/universal-translator"
)
const (
fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag"
)
type ValidationErrorsTranslations map[string]string
// InvalidValidationError describes an invalid argument passed to
// `Struct`, `StructExcept`, StructPartial` or `Field`
type InvalidValidationError struct {
@ -39,18 +43,32 @@ func (ve ValidationErrors) Error() string {
buff := bytes.NewBufferString("")
var err *fieldError
var fe *fieldError
for i := 0; i < len(ve); i++ {
err = ve[i].(*fieldError)
buff.WriteString(err.Error())
fe = ve[i].(*fieldError)
buff.WriteString(fe.Error())
buff.WriteString("\n")
}
return strings.TrimSpace(buff.String())
}
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)
trans[fe.ns] = fe.Translate(ut)
}
return trans
}
// FieldError contains all functions to get error details
type FieldError interface {
@ -118,6 +136,13 @@ type FieldError interface {
//
// // 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: is not registered translation can be found it returns the same
// as calling fe.Error()
Translate(ut ut.Translator) string
}
// compile time interface checks
@ -128,6 +153,7 @@ var _ error = new(fieldError)
// 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
@ -202,3 +228,23 @@ func (fe *fieldError) Type() reflect.Type {
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: is not registered translation can be found it returns the same
// as calling fe.Error()
func (fe *fieldError) Translate(ut ut.Translator) string {
m, ok := fe.v.transTagFunc[ut.Locale()]
if !ok {
return fe.Error()
}
fn, ok := m[fe.tag]
if !ok {
return fe.Error()
}
return fn(ut, fe)
}

@ -117,6 +117,7 @@ func (v *validate) ReportError(field interface{}, fieldName, structFieldName, ta
v.errs = append(v.errs,
&fieldError{
v: v.v,
tag: tag,
actualTag: tag,
ns: v.str1,
@ -132,6 +133,7 @@ func (v *validate) ReportError(field interface{}, fieldName, structFieldName, ta
v.errs = append(v.errs,
&fieldError{
v: v.v,
tag: tag,
actualTag: tag,
ns: v.str1,

@ -0,0 +1,11 @@
package validator
import "github.com/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 withing the 'TranslationFunc'
type RegisterTranslationsFunc func(ut ut.Translator) error

@ -113,6 +113,7 @@ func (v *validate) traverseField(parent reflect.Value, current reflect.Value, ns
v.errs = append(v.errs,
&fieldError{
v: v.v,
tag: ct.aliasTag,
actualTag: ct.tag,
ns: v.str1,
@ -129,6 +130,7 @@ func (v *validate) traverseField(parent reflect.Value, current reflect.Value, ns
v.errs = append(v.errs,
&fieldError{
v: v.v,
tag: ct.aliasTag,
actualTag: ct.tag,
ns: v.str1,
@ -323,6 +325,7 @@ OUTER:
v.errs = append(v.errs,
&fieldError{
v: v.v,
tag: ct.aliasTag,
actualTag: ct.actualAliasTag,
ns: v.str1,
@ -342,6 +345,7 @@ OUTER:
v.errs = append(v.errs,
&fieldError{
v: v.v,
tag: tVal,
actualTag: tVal,
ns: v.str1,
@ -381,6 +385,7 @@ OUTER:
v.errs = append(v.errs,
&fieldError{
v: v.v,
tag: ct.aliasTag,
actualTag: ct.tag,
ns: v.str1,

@ -7,6 +7,8 @@ import (
"strings"
"sync"
"time"
"github.com/go-playground/universal-translator"
)
const (
@ -53,6 +55,7 @@ type Validate struct {
customFuncs map[reflect.Type]CustomTypeFunc
aliases map[string]string
validations map[string]Func
transTagFunc map[string]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
tagCache *tagCache
structCache *structCache
}
@ -189,6 +192,27 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{
v.hasCustomFuncs = true
}
func (v *Validate) RegisterTranslation(tag string, ut ut.Translator, registerFn RegisterTranslationsFunc, translationFn TranslationFunc) (err error) {
if v.transTagFunc == nil {
v.transTagFunc = make(map[string]map[string]TranslationFunc)
}
if err = registerFn(ut); err != nil {
return
}
m, ok := v.transTagFunc[ut.Locale()]
if !ok {
m = make(map[string]TranslationFunc)
v.transTagFunc[ut.Locale()] = 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.

@ -11,6 +11,11 @@ import (
"time"
. "gopkg.in/go-playground/assert.v1"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/fr"
"github.com/go-playground/locales/nl"
"github.com/go-playground/universal-translator"
)
// NOTES:
@ -6413,3 +6418,145 @@ func TestRequired(t *testing.T) {
NotEqual(t, err, nil)
AssertError(t, err.(ValidationErrors), "Test.Value", "Test.Value", "Value", "Value", "required")
}
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, 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)
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")
trans.Add("required", "{0} is a required field", false) // using translator outside of validator also
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', value being ignored")
}

Loading…
Cancel
Save