Comprehensive error reporting system with detailed validation error information, customizable formatting, and advanced error analysis for debugging and user feedback.
Detailed error objects that provide comprehensive information about validation failures with precise location and context information.
/**
* Core error object structure for validation failures
*/
interface ErrorObject<K = string, P = Record<string, any>, S = Record<string, any>> {
/** The keyword that failed validation */
keyword: K;
/** JSONPointer path to the invalid data */
instancePath: string;
/** JSONPointer path to the failing schema */
schemaPath: string;
/** The invalid data value */
data?: any;
/** Keyword-specific parameters */
params: P;
/** Human-readable error message */
message?: string;
/** The schema that failed */
schema: S;
/** Parent schema containing the failing schema */
parentSchema?: AnySchemaObject;
/** Root schema of the validation */
rootSchema?: AnySchemaObject;
}
/**
* Error object for keywords without parameters
*/
interface ErrorNoParams<K = string, S = Record<string, any>> extends Omit<ErrorObject<K, never, S>, 'params'> {}Usage Examples:
import Ajv from "ajv";
const ajv = new Ajv({ allErrors: true });
const schema = {
type: "object",
properties: {
name: { type: "string", minLength: 2 },
age: { type: "integer", minimum: 0, maximum: 120 },
email: { type: "string", format: "email" }
},
required: ["name", "age"],
additionalProperties: false
};
const invalidData = {
name: "A", // Too short
age: -5, // Below minimum
email: "invalid", // Invalid format
extra: "not allowed" // Additional property
};
const valid = ajv.validate(schema, invalidData);
if (!valid && ajv.errors) {
ajv.errors.forEach((error, index) => {
console.log(`Error ${index + 1}:`);
console.log(` Keyword: ${error.keyword}`);
console.log(` Instance Path: ${error.instancePath}`);
console.log(` Schema Path: ${error.schemaPath}`);
console.log(` Message: ${error.message}`);
console.log(` Data: ${JSON.stringify(error.data)}`);
console.log(` Parameters:`, error.params);
console.log(` Schema:`, error.schema);
console.log("---");
});
}
/* Output:
Error 1:
Keyword: minLength
Instance Path: /name
Schema Path: #/properties/name/minLength
Message: must NOT have fewer than 2 characters
Data: "A"
Parameters: { limit: 2 }
Schema: 2
---
Error 2:
Keyword: minimum
Instance Path: /age
Schema Path: #/properties/age/minimum
Message: must be >= 0
Data: -5
Parameters: { comparison: ">=", limit: 0 }
Schema: 0
---
*/Flexible error text formatting with customizable options for different presentation needs.
/**
* Formats validation errors as human-readable text
* @param errors - Array of error objects (defaults to ajv.errors)
* @param options - Formatting options
* @returns Formatted error string
*/
errorsText(errors?: ErrorObject[] | null, options?: ErrorsTextOptions): string;
interface ErrorsTextOptions {
/** Separator between multiple errors (default: ", ") */
separator?: string;
/** Variable name to use in error messages (default: "data") */
dataVar?: string;
}Usage Examples:
import Ajv from "ajv";
const ajv = new Ajv({ allErrors: true });
const userSchema = {
type: "object",
properties: {
username: { type: "string", minLength: 3, maxLength: 20 },
password: { type: "string", minLength: 8 },
email: { type: "string", format: "email" },
age: { type: "integer", minimum: 13 }
},
required: ["username", "password", "email"]
};
const invalidUser = {
username: "ab",
password: "123",
email: "not-an-email",
age: 12
};
const valid = ajv.validate(userSchema, invalidUser);
if (!valid) {
// Default formatting
console.log("Errors:", ajv.errorsText());
// Output: "data/username must NOT have fewer than 3 characters, data/password must NOT have fewer than 8 characters, data/email must match format \"email\", data/age must be >= 13"
// Custom separator
console.log("Errors (line separated):");
console.log(ajv.errorsText(ajv.errors, { separator: "\n- " }));
/* Output:
Errors (line separated):
data/username must NOT have fewer than 3 characters
- data/password must NOT have fewer than 8 characters
- data/email must match format "email"
- data/age must be >= 13
*/
// Custom data variable name
console.log("Form errors:", ajv.errorsText(ajv.errors, {
dataVar: "form",
separator: "; "
}));
// Output: "form/username must NOT have fewer than 3 characters; form/password must NOT have fewer than 8 characters; form/email must match format \"email\"; form/age must be >= 13"
}Built-in error class for handling validation failures with structured error information.
/**
* Validation error class containing validation results
*/
class ValidationError extends Error {
/** Array of validation errors */
errors: ErrorObject[];
/** The Ajv instance that performed validation */
ajv: Ajv;
/** The validation function that failed */
validation: AnyValidateFunction;
constructor(errors: ErrorObject[]);
}Usage Examples:
import Ajv, { ValidationError } from "ajv";
const ajv = new Ajv();
const schema = {
type: "object",
properties: {
id: { type: "integer" },
name: { type: "string", minLength: 1 }
},
required: ["id", "name"]
};
function validateAndThrow(data: unknown) {
const validate = ajv.compile(schema);
if (!validate(data)) {
throw new ValidationError(validate.errors || []);
}
return data;
}
try {
validateAndThrow({ id: "not-a-number", name: "" });
} catch (error) {
if (error instanceof ValidationError) {
console.log("Validation failed with errors:");
error.errors.forEach(err => {
console.log(`- ${err.instancePath}: ${err.message}`);
});
}
}Advanced error customization for keywords and validation scenarios.
/**
* Custom error definition for keywords
*/
interface KeywordErrorDefinition {
message?: string | ((cxt: KeywordCxt) => string);
params?: (cxt: KeywordCxt) => Record<string, any>;
}
/**
* Error customization in schema
*/
interface SchemaWithErrors {
errorMessage?: string | {
[keyword: string]: string;
} | {
properties?: {
[property: string]: string;
};
};
}Usage Examples:
import Ajv from "ajv";
import addFormats from "ajv-formats";
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
// Custom error messages in schema
const userSchema = {
type: "object",
properties: {
email: {
type: "string",
format: "email",
errorMessage: "Please provide a valid email address"
},
age: {
type: "integer",
minimum: 18,
errorMessage: {
type: "Age must be a whole number",
minimum: "You must be at least 18 years old"
}
},
password: {
type: "string",
minLength: 8,
pattern: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)",
errorMessage: {
minLength: "Password must be at least 8 characters long",
pattern: "Password must contain uppercase, lowercase, and numeric characters"
}
}
},
required: ["email", "age", "password"],
errorMessage: {
required: {
email: "Email address is required",
age: "Age is required",
password: "Password is required"
}
}
};
// Custom keyword with error definition
ajv.addKeyword({
keyword: "isEven",
type: "number",
error: {
message: "must be an even number",
params: (cxt) => ({ value: cxt.data })
},
validate: function(schema: boolean, data: number) {
return !schema || data % 2 === 0;
}
});
const numberSchema = {
type: "number",
isEven: true
};
const validateNumber = ajv.compile(numberSchema);
console.log(validateNumber(3)); // false
console.log(validateNumber.errors);
// [{ keyword: "isEven", message: "must be an even number", params: { value: 3 }, ... }]Advanced error analysis utilities for processing and filtering validation errors.
/**
* Error analysis utilities
*/
interface ErrorAnalysis {
/** Groups errors by instance path */
groupByPath(errors: ErrorObject[]): Map<string, ErrorObject[]>;
/** Filters errors by keyword */
filterByKeyword(errors: ErrorObject[], keywords: string[]): ErrorObject[];
/** Gets the most severe errors (root causes) */
getRootErrors(errors: ErrorObject[]): ErrorObject[];
/** Converts errors to field-specific format */
toFieldErrors(errors: ErrorObject[]): Record<string, string[]>;
}Usage Examples:
import Ajv from "ajv";
const ajv = new Ajv({ allErrors: true });
const complexSchema = {
type: "object",
properties: {
personal: {
type: "object",
properties: {
name: { type: "string", minLength: 1 },
age: { type: "integer", minimum: 0 }
},
required: ["name"]
},
contact: {
type: "object",
properties: {
email: { type: "string", format: "email" },
phone: { type: "string", pattern: "^\\+?\\d{10,15}$" }
},
required: ["email"]
}
},
required: ["personal", "contact"]
};
const invalidData = {
personal: { name: "", age: -1 },
contact: { email: "invalid", phone: "123" }
};
const valid = ajv.validate(complexSchema, invalidData);
if (!valid && ajv.errors) {
// Group errors by field path
const errorsByPath = new Map<string, ErrorObject[]>();
ajv.errors.forEach(error => {
const path = error.instancePath;
if (!errorsByPath.has(path)) {
errorsByPath.set(path, []);
}
errorsByPath.get(path)!.push(error);
});
// Convert to field-specific errors
const fieldErrors: Record<string, string[]> = {};
errorsByPath.forEach((errors, path) => {
const field = path.replace(/^\//, '').replace(/\//g, '.');
fieldErrors[field] = errors.map(err => err.message || 'Validation failed');
});
console.log("Field errors:", fieldErrors);
/* Output:
{
"personal.name": ["must NOT have fewer than 1 characters"],
"personal.age": ["must be >= 0"],
"contact.email": ["must match format \"email\""],
"contact.phone": ["must match pattern \"^\\+?\\d{10,15}$\""]
}
*/
// Filter specific error types
const formatErrors = ajv.errors.filter(err => err.keyword === 'format');
const requiredErrors = ajv.errors.filter(err => err.keyword === 'required');
console.log("Format errors:", formatErrors);
console.log("Required field errors:", requiredErrors);
}Error handling for asynchronous validation with proper error propagation.
/**
* Async validation error handling
*/
interface AsyncErrorHandling {
/** Handles async validation errors */
handleAsyncErrors(validation: Promise<any>): Promise<any>;
/** Wraps async validation with error formatting */
wrapAsyncValidation<T>(validate: AsyncValidateFunction<T>): (data: unknown) => Promise<T>;
}Usage Examples:
import Ajv from "ajv";
const ajv = new Ajv({
loadSchema: async (uri: string) => {
const response = await fetch(uri);
return response.json();
}
});
const asyncSchema = {
$async: true,
type: "object",
properties: {
user: { $ref: "https://example.com/user-schema.json" }
}
};
async function validateAsync(data: unknown) {
try {
const validate = await ajv.compileAsync(asyncSchema);
const result = await validate(data);
return result;
} catch (error) {
if (error instanceof Ajv.ValidationError) {
console.log("Async validation failed:");
error.errors.forEach(err => {
console.log(`- ${err.instancePath}: ${err.message}`);
});
} else {
console.log("Schema compilation failed:", error.message);
}
throw error;
}
}
// Usage with proper error handling
validateAsync({ user: { name: "Alice" } })
.then(result => console.log("Valid:", result))
.catch(error => console.log("Error:", error.message));