Runtime validation for static types
—
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.
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"
}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 failedUsage 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", ... }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 loggedimport { 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"
}
}
}
}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}`);
}
});
}
});
}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
}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));
}
}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}`);
});
}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