TypeScript runtime type system for IO decoding/encoding
72
Comprehensive error reporting system with detailed context information for debugging validation failures.
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;
}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" }
]);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;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);
// }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)
)
);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)"]
}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']"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
}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-tsdocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10