Runtime library to validate data against TypeScript interfaces
—
Comprehensive error reporting with path information, nested error details, and custom error types for validation failures.
Custom error class for validation failures with path information.
/**
* Error thrown by validation. Besides an informative message, it includes
* the path to the property which triggered the failure.
*/
class VError extends Error {
constructor(public path: string, message: string);
}Usage Examples:
import { createCheckers, VError } from "ts-interface-checker";
import userTypes from "./user-ti";
const { User } = createCheckers(userTypes);
try {
User.check({ name: "Alice" }); // Missing age property
} catch (error) {
if (error instanceof VError) {
console.log("Path:", error.path); // "value.age"
console.log("Message:", error.message); // "value.age is missing"
// Extract just the property path
const propertyPath = error.path.replace(/^value\.?/, "");
console.log("Property:", propertyPath); // "age"
}
}
// Custom path reporting
User.setReportedPath("userData");
try {
User.check({ name: "Bob" });
} catch (error) {
if (error instanceof VError) {
console.log("Path:", error.path); // "userData.age"
}
}Detailed error information structure for comprehensive error reporting.
/**
* Describes errors as returned by validate() and validateStrict() methods
*/
interface IErrorDetail {
path: string;
message: string;
nested?: IErrorDetail[];
}Usage Examples:
const { User } = createCheckers(userTypes);
const invalidData = {
name: 123, // Should be string
profile: {
email: "not-an-email", // Invalid email format
age: "thirty" // Should be number
}
};
const errors = User.validate(invalidData);
if (errors) {
function printErrors(errorList: IErrorDetail[], indent = 0) {
for (const error of errorList) {
const prefix = " ".repeat(indent);
console.log(`${prefix}${error.path}: ${error.message}`);
if (error.nested) {
printErrors(error.nested, indent + 1);
}
}
}
printErrors(errors);
// Output:
// value.name: is not a string
// value.profile.email: is not a valid email
// value.profile.age: is not a number
}Internal context system for collecting and reporting validation errors.
/**
* Context interface used during validation to collect error messages
*/
interface IContext {
fail(relPath: string|number|null, message: string|null, score: number): false;
unionResolver(): IUnionResolver;
resolveUnion(ur: IUnionResolver): void;
fork(): IContext;
completeFork(): boolean;
failed(): boolean;
}The context system is used internally but understanding it helps with advanced error handling:
Context Types:
/**
* Union resolver interface for handling union type validation errors
*/
interface IUnionResolver {
createContext(): IContext;
}
/**
* Fast implementation for boolean-only validation (no error messages)
*/
class NoopContext implements IContext, IUnionResolver {
fail(relPath: string|number|null, message: string|null, score: number): false;
fork(): IContext;
completeFork(): boolean;
failed(): boolean;
unionResolver(): IUnionResolver;
createContext(): IContext;
resolveUnion(ur: IUnionResolver): void;
}
/**
* Complete implementation that collects detailed error information
*/
class DetailContext implements IContext {
static maxForks: number; // Maximum number of errors recorded at one level (default: 3)
fail(relPath: string|number|null, message: string|null, score: number): false;
fork(): IContext;
completeFork(): boolean;
failed(): boolean;
unionResolver(): IUnionResolver;
resolveUnion(ur: IUnionResolver): void;
getError(path: string): VError;
getErrorDetails(path: string): IErrorDetail[];
}Common patterns for handling validation errors in applications.
Usage Examples:
import { createCheckers, VError, IErrorDetail } from "ts-interface-checker";
const { User } = createCheckers(userTypes);
// Basic error handling with try-catch
function validateUserData(data: unknown): User | null {
try {
User.check(data);
return data as User;
} catch (error) {
if (error instanceof VError) {
console.error(`Validation failed at ${error.path}: ${error.message}`);
}
return null;
}
}
// Detailed error handling with validate()
function validateWithDetails(data: unknown): {
isValid: boolean;
user?: User;
errors?: IErrorDetail[]
} {
const errors = User.validate(data);
if (errors) {
return { isValid: false, errors };
}
return { isValid: true, user: data as User };
}
// API response error formatting
function formatValidationErrors(errors: IErrorDetail[]): string[] {
function extractMessages(errorList: IErrorDetail[]): string[] {
const messages: string[] = [];
for (const error of errorList) {
messages.push(`${error.path}: ${error.message}`);
if (error.nested) {
messages.push(...extractMessages(error.nested));
}
}
return messages;
}
return extractMessages(errors);
}
// Express.js middleware example
function validateRequestBody(checkerName: string) {
return (req: any, res: any, next: any) => {
const checker = checkers[checkerName];
const errors = checker.validate(req.body);
if (errors) {
return res.status(400).json({
error: "Validation failed",
details: formatValidationErrors(errors)
});
}
next();
};
}Understanding error message structure for custom handling.
Error Message Types:
// Different error scenarios produce different message patterns:
// Missing required property
// Path: "value.age", Message: "is missing"
// Wrong type
// Path: "value.name", Message: "is not a string"
// Extra property (strict mode)
// Path: "value.extra", Message: "is extraneous"
// Union type mismatch
// Path: "value", Message: "is none of string, number"
// Array index error
// Path: "value[2]", Message: "is not a number"
// Nested object error
// Path: "value.profile.email", Message: "is not a string"Usage Examples:
// Custom error message processing
function processError(error: VError): { field: string; type: string; message: string } {
const path = error.path;
const message = error.message;
// Extract field name from path
const field = path.replace(/^value\.?/, "") || "root";
// Determine error type from message
let type = "unknown";
if (message.includes("is missing")) {
type = "required";
} else if (message.includes("is not a")) {
type = "type";
} else if (message.includes("is extraneous")) {
type = "extra";
} else if (message.includes("is none of")) {
type = "union";
}
return { field, type, message };
}
// Usage
try {
User.check({ name: 123 });
} catch (error) {
if (error instanceof VError) {
const processed = processError(error);
console.log(processed);
// { field: "name", type: "type", message: "value.name is not a string" }
}
}Install with Tessl CLI
npx tessl i tessl/npm-ts-interface-checker