| 1 | # ts-interface-checker
|
|---|
| 2 |
|
|---|
| 3 | [](https://travis-ci.org/gristlabs/ts-interface-checker)
|
|---|
| 4 | [](https://badge.fury.io/js/ts-interface-checker)
|
|---|
| 5 |
|
|---|
| 6 |
|
|---|
| 7 | > Runtime library to validate data against TypeScript interfaces.
|
|---|
| 8 |
|
|---|
| 9 | This package is the runtime support for validators created by
|
|---|
| 10 | [ts-interface-builder](https://github.com/gristlabs/ts-interface-builder).
|
|---|
| 11 | It allows validating data, such as parsed JSON objects received
|
|---|
| 12 | over the network, or parsed JSON or YAML files, to check if they satisfy a
|
|---|
| 13 | TypeScript interface, and to produce informative error messages if they do not.
|
|---|
| 14 |
|
|---|
| 15 | ## Installation
|
|---|
| 16 |
|
|---|
| 17 | ```bash
|
|---|
| 18 | npm install --save-dev ts-interface-builder
|
|---|
| 19 | npm install --save ts-interface-checker
|
|---|
| 20 | ```
|
|---|
| 21 |
|
|---|
| 22 | ## Usage
|
|---|
| 23 |
|
|---|
| 24 | Suppose you have a TypeScript file defining an interface:
|
|---|
| 25 | ```typescript
|
|---|
| 26 | // foo.ts
|
|---|
| 27 | interface Square {
|
|---|
| 28 | size: number;
|
|---|
| 29 | color?: string;
|
|---|
| 30 | }
|
|---|
| 31 | ```
|
|---|
| 32 |
|
|---|
| 33 | The first step is to generate some code for runtime checks:
|
|---|
| 34 | ```bash
|
|---|
| 35 | `npm bin`/ts-interface-builder foo.ts
|
|---|
| 36 | ```
|
|---|
| 37 |
|
|---|
| 38 | It produces a file like this:
|
|---|
| 39 | ```typescript
|
|---|
| 40 | // foo-ti.js
|
|---|
| 41 | import * as t from "ts-interface-checker";
|
|---|
| 42 |
|
|---|
| 43 | export const Square = t.iface([], {
|
|---|
| 44 | "size": "number",
|
|---|
| 45 | "color": t.opt("string"),
|
|---|
| 46 | });
|
|---|
| 47 | ...
|
|---|
| 48 | ```
|
|---|
| 49 |
|
|---|
| 50 | Now at runtime, to check if a value satisfies the Square interface:
|
|---|
| 51 | ```typescript
|
|---|
| 52 | import fooTI from "./foo-ti";
|
|---|
| 53 | import {createCheckers} from "ts-interface-checker";
|
|---|
| 54 |
|
|---|
| 55 | const {Square} = createCheckers(fooTI);
|
|---|
| 56 |
|
|---|
| 57 | Square.check({size: 1}); // OK
|
|---|
| 58 | Square.check({size: 1, color: "green"}); // OK
|
|---|
| 59 | Square.check({color: "green"}); // Fails with "value.size is missing"
|
|---|
| 60 | Square.check({size: 4, color: 5}); // Fails with "value.color is not a string"
|
|---|
| 61 | ```
|
|---|
| 62 |
|
|---|
| 63 | Note that `ts-interface-builder` is only needed for the build-time step, and
|
|---|
| 64 | `ts-interface-checker` is needed at runtime. That's why the recommendation is to npm-install the
|
|---|
| 65 | former using `--save-dev` flag and the latter using `--save`.
|
|---|
| 66 |
|
|---|
| 67 | ## Checking method calls
|
|---|
| 68 |
|
|---|
| 69 | If you have an interface with methods, you can validate method call arguments and return values:
|
|---|
| 70 | ```typescript
|
|---|
| 71 | // greet.ts
|
|---|
| 72 | interface Greeter {
|
|---|
| 73 | greet(name: string): string;
|
|---|
| 74 | }
|
|---|
| 75 | ```
|
|---|
| 76 |
|
|---|
| 77 | After generating the runtime code, you can now check calls like:
|
|---|
| 78 | ```typescript
|
|---|
| 79 | import greetTI from "./greet-ti";
|
|---|
| 80 | import {createCheckers} from "ts-interface-checker";
|
|---|
| 81 |
|
|---|
| 82 | const {Greeter} = createCheckers(greetTI);
|
|---|
| 83 |
|
|---|
| 84 | Greeter.methodArgs("greet").check(["Bob"]); // OK
|
|---|
| 85 | Greeter.methodArgs("greet").check([17]); // Fails with "value.name is not a string"
|
|---|
| 86 | Greeter.methodArgs("greet").check([]); // Fails with "value.name is missing"
|
|---|
| 87 |
|
|---|
| 88 | Greeter.methodResult("greet").check("hello"); // OK
|
|---|
| 89 | Greeter.methodResult("greet").check(null); // Fails with "value is not a string"
|
|---|
| 90 | ```
|
|---|
| 91 |
|
|---|
| 92 | ## Type suites
|
|---|
| 93 |
|
|---|
| 94 | If one type refers to a type defined in another file, you need to tell the interface checker about
|
|---|
| 95 | all type names when you call `createCheckers()`. E.g. given
|
|---|
| 96 |
|
|---|
| 97 | ```typescript
|
|---|
| 98 | // color.ts
|
|---|
| 99 | export type Color = RGB | string;
|
|---|
| 100 | export type RGB = [number, number, number];
|
|---|
| 101 | ```
|
|---|
| 102 |
|
|---|
| 103 | ```typescript
|
|---|
| 104 | // shape.ts
|
|---|
| 105 | import {Color} from "./color";
|
|---|
| 106 | export interface Square {
|
|---|
| 107 | size: number;
|
|---|
| 108 | color?: Color;
|
|---|
| 109 | }
|
|---|
| 110 | ```
|
|---|
| 111 |
|
|---|
| 112 | the produced files `color-ti.ts` and `shape-ti.ts` do not automatically refer to each other, but
|
|---|
| 113 | expect you to relate them in `createCheckers()` call:
|
|---|
| 114 | ```typescript
|
|---|
| 115 | import color from "./color-ti";
|
|---|
| 116 | import shape from "./shape-ti";
|
|---|
| 117 | import {createCheckers} from "ts-interface-checker";
|
|---|
| 118 |
|
|---|
| 119 | const {Square} = createCheckers(shape, color); // Pass in all required type suites.
|
|---|
| 120 |
|
|---|
| 121 | Square.check({size: 1, color: [255,255,255]});
|
|---|
| 122 | ```
|
|---|
| 123 |
|
|---|
| 124 | ## Strict checking
|
|---|
| 125 |
|
|---|
| 126 | You may check that data contains no extra properties. Note that it is not generally recommended as
|
|---|
| 127 | it this prevents backward compatibility: if you add new properties to an interface, then older
|
|---|
| 128 | code with strict checks will not accept them.
|
|---|
| 129 |
|
|---|
| 130 | Following on the example above:
|
|---|
| 131 | ```typescript
|
|---|
| 132 | Square.strictCheck({size: 1, color: [255,255,255], bg: "blue"}); // Fails with value.bg is extraneous
|
|---|
| 133 | Square.strictCheck({size: 1, color: [255,255,255,0.5]}); // Fails with ...value.color[3] is extraneous
|
|---|
| 134 | ```
|
|---|
| 135 |
|
|---|
| 136 | ## Type guards
|
|---|
| 137 |
|
|---|
| 138 | Standard `Checker` objects do the type checking logic, but are unable to make the TypeScript
|
|---|
| 139 | compiler aware that an object of `unknown` type implements a certain interface.
|
|---|
| 140 |
|
|---|
| 141 | Basic code:
|
|---|
| 142 | ```typescript
|
|---|
| 143 | const unk: unknown = {size: 1, color: "green"};
|
|---|
| 144 | // Type is unknown, so TypeScript will not let you access the members.
|
|---|
| 145 | console.log(unk.size); // Error: "Object is of type 'unknown'"
|
|---|
| 146 | ```
|
|---|
| 147 |
|
|---|
| 148 | With a `Checker` available:
|
|---|
| 149 | ```typescript
|
|---|
| 150 | import fooTI from "./foo-ti";
|
|---|
| 151 | import {createCheckers} from "ts-interface-checker";
|
|---|
| 152 |
|
|---|
| 153 | const {Square} = createCheckers(fooTI);
|
|---|
| 154 |
|
|---|
| 155 | const unk: unknown = {size: 1, color: "green"};
|
|---|
| 156 |
|
|---|
| 157 | if (Square.test(unk)) {
|
|---|
| 158 | // unk does implement Square, but TypeScript is not aware of it.
|
|---|
| 159 | console.log(unk.size); // Error: "Object is of type 'unknown'"
|
|---|
| 160 | }
|
|---|
| 161 | ```
|
|---|
| 162 |
|
|---|
| 163 | To enable type guard functionality on the existing `test`, and `strictTest` functions, `Checker`
|
|---|
| 164 | objects should be cast to `CheckerT<>` using the appropriate type.
|
|---|
| 165 |
|
|---|
| 166 | Using `CheckerT<>`:
|
|---|
| 167 | ```typescript
|
|---|
| 168 | import {Square} from "./foo";
|
|---|
| 169 | import fooTI from "./foo-ti";
|
|---|
| 170 | import {createCheckers, CheckerT} from "ts-interface-checker";
|
|---|
| 171 |
|
|---|
| 172 | const {Square} = createCheckers(fooTI) as {Square: CheckerT<Square>};
|
|---|
| 173 |
|
|---|
| 174 | const unk: unknown = {size: 1, color: "green"};
|
|---|
| 175 |
|
|---|
| 176 | if (Square.test(unk)) {
|
|---|
| 177 | // TypeScript is now aware that unk implements Square, and allows member access.
|
|---|
| 178 | console.log(unk.size);
|
|---|
| 179 | }
|
|---|
| 180 | ```
|
|---|
| 181 |
|
|---|
| 182 | ## Type assertions
|
|---|
| 183 |
|
|---|
| 184 | `CheckerT<>` will eventually support type assertions using the `check` and `strictCheck` functions,
|
|---|
| 185 | however, this feature is not yet fully working in TypeScript.
|
|---|