Validating Struct Fields in Go: With and Without Packages
Validation is essential for ensuring that the data in your application meets specific criteria. Go offers multiple ways to validate struct fields—both with the help of external packages and through custom-built validation logic. In this article, we'll explore both methods, including how to provide custom error messages in validation tags.
Validating Struct Fields with Packages
There are several powerful Go packages for struct validation. They offer built-in validation rules, customizable error messages, and flexibility. One of the most popular options is the go-playground/validator
package, which allows you to define validation rules within struct tags.
1. Using go-playground/validator
The go-playground/validator
package makes validation straightforward by using struct tags to define rules. It also allows you to include custom error messages through tags, providing user-friendly feedback.
Example Usage:
import (
"github.com/go-playground/validator/v10"
"fmt"
)
type User struct {
Name string `validate:"required,min=3,max=50" validateMessage:"Name must be between 3 and 50 characters long"`
Age int `validate:"gte=0,lte=130" validateMessage:"Age must be between 0 and 130"`
Email string `validate:"required,email" validateMessage:"Invalid email format"`
}
func main() {
validate := validator.New()
user := User{Name: "Jo", Age: 200, Email: "invalid-email"}
err := validate.Struct(user)
if err != nil {
for _, err := range err.(validator.ValidationErrors) {
field := err.Field()
customMessage := err.StructField() + " " + err.Tag() // Extract the custom message (simplified)
fmt.Println(customMessage)
}
}
}
In this example, the validateMessage
tag would ideally contain the custom error message. However, to actually implement custom error messages, you may need to manually handle these using custom functions.
To truly customize messages in go-playground/validator
, you can register custom validation functions:
func main() {
validate := validator.New()
// Register a custom validation for the Name field
validate.RegisterValidation("custom_name", func(fl validator.FieldLevel) bool {
name := fl.Field().String()
if len(name) < 3 || len(name) > 50 {
return false
}
return true
})
user := User{Name: "Jo", Age: 200, Email: "invalid-email"}
err := validate.Struct(user)
if err != nil {
for _, err := range err.(validator.ValidationErrors) {
field := err.Field()
customMessage := err.Field() + " " + "failed custom validation"
fmt.Println(customMessage)
}
}
}
2. Using ozzo-validation
The ozzo-validation
package takes a different approach, allowing you to programmatically define validation rules and custom error messages.
Example Usage:
import (
"github.com/go-ozzo/ozzo-validation/v4"
"fmt"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
}
func (u User) Validate() error {
return validation.ValidateStruct(&u,
validation.Field(&u.Name, validation.Required.Error("Name is required"), validation.Length(3, 50).Error("Name must be between 3 and 50 characters long")),
validation.Field(&u.Age, validation.Required, validation.Min(0).Error("Age must be greater than 0"), validation.Max(130).Error("Age must be less than or equal to 130")),
validation.Field(&u.Email, validation.Required.Error("Email is required"), validation.Email.Error("Invalid email format")),
)
}
func main() {
user := User{Name: "Jo", Age: 200, Email: "invalid-email"}
err := user.Validate()
if err != nil {
fmt.Println("Validation error:", err)
}
}
In this example, ozzo-validation
allows you to define custom error messages directly in the validation rules, making it easy to provide user-friendly feedback.
Validating Struct Fields Without Packages
If you prefer to avoid external dependencies, you can implement custom validation logic directly in Go. This approach requires you to write your own validation functions and manage error messages manually.
1. Defining the Struct
First, define the struct you want to validate:
type User struct {
Name string
Age int
Email string
}
2. Writing Custom Validation Functions
Next, create validation functions for each field, returning custom error messages if the validation fails.
import "errors"
func validateName(name string) error {
if len(name) < 3 || len(name) > 50 {
return errors.New("Name must be between 3 and 50 characters long")
}
return nil
}
func validateAge(age int) error {
if age < 0 || age > 130 {
return errors.New("Age must be between 0 and 130")
}
return nil
}
func validateEmail(email string) error {
if len(email) == 0 || !containsAtSign(email) {
return errors.New("Invalid email address")
}
return nil
}
func containsAtSign(s string) bool {
for _, ch := range s {
if ch == '@' {
return true
}
}
return false
}
3. Combining Validation Functions
Combine these validation functions to validate the entire struct and return error messages if any validation fails.
func validateUser(user User) error {
if err := validateName(user.Name); err != nil {
return err
}
if err := validateAge(user.Age); err != nil {
return err
}
if err := validateEmail(user.Email); err != nil {
return err
}
return nil
}
4. Using the Validation Function
Finally, use the validation function in your application and handle any validation errors:
func main() {
user := User{Name: "Jo", Age: 200, Email: "invalid-email"}
if err := validateUser(user); err != nil {
fmt.Println("Validation error:", err)
} else {
fmt.Println("User is valid!")
}
}
Conclusion
Whether you choose to use a validation package or write your own validation logic, Go provides several flexible options for ensuring that your data conforms to specific rules. Packages like go-playground/validator
and ozzo-validation
allow for advanced validation features, including custom error messages. On the other hand, writing custom validation logic gives you full control over how your data is validated. Ultimately, the choice depends on your project’s complexity and your preference for external dependencies.
By understanding both approaches, you can select the method that best suits your application’s needs.