CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-runtypes

Runtime validation for static types

Pending
Overview
Eval results
Files

results.mddocs/

Results and Errors

Structured results for validation operations with detailed error information. The result system provides comprehensive feedback about validation success or failure, including specific error codes and contextual information.

Capabilities

Result Types

The fundamental result types returned by validation operations.

/**
 * Result of a validation operation - either success or failure
 */
type Result<T> = Success<T> | Failure;

/**
 * Successful validation result
 */
interface Success<T> {
  success: true;
  value: T;
}

/**
 * Failed validation result with detailed error information
 */
interface Failure {
  success: false;
  code: Failcode;
  message: string;
  expected: Runtype;
  received: unknown;
  details?: Record<PropertyKey, Failure>;
  detail?: Failure;
  thrown?: unknown;
}

Usage Examples:

import { String, Number, Object } from "runtypes";

// Inspect method returns Result<T>
const stringResult = String.inspect("hello");
if (stringResult.success) {
  console.log("Value:", stringResult.value); // "hello"
} else {
  console.log("Error:", stringResult.message);
}

// Failed validation
const numberResult = Number.inspect("not a number");
if (!numberResult.success) {
  console.log("Code:", numberResult.code); // "TYPE_INCORRECT"
  console.log("Expected:", numberResult.expected.tag); // "number"
  console.log("Received:", numberResult.received); // "not a number"
}

Failure Codes

Predefined error codes that categorize different types of validation failures.

/**
 * Predefined error codes for validation failures
 */
type Failcode = 
  | "TYPE_INCORRECT"      // Wrong primitive type (string vs number)
  | "VALUE_INCORRECT"     // Wrong value (42 vs 43 for Literal(42))
  | "KEY_INCORRECT"       // Invalid object key in Record
  | "CONTENT_INCORRECT"   // Invalid array/object contents
  | "ARGUMENTS_INCORRECT" // Invalid function arguments
  | "RETURN_INCORRECT"    // Invalid function return value
  | "RESOLVE_INCORRECT"   // Invalid Promise resolution value
  | "CONSTRAINT_FAILED"   // Custom constraint not satisfied
  | "PROPERTY_MISSING"    // Required object property missing
  | "PROPERTY_PRESENT"    // Unexpected property in exact object
  | "NOTHING_EXPECTED"    // Value provided but none expected
  | "PARSING_FAILED"      // Parser function threw error
  | "INSTANCEOF_FAILED";  // instanceof check failed

Usage Examples:

import { String, Number, Object, Array, Literal, Union } from "runtypes";

// TYPE_INCORRECT - wrong primitive type
const typeError = String.inspect(123);
// { success: false, code: "TYPE_INCORRECT", message: "Expected string, but was number", ... }

// VALUE_INCORRECT - wrong literal value
const valueError = Literal("hello").inspect("world");
// { success: false, code: "VALUE_INCORRECT", message: "Expected \"hello\", but was \"world\"", ... }

// CONTENT_INCORRECT - array/object content errors
const User = Object({ name: String, age: Number });
const contentError = User.inspect({ name: "Alice", age: "not a number" });
// { success: false, code: "CONTENT_INCORRECT", details: { age: Failure }, ... }

// PROPERTY_MISSING - required property missing
const missingError = User.inspect({ name: "Alice" });
// { success: false, code: "CONTENT_INCORRECT", details: { age: { code: "PROPERTY_MISSING", ... } }, ... }

// CONSTRAINT_FAILED - custom constraint
const PositiveNumber = Number.withConstraint(n => n > 0 || "Must be positive");
const constraintError = PositiveNumber.inspect(-5);
// { success: false, code: "CONSTRAINT_FAILED", thrown: "Must be positive", ... }

ValidationError

Exception thrown by validation methods when validation fails.

/**
 * Error thrown when validation fails
 */
class ValidationError extends Error {
  name: "ValidationError";
  message: string;
  failure: Failure;
  
  constructor(failure: Failure);
  
  static isValidationError(value: unknown): value is ValidationError;
}

Usage Examples:

import { String, ValidationError } from "runtypes";

// Catching validation errors
try {
  const result = String.check(123);
} catch (error) {
  if (ValidationError.isValidationError(error)) {
    console.log("Validation failed:", error.message);
    console.log("Error code:", error.failure.code);
    console.log("Expected:", error.failure.expected.tag);
    console.log("Received:", error.failure.received);
  }
}

// Custom error handling
function safeValidate<T>(runtype: Runtype<T>, value: unknown): T | null {
  try {
    return runtype.check(value);
  } catch (error) {
    if (ValidationError.isValidationError(error)) {
      console.warn(`Validation failed: ${error.message}`);
      return null;
    }
    throw error; // Re-throw non-validation errors
  }
}

const result = safeValidate(String, 123); // null, with warning logged

Complex Error Structures

Nested Object Errors

import { Object, String, Number, Array } from "runtypes";

const User = Object({
  profile: Object({
    name: String,
    age: Number,
    contact: Object({
      email: String,
      phone: String
    })
  }),
  preferences: Object({
    theme: Union(Literal("light"), Literal("dark")),
    notifications: Boolean
  })
});

// Validate complex nested structure
const result = User.inspect({
  profile: {
    name: "Alice",
    age: "not a number", // Error here
    contact: {
      email: "alice@example.com",
      phone: 1234567890 // Error here - should be string
    }
  },
  preferences: {
    theme: "invalid", // Error here
    notifications: true
  }
});

if (!result.success) {
  console.log("Main error:", result.code); // "CONTENT_INCORRECT"
  
  // Navigate nested errors
  const profileErrors = result.details?.profile;
  if (profileErrors && !profileErrors.success) {
    const ageError = profileErrors.details?.age;
    const contactErrors = profileErrors.details?.contact;
    
    if (ageError && !ageError.success) {
      console.log("Age error:", ageError.code); // "TYPE_INCORRECT"
    }
    
    if (contactErrors && !contactErrors.success) {
      const phoneError = contactErrors.details?.phone;
      if (phoneError && !phoneError.success) {
        console.log("Phone error:", phoneError.code); // "TYPE_INCORRECT"
      }
    }
  }
}

Array Validation Errors

import { Array, Object, String, Number } from "runtypes";

const Users = Array(Object({
  id: Number,
  name: String,
  email: String
}));

const result = Users.inspect([
  { id: 1, name: "Alice", email: "alice@example.com" }, // Valid
  { id: "2", name: "Bob", email: "bob@example.com" },  // Invalid id
  { id: 3, name: "Charlie" },                          // Missing email
  { id: 4, name: "David", email: "david@example.com" } // Valid
]);

if (!result.success) {
  console.log("Array validation failed");
  
  // Check individual element errors
  Object.entries(result.details || {}).forEach(([index, error]) => {
    console.log(`Element ${index} errors:`);
    if (!error.success && error.details) {
      Object.entries(error.details).forEach(([field, fieldError]) => {
        if (!fieldError.success) {
          console.log(`  ${field}: ${fieldError.message}`);
        }
      });
    }
  });
}

Union Validation Errors

import { Union, String, Number, Object, Literal } from "runtypes";

const ID = Union(String, Number);
const Status = Union(Literal("active"), Literal("inactive"), Literal("pending"));

// Union errors show all attempted alternatives
const idResult = ID.inspect(true);
if (!idResult.success) {
  console.log("Union validation failed:", idResult.code); // "TYPE_INCORRECT"
  console.log("Details:", idResult.details);
  // Details will contain failure information for each alternative:
  // { 0: Failure (String attempt), 1: Failure (Number attempt) }
}

const statusResult = Status.inspect("unknown");
if (!statusResult.success) {
  console.log("Status validation failed");
  // Shows failures for each literal alternative
}

Error Analysis and Debugging

Custom Error Formatters

import { ValidationError, type Failure } from "runtypes";

function formatValidationError(failure: Failure, path: string = ""): string {
  const location = path ? `at ${path}: ` : "";
  
  switch (failure.code) {
    case "TYPE_INCORRECT":
      return `${location}Expected ${failure.expected.tag} but received ${typeof failure.received}`;
    
    case "VALUE_INCORRECT":
      return `${location}Expected specific value but received ${JSON.stringify(failure.received)}`;
    
    case "PROPERTY_MISSING":
      return `${location}Required property is missing`;
    
    case "PROPERTY_PRESENT":
      return `${location}Unexpected property ${JSON.stringify(failure.received)}`;
    
    case "CONTENT_INCORRECT":
      if (failure.details) {
        const errors = Object.entries(failure.details)
          .map(([key, detail]) => formatValidationError(detail, `${path}${path ? '.' : ''}${key}`))
          .join('\n');
        return `${location}Content validation failed:\n${errors}`;
      }
      return `${location}Content validation failed`;
    
    case "CONSTRAINT_FAILED":
      return `${location}Constraint failed: ${failure.thrown}`;
    
    default:
      return `${location}Validation failed: ${failure.message}`;
  }
}

// Usage
try {
  const user = User.check(invalidData);
} catch (error) {
  if (ValidationError.isValidationError(error)) {
    console.log(formatValidationError(error.failure));
  }
}

Error Collection and Reporting

import { type Failure } from "runtypes";

interface ValidationReport {
  isValid: boolean;
  errors: Array<{
    path: string;
    code: string;
    message: string;
    expected: string;
    received: unknown;
  }>;
}

function collectErrors(failure: Failure, path: string = ""): ValidationReport["errors"] {
  const errors: ValidationReport["errors"] = [];
  
  if (failure.code === "CONTENT_INCORRECT" && failure.details) {
    // Collect nested errors
    for (const [key, detail] of Object.entries(failure.details)) {
      const nestedPath = path ? `${path}.${key}` : key;
      errors.push(...collectErrors(detail, nestedPath));
    }
  } else {
    // Leaf error
    errors.push({
      path,
      code: failure.code,
      message: failure.message,
      expected: failure.expected.tag,
      received: failure.received
    });
  }
  
  return errors;
}

function validateWithReport<T>(runtype: Runtype<T>, value: unknown): ValidationReport {
  const result = runtype.inspect(value);
  
  if (result.success) {
    return { isValid: true, errors: [] };
  }
  
  return {
    isValid: false,
    errors: collectErrors(result)
  };
}

// Usage
const report = validateWithReport(User, userData);
if (!report.isValid) {
  console.log("Validation errors:");
  report.errors.forEach(error => {
    console.log(`- ${error.path}: ${error.message}`);
  });
}

Development vs Production Error Handling

import { ValidationError } from "runtypes";

class AppError extends Error {
  constructor(
    message: string,
    public code: string,
    public statusCode: number = 400,
    public details?: any
  ) {
    super(message);
    this.name = "AppError";
  }
}

function handleValidationError(error: ValidationError): AppError {
  if (process.env.NODE_ENV === 'development') {
    // Detailed errors in development
    return new AppError(
      `Validation failed: ${error.message}`,
      'VALIDATION_ERROR',
      400,
      {
        code: error.failure.code,
        expected: error.failure.expected.tag,
        received: error.failure.received,
        path: error.failure.details ? Object.keys(error.failure.details) : undefined
      }
    );
  } else {
    // Generic errors in production
    return new AppError(
      "Invalid request data",
      'VALIDATION_ERROR',
      400
    );
  }
}

// Usage in API handlers
function createUser(requestData: unknown) {
  try {
    const userData = UserSchema.check(requestData);
    // Process valid user data...
    return userData;
  } catch (error) {
    if (ValidationError.isValidationError(error)) {
      throw handleValidationError(error);
    }
    throw error;
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-runtypes

docs

composite.md

constraints.md

contracts.md

index.md

literals.md

primitives.md

results.md

templates.md

union-intersect.md

utilities.md

validation.md

tile.json