Comprehensive error system with detailed validation failure reporting, recovery mechanisms, and debugging information.
Core error classes for validation failures and processing errors.
/**
* Collection of validation errors
*/
class ArkErrors extends Array<ArkError> {
/** Human-readable summary of all errors */
summary: string;
/** String representation */
toString(): string;
/** Organize errors by path and error code */
by: {
path: Record<string, ArkError[]>;
code: Record<string, ArkError[]>;
};
/** Total error count */
count: number;
/** First error in the collection */
first: ArkError;
}
/**
* Individual validation error
*/
class ArkError {
/** Error message */
message: string;
/** Path to the invalid value */
path: PropertyKey[];
/** Error code/type */
code: string;
/** Expected value description */
expected: string;
/** Actual value received */
actual: string;
/** Validation context */
ctx: TraversalContext;
}
/**
* Error thrown by assert() methods
*/
class TraversalError extends Error {
/** Collection of validation errors */
errors: ArkErrors;
/** Human-readable summary */
summary: string;
/** Original input data that failed validation */
input: unknown;
}Usage Examples:
import { type } from "arktype";
const User = type({
name: "string",
age: "number.integer >= 0",
email: "string.email"
});
// Validation returns ArkErrors on failure
const result = User({
name: 123, // Wrong type
age: -5, // Invalid range
email: "not-email" // Invalid format
});
if (result instanceof type.errors) {
console.log(result.summary);
// Output: Multiple validation errors occurred
console.log(result.count); // 3
// Access individual errors
result.forEach(error => {
console.log(`${error.path.join('.')}: ${error.message}`);
});
// Organize by path
console.log(result.by.path["name"]); // Errors for name field
console.log(result.by.path["age"]); // Errors for age field
}Methods to detect and handle validation failures.
interface Type<t> {
/** Validate data, return data or ArkErrors */
(data: unknown): t | ArkErrors;
/** Validate and throw on failure */
assert(data: unknown): t;
/** Type guard - returns boolean */
allows(data: unknown): data is t;
/** Check if result is an error */
static errors: typeof ArkErrors;
}
// Error checking patterns
function isErrors(result: unknown): result is ArkErrors {
return result instanceof ArkErrors;
}Usage Examples:
const StringType = type("string");
// Pattern 1: Check return value
const result = StringType(123);
if (result instanceof type.errors) {
console.log("Validation failed:", result.summary);
} else {
console.log("Valid string:", result);
}
// Pattern 2: Use assert for exceptions
try {
const validString = StringType.assert(123);
console.log("This won't execute");
} catch (error) {
if (error instanceof TraversalError) {
console.log("Validation error:", error.summary);
}
}
// Pattern 3: Type guard for filtering
const inputs = [42, "hello", null, "world"];
const validStrings = inputs.filter(StringType.allows);
console.log(validStrings); // ["hello", "world"]
// Pattern 4: Utility function
function validateAndLog<T>(type: Type<T>, data: unknown): T | null {
const result = type(data);
if (result instanceof type.errors) {
console.error("Validation failed:", result.summary);
return null;
}
return result;
}Detailed error location and context information.
interface ArkError {
/** Path to invalid value (array of property keys) */
path: PropertyKey[];
/** Formatted path string */
pathString: string;
/** Parent object containing the invalid value */
parent: unknown;
/** The invalid value itself */
value: unknown;
/** Traversal context with additional info */
ctx: TraversalContext;
}
interface TraversalContext {
/** Current path being validated */
path: PropertyKey[];
/** Root data being validated */
root: unknown;
/** Current node being validated */
data: unknown;
/** Create error with context */
error(expected: string): ArkError;
/** Reject with detailed error info */
reject(errorInfo: ErrorInfo): false;
}
interface ErrorInfo {
code: string;
expected: string;
actual?: string;
problem?: string;
}Usage Examples:
// Nested object validation
const UserProfile = type({
user: {
personal: {
name: "string",
age: "number.integer >= 0"
},
contact: {
email: "string.email",
phone: "string"
}
},
preferences: {
theme: "'light' | 'dark'",
notifications: "boolean"
}
});
const invalidData = {
user: {
personal: {
name: 123, // Error at path: user.personal.name
age: -5 // Error at path: user.personal.age
},
contact: {
email: "invalid", // Error at path: user.contact.email
phone: null // Error at path: user.contact.phone
}
},
preferences: {
theme: "purple", // Error at path: preferences.theme
notifications: "yes" // Error at path: preferences.notifications
}
};
const result = UserProfile(invalidData);
if (result instanceof type.errors) {
result.forEach(error => {
console.log(`Path: ${error.path.join('.')}`);
console.log(`Expected: ${error.expected}`);
console.log(`Actual: ${error.actual}`);
console.log(`Message: ${error.message}`);
console.log('---');
});
// Access errors by path
const nameErrors = result.by.path["user.personal.name"];
const emailErrors = result.by.path["user.contact.email"];
}Customize error messages and descriptions for better user experience.
interface Type<t> {
/** Configure type with custom metadata */
configure(meta: TypeMeta.MappableInput): this;
/** Add custom description */
describe(description: string): this;
}
interface TypeMeta {
/** Custom description for error messages */
description?: string;
/** Custom error message */
message?: string;
/** Additional metadata */
[key: string]: unknown;
}Usage Examples:
// Custom descriptions
const Age = type("number.integer >= 0 <= 120")
.describe("a valid age between 0 and 120");
const Email = type("string.email")
.describe("a valid email address");
// Custom error configuration
const Password = type("string >= 8")
.configure({
description: "a secure password",
message: "Password must be at least 8 characters long"
});
// Usage with custom messages
const UserForm = type({
username: type("string >= 3 <= 20")
.describe("a username (3-20 characters)"),
password: type("string >= 8")
.describe("a password (minimum 8 characters)"),
confirmPassword: type("string")
.describe("password confirmation"),
age: type("number.integer >= 13")
.describe("age (must be 13 or older)")
});
const result = UserForm({
username: "ab", // Too short
password: "123", // Too short
confirmPassword: "different", // Would need custom validation
age: 12 // Too young
});
if (result instanceof type.errors) {
result.forEach(error => {
console.log(`${error.path.join('.')}: ${error.message}`);
});
// Output includes custom descriptions
}Strategies for handling validation errors in applications.
// Error recovery utilities
function tryValidate<T>(type: Type<T>, data: unknown):
{ success: true; data: T } | { success: false; errors: ArkErrors } {
const result = type(data);
if (result instanceof type.errors) {
return { success: false, errors: result };
}
return { success: true, data: result };
}
// Partial validation with error collection
function validatePartial<T extends object>(
schema: { [K in keyof T]: Type<T[K]> },
data: Partial<T>
): { valid: Partial<T>; errors: Record<string, ArkErrors> } {
const valid: Partial<T> = {};
const errors: Record<string, ArkErrors> = {};
for (const [key, validator] of Object.entries(schema)) {
if (key in data) {
const result = validator(data[key as keyof T]);
if (result instanceof type.errors) {
errors[key] = result;
} else {
valid[key as keyof T] = result;
}
}
}
return { valid, errors };
}Usage Examples:
// Error recovery pattern
const UserValidator = type({
name: "string",
email: "string.email",
age: "number.integer >= 0"
});
function processUser(userData: unknown) {
const validation = tryValidate(UserValidator, userData);
if (validation.success) {
// Process valid user
return { status: 'success', user: validation.data };
} else {
// Handle validation errors
return {
status: 'error',
message: validation.errors.summary,
details: validation.errors.by.path
};
}
}
// Graceful degradation
function processUserWithDefaults(userData: unknown) {
const result = UserValidator(userData);
if (result instanceof type.errors) {
// Extract what we can and apply defaults
const fallbackUser = {
name: (userData as any)?.name || 'Anonymous',
email: 'noreply@example.com',
age: 0
};
return {
user: fallbackUser,
warnings: result.summary,
isComplete: false
};
}
return {
user: result,
warnings: null,
isComplete: true
};
}
// Form validation with field-level errors
function validateForm(formData: Record<string, unknown>) {
const fieldValidators = {
username: type("string >= 3").describe("username"),
email: type("string.email").describe("email address"),
age: type("string.integer.parse >= 18").describe("age (18+)")
};
const { valid, errors } = validatePartial(fieldValidators, formData);
return {
isValid: Object.keys(errors).length === 0,
data: valid,
fieldErrors: Object.fromEntries(
Object.entries(errors).map(([field, errs]) => [
field,
errs.summary
])
)
};
}Tools for debugging validation issues and understanding type behavior.
interface Type<t> {
/** JSON representation of the type */
json: JsonStructure;
/** Human-readable expression */
expression: string;
/** Natural language description */
description: string;
/** Generate JSON Schema */
toJsonSchema(): JsonSchema;
}
interface ArkErrors {
/** Detailed error information for debugging */
details: Array<{
path: string;
expected: string;
actual: string;
code: string;
}>;
/** Original input data */
input: unknown;
}Usage Examples:
// Type introspection for debugging
const ComplexType = type({
user: {
id: "string.uuid",
profile: {
name: "string.trim >= 2",
email: "string.email",
age: "number.integer >= 18"
}
},
metadata: {
created: "string.date.iso",
source: "'web' | 'mobile' | 'api'"
}
});
// Debug type structure
console.log("Type expression:", ComplexType.expression);
console.log("Type description:", ComplexType.description);
console.log("JSON Schema:", ComplexType.toJsonSchema());
// Debug validation failures
const testData = {
user: {
id: "not-a-uuid",
profile: {
name: "X",
email: "invalid-email",
age: 16
}
},
metadata: {
created: "not-a-date",
source: "unknown"
}
};
const result = ComplexType(testData);
if (result instanceof type.errors) {
console.log("=== Validation Report ===");
console.log("Summary:", result.summary);
console.log("Total errors:", result.count);
console.log("\n=== Error Details ===");
result.forEach((error, index) => {
console.log(`Error ${index + 1}:`);
console.log(` Path: ${error.path.join('.')}`);
console.log(` Expected: ${error.expected}`);
console.log(` Received: ${error.actual}`);
console.log(` Message: ${error.message}`);
});
console.log("\n=== Errors by Path ===");
for (const [path, pathErrors] of Object.entries(result.by.path)) {
console.log(`${path}: ${pathErrors.map(e => e.message).join(', ')}`);
}
}