Skip to content

Instantly share code, notes, and snippets.

@shaunlee
Last active June 22, 2024 02:51
Show Gist options
  • Save shaunlee/36386d2b9e7633b67410ff78a48f1964 to your computer and use it in GitHub Desktop.
Save shaunlee/36386d2b9e7633b67410ff78a48f1964 to your computer and use it in GitHub Desktop.
Form validation with custom errors and nested structs
package forms
import (
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
"reflect"
"strings"
)
/*
type LoginMetadata struct {
OS string `form:"os" json:"os" validate:"required" errors.required:"OS is required"`
Version string `form:"version" json:"version" validate:"required" errors.required:"Version is required"`
}
type LoginForm struct {
Username string `form:"username" validate:"required" errors.required:"Username is required"`
Password string `form:"password" validate:"required" errors.required:"Passsword is required"`
Metadata LoginMetadata `form:"metadata" json:"metadata"`
}
*/
type ValidationError fiber.Map
func (e ValidationError) Error() string {
return "ValidationError: Unprocessable Content"
}
var validate = validator.New()
func BindAndValidate(c *fiber.Ctx, v interface{}) error {
if err := c.BodyParser(v); err != nil {
return ValidationError{
"default": err.Error(),
}
}
if err := validate.Struct(v); err != nil {
errs := ValidationError{}
ref := reflect.TypeOf(v).Elem()
for _, e := range err.(validator.ValidationErrors) {
if field, ok := ref.FieldByName(e.Field()); ok {
k, v := getErrorMessage(field, e)
errs[k] = v
} else {
subLevel(ref, e, errs, 1)
}
}
return errs
}
return nil
}
func subLevel(ref reflect.Type, e validator.FieldError, errs ValidationError, level int) {
if pf, ok := ref.FieldByName(strings.SplitN(e.Namespace(), ".", -1)[level]); ok {
k := pf.Tag.Get("form")
if len(k) == 0 {
k = pf.Tag.Get("json")
if len(k) == 0 {
k = pf.Name
}
}
if _, ok := errs[k]; !ok {
errs[k] = ValidationError{}
}
if cf, ok := pf.Type.FieldByName(e.Field()); ok {
ck, cv := getErrorMessage(cf, e)
if kv, ok := errs[k].(ValidationError); ok {
kv[ck] = cv
}
} else if kv, ok := errs[k].(ValidationError); ok {
subLevel(pf.Type, e, kv, level+1)
}
}
}
func getErrorMessage(field reflect.StructField, e validator.FieldError) (string, string) {
k := field.Tag.Get("form")
if len(k) == 0 {
k = field.Tag.Get("json")
if len(k) == 0 {
k = e.Field()
}
}
v := field.Tag.Get("errors." + e.ActualTag())
if len(v) == 0 {
v = e.Error()
}
return k, v
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment