CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-io-ts

TypeScript runtime type system for IO decoding/encoding

72

1.14x
Overview
Eval results
Files

validation.mddocs/

Validation & Error Handling

Comprehensive error reporting system with detailed context information for debugging validation failures.

Capabilities

Validation Types

Core types for validation results and error handling.

/** Validation result type - Either success or array of errors */
type Validation<A> = Either<Errors, A>;

/** Array of validation errors with context information */
interface Errors extends Array<ValidationError> {}

/** Individual validation error with context and optional message */
interface ValidationError {
  /** the offending (sub)value */
  readonly value: unknown;
  /** where the error originated */
  readonly context: Context;
  /** optional custom error message */
  readonly message?: string;
}

/** Context information for error path tracking */
interface Context extends ReadonlyArray<ContextEntry> {}

/** Single context entry in the validation path */
interface ContextEntry {
  readonly key: string;
  readonly type: Decoder<any, any>;
  /** the input data */
  readonly actual?: unknown;
}

Validation Functions

Functions for creating validation results.

/**
 * Create a successful validation result
 * @param value - The successful value
 */
function success<T>(value: T): Validation<T>;

/**
 * Create a single validation failure
 * @param value - The failing value
 * @param context - Context where the failure occurred
 * @param message - Optional error message
 */
function failure<T>(value: unknown, context: Context, message?: string): Validation<T>;

/**
 * Create a validation failure from multiple errors
 * @param errors - Array of validation errors
 */
function failures<T>(errors: Errors): Validation<T>;

Usage Example:

import * as t from "io-ts";

// Create context for error reporting
const context: t.Context = [{ key: '', type: t.string, actual: 42 }];

// Create validation results
const successResult = t.success("hello");
const failureResult = t.failure(42, context, "Expected string but got number");
const multipleFailures = t.failures([
  { value: 42, context, message: "Not a string" },
  { value: null, context, message: "Cannot be null" }
]);

Context Utilities

Functions for managing validation context during error reporting.

/**
 * Create a context entry for error reporting
 * @param key - Property key or array index
 * @param decoder - The decoder that failed
 */
function getContextEntry(key: string, decoder: Decoder<any, any>): ContextEntry;

/**
 * Append a new entry to the validation context
 * @param context - Current context
 * @param key - Property key or array index
 * @param decoder - The decoder being applied
 * @param actual - The actual value being validated
 */
function appendContext(
  context: Context, 
  key: string, 
  decoder: Decoder<any, any>, 
  actual?: unknown
): Context;

Error Reporters

Built-in reporters for formatting validation errors.

/** Reporter interface for formatting validation results */
interface Reporter<A> {
  report(validation: Validation<A>): A;
}

/** Path reporter that formats errors as readable strings */
const PathReporter: Reporter<Array<string>>;

/**
 * @deprecated Use PathReporter instead
 * Throw reporter that throws on validation failure
 */
const ThrowReporter: Reporter<any>;

Usage Examples:

import * as t from "io-ts";
import { PathReporter } from "io-ts/PathReporter";
import { ThrowReporter } from "io-ts/ThrowReporter";

const User = t.type({
  name: t.string,
  age: t.number,
  email: t.string
});

const invalidData = {
  name: 123,
  age: "thirty",
  email: null
};

const result = User.decode(invalidData);

if (result._tag === "Left") {
  // Use PathReporter for readable error messages
  const errors = PathReporter.failure(result.left);
  console.log("Validation errors:");
  errors.forEach(error => console.log(`  - ${error}`));
  
  // Output:
  // - Invalid value 123 at path: name (expected: string)
  // - Invalid value "thirty" at path: age (expected: number)  
  // - Invalid value null at path: email (expected: string)
}

// ❌ ThrowReporter usage (deprecated - don't use)
// try {
//   ThrowReporter.report(result);
// } catch (error) {
//   console.error("Validation failed:", error.message);
// }

Advanced Error Handling

Custom Error Messages

import * as t from "io-ts";
from { pipe } from "fp-ts/function";
import { fold } from "fp-ts/Either";

const EmailCodec = new t.Type<string, string, unknown>(
  'Email',
  (u): u is string => typeof u === 'string' && /\S+@\S+\.\S+/.test(u),
  (u, c) => {
    if (typeof u !== 'string') {
      return t.failure(u, c, 'Must be a string');
    }
    if (!/\S+@\S+\.\S+/.test(u)) {
      return t.failure(u, c, 'Must be a valid email address');
    }
    return t.success(u);
  },
  t.identity
);

const result = EmailCodec.decode("invalid-email");
pipe(
  result,
  fold(
    (errors) => {
      console.log("Custom error:", errors[0].message);
      // Output: "Must be a valid email address"
    },
    (email) => console.log("Valid email:", email)
  )
);

Error Context Tracking

import * as t from "io-ts";
import { PathReporter } from "io-ts/PathReporter";

const NestedData = t.type({
  users: t.array(t.type({
    profile: t.type({
      contact: t.type({
        email: t.string,
        phone: t.string
      })
    })
  }))
});

const invalidData = {
  users: [
    {
      profile: {
        contact: {
          email: "valid@example.com",
          phone: 123 // Should be string
        }
      }
    }
  ]
};

const result = NestedData.decode(invalidData);
if (result._tag === "Left") {
  const errors = PathReporter.failure(result.left);
  console.log(errors);
  // Output: ["Invalid value 123 at users.0.profile.contact.phone (expected: string)"]
}

Working with Either Results

import * as t from "io-ts";
import { pipe } from "fp-ts/function";
import { fold, map, mapLeft } from "fp-ts/Either";

const User = t.type({
  name: t.string,
  age: t.number
});

const processUser = (data: unknown) =>
  pipe(
    User.decode(data),
    mapLeft(errors => ({
      type: 'VALIDATION_ERROR' as const,
      errors: errors.map(e => e.message || 'Validation failed')
    })),
    map(user => ({
      type: 'SUCCESS' as const,
      user: {
        ...user,
        displayName: user.name.toUpperCase()
      }
    })),
    fold(
      error => {
        console.error('Validation failed:', error.errors);
        return null;
      },
      success => {
        console.log('User processed:', success.user);
        return success.user;
      }
    )
  );

// Usage
const result1 = processUser({ name: "Alice", age: 30 });
// Output: "User processed: { name: 'Alice', age: 30, displayName: 'ALICE' }"

const result2 = processUser({ name: "Bob" }); // Missing age
// Output: "Validation failed: ['Validation failed']"

Error Aggregation

import * as t from "io-ts";
import { sequenceT } from "fp-ts/Apply";
import { either } from "fp-ts/Either";

// Validate multiple independent values and collect all errors
const validateMultiple = (data: {
  name: unknown;
  email: unknown; 
  age: unknown;
}) => {
  return sequenceT(either)(
    t.string.decode(data.name),
    t.string.decode(data.email),
    t.number.decode(data.age)
  );
};

const result = validateMultiple({
  name: 123,     // Invalid
  email: null,   // Invalid  
  age: "thirty"  // Invalid
});

if (result._tag === "Left") {
  console.log("All validation errors collected:", result.left);
  // All three validation errors are collected together
}

Custom Reporter

import * as t from "io-ts";

// Create a custom reporter that formats errors differently
const CustomReporter: t.Reporter<{ success: boolean; errors: string[] }> = {
  report: (validation) => {
    if (validation._tag === "Right") {
      return { success: true, errors: [] };
    } else {
      return {
        success: false,
        errors: validation.left.map(error => {
          const path = error.context.map(c => c.key).join('.');
          return `${path}: ${error.message || 'Validation failed'}`;
        })
      };
    }
  }
};

const User = t.type({ name: t.string, age: t.number });
const result = User.decode({ name: 123, age: "thirty" });
const report = CustomReporter.report(result);

console.log(report);
// Output: { 
//   success: false, 
//   errors: ["name: Validation failed", "age: Validation failed"] 
// }

Install with Tessl CLI

npx tessl i tessl/npm-io-ts

docs

codec.md

combinators.md

core-types.md

decoder.md

encoder.md

index.md

infrastructure.md

primitives.md

refinement.md

reporters.md

schema.md

task-decoder.md

validation.md

tile.json