or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

composite.mdconstraints.mdcontracts.mdindex.mdliterals.mdprimitives.mdresults.mdtemplates.mdunion-intersect.mdutilities.mdvalidation.md

validation.mddocs/

0

# Validation Methods

1

2

Every runtype provides essential validation methods for different use cases. These methods form the core API for runtime type checking and offer different approaches depending on whether you need exceptions, type guards, or detailed results.

3

4

## Capabilities

5

6

### Core Validation Methods

7

8

Every runtype implements these fundamental validation methods.

9

10

```typescript { .api }

11

/**

12

* Core validation methods available on every runtype

13

*/

14

interface Runtype<T> {

15

/**

16

* Validates and returns the value, throwing ValidationError on failure

17

* @param x - Value to validate

18

* @returns Validated value with proper typing

19

* @throws ValidationError when validation fails

20

*/

21

check<U>(x: U): T & U;

22

23

/**

24

* Type guard that returns boolean indicating validation success

25

* @param x - Value to validate

26

* @returns True if valid, false otherwise (no exceptions)

27

*/

28

guard<U>(x: U): x is T & U;

29

30

/**

31

* Assertion function that throws ValidationError on failure

32

* @param x - Value to validate

33

* @throws ValidationError when validation fails

34

*/

35

assert<U>(x: U): asserts x is T & U;

36

37

/**

38

* Validates and parses the value, applying transformations

39

* @param x - Value to validate and parse

40

* @returns Parsed/transformed value

41

* @throws ValidationError when validation fails

42

*/

43

parse<U>(x: U): T;

44

45

/**

46

* Detailed validation with structured result (no exceptions)

47

* @param x - Value to validate

48

* @param options - Validation options

49

* @returns Result object with success/failure details

50

*/

51

inspect<U>(x: U, options?: { parse?: boolean }): Result<T>;

52

}

53

```

54

55

### check - Validated Value with Exceptions

56

57

The most commonly used validation method. Returns the validated value or throws an exception.

58

59

```typescript

60

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

61

62

// Basic usage

63

const name = String.check("Alice"); // "Alice"

64

const age = Number.check(25); // 25

65

66

// Throws ValidationError on failure

67

try {

68

const invalid = String.check(123);

69

} catch (error) {

70

console.log("Validation failed:", error.message);

71

}

72

73

// Type narrowing with intersection

74

function processUserData(input: unknown) {

75

const userData = Object({

76

name: String,

77

age: Number,

78

email: String

79

}).check(input);

80

81

// userData is now properly typed

82

console.log(userData.name.toUpperCase()); // TypeScript knows name is string

83

console.log(userData.age + 10); // TypeScript knows age is number

84

}

85

86

// Array validation

87

const scores = Array(Number).check([95, 87, 92]); // number[]

88

scores.forEach(score => console.log(score * 1.1)); // TypeScript knows each score is number

89

```

90

91

### guard - Type Guard with Boolean Result

92

93

Returns a boolean and acts as a TypeScript type guard. Never throws exceptions.

94

95

```typescript

96

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

97

98

// Basic type guarding

99

function processValue(input: unknown) {

100

if (String.guard(input)) {

101

// TypeScript knows input is string here

102

return input.toUpperCase();

103

}

104

105

if (Number.guard(input)) {

106

// TypeScript knows input is number here

107

return input * 2;

108

}

109

110

return "Unknown type";

111

}

112

113

// Object type guarding

114

const User = Object({

115

id: Number,

116

name: String,

117

active: Boolean

118

});

119

120

function handleUserData(data: unknown) {

121

if (User.guard(data)) {

122

// data is now typed as { id: number; name: string; active: boolean }

123

console.log(`User ${data.id}: ${data.name} (${data.active ? 'active' : 'inactive'})`);

124

} else {

125

console.log("Invalid user data");

126

}

127

}

128

129

// Union type discrimination

130

const Status = Union(Literal("pending"), Literal("approved"), Literal("rejected"));

131

132

function handleStatus(status: unknown) {

133

if (Status.guard(status)) {

134

// status is now typed as "pending" | "approved" | "rejected"

135

switch (status) {

136

case "pending":

137

console.log("Waiting for approval");

138

break;

139

case "approved":

140

console.log("Request approved");

141

break;

142

case "rejected":

143

console.log("Request rejected");

144

break;

145

}

146

}

147

}

148

```

149

150

### assert - Assertion Function

151

152

Assertion function that throws on failure but doesn't return a value. Useful for type narrowing.

153

154

```typescript

155

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

156

157

// Basic assertion

158

function processString(input: unknown) {

159

String.assert(input);

160

// input is now typed as string

161

return input.toLowerCase();

162

}

163

164

// Multiple assertions

165

function processUserInput(data: unknown) {

166

const UserData = Object({

167

name: String,

168

age: Number,

169

email: String

170

});

171

172

UserData.assert(data);

173

// data is now properly typed

174

175

console.log(`Processing user: ${data.name}`);

176

console.log(`Age: ${data.age}`);

177

console.log(`Email: ${data.email}`);

178

}

179

180

// Conditional assertions

181

function validateConfig(config: unknown) {

182

if (typeof config === "object" && config !== null) {

183

Object({

184

apiKey: String,

185

timeout: Number,

186

retries: Number.optional()

187

}).assert(config);

188

189

// config is now properly typed

190

return config;

191

}

192

193

throw new Error("Config must be an object");

194

}

195

```

196

197

### parse - Value Transformation

198

199

Validates and applies parsing/transformation. The difference from `check` is that `parse` applies any parsers in the runtype chain.

200

201

```typescript

202

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

203

204

// Basic parsing (same as check for simple types)

205

const name = String.parse("Alice"); // "Alice"

206

const age = Number.parse(25); // 25

207

208

// With parser transformations

209

const UppercaseString = String.withParser(s => s.toUpperCase());

210

const result = UppercaseString.parse("hello"); // "HELLO"

211

212

const DateFromString = String.withParser(s => new Date(s));

213

const date = DateFromString.parse("2024-01-15"); // Date object

214

215

// Complex object parsing

216

const UserInput = Object({

217

name: String,

218

age: String, // Input as string

219

preferences: String // JSON string

220

});

221

222

const UserProcessor = UserInput.withParser(input => ({

223

name: input.name.trim(),

224

age: parseInt(input.age, 10),

225

preferences: JSON.parse(input.preferences),

226

processedAt: new Date()

227

}));

228

229

const user = UserProcessor.parse({

230

name: " Alice ",

231

age: "25",

232

preferences: '{"theme": "dark", "notifications": true}'

233

});

234

// {

235

// name: "Alice",

236

// age: 25,

237

// preferences: { theme: "dark", notifications: true },

238

// processedAt: Date

239

// }

240

241

// Chained transformations

242

const ProcessedString = String

243

.withParser(s => s.trim())

244

.withParser(s => s.toLowerCase())

245

.withParser(s => s.replace(/\s+/g, "-"));

246

247

const slug = ProcessedString.parse(" Hello World "); // "hello-world"

248

```

249

250

### inspect - Detailed Results

251

252

Returns detailed Result object without throwing exceptions. Useful when you need comprehensive error information.

253

254

```typescript

255

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

256

257

// Basic inspection

258

const stringResult = String.inspect("hello");

259

if (stringResult.success) {

260

console.log("Valid string:", stringResult.value);

261

} else {

262

console.log("Error:", stringResult.message);

263

console.log("Code:", stringResult.code);

264

}

265

266

// Parsing vs checking with inspect

267

const UppercaseString = String.withParser(s => s.toUpperCase());

268

269

// Without parsing

270

const checkResult = UppercaseString.inspect("hello", { parse: false });

271

// Returns original value if valid

272

273

// With parsing (default)

274

const parseResult = UppercaseString.inspect("hello", { parse: true });

275

// Returns transformed value if valid

276

277

// Complex validation with detailed errors

278

const User = Object({

279

profile: Object({

280

name: String,

281

age: Number

282

}),

283

settings: Object({

284

theme: Union(Literal("light"), Literal("dark")),

285

notifications: Boolean

286

})

287

});

288

289

function analyzeValidation(data: unknown): void {

290

const result = User.inspect(data);

291

292

if (result.success) {

293

console.log("✓ Validation successful");

294

return;

295

}

296

297

console.log("✗ Validation failed:");

298

console.log(`Main error: ${result.code} - ${result.message}`);

299

300

if (result.details) {

301

console.log("Detailed errors:");

302

analyzeDetails(result.details, "");

303

}

304

}

305

306

function analyzeDetails(details: Record<string, any>, path: string) {

307

for (const [key, detail] of Object.entries(details)) {

308

const currentPath = path ? `${path}.${key}` : key;

309

310

if (!detail.success) {

311

console.log(` ${currentPath}: ${detail.code} - ${detail.message}`);

312

313

if (detail.details) {

314

analyzeDetails(detail.details, currentPath);

315

}

316

}

317

}

318

}

319

320

// Usage

321

analyzeValidation({

322

profile: {

323

name: "Alice",

324

age: "not a number" // Error

325

},

326

settings: {

327

theme: "invalid", // Error

328

notifications: true

329

}

330

});

331

```

332

333

## Runtype Composition and Modification Methods

334

335

Beyond validation, every runtype provides methods for composition, constraints, and transformations.

336

337

### Composition Methods

338

339

Combine runtypes using logical operations.

340

341

```typescript { .api }

342

/**

343

* Composition methods for combining runtypes

344

*/

345

interface Runtype<T> {

346

/**

347

* Create union type (logical OR)

348

* @param other - Another runtype to union with

349

* @returns Union runtype accepting either type

350

*/

351

or<R extends Runtype.Core>(other: R): Union<[this, R]>;

352

353

/**

354

* Create intersection type (logical AND)

355

* @param other - Another runtype to intersect with

356

* @returns Intersect runtype requiring both types

357

*/

358

and<R extends Runtype.Core>(other: R): Intersect<[this, R]>;

359

}

360

```

361

362

**Usage Examples:**

363

364

```typescript

365

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

366

367

// Union using .or()

368

const StringOrNumber = String.or(Number);

369

StringOrNumber.check("hello"); // "hello"

370

StringOrNumber.check(42); // 42

371

StringOrNumber.check(true); // throws ValidationError

372

373

// Intersection using .and()

374

const PersonBase = Object({ name: String });

375

const PersonWithAge = Object({ age: Number });

376

const CompletePerson = PersonBase.and(PersonWithAge);

377

378

CompletePerson.check({ name: "Alice", age: 30 }); // ✓

379

CompletePerson.check({ name: "Bob" }); // ✗ missing age

380

```

381

382

### Optional and Nullable Methods

383

384

Shortcuts for common nullable/optional patterns.

385

386

```typescript { .api }

387

/**

388

* Methods for optional and nullable types

389

*/

390

interface Runtype<T> {

391

/** Make property optional (can be absent) */

392

optional(): Optional<this, never>;

393

394

/** Make property optional with default value */

395

default<V = never>(value: V): Optional<this, V>;

396

397

/** Allow null values */

398

nullable(): Union<[this, Literal<null>]>;

399

400

/** Allow undefined values */

401

undefinedable(): Union<[this, Literal<undefined>]>;

402

403

/** Allow null or undefined values */

404

nullishable(): Union<[this, Literal<null>, Literal<undefined>]>;

405

}

406

```

407

408

**Usage Examples:**

409

410

```typescript

411

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

412

413

// Optional properties in objects

414

const User = Object({

415

name: String,

416

email: String,

417

age: Number.optional(), // Can be absent

418

bio: String.default("No bio provided"), // Default value if absent

419

avatar: String.nullable(), // Can be null

420

lastSeen: String.undefinedable(), // Can be undefined

421

metadata: String.nullishable() // Can be null or undefined

422

});

423

424

type UserType = Static<typeof User>;

425

// {

426

// name: string;

427

// email: string;

428

// age?: number;

429

// bio: string;

430

// avatar: string | null;

431

// lastSeen: string | undefined;

432

// metadata: string | null | undefined;

433

// }

434

```

435

436

### Constraint Methods

437

438

Add custom validation logic while preserving types.

439

440

```typescript { .api }

441

/**

442

* Methods for adding constraints and custom validation

443

*/

444

interface Runtype<T> {

445

/**

446

* Add constraint with boolean or error message return

447

* @param constraint - Function returning boolean or error string

448

* @returns Constrained runtype

449

*/

450

withConstraint<Y extends T>(constraint: (x: T) => boolean | string): Constraint<this, Y>;

451

452

/**

453

* Add type guard constraint

454

* @param guard - TypeScript type guard function

455

* @returns Constrained runtype with narrowed type

456

*/

457

withGuard<Y extends T>(guard: (x: T) => x is Y): Constraint<this, Y>;

458

459

/**

460

* Add assertion constraint

461

* @param assert - TypeScript assertion function

462

* @returns Constrained runtype with narrowed type

463

*/

464

withAssertion<Y extends T>(assert: (x: T) => asserts x is Y): Constraint<this, Y>;

465

}

466

```

467

468

**Usage Examples:**

469

470

```typescript

471

import { String, Number } from "runtypes";

472

473

// Constraint with boolean

474

const PositiveNumber = Number.withConstraint(n => n > 0);

475

476

// Constraint with error message

477

const NonEmptyString = String.withConstraint(

478

s => s.length > 0 || "String cannot be empty"

479

);

480

481

// Email validation with regex

482

const Email = String.withConstraint(

483

s => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s) || "Invalid email format"

484

);

485

486

// Type guard constraint

487

const isPositive = (n: number): n is number => n > 0;

488

const PositiveWithGuard = Number.withGuard(isPositive);

489

490

// Assertion constraint

491

const assertIsEmail = (s: string): asserts s is string => {

492

if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s)) {

493

throw new Error("Invalid email");

494

}

495

};

496

const EmailWithAssertion = String.withAssertion(assertIsEmail);

497

```

498

499

### Branding and Parsing Methods

500

501

Add semantic meaning and transformations to types.

502

503

```typescript { .api }

504

/**

505

* Methods for branding and parsing/transformation

506

*/

507

interface Runtype<T, X = T> {

508

/**

509

* Add brand to create nominal typing

510

* @param brand - Brand identifier string

511

* @returns Branded runtype

512

*/

513

withBrand<B extends string>(brand: B): Brand<B, this>;

514

515

/**

516

* Add transformation/parsing logic

517

* @param parser - Function to transform validated value

518

* @returns Parser runtype with transformed output type

519

*/

520

withParser<Y>(parser: (value: X) => Y): Parser<this, Y>;

521

}

522

```

523

524

**Usage Examples:**

525

526

```typescript

527

import { String, Number, type Static } from "runtypes";

528

529

// Branded types for domain modeling

530

const UserId = String.withBrand("UserId");

531

const ProductId = String.withBrand("ProductId");

532

533

type UserIdType = Static<typeof UserId>; // string & Brand<"UserId">

534

type ProductIdType = Static<typeof ProductId>; // string & Brand<"ProductId">

535

536

// These are now different types despite both being strings

537

const userId = UserId.check("user-123");

538

const productId = ProductId.check("prod-456");

539

540

// Type error: can't assign UserId to ProductId variable

541

// const wrongId: ProductIdType = userId; // ✗

542

543

// Parsing transformations

544

const DateFromString = String.withParser(s => new Date(s));

545

const UppercaseString = String.withParser(s => s.toUpperCase());

546

const NumberFromString = String.withParser(s => parseInt(s, 10));

547

548

// Chaining parsers

549

const PositiveIntFromString = String

550

.withParser(s => parseInt(s, 10))

551

.withConstraint(n => n > 0 || "Must be positive");

552

553

const result = PositiveIntFromString.parse("42"); // number (42)

554

```

555

556

### Extension and Utility Methods

557

558

Add custom properties and utilities to runtypes.

559

560

```typescript { .api }

561

/**

562

* Utility methods for extending and introspecting runtypes

563

*/

564

interface Runtype<T, X = T> {

565

/**

566

* Add custom properties to the runtype

567

* @param extension - Object or function returning properties to add

568

* @returns Extended runtype with additional properties

569

*/

570

with<P extends object>(extension: P | ((self: this) => P)): this & P;

571

572

/**

573

* Create a shallow clone of the runtype

574

* @returns Cloned runtype

575

*/

576

clone(): this;

577

578

/**

579

* Ensure runtype conforms to a specific TypeScript type

580

* @returns Runtype with type conformance checking

581

*/

582

conform<V, Y = V>(this: Conform<V, Y>): Conform<V, Y> & this;

583

}

584

585

/**

586

* Static methods on the Runtype constructor

587

*/

588

interface RuntypeStatic {

589

/**

590

* Check if a value is a runtype

591

* @param x - Value to check

592

* @returns Type guard for runtype

593

*/

594

isRuntype(x: unknown): x is Runtype.Interfaces;

595

596

/**

597

* Assert that a value is a runtype

598

* @param x - Value to assert

599

* @throws If value is not a runtype

600

*/

601

assertIsRuntype(x: unknown): asserts x is Runtype.Interfaces;

602

}

603

```

604

605

**Usage Examples:**

606

607

```typescript

608

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

609

610

// Adding custom utilities to runtypes

611

const Username = String

612

.withConstraint(s => s.length >= 3 || "Username too short")

613

.with({

614

defaultValue: "anonymous",

615

validateUnique: async (username: string) => {

616

// Custom async validation logic

617

return !await userExists(username);

618

}

619

});

620

621

// Access custom properties

622

console.log(Username.defaultValue); // "anonymous"

623

await Username.validateUnique("newuser");

624

625

// Using function for extension (access to self)

626

const Temperature = Number.with(self => ({

627

celsius: (fahrenheit: number) => {

628

const celsius = (fahrenheit - 32) * 5/9;

629

return self.check(celsius);

630

},

631

fahrenheit: (celsius: number) => {

632

const fahrenheit = celsius * 9/5 + 32;

633

return self.check(fahrenheit);

634

}

635

}));

636

637

const temp = Temperature.celsius(100); // 37.77...

638

639

// Type conformance

640

interface User {

641

name: string;

642

age: number;

643

email?: string;

644

}

645

646

const UserRuntype = Object({

647

name: String,

648

age: Number,

649

email: String.optional()

650

}).conform<User>(); // Ensures exact match with User interface

651

652

// Static utility methods

653

const maybeRuntype: unknown = String;

654

655

if (Runtype.isRuntype(maybeRuntype)) {

656

// maybeRuntype is now typed as Runtype.Interfaces

657

const result = maybeRuntype.check("test");

658

}

659

660

// Assert for type narrowing

661

Runtype.assertIsRuntype(maybeRuntype); // Throws if not runtype

662

// maybeRuntype is now typed as Runtype.Interfaces

663

```

664

665

## Method Comparison and Use Cases

666

667

### When to Use Each Method

668

669

```typescript

670

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

671

672

const UserSchema = Object({

673

id: Number,

674

name: String,

675

email: String

676

});

677

678

// check() - Most common, when you want the validated value

679

function processUser(data: unknown) {

680

try {

681

const user = UserSchema.check(data);

682

return `User: ${user.name} (${user.email})`;

683

} catch (error) {

684

return "Invalid user data";

685

}

686

}

687

688

// guard() - Type narrowing in conditionals

689

function maybeProcessUser(data: unknown) {

690

if (UserSchema.guard(data)) {

691

// data is now typed, no exception handling needed

692

return `User: ${data.name} (${data.email})`;

693

}

694

return "Not a user";

695

}

696

697

// assert() - When you need type narrowing but not the return value

698

function validateAndProcess(data: unknown) {

699

UserSchema.assert(data); // Throws if invalid

700

// data is now typed as user object

701

702

// Continue processing with typed data

703

processUserEmail(data.email);

704

updateUserName(data.name);

705

}

706

707

// parse() - When transformations are involved

708

const UserWithTransform = UserSchema.withParser(user => ({

709

...user,

710

displayName: `${user.name} <${user.email}>`,

711

processedAt: new Date()

712

}));

713

714

function createDisplayUser(data: unknown) {

715

return UserWithTransform.parse(data); // Returns transformed object

716

}

717

718

// inspect() - When you need detailed error analysis

719

function validateWithReport(data: unknown) {

720

const result = UserSchema.inspect(data);

721

722

return {

723

isValid: result.success,

724

data: result.success ? result.value : null,

725

errors: result.success ? [] : collectErrors(result)

726

};

727

}

728

```

729

730

### Performance Considerations

731

732

```typescript

733

import { String, Object } from "runtypes";

734

735

const SimpleSchema = Object({ name: String, age: Number });

736

737

// guard() is fastest for boolean checks (no exception creation)

738

function fastCheck(data: unknown): boolean {

739

return SimpleSchema.guard(data);

740

}

741

742

// check() has exception overhead on failure

743

function checkWithTryCatch(data: unknown) {

744

try {

745

return SimpleSchema.check(data);

746

} catch {

747

return null;

748

}

749

}

750

751

// inspect() provides detailed results without exceptions

752

function detailedCheck(data: unknown) {

753

const result = SimpleSchema.inspect(data);

754

return result.success ? result.value : null;

755

}

756

757

// For hot paths where you expect mostly valid data, use check()

758

// For validation where failures are common, use guard() or inspect()

759

// For debugging and development, use inspect() for detailed feedback

760

```

761

762

### Error Handling Patterns

763

764

```typescript

765

import { ValidationError } from "runtypes";

766

767

// Pattern 1: Try-catch with check

768

function safeProcess(data: unknown) {

769

try {

770

const validated = UserSchema.check(data);

771

return { success: true, data: validated };

772

} catch (error) {

773

if (ValidationError.isValidationError(error)) {

774

return { success: false, error: error.message };

775

}

776

throw error; // Re-throw non-validation errors

777

}

778

}

779

780

// Pattern 2: Guard with fallback

781

function guardProcess(data: unknown) {

782

if (UserSchema.guard(data)) {

783

return processValidUser(data);

784

}

785

return getDefaultUser();

786

}

787

788

// Pattern 3: Inspect with detailed handling

789

function inspectProcess(data: unknown) {

790

const result = UserSchema.inspect(data);

791

792

if (result.success) {

793

return result.value;

794

}

795

796

// Handle specific error types

797

switch (result.code) {

798

case "TYPE_INCORRECT":

799

return handleTypeError(result);

800

case "PROPERTY_MISSING":

801

return handleMissingProperty(result);

802

case "CONTENT_INCORRECT":

803

return handleContentError(result);

804

default:

805

return handleGenericError(result);

806

}

807

}

808

```