Valforge
A blazingly fast, struct validation code generator for Go. Valforge generates validation code, eliminating runtime reflection overhead and providing type-safe validation with zero runtime dependencies.
Features
- Code can generated standalone or using
go generate, no reflection or runtime type inspection
- Validation rules are checked at generation time against your struct field types
- Easy to add custom validation rules
- Detailed validation errors with field names and values
- Built-in JSON error formatting
- Support for strings, integers, comparisons, email validation, more will be added
Installation
go install github.com/richardbowden/valforge@latest
Quick Start
package models
type User struct {
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"required,gte=18"`
Password string `json:"password" validate:"required,minlen=8"`
Confirm string `json:"confirm" validate:"required,eqfield=Password"`
}
2. Generate validation code
# Generate for a specific file
valforge -file models/user.go
# Or generate for an entire package
valforge -package ./models
3. Use the generated validation
package main
import (
"fmt"
"your-module/models"
)
func main() {
user := models.User{
Email: "invalid-email",
Age: 15,
Password: "short",
Confirm: "different",
}
if err := user.Validate(); err != nil {
fmt.Println(err)
// Output: User validation failed: email: invalid email format,
// age: age must be greater than or equal to 18,
// password: password must be at least 8 characters,
// confirm: confirm must match Password
}
}
Available Validation Rules
String Rules
| Rule |
Description |
Example |
required |
Field cannot be empty |
validate:"required" |
minlen=N |
Minimum string length |
validate:"minlen=8" |
maxlen=N |
Maximum string length |
validate:"maxlen=100" |
len=N |
Exact string length |
validate:"len=10" |
email |
Valid email format |
validate:"email" |
Numeric Rules
| Rule |
Description |
Example |
required |
Field cannot be zero |
validate:"required" |
gt=N |
Greater than |
validate:"gt=0" |
gte=N |
Greater than or equal |
validate:"gte=18" |
lt=N |
Less than |
validate:"lt=100" |
lte=N |
Less than or equal |
validate:"lte=65" |
Cross-Field Rules
| Rule |
Description |
Example |
eqfield=Field |
Must equal another field |
validate:"eqfield=Password" |
eqfieldsecure=Field |
Constant-time string comparison |
validate:"eqfieldsecure=Password" |
Combining Rules
Rules can be combined using commas:
type Product struct {
Name string `validate:"required,minlen=3,maxlen=100"`
Price int `validate:"required,gt=0,lt=1000000"`
}
Configuration Options
# Specify input file
valforge -file path/to/file.go
# Specify input package
valforge -package ./path/to/package
# Specify output file (auto-generated by default)
valforge -output validation.gen.go
# Customize error package name (default: valgen)
valforge -valforge-package myvalidation
# Customize error package path (default: internal/valgen)
valforge -valforge-path internal/myvalidation
# Show version
valforge -version
Generated Code Structure
Valforge generates two types of files:
1. Validation Methods (*_validation.gen.go)
Contains the Validate() method for each struct:
func (v User) Validate() error {
verr := valgen.NewValidationError("User")
if v.Email == "" {
verr.AddFieldError("email", "email is required", v.Email)
}
// ... more validations
if len(verr.Errors) > 0 {
return verr
}
return nil
}
2. Supporting Package (internal/valgen/)
errors.gen.go: Validation error types with JSON support
emailvalidation.gen.go: Email validation logic
Error Handling
Validation errors provide multiple ways to inspect failures:
if err := user.Validate(); err != nil {
validationErr := err.(*valgen.ValidationError)
// Check if a specific field has an error
if validationErr.HasField("email") {
fmt.Println("Email is invalid")
}
// Get all field errors
for _, fieldErr := range validationErr.Errors {
fmt.Printf("Field: %s, Message: %s, Value: %v\n",
fieldErr.Field, fieldErr.Message, fieldErr.Value)
}
// Get JSON representation
jsonBytes, _ := validationErr.JSON()
fmt.Println(string(jsonBytes))
// {"struct":"User","errors":[{"field":"email","message":"email is required","value":""}]}
}
Integration with Go Generate
Add a //go:generate directive to your Go files:
//go:generate valforge -file $GOFILE
package models
type User struct {
Email string `json:"email" validate:"required,email"`
}
Then run:
go generate ./...
Project Structure
your-project/
├── models/
│ ├── user.go # Your structs with validation tags
│ └── user_validation.gen.go # Generated validation methods
├── internal/
│ └── valgen/ # Generated supporting code
│ ├── errors.gen.go # Validation error types
│ └── emailvalidation.gen.go # Email validation logic
└── main.go
Type Safety
Valforge performs compile-time type checking. Invalid configurations will be caught during generation:
// ❌ This will fail at generation time
type User struct {
Email string `validate:"gt=10"` // Error: gt rule not compatible with string
Age string `validate:"email"` // Error: email rule only works with strings
}
Why Code Generation Over Runtime Reflection?
Valforge uses code generation instead of runtime reflection for several important performance and reliability reasons.
Runtime validation libraries using reflection have significant performance costs on every validation call. Reflection in Go requires:
- Dynamic type assertions and conversions
- Runtime type information lookups
- Interface allocations for accessing struct fields
- Method lookups through reflection
Generated code is just plain Go functions with direct field access. The performance is the same as hand-written validation code.
Reflection-based validators create many heap allocations:
reflect.Value objects for each field access
- Interface boxing and unboxing for primitive types
- Dynamic string allocations for error messages
- Temporary objects for type conversions
These allocations trigger garbage collection more frequently. In high-throughput services processing thousands of requests per second, this GC pressure causes latency spikes and reduced throughput.
Generated code produces predictable memory access patterns that CPUs can optimize through branch prediction and cache prefetching. Reflection-based code has unpredictable branching and pointer chasing that defeats CPU optimizations.
Production Reliability
With code generation, validation rule errors are caught at build time, not in production. Type mismatches, invalid rule parameters, and missing field references become compiler errors rather than runtime panics.
In typical scenarios, generated validation is 10-50x faster than reflection-based validation. The difference becomes more pronounced as struct complexity increases.
For services handling high request volumes (thousands of validations per second), the cumulative effect of zero allocations and no reflection means:
- Predictable, low latency with no GC pauses from validation
- Higher throughput per CPU core
- Lower memory footprint
- Better resource utilization and cost efficiency
Code generation makes validation a zero-cost abstraction, suitable for even the most performance-critical microservices.
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
License
[Add your license here]
Version
Current version: Generated at build time