Documentation
¶
Overview ¶
Package validation provides flexible, multi-strategy validation for Go structs.
Getting Started ¶
The simplest way to use this package is with the package-level Validate function:
type User struct {
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=18"`
}
user := User{Email: "invalid", Age: 15}
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)
}
}
}
For more control, create a Validator instance with New or MustNew:
validator := validation.MustNew(
validation.WithRedactor(sensitiveFieldRedactor),
validation.WithMaxErrors(10),
validation.WithCustomTag("phone", phoneValidator),
)
if err := validator.Validate(ctx, &user); err != nil {
// Handle validation errors
}
Validation Strategies ¶
The package supports three validation strategies:
- Struct Tags - Using go-playground/validator tags (e.g., `validate:"required,email"`)
- JSON Schema - RFC-compliant JSON Schema validation via JSONSchemaProvider interface
- Custom Interfaces - Implement ValidatorInterface or ValidatorWithContext for custom validation logic
The package automatically selects the best strategy based on the value type, or you can explicitly choose a strategy using WithStrategy.
Partial Validation ¶
For PATCH requests where only some fields are provided, use ValidatePartial:
presence, _ := validation.ComputePresence(rawJSON) err := validator.ValidatePartial(ctx, &user, presence)
Custom Validation Interface ¶
Implement ValidatorInterface for custom validation logic:
type User struct {
Email string
}
func (u *User) Validate() error {
if !strings.Contains(u.Email, "@") {
return errors.New("email must contain @")
}
return nil
}
// validation.Validate will automatically call u.Validate()
err := validation.Validate(ctx, &user)
For context-aware validation, implement ValidatorWithContext:
func (u *User) ValidateContext(ctx context.Context) error {
// Access request-scoped data from context
tenant := ctx.Value("tenant").(string)
// Apply tenant-specific validation rules
return nil
}
Thread Safety ¶
Validator instances are safe for concurrent use by multiple goroutines. The package-level functions use a default validator that is also thread-safe.
Security ¶
The package includes protections against:
- Stack overflow from deeply nested structures (max depth: 100)
- Unbounded memory usage (configurable limits on errors and fields)
- Sensitive data exposure (redaction support via WithRedactor)
Standalone Usage ¶
This package can be used independently without the full Rivaas framework:
import "rivaas.dev/validation" validator := validation.MustNew() err := validator.Validate(ctx, &user)
Index ¶
- Variables
- func ExtractRawJSONCtx(ctx context.Context) ([]byte, bool)
- func InjectRawJSONCtx(ctx context.Context, raw []byte) context.Context
- func Validate(ctx context.Context, v any, opts ...Option) error
- func ValidatePartial(ctx context.Context, v any, pm PresenceMap, opts ...Option) error
- type Error
- func (v *Error) Add(path, code, message string, meta map[string]any)
- func (v *Error) AddError(err error)
- func (v Error) Code() string
- func (v Error) Details() any
- func (v Error) Error() string
- func (v Error) GetField(path string) *FieldError
- func (v Error) HTTPStatus() int
- func (v Error) Has(path string) bool
- func (v Error) HasCode(code string) bool
- func (v Error) HasErrors() bool
- func (v *Error) Sort()
- func (v Error) Unwrap() error
- type FieldError
- type JSONSchemaProvider
- type MessageFunc
- type Option
- func WithContext(ctx context.Context) Option
- func WithCustomSchema(id, schema string) Option
- func WithCustomTag(name string, fn validator.Func) Option
- func WithCustomValidator(fn func(any) error) Option
- func WithDisallowUnknownFields(disallow bool) Option
- func WithFieldNameMapper(mapper func(string) string) Option
- func WithMaxCachedSchemas(maxCachedSchemas int) Option
- func WithMaxErrors(maxErrors int) Option
- func WithMaxFields(maxFields int) Option
- func WithMessageFunc(tag string, fn MessageFunc) Option
- func WithMessages(messages map[string]string) Option
- func WithPartial(partial bool) Option
- func WithPresence(presence PresenceMap) Option
- func WithRedactor(redactor Redactor) Option
- func WithRequireAny(require bool) Option
- func WithRunAll(runAll bool) Option
- func WithStrategy(strategy Strategy) Option
- type PresenceMap
- type Redactor
- type Strategy
- type Validator
- type ValidatorInterface
- type ValidatorWithContext
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrCannotValidateNilValue is returned when attempting to validate a nil value. ErrCannotValidateNilValue = errors.New("cannot validate nil value") // ErrCannotValidateInvalidValue is returned when the value is not valid for reflection. ErrCannotValidateInvalidValue = errors.New("cannot validate invalid value") // ErrUnknownValidationStrategy is returned when an unknown validation strategy is specified. ErrUnknownValidationStrategy = errors.New("unknown validation strategy") // ErrValidationFailed is a generic validation failure error. ErrValidationFailed = errors.New("validation failed") // ErrInvalidType is returned when a value has an unexpected type. ErrInvalidType = errors.New("invalid type") )
Predefined validation errors.
var ErrValidation = errors.New("validation")
ErrValidation is a sentinel error for validation failures. Use errors.Is(err, ErrValidation) to check if an error is a validation error.
Functions ¶
func ExtractRawJSONCtx ¶
ExtractRawJSONCtx retrieves raw JSON bytes from context if present. It returns (nil, false) if the context does not contain raw JSON (i.e., InjectRawJSONCtx was not called).
func InjectRawJSONCtx ¶
InjectRawJSONCtx injects raw JSON bytes into context for StrategyJSONSchema validation.
It is an internal API used by request binding frameworks. The raw bytes are used directly for schema validation instead of re-encoding the Go struct back to JSON.
Do not call it directly in application code unless you are implementing a custom binding framework.
Parameters:
- ctx: The parent context
- raw: Raw JSON bytes from the request body
func Validate ¶
Validate validates a value using the default Validator. For customized validation, create a Validator with New or MustNew.
Validate returns nil if validation passes, or an *Error if validation fails. The error can be type-asserted to *Error for structured field errors.
Parameters:
- ctx: Context passed to ValidatorWithContext implementations
- v: The value to validate (typically a pointer to a struct)
- opts: Optional per-call configuration (see Option)
Example:
var req CreateUserRequest
if err := validation.Validate(ctx, &req); err != nil {
var verr *validation.Error
if errors.As(err, &verr) {
// Handle structured validation errors
}
}
With options:
if err := validation.Validate(ctx, &req,
validation.WithStrategy(StrategyTags),
validation.WithPartial(true),
validation.WithPresence(presenceMap),
); err != nil {
// Handle validation error
}
Example ¶
ExampleValidate demonstrates basic validation with struct tags.
package main
import (
"context"
"errors"
"fmt"
"rivaas.dev/validation"
)
func main() {
type User struct {
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=18"`
}
user := User{Email: "invalid", Age: 15}
err := validation.Validate(context.Background(), &user)
if err != nil {
var verr *validation.Error
if errors.As(err, &verr) {
for _, fieldErr := range verr.Fields {
if _, err = fmt.Printf("%s: %s\n", fieldErr.Path, fieldErr.Message); err != nil {
panic(err)
}
}
}
}
}
Output: age: must be at least 18 email: must be a valid email address
Example (WithOptions) ¶
ExampleValidate_withOptions demonstrates validation with various options.
package main
import (
"context"
"errors"
"fmt"
"rivaas.dev/validation"
)
func main() {
type User struct {
Password string `json:"password" validate:"required,min=8"` //nolint:gosec // G117: example fixture, no real credentials
Token string `json:"token" validate:"required"`
}
user := User{
Password: "short",
Token: "secret-token-12345",
}
// Use redactor to hide sensitive fields
redactor := func(path string) bool {
return path == "password" || path == "token"
}
err := validation.Validate(context.Background(), &user,
validation.WithRedactor(redactor),
validation.WithMaxErrors(5),
)
if err != nil {
var verr *validation.Error
if errors.As(err, &verr) {
for _, fieldErr := range verr.Fields {
if _, printErr := fmt.Printf("%s: %s (value: %v)\n",
fieldErr.Path,
fieldErr.Message,
fieldErr.Meta["value"],
); printErr != nil {
panic(printErr)
}
}
}
}
}
Output: password: must be at least 8 characters (value: ***REDACTED***)
func ValidatePartial ¶
ValidatePartial validates only fields present in the PresenceMap using the default Validator. ValidatePartial is useful for PATCH requests where only provided fields should be validated. Use ComputePresence to create a PresenceMap from raw JSON.
Example ¶
ExampleValidatePartial demonstrates partial validation for PATCH requests.
package main
import (
"context"
"fmt"
"rivaas.dev/validation"
)
func main() {
type User struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=18"`
}
// Simulate a PATCH request that only updates email
rawJSON := []byte(`{"email": "[email protected]"}`)
presence, err := validation.ComputePresence(rawJSON)
if err != nil {
panic(err)
}
user := User{Name: "Existing Name", Email: "[email protected]", Age: 25}
err = validation.ValidatePartial(context.Background(), &user, presence)
if err != nil {
if _, printErr := fmt.Printf("Validation failed: %v\n", err); printErr != nil {
panic(printErr)
}
} else {
if _, printErr := fmt.Println("Validation passed"); printErr != nil {
panic(printErr)
}
}
}
Output: Validation passed
Types ¶
type Error ¶
type Error struct {
Fields []FieldError `json:"errors"` // List of field errors
Truncated bool `json:"truncated,omitempty"` // True if errors were truncated due to maxErrors limit
}
Error represents validation errors for one or more fields. Error implements error and can be used with errors.Is/errors.As. It contains a slice of FieldError values, one for each field that failed validation.
Example:
var err *Error
if errors.As(validationErr, &err) {
for _, fieldErr := range err.Fields {
fmt.Printf("%s: %s\n", fieldErr.Path, fieldErr.Message)
}
}
func (*Error) Add ¶
Add adds a new FieldError to the collection.
Example:
var err Error
err.Add("email", "tag.required", "is required", map[string]any{"tag": "required"})
func (*Error) AddError ¶
AddError adds an error to the collection, handling different error types. AddError accepts FieldError, Error, or generic errors and converts them appropriately.
Example:
var err Error
err.AddError(FieldError{Path: "email", Code: "tag.required", Message: "is required"})
func (Error) GetField ¶
func (v Error) GetField(path string) *FieldError
GetField returns the first FieldError for a given path, or nil if not found.
Example:
fieldErr := err.GetField("email")
if fieldErr != nil {
fmt.Println(fieldErr.Message)
}
func (Error) HTTPStatus ¶
HTTPStatus implements rivaas.dev/errors.ErrorType.
func (Error) Has ¶
Has checks if a specific field path has an error.
Example:
if err.Has("email") {
// Handle email field errors
}
func (Error) HasCode ¶
HasCode returns true if any error has the given code.
Example:
if err.HasCode("tag.required") {
// Handle required field errors
}
func (*Error) Sort ¶
func (v *Error) Sort()
Sort sorts errors by path, then by code. Sort modifies the error in place and is useful for consistent error presentation.
func (Error) Unwrap ¶
Unwrap returns ErrValidation for errors.Is/errors.As compatibility.
type FieldError ¶
type FieldError struct {
Path string `json:"path"` // JSON path (e.g., "items.2.price")
Code string `json:"code"` // Stable code (e.g., "tag.required", "schema.type")
Message string `json:"message"` // Human-readable message
Meta map[string]any `json:"meta,omitempty"` // Additional metadata (tag, param, value, etc.)
}
FieldError represents a single validation error for a specific field. Multiple FieldError values are collected in an Error.
Example:
err := FieldError{
Path: "email",
Code: "tag.required",
Message: "is required",
Meta: map[string]any{"tag": "required"},
}
func (FieldError) Error ¶
func (e FieldError) Error() string
Error returns a formatted error message as "path: message" or just "message" if path is empty.
func (FieldError) HTTPStatus ¶
func (e FieldError) HTTPStatus() int
HTTPStatus implements rivaas.dev/errors.ErrorType.
func (FieldError) Unwrap ¶
func (e FieldError) Unwrap() error
Unwrap returns ErrValidation for errors.Is/errors.As compatibility.
type JSONSchemaProvider ¶
type JSONSchemaProvider interface {
JSONSchema() (id, schema string)
}
JSONSchemaProvider interface for types that provide their own JSON Schema. When implemented by a struct, it causes the returned schema to be used for validation.
Example:
type User struct {
Email string `json:"email"`
}
func (u *User) JSONSchema() (id string, schema string) {
return "user-v1", `{
"type": "object",
"properties": {
"email": {"type": "string", "format": "email"}
},
"required": ["email"]
}`
}
type MessageFunc ¶ added in v0.5.0
MessageFunc generates a dynamic error message for parameterized validation tags. Use WithMessageFunc to configure messages for tags like "min", "max", "len", "oneof" that include parameters.
The function receives the tag parameter (e.g., "3" for `min=3`) and the field's reflect.Kind to enable type-aware messages (e.g., "characters" for strings vs plain numbers for integers).
type Option ¶
type Option func(*config)
Option is a functional option for configuring validation. Options can be passed to New, MustNew, Validate, or Validator.Validate.
func WithContext ¶
WithContext overrides the context used for validation. This is useful when you need a different context than the one passed to Validate().
Note: In most cases, you should simply pass the context directly to Validate(). This option exists for advanced use cases where context override is needed.
Example:
validator.Validate(requestCtx, &req, WithContext(backgroundCtx))
func WithCustomSchema ¶
WithCustomSchema sets a custom JSON Schema for validation. This overrides any schema provided by the JSONSchemaProvider interface.
Example:
schema := `{"type": "object", "properties": {"email": {"type": "string", "format": "email"}}}`
validator.Validate(ctx, &req, WithCustomSchema("user-schema", schema))
func WithCustomTag ¶ added in v0.2.0
WithCustomTag registers a custom validation tag for use in struct tags. Custom tags are registered when the Validator is created.
Example:
validator := validation.MustNew(
validation.WithCustomTag("phone", func(fl validator.FieldLevel) bool {
return phoneRegex.MatchString(fl.Field().String())
}),
)
type User struct {
Phone string `validate:"phone"`
}
func WithCustomValidator ¶
WithCustomValidator sets a custom validation function. It calls the function before any other validation strategies.
Example:
validator.Validate(ctx, &req, WithCustomValidator(func(v any) error {
req := v.(*UserRequest)
if req.Age < 18 {
return errors.New("must be 18 or older")
}
return nil
}))
func WithDisallowUnknownFields ¶
WithDisallowUnknownFields rejects JSON with unknown fields (typo detection). When enabled, it causes BindJSONStrict to reject requests with fields not defined in the struct.
Example:
BindAndValidateStrict(&req, WithDisallowUnknownFields(true))
func WithFieldNameMapper ¶
WithFieldNameMapper sets a function to transform field names in error messages. It is useful for localization or custom naming conventions.
Example:
validator.Validate(ctx, &req, WithFieldNameMapper(func(name string) string {
return strings.ReplaceAll(name, "_", " ")
}))
func WithMaxCachedSchemas ¶
WithMaxCachedSchemas sets the maximum number of JSON schemas to cache. Set to 0 to use the default (1024).
Example:
validation.MustNew(validation.WithMaxCachedSchemas(2048))
func WithMaxErrors ¶
WithMaxErrors limits the number of errors returned. Set to 0 for unlimited errors (default).
Example:
validator.Validate(ctx, &req, WithMaxErrors(10))
func WithMaxFields ¶
WithMaxFields sets the maximum number of fields to validate in partial mode. It prevents pathological inputs with extremely large presence maps. Set to 0 to use the default (10000).
Example:
validator.Validate(ctx, &req, WithMaxFields(5000), WithPartial(true))
func WithMessageFunc ¶ added in v0.5.0
func WithMessageFunc(tag string, fn MessageFunc) Option
WithMessageFunc sets a dynamic message generator for a parameterized tag. Use for tags like "min", "max", "len", "oneof" that include parameters.
Example:
validator := validation.MustNew(
validation.WithMessageFunc("min", func(param string, kind reflect.Kind) string {
if kind == reflect.String {
return fmt.Sprintf("too short (min %s chars)", param)
}
return fmt.Sprintf("too small (min %s)", param)
}),
)
func WithMessages ¶ added in v0.5.0
WithMessages sets static error messages for validation tags. Messages override the default English messages for specified tags. Unspecified tags continue to use defaults.
Example:
validator := validation.MustNew(
validation.WithMessages(map[string]string{
"required": "cannot be empty",
"email": "invalid email format",
}),
)
func WithPartial ¶
WithPartial enables partial update validation mode (for PATCH requests). It validates only present fields and ignores "required" constraints for absent fields.
Example:
validator.Validate(ctx, &req, WithPartial(true), WithPresence(presenceMap))
func WithPresence ¶
func WithPresence(presence PresenceMap) Option
WithPresence sets the presence map for partial validation. The PresenceMap tracks which fields were provided in the request body. Use ComputePresence to create a PresenceMap from raw JSON.
Example:
presence, _ := ComputePresence(rawJSON) validator.Validate(ctx, &req, WithPresence(presence), WithPartial(true))
func WithRedactor ¶
WithRedactor 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:
validator.Validate(ctx, &req, WithRedactor(func(path string) bool {
return strings.Contains(path, "password") || strings.Contains(path, "token")
}))
func WithRequireAny ¶
WithRequireAny requires at least one validation strategy to pass when using WithRunAll. When enabled with WithRunAll(true), it causes validation to succeed if at least one strategy produces no errors, even if other strategies fail.
It is useful when you have multiple validation strategies and want to accept the value if it passes any one of them (OR logic).
Example:
// Validate with all strategies, succeed if any one passes
err := validator.Validate(ctx, &req,
WithRunAll(true),
WithRequireAny(true),
)
func WithRunAll ¶
WithRunAll runs all applicable validation strategies and aggregates errors. By default, validation stops at the first successful strategy.
Example:
validator.Validate(ctx, &req, WithRunAll(true))
func WithStrategy ¶
WithStrategy sets the validation strategy.
Example:
validator.Validate(ctx, &req, WithStrategy(StrategyTags))
type PresenceMap ¶
PresenceMap tracks which fields are present in the request body. Keys are normalized dot paths (e.g., "items.2.price"), values are always true.
PresenceMap is used for partial update validation (PATCH requests), where only present fields should be validated, while absent fields should be ignored even if they have "required" constraints.
Use ComputePresence to create a PresenceMap from raw JSON, and WithPresence to pass it to validation.
func ComputePresence ¶
func ComputePresence(rawJSON []byte) (PresenceMap, error)
ComputePresence analyzes raw JSON and returns a PresenceMap of present field paths. It enables partial validation where only provided fields are validated.
It returns an empty map (not nil) if rawJSON is empty. It has a maximum recursion depth of 100 to prevent stack overflow from deeply nested JSON structures.
Example:
rawJSON := []byte(`{"user": {"name": "Alice", "age": 0}}`)
presence, err := ComputePresence(rawJSON)
// Returns: {"user": true, "user.name": true, "user.age": true}
Errors:
- Returns error if rawJSON is not valid JSON
Example ¶
ExampleComputePresence demonstrates how to compute presence map from JSON.
package main
import (
"fmt"
"rivaas.dev/validation"
)
func main() {
rawJSON := []byte(`{
"user": {
"name": "Alice",
"age": 30
},
"items": [
{"id": 1, "name": "Item 1"},
{"id": 2}
]
}`)
presence, err := validation.ComputePresence(rawJSON)
if err != nil {
if _, printErr := fmt.Printf("Error: %v\n", err); printErr != nil {
panic(printErr)
}
return
}
// Check if specific paths are present
if _, err = fmt.Printf("user.name present: %v\n", presence.Has("user.name")); err != nil {
panic(err)
}
if _, err = fmt.Printf("user.email present: %v\n", presence.Has("user.email")); err != nil {
panic(err)
}
if _, err = fmt.Printf("items.0.name present: %v\n", presence.Has("items.0.name")); err != nil {
panic(err)
}
if _, err = fmt.Printf("items.1.name present: %v\n", presence.Has("items.1.name")); err != nil {
panic(err)
}
// Get leaf paths (fields that aren't prefixes of others)
leaves := presence.LeafPaths()
// Sort for consistent output in example
if _, err = fmt.Printf("Leaf paths count: %d\n", len(leaves)); err != nil {
panic(err)
}
if _, err = fmt.Printf("Sample leaf: %s\n", leaves[0]); err != nil {
panic(err)
}
}
Output: user.name present: true user.email present: false items.0.name present: true items.1.name present: false Leaf paths count: 5 Sample leaf: items.0.id
func (PresenceMap) Has ¶
func (pm PresenceMap) Has(path string) bool
Has returns true if the exact path is present.
func (PresenceMap) HasPrefix ¶
func (pm PresenceMap) HasPrefix(prefix string) bool
HasPrefix returns true if any path with the given prefix is present. HasPrefix is useful for checking if a nested object or array element is present.
func (PresenceMap) LeafPaths ¶
func (pm PresenceMap) LeafPaths() []string
LeafPaths returns paths that aren't prefixes of others. LeafPaths is useful for partial validation where only leaf fields that were actually provided should be validated, not their parent objects.
Example:
- If presence contains "address" and "address.city", only "address.city" is a leaf.
- If presence contains "items.0" and "items.0.name", only "items.0.name" is a leaf.
type Redactor ¶
Redactor is a function that determines if a field should be redacted in error messages. It returns true if the field at the given path should have its value hidden. Use WithRedactor to configure a redactor for a Validator.
Example:
redactor := func(path string) bool {
return strings.Contains(path, "password") || strings.Contains(path, "token")
}
type Strategy ¶
type Strategy int
Strategy defines the validation approach to use. Use WithStrategy to set a strategy, or leave as StrategyAuto for automatic selection.
const ( // StrategyAuto automatically selects the best strategy based on the type. // Priority: Interface methods > Struct tags > JSON Schema StrategyAuto Strategy = iota // StrategyTags uses struct tag validation (go-playground/validator). StrategyTags // StrategyJSONSchema uses JSON Schema validation. StrategyJSONSchema // StrategyInterface calls Validate() or ValidateContext() methods. StrategyInterface )
type Validator ¶
type Validator struct {
// contains filtered or unexported fields
}
Validator provides struct validation with configurable options.
Use New or MustNew to create a configured Validator, or use package-level functions (Validate, ValidatePartial) for zero-configuration validation.
Validator supports three validation strategies (see Strategy):
- Struct tags via go-playground/validator (StrategyTags)
- JSON Schema validation (StrategyJSONSchema)
- Custom interface methods (StrategyInterface)
Validator is safe for concurrent use by multiple goroutines.
Example:
validator := validation.MustNew(
validation.WithRedactor(sensitiveRedactor),
validation.WithMaxErrors(10),
)
err := validator.Validate(ctx, &user)
func MustNew ¶ added in v0.2.0
MustNew creates a Validator with the given options. Panics if configuration is invalid.
Use in main() or init() where panic on startup is acceptable.
Example:
validator := validation.MustNew(
validation.WithRedactor(sensitiveRedactor),
validation.WithMaxErrors(10),
)
Example ¶
ExampleMustNew demonstrates creating a Validator with MustNew (panics on error).
package main
import (
"context"
"fmt"
"rivaas.dev/validation"
)
func main() {
// MustNew panics if configuration is invalid - suitable for use in main() or init()
validator := validation.MustNew(
validation.WithMaxErrors(10),
)
type User struct {
Name string `json:"name" validate:"required"`
}
user := User{Name: "Alice"}
if err := validator.Validate(context.Background(), &user); err != nil {
if _, printErr := fmt.Printf("Validation failed: %v\n", err); printErr != nil {
panic(printErr)
}
} else {
if _, printErr := fmt.Println("User is valid"); printErr != nil {
panic(printErr)
}
}
}
Output: User is valid
func New ¶ added in v0.2.0
New creates a Validator with the given options. New returns an error if configuration is invalid (e.g., negative maxErrors).
See Option for available configuration options.
Example:
validator, err := validation.New(
validation.WithMaxErrors(10),
validation.WithRedactor(sensitiveRedactor),
)
if err != nil {
return fmt.Errorf("failed to create validator: %w", err)
}
Example ¶
ExampleNew demonstrates creating a configured Validator instance.
package main
import (
"context"
"fmt"
"rivaas.dev/validation"
)
func main() {
// Create a validator with custom configuration
validator, err := validation.New(
validation.WithMaxErrors(5),
validation.WithRedactor(func(path string) bool {
return path == "password" || path == "token"
}),
)
if err != nil {
if _, printErr := fmt.Printf("Failed to create validator: %v\n", err); printErr != nil {
panic(printErr)
}
return
}
type User struct {
Email string `json:"email" validate:"required,email"`
}
user := User{Email: "[email protected]"}
if validateErr := validator.Validate(context.Background(), &user); validateErr != nil {
if _, err = fmt.Printf("Validation failed: %v\n", validateErr); err != nil {
panic(err)
}
} else {
if _, err = fmt.Println("Validation passed"); err != nil {
panic(err)
}
}
}
Output: Validation passed
func (*Validator) Validate ¶
Validate validates a value using this validator's configuration.
Validate returns nil if validation passes, or an *Error if validation fails. The error can be type-asserted to *Error for structured field errors. Per-call options override the validator's base configuration.
Parameters:
- ctx: Context passed to ValidatorWithContext implementations
- val: The value to validate (typically a pointer to a struct)
- opts: Optional per-call configuration overrides (see Option)
Example:
validator := validation.MustNew(validation.WithMaxErrors(10))
if err := validator.Validate(ctx, &req); err != nil {
var verr *validation.Error
if errors.As(err, &verr) {
// Handle structured validation errors
}
}
Example ¶
ExampleValidator_Validate demonstrates using a Validator instance.
package main
import (
"context"
"errors"
"fmt"
"rivaas.dev/validation"
)
func main() {
validator := validation.MustNew()
type User struct {
Email string `json:"email" validate:"required,email"`
}
user := User{Email: "invalid-email"}
err := validator.Validate(context.Background(), &user)
if err != nil {
var verr *validation.Error
if errors.As(err, &verr) {
if _, printErr := fmt.Printf("Found %d error(s)\n", len(verr.Fields)); printErr != nil {
panic(printErr)
}
if _, printErr := fmt.Printf("First error: %s\n", verr.Fields[0].Message); printErr != nil {
panic(printErr)
}
}
}
}
Output: Found 1 error(s) First error: must be a valid email address
func (*Validator) ValidatePartial ¶ added in v0.2.0
func (v *Validator) ValidatePartial(ctx context.Context, val any, pm PresenceMap, opts ...Option) error
ValidatePartial validates only fields present in the PresenceMap. It is useful for PATCH requests where only provided fields should be validated. Use ComputePresence to create a PresenceMap from raw JSON.
type ValidatorInterface ¶ added in v0.2.0
type ValidatorInterface interface {
Validate() error
}
ValidatorInterface is the interface for custom validation methods. When implemented by a struct, it causes Validate() to be called during validation.
Note: This interface is named ValidatorInterface to avoid confusion with the Validator struct which is the main validation engine.
Example:
type User struct {
Email string `json:"email"`
}
func (u *User) Validate() error {
if !strings.Contains(u.Email, "@") {
return errors.New("email must contain @")
}
return nil
}
type ValidatorWithContext ¶
ValidatorWithContext interface for context-aware validation methods. It is preferred over ValidatorInterface when a context is available, as it allows for tenant-specific rules, request-scoped data, etc.
Example:
type User struct {
Email string `json:"email"`
}
func (u *User) ValidateContext(ctx context.Context) error {
userID := ctx.Value("user_id").(string)
// Use context data for validation (e.g., tenant-specific rules)
return nil
}