Custom validation in Echo using ozzo-validation
makes input handling much cleaner and more structured. Instead of manually checking each field, you can define validation rules declaratively.
Let’s say we need to validate a form submission where each field has attributes like label, type, and is_required.
package form_requests
import (
"fmt"
"regexp"
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/labstack/echo/v4"
)
// FieldRequest defines a single form field
type FieldRequest struct {
Label string `json:"label"`
Type string `json:"type"`
IsRequired bool `json:"is_required"`
}
// CreateFormFieldsRequest wraps multiple fields
type CreateFormFieldsRequest struct {
Fields []FieldRequest `json:"fields"`
}
// BindAndValidate binds JSON data and runs validation
func (r *CreateFormFieldsRequest) BindAndValidate(c echo.Context) []string {
if err := c.Bind(r); err != nil {
return []string{"Invalid request payload"}
}
err := validation.ValidateStruct(r,
validation.Field(&r.Fields, validation.Required.Error("Fields array is required"), validation.Each(
validation.By(validateField),
)),
)
return extractValidationErrors(err)
}
// validateField ensures each field meets the required rules
func validateField(value interface{}) error {
field, ok := value.(FieldRequest)
if !ok {
return fmt.Errorf("invalid field format")
}
return validation.ValidateStruct(&field,
validation.Field(&field.Label,
validation.Required.Error("Label is required"),
validation.Length(3, 50).Error("Label must be between 3 and 50 characters"),
validation.Match(regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)).Error("Label can only contain letters, numbers, underscores, and dashes"),
),
validation.Field(&field.Type,
validation.Required.Error("Type is required"),
validation.In("text", "number", "email", "date", "checkbox").Error("Invalid field type"),
),
validation.Field(&field.IsRequired, validation.Required.Error("IsRequired is required")),
)
}
// extractValidationErrors formats errors into a slice of strings
func extractValidationErrors(err error) []string {
if err == nil {
return nil
}
var errors []string
if ve, ok := err.(validation.Errors); ok {
for _, e := range ve {
errors = append(errors, e.Error())
}
}
return errors
}
- Declarative Validation: Instead of manually checking fields,
ozzo-validation
provides a clean way to define rules. - Custom Validation Function:
validateField()
ensures each field meets the required format and constraints. - Consistent Error Handling:
extractValidationErrors()
collects and formats validation errors into a slice for easier debugging and response handling.