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