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:
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
requiredwith 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¶
- Events - Lifecycle events including validation
- Entity Pattern - Entity structure
- Repository Pattern - Save operations