Skip to content

Validation

Dynorm provides built-in validation for entities using struct tags.

Overview

Add validation rules to entity fields using the validate tag:

type User struct {
    dynorm.Entity

    Email    string `validate:"required,email"`
    Username string `validate:"required,min=3,max=20"`
    Age      int    `validate:"min=0,max=150"`
    Status   string `validate:"required,enum=active|inactive|pending"`
}

Validation Rules

Required

Field must not be empty:

Email string `validate:"required"`

String Length

Rule Description Example
min=n Minimum length validate:"min=3"
max=n Maximum length validate:"max=100"
Username string `validate:"min=3,max=20"`
Password string `validate:"min=8"`
Bio      string `validate:"max=500"`

Numeric Range

Rule Description Example
min=n Minimum value validate:"min=0"
max=n Maximum value validate:"max=100"
Age      int     `validate:"min=0,max=150"`
Price    float64 `validate:"min=0.01"`
Quantity int     `validate:"min=1,max=999"`

Format

Rule Description Example
email Valid email format validate:"email"
url Valid URL format validate:"url"
uuid Valid UUID format validate:"uuid"
ulid Valid ULID format validate:"ulid"
Email   string `validate:"required,email"`
Website string `validate:"url"`
OrderID string `validate:"ulid"`

Enum

Field must be one of specified values:

Status string `validate:"enum=active|inactive|pending"`
Role   string `validate:"required,enum=user|admin|moderator"`

Pattern

Match a regular expression:

Phone    string `validate:"pattern=^\\+?[0-9]{10,14}$"`
ZipCode  string `validate:"pattern=^[0-9]{5}(-[0-9]{4})?$"`
Username string `validate:"pattern=^[a-zA-Z][a-zA-Z0-9_]*$"`

Combining Rules

Combine multiple rules with commas:

type User struct {
    dynorm.Entity

    Email    string `validate:"required,email"`
    Username string `validate:"required,min=3,max=20,pattern=^[a-z0-9_]+$"`
    Password string `validate:"required,min=8,max=100"`
    Age      int    `validate:"min=13,max=120"`
    Role     string `validate:"required,enum=user|admin"`
    Website  string `validate:"url"` // Optional, but must be valid URL if provided
}

Validation Execution

Using the Validation Package

Import the validation package and call Validate() before saving:

import (
    "github.com/go-gamma/dynorm"
    "github.com/go-gamma/dynorm/pkg/validation"
)

user := &User{
    Email:    "invalid-email",
    Username: "ab", // Too short
}

// Validate before saving
if err := validation.Validate(user); err != nil {
    // Handle validation errors
    fmt.Println(err)
    // "Email: must be a valid email address; Username: must be at least 3 characters"
    return err
}

// Save if validation passes
err := UserRepo.Save(user)

Custom Validators

Register custom validation functions using the validation package:

import "github.com/go-gamma/dynorm/pkg/validation"

func init() {
    validation.RegisterValidator("username", func(value any) error {
        s, ok := value.(string)
        if !ok {
            return errors.New("must be a string")
        }

        // Check against reserved words
        reserved := []string{"admin", "root", "system"}
        for _, r := range reserved {
            if strings.EqualFold(s, r) {
                return errors.New("username is reserved")
            }
        }

        return nil
    })
}

type User struct {
    dynorm.Entity

    Username string `validate:"required,username"`
}

Error Handling

Validation errors contain field-specific messages:

import "github.com/go-gamma/dynorm/pkg/validation"

user := &User{
    Email:    "not-an-email",
    Username: "x",
    Age:      -5,
}

err := validation.Validate(user)
if err != nil {
    // The error message contains all validation failures
    fmt.Println(err)
    // Output:
    // Email: must be a valid email address
    // Username: must be at least 3 characters
    // Age: must be at least 0
}

Conditional Validation

Since validation is manual, you control when to validate:

// Always validate user input
if err := validation.Validate(user); err != nil {
    return err
}
err := UserRepo.Save(user)

// Skip validation for trusted internal operations (use with caution)
// Simply don't call validation.Validate() before Save()
err := UserRepo.Save(trustedEntity)

Validation Examples

User Registration

type User struct {
    dynorm.Entity

    Email           string `validate:"required,email"`
    Username        string `validate:"required,min=3,max=20,pattern=^[a-z0-9_]+$"`
    Password        string `validate:"required,min=8"`
    ConfirmPassword string `validate:"required"` // Check in custom validator
    Age             int    `validate:"min=13"`
    AcceptedTerms   bool   `validate:"required"`
}

Product

type Product struct {
    dynorm.Entity

    Name        string   `validate:"required,min=1,max=200"`
    SKU         string   `validate:"required,pattern=^[A-Z0-9-]+$"`
    Price       float64  `validate:"required,min=0.01"`
    Description string   `validate:"max=2000"`
    Category    string   `validate:"required,enum=electronics|clothing|food|other"`
    Stock       int      `validate:"min=0"`
    Weight      float64  `validate:"min=0"`
    Tags        []string `validate:"max=10"` // Max 10 tags
}

Order

type Order struct {
    dynorm.Entity

    CustomerID    string  `validate:"required,ulid"`
    CustomerEmail string  `validate:"required,email"`
    Status        string  `validate:"required,enum=pending|processing|shipped|delivered|cancelled"`
    Total         float64 `validate:"required,min=0"`
    Currency      string  `validate:"required,enum=USD|EUR|GBP"`
    ShippingAddr  string  `validate:"required,min=10,max=500"`
}

Validation Rule Reference

Rule Type Description
required All Field must not be empty
min=n String/Number Minimum length/value
max=n String/Number Maximum length/value
email String Valid email format
url String Valid URL format
uuid String Valid UUID format
ulid String Valid ULID format
enum=a\|b\|c String Must be one of values
pattern=regex String Must match regex

Best Practices

Do

  • Validate at the entity level
  • Use appropriate rules for each field type
  • Combine required with format validators
  • Handle validation errors gracefully

Don't

  • Skip validation without good reason
  • Use overly complex regex patterns
  • Rely solely on client-side validation
  • Ignore validation errors

Next Steps