or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

built-in-validators.mddata-transformation.mderror-handling.mdgeneric-types.mdindex.mdscope-modules.mdstring-processing.mdtype-creation.md
tile.json

error-handling.mddocs/

Error Handling

Comprehensive error system with detailed validation failure reporting, recovery mechanisms, and debugging information.

Capabilities

Error Types

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
}

Error Detection and Checking

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;
}

Error Paths and Context

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"];
}

Custom Error Messages

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
}

Error Recovery and Handling Patterns

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
      ])
    )
  };
}

Debugging and Introspection

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(', ')}`);
  }
}