package validator import ( "bytes" "fmt" "reflect" "strings" ut "github.com/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()) } // 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 } // 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) }