Validation
go.putnami.dev/schema provides struct-tag-based validation with type coercion and default values.
Validating data
Validate a map[string]any against a Go struct type:
import (
"reflect"
"go.putnami.dev/schema"
)
type CreateUserInput struct {
Name string `json:"name" validate:"required,minlen=2,maxlen=100"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=0,max=150"`
}
result := schema.Validate(
reflect.TypeOf(CreateUserInput{}),
map[string]any{
"name": "Jane",
"email": "not-an-email",
"age": -5,
},
)
if result.HasErrors() {
for _, err := range result.Errors {
fmt.Printf("%s: %s\n", err.Field, err.Message)
// email: must be a valid email address
// age: must be at least 0
}
}Validation tags
Add validation rules to struct fields using the validate tag:
type Product struct {
ID string `json:"id" validate:"required,uuid"`
Name string `json:"name" validate:"required,minlen=1,maxlen=200"`
Description string `json:"description" validate:"maxlen=2000"`
Price float64 `json:"price" validate:"required,min=0"`
Category string `json:"category" validate:"required,oneof=electronics|clothing|food"`
URL string `json:"url" validate:"url"`
}Available validators
| Tag | Description | Example |
|---|---|---|
required |
Must be present and non-zero | validate:"required" |
uuid |
UUID v4 format | validate:"uuid" |
email |
Valid email address | validate:"email" |
url |
Valid URL | validate:"url" |
min=N |
Minimum numeric value | validate:"min=0" |
max=N |
Maximum numeric value | validate:"max=100" |
minlen=N |
Minimum string length | validate:"minlen=2" |
maxlen=N |
Maximum string length | validate:"maxlen=255" |
pattern=REGEX |
Must match regular expression | validate:"pattern=^[a-z]+$" |
oneof=a|b|c |
Must be one of the listed values | validate:"oneof=active|inactive" |
Combine multiple validators with commas:
validate:"required,minlen=2,maxlen=100"Default values
Set default values for optional fields:
type SearchParams struct {
Query string `json:"query" validate:"required"`
Page int `json:"page" default:"1" validate:"min=1"`
PageSize int `json:"pageSize" default:"20" validate:"min=1,max=100"`
Sort string `json:"sort" default:"relevance" validate:"oneof=relevance|date|price"`
}Defaults are applied before validation, so fields with defaults are always populated.
Type coercion
Enable automatic type coercion to convert string values to their target types:
result := schema.Validate(
reflect.TypeOf(SearchParams{}),
map[string]any{
"query": "shoes",
"page": "2", // string → int
"pageSize": "50", // string → int
},
schema.WithCoerce(),
)
// result.Data["page"] == 2 (int, not "2")Validation result
type Result struct {
Data map[string]any // Validated and coerced values
Errors []FieldError // Validation errors
}
type FieldError struct {
Field string // Field name
Message string // Error description
}
result.HasErrors() // true if any validation errorsValidation options
| Option | Description |
|---|---|
WithCoerce() |
Enable automatic type coercion (string → int, etc.) |
WithLabel(prefix) |
Prefix error field names (e.g., "body." → "body.name") |
Using with HTTP endpoints
Manual validation
func createUser(ctx *fhttp.Context) *fhttp.Response {
var input map[string]any
if err := ctx.Body(&input); err != nil {
return fhttp.JSONStatus(400, map[string]string{"error": "invalid JSON"})
}
result := schema.Validate(
reflect.TypeOf(CreateUserInput{}),
input,
schema.WithCoerce(),
)
if result.HasErrors() {
return fhttp.JSONStatus(422, map[string]any{"errors": result.Errors})
}
// Use result.Data for validated values
name := result.Data["name"].(string)
email := result.Data["email"].(string)
// ...
}With the endpoint builder
The endpoint builder integrates validation automatically:
endpoint := fhttp.Endpoint("POST", "/users").
Body(reflect.TypeOf(CreateUserInput{})).
Handle(func(ctx *fhttp.EndpointContext) *fhttp.Response {
// ctx.ValidatedBody contains validated and coerced data
name := ctx.ValidatedBody["name"].(string)
return fhttp.JSONStatus(201, map[string]string{"name": name})
})See HTTP & Middleware for details.
Nested struct validation
Nested structs are validated recursively:
type Address struct {
Street string `json:"street" validate:"required"`
City string `json:"city" validate:"required"`
Zip string `json:"zip" validate:"required,pattern=^[0-9]{5}$"`
}
type CreateUserInput struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Address Address `json:"address" validate:"required"`
}Related guides
- HTTP & Middleware — endpoint validation
- OpenAPI — validation tags in API specs
- Errors — validation error responses