Skip to content

Latest commit

 

History

History
868 lines (776 loc) · 29.3 KB

assert.mdx

File metadata and controls

868 lines (776 loc) · 29.3 KB

import { Tabs, Tab } from 'nextra-theme-docs' import Alert from '@mui/material/Alert'; import AlertTitle from '@mui/material/AlertTitle';

assert() function

<Tabs items={[ typia, TypeGuardError.ts, ]}>

export function assert<T>(input: T): T;
export function assert<T>(input: unknown): T;
```typescript export class TypeGuardError extends Error { public readonly method: string; public readonly path: string | undefined; public readonly expected: string; public readonly value: any; } ```

Asserts a value type.

typia.assert<T>() function throws a TypeGuardError when wrong type comes.

The TypeGuardError instance has only the first type error info, with access path and expected type. In the below example case, as the age property is wrong with its definition (@exclusiveMinimum), such TypeGuardError would be thrown:

  • method: typia.assert()
  • path: input.age
  • value: 18,
  • expected: number & ExclusiveMinimum<19>

**AOT compliation**

If you'd used other competitive validator libraries like ajv or class-validator, you may found that typia does not require any extra schema definition. If you have not experienced them, I can sure that you may get shocked after reading below extra schema definition files.

Yeah, typia needs only pure TypeScript type. As typia is a compiler library, it can analyze TypeScript type by itself, and possible to write the optimal validation code like below. This is the key principle of typia, which needs only one line with pure TypeScript type.

<Tabs items={['TypeScript Source Code', 'Compiled JavaScript File']}>

import typia, { tags } from "typia";
import { v4 } from "uuid";

typia.assert<IMember>({
  id: v4(),
  email: "samchon.github@gmail.com",
  age: 18, // wrong, must be greater than 19
});

interface IMember {
  id: string & tags.Format<"uuid">;
  email: string & tags.Format<"email">;
  age: number &
    tags.Type<"uint32"> &
    tags.ExclusiveMinimum<19> &
    tags.Maximum<100>;
}
```javascript filename="examples/bin/assert.js" showLineNumbers "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const typia_1 = __importDefault(require("typia")); const uuid_1 = require("uuid"); ((input) => { const __is = (input) => { return ( "object" === typeof input && null !== input && "string" === typeof input.id && /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test( input.id, ) && "string" === typeof input.email && /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test( input.email, ) && "number" === typeof input.age && Math.floor(input.age) === input.age && 0 <= input.age && input.age <= 4294967295 && 19 < input.age && input.age <= 100 ); }; if (false === __is(input)) ((input, _path, _exceptionable = true) => { const $guard = typia_1.default.assert.guard; const $ao0 = (input, _path, _exceptionable = true) => (("string" === typeof input.id && (/^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test( input.id, ) || $guard(_exceptionable, { path: _path + ".id", expected: 'string & Format<"uuid">', value: input.id, }))) || $guard(_exceptionable, { path: _path + ".id", expected: '(string & Format<"uuid">)', value: input.id, })) && (("string" === typeof input.email && (/^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test( input.email, ) || $guard(_exceptionable, { path: _path + ".email", expected: 'string & Format<"email">', value: input.email, }))) || $guard(_exceptionable, { path: _path + ".email", expected: '(string & Format<"email">)', value: input.email, })) && (("number" === typeof input.age && ((Math.floor(input.age) === input.age && 0 <= input.age && input.age <= 4294967295) || $guard(_exceptionable, { path: _path + ".age", expected: 'number & Type<"uint32">', value: input.age, })) && (19 < input.age || $guard(_exceptionable, { path: _path + ".age", expected: "number & ExclusiveMinimum<19>", value: input.age, })) && (input.age <= 100 || $guard(_exceptionable, { path: _path + ".age", expected: "number & Maximum<100>", value: input.age, }))) || $guard(_exceptionable, { path: _path + ".age", expected: '(number & Type<"uint32"> & ExclusiveMinimum<19> & Maximum<100>)', value: input.age, })); return ( ((("object" === typeof input && null !== input) || $guard(true, { path: _path + "", expected: "IMember", value: input, })) && $ao0(input, _path + "", true)) || $guard(true, { path: _path + "", expected: "IMember", value: input, }) ); })(input, "$input", true); return input; })({ id: (0, uuid_1.v4)(), email: "samchon.github@gmail.com", age: 18, // wrong, must be greater than 19 }); ```

assertEquals() function

<Tabs items={[ typia, TypeGuardError.ts, ]}>

export function assertEquals<T>(input: T): T;
export function assertEquals<T>(input: unknown): T;
```typescript export class TypeGuardError extends Error { public readonly method: string; public readonly path: string | undefined; public readonly expected: string; public readonly value: any; } ```

More strict assert function prohibiting superfluous properties.

typia.assert<T>() function inspects input value type and throws TypeGuardError when mismatched, however, it can't detect superfluous properties. If you want to prohibit those superfluous properties, therefore throws an TypeGuardError when superfluous property exists, use typia.assertEquals<T()> function instead.

In the below example case, as sex property is not defined in the IMember type, such TypeGuardError would be thrown:

  • method: typia.assertEquals()
  • path: input.sex
  • value: 1, expected: undefined

<Tabs items={['TypeScript Source Code', 'Compiled JavaScript File']}>

import typia, { tags } from "typia";
import { v4 } from "uuid";

typia.assert<IMember>({
  id: v4(),
  email: "samchon.github@gmail.com",
  age: 30,
  sex: 1, // extra
});

interface IMember {
  id: string & tags.Format<"uuid">;
  email: string & tags.Format<"email">;
  age: number &
    tags.Type<"uint32"> &
    tags.ExclusiveMinimum<19> &
    tags.Maximum<100>;
}
```javascript filename="examples/bin/assertEquals.js" showLineNumbers "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const typia_1 = __importDefault(require("typia")); const uuid_1 = require("uuid"); ((input) => { const __is = (input) => { return ( "object" === typeof input && null !== input && "string" === typeof input.id && /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test( input.id, ) && "string" === typeof input.email && /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test( input.email, ) && "number" === typeof input.age && Math.floor(input.age) === input.age && 0 <= input.age && input.age <= 4294967295 && 19 < input.age && input.age <= 100 ); }; if (false === __is(input)) ((input, _path, _exceptionable = true) => { const $guard = typia_1.default.assert.guard; const $ao0 = (input, _path, _exceptionable = true) => (("string" === typeof input.id && (/^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test( input.id, ) || $guard(_exceptionable, { path: _path + ".id", expected: 'string & Format<"uuid">', value: input.id, }))) || $guard(_exceptionable, { path: _path + ".id", expected: '(string & Format<"uuid">)', value: input.id, })) && (("string" === typeof input.email && (/^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test( input.email, ) || $guard(_exceptionable, { path: _path + ".email", expected: 'string & Format<"email">', value: input.email, }))) || $guard(_exceptionable, { path: _path + ".email", expected: '(string & Format<"email">)', value: input.email, })) && (("number" === typeof input.age && ((Math.floor(input.age) === input.age && 0 <= input.age && input.age <= 4294967295) || $guard(_exceptionable, { path: _path + ".age", expected: 'number & Type<"uint32">', value: input.age, })) && (19 < input.age || $guard(_exceptionable, { path: _path + ".age", expected: "number & ExclusiveMinimum<19>", value: input.age, })) && (input.age <= 100 || $guard(_exceptionable, { path: _path + ".age", expected: "number & Maximum<100>", value: input.age, }))) || $guard(_exceptionable, { path: _path + ".age", expected: '(number & Type<"uint32"> & ExclusiveMinimum<19> & Maximum<100>)', value: input.age, })); return ( ((("object" === typeof input && null !== input) || $guard(true, { path: _path + "", expected: "IMember", value: input, })) && $ao0(input, _path + "", true)) || $guard(true, { path: _path + "", expected: "IMember", value: input, }) ); })(input, "$input", true); return input; })({ id: (0, uuid_1.v4)(), email: "samchon.github@gmail.com", age: 30, sex: 1, // extra }) ```

assertGuard() functions

<Tabs items={[ typia, TypeGuardError.ts, ]}>

export function assertGurad<T>(input: T): asserts inut is T;
export function assertGuard<T>(input: unknown): asserts input is T;

export function assertGuardEquals<T>(input: T): asserts inut is T;
export function assertGuardEquals<T>(input: unknown): asserts input is T;
```typescript export class TypeGuardError extends Error { public readonly method: string; public readonly path: string | undefined; public readonly expected: string; public readonly value: any; } ```

Assertion guard of a value type.

typia.assertGuard<T>() is similar with typia.assert<T>() throwing a TypeGuardError when wrong type.

However, typia.assert<T>() returns the paramteric input value itself when there's no type problem on the parametric input value, whereas the typia.assertGuard<T>() function returns nothing. Instead, the parametric input value would be automatically cased to the type T. This is the concept of "Assertion Guard" of a value type.

Such similarities and differences of typia.assertGuard<T>() and typia.assert<T>() functions are the same in the case of typia.assertGuardEquals<T>() and typia.assertEquals<T>() functions. If there's no type problem on the typia.assertGuardEquals<T>() function, it also performs the "Assertion Guard".

Look at the below code, then you may understand what the "Assertion Guard" means.

import typia from "typia";

interface IPoint {
  x: number;
  y: number;
}
const input: unknown = { x: 1, y: 2 };

// PERFORM THE ASSERTION GUARD
typia.assertGuard<IPoint>(input);

// FROM NOW ON, "input" IS THE "IPoint" TYPE
input.x; // OK
input.y; // OK

Reusable functions

<Tabs items={[ typia, TypeGuardError.ts, AssertionGuard.ts, ]}>

export function createAssert<T>(): (input: unknown) => T;
export function createAssertEquals<T>(): (input: unknown) => T;

export function createAssertGuard<T>(): AssertionGuard<T>;
export function createAssertGuardEquals<T>(): AssertionGuard<T>;
```typescript export class TypeGuardError extends Error { public readonly method: string; public readonly path: string | undefined; public readonly expected: string; public readonly value: any; } ``` ```typescript export type AssertionGuard = (input: unknown) => asserts input is T; ```

Reusable typia.assert<T>() function generators.

If you repeat to call typia.assert<T>() function on the same type, size of JavaScript files would be larger because of duplicated AOT compilation. To prevent it, you can generate reusable function through typia.createAssert<T>() function.

Just look at the code below, then you may understand how to use it.

<Tabs items={['TypeScript Source Code', 'Compiled JavaScript File']}>

import typia, { tags } from "typia";

export const assertMember = typia.createAssert<IMember>();

interface IMember {
  id: string & tags.Format<"uuid">;
  email: string & tags.Format<"email">;
  age: number &
    tags.Type<"uint32"> &
    tags.ExclusiveMinimum<19> &
    tags.Maximum<100>;
}
```javascript filename="examples/bin/createAssert.js" showLineNumbers "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.assertMember = void 0; const typia_1 = __importDefault(require("typia")); const assertMember = (input) => { const __is = (input) => { return ( "object" === typeof input && null !== input && "string" === typeof input.id && /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test( input.id, ) && "string" === typeof input.email && /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test( input.email, ) && "number" === typeof input.age && Math.floor(input.age) === input.age && 0 <= input.age && input.age <= 4294967295 && 19 < input.age && input.age <= 100 ); }; if (false === __is(input)) ((input, _path, _exceptionable = true) => { const $guard = typia_1.default.createAssert.guard; const $ao0 = (input, _path, _exceptionable = true) => (("string" === typeof input.id && (/^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test( input.id, ) || $guard(_exceptionable, { path: _path + ".id", expected: 'string & Format<"uuid">', value: input.id, }))) || $guard(_exceptionable, { path: _path + ".id", expected: '(string & Format<"uuid">)', value: input.id, })) && (("string" === typeof input.email && (/^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test( input.email, ) || $guard(_exceptionable, { path: _path + ".email", expected: 'string & Format<"email">', value: input.email, }))) || $guard(_exceptionable, { path: _path + ".email", expected: '(string & Format<"email">)', value: input.email, })) && (("number" === typeof input.age && ((Math.floor(input.age) === input.age && 0 <= input.age && input.age <= 4294967295) || $guard(_exceptionable, { path: _path + ".age", expected: 'number & Type<"uint32">', value: input.age, })) && (19 < input.age || $guard(_exceptionable, { path: _path + ".age", expected: "number & ExclusiveMinimum<19>", value: input.age, })) && (input.age <= 100 || $guard(_exceptionable, { path: _path + ".age", expected: "number & Maximum<100>", value: input.age, }))) || $guard(_exceptionable, { path: _path + ".age", expected: '(number & Type<"uint32"> & ExclusiveMinimum<19> & Maximum<100>)', value: input.age, })); return ( ((("object" === typeof input && null !== input) || $guard(true, { path: _path + "", expected: "IMember", value: input, })) && $ao0(input, _path + "", true)) || $guard(true, { path: _path + "", expected: "IMember", value: input, }) ); })(input, "$input", true); return input; }; exports.assertMember = assertMember; ``` **Explicity of Assertion Guard**

Be careful when using typia.createAssertGuard<T>() or typia.createAssertGuardEquals<T>() functions.

When calling those functions, you've to declare the variable type explicit on the caller variable. If you don't do it, so that the caller variables come the implicit function type, TypeScript compiler throws an error like below. This is a special limitation of TypeScript compiler about the "Assertion Guard".

import typia, { AssertionGuard } from "typia";

//MUST DECLARE THE VARIABLE TYPE
const explicit: AssertionGuard<number> = typia.createAssertGuard<number>();

// IF NOT, COMPILATION ERROR BE OCCURED
const implicit = typia.createAssertGuard<number>();
Assertions require every name in the call target to be declared with an explicit type annotation.

Restrictions

typia.assert<T>() function does not check function and user-defined class types.

It validates only the primitive properties. Therefore, typia.assert<T>() function does not perform the instanceof ClassName for user-defined classes. If you want to validate the user-defined class type in addition to the property types, do it by yourself. Also, typia.assert<T>() function does not validate the function type either, unless configuring functional property of plugin option in the tsconfig.json file.

{
  "compilerOptions": {
    "plugins": [
      {
        "transform": "typia/lib/transform",
        "functional": true
      }
    ]
  }
}

By the way, there're some exception cases.

If JS native class type like Date, Uint8Array, or Map<Key, T> being utilized, typia.assert<T>() function validates them. Especially about the Set<T>, and Map<Key, T> class cases, typia.assert<T>() function validates all of their contained element types, too.

Therefore, the instanceof statement does not be used only for the user-defined classes.

<Tabs items={['TypeScript Source Code', 'Compiled JavaScript File']}>

import typia from "typia";

typia.createIs<Map<string, boolean | number | string>>();
```javascript filename="examples/bin/is-map.js" showLineNumbers "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const typia_1 = __importDefault(require("typia")); (input) => { return ( input instanceof Map && (() => [...input].every( (elem) => Array.isArray(elem) && elem.length === 2 && "string" === typeof elem[0] && ("string" === typeof elem[1] || "number" === typeof elem[1] || "boolean" === typeof elem[1]), ))() ); }; ```

Customization

You can enhance validation logic by special tags.

Also, with those tags, you can add your custom validation logic, too.

If you want to know about such special tags detaily, read below article:

<Tabs items={['TypeScript Source Code', 'Compiled JavaScript File']}>

import typia, { tags } from "typia";

export const assertSomething = typia.createAssert<Something>();

//----
// DEFINE CUSTOM TYPE TAGS
//----
type Dollar = tags.TagBase<{
  kind: "dollar";
  target: "string";
  value: undefined;
  validate: `$input[0] === "$" && !isNaN(Number($input.substring(1).split(",").join("")))`;
}>;

type Postfix<Value extends string> = tags.TagBase<{
  kind: "postfix";
  target: "string";
  value: Value;
  validate: `$input.endsWith("${Value}")`;
}>;

type IsEven<Value extends number | bigint> = tags.TagBase<{
  kind: "isEven";
  target: Value extends number ? "number" : "bigint";
  value: undefined;
  validate: `$input % ${Numeric<2>} === ${Numeric<0>}`;
}>;

type Numeric<Value extends number | bigint> = Value extends number
  ? Value
  : `BigInt(${Value})`;

//----
// VALIDATION
//----
interface Something {
  dollar: string & Dollar;
  postfix: string & Postfix<"!!!">;
  isEven: number & IsEven<number>;
}
```javascript filename="examples/bin/assert-custom-tags.js" showLineNumbers "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.assertSomething = void 0; const typia_1 = __importDefault(require("typia")); const assertSomething = (input) => { const __is = (input) => { return ( "object" === typeof input && null !== input && "string" === typeof input.dollar && input.dollar[0] === "$" && !isNaN(Number(input.dollar.substring(1).split(",").join(""))) && "string" === typeof input.postfix && input.postfix.endsWith("!!!") && "number" === typeof input.isEven && input.isEven % 2 === 0 ); }; if (false === __is(input)) ((input, _path, _exceptionable = true) => { const $guard = typia_1.default.createAssert.guard; const $ao0 = (input, _path, _exceptionable = true) => (("string" === typeof input.dollar && ((input.dollar[0] === "$" && !isNaN(Number(input.dollar.substring(1).split(",").join("")))) || $guard(_exceptionable, { path: _path + ".dollar", expected: "string & Dollar", value: input.dollar, }))) || $guard(_exceptionable, { path: _path + ".dollar", expected: "(string & Dollar)", value: input.dollar, })) && (("string" === typeof input.postfix && (input.postfix.endsWith("!!!") || $guard(_exceptionable, { path: _path + ".postfix", expected: 'string & Postfix<"!!!">', value: input.postfix, }))) || $guard(_exceptionable, { path: _path + ".postfix", expected: '(string & Postfix<"!!!">)', value: input.postfix, })) && (("number" === typeof input.isEven && (input.isEven % 2 === 0 || $guard(_exceptionable, { path: _path + ".isEven", expected: "number & IsEven", value: input.isEven, }))) || $guard(_exceptionable, { path: _path + ".isEven", expected: "(number & IsEven)", value: input.isEven, })); return ( ((("object" === typeof input && null !== input) || $guard(true, { path: _path + "", expected: "Something", value: input, })) && $ao0(input, _path + "", true)) || $guard(true, { path: _path + "", expected: "Something", value: input, }) ); })(input, "$input", true); return input; }; exports.assertSomething = assertSomething; ```

Performance

Super-fast and super-safe.

Comparing typia.assert<T>() function with other competitive libraries, maximum 20,000x faster.

Furthermore, only typia can validate complicate union types.

Assert Function Benchmark

Measured on Intel i5-1135g7, Surface Pro 8

Components typia TypeBox ajv io-ts zod C.V.
Easy to use
Object (simple)
Object (hierarchical)
Object (recursive)
Object (union, implicit)
Object (union, explicit)
Object (additional tags)
Object (template literal types)
Object (dynamic properties)
Array (rest tuple)
Array (hierarchical)
Array (recursive)
Array (recursive, union)
Array (R+U, implicit)
Array (repeated)
Array (repeated, union)
Ultimate Union Type

C.V. means class-validator