Skip to main content
Version: 0.1.1

Error Handling

How to handle validation errors in Pedantigo.

Overview

Pedantigo collects all validation errors in a single pass and returns them as a ValidationError type. This means you get complete feedback about all validation failures at once, rather than discovering errors one by one.

ValidationError Type

The main error type wraps multiple field-level errors:

type ValidationError struct {
Errors []FieldError
}

// Error implements the error interface
func (e *ValidationError) Error() string

The Error() method returns a human-readable summary:

  • If one error: "field: message"
  • If multiple errors: "field: message (and N more errors)"

FieldError Type

Each error in the Errors slice is a FieldError:

type FieldError struct {
Field string // Field path (e.g., "email", "address.city", "items[0].price")
Code string // Machine-readable error code (e.g., "INVALID_EMAIL")
Message string // Human-readable error message
Value any // The actual value that failed validation
}

Field Path Format

The Field string describes the location of the error:

ExampleMeaning
"email"Top-level field named email
"user.email"Nested field email in nested struct user
"address.city"Field city in nested struct address
"items[0]"First element of array/slice items
"items[0].name"Field name in first element of items array

Error Handling Patterns

Type Assertion

Check if an error is a validation error using type assertion:

user, err := pedantigo.Unmarshal[User](jsonData)
if err != nil {
if validationErr, ok := err.(*pedantigo.ValidationError); ok {
// Handle validation errors
for _, fieldErr := range validationErr.Errors {
fmt.Printf("Field: %s, Message: %s\n", fieldErr.Field, fieldErr.Message)
}
} else {
// Handle other errors (JSON syntax, etc.)
fmt.Printf("Error: %v\n", err)
}
}

Use the errors.As() function for cleaner error handling:

import (
"errors"
"github.com/smrutai/pedantigo"
)

user, err := pedantigo.Unmarshal[User](jsonData)
if err != nil {
var validationErr *pedantigo.ValidationError
if errors.As(err, &validationErr) {
// Handle validation errors
for _, fieldErr := range validationErr.Errors {
fmt.Printf("[%s] %s\n", fieldErr.Field, fieldErr.Message)
}
} else {
// Handle other error types
fmt.Printf("Unexpected error: %v\n", err)
}
}

Iterating All Errors

type User struct {
Email string `json:"email" pedantigo:"required,email"`
Age int `json:"age" pedantigo:"required,min=18,max=120"`
Username string `json:"username" pedantigo:"required,min=3,max=20"`
}

user, err := pedantigo.Unmarshal[User](jsonData)
if err != nil {
var validationErr *pedantigo.ValidationError
if errors.As(err, &validationErr) {
fmt.Printf("Validation failed with %d error(s):\n", len(validationErr.Errors))
for i, fieldErr := range validationErr.Errors {
fmt.Printf("%d. Field '%s':\n", i+1, fieldErr.Field)
fmt.Printf(" Message: %s\n", fieldErr.Message)
fmt.Printf(" Value: %v\n", fieldErr.Value)
if fieldErr.Code != "" {
fmt.Printf(" Code: %s\n", fieldErr.Code)
}
}
}
}

Filtering Errors by Field

var validationErr *pedantigo.ValidationError
if errors.As(err, &validationErr) {
// Get all errors for a specific field
emailErrors := make([]pedantigo.FieldError, 0)
for _, fieldErr := range validationErr.Errors {
if fieldErr.Field == "email" {
emailErrors = append(emailErrors, fieldErr)
}
}

if len(emailErrors) > 0 {
fmt.Printf("Email validation failed: %s\n", emailErrors[0].Message)
}
}

Common Error Messages

String Constraints

ConstraintError MessageExample
requiredfield is requiredMissing required field
emailmust be a valid email addressInvalid email format
urlmust be a valid URLInvalid URL format
uuidmust be a valid UUIDInvalid UUID format
min=Nmust be at least N charactersString too short
max=Nmust be at most N charactersString too long
alphamust contain only alphabetic charactersContains non-letters
alphanummust contain only alphanumeric charactersContains special chars
patternmust match pattern .*Regex pattern mismatch

Numeric Constraints

ConstraintError MessageExample
min=Nmust be at least NValue too small
max=Nmust be at most NValue too large
gt=Nmust be greater than NValue not greater
lt=Nmust be less than NValue not less
gte=Nmust be at least NValue too small
lte=Nmust be at most NValue too large

Array/Slice Constraints

ConstraintError MessageExample
minItems=Nmust have at least N itemsArray too short
maxItems=Nmust have at most N itemsArray too long
uniqueitems must be uniqueDuplicate items

Error Handling Example

Here's a complete example showing error handling with nested structs:

package main

import (
"encoding/json"
"errors"
"fmt"
"log"

"github.com/smrutai/pedantigo"
)

type Address struct {
Street string `json:"street" pedantigo:"required,min=5"`
City string `json:"city" pedantigo:"required,min=2"`
Zip string `json:"zip" pedantigo:"required,regexp=^\\d{5}$"`
}

type User struct {
Email string `json:"email" pedantigo:"required,email"`
Age int `json:"age" pedantigo:"required,min=18,max=120"`
Address Address `json:"address" pedantigo:"required"`
}

func handleValidationError(err error) {
var validationErr *pedantigo.ValidationError
if !errors.As(err, &validationErr) {
log.Fatalf("Unexpected error: %v", err)
return
}

// Organize errors by field
errorsByField := make(map[string][]pedantigo.FieldError)
for _, fieldErr := range validationErr.Errors {
errorsByField[fieldErr.Field] = append(errorsByField[fieldErr.Field], fieldErr)
}

// Display errors grouped by field
fmt.Printf("Validation failed with %d error(s):\n\n", len(validationErr.Errors))
for field, errors := range errorsByField {
fmt.Printf("Field: %s\n", field)
for i, err := range errors {
fmt.Printf(" %d. %s\n", i+1, err.Message)
if err.Code != "" {
fmt.Printf(" Code: %s\n", err.Code)
}
}
fmt.Println()
}
}

func main() {
jsonData := []byte(`{
"email": "not-an-email",
"age": 15,
"address": {
"street": "Main",
"city": "NY",
"zip": "not-a-zip"
}
}`)

user, err := pedantigo.Unmarshal[User](jsonData)
if err != nil {
handleValidationError(err)
return
}

// Continue with valid user
fmt.Printf("Valid user: %+v\n", user)
}

Non-Validation Errors

Pedantigo can also return non-validation errors in certain cases:

// JSON syntax error
_, err := pedantigo.Unmarshal[User]([]byte(`{invalid json}`))
// err will be a json.SyntaxError (not ValidationError)

// Type mismatch (if type conversion fails)
_, err := pedantigo.Unmarshal[User]([]byte(`{"age": "not a number"}`))
// err will be a json.UnmarshalTypeError (not ValidationError)

Always check the actual error type before assuming it's a ValidationError.

Error Behavior with Different API Methods

Unmarshal

Returns both validation errors and JSON parsing errors:

user, err := pedantigo.Unmarshal[User](jsonData)
if err != nil {
// Could be ValidationError, json.SyntaxError, or json.UnmarshalTypeError
}

Validate

Returns only validation errors:

err := pedantigo.Validate(user)
if err != nil {
// Always a ValidationError (if non-nil)
var validationErr *pedantigo.ValidationError
errors.As(err, &validationErr) // Always succeeds
}

NewModel

Returns both validation errors and type conversion errors:

user, err := pedantigo.NewModel[User](data)
if err != nil {
// Could be ValidationError or type conversion error
}

Best Practices

  1. Always check error type: Use errors.As() to safely check for ValidationError
  2. Collect all errors: Process the entire Errors slice to show users all problems at once
  3. Use error codes: When available, use FieldError.Code for programmatic error handling
  4. Preserve error context: Log the Value field to debug what value caused the error
  5. Return meaningful messages: Format errors user-friendly in your API responses
// Return validation errors as JSON in HTTP response
func handleUserCreation(w http.ResponseWriter, r *http.Request) {
var jsonData []byte
// ... read request body into jsonData ...

user, err := pedantigo.Unmarshal[User](jsonData)
if err != nil {
var validationErr *pedantigo.ValidationError
if errors.As(err, &validationErr) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]any{
"error": "validation_failed",
"errors": validationErr.Errors,
})
return
}
// Handle non-validation errors...
}

// Process valid user...
}
tip

See Validation Basics for more details on how validation works.