validation Package
Request validation using struct tags with go-playground/validator. Integrates seamlessly with LaResto's error handling.
Features
- Struct Tag Validation: Declarative validation rules
- 30+ Built-in Validators: Email, URL, min/max length, numeric ranges, etc.
- Custom Validators: Phone numbers, strong passwords (LaResto-specific)
- Error Integration: Returns
errors.ErrValidation with detailed field errors
- Human-Readable Messages: Automatic error message formatting
Installation
import "github.com/LaRestoOU/laresto-go-common/pkg/validation"
Quick Start
Define Request Structs
type LoginRequest struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
}
type RegisterRequest struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,strong_password"`
ConfirmPassword string `json:"confirm_password" validate:"required,eqfield=Password"`
FirstName string `json:"first_name" validate:"required,min=2,max=50"`
Age int `json:"age" validate:"required,gte=18,lte=120"`
Phone string `json:"phone" validate:"required,phone"`
}
Validate Requests
v := validation.New()
req := LoginRequest{
Email: "user@example.com",
Password: "password123",
}
if err := v.Validate(req); err != nil {
// err is *errors.AppError with validation details
return err
}
String Validators
type Example struct {
Required string `validate:"required"` // Must not be empty
Email string `validate:"email"` // Valid email format
URL string `validate:"url"` // Valid URL
UUID string `validate:"uuid"` // Valid UUID
Alpha string `validate:"alpha"` // Letters only
Alphanum string `validate:"alphanum"` // Letters and numbers
Numeric string `validate:"numeric"` // Numbers only
Lowercase string `validate:"lowercase"` // Lowercase only
Uppercase string `validate:"uppercase"` // Uppercase only
}
Length Validators
type Example struct {
MinLen string `validate:"min=5"` // At least 5 chars
MaxLen string `validate:"max=100"` // At most 100 chars
ExactLen string `validate:"len=8"` // Exactly 8 chars
BetweenLen string `validate:"min=5,max=10"` // Between 5-10 chars
}
Numeric Validators
type Example struct {
GreaterThan int `validate:"gt=0"` // Greater than 0
GreaterOrEq int `validate:"gte=18"` // Greater or equal 18
LessThan int `validate:"lt=100"` // Less than 100
LessOrEq int `validate:"lte=120"` // Less or equal 120
Range int `validate:"gte=1,lte=10"` // Between 1-10
Positive float64 `validate:"gt=0"` // Positive number
}
Comparison Validators
type Example struct {
Equal string `validate:"eq=admin"` // Equals "admin"
NotEqual string `validate:"ne=guest"` // Not equal "guest"
OneOf string `validate:"oneof=red green blue"` // One of listed values
EqualField string `validate:"eqfield=Password"` // Equals another field
}
Special Validators
type Example struct {
Contains string `validate:"contains=@"` // Contains substring
StartsWith string `validate:"startswith=https://"`// Starts with prefix
EndsWith string `validate:"endswith=.com"` // Ends with suffix
JSON string `validate:"json"` // Valid JSON
Base64 string `validate:"base64"` // Valid base64
Hexadecimal string `validate:"hexadecimal"` // Valid hex
}
Custom LaResto Validators
Phone Number
type Request struct {
Phone string `validate:"required,phone"`
}
// Valid formats:
// +1234567890
// +1 (234) 567-8900
// +44 20 1234 5678
Strong Password
type Request struct {
Password string `validate:"required,strong_password"`
}
// Requirements:
// - At least 8 characters
// - At least one uppercase letter
// - At least one lowercase letter
// - At least one digit
Usage with Gin
Handler Example
func (h *Handler) Register(c *gin.Context) {
var req RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "Invalid JSON"})
return
}
// Validate request
if err := h.validator.Validate(req); err != nil {
// err is *errors.AppError with validation details
appErr := err.(*errors.AppError)
c.JSON(appErr.Status, gin.H{
"error": gin.H{
"code": appErr.Code,
"message": appErr.Message,
"details": appErr.Details,
},
})
return
}
// Process valid request
user, err := h.service.Register(req)
// ...
}
When validation fails, the response looks like:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": {
"validation_errors": [
{
"field": "Email",
"message": "Email must be a valid email address",
"tag": "email",
"value": "invalid-email"
},
{
"field": "Password",
"message": "Password must be at least 8 characters",
"tag": "min",
"value": "short"
}
]
}
}
}
Validating Single Variables
v := validation.New()
// Validate a single value
email := "user@example.com"
if err := v.ValidateVar(email, "required,email"); err != nil {
// Handle error
}
// Validate with multiple rules
age := 25
if err := v.ValidateVar(age, "required,gte=18,lte=120"); err != nil {
// Handle error
}
Creating Custom Validators
v := validation.New()
// Register custom validator
v.RegisterValidation("is_even", func(fl validator.FieldLevel) bool {
value, ok := fl.Field().Interface().(int)
if !ok {
return false
}
return value%2 == 0
})
// Use in struct
type Example struct {
Number int `validate:"required,is_even"`
}
Validation Tag Reference
Complete Tag List
| Tag |
Description |
Example |
required |
Field must not be empty |
validate:"required" |
email |
Valid email address |
validate:"email" |
url |
Valid URL |
validate:"url" |
uuid |
Valid UUID |
validate:"uuid" |
min |
Minimum length/value |
validate:"min=5" |
max |
Maximum length/value |
validate:"max=100" |
len |
Exact length |
validate:"len=10" |
eq |
Equal to value |
validate:"eq=admin" |
ne |
Not equal to value |
validate:"ne=guest" |
gt |
Greater than |
validate:"gt=0" |
gte |
Greater or equal |
validate:"gte=18" |
lt |
Less than |
validate:"lt=100" |
lte |
Less or equal |
validate:"lte=120" |
eqfield |
Equal to another field |
validate:"eqfield=Password" |
nefield |
Not equal to another field |
validate:"nefield=OldPassword" |
oneof |
One of listed values |
validate:"oneof=red green blue" |
contains |
Contains substring |
validate:"contains=@" |
startswith |
Starts with prefix |
validate:"startswith=https://" |
endswith |
Ends with suffix |
validate:"endswith=.com" |
alpha |
Letters only |
validate:"alpha" |
alphanum |
Letters and numbers |
validate:"alphanum" |
numeric |
Numbers only |
validate:"numeric" |
number |
Valid number |
validate:"number" |
json |
Valid JSON |
validate:"json" |
jwt |
Valid JWT token |
validate:"jwt" |
latitude |
Valid latitude |
validate:"latitude" |
longitude |
Valid longitude |
validate:"longitude" |
phone |
Valid phone (custom) |
validate:"phone" |
strong_password |
Strong password (custom) |
validate:"strong_password" |
Best Practices
DO ✅
// Use clear, descriptive validation tags
type UserRequest struct {
Email string `validate:"required,email"`
Age int `validate:"required,gte=18,lte=120"`
Username string `validate:"required,min=3,max=20,alphanum"`
}
// Combine multiple rules
Password string `validate:"required,min=8,max=128,strong_password"`
// Use eqfield for password confirmation
ConfirmPassword string `validate:"required,eqfield=Password"`
// Validate enums with oneof
Role string `validate:"required,oneof=admin user guest"`
DON'T ❌
// Don't skip validation
var req LoginRequest
c.ShouldBindJSON(&req)
// MISSING: if err := v.Validate(req); err != nil { ... }
// Don't use weak validation
Password string `validate:"required"` // Too weak!
// Don't forget required on important fields
Email string `validate:"email"` // Missing required!
// Don't ignore validation errors
v.Validate(req) // Ignoring error!
Testing
func TestValidation(t *testing.T) {
v := validation.New()
// Test valid data
valid := LoginRequest{
Email: "user@example.com",
Password: "password123",
}
if err := v.Validate(valid); err != nil {
t.Errorf("Expected valid data to pass: %v", err)
}
// Test invalid data
invalid := LoginRequest{
Email: "not-an-email",
Password: "short",
}
if err := v.Validate(invalid); err == nil {
t.Error("Expected invalid data to fail")
}
}
iOS Developer Notes
Validation is similar to:
- SwiftUI's
@Published property wrappers with validation
- Combine's validation operators
- Manual validation in view controllers
Comparison:
// Swift validation (manual)
func validate() -> [String] {
var errors: [String] = []
if email.isEmpty {
errors.append("Email is required")
}
if !email.contains("@") {
errors.append("Email must be valid")
}
return errors
}
// Go validation (declarative)
type Request struct {
Email string `validate:"required,email"`
}
Key concepts:
- Struct tags = Like Swift property wrappers
- Validation rules = Declarative, not imperative
- Error details = Like
ValidationError arrays in iOS
- Fast: Reflection is cached, validation is optimized
- No allocations: Most validations don't allocate memory
- Parallel-safe: Validator can be used concurrently
Benchmarks (from go-playground/validator):
- ~1-2µs per struct with 10 fields
- Caching eliminates reflection overhead
- Thread-safe for concurrent use
API Reference
Types
type Validator struct {
// Wraps go-playground/validator
}
type ValidationError struct {
Field string // Field name that failed
Message string // Human-readable error message
Tag string // Validation tag that failed
Value string // Value that failed validation
}
Functions
// New creates a new Validator
func New() *Validator
Methods
// Validate validates a struct
func (v *Validator) Validate(s interface{}) error
// ValidateVar validates a single variable
func (v *Validator) ValidateVar(field interface{}, tag string) error
// RegisterValidation adds a custom validator
func (v *Validator) RegisterValidation(tag string, fn validator.Func) error
License
MIT License - see LICENSE file for details