Validation Package
Complete API reference for the rivaas.dev/validation package
This is the API reference for the rivaas.dev/validation package. For learning-focused documentation, see the Validation Guide.
Overview
The validation package provides flexible validation for Go structs with support for multiple strategies: struct tags, JSON Schema, and custom interfaces. It’s designed for web applications with features like partial validation for PATCH requests, sensitive data redaction, and detailed error reporting.
import "rivaas.dev/validation"
type User struct {
Email string `validate:"required,email"`
Age int `validate:"min=18"`
}
err := validation.Validate(ctx, &user)
Key Features
- Multiple Validation Strategies: Struct tags, JSON Schema, custom interfaces
- Partial Validation: PATCH request support with presence tracking
- Thread-Safe: Safe for concurrent use
- Security: Built-in redaction, nesting limits, memory protection
- Structured Errors: Field-level errors with codes and metadata
- Extensible: Custom tags, validators, and error messages
Package Architecture
graph TB
User[User Code] --> API[Public API]:::info
API --> Validate[Validate Functions]
API --> Validator[Validator Type]
API --> Presence[Presence Tracking]
Validate --> Strategy[Strategy Selection]:::warning
Validator --> Strategy
Strategy --> Tags[Struct Tags]
Strategy --> Schema[JSON Schema]
Strategy --> Interface[Custom Interfaces]
Tags --> Errors[Error Collection]
Schema --> Errors
Interface --> Errors
Errors --> ErrorType[Error/FieldError]:::danger
Presence --> Partial[Partial Validation]
Partial --> Strategy
classDef default fill:#F8FAF9,stroke:#1E6F5C,color:#1F2A27
classDef info fill:#D1ECF1,stroke:#17A2B8,color:#1F2A27
classDef warning fill:#FFF3CD,stroke:#FFC107,color:#1F2A27
classDef danger fill:#F8D7DA,stroke:#DC3545,color:#1F2A27Quick Navigation
Core types, functions, and validation methods.
View →
Configuration options and validator settings.
View →
Custom validation interfaces and providers.
View →
Validation strategy selection and priority.
View →
Common validation issues and solutions.
View →
Step-by-step tutorials and examples.
View →
Core API
Package-Level Functions
Simple validation without creating a validator instance:
// Validate with default configuration
func Validate(ctx context.Context, v any, opts ...Option) error
// Validate only present fields (PATCH requests)
func ValidatePartial(ctx context.Context, v any, pm PresenceMap, opts ...Option) error
// Compute which fields are present in JSON
func ComputePresence(rawJSON []byte) (PresenceMap, error)
Validator Type
Create configured validator instances for reuse:
// Create validator (returns error on invalid config)
func New(opts ...Option) (*Validator, error)
// Create validator (panics on invalid config)
func MustNew(opts ...Option) *Validator
// Validator methods
func (v *Validator) Validate(ctx context.Context, val any, opts ...Option) error
func (v *Validator) ValidatePartial(ctx context.Context, val any, pm PresenceMap, opts ...Option) error
Error Types
Structured validation errors:
// Main error type with multiple field errors
type Error struct {
Fields []FieldError
Truncated bool
}
// Individual field error
type FieldError struct {
Path string // JSON path (e.g., "items.2.price")
Code string // Error code (e.g., "tag.required")
Message string // Human-readable message
Meta map[string]any // Additional metadata
}
Interfaces
Implement these for custom validation:
// Simple custom validation
type ValidatorInterface interface {
Validate() error
}
// Context-aware custom validation
type ValidatorWithContext interface {
ValidateContext(context.Context) error
}
// JSON Schema provider
type JSONSchemaProvider interface {
JSONSchema() (id, schema string)
}
// Redactor for sensitive fields
type Redactor func(path string) bool
Validation Strategies
The package supports three strategies with automatic selection:
Priority Order:
- Interface methods (
Validate() / ValidateContext()) - Struct tags (
validate:"...") - JSON Schema (
JSONSchemaProvider)
// Automatic strategy selection
err := validation.Validate(ctx, &user)
// Explicit strategy
err := validation.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyTags),
)
// Run all strategies
err := validation.Validate(ctx, &user,
validation.WithRunAll(true),
)
Configuration Options
Validator Creation Options
validator := validation.MustNew(
validation.WithMaxErrors(10), // Limit errors returned
validation.WithMaxCachedSchemas(2048), // Schema cache size
validation.WithRedactor(redactorFunc), // Redact sensitive fields
validation.WithCustomTag("phone", phoneValidator), // Custom tag
validation.WithMessages(messageMap), // Custom error messages
validation.WithMessageFunc("min", minFunc), // Dynamic messages
)
Per-Call Options
err := validator.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyTags),
validation.WithPartial(true),
validation.WithPresence(presenceMap),
validation.WithMaxErrors(5),
validation.WithCustomValidator(customFunc),
)
Usage Patterns
Basic Validation
type User struct {
Email string `validate:"required,email"`
Age int `validate:"min=18"`
}
user := User{Email: "test@example.com", Age: 25}
if err := validation.Validate(ctx, &user); err != nil {
var verr *validation.Error
if errors.As(err, &verr) {
for _, fieldErr := range verr.Fields {
fmt.Printf("%s: %s\n", fieldErr.Path, fieldErr.Message)
}
}
}
Partial Validation (PATCH)
rawJSON := []byte(`{"email": "new@example.com"}`)
presence, _ := validation.ComputePresence(rawJSON)
var req UpdateUserRequest
json.Unmarshal(rawJSON, &req)
err := validation.ValidatePartial(ctx, &req, presence)
Custom Validator
validator := validation.MustNew(
validation.WithCustomTag("phone", func(fl validator.FieldLevel) bool {
return phoneRegex.MatchString(fl.Field().String())
}),
validation.WithRedactor(func(path string) bool {
return strings.Contains(path, "password")
}),
)
err := validator.Validate(ctx, &user)
- First validation: ~500ns overhead for reflection
- Subsequent validations: ~50ns overhead (cache lookup)
- Schema caching: LRU with configurable size (default 1024)
- Thread-safe: All operations safe for concurrent use
- Zero allocation: Field paths cached per type
Integration
With net/http
func Handler(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
json.NewDecoder(r.Body).Decode(&req)
if err := validation.Validate(r.Context(), &req); err != nil {
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
return
}
// Process request
}
With rivaas.dev/router
func Handler(c *router.Context) error {
var req CreateUserRequest
c.BindJSON(&req)
if err := validation.Validate(c.Request().Context(), &req); err != nil {
return c.JSON(http.StatusUnprocessableEntity, err)
}
return c.JSON(http.StatusOK, processRequest(req))
}
With rivaas.dev/app
func Handler(c *app.Context) error {
var req CreateUserRequest
if err := c.Bind(&req); err != nil {
return err // Automatically validated and handled
}
return c.JSON(http.StatusOK, processRequest(req))
}
Version Compatibility
The validation package follows semantic versioning:
- v1.x: Stable API, backward compatible
- v2.x: Major changes, may require code updates
External Links
See Also
For step-by-step guides and tutorials, see the Validation Guide.
For real-world examples, see the Examples page.
1 - API Reference
Core types, functions, and methods
Complete API reference for the validation package’s core types, functions, and methods.
Package-Level Functions
Validate
func Validate(ctx context.Context, v any, opts ...Option) error
Validates a value using the default validator. Returns nil if validation passes, or *Error if validation fails.
Parameters:
ctx - Context passed to ValidatorWithContext implementations.v - The value to validate. Typically a pointer to a struct.opts - Optional per-call configuration options.
Returns:
nil on success.*Error with field-level errors on failure.
Example:
err := validation.Validate(ctx, &user)
if err != nil {
var verr *validation.Error
if errors.As(err, &verr) {
// Handle validation errors
}
}
ValidatePartial
func ValidatePartial(ctx context.Context, v any, pm PresenceMap, opts ...Option) error
Validates only fields present in the PresenceMap. Useful for PATCH requests where only provided fields should be validated.
Parameters:
ctx - Context for validation.v - The value to validate.pm - Map of present fields.opts - Optional configuration options.
Example:
presence, _ := validation.ComputePresence(rawJSON)
err := validation.ValidatePartial(ctx, &req, presence)
ComputePresence
func ComputePresence(rawJSON []byte) (PresenceMap, error)
Analyzes raw JSON and returns a map of present field paths. Used for partial validation.
Parameters:
rawJSON - Raw JSON bytes.
Returns:
PresenceMap - Map of field paths to true.error - If JSON is invalid.
Example:
rawJSON := []byte(`{"email": "test@example.com", "age": 25}`)
presence, err := validation.ComputePresence(rawJSON)
// presence = {"email": true, "age": true}
Validator Type
New
func New(opts ...Option) (*Validator, error)
Creates a new Validator with the given options. Returns an error if configuration is invalid.
Parameters:
opts - Configuration options
Returns:
*Validator - Configured validator instanceerror - If configuration is invalid
Example:
validator, err := validation.New(
validation.WithMaxErrors(10),
validation.WithRedactor(redactor),
)
if err != nil {
return fmt.Errorf("failed to create validator: %w", err)
}
MustNew
func MustNew(opts ...Option) *Validator
Creates a new Validator with the given options. Panics if configuration is invalid. Use in main() or init() where panic on startup is acceptable.
Parameters:
opts - Configuration options
Returns:
*Validator - Configured validator instance
Panics:
- If configuration is invalid
Example:
var validator = validation.MustNew(
validation.WithMaxErrors(10),
validation.WithRedactor(redactor),
)
Validator.Validate
func (v *Validator) Validate(ctx context.Context, val any, opts ...Option) error
Validates a value using this validator’s configuration. Per-call options override the validator’s base configuration.
Parameters:
ctx - Context for validationval - The value to validateopts - Optional per-call configuration overrides
Returns:
nil on success*Error on failure
Example:
err := validator.Validate(ctx, &user,
validation.WithMaxErrors(5), // Override base config
)
Validator.ValidatePartial
func (v *Validator) ValidatePartial(ctx context.Context, val any, pm PresenceMap, opts ...Option) error
Validates only fields present in the PresenceMap using this validator’s configuration.
Parameters:
ctx - Context for validationval - The value to validatepm - Map of present fieldsopts - Optional configuration overrides
Returns:
nil on success*Error on failure
Error Types
Error
type Error struct {
Fields []FieldError
Truncated bool
}
Main validation error type containing multiple field errors.
Fields:
Fields - Slice of field-level errorsTruncated - True if errors were truncated due to maxErrors limit
Methods:
func (e Error) Error() string
func (e Error) Unwrap() error // Returns ErrValidation
func (e Error) HTTPStatus() int // Returns 422
func (e Error) Code() string // Returns "validation_error"
func (e Error) Details() any // Returns Fields
func (e *Error) Add(path, code, message string, meta map[string]any)
func (e *Error) AddError(err error)
func (e Error) HasErrors() bool
func (e Error) HasCode(code string) bool
func (e Error) Has(path string) bool
func (e Error) GetField(path string) *FieldError
func (e *Error) Sort()
Example:
var verr *validation.Error
if errors.As(err, &verr) {
fmt.Printf("Found %d errors\n", len(verr.Fields))
if verr.Truncated {
fmt.Println("(more errors exist)")
}
if verr.Has("email") {
fmt.Println("Email field has an error")
}
}
FieldError
type FieldError struct {
Path string
Code string
Message string
Meta map[string]any
}
Individual field validation error.
Fields:
Path - JSON path to the field (e.g., "items.2.price")Code - Stable error code (e.g., "tag.required", "schema.type")Message - Human-readable error messageMeta - Additional metadata (tag, param, value, etc.)
Methods:
func (e FieldError) Error() string // Returns "path: message"
func (e FieldError) Unwrap() error // Returns ErrValidation
func (e FieldError) HTTPStatus() int // Returns 422
Example:
for _, fieldErr := range verr.Fields {
fmt.Printf("Field: %s\n", fieldErr.Path)
fmt.Printf("Code: %s\n", fieldErr.Code)
fmt.Printf("Message: %s\n", fieldErr.Message)
if tag, ok := fieldErr.Meta["tag"].(string); ok {
fmt.Printf("Tag: %s\n", tag)
}
}
PresenceMap Type
type PresenceMap map[string]bool
Tracks which fields are present in a request body. Keys are JSON field paths.
Methods:
func (pm PresenceMap) Has(path string) bool
func (pm PresenceMap) HasPrefix(prefix string) bool
func (pm PresenceMap) LeafPaths() []string
Example:
presence := PresenceMap{
"email": true,
"address": true,
"address.city": true,
}
if presence.Has("email") {
// Email was provided
}
if presence.HasPrefix("address") {
// At least one address field was provided
}
leaves := presence.LeafPaths()
// Returns: ["email", "address.city"]
// (address is excluded as it has children)
Strategy Type
type Strategy int
const (
StrategyAuto Strategy = iota
StrategyTags
StrategyJSONSchema
StrategyInterface
)
Defines the validation approach to use.
Constants:
StrategyAuto - Automatically select best strategy (default)StrategyTags - Use struct tag validationStrategyJSONSchema - Use JSON Schema validationStrategyInterface - Use interface methods (Validate() / ValidateContext())
Example:
err := validation.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyTags),
)
Sentinel Errors
var (
ErrValidation = errors.New("validation")
ErrCannotValidateNilValue = errors.New("cannot validate nil value")
ErrCannotValidateInvalidValue = errors.New("cannot validate invalid value")
ErrUnknownValidationStrategy = errors.New("unknown validation strategy")
ErrValidationFailed = errors.New("validation failed")
ErrInvalidType = errors.New("invalid type")
)
Sentinel errors for error checking with errors.Is.
Example:
if errors.Is(err, validation.ErrValidation) {
// This is a validation error
}
Type Definitions
Option
type Option func(*config)
Functional option for configuring validation. See Options for all available options.
Redactor
type Redactor func(path string) bool
Function that determines if a field should be redacted in error messages. Returns true if the field at the given path should have its value hidden.
Example:
redactor := func(path string) bool {
return strings.Contains(path, "password") ||
strings.Contains(path, "token")
}
validator := validation.MustNew(
validation.WithRedactor(redactor),
)
MessageFunc
type MessageFunc func(param string, kind reflect.Kind) string
Generates dynamic error messages for parameterized validation tags. Receives the tag parameter and field’s reflect.Kind.
Example:
minMessage := func(param string, kind reflect.Kind) string {
if kind == reflect.String {
return fmt.Sprintf("must be at least %s characters", param)
}
return fmt.Sprintf("must be at least %s", param)
}
validator := validation.MustNew(
validation.WithMessageFunc("min", minMessage),
)
Constants
const (
defaultMaxCachedSchemas = 1024
maxRecursionDepth = 100
)
defaultMaxCachedSchemas - Default JSON Schema cache sizemaxRecursionDepth - Maximum nesting depth for ComputePresence
Thread Safety
All types and functions in the validation package are safe for concurrent use by multiple goroutines:
Validator instances are thread-safe- Package-level functions use a shared thread-safe default validator
PresenceMap is read-only after creation (safe for concurrent reads)
- First validation of a type: ~500ns overhead for reflection
- Subsequent validations: ~50ns overhead (cache lookup)
- Schema compilation: Cached with LRU eviction
- Path computation: Cached per type
- Zero allocations: For cached types
Next Steps
2 - Options
Configuration options for validators
Complete reference for all configuration options (With* functions) available in the validation package.
Option Types
Options can be used in two ways:
- Validator Creation: Pass to
New() or MustNew(). Applies to all validations. - Per-Call: Pass to
Validate() or ValidatePartial(). Applies to that call only.
// Validator creation options
validator := validation.MustNew(
validation.WithMaxErrors(10),
validation.WithRedactor(redactor),
)
// Per-call options (override validator config)
err := validator.Validate(ctx, &req,
validation.WithMaxErrors(5), // Overrides the 10 from creation
validation.WithStrategy(validation.StrategyTags),
)
Strategy Options
WithStrategy
func WithStrategy(strategy Strategy) Option
Sets the validation strategy to use.
Values:
StrategyAuto - Automatically select best strategy. This is the default.StrategyTags - Use struct tags only.StrategyJSONSchema - Use JSON Schema only.StrategyInterface - Use interface methods only.
Example:
err := validation.Validate(ctx, &req,
validation.WithStrategy(validation.StrategyTags),
)
WithRunAll
func WithRunAll(runAll bool) Option
Runs all applicable validation strategies and aggregates errors. By default, validation stops at the first successful strategy.
Example:
err := validation.Validate(ctx, &req,
validation.WithRunAll(true),
)
WithRequireAny
func WithRequireAny(require bool) Option
When used with WithRunAll(true), succeeds if at least one strategy passes (OR logic).
Example:
// Pass if ANY strategy succeeds
err := validation.Validate(ctx, &req,
validation.WithRunAll(true),
validation.WithRequireAny(true),
)
Partial Validation Options
WithPartial
func WithPartial(partial bool) Option
Enables partial validation mode for PATCH requests. Validates only present fields and ignores “required” constraints for absent fields.
Example:
err := validation.Validate(ctx, &req,
validation.WithPartial(true),
validation.WithPresence(presenceMap),
)
WithPresence
func WithPresence(presence PresenceMap) Option
Sets the presence map for partial validation. Tracks which fields were provided in the request body.
Example:
presence, _ := validation.ComputePresence(rawJSON)
err := validation.Validate(ctx, &req,
validation.WithPresence(presence),
validation.WithPartial(true),
)
Error Limit Options
WithMaxErrors
func WithMaxErrors(maxErrors int) Option
Limits the number of errors returned. Set to 0 for unlimited errors (default).
Example:
// Return at most 5 errors
err := validation.Validate(ctx, &req,
validation.WithMaxErrors(5),
)
var verr *validation.Error
if errors.As(err, &verr) {
if verr.Truncated {
fmt.Println("More errors exist")
}
}
WithMaxFields
func WithMaxFields(maxFields int) Option
Sets the maximum number of fields to validate in partial mode. Prevents pathological inputs with extremely large presence maps. Set to 0 to use the default (10000).
Example:
validator := validation.MustNew(
validation.WithMaxFields(5000),
)
Cache Options
WithMaxCachedSchemas
func WithMaxCachedSchemas(maxCachedSchemas int) Option
Sets the maximum number of JSON schemas to cache. Uses LRU eviction when limit is reached. Set to 0 to use the default (1024).
Example:
validator := validation.MustNew(
validation.WithMaxCachedSchemas(2048),
)
Security Options
WithRedactor
func WithRedactor(redactor Redactor) Option
Sets a redactor function to hide sensitive values in error messages. The redactor returns true if the field at the given path should be redacted.
Example:
redactor := func(path string) bool {
return strings.Contains(path, "password") ||
strings.Contains(path, "token") ||
strings.Contains(path, "secret")
}
validator := validation.MustNew(
validation.WithRedactor(redactor),
)
WithDisallowUnknownFields
func WithDisallowUnknownFields(disallow bool) Option
Rejects JSON with unknown fields (typo detection). When enabled, causes strict JSON binding to reject requests with fields not defined in the struct.
Example:
err := validation.Validate(ctx, &req,
validation.WithDisallowUnknownFields(true),
)
Context Options
WithContext
func WithContext(ctx context.Context) Option
Overrides the context used for validation. Useful when you need a different context than the one passed to Validate().
Note: In most cases, you should pass the context directly to Validate(). This option exists for advanced use cases.
Example:
err := validator.Validate(requestCtx, &req,
validation.WithContext(backgroundCtx),
)
Custom Validation Options
WithCustomSchema
func WithCustomSchema(id, schema string) Option
Sets a custom JSON Schema for validation. This overrides any schema provided by the JSONSchemaProvider interface.
Example:
customSchema := `{
"type": "object",
"properties": {
"email": {"type": "string", "format": "email"}
}
}`
err := validation.Validate(ctx, &req,
validation.WithCustomSchema("custom-user", customSchema),
)
WithCustomValidator
func WithCustomValidator(fn func(any) error) Option
Sets a custom validation function that runs before any other validation strategies.
Example:
err := validation.Validate(ctx, &req,
validation.WithCustomValidator(func(v any) error {
req := v.(*UserRequest)
if req.Age < 18 {
return errors.New("must be 18 or older")
}
return nil
}),
)
WithCustomTag
func WithCustomTag(name string, fn validator.Func) Option
Registers a custom validation tag for use in struct tags. Custom tags are registered when the validator is created.
Example:
phoneValidator := func(fl validator.FieldLevel) bool {
return phoneRegex.MatchString(fl.Field().String())
}
validator := validation.MustNew(
validation.WithCustomTag("phone", phoneValidator),
)
type User struct {
Phone string `validate:"phone"`
}
Error Message Options
WithMessages
func WithMessages(messages map[string]string) Option
Sets static error messages for validation tags. Messages override the default English messages for specified tags.
Example:
validator := validation.MustNew(
validation.WithMessages(map[string]string{
"required": "cannot be empty",
"email": "invalid email format",
"min": "value too small",
}),
)
WithMessageFunc
func WithMessageFunc(tag string, fn MessageFunc) Option
Sets a dynamic message generator for a parameterized tag. Use for tags like “min”, “max”, “len” that include parameters.
Example:
minMessage := func(param string, kind reflect.Kind) string {
if kind == reflect.String {
return fmt.Sprintf("must be at least %s characters", param)
}
return fmt.Sprintf("must be at least %s", param)
}
validator := validation.MustNew(
validation.WithMessageFunc("min", minMessage),
)
Field Name Options
WithFieldNameMapper
func WithFieldNameMapper(mapper func(string) string) Option
Sets a function to transform field names in error messages. Useful for localization or custom naming conventions.
Example:
validator := validation.MustNew(
validation.WithFieldNameMapper(func(name string) string {
// Convert snake_case to Title Case
return strings.Title(strings.ReplaceAll(name, "_", " "))
}),
)
Options Summary
Validator Creation Options
Options that should be set when creating a validator (affect all validations):
| Option | Purpose |
|---|
WithMaxErrors | Limit total errors returned |
WithMaxFields | Limit fields in partial validation |
WithMaxCachedSchemas | Schema cache size |
WithRedactor | Redact sensitive fields |
WithCustomTag | Register custom validation tag |
WithMessages | Custom error messages |
WithMessageFunc | Dynamic error messages |
WithFieldNameMapper | Transform field names |
Per-Call Options
Options commonly used per-call (override validator config):
| Option | Purpose |
|---|
WithStrategy | Choose validation strategy |
WithRunAll | Run all strategies |
WithRequireAny | OR logic with WithRunAll |
WithPartial | Enable partial validation |
WithPresence | Set presence map |
WithMaxErrors | Override error limit |
WithCustomValidator | Add custom validator |
WithCustomSchema | Override JSON Schema |
WithDisallowUnknownFields | Reject unknown fields |
WithContext | Override context |
Usage Patterns
validator := validation.MustNew(
// Security
validation.WithRedactor(sensitiveRedactor),
validation.WithMaxErrors(20),
validation.WithMaxFields(5000),
// Custom validation
validation.WithCustomTag("phone", phoneValidator),
validation.WithCustomTag("username", usernameValidator),
// Error messages
validation.WithMessages(map[string]string{
"required": "is required",
"email": "must be a valid email",
}),
)
Per-Call Overrides
// Use tags strategy only
err := validator.Validate(ctx, &req,
validation.WithStrategy(validation.StrategyTags),
validation.WithMaxErrors(5),
)
// Partial validation
err := validator.Validate(ctx, &req,
validation.WithPartial(true),
validation.WithPresence(presence),
)
// Custom validation
err := validator.Validate(ctx, &req,
validation.WithCustomValidator(complexBusinessLogic),
)
Next Steps
3 - Interfaces
Custom validation interfaces
Complete reference for validation interfaces that can be implemented for custom validation logic.
ValidatorInterface
type ValidatorInterface interface {
Validate() error
}
Implement this interface for simple custom validation without context.
When to Use
- Simple validation rules that don’t need external data
- Business logic validation
- Cross-field validation within the struct
Implementation
type User struct {
Email string
Name string
}
func (u *User) Validate() error {
if !strings.Contains(u.Email, "@") {
return errors.New("email must contain @")
}
if len(u.Name) < 2 {
return errors.New("name must be at least 2 characters")
}
return nil
}
Returning Structured Errors
Return *validation.Error for detailed field-level errors:
func (u *User) Validate() error {
var verr validation.Error
if !strings.Contains(u.Email, "@") {
verr.Add("email", "format", "must contain @", nil)
}
if len(u.Name) < 2 {
verr.Add("name", "length", "must be at least 2 characters", nil)
}
if verr.HasErrors() {
return &verr
}
return nil
}
Pointer vs Value Receivers
Both are supported:
// Pointer receiver (can modify struct)
func (u *User) Validate() error {
u.Email = strings.ToLower(u.Email) // Normalize
return validateEmail(u.Email)
}
// Value receiver (read-only)
func (u User) Validate() error {
return validateEmail(u.Email)
}
Use pointer receivers when you need to modify the struct during validation (normalization, etc.).
ValidatorWithContext
type ValidatorWithContext interface {
ValidateContext(context.Context) error
}
Implement this interface for context-aware validation that needs access to request-scoped data or external services.
When to Use
- Database lookups (uniqueness checks, existence validation)
- Tenant-specific validation rules
- Rate limiting or quota checks
- External service calls
- Request-scoped data access
Implementation
type User struct {
Username string
Email string
TenantID string
}
func (u *User) ValidateContext(ctx context.Context) error {
// Get services from context
db := ctx.Value("db").(*sql.DB)
tenant := ctx.Value("tenant").(string)
// Tenant validation
if u.TenantID != tenant {
return errors.New("user does not belong to this tenant")
}
// Database validation
var exists bool
err := db.QueryRowContext(ctx,
"SELECT EXISTS(SELECT 1 FROM users WHERE username = $1)",
u.Username,
).Scan(&exists)
if err != nil {
return fmt.Errorf("failed to check username: %w", err)
}
if exists {
return errors.New("username already taken")
}
return nil
}
Context Values
Access data from context:
func (u *User) ValidateContext(ctx context.Context) error {
// Database connection
db := ctx.Value("db").(*sql.DB)
// Current user/tenant
currentUser := ctx.Value("user_id").(string)
tenant := ctx.Value("tenant").(string)
// Request metadata
requestID := ctx.Value("request_id").(string)
// Use in validation logic
return validateWithContext(db, u, tenant)
}
Cancellation Support
Respect context cancellation for long-running validations:
func (u *User) ValidateContext(ctx context.Context) error {
// Check cancellation before expensive operation
select {
case <-ctx.Done():
return ctx.Err()
default:
}
// Expensive validation
return checkUsernameUniqueness(ctx, u.Username)
}
JSONSchemaProvider
type JSONSchemaProvider interface {
JSONSchema() (id, schema string)
}
Implement this interface to provide a JSON Schema for validation.
When to Use
- Portable validation rules (shared with frontend/documentation)
- Complex validation logic without code
- RFC-compliant validation
- Schema versioning
Implementation
type Product struct {
Name string `json:"name"`
Price float64 `json:"price"`
Category string `json:"category"`
}
func (p Product) JSONSchema() (id, schema string) {
return "product-v1", `{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"price": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "books"]
}
},
"required": ["name", "price", "category"],
"additionalProperties": false
}`
}
Schema ID
The ID is used for caching:
func (p Product) JSONSchema() (id, schema string) {
return "product-v1", schemaString
// ^^^^^^^^^^^ Used as cache key
}
Use versioned IDs (e.g., "product-v1", "product-v2") to invalidate cache when schema changes.
Supported formats:
email - Email addressuri / url - URLhostname - DNS hostnameipv4 / ipv6 - IP addressesdate - Date (YYYY-MM-DD)date-time - RFC3339 date-timeuuid - UUID
Embedded Schemas
For complex schemas, consider embedding:
import _ "embed"
//go:embed user_schema.json
var userSchemaJSON string
func (u User) JSONSchema() (id, schema string) {
return "user-v1", userSchemaJSON
}
Redactor
type Redactor func(path string) bool
Function that determines if a field should be redacted in error messages.
When to Use
- Protecting passwords, tokens, secrets
- Hiding credit card numbers, SSNs
- Redacting PII (personally identifiable information)
- Compliance requirements (GDPR, PCI-DSS)
Implementation
func sensitiveFieldRedactor(path string) bool {
pathLower := strings.ToLower(path)
// Password fields
if strings.Contains(pathLower, "password") {
return true
}
// Tokens and secrets
if strings.Contains(pathLower, "token") ||
strings.Contains(pathLower, "secret") ||
strings.Contains(pathLower, "key") {
return true
}
// Payment information
if strings.Contains(pathLower, "card") ||
strings.Contains(pathLower, "cvv") ||
strings.Contains(pathLower, "credit") {
return true
}
return false
}
validator := validation.MustNew(
validation.WithRedactor(sensitiveFieldRedactor),
)
Path-Based Redaction
Redact specific paths:
func pathRedactor(path string) bool {
redactedPaths := map[string]bool{
"user.password": true,
"payment.card_number": true,
"payment.cvv": true,
"auth.refresh_token": true,
}
return redactedPaths[path]
}
Nested Field Redaction
func nestedRedactor(path string) bool {
// Redact all fields under payment.*
if strings.HasPrefix(path, "payment.") {
return true
}
// Redact specific nested field
if strings.HasPrefix(path, "user.credentials.") {
return true
}
return false
}
Interface Priority
When multiple interfaces are implemented, they have different priorities:
Priority Order:
ValidatorWithContext / ValidatorInterface (highest)- Struct tags (
validate:"...") JSONSchemaProvider (lowest)
type User struct {
Email string `validate:"required,email"` // Priority 2
}
func (u User) JSONSchema() (id, schema string) {
// Priority 3 (lowest)
return "user-v1", `{...}`
}
func (u *User) Validate() error {
// Priority 1 (highest) - this runs instead of tags/schema
return customValidation(u.Email)
}
Override priority with explicit strategy:
// Skip Validate() method, use tags
err := validation.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyTags),
)
Combining Interfaces
Run all strategies with WithRunAll:
type User struct {
Email string `validate:"required,email"` // Struct tags
}
func (u User) JSONSchema() (id, schema string) {
// JSON Schema
return "user-v1", `{...}`
}
func (u *User) Validate() error {
// Interface method
return businessLogic(u)
}
// Run all three strategies
err := validation.Validate(ctx, &user,
validation.WithRunAll(true),
)
Best Practices
1. Choose the Right Interface
// Simple validation - ValidatorInterface
func (u *User) Validate() error {
return validateEmail(u.Email)
}
// Needs external data - ValidatorWithContext
func (u *User) ValidateContext(ctx context.Context) error {
db := ctx.Value("db").(*sql.DB)
return checkUniqueness(ctx, db, u.Email)
}
2. Return Structured Errors
// Good
func (u *User) Validate() error {
var verr validation.Error
verr.Add("email", "invalid", "must be valid email", nil)
return &verr
}
// Bad
func (u *User) Validate() error {
return errors.New("email invalid")
}
3. Use Context Safely
func (u *User) ValidateContext(ctx context.Context) error {
db, ok := ctx.Value("db").(*sql.DB)
if !ok {
return errors.New("database not available in context")
}
return validateWithDB(ctx, db, u)
}
4. Document Custom Validation
// ValidateContext validates the user against business rules:
// - Username must be unique within tenant
// - Email domain must be allowed for tenant
// - User must not exceed account limits
func (u *User) ValidateContext(ctx context.Context) error {
// Implementation
}
Testing
Testing ValidatorInterface
func TestUserValidation(t *testing.T) {
tests := []struct {
name string
user User
wantErr bool
}{
{"valid", User{Email: "test@example.com"}, false},
{"invalid", User{Email: "invalid"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.user.Validate()
if (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
Testing ValidatorWithContext
func TestUserValidationWithContext(t *testing.T) {
ctx := context.Background()
ctx = context.WithValue(ctx, "db", mockDB)
ctx = context.WithValue(ctx, "tenant", "test-tenant")
user := User{Username: "testuser"}
err := user.ValidateContext(ctx)
if err != nil {
t.Errorf("ValidateContext() error = %v", err)
}
}
Next Steps
4 - Validation Strategies
Strategy selection and priority
Complete reference for validation strategies, automatic selection, and priority order.
Overview
The validation package supports three validation strategies that can be used individually or combined:
- Interface Methods -
Validate() / ValidateContext() - Struct Tags -
validate:"..." tags - JSON Schema -
JSONSchemaProvider interface
Strategy Types
StrategyAuto
const StrategyAuto Strategy = iota
Automatically selects the best strategy based on the type (default behavior).
Priority Order:
- Interface methods (highest priority)
- Struct tags
- JSON Schema (lowest priority)
Example:
// Uses automatic strategy selection
err := validation.Validate(ctx, &user)
const StrategyTags Strategy = ...
Uses struct tag validation with go-playground/validator.
Requirements:
- Struct type
- Fields with
validate tags
Example:
type User struct {
Email string `validate:"required,email"`
}
err := validation.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyTags),
)
StrategyJSONSchema
const StrategyJSONSchema Strategy = ...
Uses JSON Schema validation (RFC-compliant).
Requirements:
- Type implements
JSONSchemaProvider interface, OR - Custom schema provided with
WithCustomSchema
Example:
func (u User) JSONSchema() (id, schema string) {
return "user-v1", `{...}`
}
err := validation.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyJSONSchema),
)
StrategyInterface
const StrategyInterface Strategy = ...
Uses custom interface methods (Validate() or ValidateContext()).
Requirements:
- Type implements
ValidatorInterface or ValidatorWithContext
Example:
func (u *User) Validate() error {
return customValidation(u)
}
err := validation.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyInterface),
)
Automatic Strategy Selection
Selection Process
When StrategyAuto is used (default), the validator checks strategies in priority order:
graph TD
Start[Start Validation] --> CheckInterface{Implements<br/>ValidatorInterface or<br/>ValidatorWithContext?}
CheckInterface -->|Yes| UseInterface[Use Interface Strategy]:::success
CheckInterface -->|No| CheckTags{Has validate<br/>tags?}
CheckTags -->|Yes| UseTags[Use Tags Strategy]:::warning
CheckTags -->|No| CheckSchema{Implements<br/>JSONSchemaProvider?}
CheckSchema -->|Yes| UseSchema[Use JSON Schema Strategy]:::info
CheckSchema -->|No| DefaultTags[Default to Tags]
UseInterface --> Done[Validate]
UseTags --> Done
UseSchema --> Done
DefaultTags --> Done
classDef default fill:#F8FAF9,stroke:#1E6F5C,color:#1F2A27
classDef info fill:#D1ECF1,stroke:#17A2B8,color:#1F2A27
classDef success fill:#D4EDDA,stroke:#28A745,color:#1F2A27
classDef warning fill:#FFF3CD,stroke:#FFC107,color:#1F2A27Applicability Checks
A strategy is considered “applicable” if:
Interface Strategy:
- Type implements
ValidatorInterface or ValidatorWithContext - Checks both value and pointer receivers
Tags Strategy:
- Type is a struct
- At least one field has a
validate tag
JSON Schema Strategy:
- Type implements
JSONSchemaProvider, OR - Custom schema provided with
WithCustomSchema
Priority Examples
// Example 1: Only interface method
type User struct {
Email string
}
func (u *User) Validate() error {
return validateEmail(u.Email)
}
// Uses: StrategyInterface (highest priority)
validation.Validate(ctx, &user)
// Example 2: Both interface and tags
type User struct {
Email string `validate:"required,email"`
}
func (u *User) Validate() error {
return customLogic(u.Email)
}
// Uses: StrategyInterface (interface has priority over tags)
validation.Validate(ctx, &user)
// Example 3: All three strategies
type User struct {
Email string `validate:"required,email"`
}
func (u User) JSONSchema() (id, schema string) {
return "user-v1", `{...}`
}
func (u *User) Validate() error {
return customLogic(u.Email)
}
// Uses: StrategyInterface (highest priority)
validation.Validate(ctx, &user)
Explicit Strategy Selection
Override automatic selection with WithStrategy:
type User struct {
Email string `validate:"required,email"` // Has tags
}
func (u *User) Validate() error {
return customLogic(u.Email) // Has interface method
}
// Force use of tags (skip interface method)
err := validation.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyTags),
)
Running Multiple Strategies
WithRunAll
Run all applicable strategies and aggregate errors:
type User struct {
Email string `validate:"required,email"` // Tags
}
func (u User) JSONSchema() (id, schema string) {
return "user-v1", `{...}` // JSON Schema
}
func (u *User) Validate() error {
return customLogic(u.Email) // Interface
}
// Run all three strategies
err := validation.Validate(ctx, &user,
validation.WithRunAll(true),
)
// All errors from all strategies are collected
var verr *validation.Error
if errors.As(err, &verr) {
// verr.Fields contains errors from all strategies
}
WithRequireAny
With WithRunAll, succeed if any one strategy passes (OR logic):
// Pass if ANY strategy succeeds
err := validation.Validate(ctx, &user,
validation.WithRunAll(true),
validation.WithRequireAny(true),
)
Use Cases:
- Multiple validation approaches, any one is sufficient
- Fallback validation strategies
- Gradual migration between strategies
Strategy Comparison
| Strategy | Advantages | Disadvantages | Best For |
|---|
| Interface | Most flexible, full programmatic control | More code, not declarative | Complex business logic, database checks |
| Tags | Concise, declarative, well-documented | Limited to supported tags | Standard validation, simple rules |
| JSON Schema | Portable, language-independent | Verbose, learning curve | Shared validation with frontend |
Strategy Patterns
type User struct {
Email string `validate:"required,email"`
Username string `validate:"required,min=3,max=20"`
Age int `validate:"required,min=18"`
}
func (u *User) ValidateContext(ctx context.Context) error {
// Complex validation (database checks, etc.)
db := ctx.Value("db").(*sql.DB)
return checkUsernameUnique(ctx, db, u.Username)
}
// Tags validate format, interface validates business rules
validation.Validate(ctx, &user)
Pattern 2: Schema for API, Interface for Internal
func (u User) JSONSchema() (id, schema string) {
// For external API documentation/validation
return "user-v1", apiSchema
}
func (u *User) ValidateContext(ctx context.Context) error {
// Internal business rules
return validateInternal(ctx, u)
}
// External API: use schema
validation.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyJSONSchema),
)
// Internal: use interface
validation.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyInterface),
)
Pattern 3: Progressive Enhancement
// Start with tags
type User struct {
Email string `validate:"required,email"`
}
// Add interface for complex validation later
func (u *User) ValidateContext(ctx context.Context) error {
// Complex validation added over time
return additionalValidation(ctx, u)
}
// Automatically uses interface (higher priority)
validation.Validate(ctx, &user)
Fastest to Slowest:
- Tags - Cached reflection, zero allocation after first use
- Interface - Direct method call, user code performance
- JSON Schema - Schema compilation (cached), RFC validation
Optimization Tips
// Fast: Use tags for simple validation
type User struct {
Email string `validate:"required,email"`
}
// Slower: JSON Schema (first time, then cached)
func (u User) JSONSchema() (id, schema string) {
return "user-v1", complexSchema
}
// Variable: Depends on your implementation
func (u *User) ValidateContext(ctx context.Context) error {
// Keep this fast - runs every time
return quickValidation(u)
}
Caching
- Tags: Struct reflection cached per type
- JSON Schema: Schemas cached by ID (LRU eviction)
- Interface: No caching (direct method call)
Error Aggregation
When running multiple strategies:
err := validation.Validate(ctx, &user,
validation.WithRunAll(true),
)
var verr *validation.Error
if errors.As(err, &verr) {
// Errors are aggregated from all strategies
for _, fieldErr := range verr.Fields {
fmt.Printf("%s: %s (from %s strategy)\n",
fieldErr.Path,
fieldErr.Message,
inferStrategy(fieldErr.Code), // tag.*, schema.*, etc.
)
}
// Sort for consistent output
verr.Sort()
}
Error codes indicate strategy:
tag.* - From struct tagsschema.* - From JSON Schema- Custom codes - From interface methods
Best Practices
1. Use Automatic Selection
// Good - let validator choose
validation.Validate(ctx, &user)
// Only override when necessary
validation.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyTags),
)
2. Single Strategy Per Type
// Good - clear which strategy is used
type User struct {
Email string `validate:"required,email"`
}
// Confusing - multiple strategies compete
type User struct {
Email string `validate:"required,email"`
}
func (u User) JSONSchema() (id, schema string) { ... }
func (u *User) Validate() error { ... }
3. Document Strategy Choice
// User validation uses struct tags for simplicity and performance.
// Email format and length are validated declaratively.
type User struct {
Email string `validate:"required,email,max=255"`
}
4. Use WithRunAll Sparingly
// Most cases: automatic selection is sufficient
validation.Validate(ctx, &user)
// Only when you need to validate with multiple strategies
validation.Validate(ctx, &user,
validation.WithRunAll(true),
)
Next Steps
5 - Troubleshooting
Common issues and solutions
Common issues, solutions, and debugging tips for the validation package.
Validation Not Running
Issue: Validation passes when it should fail
Symptom:
type User struct {
Email string `validate:"required,email"`
}
user := User{Email: ""} // Should fail
err := validation.Validate(ctx, &user) // err is nil (unexpected)
Possible Causes:
- Struct tags not being checked
Check if a higher-priority strategy is being used:
func (u *User) Validate() error {
return nil // This runs instead of tags
}
Solution: Remove interface method or use WithStrategy:
err := validation.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyTags),
)
- Wrong tag name
// Wrong
Email string `validation:"required"` // Should be "validate"
// Correct
Email string `validate:"required"`
- Validating value instead of pointer
// May not work with pointer receivers
user := User{} // value
user.Validate() // Method might not be found
// Use pointer
user := &User{} // pointer
validation.Validate(ctx, user)
Partial Validation Issues
Issue: Required fields failing in PATCH requests
Symptom:
type UpdateUser struct {
Email string `validate:"required,email"`
}
// PATCH with only age
err := validation.ValidatePartial(ctx, &req, presence)
// Error: email is required (but it wasn't provided)
Solution: Use omitempty instead of required for PATCH:
type UpdateUser struct {
Email string `validate:"omitempty,email"` // Not "required"
}
Issue: Presence map not being respected
Symptom:
presence, _ := validation.ComputePresence(rawJSON)
err := validation.Validate(ctx, &req, // Missing WithPresence!
validation.WithPartial(true),
)
Solution: Always pass presence map:
err := validation.Validate(ctx, &req,
validation.WithPartial(true),
validation.WithPresence(presence), // Add this
)
Custom Validation Issues
Issue: Custom tag not working
Symptom:
validator := validation.MustNew(
validation.WithCustomTag("phone", phoneValidator),
)
type User struct {
Phone string `validate:"phone"` // Not recognized
}
Possible Causes:
- Tag registered on wrong validator
// Registered on custom validator
validator := validation.MustNew(
validation.WithCustomTag("phone", phoneValidator),
)
// But using package-level function (different validator)
validation.Validate(ctx, &user) // Doesn't have custom tag
Solution: Use the same validator:
validator.Validate(ctx, &user) // Use custom validator
- Tag function signature wrong
// Wrong
func phoneValidator(val string) bool { ... }
// Correct
func phoneValidator(fl validator.FieldLevel) bool { ... }
Issue: ValidateContext not being called
Symptom:
func (u *User) ValidateContext(ctx context.Context) error {
fmt.Println("Never prints")
return nil
}
Possible Causes:
- Wrong receiver type
// Method defined on value
func (u User) ValidateContext(ctx context.Context) error { ... }
// But validating pointer
user := &User{}
validation.Validate(ctx, user) // Method not found
Solution: Use pointer receiver:
func (u *User) ValidateContext(ctx context.Context) error { ... }
- Struct tags have priority
If auto-selection chooses tags, interface method isn’t called.
Solution: Explicitly use interface strategy:
validation.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyInterface),
)
Error Handling Issues
Issue: Can’t access field errors
Symptom:
err := validation.Validate(ctx, &user)
// How do I get field-level errors?
Solution: Use errors.As:
var verr *validation.Error
if errors.As(err, &verr) {
for _, fieldErr := range verr.Fields {
fmt.Printf("%s: %s\n", fieldErr.Path, fieldErr.Message)
}
}
Issue: Sensitive data visible in errors
Symptom:
// Error message contains password value
email: invalid email (value: "password123")
Solution: Use redactor:
validator := validation.MustNew(
validation.WithRedactor(func(path string) bool {
return strings.Contains(path, "password")
}),
)
Issue: Validation is slow
Possible Causes:
- Creating validator on every request
// Bad - creates validator every time
func Handler(w http.ResponseWriter, r *http.Request) {
validator := validation.MustNew(...) // Slow
validator.Validate(ctx, &req)
}
Solution: Create once, reuse:
var validator = validation.MustNew(...)
func Handler(w http.ResponseWriter, r *http.Request) {
validator.Validate(ctx, &req) // Fast
}
- JSON Schema not cached
func (u User) JSONSchema() (id, schema string) {
return "", `{...}` // Empty ID = no caching
}
Solution: Use stable ID:
func (u User) JSONSchema() (id, schema string) {
return "user-v1", `{...}` // Cached
}
- Expensive ValidateContext
func (u *User) ValidateContext(ctx context.Context) error {
// Expensive operation on every validation
return checkWithExternalAPI(u.Email)
}
Solution: Optimize or cache:
func (u *User) ValidateContext(ctx context.Context) error {
// Fast checks first
if !basicValidation(u.Email) {
return errors.New("invalid format")
}
// Expensive check last
return checkWithExternalAPI(u.Email)
}
JSON Schema Issues
Issue: Schema validation not working
Symptom:
func (u User) JSONSchema() (id, schema string) {
return "user-v1", `{...}`
}
// But validation doesn't use schema
Possible Causes:
- Higher priority strategy exists
type User struct {
Email string `validate:"email"` // Tags have higher priority
}
func (u User) JSONSchema() (id, schema string) {
return "user-v1", `{...}` // Not used
}
Solution: Use explicit strategy:
validation.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyJSONSchema),
)
- Invalid JSON Schema
func (u User) JSONSchema() (id, schema string) {
return "user-v1", `{ invalid json }` // Parse error
}
Solution: Validate schema syntax:
// Use online validator: https://www.jsonschemavalidator.net/
Context Issues
Issue: Context values not available
Symptom:
func (u *User) ValidateContext(ctx context.Context) error {
db := ctx.Value("db") // db is nil
// ...
}
Solution: Ensure values are in context:
ctx = context.WithValue(ctx, "db", db)
err := validation.Validate(ctx, &user)
Issue: Wrong context being used
Symptom:
err := validation.Validate(ctx1, &user,
validation.WithContext(ctx2), // Overrides ctx1
)
Solution: Don’t use WithContext unless necessary:
// Just pass the right context
err := validation.Validate(correctCtx, &user)
Module and Import Issues
Issue: Cannot find module
go: finding module for package rivaas.dev/validation
Solution:
go mod tidy
go get rivaas.dev/validation
Issue: Version conflicts
require rivaas.dev/validation v1.0.0
// +incompatible
Solution: Update to compatible version:
go get rivaas.dev/validation@latest
go mod tidy
Common Error Messages
“cannot validate nil value”
Cause: Passing nil to Validate:
var user *User
validation.Validate(ctx, user) // Error: cannot validate nil value
Solution: Ensure value is not nil:
user := &User{Email: "test@example.com"}
validation.Validate(ctx, user)
“cannot validate invalid value”
Cause: Passing invalid reflect.Value:
var v interface{}
validation.Validate(ctx, v) // Error: cannot validate invalid value
Solution: Pass actual struct:
user := &User{}
validation.Validate(ctx, user)
“unknown validation strategy”
Cause: Invalid strategy value:
validation.Validate(ctx, &user,
validation.WithStrategy(999), // Invalid
)
Solution: Use valid strategy constants:
validation.Validate(ctx, &user,
validation.WithStrategy(validation.StrategyTags),
)
Debugging Tips
1. Check which strategy is being used
// Temporarily force each strategy to see which works
strategies := []validation.Strategy{
validation.StrategyInterface,
validation.StrategyTags,
validation.StrategyJSONSchema,
}
for _, strategy := range strategies {
err := validation.Validate(ctx, &user,
validation.WithStrategy(strategy),
)
fmt.Printf("%v: %v\n", strategy, err)
}
2. Enable all error reporting
err := validation.Validate(ctx, &user,
validation.WithMaxErrors(0), // Unlimited
)
import "reflect"
t := reflect.TypeOf(User{})
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("%s: %s\n", field.Name, field.Tag.Get("validate"))
}
4. Test interface implementation
var _ validation.ValidatorInterface = (*User)(nil) // Compile-time check
var _ validation.ValidatorWithContext = (*User)(nil)
var _ validation.JSONSchemaProvider = (*User)(nil)
Getting Help
If you’re still stuck:
- Check documentation: User Guide
- Review examples: Examples
- Check pkg.go.dev: API Documentation
- GitHub Issues: Report a bug
- Discussions: Ask a question
Next Steps