Customizing Error Messages in Struct Validation Using Tags in Go

Huynh Thanh Phuc - May 21 '23 - - Dev Community

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()



Enter fullscreen mode Exit fullscreen mode

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"`
}



Enter fullscreen mode Exit fullscreen mode

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
}



Enter fullscreen mode Exit fullscreen mode

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)
}


Enter fullscreen mode Exit fullscreen mode

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
}


Enter fullscreen mode Exit fullscreen mode

Section 4: Practical Example



// Create a new PaymentInfo instance
paymentInfo := PaymentInfo{
    CreditCardNumber: "",
    CVV:              "123",
}

// Validate the PaymentInfo instance
err := paymentInfo.Validate(validate)


Enter fullscreen mode Exit fullscreen mode

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)


Enter fullscreen mode Exit fullscreen mode

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.

Buy Me a Coffee:
Buy Me A Coffee

. . . . . . . .
Terabox Video Player