Introduction
Struct validation is an essential aspect of building robust applications in Go. It ensures that the data we receive from the process adheres to the expected format and constraints. While the standard validation package in Go provides useful features, customize error messages in struct validation using tags, along with a practical example.
Overview of Struct Validation in Go
Package: https://pkg.go.dev/github.com/go-playground/validator/v10
Struct validation is a crucial process in software development. It ensures that the data received during the process adheres to the expected format and constraints, preventing errors and ensuring data integrity. In Go, this process is made easy by the standard validation package, which provides a variety of useful features.
The validation package in Go allows developers to specify constraints for struct fields using tags. These constraints can include maximum and minimum values, regular expressions, and custom validation functions. By adding these tags to the struct fields, developers can ensure that the data being received meets the expected format and constraints.
Another benefit of using the validation package in Go is the ability to customize error messages. Developers can add tags to their struct fields that specify custom error messages for each constraint. This can help to make error messages more informative and user-friendly, improving the overall user experience.
To demonstrate the power of struct validation in Go, let's consider a practical example. Suppose we are building an e-commerce platform that requires users to enter their payment information. Using the validation package, we can ensure that the credit card number entered by the user is valid and meets the expected format. We can also ensure that the expiration date is valid and that the CVV code is three digits long.
By using the validation package in Go, we can ensure that our e-commerce platform is secure and error-free. This will improve the user experience and help to build trust with our customers.
Section 2: Standard Struct Validation in Go
The validation package in Go provides a variety of useful features for struct validation. To use the validation package, we first need to create a validator instance. We can create a new validator instance using the New() function provided by the validation package.
import "github.com/go-playground/validator/v10"
// Create a new validator instance
validate := validator.New()
Once we have a validator instance, we can use it to validate our struct fields. To do this, we add tags to our struct fields that specify the desired constraints.
type PaymentInfo struct {
CreditCardNumber string `validate:"required"`
CVV string `validate:"required,len=3"`
}
In this example, we have added tags to the fields of our PaymentInfo
struct. These tags specify that the CreditCardNumber
field must required, the CVV
field must contain exactly three digits.
We can then use the validator instance to validate our struct fields.
// Create a new PaymentInfo instance
paymentInfo := PaymentInfo{
CreditCardNumber: "4111111111111111",
CVV: "123",
}
// Validate the PaymentInfo instance
err := validate.Struct(paymentInfo)
if err != nil {
// Handle validation error
}
If any of the struct fields fail validation, the validate.Struct()
function will return a validation error. We can then handle this error in our application code.
Key: 'PaymentInfo.CreditCardNumber' Error:Field validation for 'CreditCardNumber' failed on the 'required' tag
Key: 'PaymentInfo.CVV' Error:Field validation for 'CVV' failed on the 'len' tag
By using the validation package in Go, we can ensure that our struct fields meet the expected format and constraints, improving the overall quality and security of our applications.
Section 3: Customizing Error Messages with Tags
In addition to specifying constraints for struct fields, we can also customize error messages using tags. To do this, we add a message
tag to our struct field tags.
type PaymentInfo struct {
CreditCardNumber string `validate:"required" errormgs:"Invalid credit card is required xxx"`
CVV string `validate:"required,len=3" errormgs:"CVV code must be three digits long"`
}
func (m *PaymentInfo) Validate(validate *validator.Validate) error {
return ValidateFunc[PaymentInfo](*m, validate)
}
My ValidateFunc:
const tagCustom = "errormgs"
func errorTagFunc[T interface{}](obj interface{}, snp string, fieldname, actualTag string) error {
o := obj.(T)
if !strings.Contains(snp, fieldname) {
return nil
}
fieldArr := strings.Split(snp, ".")
rsf := reflect.TypeOf(o)
for i := 1; i < len(fieldArr); i++ {
field, found := rsf.FieldByName(fieldArr[i])
if found {
if fieldArr[i] == fieldname {
customMessage := field.Tag.Get(tagCustom)
if customMessage != "" {
return fmt.Errorf("%s: %s (%s)", fieldname, customMessage, actualTag)
}
return nil
} else {
if field.Type.Kind() == reflect.Ptr {
// If the field type is a pointer, dereference it
rsf = field.Type.Elem()
} else {
rsf = field.Type
}
}
}
}
return nil
}
func ValidateFunc[T interface{}](obj interface{}, validate *validator.Validate) (errs error) {
o := obj.(T)
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in Validate:", r)
errs = fmt.Errorf("can't validate %+v", r)
}
}()
if err := validate.Struct(o); err != nil {
errorValid := err.(validator.ValidationErrors)
for _, e := range errorValid {
// snp X.Y.Z
snp := e.StructNamespace()
errmgs := errorTagFunc[T](obj, snp, e.Field(), e.ActualTag())
if errmgs != nil {
errs = errors.Join(errs, fmt.Errorf("%w", errmgs))
} else {
errs = errors.Join(errs, fmt.Errorf("%w", e))
}
}
}
if errs != nil {
return errs
}
return nil
}
Section 4: Practical Example
// Create a new PaymentInfo instance
paymentInfo := PaymentInfo{
CreditCardNumber: "",
CVV: "123",
}
// Validate the PaymentInfo instance
err := paymentInfo.Validate(validate)
The error messages we specified in the errormgs
tag will be returned if the field fails validation. For example, if we set the CreditCardNumber
to an empty string and validate the PaymentInfo
instance, the error message "Invalid credit card is required xxx" will be returned. By customizing error messages in this way, we can make them more informative and user-friendly, improving the overall user experience of our applications.
CreditCardNumber: Invalid credit card is required xxx (required)
CVV: CVV code must be three digits long (len)
https://go.dev/play/p/Tigv2u0o_51
Section 6: Conclusion
In conclusion, struct validation is an essential aspect of building robust applications in Go. While the standard validation package provides a variety of useful features, customizing error messages with tags can make them more informative and user-friendly, enhancing the overall user experience. By following the practical example presented in this article, developers can ensure that their struct fields meet the expected format and constraints, building trust with their users and improving the quality of their applications.