Validating and sanitizing structs and variables
This is basically a wrapper around go-playground/validator and go-playground/mold.
Besides the functionality of the go-playground packages it supports a more flexible error translation by using struct tags for error messages.
The error message is set into the errors
tag of the struct field, the key is the validator as provided in the validate
struct tag. Multiple validators/errors are supported.
Validating structs
type Address struct {
Street string `json:"street" validate:"required" errors:"required:global.street.required"`
}
type LoginData struct {
AppId string `json:"app_id" example:"E790D106-0C05-4263-882D-E5D665CF53C1"`
Username string `json:"username" validate:"required,email" errors:"required:global.username.required,email:global.username.invalid"`
Password string `json:"password"`
Address Address `json:"address"`
}
loginData := LoginData{
Username: "",
Address: Address{
Street: "",
},
}
fieldErrors, err := NewValidator().Struct(loginData, nil)
fieldErrors is a map containing the name of the failed field as key and the error message from the errors struct tag.
username -> global.username.required
address.street -> global.street.required
Nested fields are represented using a dot notation.
Translation
In many cases the translation is application specific, it does not make sense integrating a sofisticated translation
system into this package. However, it is possible to make the error translations by passing your own translation function.
The second argument to Struct() is an optional translation function with the type of type Translate func(key string, args ...any) string
,
this function accepts the text as given in the errors tag and should return the translated version of the error text.
If no translation func was given, the text as specified in the errors tag is returned unmodified.
func Tr(key string, args ...any) string {
// in the real world, do something useful
return "tr." + key
}
fieldErrors, err := NewValidator().Struct(loginData, Tr)
Slices of structs
The package supports slices or array of structs.
type Address struct {
Street string `json:"street" validate:"required" errors:"required:global.street.required"`
}
type Customer struct {
Firstname string `json:"firstname" validate:"required" errors:"required:global.firstname.required"`
Lastname string `json:"lastname" validate:"required" errors:"required:global.lastname.required"`
Addresses []Address `json:"addresses" validate:"dive"` // the dive keyword is important
}
addressOk := Address{Street: "Daham 66"}
addressWrong := Address{Street: ""}
data := Customer{Adresses: []Address{addressOk, addressWrong}}
fieldErrors, err := NewValidator().Struct(data, nil)
fieldErrors contains addresses.1.street -> global.street.required
, the struct which contains the invalid
value is indicated by its index.
This does work with slices of scalar values too
type SliceOneOf struct {
Name []string `json:"name" validate:"dive,oneof=one two three" errors:"oneof:name must be one two or three"`
}
data := SliceOneOf{Name: []string{"four"}}
fieldErrors, err := NewValidator().Struct(data, nil)
fieldErrors contains: name.0 -> name must be one two or three
Sanitize
If you want to do some kind of sanitization, like triming, you can use the sanitizer which is a simple wrapper around
go-playground/mold.
type LoginData struct {
AppId string `json:"app_id" mod:"trim"`
Username string `json:"username" mod:"trim,lcase"`
Password string `json:"password"`
Address Address `json:"address"`
}
loginData := LoginData2{
AppId: "E790D106-0C05-4263-882D-E5D665CF53C1 ",
Username: "foo@bar.COM ",
}
err := NewSanitizer().Struct(&loginData)
In this case it trims the AppId field and the Username field, the Username field is modified to lowercase.
The loginData struct is modified in place.
For available modifiers see: go-playground/mold.
Examples
Password quality check
type RequestData struct {
Username string `json:"username" mod:"trim,lcase" validate:"required,email" errors:"required:validation.global.email_required,email:validation.global.email_invalid"`
Password1 string `json:"password1" validate:"required,min=8,containsLowercase,containsUppercase,containsDigit,containsSpecialChar" errors:"required:validation.global.password_required,min:validation.global.password.minlength,containsLowercase:validation.global.password.lowercase,containsUppercase:validation.global.password.uppercase,containsDigit:validation.global.password.digit,containsSpecialChar:validation.global.password.special_character"`
Password2 string `json:"password2" validate:"required,eqfield=Password1" errors:"required:user.validation.password_required"`
}
Validate non-empty fields only
type RequestData struct {
Id types.UUID `json:"id" validate:"omitempty,uuid4" errors:"uuid4:validation.global.id_invalid"`
}