├── .github └── workflows │ └── main.yml ├── .gitignore ├── .npmignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── assets ├── header-large.jpg ├── header-round-large.png ├── header-round-medium.png └── logo.ai ├── babel.config.js ├── package.json ├── src ├── definitions │ ├── index.ts │ └── jsonSchema6.ts ├── index.ts ├── meta-types │ ├── any.ts │ ├── array.ts │ ├── const.ts │ ├── enum.ts │ ├── error.ts │ ├── index.ts │ ├── intersection │ │ ├── array.ts │ │ ├── const.ts │ │ ├── enum.ts │ │ ├── index.ts │ │ ├── object.ts │ │ ├── primitive.ts │ │ ├── tuple.ts │ │ └── union.ts │ ├── never.ts │ ├── object.ts │ ├── primitive.ts │ ├── tuple.ts │ └── union.ts ├── parse-schema │ ├── allOf.ts │ ├── anyOf.ts │ ├── array.ts │ ├── const.ts │ ├── enum.ts │ ├── index.ts │ ├── mixed.ts │ ├── object.ts │ ├── oneOf.ts │ └── utils.ts └── utils │ ├── and.ts │ ├── concat.ts │ ├── extends.ts │ ├── filter.ts │ ├── get.ts │ ├── hasKeyIn.ts │ ├── head.ts │ ├── index.ts │ ├── merge.ts │ ├── optionalProps.ts │ ├── prepend.ts │ ├── readonly.ts │ ├── replace.ts │ ├── requiredProps.ts │ ├── reverse.ts │ ├── tail.ts │ └── writeable.ts ├── tests ├── e2e │ ├── allOf.test.ts │ ├── anyOf.test.ts │ ├── array.test.ts │ ├── boolean.test.ts │ ├── const.test.ts │ ├── enum.test.ts │ ├── integer.test.ts │ ├── mixed.test.ts │ ├── noSchema.test.ts │ ├── null.test.ts │ ├── number.test.ts │ ├── object.test.ts │ ├── oneOf.test.ts │ └── string.test.ts ├── meta-types │ ├── any.ts │ ├── array.ts │ ├── const.ts │ ├── enum.ts │ ├── error.ts │ ├── intersection │ │ ├── array.ts │ │ ├── const.ts │ │ ├── enum.ts │ │ ├── helpers.ts │ │ ├── index.ts │ │ ├── object.ts │ │ ├── primitive.ts │ │ ├── tuple.ts │ │ └── union.ts │ ├── never.ts │ ├── object.ts │ ├── tuple.ts │ └── union.ts └── utils │ └── concat.ts ├── tsconfig.json └── yarn.lock /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | on: 6 | push: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | test: 11 | # The type of runner that the job will run on 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up 16 | run: yarn 17 | - name: Tests 18 | run: yarn test 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | .vscode 3 | assets 4 | node_modules 5 | src 6 | babel.config.js 7 | tsconfig.json 8 | yarn.lock -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Thomas Aribart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | If you use this repo, star it ✨ 5 |

6 | 7 | # Stop typing twice 🙅‍♂️ 8 | 9 | A lot of projects use JSON schemas for runtime data validation along with TypeScript for static type checking. 10 | 11 | Their code may look like this: 12 | 13 | ```typescript 14 | const dogSchema = { 15 | type: "object", 16 | properties: { 17 | name: { type: "string" }, 18 | age: { type: "integer" }, 19 | hobbies: { type: "array", items: { type: "string" } }, 20 | favoriteFood: { enum: ["pizza", "taco", "fries"] }, 21 | }, 22 | required: ["name", "age"], 23 | }; 24 | 25 | type Dog = { 26 | name: string; 27 | age: number; 28 | hobbies?: string[]; 29 | favoriteFood?: "pizza" | "taco" | "fries"; 30 | }; 31 | ``` 32 | 33 | Both objects carry similar if not exactly the same information. This is a code duplication that can annoy developers and introduce bugs if not properly maintained. 34 | 35 | That's when `json-schema-to-ts` comes to the rescue 💪 36 | 37 | ## FromSchema 38 | 39 | The `FromSchema` method lets you infer TS types directly from JSON schemas: 40 | 41 | ```typescript 42 | import { FromSchema } from "json-schema-to-ts"; 43 | 44 | const dogSchema = { 45 | type: "object", 46 | properties: { 47 | name: { type: "string" }, 48 | age: { type: "integer" }, 49 | hobbies: { type: "array", items: { type: "string" } }, 50 | favoriteFood: { enum: ["pizza", "taco", "fries"] }, 51 | }, 52 | required: ["name", "age"], 53 | } as const; 54 | 55 | type Dog = FromSchema; 56 | // => Will infer the same type as above 57 | ``` 58 | 59 | Schemas can even be nested, as long as you don't forget the `as const` statement: 60 | 61 | ```typescript 62 | const catSchema = { ... } as const; 63 | 64 | const petSchema = { 65 | anyOf: [dogSchema, catSchema], 66 | } as const; 67 | 68 | type Pet = FromSchema; 69 | // => Will work 🙌 70 | ``` 71 | 72 | > The `as const` statement is used so that TypeScript takes the schema definition to the word (e.g. _true_ is interpreted as the _true_ constant and not widened as _boolean_). It is pure TypeScript and has zero impact on the compiled code. 73 | 74 | ## Why use `json-schema-to-ts`? 75 | 76 | If you're looking for runtime validation with added types, libraries like [yup](https://github.com/jquense/yup), [zod](https://github.com/vriad/zod) or [runtypes](https://github.com/pelotom/runtypes) may suit your needs while being easier to use! 77 | 78 | On the other hand, JSON schemas have the benefit of being widely used, more versatile and reusable (swaggers, APIaaS...). 79 | 80 | If you prefer to stick to them and can define your schemas in TS instead of JSON (importing JSONs `as const` is not available yet), then `json-schema-to-ts` is made for you: 81 | 82 | - ✅ **Schema validation** `FromSchema` raises TS errors on invalid schemas, based on [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/json-schema)'s definitions 83 | - ✨ **No impact on compiled code**: `json-schema-to-ts` only operates in type space. And after all, what's lighter than a dev-dependency? 84 | - 🍸 **DRYness**: Less code means less embarrassing typos 85 | - 🤝 **Consistency**: See that `string` that you used instead of an `enum`? Or this `additionalProperties` you confused with `additionalItems`? Or forgot entirely? Well, `json-schema-to-ts` does! 86 | - 🔧 **Reliability**: `FromSchema` is extensively tested against [AJV](https://github.com/ajv-validator/ajv), and covers all the use cases that can be handled by TS for now\* 87 | - 🏋️‍♂️ **Help on complex schemas**: Get complex schemas right first time with instantaneous typing feedbacks! For instance, it's not obvious the following schema can never be validated: 88 | 89 | ```typescript 90 | const addressSchema = { 91 | type: "object", 92 | allOf: [ 93 | { 94 | properties: { 95 | street: { type: "string" }, 96 | city: { type: "string" }, 97 | state: { type: "string" }, 98 | }, 99 | required: ["street", "city", "state"], 100 | }, 101 | { 102 | properties: { 103 | type: { enum: ["residential", "business"] }, 104 | }, 105 | }, 106 | ], 107 | additionalProperties: false, 108 | } as const; 109 | ``` 110 | 111 | But it is with `FromSchema`! 112 | 113 | ```typescript 114 | type Address = FromSchema; 115 | // => never 🙌 116 | ``` 117 | 118 | > \*If `json-schema-to-ts` misses one of your use case, feel free to [open an issue](https://github.com/ThomasAribart/json-schema-to-ts/issues) 🤗 119 | 120 | ## Table of content 121 | 122 | - [Installation](#installation) 123 | - [Use cases](#use-cases) 124 | - [Const](#const) 125 | - [Enums](#enums) 126 | - [Primitive types](#primitive-types) 127 | - [Arrays](#arrays) 128 | - [Tuples](#tuples) 129 | - [Objects](#objects) 130 | - [Combining schemas](#combining-schemas) 131 | - [AnyOf](#anyof) 132 | - [AllOf](#allof) 133 | - [OneOf](#oneof) 134 | - [Not and If-Then-Else](#not-and-if-then-else) 135 | 136 | ## Installation 137 | 138 | ```bash 139 | # npm 140 | npm install --save-dev json-schema-to-ts 141 | 142 | # yarn 143 | yarn add --dev json-schema-to-ts 144 | ``` 145 | 146 | > `json-schema-to-ts` requires TypeScript 3.3+. Activating `strictNullChecks` or using `strict` mode is recommended. 147 | 148 | ## Use cases 149 | 150 | ### Const 151 | 152 | ```typescript 153 | const fooSchema = { 154 | const: "foo", 155 | } as const; 156 | 157 | type Foo = FromSchema; 158 | // => "foo" 159 | ``` 160 | 161 | ### Enums 162 | 163 | ```typescript 164 | const enumSchema = { 165 | enum: [true, 42, { foo: "bar" }], 166 | } as const; 167 | 168 | type Enum = FromSchema; 169 | // => true | 42 | { foo: "bar"} 170 | ``` 171 | 172 | You can also go full circle with typescript `enums`. 173 | 174 | ```typescript 175 | enum Food { 176 | Pizza = "pizza", 177 | Taco = "taco", 178 | Fries = "fries", 179 | } 180 | 181 | const enumSchema = { 182 | enum: Object.values(Food), 183 | } as const; 184 | 185 | type Enum = FromSchema; 186 | // => Food 187 | ``` 188 | 189 | ### Primitive types 190 | 191 | ```typescript 192 | const primitiveTypeSchema = { 193 | type: "null", // "boolean", "string", "integer", "number" 194 | } as const; 195 | 196 | type PrimitiveType = FromSchema; 197 | // => null, boolean, string or number 198 | ``` 199 | 200 | ```typescript 201 | const primitiveTypesSchema = { 202 | type: ["null", "string"], 203 | } as const; 204 | 205 | type PrimitiveTypes = FromSchema; 206 | // => null | string 207 | ``` 208 | 209 | > For more complex types, refinment keywords like `required` or `additionalItems` will apply 🙌 210 | 211 | ### Arrays 212 | 213 | ```typescript 214 | const arraySchema = { 215 | type: "array", 216 | items: { type: "string" }, 217 | } as const; 218 | 219 | type Array = FromSchema; 220 | // => string[] 221 | ``` 222 | 223 | ### Tuples 224 | 225 | ```typescript 226 | const tupleSchema = { 227 | type: "array", 228 | items: [{ type: "boolean" }, { type: "string" }], 229 | } as const; 230 | 231 | type Tuple = FromSchema; 232 | // => [] | [boolean] | [boolean, string] | [boolean, string, ...unknown[]] 233 | ``` 234 | 235 | `FromSchema` supports the `additionalItems` keyword: 236 | 237 | ```typescript 238 | const tupleSchema = { 239 | type: "array", 240 | items: [{ type: "boolean" }, { type: "string" }], 241 | additionalItems: false, 242 | } as const; 243 | 244 | type Tuple = FromSchema; 245 | // => [] | [boolean] | [boolean, string] 246 | ``` 247 | 248 | ```typescript 249 | const tupleSchema = { 250 | type: "array", 251 | items: [{ type: "boolean" }, { type: "string" }], 252 | additionalItems: { type: "number" }, 253 | } as const; 254 | 255 | type Tuple = FromSchema; 256 | // => [] | [boolean] | [boolean, string] | [boolean, string, ...number[]] 257 | ``` 258 | 259 | ...as well as the `minItems` and `maxItems` keywords: 260 | 261 | ```typescript 262 | const tupleSchema = { 263 | type: "array", 264 | items: [{ type: "boolean" }, { type: "string" }], 265 | minItems: 1, 266 | maxItems: 2, 267 | } as const; 268 | 269 | type Tuple = FromSchema; 270 | // => [boolean] | [boolean, string] 271 | ``` 272 | 273 | > Additional items will only work if Typescript's `strictNullChecks` option is activated 274 | 275 | ### Objects 276 | 277 | ```typescript 278 | const objectSchema = { 279 | type: "object", 280 | properties: { 281 | foo: { type: "string" }, 282 | bar: { type: "number" }, 283 | }, 284 | required: ["foo"], 285 | } as const; 286 | 287 | type Object = FromSchema; 288 | // => { [x: string]: unknown; foo: string; bar?: number; } 289 | ``` 290 | 291 | `FromSchema` partially supports the `additionalProperties` and `patternProperties` keywords: 292 | 293 | - `additionalProperties` can be used to deny additional items. 294 | 295 | ```typescript 296 | const closedObjectSchema = { 297 | ...objectSchema, 298 | additionalProperties: false, 299 | } as const; 300 | 301 | type Object = FromSchema; 302 | // => { foo: string; bar?: number; } 303 | ``` 304 | 305 | - Used on their own, `additionalProperties` and/or `patternProperties` can be used to type unnamed properties. 306 | 307 | ```typescript 308 | const openObjectSchema = { 309 | type: "object", 310 | additionalProperties: { 311 | type: "boolean", 312 | }, 313 | patternProperties: { 314 | "^S": { type: "string" }, 315 | "^I": { type: "integer" }, 316 | }, 317 | } as const; 318 | 319 | type Object = FromSchema; 320 | // => { [x: string]: string | number | boolean } 321 | ``` 322 | 323 | - However, when used in combination with the `properties` keyword, extra properties will always be typed as `unknown` to avoid conflicts. 324 | 325 | ## Combining schemas 326 | 327 | ### AnyOf 328 | 329 | ```typescript 330 | const anyOfSchema = { 331 | anyOf: [ 332 | { type: "string" }, 333 | { 334 | type: "array", 335 | items: { type: "string" }, 336 | }, 337 | ], 338 | } as const; 339 | 340 | type AnyOf = FromSchema; 341 | // => string | string[] 342 | ``` 343 | 344 | `FromSchema` will correctly infer factored schemas: 345 | 346 | ```typescript 347 | const factoredSchema = { 348 | type: "object", 349 | properties: { 350 | bool: { type: "boolean" }, 351 | }, 352 | required: ["bool"], 353 | anyOf: [ 354 | { 355 | properties: { 356 | str: { type: "string" }, 357 | }, 358 | required: ["str"], 359 | }, 360 | { 361 | properties: { 362 | num: { type: "number" }, 363 | }, 364 | }, 365 | ], 366 | } as const; 367 | 368 | type Factored = FromSchema; 369 | // => { 370 | // [x:string]: unknown; 371 | // bool: boolean; 372 | // str: string; 373 | // } | { 374 | // [x:string]: unknown; 375 | // bool: boolean; 376 | // num?: number; 377 | // } 378 | ``` 379 | 380 | ### OneOf 381 | 382 | Because TypeScript misses [refinment types](https://en.wikipedia.org/wiki/Refinement_type), `FromSchema` will use the `oneOf` keyword in the same way as `anyOf`: 383 | 384 | ```typescript 385 | const catSchema = { 386 | type: "object", 387 | oneOf: [ 388 | { 389 | properties: { 390 | name: { type: "string" }, 391 | }, 392 | required: ["name"], 393 | }, 394 | { 395 | properties: { 396 | color: { enum: ["black", "brown", "white"] }, 397 | }, 398 | }, 399 | ], 400 | } as const; 401 | 402 | type Cat = FromSchema; 403 | // => { 404 | // [x: string]: unknown; 405 | // name: string; 406 | // } | { 407 | // [x: string]: unknown; 408 | // color?: "black" | "brown" | "white"; 409 | // } 410 | 411 | // => FromSchema will not detect the following invalid obj 😱 412 | const invalidCat: Cat = { name: "Garfield" }; 413 | ``` 414 | 415 | ### AllOf 416 | 417 | ```typescript 418 | const addressSchema = { 419 | type: "object", 420 | allOf: [ 421 | { 422 | properties: { 423 | address: { type: "string" }, 424 | city: { type: "string" }, 425 | state: { type: "string" }, 426 | }, 427 | required: ["address", "city", "state"], 428 | }, 429 | { 430 | properties: { 431 | type: { enum: ["residential", "business"] }, 432 | }, 433 | }, 434 | ], 435 | } as const; 436 | 437 | type Address = FromSchema; 438 | // => { 439 | // [x: string]: unknown; 440 | // address: string; 441 | // city: string; 442 | // state: string; 443 | // type?: "residential" | "business"; 444 | // } 445 | ``` 446 | 447 | ### Not and If-Then-Else 448 | 449 | For the same reason as `oneOf` (missing refinment types), I feel like implementing the `not` and the `if/then/else` keywords in `FromSchema` would lead into a rabbit hole... 450 | 451 | But I may be wrong! If you think that it can be implemented, feel free to [open an issue](https://github.com/ThomasAribart/json-schema-to-ts/issues) 🤗 452 | -------------------------------------------------------------------------------- /assets/header-large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel/json-schema-to-ts/76a7f89db0e4c54697915c30e598fc9e2e46dc92/assets/header-large.jpg -------------------------------------------------------------------------------- /assets/header-round-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel/json-schema-to-ts/76a7f89db0e4c54697915c30e598fc9e2e46dc92/assets/header-round-large.png -------------------------------------------------------------------------------- /assets/header-round-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel/json-schema-to-ts/76a7f89db0e4c54697915c30e598fc9e2e46dc92/assets/header-round-medium.png -------------------------------------------------------------------------------- /assets/logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vercel/json-schema-to-ts/76a7f89db0e4c54697915c30e598fc9e2e46dc92/assets/logo.ai -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | "@babel/preset-typescript", 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-schema-to-ts", 3 | "version": "1.4.0", 4 | "description": "Infer typescript types from your JSON schemas!", 5 | "main": "lib/index.d.ts", 6 | "scripts": { 7 | "build": "tsc --emitDeclarationOnly --declaration --declarationDir lib ./src/*.ts", 8 | "test": "tsc --noEmit && jest --verbose" 9 | }, 10 | "dependencies": { 11 | "@types/json-schema": "^7.0.6" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "^7.10.4", 15 | "@babel/preset-env": "^7.10.4", 16 | "@babel/preset-typescript": "^7.10.4", 17 | "@types/jest": "^26.0.4", 18 | "ajv": "^6.12.6", 19 | "babel-jest": "^26.1.0", 20 | "jest": "^26.1.0", 21 | "typescript": "^3.9.6" 22 | }, 23 | "author": "Thomas Aribart", 24 | "license": "MIT", 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/ThomasAribart/json-schema-to-ts.git" 28 | }, 29 | "keywords": [ 30 | "json", 31 | "schema", 32 | "typescript", 33 | "type", 34 | "ts" 35 | ], 36 | "bugs": { 37 | "url": "https://github.com/ThomasAribart/json-schema-to-ts/issues" 38 | }, 39 | "homepage": "https://github.com/ThomasAribart/json-schema-to-ts#readme" 40 | } 41 | -------------------------------------------------------------------------------- /src/definitions/index.ts: -------------------------------------------------------------------------------- 1 | export { JSONSchema6DefinitionWithoutInterface } from "./jsonSchema6"; 2 | -------------------------------------------------------------------------------- /src/definitions/jsonSchema6.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema6Definition, JSONSchema6 } from "json-schema"; 2 | 3 | import { Replace } from "../utils"; 4 | 5 | // JSONSchema6Definition uses interfaces in "const" and "enum" typing (I guess for the purpose of denying "undefined" types in "const" and "enums") 6 | // However, specific interfaces CANNOT be saved into a more generic interface (see issue https://github.com/microsoft/TypeScript/issues/15300) 7 | // This results in an error "Index signature is missing in type..." on valid JSON schemas when using FromSchema 8 | // This fix replaces JSONSchema6Definition "const" and "enum" definitions by "unknown" 9 | // It is not ideal, but it's the best thing to do at the moment 10 | export type JSONSchema6DefinitionWithoutInterface = JSONSchema6Definition extends infer S 11 | ? S extends JSONSchema6 12 | ? Replace 13 | : S 14 | : never; 15 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema6Definition } from "json-schema"; 2 | 3 | import { JSONSchema6DefinitionWithoutInterface } from "./definitions"; 4 | import { Resolve } from "./meta-types"; 5 | import { ParseSchema } from "./parse-schema"; 6 | import { WriteableRec, ReadonlyRec } from "./utils"; 7 | 8 | /** 9 | * Given a JSON schema defined with the `as const` statement, infers the type of valid instances 10 | * 11 | * @param S JSON schema 12 | */ 13 | export type FromSchema< 14 | S extends 15 | | JSONSchema6Definition 16 | | ReadonlyRec 17 | > = Resolve>>; 18 | -------------------------------------------------------------------------------- /src/meta-types/any.ts: -------------------------------------------------------------------------------- 1 | export type AnyType = "any"; 2 | 3 | export type Any = { 4 | type: AnyType; 5 | }; 6 | 7 | export type ResolveAny = unknown; 8 | -------------------------------------------------------------------------------- /src/meta-types/array.ts: -------------------------------------------------------------------------------- 1 | import { Get } from "../utils"; 2 | 3 | import { Resolve, Any } from "."; 4 | 5 | export type ArrType = "array"; 6 | 7 | export type Arr = { 8 | type: ArrType; 9 | values: V; 10 | }; 11 | 12 | export type Values = Get; 13 | 14 | export type ResolveArr = Resolve>[]; 15 | -------------------------------------------------------------------------------- /src/meta-types/const.ts: -------------------------------------------------------------------------------- 1 | import { Get } from "../utils"; 2 | 3 | export type ConstType = "const"; 4 | 5 | export type Const = { 6 | type: ConstType; 7 | value: V; 8 | }; 9 | 10 | export type Value = Get; 11 | 12 | export type ResolveConst = Value; 13 | -------------------------------------------------------------------------------- /src/meta-types/enum.ts: -------------------------------------------------------------------------------- 1 | import { Get } from "../utils"; 2 | 3 | export type EnumType = "enum"; 4 | 5 | export type Enum = { type: "enum"; values: V }; 6 | 7 | export type Values = Get; 8 | 9 | export type ResolveEnum = Values; 10 | -------------------------------------------------------------------------------- /src/meta-types/error.ts: -------------------------------------------------------------------------------- 1 | import { Get } from "../utils"; 2 | 3 | export type ErrorType = "error"; 4 | 5 | export type Error = { 6 | type: ErrorType; 7 | message: M; 8 | }; 9 | 10 | export type Message = Get; 11 | -------------------------------------------------------------------------------- /src/meta-types/index.ts: -------------------------------------------------------------------------------- 1 | import { Get } from "../utils"; 2 | 3 | import { Any, AnyType, ResolveAny } from "./any"; 4 | import { Never, NeverType, ResolveNever } from "./never"; 5 | import { Const, ConstType, ResolveConst } from "./const"; 6 | import { Enum, EnumType, ResolveEnum } from "./enum"; 7 | import { Primitive, PrimitiveType, ResolvePrimitive } from "./primitive"; 8 | import { Arr, ArrType, ResolveArr } from "./array"; 9 | import { Tuple, TupleType, ResolveTuple } from "./tuple"; 10 | import { Object, ObjectType, ResolveObject } from "./object"; 11 | import { Union, UnionType, ResolveUnion } from "./union"; 12 | import { 13 | Intersection, 14 | IntersectionType, 15 | ResolveIntersection, 16 | } from "./intersection"; 17 | import { Error, ErrorType } from "./error"; 18 | 19 | export type MetaType = 20 | | AnyType 21 | | NeverType 22 | | ConstType 23 | | EnumType 24 | | PrimitiveType 25 | | ArrType 26 | | TupleType 27 | | ObjectType 28 | | UnionType 29 | | IntersectionType 30 | | ErrorType; 31 | 32 | export type Resolve> = { 33 | any: ResolveAny; 34 | never: ResolveNever; 35 | const: ResolveConst; 36 | enum: ResolveEnum; 37 | primitive: ResolvePrimitive; 38 | array: ResolveArr; 39 | tuple: ResolveTuple; 40 | object: ResolveObject; 41 | union: ResolveUnion; 42 | intersection: ResolveIntersection; 43 | error: never; 44 | }[Get extends MetaType ? Get : "error"]; 45 | 46 | export { 47 | Any, 48 | Never, 49 | Const, 50 | Enum, 51 | Primitive, 52 | Arr, 53 | Tuple, 54 | Object, 55 | Union, 56 | Intersection, 57 | Error, 58 | }; 59 | -------------------------------------------------------------------------------- /src/meta-types/intersection/array.ts: -------------------------------------------------------------------------------- 1 | import { Get } from "../../utils"; 2 | 3 | import { MetaType, Never, Arr, Error } from ".."; 4 | import { Values } from "../array"; 5 | 6 | import { IntersectConst } from "./const"; 7 | import { IntersectEnum } from "./enum"; 8 | import { IntersectTuple } from "./tuple"; 9 | import { ClearIntersections, Intersect } from "./index"; 10 | 11 | export type ClearArrIntersections = Arr>>; 12 | 13 | export type IntersectArr = { 14 | any: A; 15 | never: Never; 16 | const: IntersectConst; 17 | enum: IntersectEnum; 18 | primitive: Never; 19 | array: IntersectArrs; 20 | tuple: IntersectTuple; 21 | object: Never; 22 | union: Intersect; 23 | intersection: Error<"Cannot intersect intersection">; 24 | error: B; 25 | errorTypeProperty: Error<"Missing type property">; 26 | }[Get extends MetaType ? Get : "errorTypeProperty"]; 27 | 28 | type IntersectArrs, Values>> = I extends Never 29 | ? Never 30 | : Arr; 31 | -------------------------------------------------------------------------------- /src/meta-types/intersection/const.ts: -------------------------------------------------------------------------------- 1 | import { Get, IsObject } from "../../utils"; 2 | 3 | import { Resolve, MetaType, Never, Error } from ".."; 4 | import { Const, Value } from "../const"; 5 | import { Values, Required, IsOpen, OpenProps } from "../object"; 6 | 7 | import { Intersect } from "./index"; 8 | 9 | export type IntersectConst = { 10 | any: A; 11 | never: Never; 12 | const: CheckExtendsResolved; 13 | enum: CheckExtendsResolved; 14 | primitive: CheckExtendsResolved; 15 | array: CheckExtendsResolved; 16 | tuple: CheckExtendsResolved; 17 | object: ToObject; 18 | union: Intersect; 19 | intersection: Error<"Cannot intersect intersection">; 20 | error: B; 21 | errorTypeProperty: Error<"Missing type property">; 22 | }[Get extends MetaType ? Get : "errorTypeProperty"]; 23 | 24 | type CheckExtendsResolved = Value extends Resolve ? A : Never; 25 | 26 | type ToObject = IsObject> extends true 27 | ? IntersectConstToObject 28 | : Never; 29 | 30 | type IntersectConstToObject< 31 | A, 32 | B, 33 | V = IntersectConstValues, B> 34 | > = NeverKeys extends never ? A : Never; 35 | 36 | type IntersectConstValues = { 37 | [key in keyof V | Required]: key extends keyof V 38 | ? key extends keyof Values 39 | ? Intersect, Values[key]> 40 | : IsOpen extends true 41 | ? Intersect, OpenProps> 42 | : Never 43 | : Never; 44 | }; 45 | 46 | type NeverKeys = { 47 | [key in keyof O]: O[key] extends Never ? key : never; 48 | }[keyof O]; 49 | -------------------------------------------------------------------------------- /src/meta-types/intersection/enum.ts: -------------------------------------------------------------------------------- 1 | import { Get } from "../../utils"; 2 | 3 | import { MetaType, Never, Const, Error } from ".."; 4 | import { Enum, Values } from "../enum"; 5 | 6 | import { IntersectConst } from "./const"; 7 | import { Intersect } from "./index"; 8 | 9 | export type IntersectEnum = { 10 | any: A; 11 | never: Never; 12 | const: IntersectConst; 13 | enum: FilterExtendingResolved; 14 | primitive: FilterExtendingResolved; 15 | array: FilterExtendingResolved; 16 | tuple: FilterExtendingResolved; 17 | object: FilterExtendingResolved; 18 | union: Intersect; 19 | intersection: Error<"Cannot intersect intersection">; 20 | error: B; 21 | errorTypeProperty: Error<"Missing type property">; 22 | }[Get extends MetaType ? Get : "errorTypeProperty"]; 23 | 24 | type FilterExtendingResolved = Enum, B>>; 25 | 26 | type RecurseOnEnumValues = V extends infer T 27 | ? Intersect, B> extends Never 28 | ? never 29 | : T 30 | : never; 31 | -------------------------------------------------------------------------------- /src/meta-types/intersection/index.ts: -------------------------------------------------------------------------------- 1 | import { DoesExtend, Get } from "../../utils"; 2 | 3 | import { Resolve, MetaType, Never, Error } from ".."; 4 | 5 | import { IntersectConst } from "./const"; 6 | import { IntersectEnum } from "./enum"; 7 | import { IntersectPrimitive } from "./primitive"; 8 | import { ClearArrIntersections, IntersectArr } from "./array"; 9 | import { ClearTupleIntersections, IntersectTuple } from "./tuple"; 10 | import { ClearObjectIntersections, IntersectObject } from "./object"; 11 | import { ClearUnionIntersections, IntersectUnion } from "./union"; 12 | 13 | export type IntersectionType = "intersection"; 14 | 15 | export type Intersection = { 16 | type: IntersectionType; 17 | left: L; 18 | right: R; 19 | }; 20 | 21 | export type IsIntersection = DoesExtend, IntersectionType>; 22 | 23 | export type Left = Get; 24 | 25 | export type Right = Get; 26 | 27 | export type ResolveIntersection = Resolve>; 28 | 29 | export type ClearIntersections = { 30 | any: T; 31 | never: T; 32 | const: T; 33 | enum: T; 34 | primitive: T; 35 | array: ClearArrIntersections; 36 | tuple: ClearTupleIntersections; 37 | object: ClearObjectIntersections; 38 | union: ClearUnionIntersections; 39 | error: T; 40 | intersection: Intersect< 41 | ClearIntersections>, 42 | ClearIntersections> 43 | >; 44 | errorMissingType: Error<"Missing type property">; 45 | }[Get extends MetaType ? Get : "errorMissingType"]; 46 | 47 | export type Intersect = { 48 | any: B; 49 | never: Never; 50 | const: IntersectConst; 51 | enum: IntersectEnum; 52 | primitive: IntersectPrimitive; 53 | array: IntersectArr; 54 | tuple: IntersectTuple; 55 | object: IntersectObject; 56 | union: IntersectUnion; 57 | error: A; 58 | errorMissingType: Error<"Missing type property">; 59 | intersection: Error<"Cannot intersect intersection">; 60 | }[Get extends MetaType ? Get : "errorMissingType"]; 61 | -------------------------------------------------------------------------------- /src/meta-types/intersection/object.ts: -------------------------------------------------------------------------------- 1 | import { Get, And } from "../../utils"; 2 | 3 | import { MetaType, Never, Error } from ".."; 4 | import { Object, Values, Required, IsOpen, OpenProps } from "../object"; 5 | 6 | import { IntersectConst } from "./const"; 7 | import { IntersectEnum } from "./enum"; 8 | import { DistributeIntersection } from "./union"; 9 | import { ClearIntersections, Intersect } from "./index"; 10 | 11 | export type ClearObjectIntersections< 12 | A, 13 | V = ClearObjectValuesIntersections>, 14 | N = NeverKeys, 15 | O = ClearIntersections> 16 | > = Required extends Exclude, N> 17 | ? Object< 18 | { 19 | [key in Exclude]: V[key]; 20 | }, 21 | Required, 22 | O extends Never ? false : IsOpen, 23 | O 24 | > 25 | : Never; 26 | 27 | type ClearObjectValuesIntersections = { 28 | [key in keyof V]: ClearIntersections; 29 | }; 30 | 31 | export type IntersectObject = { 32 | any: A; 33 | never: Never; 34 | const: IntersectConst; 35 | enum: IntersectEnum; 36 | primitive: Never; 37 | array: Never; 38 | tuple: Never; 39 | object: IntersectObjects; 40 | union: DistributeIntersection; 41 | intersection: Error<"Cannot intersect intersection">; 42 | error: B; 43 | errorTypeProperty: Error<"Missing type property">; 44 | }[Get extends MetaType ? Get : "errorTypeProperty"]; 45 | 46 | type IntersectObjects< 47 | A, 48 | B, 49 | V = IntersectValues, 50 | N = NeverKeys, 51 | O = IntersectOpenProps 52 | > = Required | Required extends Exclude | Required, N> 53 | ? Object< 54 | { 55 | [key in Exclude]: V[key]; 56 | }, 57 | Required | Required, 58 | O extends Never ? false : And, IsOpen>, 59 | O 60 | > 61 | : Never; 62 | 63 | type IntersectValues = { 64 | [key in keyof Values | keyof Values]: key extends keyof Values 65 | ? key extends keyof Values 66 | ? Intersect[key], Values[key]> 67 | : IsOpen extends true 68 | ? Intersect[key], OpenProps> 69 | : Never 70 | : key extends keyof Values 71 | ? IsOpen extends true 72 | ? Intersect, Values[key]> 73 | : Never 74 | : Never; 75 | }; 76 | 77 | type NeverKeys = { 78 | [key in keyof O]: O[key] extends Never ? key : never; 79 | }[keyof O]; 80 | 81 | type IntersectOpenProps = Intersect, OpenProps>; 82 | -------------------------------------------------------------------------------- /src/meta-types/intersection/primitive.ts: -------------------------------------------------------------------------------- 1 | import { Get } from "../../utils"; 2 | 3 | import { MetaType, Never, Error } from ".."; 4 | import { Value } from "../primitive"; 5 | 6 | import { IntersectConst } from "./const"; 7 | import { IntersectEnum } from "./enum"; 8 | import { Intersect } from "."; 9 | 10 | export type IntersectPrimitive = { 11 | any: A; 12 | never: Never; 13 | const: IntersectConst; 14 | enum: IntersectEnum; 15 | primitive: Value extends Value 16 | ? A 17 | : Value extends Value 18 | ? B 19 | : Never; 20 | array: Never; 21 | tuple: Never; 22 | object: Never; 23 | union: Intersect; 24 | intersection: Error<"Cannot intersect intersection">; 25 | error: B; 26 | errorTypeProperty: Error<"Missing type property">; 27 | }[Get extends MetaType ? Get : "errorTypeProperty"]; 28 | -------------------------------------------------------------------------------- /src/meta-types/intersection/tuple.ts: -------------------------------------------------------------------------------- 1 | import { Get, Head, Tail, Prepend, Reverse, And } from "../../utils"; 2 | 3 | import { MetaType, Never, Tuple, Error } from ".."; 4 | import { Values as ArrValues } from "../array"; 5 | import { Values, IsOpen, OpenProps } from "../tuple"; 6 | 7 | import { IntersectConst } from "./const"; 8 | import { IntersectEnum } from "./enum"; 9 | import { DistributeIntersection } from "./union"; 10 | import { ClearIntersections, Intersect } from "."; 11 | 12 | export type ClearTupleIntersections< 13 | T, 14 | O = ClearIntersections> 15 | > = Tuple< 16 | ClearTupleValuesIntersections>, 17 | O extends Never ? false : IsOpen, 18 | O 19 | >; 20 | 21 | type ClearTupleValuesIntersections = { 22 | stop: Reverse; 23 | continue: ClearTupleValuesIntersections< 24 | Tail, 25 | Prepend>, R> 26 | >; 27 | }[V extends [any, ...any[]] ? "continue" : "stop"]; 28 | 29 | export type IntersectTuple = { 30 | any: A; 31 | never: Never; 32 | const: IntersectConst; 33 | enum: IntersectEnum; 34 | primitive: Never; 35 | array: IntersectTupleToArray; 36 | tuple: IntersectTuples; 37 | object: Never; 38 | union: DistributeIntersection; 39 | intersection: Error<"Cannot intersect intersection">; 40 | error: B; 41 | errorTypeProperty: Error<"Missing type property">; 42 | }[Get extends MetaType ? Get : "errorTypeProperty"]; 43 | 44 | type IntersectTupleToArray< 45 | T, 46 | A, 47 | V = IntersectTupleToArrValues, ArrValues>, 48 | N = HasNeverValue, 49 | O = Intersect, ArrValues> 50 | > = N extends true 51 | ? Never 52 | : Tuple< 53 | V, 54 | IsOpen extends true ? (O extends Never ? false : true) : false, 55 | O 56 | >; 57 | 58 | type IntersectTupleToArrValues = { 59 | stop: Reverse; 60 | continue: R extends any[] 61 | ? IntersectTupleToArrValues, T, Prepend, T>, R>> 62 | : never; 63 | }[V extends [any, ...any[]] ? "continue" : "stop"]; 64 | 65 | type HasNeverValue = { 66 | stop: R; 67 | continue: Head extends Never ? true : HasNeverValue>; 68 | }[V extends [any, ...any[]] ? "continue" : "stop"]; 69 | 70 | type IntersectTuples< 71 | A, 72 | B, 73 | V = IntersectTupleValues< 74 | Values, 75 | Values, 76 | IsOpen, 77 | IsOpen, 78 | OpenProps, 79 | OpenProps 80 | >, 81 | N = HasNeverValue, 82 | O = Intersect, OpenProps> 83 | > = N extends true 84 | ? Never 85 | : Tuple, IsOpen>, O>; 86 | 87 | type IntersectTupleValues = { 88 | stop: Reverse; 89 | continue1: IntersectTupleValues< 90 | Tail, 91 | V2, 92 | O1, 93 | O2, 94 | P1, 95 | P2, 96 | Prepend, P2> : Never, R> 97 | >; 98 | continue2: IntersectTupleValues< 99 | V1, 100 | Tail, 101 | O1, 102 | O2, 103 | P1, 104 | P2, 105 | Prepend, P1> : Never, R> 106 | >; 107 | continueBoth: IntersectTupleValues< 108 | Tail, 109 | Tail, 110 | O1, 111 | O2, 112 | P1, 113 | P2, 114 | Prepend, Head>, R> 115 | >; 116 | }[V1 extends [any, ...any[]] 117 | ? V2 extends [any, ...any[]] 118 | ? "continueBoth" 119 | : "continue1" 120 | : V2 extends [any, ...any[]] 121 | ? "continue2" 122 | : "stop"]; 123 | -------------------------------------------------------------------------------- /src/meta-types/intersection/union.ts: -------------------------------------------------------------------------------- 1 | import { Get } from "../../utils"; 2 | 3 | import { MetaType, Never, Error } from ".."; 4 | import { Union, Values } from "../union"; 5 | 6 | import { ClearIntersections, Intersect } from "./index"; 7 | 8 | export type ClearUnionIntersections = Union< 9 | ClearUnionValuesIntersections> 10 | >; 11 | 12 | type ClearUnionValuesIntersections = V extends infer T 13 | ? ClearIntersections 14 | : never; 15 | 16 | export type IntersectUnion = { 17 | any: A; 18 | never: Never; 19 | const: DistributeIntersection; 20 | enum: DistributeIntersection; 21 | primitive: DistributeIntersection; 22 | array: DistributeIntersection; 23 | tuple: DistributeIntersection; 24 | object: DistributeIntersection; 25 | union: DistributeIntersection; 26 | intersection: Error<"Cannot intersect intersection">; 27 | error: B; 28 | errorTypeProperty: Error<"Missing type property">; 29 | }[Get extends MetaType ? Get : "errorTypeProperty"]; 30 | 31 | export type DistributeIntersection = Union, B>>; 32 | 33 | type RecurseOnUnion = V extends infer T ? Intersect : never; 34 | -------------------------------------------------------------------------------- /src/meta-types/never.ts: -------------------------------------------------------------------------------- 1 | export type NeverType = "never"; 2 | 3 | export type Never = { 4 | type: NeverType; 5 | }; 6 | 7 | export type ResolveNever = never; 8 | -------------------------------------------------------------------------------- /src/meta-types/object.ts: -------------------------------------------------------------------------------- 1 | import { DoesExtend, Get, UnsafeMergeRec } from "../utils"; 2 | 3 | import { Resolve, Any } from "."; 4 | 5 | export type ObjectType = "object"; 6 | 7 | export type Object = { 8 | type: ObjectType; 9 | values: V; 10 | required: R; 11 | isOpen: O; 12 | openProps: P; 13 | }; 14 | 15 | export type Values = Get; 16 | 17 | export type Required = Get extends string 18 | ? Get 19 | : never; 20 | 21 | export type IsOpen = Get; 22 | 23 | export type OpenProps = Get; 24 | 25 | type IsEmpty = DoesExtend, keyof Values>, never>; 26 | 27 | export type ResolveObject = IsObjectValid extends true 28 | ? ResolveValidObject 29 | : never; 30 | 31 | type IsObjectValid = IsOpen extends false 32 | ? Required extends keyof Values 33 | ? true 34 | : false 35 | : true; 36 | 37 | type ResolveValidObject = UnsafeMergeRec< 38 | IsOpen extends true 39 | ? IsEmpty extends true 40 | ? { [key: string]: Resolve> } 41 | : { [key: string]: Resolve } 42 | : {}, 43 | UnsafeMergeRec< 44 | { 45 | [key in Exclude, Required>]?: Resolve[key]>; 46 | }, 47 | { 48 | [key in Required]: key extends keyof Values 49 | ? Resolve[key]> 50 | : Resolve; 51 | } 52 | > 53 | >; 54 | -------------------------------------------------------------------------------- /src/meta-types/primitive.ts: -------------------------------------------------------------------------------- 1 | import { Get } from "../utils"; 2 | 3 | export type PrimitiveType = "primitive"; 4 | 5 | export type Primitive = { 6 | type: PrimitiveType; 7 | value: L; 8 | }; 9 | 10 | export type Value = Get; 11 | 12 | export type ResolvePrimitive = Get; 13 | -------------------------------------------------------------------------------- /src/meta-types/tuple.ts: -------------------------------------------------------------------------------- 1 | import { Get, Head, Tail, Prepend, Concat, Reverse } from "../utils"; 2 | 3 | import { Resolve, Any } from "."; 4 | 5 | export type TupleType = "tuple"; 6 | 7 | export type Tuple = { 8 | type: TupleType; 9 | values: V; 10 | isOpen: O; 11 | openProps: P; 12 | }; 13 | 14 | export type Values = Get; 15 | 16 | export type IsOpen = Get; 17 | 18 | export type OpenProps = Get; 19 | 20 | export type ResolveTuple = IsOpen extends true 21 | ? Concat>, [...Resolve>[]]> 22 | : RecurseOnTuple>; 23 | 24 | type RecurseOnTuple = { 25 | stop: Reverse; 26 | continue: V extends any[] 27 | ? RecurseOnTuple, Prepend>, R>> 28 | : never; 29 | }[V extends [any, ...any[]] ? "continue" : "stop"]; 30 | -------------------------------------------------------------------------------- /src/meta-types/union.ts: -------------------------------------------------------------------------------- 1 | import { Get } from "../utils"; 2 | 3 | import { Resolve } from "."; 4 | 5 | export type UnionType = "union"; 6 | 7 | export type Union = { 8 | type: UnionType; 9 | values: V; 10 | }; 11 | 12 | export type Values = Get; 13 | 14 | export type ResolveUnion = RecurseOnUnionTree>; 15 | 16 | type RecurseOnUnionTree = V extends infer T ? Resolve : never; 17 | -------------------------------------------------------------------------------- /src/parse-schema/allOf.ts: -------------------------------------------------------------------------------- 1 | import { Any, Intersection } from "../meta-types"; 2 | import { Tail, Head, Get, HasKeyIn, Merge } from "../utils"; 3 | 4 | import { ParseSchema } from "."; 5 | import { RemoveInvalidAdditionalItems } from "./utils"; 6 | 7 | export type ParseAllOfSchema = RecurseOnAllOfSchema< 8 | Get, 9 | S, 10 | HasKeyIn extends true 11 | ? ParseSchema> 12 | : Any 13 | >; 14 | 15 | type RecurseOnAllOfSchema = { 16 | stop: R; 17 | continue: V extends any[] 18 | ? RecurseOnAllOfSchema< 19 | Tail, 20 | S, 21 | Intersection< 22 | ParseSchema< 23 | Merge< 24 | Omit, 25 | Merge< 26 | { properties: {}; additionalProperties: true; required: [] }, 27 | RemoveInvalidAdditionalItems> 28 | > 29 | > 30 | >, 31 | R 32 | > 33 | > 34 | : never; 35 | }[V extends [any, ...any[]] ? "continue" : "stop"]; 36 | -------------------------------------------------------------------------------- /src/parse-schema/anyOf.ts: -------------------------------------------------------------------------------- 1 | import { Intersection, Union } from "../meta-types"; 2 | import { Tail, Head, Get, HasKeyIn, Merge } from "../utils"; 3 | 4 | import { ParseSchema } from "."; 5 | import { RemoveInvalidAdditionalItems } from "./utils"; 6 | 7 | export type ParseAnyOfSchema = Union< 8 | RecurseOnAnyOfSchema, S> 9 | >; 10 | 11 | type RecurseOnAnyOfSchema = { 12 | stop: R; 13 | continue: S extends any[] 14 | ? RecurseOnAnyOfSchema< 15 | Tail, 16 | P, 17 | | R 18 | | (HasKeyIn extends true 19 | ? Intersection< 20 | ParseSchema>, 21 | ParseSchema< 22 | Merge< 23 | Omit, 24 | Merge< 25 | { 26 | properties: {}; 27 | additionalProperties: true; 28 | required: []; 29 | }, 30 | RemoveInvalidAdditionalItems> 31 | > 32 | > 33 | > 34 | > 35 | : ParseSchema< 36 | Merge, RemoveInvalidAdditionalItems>> 37 | >) 38 | > 39 | : never; 40 | }[S extends [any, ...any[]] ? "continue" : "stop"]; 41 | -------------------------------------------------------------------------------- /src/parse-schema/array.ts: -------------------------------------------------------------------------------- 1 | import { Arr, Tuple, Union, Error } from "../meta-types"; 2 | import { 3 | Head, 4 | Tail, 5 | Reverse, 6 | DoesExtend, 7 | Get, 8 | Prepend, 9 | IsObject, 10 | } from "../utils"; 11 | 12 | import { ParseSchema } from "."; 13 | 14 | export type ParseArrSchema = "items" extends keyof S 15 | ? IsObject extends true 16 | ? Arr> 17 | : S["items"] extends any[] 18 | ? Union, S>> 19 | : Error<'Invalid value in "items" property'> 20 | : Arr; 21 | 22 | export type ParseTuple = { 23 | stop: R; 24 | continue: S extends any[] 25 | ? ParseTuple, Prepend>, R>> 26 | : never; 27 | }[S extends [any, ...any[]] ? "continue" : "stop"]; 28 | 29 | type FromTreeTuple = T extends any[] 30 | ? ApplyAdditionalItems< 31 | ApplyBoundaries< 32 | T, 33 | "minItems" extends keyof S ? S["minItems"] : 0, 34 | "maxItems" extends keyof S ? S["maxItems"] : undefined 35 | >, 36 | "additionalItems" extends keyof S ? S["additionalItems"] : true 37 | > 38 | : never; 39 | 40 | type ApplyBoundaries< 41 | T extends any[], 42 | Min, 43 | Max, 44 | R = never, 45 | HasMin extends boolean = false, 46 | HasMax extends boolean = false, 47 | C = T 48 | > = { 49 | stop: { 50 | result: Max extends undefined 51 | ? R | Tuple, false> 52 | : HasMax extends true 53 | ? R | Tuple, false> 54 | : Max extends T["length"] 55 | ? Tuple, false> 56 | : IsLongerThan, Max> extends true 57 | ? never 58 | : R | Tuple, false>; 59 | hasEncounteredMin: DoesExtend; 60 | hasEncounteredMax: HasMax extends true 61 | ? true 62 | : Max extends T["length"] 63 | ? true 64 | : IsLongerThan, Max>; 65 | completeTuple: C; 66 | }; 67 | continue: ApplyBoundaries< 68 | Tail, 69 | Min, 70 | Max, 71 | T["length"] extends Max 72 | ? Tuple, false> 73 | : R | Tuple, false>, 74 | HasMin extends true ? true : DoesExtend, 75 | HasMax extends true ? true : DoesExtend, 76 | C 77 | >; 78 | }[Min extends T["length"] 79 | ? "stop" 80 | : T extends [any, ...any[]] 81 | ? "continue" 82 | : "stop"]; 83 | 84 | type IsLongerThan = { 85 | continue: T["length"] extends N ? true : IsLongerThan, N>; 86 | stop: T["length"] extends N ? true : R; 87 | }[T extends [any, ...any[]] ? "continue" : "stop"]; 88 | 89 | type ApplyAdditionalItems = Get extends true 90 | ? Get extends true 91 | ? Get 92 | : Error<'"minItems" property is lower than "maxItems"'> 93 | : A extends false 94 | ? Get extends true 95 | ? Get 96 | : Error<'"minItems" property is higher than allowed number of items'> 97 | : A extends true 98 | ? Get extends true 99 | ? Get | Tuple>> 100 | : Tuple>> 101 | : IsObject extends true 102 | ? Get extends true 103 | ? 104 | | Get 105 | | Tuple>, true, ParseSchema> 106 | : Tuple>, true, ParseSchema> 107 | : Error<'Invalid value in "additionalItems" property'>; 108 | -------------------------------------------------------------------------------- /src/parse-schema/const.ts: -------------------------------------------------------------------------------- 1 | import { Const, Intersection } from "../meta-types"; 2 | import { Get, HasKeyIn } from "../utils"; 3 | 4 | import { ParseSchema } from "."; 5 | 6 | export type ParseConstSchema = HasKeyIn extends true 7 | ? Intersection>, ParseSchema>> 8 | : Const>; 9 | -------------------------------------------------------------------------------- /src/parse-schema/enum.ts: -------------------------------------------------------------------------------- 1 | import { Enum, Intersection } from "../meta-types"; 2 | import { GetRec, HasKeyIn } from "../utils"; 3 | 4 | import { ParseSchema } from "."; 5 | 6 | export type ParseEnumSchema = HasKeyIn extends true 7 | ? Intersection< 8 | Enum>, 9 | ParseSchema> 10 | > 11 | : Enum>; 12 | -------------------------------------------------------------------------------- /src/parse-schema/index.ts: -------------------------------------------------------------------------------- 1 | import { Primitive, Any, Never } from "../meta-types"; 2 | 3 | import { ParseConstSchema } from "./const"; 4 | import { ParseEnumSchema } from "./enum"; 5 | import { ParseMixedSchema } from "./mixed"; 6 | import { ParseArrSchema } from "./array"; 7 | import { ParseObjectSchema } from "./object"; 8 | import { ParseAnyOfSchema } from "./anyOf"; 9 | import { ParseOneOfSchema } from "./oneOf"; 10 | import { ParseAllOfSchema } from "./allOf"; 11 | 12 | export type ParseSchema = { 13 | any: Any; 14 | never: Never; 15 | null: Primitive; 16 | boolean: Primitive; 17 | number: Primitive; 18 | string: Primitive; 19 | mixed: ParseMixedSchema; 20 | object: ParseObjectSchema; 21 | array: ParseArrSchema; 22 | const: ParseConstSchema; 23 | enum: ParseEnumSchema; 24 | anyOf: ParseAnyOfSchema; 25 | oneOf: ParseOneOfSchema; 26 | allOf: ParseAllOfSchema; 27 | }[InferSchemaType]; 28 | 29 | type InferSchemaType = S extends true | string 30 | ? "any" 31 | : S extends false 32 | ? "never" 33 | : "allOf" extends keyof S 34 | ? "allOf" 35 | : "oneOf" extends keyof S 36 | ? "oneOf" 37 | : "anyOf" extends keyof S 38 | ? "anyOf" 39 | : "enum" extends keyof S 40 | ? "enum" 41 | : "const" extends keyof S 42 | ? "const" 43 | : "type" extends keyof S 44 | ? S["type"] extends any[] 45 | ? "mixed" 46 | : S["type"] extends "null" 47 | ? "null" 48 | : S["type"] extends "boolean" 49 | ? "boolean" 50 | : S["type"] extends "integer" | "number" 51 | ? "number" 52 | : S["type"] extends "string" 53 | ? "string" 54 | : S["type"] extends "object" 55 | ? "object" 56 | : S["type"] extends "array" 57 | ? "array" 58 | : "never" 59 | : "any"; 60 | -------------------------------------------------------------------------------- /src/parse-schema/mixed.ts: -------------------------------------------------------------------------------- 1 | import { Union } from "../meta-types"; 2 | import { Get, Head, Tail, UnsafeMergeRec } from "../utils"; 3 | 4 | import { ParseSchema } from "."; 5 | 6 | export type ParseMixedSchema = Union< 7 | RecurseOnMixedSchema, S> 8 | >; 9 | 10 | type RecurseOnMixedSchema = { 11 | stop: R; 12 | continue: T extends any[] 13 | ? RecurseOnMixedSchema< 14 | Tail, 15 | S, 16 | R | ParseSchema }>> 17 | > 18 | : never; 19 | }[T extends [any, ...any[]] ? "continue" : "stop"]; 20 | -------------------------------------------------------------------------------- /src/parse-schema/object.ts: -------------------------------------------------------------------------------- 1 | import { Object, Any, Never, Union, Error } from "../meta-types"; 2 | import { IsObject } from "../utils"; 3 | 4 | import { ParseSchema } from "."; 5 | 6 | export type ParseObjectSchema = "properties" extends keyof S 7 | ? Object< 8 | { 9 | [key in keyof S["properties"]]: ParseSchema; 10 | }, 11 | GetRequired, 12 | "additionalProperties" extends keyof S 13 | ? S["additionalProperties"] extends false 14 | ? false 15 | : true 16 | : true, 17 | GetOpenProps 18 | > 19 | : Object<{}, GetRequired, true, GetOpenProps>; 20 | 21 | type GetRequired = "required" extends keyof S 22 | ? number extends keyof S["required"] 23 | ? S["required"][number] 24 | : never 25 | : never; 26 | 27 | type GetOpenProps = "additionalProperties" extends keyof S 28 | ? "patternProperties" extends keyof S 29 | ? AdditionalAndPatternProps< 30 | S["additionalProperties"], 31 | S["patternProperties"] 32 | > 33 | : AdditionalProps 34 | : "patternProperties" extends keyof S 35 | ? PatternProps 36 | : Any; 37 | 38 | type AdditionalProps = A extends false 39 | ? Never 40 | : A extends true 41 | ? Any 42 | : IsObject extends true 43 | ? ParseSchema 44 | : Error<'Invalid value in "additionalProperties" property'>; 45 | 46 | type PatternProps

= { 47 | type: "union"; 48 | values: { 49 | [key in keyof P]: ParseSchema; 50 | }[keyof P]; 51 | }; 52 | 53 | type AdditionalAndPatternProps = A extends boolean 54 | ? Union< 55 | { 56 | [key in keyof P]: ParseSchema; 57 | }[keyof P] 58 | > 59 | : IsObject extends true 60 | ? Union< 61 | | ParseSchema 62 | | { 63 | [key in keyof P]: ParseSchema; 64 | }[keyof P] 65 | > 66 | : never; 67 | -------------------------------------------------------------------------------- /src/parse-schema/oneOf.ts: -------------------------------------------------------------------------------- 1 | import { Intersection, Union } from "../meta-types"; 2 | import { Tail, Head, Get, HasKeyIn, Merge } from "../utils"; 3 | 4 | import { ParseSchema } from "."; 5 | import { RemoveInvalidAdditionalItems } from "./utils"; 6 | 7 | export type ParseOneOfSchema = Union< 8 | RecurseOnOneOfSchema, S> 9 | >; 10 | 11 | type RecurseOnOneOfSchema = { 12 | stop: R; 13 | continue: S extends any[] 14 | ? RecurseOnOneOfSchema< 15 | Tail, 16 | P, 17 | | R 18 | | (HasKeyIn extends true 19 | ? Intersection< 20 | ParseSchema>, 21 | ParseSchema< 22 | Merge< 23 | Omit, 24 | Merge< 25 | { 26 | properties: {}; 27 | additionalProperties: true; 28 | required: []; 29 | }, 30 | RemoveInvalidAdditionalItems> 31 | > 32 | > 33 | > 34 | > 35 | : ParseSchema< 36 | Merge, RemoveInvalidAdditionalItems>> 37 | >) 38 | > 39 | : never; 40 | }[S extends [any, ...any[]] ? "continue" : "stop"]; 41 | -------------------------------------------------------------------------------- /src/parse-schema/utils.ts: -------------------------------------------------------------------------------- 1 | import { Merge } from "../utils"; 2 | 3 | export type RemoveInvalidAdditionalItems = "items" extends keyof S 4 | ? "additionalItems" extends keyof S 5 | ? S 6 | : Merge 7 | : Omit; 8 | -------------------------------------------------------------------------------- /src/utils/and.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Return `true` if `A` and `B` extend `true`, `false` otherwise 3 | * 4 | * @param A Type 5 | * @param B Type 6 | * @return Boolean 7 | */ 8 | export type And = A extends true 9 | ? B extends true 10 | ? true 11 | : false 12 | : false; 13 | -------------------------------------------------------------------------------- /src/utils/concat.ts: -------------------------------------------------------------------------------- 1 | import { Head } from "./head"; 2 | import { Tail } from "./tail"; 3 | import { Prepend } from "./prepend"; 4 | import { Reverse } from "./reverse"; 5 | 6 | /** 7 | * Concatenate the reverse of tuple `A` to tuple `B` 8 | * 9 | * @param A Tuple 10 | * @param B Tuple 11 | * @return Tuple 12 | */ 13 | export type ConcatReversed = { 14 | stop: B; 15 | continue: ConcatReversed, Prepend, B>>; 16 | }[A extends [any, ...any[]] ? "continue" : "stop"]; 17 | 18 | /** 19 | * Concatenate tuple `A` to tuple `B` 20 | * 21 | * @param A Tuple 22 | * @param B Tuple 23 | * @return Tuple 24 | */ 25 | export type Concat = A extends any[] 26 | ? ConcatReversed, B> 27 | : never; 28 | -------------------------------------------------------------------------------- /src/utils/extends.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns `true` if type `A` extends type `B`, `false` if not 3 | * 4 | * @param A Type 5 | * @param B Type 6 | * @return Boolean 7 | */ 8 | export type DoesExtend = A extends B ? true : false; 9 | 10 | type ArrayKeys = keyof []; 11 | 12 | /** 13 | * Returns `true` if type is object, `false` if not (excludes arrays) 14 | * 15 | * @param T Type 16 | * @return Boolean 17 | */ 18 | export type IsObject = T extends object 19 | ? ArrayKeys extends Extract 20 | ? false 21 | : true 22 | : false; 23 | 24 | /** 25 | * Returns `true` if type is array, `false` if not (excludes objects) 26 | * 27 | * @param T Type 28 | * @return Boolean 29 | */ 30 | export type IsArray = T extends object 31 | ? ArrayKeys extends Extract 32 | ? true 33 | : false 34 | : false; 35 | -------------------------------------------------------------------------------- /src/utils/filter.ts: -------------------------------------------------------------------------------- 1 | import { Head } from "./head"; 2 | import { Tail } from "./tail"; 3 | import { Prepend } from "./prepend"; 4 | import { Reverse } from "./reverse"; 5 | 6 | /** 7 | * Filters out the values of a tuple `T` that don't extend type `F` 8 | * 9 | * Preserves the tuple original order 10 | * 11 | * @param T Tuple 12 | * @param F Type 13 | * @return Tuple 14 | */ 15 | export type FilterExtending = { 16 | continue: FilterExtending< 17 | Tail, 18 | F, 19 | Head extends F ? Prepend, R> : R 20 | >; 21 | stop: Reverse; 22 | }[T extends [any, ...any[]] ? "continue" : "stop"]; 23 | -------------------------------------------------------------------------------- /src/utils/get.ts: -------------------------------------------------------------------------------- 1 | import { Head } from "./head"; 2 | import { Tail } from "./tail"; 3 | 4 | /** 5 | * Returns the value at key `K` in object `O`, `F` if `K` misses from object 6 | * 7 | * @param O Object 8 | * @param K Property key 9 | * @param F _(optional:_ `never` _)_ Fallback type 10 | * @return Type 11 | */ 12 | export type Get = K extends keyof O ? O[K] : F; 13 | 14 | /** 15 | * Returns the value at path `P` in object `O`, `F` if `P` misses from object 16 | * 17 | * @param O Object 18 | * @param P Path 19 | * @param F _(optional:_ `never` _)_ Fallback type 20 | * @return Type 21 | */ 22 | export type GetRec = { 23 | continue: Head

extends keyof O ? GetRec], Tail

, F> : F; 24 | stop: O; 25 | }[P extends [any, ...any[]] ? "continue" : "stop"]; 26 | -------------------------------------------------------------------------------- /src/utils/hasKeyIn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns `true` if object `O` has a property key in `K` (union), `false` if not 3 | * 4 | * @param O Object 5 | * @param K Union of property keys 6 | * @return Type 7 | */ 8 | export type HasKeyIn = Extract extends never ? false : true; 9 | -------------------------------------------------------------------------------- /src/utils/head.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the first element of a tuple `T` 3 | * 4 | * @param T Tuple 5 | * @return Type 6 | */ 7 | export type Head = T extends any[] ? T[0] : never; 8 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { And } from "./and"; 2 | export { ConcatReversed, Concat } from "./concat"; 3 | export { DoesExtend, IsObject, IsArray } from "./extends"; 4 | export { FilterExtending } from "./filter"; 5 | export { Get, GetRec } from "./get"; 6 | export { HasKeyIn } from "./hasKeyIn"; 7 | export { Head } from "./head"; 8 | export { SafeMergeRec, UnsafeMergeRec, Merge } from "./merge"; 9 | export { OptionalProps } from "./optionalProps"; 10 | export { Prepend } from "./prepend"; 11 | export { ReadonlyRec } from "./readonly"; 12 | export { Replace } from "./replace"; 13 | export { RequiredProps } from "./requiredProps"; 14 | export { Reverse } from "./reverse"; 15 | export { Tail } from "./tail"; 16 | export { WriteableRec } from "./writeable"; 17 | -------------------------------------------------------------------------------- /src/utils/merge.ts: -------------------------------------------------------------------------------- 1 | import { Concat } from "./concat"; 2 | import { IsObject, IsArray } from "./extends"; 3 | 4 | /** 5 | * Recursively merge two types `A` and `B`: 6 | * - Returns `B` if `A` and `B` are not both objects or arrays 7 | * - Recursively merge `A` and `B` properties if both are objects 8 | * - Concat `A` and `B` if both are arrays 9 | * 10 | * `UnsafeMergeRec` preserves non-required properties, but can return `never` if TS infers that `A & B = never` (which can happen if some properties are incompatible) 11 | * 12 | * @param A Type 13 | * @param B Type 14 | * @return Type 15 | */ 16 | export type UnsafeMergeRec = IsObject extends true 17 | ? IsObject extends true 18 | ? { 19 | [K in keyof (A & B)]: K extends keyof B 20 | ? K extends keyof A 21 | ? UnsafeMergeRec 22 | : B[K] 23 | : K extends keyof A 24 | ? A[K] 25 | : never; 26 | } 27 | : B 28 | : IsArray extends true 29 | ? IsArray extends true 30 | ? B extends any[] 31 | ? Concat 32 | : never 33 | : B 34 | : B; 35 | 36 | /** 37 | * Recursively merge two types `A` and `B`: 38 | * - Returns `B` if `A` and `B` are not both objects or arrays 39 | * - Recursively merge `A` and `B` properties if both are objects 40 | * - Concat `A` and `B` if both are arrays 41 | * 42 | * Contrary to `UnsafeMergeRec`, `SafeMergeRec` never returns `never`, but doesn't preserve non-required properties 43 | * 44 | * @param A Type 45 | * @param B Type 46 | * @return Type 47 | */ 48 | export type SafeMergeRec = IsObject extends true 49 | ? IsObject extends true 50 | ? { 51 | [K in keyof A | keyof B]: K extends keyof B 52 | ? K extends keyof A 53 | ? SafeMergeRec 54 | : B[K] 55 | : K extends keyof A 56 | ? A[K] 57 | : never; 58 | } 59 | : B 60 | : IsArray extends true 61 | ? IsArray extends true 62 | ? B extends any[] 63 | ? Concat 64 | : never 65 | : B 66 | : B; 67 | 68 | /** 69 | * Merge two types `A` and `B`: 70 | * - Returns `B` if `A` and `B` are not both objects 71 | * - Merge `A` and `B` properties if both are objects 72 | * - Merging is not recursive: Properties of `B` erase properties of `A` 73 | * 74 | * @param A Type 75 | * @param B Type 76 | * @return Type 77 | */ 78 | export type Merge = IsObject extends true 79 | ? IsObject extends true 80 | ? { 81 | [K in keyof A | keyof B]: K extends keyof B 82 | ? B[K] 83 | : K extends keyof A 84 | ? A[K] 85 | : never; 86 | } 87 | : B 88 | : B; 89 | -------------------------------------------------------------------------------- /src/utils/optionalProps.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extract the names of an object's optional properties 3 | * 4 | * @param O Object 5 | * @return Property names 6 | */ 7 | export type OptionalProps< 8 | O extends Record 9 | > = Exclude< 10 | { 11 | [K in keyof O]: undefined extends O[K] ? K : never; 12 | }[keyof O], 13 | undefined 14 | >; 15 | -------------------------------------------------------------------------------- /src/utils/prepend.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Inserts an element at the start of a tuple 3 | * 4 | * @param E Element (type) 5 | * @param T Tuple 6 | * @return Tuple 7 | */ 8 | export type Prepend = (( 9 | element: E, 10 | ...tail: T 11 | ) => void) extends (...tuple: infer R) => void 12 | ? R 13 | : never; 14 | -------------------------------------------------------------------------------- /src/utils/readonly.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Recursively add the `readonly` directive from an object properties or tuple items 3 | * 4 | * @param O Object / Tuple 5 | * @return Object / Tuple 6 | */ 7 | export type ReadonlyRec = O extends object 8 | ? { readonly [K in keyof O]: ReadonlyRec } 9 | : O; 10 | -------------------------------------------------------------------------------- /src/utils/replace.ts: -------------------------------------------------------------------------------- 1 | import { UnsafeMergeRec } from "./merge"; 2 | import { OptionalProps } from "./optionalProps"; 3 | import { RequiredProps } from "./requiredProps"; 4 | 5 | /** 6 | * Set a specified value to an object property or properties 7 | * 8 | * @param O Object 9 | * @param P Properties 10 | * @param V Value (type) 11 | * @return Object 12 | */ 13 | export type Replace< 14 | O extends Record, 15 | P extends keyof O, 16 | V, 17 | Req extends keyof O = RequiredProps, 18 | Opt extends keyof O = OptionalProps 19 | > = UnsafeMergeRec< 20 | UnsafeMergeRec, { [key in P & Req]: V }>, 21 | { [key in P & Opt]?: V } 22 | >; 23 | -------------------------------------------------------------------------------- /src/utils/requiredProps.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extract the names of an object's required properties 3 | * 4 | * @param O Object 5 | * @return Property names 6 | */ 7 | export type RequiredProps< 8 | O extends Record 9 | > = Exclude< 10 | { 11 | [K in keyof O]: undefined extends O[K] ? never : K; 12 | }[keyof O], 13 | undefined 14 | >; 15 | -------------------------------------------------------------------------------- /src/utils/reverse.ts: -------------------------------------------------------------------------------- 1 | import { Head } from "./head"; 2 | import { Tail } from "./tail"; 3 | import { Prepend } from "./prepend"; 4 | 5 | /** 6 | * Reverses a tuple `T` 7 | * 8 | * @param T Tuple 9 | * @return Tuple 10 | */ 11 | export type Reverse = { 12 | stop: R; 13 | continue: T extends any[] ? Reverse, Prepend, R>> : never; 14 | }[T extends [any, ...any[]] ? "continue" : "stop"]; 15 | -------------------------------------------------------------------------------- /src/utils/tail.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove the first element of a tuple 3 | * 4 | * @param T Tuple 5 | * @return Tuple 6 | */ 7 | export type Tail = T extends any[] 8 | ? ((...args: T) => void) extends (head: any, ...tail: infer R) => void 9 | ? R 10 | : T 11 | : never; 12 | -------------------------------------------------------------------------------- /src/utils/writeable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Recursively remove the `readonly` directive from an object properties or tuple items 3 | * 4 | * @param O Object / Tuple 5 | * @return Object / Tuple 6 | */ 7 | export type WriteableRec = O extends object 8 | ? { -readonly [K in keyof O]: WriteableRec } 9 | : O; 10 | -------------------------------------------------------------------------------- /tests/e2e/anyOf.test.ts: -------------------------------------------------------------------------------- 1 | import Ajv from "ajv"; 2 | 3 | import { FromSchema } from "index"; 4 | 5 | var ajv = new Ajv(); 6 | 7 | describe("AnyOf schemas", () => { 8 | describe("Boolean or String", () => { 9 | const anyOfSchema = { 10 | anyOf: [{ type: "boolean" }, { type: "string" }], 11 | } as const; 12 | 13 | type StringBoolOrNumber = FromSchema; 14 | let boolOrStringInstance: StringBoolOrNumber; 15 | 16 | it("accepts boolean or string value", () => { 17 | boolOrStringInstance = "string"; 18 | expect(ajv.validate(anyOfSchema, boolOrStringInstance)).toBe(true); 19 | 20 | boolOrStringInstance = true; 21 | expect(ajv.validate(anyOfSchema, boolOrStringInstance)).toBe(true); 22 | }); 23 | 24 | it("rejects other values", () => { 25 | // @ts-expect-error 26 | boolOrStringInstance = 42; 27 | expect(ajv.validate(anyOfSchema, boolOrStringInstance)).toBe(false); 28 | }); 29 | }); 30 | 31 | describe("Along Enum", () => { 32 | const enumSchema = { 33 | enum: ["apples", 42], 34 | anyOf: [{ type: "boolean" }, { type: "string" }, { type: "number" }], 35 | } as const; 36 | 37 | type Enum = FromSchema; 38 | let enumInstance: Enum; 39 | 40 | it("accepts enum values", () => { 41 | enumInstance = "apples"; 42 | expect(ajv.validate(enumSchema, enumInstance)).toBe(true); 43 | 44 | enumInstance = 42; 45 | expect(ajv.validate(enumSchema, enumInstance)).toBe(true); 46 | }); 47 | 48 | it("rejects other values", () => { 49 | // @ts-expect-error 50 | enumInstance = "tomatoes"; 51 | expect(ajv.validate(enumSchema, enumInstance)).toBe(false); 52 | 53 | // @ts-expect-error 54 | enumInstance = 43; 55 | expect(ajv.validate(enumSchema, enumInstance)).toBe(false); 56 | 57 | // @ts-expect-error 58 | enumInstance = true; 59 | expect(ajv.validate(enumSchema, enumInstance)).toBe(false); 60 | }); 61 | }); 62 | 63 | describe("Factored object properties", () => { 64 | describe("Open objects", () => { 65 | const objectSchema = { 66 | type: "object", 67 | properties: { bool: { type: "boolean" } }, 68 | required: ["bool"], 69 | anyOf: [ 70 | { properties: { num: { type: "number" } }, required: ["num"] }, 71 | { properties: { str: { type: "string" } } }, 72 | ], 73 | } as const; 74 | 75 | type FactoredObj = FromSchema; 76 | let objectInstance: FactoredObj; 77 | 78 | it("accepts objects matching #1", () => { 79 | objectInstance = { bool: true, num: 42 }; 80 | expect(ajv.validate(objectSchema, objectInstance)).toBe(true); 81 | }); 82 | 83 | it("accepts objects matching #2", () => { 84 | objectInstance = { bool: true }; 85 | expect(ajv.validate(objectSchema, objectInstance)).toBe(true); 86 | 87 | objectInstance = { bool: true, str: "string" }; 88 | expect(ajv.validate(objectSchema, objectInstance)).toBe(true); 89 | 90 | objectInstance = { bool: true, num: "not a number" }; 91 | expect(ajv.validate(objectSchema, objectInstance)).toBe(true); 92 | }); 93 | 94 | it("rejects objects matching neither", () => { 95 | // @ts-expect-error: Bool should be boolean 96 | objectInstance = { bool: "true" }; 97 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 98 | 99 | // @ts-expect-error: Bool is required 100 | objectInstance = { num: 42 }; 101 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 102 | 103 | // @ts-expect-error: Bool is required 104 | objectInstance = { str: "string" }; 105 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 106 | }); 107 | }); 108 | 109 | describe("Open to open object (impossible)", () => { 110 | const objectSchema = { 111 | type: "object", 112 | properties: { str: { type: "string" } }, 113 | required: ["str"], 114 | anyOf: [{ additionalProperties: { type: "boolean" } }], 115 | } as const; 116 | 117 | type FactoredObj = FromSchema; 118 | let objectInstance: FactoredObj; 119 | 120 | it("rejects object not matching child", () => { 121 | // @ts-expect-error 122 | objectInstance = { str: "str" }; 123 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 124 | }); 125 | 126 | it("rejects objects not matching parent", () => { 127 | // @ts-expect-error 128 | objectInstance = { str: true }; 129 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 130 | }); 131 | }); 132 | 133 | describe("Closed (1) to open object", () => { 134 | const objectSchema = { 135 | type: "object", 136 | properties: { bool: { type: "boolean" } }, 137 | anyOf: [ 138 | { 139 | properties: { num: { type: "number" } }, 140 | required: ["num"], 141 | additionalProperties: false, 142 | }, 143 | { properties: { str: { type: "string" } } }, 144 | ], 145 | } as const; 146 | 147 | type FactoredObj = FromSchema; 148 | let objectInstance: FactoredObj; 149 | 150 | it("accepts objects matching #1", () => { 151 | objectInstance = { num: 42 }; 152 | expect(ajv.validate(objectSchema, objectInstance)).toBe(true); 153 | }); 154 | 155 | it("accepts objects matching #2", () => { 156 | objectInstance = {}; 157 | expect(ajv.validate(objectSchema, objectInstance)).toBe(true); 158 | 159 | objectInstance = { bool: true, str: "string" }; 160 | expect(ajv.validate(objectSchema, objectInstance)).toBe(true); 161 | }); 162 | }); 163 | 164 | describe("Closed (2) to open object", () => { 165 | const objectSchema = { 166 | type: "object", 167 | properties: { bool: { type: "boolean" } }, 168 | anyOf: [ 169 | { 170 | properties: { num: { type: "number" } }, 171 | required: ["num"], 172 | additionalProperties: false, 173 | }, 174 | { 175 | properties: { str: { type: "string" } }, 176 | additionalProperties: false, 177 | }, 178 | ], 179 | } as const; 180 | 181 | type FactoredObj = FromSchema; 182 | let objectInstance: FactoredObj; 183 | 184 | it("accepts objects matching #1", () => { 185 | objectInstance = { num: 42 }; 186 | expect(ajv.validate(objectSchema, objectInstance)).toBe(true); 187 | }); 188 | 189 | it("accepts objects matching #2", () => { 190 | objectInstance = {}; 191 | expect(ajv.validate(objectSchema, objectInstance)).toBe(true); 192 | 193 | objectInstance = { str: "string" }; 194 | expect(ajv.validate(objectSchema, objectInstance)).toBe(true); 195 | }); 196 | 197 | it("rejects objects matching neither", () => { 198 | // @ts-expect-error 199 | objectInstance = { num: 42, bool: true }; 200 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 201 | 202 | // @ts-expect-error 203 | objectInstance = { str: "string", bool: true }; 204 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 205 | }); 206 | }); 207 | 208 | describe("Closed to open object (impossible 1)", () => { 209 | const objectSchema = { 210 | type: "object", 211 | properties: { bool: { type: "boolean" } }, 212 | anyOf: [ 213 | { 214 | properties: { num: { type: "number" } }, 215 | required: ["num"], 216 | additionalProperties: false, 217 | }, 218 | ], 219 | required: ["bool"], 220 | } as const; 221 | 222 | type FactoredObj = FromSchema; 223 | let objectInstance: FactoredObj; 224 | 225 | it('rejects object matching child schema as parent requires "bool" prop', () => { 226 | // @ts-expect-error 227 | objectInstance = { num: 42 }; 228 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 229 | }); 230 | 231 | it("rejects object matching parent schema as child is closed", () => { 232 | // @ts-expect-error 233 | objectInstance = { num: 42, bool: true }; 234 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 235 | }); 236 | }); 237 | 238 | describe("Closed to open object (impossible 2)", () => { 239 | const objectSchema = { 240 | type: "object", 241 | properties: { bool: { type: "boolean" } }, 242 | anyOf: [ 243 | { 244 | properties: { num: { type: "number" }, bool: { type: "string" } }, 245 | required: ["num"], 246 | additionalProperties: false, 247 | }, 248 | ], 249 | required: ["bool"], 250 | } as const; 251 | 252 | type FactoredObj = FromSchema; 253 | let objectInstance: FactoredObj; 254 | 255 | it("rejects non-boolean bool prop (required by parent)", () => { 256 | // @ts-expect-error 257 | objectInstance = { num: 42, bool: "string" }; 258 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 259 | }); 260 | 261 | it("rejects non-string bool prop (required by child)", () => { 262 | // @ts-expect-error 263 | objectInstance = { num: 42, bool: true }; 264 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 265 | }); 266 | }); 267 | 268 | describe("Open (1) to closed object", () => { 269 | const objectSchema = { 270 | type: "object", 271 | properties: { bool: { type: "boolean" } }, 272 | anyOf: [{ properties: { str: { type: "string" } } }], 273 | required: ["bool"], 274 | additionalProperties: false, 275 | } as const; 276 | 277 | type FactoredObj = FromSchema; 278 | let objectInstance: FactoredObj; 279 | 280 | it("accepts valid object", () => { 281 | objectInstance = { bool: true }; 282 | expect(ajv.validate(objectSchema, objectInstance)).toBe(true); 283 | }); 284 | 285 | it("rejects invalid object", () => { 286 | // @ts-expect-error: "bool" prop is required by parent 287 | objectInstance = { str: "str" }; 288 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 289 | 290 | // @ts-expect-error: "str" is not allowed as additional property 291 | objectInstance = { bool: true, str: "str" }; 292 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 293 | }); 294 | }); 295 | 296 | describe("Open (2) to closed object", () => { 297 | const objectSchema = { 298 | type: "object", 299 | properties: { bool: { type: "boolean" } }, 300 | anyOf: [ 301 | { properties: { str: { type: "string" } }, required: ["str"] }, 302 | { properties: { num: { type: "number" } } }, 303 | ], 304 | additionalProperties: false, 305 | } as const; 306 | 307 | type FactoredObj = FromSchema; 308 | let objectInstance: FactoredObj; 309 | 310 | it("accepts objects matching #2", () => { 311 | objectInstance = {}; 312 | expect(ajv.validate(objectSchema, objectInstance)).toBe(true); 313 | 314 | objectInstance = { bool: true }; 315 | expect(ajv.validate(objectSchema, objectInstance)).toBe(true); 316 | }); 317 | 318 | it("rejects objects matching #1 as parent is closed", () => { 319 | // @ts-expect-error: "str" is not allowed as additionalProperty 320 | objectInstance = { str: "string" }; 321 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 322 | 323 | // @ts-expect-error: event with bool present 324 | objectInstance = { bool: true, str: "string" }; 325 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 326 | }); 327 | 328 | it("rejects objects matching neither", () => { 329 | // @ts-expect-error: "num" is not allowed as additionalProperty 330 | objectInstance = { num: 42 }; 331 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 332 | 333 | // @ts-expect-error: event with bool present 334 | objectInstance = { bool: true, num: 42 }; 335 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 336 | }); 337 | }); 338 | 339 | describe("Open to closed object (impossible)", () => { 340 | const objectSchema = { 341 | type: "object", 342 | properties: { bool: { type: "boolean" } }, 343 | anyOf: [{ properties: { str: { type: "string" } }, required: ["str"] }], 344 | required: ["bool"], 345 | additionalProperties: false, 346 | } as const; 347 | 348 | type FactoredObj = FromSchema; 349 | let objectInstance: FactoredObj; 350 | 351 | it('rejects object having "str" property as parent is closed', () => { 352 | // @ts-expect-error 353 | objectInstance = { bool: true, str: "str" }; 354 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 355 | }); 356 | 357 | it('rejects object not having "str" property as it is required by child', () => { 358 | // @ts-expect-error 359 | objectInstance = { bool: true }; 360 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 361 | }); 362 | 363 | it('rejects object not having "bool" property as it is required by parent', () => { 364 | // @ts-expect-error: Parent requires 'bool' property 365 | objectInstance = { str: "str" }; 366 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 367 | }); 368 | }); 369 | 370 | describe("Closed to closed object (impossible)", () => { 371 | const objectSchema = { 372 | type: "object", 373 | properties: { bool: { type: "boolean" } }, 374 | anyOf: [ 375 | { 376 | properties: { str: { type: "string" } }, 377 | additionalProperties: false, 378 | }, 379 | ], 380 | required: ["bool"], 381 | additionalProperties: false, 382 | } as const; 383 | 384 | type FactoredObj = FromSchema; 385 | let objectInstance: FactoredObj; 386 | 387 | it('rejects object with "bool" property as child is closed', () => { 388 | // @ts-expect-error 389 | objectInstance = { bool: true }; 390 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 391 | }); 392 | 393 | it('rejects object with "str" property as parent is closed', () => { 394 | // @ts-expect-error 395 | objectInstance = { str: "str" }; 396 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 397 | }); 398 | 399 | it("rejects object with both at the time", () => { 400 | // @ts-expect-error 401 | objectInstance = { bool: true, str: "str" }; 402 | expect(ajv.validate(objectSchema, objectInstance)).toBe(false); 403 | }); 404 | }); 405 | }); 406 | 407 | describe("Factored tuple properties", () => { 408 | describe("Open tuples", () => { 409 | const tupleSchema = { 410 | type: "array", 411 | items: [{ type: "string" }], 412 | anyOf: [ 413 | { items: [{ const: "num" }, { type: "number" }] }, 414 | { items: [{ const: "bool" }, { type: "boolean" }] }, 415 | ], 416 | } as const; 417 | 418 | type Tuple = FromSchema; 419 | let tupleInstance: Tuple; 420 | 421 | it("accepts tuples matching #1", () => { 422 | tupleInstance = ["num"]; 423 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 424 | 425 | tupleInstance = ["num", 42]; 426 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 427 | }); 428 | 429 | it("accepts tuples matching #2", () => { 430 | tupleInstance = ["bool"]; 431 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 432 | 433 | tupleInstance = ["bool", true]; 434 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 435 | }); 436 | 437 | it("rejects tuples matching neither", () => { 438 | // @ts-expect-error: First item should be "num"/"bool" 439 | tupleInstance = ["not num/bool"]; 440 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 441 | 442 | // @ts-expect-error: Second item should be number 443 | tupleInstance = ["num", true]; 444 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 445 | 446 | // @ts-expect-error: Second item should be bool 447 | tupleInstance = ["bool", 42]; 448 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 449 | }); 450 | }); 451 | 452 | describe("Open tuples (2)", () => { 453 | const tupleSchema = { 454 | type: "array", 455 | items: [{ type: "string" }], 456 | additionalItems: { type: "boolean" }, 457 | anyOf: [ 458 | { items: [{ const: "num" }, { type: "number" }] }, 459 | { items: [{ const: "bool" }, { type: "boolean" }] }, 460 | ], 461 | } as const; 462 | 463 | type Tuple = FromSchema; 464 | let tupleInstance: Tuple; 465 | 466 | it("accepts tuples matching #1", () => { 467 | tupleInstance = ["num"]; 468 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 469 | }); 470 | 471 | it("accepts tuples matching #2", () => { 472 | tupleInstance = ["bool"]; 473 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 474 | 475 | tupleInstance = ["bool", true]; 476 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 477 | }); 478 | 479 | it("rejects tuples not matching parent", () => { 480 | // @ts-expect-error: Second item cannot exist (should be bool AND number) 481 | tupleInstance = ["num", 42]; 482 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 483 | }); 484 | }); 485 | 486 | describe("Open tuples (3)", () => { 487 | const tupleSchema = { 488 | type: "array", 489 | items: [{ type: "string" }], 490 | additionalItems: { type: "boolean" }, 491 | anyOf: [ 492 | { 493 | items: [{ type: "string" }], 494 | additionalItems: { type: "number" }, 495 | }, 496 | ], 497 | } as const; 498 | 499 | type Tuple = FromSchema; 500 | let tupleInstance: Tuple; 501 | 502 | it("accepts tuples matching parent and child", () => { 503 | tupleInstance = ["str"]; 504 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 505 | }); 506 | 507 | it("rejects tuples with additional items as child and parent don't match", () => { 508 | // @ts-expect-error: Second item cannot exist (should be bool AND number) 509 | tupleInstance = ["num", 42]; 510 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 511 | 512 | // @ts-expect-error: Second item cannot exist (should be bool AND number) 513 | tupleInstance = ["num", true]; 514 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 515 | }); 516 | }); 517 | 518 | describe("Closed (1) to open tuple", () => { 519 | const tupleSchema = { 520 | type: "array", 521 | items: [{ type: "string" }], 522 | anyOf: [ 523 | { items: [{ const: "stop" }], additionalItems: false }, 524 | { items: [{ const: "continue" }] }, 525 | ], 526 | } as const; 527 | 528 | type Tuple = FromSchema; 529 | let tupleInstance: Tuple; 530 | 531 | it("accepts tuples matching #1", () => { 532 | tupleInstance = ["stop"]; 533 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 534 | }); 535 | 536 | it("accepts tuples matching #2", () => { 537 | tupleInstance = ["continue"]; 538 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 539 | 540 | tupleInstance = ["continue", { any: "value" }]; 541 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 542 | }); 543 | 544 | it("rejects tuples matching neither", () => { 545 | // @ts-expect-error: First item should be "stop"/"continue" 546 | tupleInstance = ["invalid value"]; 547 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 548 | 549 | // @ts-expect-error: Second item is denied 550 | tupleInstance = ["stop", { any: "value" }]; 551 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 552 | }); 553 | }); 554 | 555 | describe("Closed (2) to open tuple", () => { 556 | const tupleSchema = { 557 | type: "array", 558 | items: [{ type: "string" }], 559 | anyOf: [ 560 | { 561 | items: [{ const: "num" }, { type: "number" }], 562 | additionalItems: false, 563 | minItems: 2, 564 | }, 565 | { 566 | items: [{ const: "bool" }, { type: "boolean" }], 567 | additionalItems: false, 568 | minItems: 1, 569 | }, 570 | ], 571 | } as const; 572 | 573 | type FactoredTuple = FromSchema; 574 | let tupleInstance: FactoredTuple; 575 | 576 | it("accepts tuples matching #1", () => { 577 | tupleInstance = ["num", 42]; 578 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 579 | }); 580 | 581 | it("accepts tuples matching #2", () => { 582 | tupleInstance = ["bool"]; 583 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 584 | 585 | tupleInstance = ["bool", true]; 586 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 587 | }); 588 | 589 | it("rejects tuples matching neither", () => { 590 | // @ts-expect-error: Third item is denied 591 | tupleInstance = ["num", 42, "additional item"]; 592 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 593 | 594 | // @ts-expect-error: Third item is denied 595 | tupleInstance = ["bool", true, "additional item"]; 596 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 597 | }); 598 | }); 599 | 600 | describe("Closed to open tuple (impossible 1)", () => { 601 | const tupleSchema = { 602 | type: "array", 603 | items: [{ type: "number" }, { type: "number" }], 604 | minItems: 2, 605 | anyOf: [{ items: [{ type: "number" }], additionalItems: false }], 606 | } as const; 607 | 608 | type FactoredTuple = FromSchema; 609 | let tupleInstance: FactoredTuple; 610 | 611 | it("rejects tuple matching child schema as parent requires 2 items", () => { 612 | // @ts-expect-error 613 | tupleInstance = [0]; 614 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 615 | }); 616 | 617 | it("rejects tuple matching parent schema as child is closed", () => { 618 | // @ts-expect-error 619 | tupleInstance = [0, 1]; 620 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 621 | }); 622 | }); 623 | 624 | describe("Closed to open tuple (impossible 2)", () => { 625 | const tupleSchema = { 626 | type: "array", 627 | items: [{ type: "string" }, { type: "boolean" }], 628 | minItems: 2, 629 | anyOf: [ 630 | { 631 | items: [{ type: "string" }, { type: "number" }], 632 | additionalItems: false, 633 | }, 634 | ], 635 | } as const; 636 | 637 | type FactoredTuple = FromSchema; 638 | let tupleInstance: FactoredTuple; 639 | 640 | it("rejects non-boolean second item (required by parent)", () => { 641 | // @ts-expect-error 642 | tupleInstance = ["string", 42]; 643 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 644 | }); 645 | 646 | it("rejects non-number second item (required by child)", () => { 647 | // @ts-expect-error 648 | tupleInstance = ["string", true]; 649 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 650 | }); 651 | }); 652 | 653 | describe("Open (1) to closed tuple", () => { 654 | const tupleSchema = { 655 | type: "array", 656 | items: [{ type: "string" }], 657 | minItems: 1, 658 | additionalItems: false, 659 | anyOf: [ 660 | { items: [{ const: "apple" }] }, 661 | { items: [{ enum: ["tomato", "banana"] }] }, 662 | ], 663 | } as const; 664 | 665 | type FactoredTuple = FromSchema; 666 | let tupleInstance: FactoredTuple; 667 | 668 | it("accepts valid tuple", () => { 669 | tupleInstance = ["apple"]; 670 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 671 | 672 | tupleInstance = ["tomato"]; 673 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 674 | 675 | tupleInstance = ["banana"]; 676 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 677 | }); 678 | 679 | it("rejects invalid tuple", () => { 680 | // @ts-expect-error: One item is required by parent 681 | tupleInstance = []; 682 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 683 | 684 | // @ts-expect-error: Additional items are denied 685 | tupleInstance = ["apple", "tomato"]; 686 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 687 | }); 688 | }); 689 | 690 | describe("Open (2) to closed tuple", () => { 691 | const tupleSchema = { 692 | type: "array", 693 | items: [{ type: "string" }], 694 | additionalItems: false, 695 | anyOf: [ 696 | { items: [{ const: "several" }, { const: "items" }], minItems: 2 }, 697 | { items: [{ const: "only one" }] }, 698 | ], 699 | } as const; 700 | 701 | type FactoredTuple = FromSchema; 702 | let tupleInstance: FactoredTuple; 703 | 704 | it("accepts tuples matching #2", () => { 705 | tupleInstance = []; 706 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 707 | 708 | tupleInstance = ["only one"]; 709 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 710 | }); 711 | 712 | it("rejects tuples matching #1 as parent is closed", () => { 713 | // @ts-expect-error: Second item is not allowed as additionalProperty 714 | tupleInstance = ["several", "items"]; 715 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 716 | 717 | // @ts-expect-error: second item is missing 718 | tupleInstance = ["several"]; 719 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 720 | }); 721 | }); 722 | 723 | describe("Open (3) to closed tuple", () => { 724 | const tupleSchema = { 725 | type: "array", 726 | items: [{ type: "string" }, { type: "boolean" }], 727 | minItems: 2, 728 | additionalItems: false, 729 | anyOf: [{ items: [{ const: "can have additionalItems" }] }], 730 | } as const; 731 | 732 | type FactoredTuple = FromSchema; 733 | let tupleInstance: FactoredTuple; 734 | 735 | it("accepts tuples matching parent and child", () => { 736 | tupleInstance = ["can have additionalItems", true]; 737 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 738 | }); 739 | 740 | it("rejects invalid tuples", () => { 741 | // @ts-expect-error: Second item is required by parent 742 | tupleInstance = ["can have additionalItems"]; 743 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 744 | 745 | // @ts-expect-error: First item should be child's const 746 | tupleInstance = ["no child const"]; 747 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(false); 748 | }); 749 | }); 750 | 751 | describe("Open to closed tuple (impossible)", () => { 752 | const factoredTupleSchema = { 753 | type: "array", 754 | items: [{ type: "string" }, { type: "boolean" }], 755 | minItems: 2, 756 | additionalItems: false, 757 | anyOf: [ 758 | { items: [{ type: "string" }], additionalItems: { type: "number" } }, 759 | ], 760 | } as const; 761 | 762 | type FactoredTuple = FromSchema; 763 | let tupleInstance: FactoredTuple; 764 | 765 | it("rejects tuple having number second item as parent requires boolean", () => { 766 | // @ts-expect-error 767 | tupleInstance = ["str", 42]; 768 | expect(ajv.validate(factoredTupleSchema, tupleInstance)).toBe(false); 769 | }); 770 | 771 | it("rejects tuple having boolean second item as child requires number", () => { 772 | // @ts-expect-error 773 | tupleInstance = ["str", true]; 774 | expect(ajv.validate(factoredTupleSchema, tupleInstance)).toBe(false); 775 | }); 776 | 777 | it("rejects object not having second items as it is required by parent", () => { 778 | // @ts-expect-error 779 | tupleInstance = ["str"]; 780 | expect(ajv.validate(factoredTupleSchema, tupleInstance)).toBe(false); 781 | }); 782 | }); 783 | 784 | describe("Incorrect schema", () => { 785 | const tupleSchema = { 786 | type: "array", 787 | items: [{ type: "string" }], 788 | anyOf: [{ additionalItems: { type: "boolean" } }], 789 | } as const; 790 | 791 | type FactoredTuple = FromSchema; 792 | let tupleInstance: FactoredTuple; 793 | 794 | it("accepts tuples with any additional items", () => { 795 | tupleInstance = [ 796 | "can have additionalItems", 797 | 'yes, because "items" is missing in anyOf schema', 798 | ]; 799 | expect(ajv.validate(tupleSchema, tupleInstance)).toBe(true); 800 | }); 801 | }); 802 | 803 | describe("Min/max items", () => { 804 | const factoredTupleSchema = { 805 | type: "array", 806 | items: [{ type: "string" }, { type: "number" }, { type: "boolean" }], 807 | anyOf: [{ minItems: 3 }, { maxItems: 1 }], 808 | } as const; 809 | 810 | type FactoredTuple = FromSchema; 811 | let factoredTupleInstance: FactoredTuple; 812 | 813 | it("accepts tuples with <= 1 items", () => { 814 | factoredTupleInstance = []; 815 | expect(ajv.validate(factoredTupleSchema, factoredTupleInstance)).toBe( 816 | true 817 | ); 818 | 819 | factoredTupleInstance = ["0"]; 820 | expect(ajv.validate(factoredTupleSchema, factoredTupleInstance)).toBe( 821 | true 822 | ); 823 | }); 824 | 825 | it("accepts tuples with >= 3 items", () => { 826 | factoredTupleInstance = ["0", 1, true]; 827 | expect(ajv.validate(factoredTupleSchema, factoredTupleInstance)).toBe( 828 | true 829 | ); 830 | 831 | factoredTupleInstance = ["0", 1, true, "any"]; 832 | expect(ajv.validate(factoredTupleSchema, factoredTupleInstance)).toBe( 833 | true 834 | ); 835 | }); 836 | 837 | it("rejects tuples with 2 items", () => { 838 | // @ts-expect-error: Tuples should not have 2 items 839 | factoredTupleInstance = ["0", 1]; 840 | expect(ajv.validate(factoredTupleSchema, factoredTupleInstance)).toBe( 841 | false 842 | ); 843 | }); 844 | }); 845 | }); 846 | }); 847 | -------------------------------------------------------------------------------- /tests/e2e/array.test.ts: -------------------------------------------------------------------------------- 1 | import Ajv from "ajv"; 2 | 3 | import { FromSchema } from "index"; 4 | 5 | var ajv = new Ajv(); 6 | 7 | describe("Array schemas", () => { 8 | describe("Arrays", () => { 9 | describe("Any array", () => { 10 | const noItemsSchema = { type: "array" } as const; 11 | 12 | type UnknownArray = FromSchema; 13 | let anyArrayInstance: UnknownArray; 14 | 15 | it("accepts any array", () => { 16 | anyArrayInstance = []; 17 | expect(ajv.validate(noItemsSchema, anyArrayInstance)).toBe(true); 18 | 19 | anyArrayInstance = [42]; 20 | expect(ajv.validate(noItemsSchema, anyArrayInstance)).toBe(true); 21 | 22 | anyArrayInstance = [42, "foo"]; 23 | expect(ajv.validate(noItemsSchema, anyArrayInstance)).toBe(true); 24 | 25 | anyArrayInstance = [42, "foo", { description: "bar" }]; 26 | expect(ajv.validate(noItemsSchema, anyArrayInstance)).toBe(true); 27 | }); 28 | 29 | it("rejects other values", () => { 30 | // @ts-expect-error 31 | anyArrayInstance = "not an array"; 32 | expect(ajv.validate(noItemsSchema, anyArrayInstance)).toBe(false); 33 | }); 34 | }); 35 | 36 | describe("String array", () => { 37 | const stringArraySchema = { 38 | type: "array", 39 | items: { type: "string" }, 40 | } as const; 41 | 42 | type StringArray = FromSchema; 43 | let stringArrayInstance: StringArray; 44 | 45 | it("accepts string arrays", () => { 46 | stringArrayInstance = ["foo"]; 47 | expect(ajv.validate(stringArraySchema, stringArrayInstance)).toBe(true); 48 | 49 | stringArrayInstance = ["foo", "bar"]; 50 | expect(ajv.validate(stringArraySchema, stringArrayInstance)).toBe(true); 51 | }); 52 | 53 | it("rejects other values", () => { 54 | // @ts-expect-error 55 | stringArrayInstance = ["foo", "bar", { not: "a string" }]; 56 | expect(ajv.validate(stringArraySchema, stringArrayInstance)).toBe( 57 | false 58 | ); 59 | }); 60 | }); 61 | 62 | describe("Object array", () => { 63 | const fruitArraySchema = { 64 | type: "array", 65 | items: { 66 | type: "object", 67 | properties: { 68 | name: { type: "string" }, 69 | description: { type: "string" }, 70 | }, 71 | required: ["name"], 72 | }, 73 | } as const; 74 | 75 | type FruitArray = FromSchema; 76 | let fruitArrayInstance: FruitArray; 77 | 78 | it("accepts array of valid objects", () => { 79 | fruitArrayInstance = [{ name: "apple" }]; 80 | expect(ajv.validate(fruitArraySchema, fruitArrayInstance)).toBe(true); 81 | 82 | fruitArrayInstance = [ 83 | { name: "apple" }, 84 | { name: "tomato", description: "Delicious red fruit" }, 85 | ]; 86 | expect(ajv.validate(fruitArraySchema, fruitArrayInstance)).toBe(true); 87 | }); 88 | 89 | it("rejects array with invalid object", () => { 90 | fruitArrayInstance = [ 91 | // @ts-expect-error 92 | { name: "apple", description: 42 }, 93 | { name: "tomato", description: "Delicious red fruit" }, 94 | ]; 95 | expect(ajv.validate(fruitArraySchema, fruitArrayInstance)).toBe(false); 96 | }); 97 | 98 | it("rejects array with other values", () => { 99 | // @ts-expect-error 100 | fruitArrayInstance = [{ name: "apple" }, ["not", "a", "fruit"]]; 101 | expect(ajv.validate(fruitArraySchema, fruitArrayInstance)).toBe(false); 102 | }); 103 | }); 104 | }); 105 | 106 | describe("Tuples", () => { 107 | const recipeSchema = { 108 | type: "array", 109 | items: [ 110 | { type: "number" }, 111 | { type: "string" }, 112 | { 113 | type: "object", 114 | properties: { descr: { type: "string" } }, 115 | required: ["descr"], 116 | additionalProperties: false, 117 | }, 118 | ], 119 | } as const; 120 | 121 | describe("Base", () => { 122 | type Recipe = FromSchema; 123 | let recipeInstance: Recipe; 124 | 125 | it("accepts valid tuple", () => { 126 | recipeInstance = [0, "pasta", { descr: "Italian meal" }]; 127 | expect(ajv.validate(recipeSchema, recipeInstance)).toBe(true); 128 | }); 129 | 130 | it("accepts partial tuples", () => { 131 | recipeInstance = []; 132 | expect(ajv.validate(recipeSchema, recipeInstance)).toBe(true); 133 | 134 | recipeInstance = [0]; 135 | expect(ajv.validate(recipeSchema, recipeInstance)).toBe(true); 136 | 137 | recipeInstance = [0, "pasta"]; 138 | expect(ajv.validate(recipeSchema, recipeInstance)).toBe(true); 139 | }); 140 | 141 | it("accepts tuple with additional items", () => { 142 | recipeInstance = [ 143 | 1, 144 | "pizza", 145 | { descr: "Delicious pizza" }, 146 | "any value", 147 | ]; 148 | expect(ajv.validate(recipeSchema, recipeInstance)).toBe(true); 149 | }); 150 | 151 | it("rejects invalid tuple", () => { 152 | // @ts-expect-error 153 | recipeInstance = [1, 42, { descr: "Delicious pizza" }]; 154 | expect(ajv.validate(recipeSchema, recipeInstance)).toBe(false); 155 | }); 156 | 157 | it("rejects other values", () => { 158 | // @ts-expect-error 159 | recipeInstance = { not: "an array" }; 160 | expect(ajv.validate(recipeSchema, recipeInstance)).toBe(false); 161 | }); 162 | }); 163 | 164 | describe("Typed additional items", () => { 165 | const recipeSchema2 = { 166 | ...recipeSchema, 167 | additionalItems: { type: "boolean" }, 168 | } as const; 169 | 170 | type Recipe = FromSchema; 171 | let recipeInstance: Recipe; 172 | 173 | it("accepts valid tuples", () => { 174 | recipeInstance = [0, "pasta", { descr: "Italian meal" }]; 175 | expect(ajv.validate(recipeSchema2, recipeInstance)).toBe(true); 176 | 177 | recipeInstance = [1, "pizza", { descr: "Delicious pizza" }, true]; 178 | expect(ajv.validate(recipeSchema2, recipeInstance)).toBe(true); 179 | }); 180 | 181 | it("rejects invalid tuples", () => { 182 | // @ts-expect-error 183 | recipeInstance = [1, "pizza", { descr: "Pizza !" }, "not boolean"]; 184 | expect(ajv.validate(recipeSchema2, recipeInstance)).toBe(false); 185 | 186 | // @ts-expect-error 187 | recipeInstance = ["1", "pizza", { descr: "Delicious pizza" }]; 188 | expect(ajv.validate(recipeSchema2, recipeInstance)).toBe(false); 189 | }); 190 | }); 191 | 192 | describe("No additional items", () => { 193 | const recipeSchema3 = { 194 | ...recipeSchema, 195 | additionalItems: false, 196 | } as const; 197 | 198 | type Recipe = FromSchema; 199 | let recipeInstance: Recipe; 200 | 201 | it("accepts valid tuple", () => { 202 | recipeInstance = [0, "pasta", { descr: "Italian meal" }]; 203 | expect(ajv.validate(recipeSchema3, recipeInstance)).toBe(true); 204 | }); 205 | 206 | it("rejects tuple with additional items", () => { 207 | // @ts-expect-error 208 | recipeInstance = [1, "pizza", { descr: "Pizza !" }, "not allowed !"]; 209 | expect(ajv.validate(recipeSchema3, recipeInstance)).toBe(false); 210 | }); 211 | }); 212 | 213 | describe("Min items", () => { 214 | const recipeSchema4 = { 215 | ...recipeSchema, 216 | minItems: 2, 217 | additionalItems: true, 218 | } as const; 219 | 220 | type Recipe = FromSchema; 221 | let recipeInstance: Recipe; 222 | 223 | it("accepts tuples with > 2 items", () => { 224 | recipeInstance = [0, "pasta"]; 225 | expect(ajv.validate(recipeSchema4, recipeInstance)).toBe(true); 226 | 227 | recipeInstance = [0, "pasta", { descr: "Italian meal" }]; 228 | expect(ajv.validate(recipeSchema4, recipeInstance)).toBe(true); 229 | 230 | recipeInstance = [0, "pasta", { descr: "Italian meal" }, "any value"]; 231 | expect(ajv.validate(recipeSchema4, recipeInstance)).toBe(true); 232 | }); 233 | 234 | it("rejects tuples with < 2 items", () => { 235 | // @ts-expect-error 236 | recipeInstance = []; 237 | expect(ajv.validate(recipeSchema4, recipeInstance)).toBe(false); 238 | 239 | // @ts-expect-error 240 | recipeInstance = [0]; 241 | expect(ajv.validate(recipeSchema4, recipeInstance)).toBe(false); 242 | }); 243 | }); 244 | 245 | describe("Min & Max items", () => { 246 | const recipeSchema5 = { 247 | ...recipeSchema, 248 | minItems: 2, 249 | maxItems: 3, 250 | additionalItems: { type: "boolean" }, 251 | } as const; 252 | 253 | type Recipe = FromSchema; 254 | let recipeInstance: Recipe; 255 | 256 | it("accepts tuples with 2 or 3 items", () => { 257 | recipeInstance = [0, "pasta"]; 258 | expect(ajv.validate(recipeSchema5, recipeInstance)).toBe(true); 259 | 260 | recipeInstance = [0, "pasta", { descr: "Italian meal" }]; 261 | expect(ajv.validate(recipeSchema5, recipeInstance)).toBe(true); 262 | }); 263 | 264 | it("rejects tuples with < 2 items", () => { 265 | // @ts-expect-error 266 | recipeInstance = []; 267 | expect(ajv.validate(recipeSchema5, recipeInstance)).toBe(false); 268 | 269 | // @ts-expect-error 270 | recipeInstance = [0]; 271 | expect(ajv.validate(recipeSchema5, recipeInstance)).toBe(false); 272 | }); 273 | 274 | it("rejects tuples with > 3 items", () => { 275 | // @ts-expect-error 276 | recipeInstance = [0, "pasta", { descr: "Italian meal" }, true]; 277 | expect(ajv.validate(recipeSchema5, recipeInstance)).toBe(false); 278 | }); 279 | }); 280 | 281 | describe("Min, Max & Additional items", () => { 282 | const recipeSchema6 = { 283 | ...recipeSchema, 284 | minItems: 3, 285 | maxItems: 5, 286 | additionalItems: { type: "boolean" }, 287 | } as const; 288 | 289 | type Recipe = FromSchema; 290 | let recipeInstance: Recipe; 291 | 292 | it("accepts valid tuples", () => { 293 | recipeInstance = [0, "pasta", { descr: "Italian meal" }]; 294 | expect(ajv.validate(recipeSchema6, recipeInstance)).toBe(true); 295 | 296 | recipeInstance = [0, "pasta", { descr: "Italian meal" }, true]; 297 | expect(ajv.validate(recipeSchema6, recipeInstance)).toBe(true); 298 | }); 299 | 300 | it("rejects tuples with > 5 items", () => { 301 | // Error is not raised at the moment. Probably can be done in the future though... 302 | recipeInstance = [ 303 | 0, 304 | "pasta", 305 | { descr: "Italian meal" }, 306 | true, 307 | false, 308 | true, 309 | ]; 310 | expect(ajv.validate(recipeSchema6, recipeInstance)).toBe(false); 311 | }); 312 | 313 | it("rejects tuples with < 3 items", () => { 314 | // @ts-expect-error 315 | recipeInstance = [0, "pasta"]; 316 | expect(ajv.validate(recipeSchema6, recipeInstance)).toBe(false); 317 | }); 318 | 319 | it("rejects tuples with invalid additional items", () => { 320 | // @ts-expect-error 321 | recipeInstance = [0, "pasta", { descr: "Italian meal" }, "not bool"]; 322 | expect(ajv.validate(recipeSchema6, recipeInstance)).toBe(false); 323 | }); 324 | }); 325 | 326 | describe("Min items, no Additional items", () => { 327 | const recipeSchema7 = { 328 | ...recipeSchema, 329 | minItems: 2, 330 | maxItems: 5, 331 | additionalItems: false, 332 | } as const; 333 | 334 | type Recipe = FromSchema; 335 | let recipeInstance: Recipe; 336 | 337 | it("accepts tuples with > 2 items", () => { 338 | recipeInstance = [0, "pasta"]; 339 | expect(ajv.validate(recipeSchema7, recipeInstance)).toBe(true); 340 | 341 | recipeInstance = [0, "pasta", { descr: "Italian meal" }]; 342 | expect(ajv.validate(recipeSchema7, recipeInstance)).toBe(true); 343 | }); 344 | 345 | it("rejects tuples with < 2 items", () => { 346 | // @ts-expect-error 347 | recipeInstance = [0]; 348 | expect(ajv.validate(recipeSchema7, recipeInstance)).toBe(false); 349 | }); 350 | 351 | it("rejects tuple with invalid additional item", () => { 352 | // @ts-expect-error 353 | recipeInstance = [0, "pasta", { descr: "Italian meal" }, "any"]; 354 | expect(ajv.validate(recipeSchema7, recipeInstance)).toBe(false); 355 | }); 356 | }); 357 | 358 | describe("Max items", () => { 359 | const recipeSchema8 = { 360 | ...recipeSchema, 361 | maxItems: 2, 362 | } as const; 363 | 364 | type Recipe = FromSchema; 365 | let recipeInstance: Recipe; 366 | 367 | it("accepts tuples with < 2 items", () => { 368 | recipeInstance = []; 369 | expect(ajv.validate(recipeSchema8, recipeInstance)).toBe(true); 370 | 371 | recipeInstance = [0]; 372 | expect(ajv.validate(recipeSchema8, recipeInstance)).toBe(true); 373 | 374 | recipeInstance = [0, "pasta"]; 375 | expect(ajv.validate(recipeSchema8, recipeInstance)).toBe(true); 376 | }); 377 | 378 | it("rejects tuples with > 2 items", () => { 379 | // @ts-expect-error 380 | recipeInstance = [0, "pasta", { descr: "Italian meal" }]; 381 | expect(ajv.validate(recipeSchema8, recipeInstance)).toBe(false); 382 | }); 383 | }); 384 | 385 | describe("Min item > tuple length", () => { 386 | const recipeSchema9 = { 387 | ...recipeSchema, 388 | minItems: 4, 389 | } as const; 390 | 391 | type Recipe = FromSchema; 392 | let recipeInstance: Recipe; 393 | 394 | it("accepts tuples with > 4 items", () => { 395 | recipeInstance = [ 396 | 0, 397 | "pasta", 398 | { descr: "Italian meal" }, 399 | ["any", "value"], 400 | ]; 401 | expect(ajv.validate(recipeSchema9, recipeInstance)).toBe(true); 402 | }); 403 | 404 | it("rejects tuples with < 4 items", () => { 405 | // @ts-expect-error 406 | recipeInstance = []; 407 | expect(ajv.validate(recipeSchema9, recipeInstance)).toBe(false); 408 | 409 | // @ts-expect-error 410 | recipeInstance = [0]; 411 | expect(ajv.validate(recipeSchema9, recipeInstance)).toBe(false); 412 | 413 | // @ts-expect-error 414 | recipeInstance = [0, "pasta"]; 415 | expect(ajv.validate(recipeSchema9, recipeInstance)).toBe(false); 416 | 417 | // Error is not raised at the moment. Probably can be done in the future though... 418 | recipeInstance = [0, "pasta", { descr: "Italian meal" }]; 419 | expect(ajv.validate(recipeSchema9, recipeInstance)).toBe(false); 420 | }); 421 | }); 422 | 423 | describe("Min item > closed tuple length", () => { 424 | const recipeSchema10 = { 425 | ...recipeSchema, 426 | minItems: 4, 427 | additionalItems: false, 428 | } as const; 429 | 430 | type Recipe = FromSchema; 431 | let recipeInstance: Recipe; 432 | 433 | it("rejects any tuple", () => { 434 | // @ts-expect-error 435 | recipeInstance = [0, "pasta", { descr: "Italian meal" }]; 436 | expect(ajv.validate(recipeSchema10, recipeInstance)).toBe(false); 437 | 438 | // @ts-expect-error 439 | recipeInstance = [0, "pasta", { descr: "Italian meal" }, "any"]; 440 | expect(ajv.validate(recipeSchema10, recipeInstance)).toBe(false); 441 | }); 442 | }); 443 | }); 444 | }); 445 | -------------------------------------------------------------------------------- /tests/e2e/boolean.test.ts: -------------------------------------------------------------------------------- 1 | import Ajv from "ajv"; 2 | 3 | import { FromSchema } from "index"; 4 | 5 | var ajv = new Ajv(); 6 | 7 | describe("Boolean schemas", () => { 8 | const booleanSchema = { type: "boolean" } as const; 9 | 10 | type Boolean = FromSchema; 11 | let booleanInst: Boolean; 12 | 13 | it("accepts boolean value", () => { 14 | booleanInst = true; 15 | expect(ajv.validate(booleanSchema, booleanInst)).toBe(true); 16 | 17 | booleanInst = false; 18 | expect(ajv.validate(booleanSchema, booleanInst)).toBe(true); 19 | }); 20 | 21 | it("rejects other values", () => { 22 | // @ts-expect-error 23 | booleanInst = "true"; 24 | expect(ajv.validate(booleanSchema, booleanInst)).toBe(false); 25 | 26 | // @ts-expect-error 27 | booleanInst = 42; 28 | expect(ajv.validate(booleanSchema, booleanInst)).toBe(false); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/e2e/const.test.ts: -------------------------------------------------------------------------------- 1 | import Ajv from "ajv"; 2 | 3 | import { FromSchema } from "index"; 4 | 5 | var ajv = new Ajv(); 6 | 7 | describe("Const schemas", () => { 8 | describe("Null", () => { 9 | const null1Schema = { 10 | const: null, 11 | } as const; 12 | 13 | const null2Schema = { 14 | type: "null", 15 | const: null, 16 | } as const; 17 | 18 | type Null1 = FromSchema; 19 | let null1Instance: Null1; 20 | 21 | type Null2 = FromSchema; 22 | let null2Instance: Null2; 23 | 24 | it("accepts null", () => { 25 | null1Instance = null; 26 | expect(ajv.validate(null1Schema, null1Instance)).toBe(true); 27 | 28 | null2Instance = null; 29 | expect(ajv.validate(null2Schema, null2Instance)).toBe(true); 30 | }); 31 | 32 | it("rejects other values", () => { 33 | // @ts-expect-error 34 | null1Instance = "not null"; 35 | expect(ajv.validate(null1Schema, null1Instance)).toBe(false); 36 | 37 | // @ts-expect-error 38 | null2Instance = 42; 39 | expect(ajv.validate(null2Schema, null2Instance)).toBe(false); 40 | }); 41 | 42 | it('returns never if "type" and "const" are incompatible', () => { 43 | const invalidSchema = { 44 | type: "string", 45 | const: null, 46 | } as const; 47 | 48 | type Never = FromSchema; 49 | let neverInstance: Never; 50 | 51 | // @ts-expect-error 52 | neverInstance = null; 53 | expect(ajv.validate(false, neverInstance)).toBe(false); 54 | 55 | // @ts-expect-error 56 | neverInstance = true; 57 | expect(ajv.validate(false, neverInstance)).toBe(false); 58 | 59 | // @ts-expect-error 60 | neverInstance = "string"; 61 | expect(ajv.validate(false, neverInstance)).toBe(false); 62 | 63 | // @ts-expect-error 64 | neverInstance = 42; 65 | expect(ajv.validate(false, neverInstance)).toBe(false); 66 | 67 | // @ts-expect-error 68 | neverInstance = { foo: "bar" }; 69 | expect(ajv.validate(false, neverInstance)).toBe(false); 70 | 71 | // @ts-expect-error 72 | neverInstance = ["foo", "bar"]; 73 | expect(ajv.validate(false, neverInstance)).toBe(false); 74 | }); 75 | }); 76 | }); 77 | 78 | describe("Boolean", () => { 79 | it("only validates true", () => { 80 | const trueSchema = { 81 | const: true, 82 | } as const; 83 | 84 | type True = FromSchema; 85 | let trueInstance: True; 86 | 87 | trueInstance = true; 88 | expect(ajv.validate(trueSchema, trueInstance)).toBe(true); 89 | 90 | // @ts-expect-error 91 | trueInstance = false; 92 | expect(ajv.validate(trueSchema, trueInstance)).toBe(false); 93 | 94 | // @ts-expect-error 95 | trueInstance = "true"; 96 | expect(ajv.validate(trueSchema, trueInstance)).toBe(false); 97 | }); 98 | 99 | it("should only validate false (with added type)", () => { 100 | const falseSchema = { 101 | type: "boolean", 102 | const: false, 103 | } as const; 104 | 105 | type False = FromSchema; 106 | let falseInstance: False; 107 | 108 | falseInstance = false; 109 | expect(ajv.validate(falseSchema, falseInstance)).toBe(true); 110 | 111 | // @ts-expect-error 112 | falseInstance = true; 113 | expect(ajv.validate(falseSchema, falseInstance)).toBe(false); 114 | 115 | // @ts-expect-error 116 | falseInstance = "false"; 117 | expect(ajv.validate(falseSchema, falseInstance)).toBe(false); 118 | }); 119 | }); 120 | 121 | describe("String", () => { 122 | const applesSchema = { 123 | const: "apples", 124 | } as const; 125 | 126 | type Apples = FromSchema; 127 | let applesInstance: Apples; 128 | 129 | it("accepts 'apples'", () => { 130 | applesInstance = "apples"; 131 | expect(ajv.validate(applesSchema, applesInstance)).toBe(true); 132 | }); 133 | 134 | it("rejects invalid string", () => { 135 | // @ts-expect-error 136 | applesInstance = "tomatoes"; 137 | expect(ajv.validate(applesSchema, applesInstance)).toBe(false); 138 | }); 139 | 140 | it("rejects other values", () => { 141 | // @ts-expect-error 142 | applesInstance = 42; 143 | expect(ajv.validate(applesSchema, applesInstance)).toBe(false); 144 | }); 145 | 146 | const tomatoesSchema = { 147 | type: "string", 148 | const: "tomatoes", 149 | } as const; 150 | 151 | type Tomatoes = FromSchema; 152 | let tomatoesInstance: Tomatoes; 153 | 154 | it("accepts 'tomatoes' (with added type)", () => { 155 | tomatoesInstance = "tomatoes"; 156 | expect(ajv.validate(tomatoesSchema, tomatoesInstance)).toBe(true); 157 | }); 158 | 159 | it("rejects invalid string (with added type)", () => { 160 | // @ts-expect-error 161 | tomatoesInstance = "apples"; 162 | expect(ajv.validate(tomatoesSchema, tomatoesInstance)).toBe(false); 163 | }); 164 | }); 165 | 166 | describe("Integer", () => { 167 | const fortyTwoSchema = { 168 | const: 42, 169 | } as const; 170 | 171 | type FortyTwo = FromSchema; 172 | let fortyTwoInstance: FortyTwo; 173 | 174 | it("accepts 42", () => { 175 | fortyTwoInstance = 42; 176 | expect(ajv.validate(fortyTwoSchema, fortyTwoInstance)).toBe(true); 177 | }); 178 | 179 | it("rejects other number", () => { 180 | // @ts-expect-error 181 | fortyTwoInstance = 43; 182 | expect(ajv.validate(fortyTwoSchema, fortyTwoInstance)).toBe(false); 183 | }); 184 | 185 | it("rejects other values", () => { 186 | // @ts-expect-error 187 | fortyTwoInstance = "42"; 188 | expect(ajv.validate(fortyTwoSchema, fortyTwoInstance)).toBe(false); 189 | }); 190 | }); 191 | 192 | describe("Object", () => { 193 | const dogoSchema = { 194 | const: { name: "Dogo", age: 13, hobbies: ["barking", "urinating"] }, 195 | } as const; 196 | 197 | type Dogo = FromSchema; 198 | let dogoInstance: Dogo; 199 | 200 | it("accepts correct object", () => { 201 | dogoInstance = { name: "Dogo", age: 13, hobbies: ["barking", "urinating"] }; 202 | expect(ajv.validate(dogoSchema, dogoInstance)).toBe(true); 203 | }); 204 | 205 | it("rejects invalid object", () => { 206 | // @ts-expect-error 207 | dogoInstance = { name: "Doga", age: 13, hobbies: ["barking", "urinating"] }; 208 | expect(ajv.validate(dogoSchema, dogoInstance)).toBe(false); 209 | }); 210 | }); 211 | 212 | describe("Array", () => { 213 | const pizzaRecipe = [ 214 | 1, 215 | "pizza", 216 | { description: "A delicious pizza" }, 217 | ["tomatoes", "cheese"], 218 | ] as [ 219 | 1, 220 | "pizza", 221 | { description: "A delicious pizza" }, 222 | ["tomatoes", "cheese"] 223 | ]; 224 | 225 | const pizzaRecipeSchema = { 226 | const: pizzaRecipe, 227 | } as const; 228 | 229 | type PizzaRecipe = FromSchema; 230 | let pizzaRecipeInstance: PizzaRecipe; 231 | 232 | it("accepts valid array", () => { 233 | pizzaRecipeInstance = pizzaRecipe; 234 | expect(ajv.validate(pizzaRecipeSchema, pizzaRecipeInstance)).toBe(true); 235 | }); 236 | 237 | it("rejects invalid array", () => { 238 | pizzaRecipeInstance = [ 239 | // @ts-expect-error 240 | 2, 241 | "pizza", 242 | { description: "A delicious pizza" }, 243 | ["tomatoes", "cheese"], 244 | ]; 245 | expect(ajv.validate(pizzaRecipeSchema, pizzaRecipeInstance)).toBe(false); 246 | }); 247 | }); 248 | -------------------------------------------------------------------------------- /tests/e2e/enum.test.ts: -------------------------------------------------------------------------------- 1 | import Ajv from "ajv"; 2 | 3 | import { FromSchema } from "index"; 4 | 5 | var ajv = new Ajv(); 6 | 7 | describe("Enum schemas", () => { 8 | describe("Empty enum", () => { 9 | const emptyEnumSchema = { enum: [] } as const; 10 | 11 | type Never = FromSchema; 12 | let neverInstance: Never; 13 | 14 | it("rejects any value", () => { 15 | // @ts-expect-error 16 | neverInstance = null; 17 | expect(() => ajv.validate(emptyEnumSchema, neverInstance)).toThrow(); 18 | 19 | // @ts-expect-error 20 | neverInstance = true; 21 | expect(() => ajv.validate(emptyEnumSchema, neverInstance)).toThrow(); 22 | 23 | // @ts-expect-error 24 | neverInstance = "string"; 25 | expect(() => ajv.validate(emptyEnumSchema, neverInstance)).toThrow(); 26 | 27 | // @ts-expect-error 28 | neverInstance = 42; 29 | expect(() => ajv.validate(emptyEnumSchema, neverInstance)).toThrow(); 30 | 31 | // @ts-expect-error 32 | neverInstance = { foo: "bar" }; 33 | expect(() => ajv.validate(emptyEnumSchema, neverInstance)).toThrow(); 34 | 35 | // @ts-expect-error 36 | neverInstance = ["foo", "bar"]; 37 | expect(() => ajv.validate(emptyEnumSchema, neverInstance)).toThrow(); 38 | }); 39 | }); 40 | 41 | describe("Mixed", () => { 42 | const mixedEnumSchema = { 43 | enum: [null, true, 12, "tomatoe", { name: "dogo" }, ["foo", "bar"]], 44 | } as const; 45 | 46 | type Mixed = FromSchema; 47 | let mixedInstance: Mixed; 48 | 49 | it("accepts any listed value", () => { 50 | mixedInstance = null; 51 | expect(ajv.validate(mixedEnumSchema, mixedInstance)).toBe(true); 52 | 53 | mixedInstance = true; 54 | expect(ajv.validate(mixedEnumSchema, mixedInstance)).toBe(true); 55 | 56 | mixedInstance = 12; 57 | expect(ajv.validate(mixedEnumSchema, mixedInstance)).toBe(true); 58 | 59 | mixedInstance = "tomatoe"; 60 | expect(ajv.validate(mixedEnumSchema, mixedInstance)).toBe(true); 61 | 62 | mixedInstance = { name: "dogo" }; 63 | expect(ajv.validate(mixedEnumSchema, mixedInstance)).toBe(true); 64 | 65 | mixedInstance = ["foo", "bar"]; 66 | expect(ajv.validate(mixedEnumSchema, mixedInstance)).toBe(true); 67 | }); 68 | 69 | it("rejects other values", () => { 70 | // @ts-expect-error 71 | mixedInstance = false; 72 | expect(ajv.validate(mixedEnumSchema, mixedInstance)).toBe(false); 73 | 74 | // @ts-expect-error 75 | mixedInstance = 13; 76 | expect(ajv.validate(mixedEnumSchema, mixedInstance)).toBe(false); 77 | 78 | // @ts-expect-error 79 | mixedInstance = "apples"; 80 | expect(ajv.validate(mixedEnumSchema, mixedInstance)).toBe(false); 81 | 82 | // @ts-expect-error 83 | mixedInstance = { name: "dingo" }; 84 | expect(ajv.validate(mixedEnumSchema, mixedInstance)).toBe(false); 85 | 86 | // @ts-expect-error 87 | mixedInstance = ["foo", "baz"]; 88 | expect(ajv.validate(mixedEnumSchema, mixedInstance)).toBe(false); 89 | }); 90 | }); 91 | 92 | describe("String enum", () => { 93 | const fruitEnumSchema = { 94 | type: "string", 95 | enum: ["apples", "tomatoes", "bananas", true], 96 | } as const; 97 | 98 | type Fruit = FromSchema; 99 | let fruitInstance: Fruit; 100 | 101 | it("accepts valid string", () => { 102 | fruitInstance = "apples"; 103 | expect(ajv.validate(fruitEnumSchema, fruitInstance)).toBe(true); 104 | 105 | fruitInstance = "tomatoes"; 106 | expect(ajv.validate(fruitEnumSchema, fruitInstance)).toBe(true); 107 | 108 | fruitInstance = "bananas"; 109 | expect(ajv.validate(fruitEnumSchema, fruitInstance)).toBe(true); 110 | }); 111 | 112 | it("rejects other values", () => { 113 | // @ts-expect-error 114 | fruitInstance = true; 115 | expect(ajv.validate(fruitEnumSchema, true)).toBe(false); 116 | 117 | // @ts-expect-error 118 | fruitInstance = 42; 119 | expect(ajv.validate(fruitEnumSchema, true)).toBe(false); 120 | }); 121 | }); 122 | 123 | describe("Number enum", () => { 124 | const numberEnumSchema = { 125 | type: "number", 126 | enum: [13, 42, { not: "a number" }], 127 | } as const; 128 | 129 | type Number = FromSchema; 130 | let numberInstance: Number; 131 | 132 | it("accepts valid number", () => { 133 | numberInstance = 13; 134 | expect(ajv.validate(numberEnumSchema, numberInstance)).toBe(true); 135 | 136 | numberInstance = 42; 137 | expect(ajv.validate(numberEnumSchema, numberInstance)).toBe(true); 138 | }); 139 | 140 | it("rejects other values", () => { 141 | // @ts-expect-error 142 | numberInstance = { not: "a number" }; 143 | expect(ajv.validate(numberEnumSchema, numberInstance)).toBe(false); 144 | }); 145 | }); 146 | 147 | describe("Tuple enum", () => { 148 | const recipeEnumSchema = { 149 | type: "array", 150 | enum: [ 151 | [0, "pasta", { descr: "Italian meal" }], 152 | [1, "pizza", { descr: "A delicious pizza" }, ["tomatoes", "cheese"]], 153 | { not: "a recipe" }, 154 | ], 155 | } as const; 156 | 157 | type Recipe = FromSchema; 158 | let recipe: Recipe; 159 | 160 | it("accepts valid tuples", () => { 161 | recipe = [0, "pasta", { descr: "Italian meal" }]; 162 | expect(ajv.validate(recipeEnumSchema, recipe)).toBe(true); 163 | 164 | recipe = [ 165 | 1, 166 | "pizza", 167 | { descr: "A delicious pizza" }, 168 | ["tomatoes", "cheese"], 169 | ]; 170 | expect(ajv.validate(recipeEnumSchema, recipe)).toBe(true); 171 | }); 172 | 173 | it("rejects other values", () => { 174 | // @ts-expect-error 175 | recipe = { not: "a recipe" }; 176 | expect(ajv.validate(recipeEnumSchema, recipe)).toBe(false); 177 | 178 | // @ts-expect-error 179 | recipe = ["not", "a", "recipe"]; 180 | expect(ajv.validate(recipeEnumSchema, recipe)).toBe(false); 181 | }); 182 | }); 183 | 184 | describe("Object enum", () => { 185 | const catEnumSchema = { 186 | type: "object", 187 | enum: [ 188 | { name: "Garfield", age: 13 }, 189 | { name: "Billy", age: 5 }, 190 | "not a cat", 191 | ], 192 | } as const; 193 | 194 | type Cat = FromSchema; 195 | let catInstance: Cat; 196 | 197 | it("accepts valid objects", () => { 198 | catInstance = { name: "Garfield", age: 13 }; 199 | expect(ajv.validate(catEnumSchema, catInstance)).toBe(true); 200 | 201 | catInstance = { name: "Billy", age: 5 }; 202 | expect(ajv.validate(catEnumSchema, catInstance)).toBe(true); 203 | }); 204 | 205 | it("rejects invalid object", () => { 206 | // @ts-expect-error 207 | catInstance = { name: "Billy", age: 6 }; 208 | expect(ajv.validate(catEnumSchema, catInstance)).toBe(false); 209 | }); 210 | 211 | it("rejects non-object values", () => { 212 | // @ts-expect-error 213 | catInstance = "not a cat"; 214 | expect(ajv.validate(catEnumSchema, catInstance)).toBe(false); 215 | 216 | // @ts-expect-error 217 | catInstance = 42; 218 | expect(ajv.validate(catEnumSchema, catInstance)).toBe(false); 219 | }); 220 | }); 221 | 222 | describe("TS enums", () => { 223 | enum Food { 224 | Pizza = "Pizza", 225 | Tacos = "Tacos", 226 | Fries = "Fries", 227 | } 228 | 229 | it("infers correct partial enum", () => { 230 | const pizzaTacosSchema = { 231 | enum: [Food.Pizza, Food.Tacos], 232 | } as const; 233 | 234 | type PizzaTacos = FromSchema; 235 | let pizzaOrTacos: PizzaTacos; 236 | 237 | pizzaOrTacos = Food.Pizza; 238 | expect(ajv.validate(pizzaTacosSchema, pizzaOrTacos)).toBe(true); 239 | 240 | pizzaOrTacos = Food.Tacos; 241 | expect(ajv.validate(pizzaTacosSchema, pizzaOrTacos)).toBe(true); 242 | 243 | // @ts-expect-error 244 | pizzaOrTacos = Food.Fries; 245 | expect(ajv.validate(pizzaTacosSchema, pizzaOrTacos)).toBe(false); 246 | }); 247 | 248 | it("reconstructs whole enum", () => { 249 | const foodSchema = { 250 | enum: Object.values(Food), 251 | }; 252 | 253 | type FoodType = FromSchema; 254 | let food: FoodType; 255 | 256 | food = Food.Pizza; 257 | expect(ajv.validate(foodSchema, food)).toBe(true); 258 | 259 | food = Food.Tacos; 260 | expect(ajv.validate(foodSchema, food)).toBe(true); 261 | 262 | food = Food.Fries; 263 | expect(ajv.validate(foodSchema, food)).toBe(true); 264 | 265 | // @ts-expect-error 266 | food = "Not a food"; 267 | expect(ajv.validate(foodSchema, food)).toBe(false); 268 | }); 269 | }); 270 | 271 | describe("TS enums (with type)", () => { 272 | enum Food { 273 | Pizza = "Pizza", 274 | Tacos = "Tacos", 275 | Fries = "Fries", 276 | } 277 | 278 | it("infers correct enum", () => { 279 | const foodSchema = { 280 | type: "string", 281 | enum: Object.values(Food), 282 | } as const; 283 | 284 | type FoodType = FromSchema; 285 | let food: FoodType; 286 | 287 | food = Food.Pizza; 288 | expect(ajv.validate(foodSchema, food)).toBe(true); 289 | 290 | food = Food.Tacos; 291 | expect(ajv.validate(foodSchema, food)).toBe(true); 292 | 293 | food = Food.Fries; 294 | expect(ajv.validate(foodSchema, food)).toBe(true); 295 | 296 | // @ts-expect-error 297 | food = "Not a food"; 298 | expect(ajv.validate(foodSchema, food)).toBe(false); 299 | }); 300 | }); 301 | }); 302 | -------------------------------------------------------------------------------- /tests/e2e/integer.test.ts: -------------------------------------------------------------------------------- 1 | import Ajv from "ajv"; 2 | 3 | import { FromSchema } from "index"; 4 | 5 | var ajv = new Ajv(); 6 | 7 | describe("Integer schemas", () => { 8 | const integerSchema = { type: "integer" } as const; 9 | 10 | type Number = FromSchema; 11 | let integerInstance: Number; 12 | 13 | it("accepts any integer value", () => { 14 | integerInstance = 42; 15 | expect(ajv.validate(integerSchema, integerInstance)).toBe(true); 16 | }); 17 | 18 | it("rejects other values", () => { 19 | // @ts-expect-error 20 | integerInstance = "not a number"; 21 | expect(ajv.validate(integerSchema, integerInstance)).toBe(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/e2e/mixed.test.ts: -------------------------------------------------------------------------------- 1 | import Ajv from "ajv"; 2 | 3 | import { FromSchema } from "index"; 4 | 5 | var ajv = new Ajv(); 6 | 7 | describe("Mixed types schemas", () => { 8 | describe("Primitives", () => { 9 | const simpleTypesSchema = { 10 | type: ["null", "boolean", "integer"], 11 | } as const; 12 | 13 | type Simple = FromSchema; 14 | let simpleInstance: Simple; 15 | 16 | it("accepts null value", () => { 17 | simpleInstance = null; 18 | expect(ajv.validate(simpleTypesSchema, simpleInstance)).toBe(true); 19 | }); 20 | 21 | it("accepts boolean value", () => { 22 | simpleInstance = true; 23 | expect(ajv.validate(simpleTypesSchema, simpleInstance)).toBe(true); 24 | }); 25 | 26 | it("accepts number value", () => { 27 | simpleInstance = 42; 28 | expect(ajv.validate(simpleTypesSchema, simpleInstance)).toBe(true); 29 | }); 30 | 31 | it("rejects string value", () => { 32 | // @ts-expect-error 33 | simpleInstance = "string"; 34 | expect(ajv.validate(simpleTypesSchema, simpleInstance)).toBe(false); 35 | }); 36 | 37 | it("rejects array value", () => { 38 | // @ts-expect-error 39 | simpleInstance = [null, true, 3]; 40 | expect(ajv.validate(simpleTypesSchema, simpleInstance)).toBe(false); 41 | }); 42 | }); 43 | 44 | describe("Number or array", () => { 45 | const complexTypesSchema = { 46 | type: ["number", "array"], 47 | items: { type: "string" }, 48 | } as const; 49 | 50 | type Complex = FromSchema; 51 | let complexInstance: Complex; 52 | 53 | it("accepts number value", () => { 54 | complexInstance = 42; 55 | expect(ajv.validate(complexTypesSchema, complexInstance)).toBe(true); 56 | }); 57 | 58 | it("accepts string array value", () => { 59 | complexInstance = ["apples", "tomatoes"]; 60 | expect(ajv.validate(complexTypesSchema, complexInstance)).toBe(true); 61 | }); 62 | 63 | it("rejects string or number array value", () => { 64 | // @ts-expect-error 65 | complexInstance = ["apples", 42]; 66 | expect(ajv.validate(complexTypesSchema, complexInstance)).toBe(false); 67 | }); 68 | 69 | it("rejects other value", () => { 70 | // @ts-expect-error 71 | complexInstance = { not: "a number", neither: ["a", "string", "array"] }; 72 | expect(ajv.validate(complexTypesSchema, complexInstance)).toBe(false); 73 | }); 74 | }); 75 | 76 | describe("Tuple or object", () => { 77 | const uberComplexTypesSchema = { 78 | type: ["array", "object"], 79 | items: [ 80 | { type: "number" }, 81 | { type: "string" }, 82 | { 83 | type: "object", 84 | properties: { descr: { type: "string" } }, 85 | required: ["descr"], 86 | }, 87 | ], 88 | additionalItems: false, 89 | properties: { name: { type: "string" }, description: { type: "string" } }, 90 | required: ["name"], 91 | } as const; 92 | 93 | type UberComplex = FromSchema; 94 | let uberComplexInstance: UberComplex; 95 | 96 | it("accepts object with required & valid properties", () => { 97 | uberComplexInstance = { name: "Garfield" }; 98 | expect(ajv.validate(uberComplexTypesSchema, uberComplexInstance)).toBe( 99 | true 100 | ); 101 | 102 | uberComplexInstance = { name: "Garfield", description: "a cool cat" }; 103 | expect(ajv.validate(uberComplexTypesSchema, uberComplexInstance)).toBe( 104 | true 105 | ); 106 | }); 107 | 108 | it("rejects object with invalid property", () => { 109 | // @ts-expect-error 110 | uberComplexInstance = { name: "Garfield", description: 42 }; 111 | expect(ajv.validate(uberComplexTypesSchema, uberComplexInstance)).toBe( 112 | false 113 | ); 114 | }); 115 | 116 | it("accepts tuples with valid values", () => { 117 | uberComplexInstance = []; 118 | expect(ajv.validate(uberComplexTypesSchema, uberComplexInstance)).toBe( 119 | true 120 | ); 121 | 122 | uberComplexInstance = [42]; 123 | expect(ajv.validate(uberComplexTypesSchema, uberComplexInstance)).toBe( 124 | true 125 | ); 126 | 127 | uberComplexInstance = [42, "foo"]; 128 | expect(ajv.validate(uberComplexTypesSchema, uberComplexInstance)).toBe( 129 | true 130 | ); 131 | 132 | uberComplexInstance = [42, "foo", { descr: "bar" }]; 133 | expect(ajv.validate(uberComplexTypesSchema, uberComplexInstance)).toBe( 134 | true 135 | ); 136 | }); 137 | 138 | it("rejects tuple with invalid value", () => { 139 | // @ts-expect-error 140 | uberComplexInstance = ["42", "foo", { descr: "bar" }]; 141 | expect(ajv.validate(uberComplexTypesSchema, uberComplexInstance)).toBe( 142 | false 143 | ); 144 | }); 145 | 146 | it("rejects tuple with additional items", () => { 147 | // @ts-expect-error 148 | uberComplexInstance = [42, "foo", { descr: "bar" }, "baz"]; 149 | expect(ajv.validate(uberComplexTypesSchema, uberComplexInstance)).toBe( 150 | false 151 | ); 152 | }); 153 | }); 154 | }); 155 | -------------------------------------------------------------------------------- /tests/e2e/noSchema.test.ts: -------------------------------------------------------------------------------- 1 | import Ajv from "ajv"; 2 | 3 | import { FromSchema } from "index"; 4 | 5 | var ajv = new Ajv(); 6 | 7 | describe("No schema", () => { 8 | describe("True", () => { 9 | type Unknown = FromSchema; 10 | let anyInstance: Unknown; 11 | 12 | it("accepts any value", () => { 13 | anyInstance = null; 14 | expect(ajv.validate(true, anyInstance)).toBe(true); 15 | 16 | anyInstance = true; 17 | expect(ajv.validate(true, anyInstance)).toBe(true); 18 | 19 | anyInstance = "string"; 20 | expect(ajv.validate(true, anyInstance)).toBe(true); 21 | 22 | anyInstance = 42; 23 | expect(ajv.validate(true, anyInstance)).toBe(true); 24 | 25 | anyInstance = { foo: "bar" }; 26 | expect(ajv.validate(true, anyInstance)).toBe(true); 27 | 28 | anyInstance = ["foo", "bar"]; 29 | expect(ajv.validate(true, anyInstance)).toBe(true); 30 | }); 31 | }); 32 | 33 | describe("False", () => { 34 | type Never = FromSchema; 35 | let neverInstance: Never; 36 | 37 | it("rejects any value", () => { 38 | // @ts-expect-error 39 | neverInstance = null; 40 | expect(ajv.validate(false, neverInstance)).toBe(false); 41 | 42 | // @ts-expect-error 43 | neverInstance = true; 44 | expect(ajv.validate(false, neverInstance)).toBe(false); 45 | 46 | // @ts-expect-error 47 | neverInstance = "string"; 48 | expect(ajv.validate(false, neverInstance)).toBe(false); 49 | 50 | // @ts-expect-error 51 | neverInstance = 42; 52 | expect(ajv.validate(false, neverInstance)).toBe(false); 53 | 54 | // @ts-expect-error 55 | neverInstance = { foo: "bar" }; 56 | expect(ajv.validate(false, neverInstance)).toBe(false); 57 | 58 | // @ts-expect-error 59 | neverInstance = ["foo", "bar"]; 60 | expect(ajv.validate(false, neverInstance)).toBe(false); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /tests/e2e/null.test.ts: -------------------------------------------------------------------------------- 1 | import Ajv from "ajv"; 2 | 3 | import { FromSchema } from "index"; 4 | 5 | var ajv = new Ajv(); 6 | 7 | describe("Null schema", () => { 8 | const nullSchema = { type: "null" } as const; 9 | 10 | type Null = FromSchema; 11 | let nullInstance: Null; 12 | 13 | it("accepts null const", () => { 14 | nullInstance = null; 15 | expect(ajv.validate(nullSchema, nullInstance)).toBe(true); 16 | }); 17 | 18 | it("rejects other values", () => { 19 | // @ts-expect-error 20 | nullInstance = "not null"; 21 | expect(ajv.validate(nullSchema, nullInstance)).toBe(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/e2e/number.test.ts: -------------------------------------------------------------------------------- 1 | import Ajv from "ajv"; 2 | 3 | import { FromSchema } from "index"; 4 | 5 | var ajv = new Ajv(); 6 | 7 | describe("Number schemas", () => { 8 | const numberSchema = { type: "number" } as const; 9 | 10 | type Number = FromSchema; 11 | let numberInstance: Number; 12 | 13 | it("accepts any number value", () => { 14 | numberInstance = 42; 15 | expect(ajv.validate(numberSchema, numberInstance)).toBe(true); 16 | }); 17 | 18 | it("rejects other values", () => { 19 | // @ts-expect-error 20 | numberInstance = ["not", "a", "number"]; 21 | expect(ajv.validate(numberSchema, numberInstance)).toBe(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/e2e/object.test.ts: -------------------------------------------------------------------------------- 1 | import Ajv from "ajv"; 2 | 3 | import { FromSchema } from "index"; 4 | 5 | var ajv = new Ajv(); 6 | 7 | describe("Object schemas", () => { 8 | describe("Any object", () => { 9 | const petSchema = { type: "object" } as const; 10 | 11 | type Pet = FromSchema; 12 | let petInstance: Pet; 13 | 14 | it("accepts empty object", () => { 15 | petInstance = {}; 16 | expect(ajv.validate(petSchema, petInstance)).toBe(true); 17 | }); 18 | 19 | it("accepts any object", () => { 20 | petInstance = { any: "value" }; 21 | expect(ajv.validate(petSchema, petInstance)).toBe(true); 22 | }); 23 | 24 | it("rejects other values", () => { 25 | // @ts-expect-error 26 | petInstance = ["not", "an", "object"]; 27 | expect(ajv.validate(petSchema, petInstance)).toBe(false); 28 | }); 29 | }); 30 | 31 | describe("Additional properties", () => { 32 | const setSchema = { 33 | type: "object", 34 | additionalProperties: { type: "boolean" }, 35 | } as const; 36 | 37 | type Set = FromSchema; 38 | let setInstance: Set; 39 | 40 | it("accepts object with boolean values", () => { 41 | setInstance = { a: true, b: false }; 42 | expect(ajv.validate(setSchema, setInstance)).toBe(true); 43 | }); 44 | 45 | it("rejects object with other values", () => { 46 | // @ts-expect-error 47 | setInstance = { a: 42 }; 48 | expect(ajv.validate(setSchema, setInstance)).toBe(false); 49 | }); 50 | }); 51 | 52 | describe("Pattern properties", () => { 53 | const strOrNumObjSchema = { 54 | type: "object", 55 | patternProperties: { 56 | "^S": { type: "string" }, 57 | "^I": { type: "integer" }, 58 | }, 59 | additionalProperties: false, 60 | } as const; 61 | 62 | type StrOrNumObj = FromSchema; 63 | let objInstance: StrOrNumObj; 64 | 65 | it("accepts object with str or number values", () => { 66 | objInstance = { S: "str", I: 42 }; 67 | expect(ajv.validate(strOrNumObjSchema, objInstance)).toBe(true); 68 | }); 69 | 70 | it("rejects object with boolean value", () => { 71 | // @ts-expect-error 72 | objInstance = { B: true, S: "str", I: 42 }; 73 | expect(ajv.validate(strOrNumObjSchema, objInstance)).toBe(false); 74 | }); 75 | }); 76 | 77 | describe("Additional + Pattern properties", () => { 78 | const boolStrOrNumObjSchema = { 79 | type: "object", 80 | additionalProperties: { type: "boolean" }, 81 | patternProperties: { 82 | "^S": { type: "string" }, 83 | "^I": { type: "number" }, 84 | }, 85 | } as const; 86 | 87 | type BoolStrOrNumObj = FromSchema; 88 | let objInstance: BoolStrOrNumObj; 89 | 90 | it("accepts object with str, number or boolean values", () => { 91 | objInstance = { B: true, S: "str", I: 42 }; 92 | expect(ajv.validate(boolStrOrNumObjSchema, objInstance)).toBe(true); 93 | }); 94 | 95 | it("rejects object with null value", () => { 96 | // @ts-expect-error 97 | objInstance = { B: true, S: "str", I: 42, N: null }; 98 | expect(ajv.validate(boolStrOrNumObjSchema, objInstance)).toBe(false); 99 | }); 100 | }); 101 | 102 | describe("Properties", () => { 103 | const catSchema = { 104 | type: "object", 105 | properties: { age: { type: "number" }, name: { type: "string" } }, 106 | additionalProperties: false, 107 | } as const; 108 | 109 | type Cat = FromSchema; 110 | let catInstance: Cat; 111 | 112 | it("accepts empty object", () => { 113 | catInstance = {}; 114 | expect(ajv.validate(catSchema, catInstance)).toBe(true); 115 | }); 116 | 117 | it("accept object with correct properties", () => { 118 | catInstance = { name: "Garfield", age: 42 }; 119 | expect(ajv.validate(catSchema, catInstance)).toBe(true); 120 | }); 121 | 122 | it("rejects object with additional property", () => { 123 | // @ts-expect-error 124 | catInstance = { name: "Garfield", age: 42, other: true }; 125 | expect(ajv.validate(catSchema, catInstance)).toBe(false); 126 | }); 127 | 128 | it("rejects object with invalid property", () => { 129 | // @ts-expect-error 130 | catInstance = { age: "not a number" }; 131 | expect(ajv.validate(catSchema, catInstance)).toBe(false); 132 | }); 133 | }); 134 | 135 | describe("Required", () => { 136 | const dogSchema = { 137 | type: "object", 138 | properties: { 139 | age: { type: "number" }, 140 | name: { type: "string" }, 141 | hobbies: { type: "array", items: { type: "string" } }, 142 | other: { 143 | type: "array", 144 | items: { 145 | type: "object", 146 | properties: { 147 | title: { type: "string" }, 148 | description: { type: "string" }, 149 | }, 150 | required: ["title"], 151 | additionalProperties: false, 152 | }, 153 | }, 154 | }, 155 | required: ["name", "age", "hobbies"], 156 | additionalProperties: false, 157 | } as const; 158 | 159 | type Dog = FromSchema; 160 | let dogInstance: Dog; 161 | 162 | it("accepts object with required & valid properties", () => { 163 | dogInstance = { 164 | name: "Dogo", 165 | age: 13, 166 | hobbies: ["barking", "urinating"], 167 | }; 168 | expect(ajv.validate(dogSchema, dogInstance)).toBe(true); 169 | }); 170 | 171 | it("accepts object with non-required & valid properties", () => { 172 | dogInstance = { 173 | name: "Dogo", 174 | age: 13, 175 | hobbies: ["barking", "urinating"], 176 | other: [ 177 | { title: "Likes Pizza" }, 178 | { title: "Address", description: "42, bark street" }, 179 | ], 180 | }; 181 | expect(ajv.validate(dogSchema, dogInstance)).toBe(true); 182 | }); 183 | 184 | it("rejects object missing required property", () => { 185 | // @ts-expect-error 186 | dogInstance = { name: "Dogo", age: 13 }; 187 | expect(ajv.validate(dogSchema, dogInstance)).toBe(false); 188 | }); 189 | 190 | it("rejects object with invalid property", () => { 191 | dogInstance = { 192 | name: "Dogo", 193 | age: 13, 194 | hobbies: ["barking", "urinating"], 195 | other: [ 196 | { title: "Likes Pizza" }, 197 | // @ts-expect-error 198 | { title: "Address", description: 42 }, 199 | ], 200 | }; 201 | expect(ajv.validate(dogSchema, dogInstance)).toBe(false); 202 | }); 203 | }); 204 | 205 | describe("Required + Additional properties", () => { 206 | const addressSchema = { 207 | type: "object", 208 | properties: { 209 | number: { type: "number" }, 210 | streetName: { type: "string" }, 211 | streetType: { 212 | type: "string", 213 | enum: ["Street", "Avenue", "Boulevard"], 214 | }, 215 | }, 216 | required: ["number", "streetName", "streetType", "direction"], 217 | } as const; 218 | 219 | type Address = FromSchema; 220 | let addressInstance: Address; 221 | 222 | it("accepts object with required & valid properties", () => { 223 | addressInstance = { 224 | number: 13, 225 | streetName: "Champs Elysées", 226 | streetType: "Avenue", 227 | direction: ["any", "value"], 228 | }; 229 | expect(ajv.validate(addressSchema, addressInstance)).toBe(true); 230 | }); 231 | 232 | it("accepts object with additional properties", () => { 233 | addressInstance = { 234 | number: 13, 235 | streetName: "Champs Elysées", 236 | streetType: "Avenue", 237 | direction: "NW", 238 | additionalProperty: ["any", "value"], 239 | }; 240 | expect(ajv.validate(addressSchema, addressInstance)).toBe(true); 241 | }); 242 | 243 | it("accepts object with missing required properties", () => { 244 | // @ts-expect-error 245 | addressInstance = { 246 | number: 13, 247 | streetName: "Champs Elysées", 248 | streetType: "Avenue", 249 | }; 250 | expect(ajv.validate(addressSchema, addressInstance)).toBe(false); 251 | }); 252 | }); 253 | 254 | describe("Required + Typed additional properties", () => { 255 | const addressSchema = { 256 | type: "object", 257 | properties: { 258 | number: { type: "number" }, 259 | streetName: { type: "string" }, 260 | streetType: { 261 | type: "string", 262 | enum: ["Street", "Avenue", "Boulevard"], 263 | }, 264 | }, 265 | required: ["number", "streetName", "streetType"], 266 | additionalProperties: { type: "string" }, 267 | } as const; 268 | 269 | type Address = FromSchema; 270 | let addressInstance: Address; 271 | 272 | it("accepts object with required & valid properties", () => { 273 | addressInstance = { 274 | number: 13, 275 | streetName: "Champs Elysées", 276 | streetType: "Avenue", 277 | }; 278 | expect(ajv.validate(addressSchema, addressInstance)).toBe(true); 279 | }); 280 | 281 | it("accepts object with valid additional properties", () => { 282 | addressInstance = { 283 | number: 13, 284 | streetName: "Champs Elysées", 285 | streetType: "Avenue", 286 | direction: "NW", 287 | }; 288 | expect(ajv.validate(addressSchema, addressInstance)).toBe(true); 289 | }); 290 | 291 | it("rejects object with invalid additional properties", () => { 292 | // Error cannot be raised at the moment... 293 | addressInstance = { 294 | number: 13, 295 | streetName: "Champs Elysées", 296 | streetType: "Avenue", 297 | // Requires string value for 'direction' 298 | direction: 42, 299 | }; 300 | expect(ajv.validate(addressSchema, addressInstance)).toBe(false); 301 | }); 302 | }); 303 | 304 | describe("Required missing in properties", () => { 305 | const addressSchema = { 306 | type: "object", 307 | properties: { 308 | number: { type: "number" }, 309 | streetName: { type: "string" }, 310 | }, 311 | required: ["number", "streetName", "streetType"], 312 | additionalProperties: false, 313 | } as const; 314 | 315 | type Address = FromSchema; 316 | let addressInstance: Address; 317 | 318 | it("rejects any value", () => { 319 | // @ts-expect-error 320 | addressInstance = { number: 42, streetName: "Champs Elysées" }; 321 | expect(ajv.validate(addressSchema, addressInstance)).toBe(false); 322 | 323 | // @ts-expect-error 324 | addressInstance = { number: 42, streetName: "Str", streetType: "Any" }; 325 | expect(ajv.validate(addressSchema, addressInstance)).toBe(false); 326 | }); 327 | }); 328 | }); 329 | -------------------------------------------------------------------------------- /tests/e2e/string.test.ts: -------------------------------------------------------------------------------- 1 | import Ajv from "ajv"; 2 | 3 | import { FromSchema } from "index"; 4 | 5 | var ajv = new Ajv(); 6 | 7 | describe("String schemas", () => { 8 | const stringSchema = { type: "string" } as const; 9 | 10 | type String = FromSchema; 11 | let stringInstance: String; 12 | 13 | it("accepts any string value", () => { 14 | stringInstance = "apples"; 15 | expect(ajv.validate(stringSchema, stringInstance)).toBe(true); 16 | }); 17 | 18 | it("rejects other values", () => { 19 | // @ts-expect-error 20 | stringInstance = 42; 21 | expect(ajv.validate(stringSchema, stringInstance)).toBe(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/meta-types/any.ts: -------------------------------------------------------------------------------- 1 | import { Resolve, Any } from "meta-types"; 2 | 3 | type Test1 = Resolve; 4 | const test1a: Test1 = null; 5 | test1a; 6 | const test1b: Test1 = true; 7 | test1b; 8 | const test1c: Test1 = "string"; 9 | test1c; 10 | const test1d: Test1 = 42; 11 | test1d; 12 | const test1e: Test1 = { foo: "bar" }; 13 | test1e; 14 | const test1f: Test1 = ["foo", "bar"]; 15 | test1f; 16 | -------------------------------------------------------------------------------- /tests/meta-types/array.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Resolve, 3 | Any, 4 | Never, 5 | Const, 6 | Enum, 7 | Primitive, 8 | Arr, 9 | Tuple, 10 | Object, 11 | Union, 12 | Intersection, 13 | Error, 14 | } from "meta-types"; 15 | 16 | // --- ANY --- 17 | 18 | type Test1 = Resolve>; 19 | const test1a: Test1 = [null]; 20 | test1a; 21 | const test1b: Test1 = [true]; 22 | test1b; 23 | const test1c: Test1 = ["string"]; 24 | test1c; 25 | const test1d: Test1 = [42]; 26 | test1d; 27 | const test1e: Test1 = [{ foo: "bar" }]; 28 | test1e; 29 | const test1f: Test1 = [["foo", "bar"]]; 30 | test1f; 31 | 32 | // --- NEVER --- 33 | 34 | type Test2 = Resolve>; 35 | // @ts-expect-error 36 | const test2a: Test2 = [null]; 37 | test2a; 38 | // @ts-expect-error 39 | const test2b: Test2 = [true]; 40 | test2b; 41 | // @ts-expect-error 42 | const test2c: Test2 = ["string"]; 43 | test2c; 44 | // @ts-expect-error 45 | const test2d: Test2 = [42]; 46 | test2d; 47 | // @ts-expect-error 48 | const test2e: Test2 = [{ foo: "bar" }]; 49 | test2e; 50 | // @ts-expect-error 51 | const test2f: Test2 = [["foo", "bar"]]; 52 | test2f; 53 | 54 | // --- CONST --- 55 | 56 | type Test3 = Resolve>>; 57 | const test3a: Test3 = ["foo", "foo"]; 58 | test3a; 59 | // @ts-expect-error 60 | const test3b: Test3 = ["bar"]; 61 | test3b; 62 | 63 | // --- ENUM --- 64 | 65 | type Test4 = Resolve>>; 66 | const test4a: Test4 = [42, "foo", "bar"]; 67 | test4a; 68 | // @ts-expect-error 69 | const test4b: Test4 = ["baz"]; 70 | test4b; 71 | 72 | // --- PRIMITIVES --- 73 | 74 | type Test5 = Resolve>>; 75 | const test5a: Test5 = ["foo", "bar"]; 76 | test5a; 77 | // @ts-expect-error 78 | const test5b: Test5 = ["foo", 42]; 79 | test5b; 80 | 81 | // --- ARRAY --- 82 | 83 | type Test6 = Resolve>>>; 84 | const test6a: Test6 = [["foo", "bar"]]; 85 | test6a; 86 | // @ts-expect-error 87 | const test6b: Test6 = [["foo", 42]]; 88 | test6b; 89 | 90 | // --- TUPLE --- 91 | 92 | type Test7 = Resolve], false>>>; 93 | const test7a: Test7 = [["foo"]]; 94 | test7a; 95 | // @ts-expect-error 96 | const test7b: Test7 = [["foo", "bar"]]; 97 | test7b; 98 | // @ts-expect-error 99 | const test7c: Test7 = [["foo", 42]]; 100 | test7c; 101 | 102 | // --- OBJECT --- 103 | 104 | type Test8 = Resolve< 105 | Arr< 106 | Object< 107 | { foo: Primitive; bar: Primitive }, 108 | "bar", 109 | false, 110 | Primitive 111 | > 112 | > 113 | >; 114 | const test8a: Test8 = [{ bar: 42 }, { foo: "str", bar: 15 }]; 115 | test8a; 116 | // @ts-expect-error 117 | const test8b: Test8 = [{ bar: "str" }]; 118 | test8b; 119 | // @ts-expect-error 120 | const test8c: Test8 = [{ bar: 42, foo: 50 }]; 121 | test8c; 122 | // @ts-expect-error 123 | const test8d: Test8 = [{ foo: "str" }]; 124 | test8d; 125 | 126 | // --- UNION --- 127 | 128 | type Test9 = Resolve | Const<42>>>>; 129 | const test9a: Test9 = [42, "foo", "bar"]; 130 | test9a; 131 | // @ts-expect-error 132 | const test9b: Test9 = [43]; 133 | test9b; 134 | 135 | // --- INTERSECTION --- 136 | 137 | type Test10 = Resolve, Const<"foo">>>>; 138 | const test10a: Test10 = ["foo"]; 139 | test10a; 140 | // @ts-expect-error 141 | const test10b: Test10 = ["foo", "bar"]; 142 | test10b; 143 | 144 | // --- ERROR --- 145 | 146 | type Test11 = Resolve>>; 147 | // @ts-expect-error 148 | const test11a: Test11 = ["foo"]; 149 | // @ts-expect-error 150 | const test11a: Test11 = [42]; 151 | -------------------------------------------------------------------------------- /tests/meta-types/const.ts: -------------------------------------------------------------------------------- 1 | import { Resolve, Const } from "meta-types"; 2 | 3 | // --- PRIMITIVE --- 4 | 5 | type Test1 = Resolve>; 6 | const test1a: Test1 = null; 7 | test1a; 8 | // @ts-expect-error 9 | const test1b: Test1 = "not null"; 10 | test1b; 11 | 12 | type Test2 = Resolve>; 13 | const test2a: Test2 = true; 14 | test2a; 15 | // @ts-expect-error 16 | const test2b: Test2 = false; 17 | test2b; 18 | 19 | type Test3 = Resolve>; 20 | const test3a: Test3 = "foo"; 21 | test3a; 22 | // @ts-expect-error 23 | const test3b: Test3 = false; 24 | test3b; 25 | 26 | type Test4 = Resolve>; 27 | const test4a: Test4 = 42; 28 | test4a; 29 | // @ts-expect-error 30 | const test4b: Test4 = 43; 31 | test4b; 32 | 33 | // --- TUPLE --- 34 | 35 | type Test5 = Resolve>; 36 | const test5a: Test5 = ["foo", "bar"]; 37 | test5a; 38 | // @ts-expect-error 39 | const test5b: Test5 = ["foo", "baz"]; 40 | test5b; 41 | 42 | // --- OBJECT --- 43 | 44 | type Test6 = Resolve>; 45 | const test6a: Test6 = { foo: "bar" }; 46 | test6a; 47 | // @ts-expect-error 48 | const test6b: Test6 = { foo: "baz" }; 49 | test6b; 50 | -------------------------------------------------------------------------------- /tests/meta-types/enum.ts: -------------------------------------------------------------------------------- 1 | import { Resolve, Enum } from "meta-types"; 2 | 3 | // --- EMPTY --- 4 | 5 | type Test1 = Resolve>; 6 | // @ts-expect-error 7 | const test1a: Test1 = "any value"; 8 | test1a; 9 | 10 | // --- PRIMITIVE --- 11 | 12 | type Test2 = Resolve>; 13 | const test2a: Test2 = "foo"; 14 | test2a; 15 | const test2b: Test2 = "bar"; 16 | test2b; 17 | // @ts-expect-error 18 | const test2c: Test2 = "baz"; 19 | test2c; 20 | 21 | // --- TUPLE --- 22 | 23 | type Test3 = Resolve>; 24 | const test3a: Test3 = ["foo", "bar"]; 25 | test3a; 26 | // @ts-expect-error 27 | const test3b: Test3 = ["foo", "baz"]; 28 | test3b; 29 | 30 | // --- OBJECT --- 31 | 32 | type Test4 = Resolve>; 33 | const test4a: Test4 = { foo: "bar" }; 34 | test4a; 35 | // @ts-expect-error 36 | const test4b: Test4 = { foo: "baz" }; 37 | test4b; 38 | -------------------------------------------------------------------------------- /tests/meta-types/error.ts: -------------------------------------------------------------------------------- 1 | import { Resolve, Error } from "meta-types"; 2 | 3 | type Test1 = Resolve>; 4 | // @ts-expect-error 5 | const test1a: Test1 = null; 6 | test1a; 7 | // @ts-expect-error 8 | const test1b: Test1 = true; 9 | test1b; 10 | // @ts-expect-error 11 | const test1c: Test1 = "string"; 12 | test1c; 13 | // @ts-expect-error 14 | const test1d: Test1 = 42; 15 | test1d; 16 | // @ts-expect-error 17 | const test1e: Test1 = { foo: "bar" }; 18 | test1e; 19 | // @ts-expect-error 20 | const test1f: Test1 = ["foo", "bar"]; 21 | test1f; 22 | -------------------------------------------------------------------------------- /tests/meta-types/intersection/array.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Intersection, 3 | Const, 4 | Enum, 5 | Union, 6 | Primitive, 7 | Arr, 8 | Tuple, 9 | Object, 10 | Error, 11 | } from "meta-types"; 12 | import { IntersectArr } from "meta-types/intersection/array"; 13 | 14 | import { 15 | mNever, 16 | mConst, 17 | mEnum, 18 | mPrimitive, 19 | mArr, 20 | mTuple, 21 | mUnion, 22 | mError, 23 | } from "./helpers"; 24 | 25 | // --- CONSTS --- 26 | 27 | type Test1a = IntersectArr>, Const<["foo", "bar"]>>; 28 | const test1a: Test1a = mConst(["foo", "bar"]); 29 | test1a; 30 | 31 | type Test1b = IntersectArr>, Const<["foo", 42]>>; 32 | const test1b: Test1b = mNever(); 33 | test1b; 34 | 35 | // --- ENUM --- 36 | 37 | type Test2a = IntersectArr< 38 | Arr>, 39 | Enum<["foo"] | ["bar"] | 42> 40 | >; 41 | let test2a: Test2a = mEnum(["foo"]); 42 | test2a = mEnum(["bar"]); 43 | test2a; 44 | 45 | type Test2b = IntersectArr>, Enum<["bar", "baz"] | [42]>>; 46 | let test2b: Test2b = mEnum([42]); 47 | // @ts-expect-error 48 | test2b = mEnum(["bar", "baz"]); 49 | test2b; 50 | 51 | type Test2c = IntersectArr>, Enum<["bar", "baz"]>>; 52 | // @ts-expect-error 53 | const test2c: Test2c = mEnum(["bar", "baz"]); 54 | test2c; 55 | 56 | // --- PRIMITIVES --- 57 | 58 | type Test3a = IntersectArr>, Primitive>; 59 | const test3a: Test3a = mNever(); 60 | test3a; 61 | 62 | // --- ARRAY --- 63 | 64 | type Test4a = IntersectArr>, Arr>>; 65 | const test4a: Test4a = mArr(mPrimitive("string")); 66 | test4a; 67 | 68 | type Test4b = IntersectArr>, Arr>>; 69 | const test4b: Test4b = mNever(); 70 | test4b; 71 | 72 | // --- TUPLE --- 73 | 74 | type Test5a = IntersectArr>, Tuple<[Primitive]>>; 75 | const test5a: Test5a = mTuple( 76 | [mPrimitive("string")], 77 | true, 78 | mPrimitive("string") 79 | ); 80 | test5a; 81 | 82 | type Test5b = IntersectArr< 83 | Arr>, 84 | Tuple<[Primitive], true, Primitive> 85 | >; 86 | const test5b: Test5b = mTuple( 87 | [mPrimitive("string")], 88 | true, 89 | mPrimitive("string") 90 | ); 91 | test5b; 92 | 93 | type Test5c = IntersectArr< 94 | Arr>, 95 | Tuple<[Primitive], true, Const<"foo">> 96 | >; 97 | const test5c: Test5c = mTuple([mPrimitive("string")], true, mConst("foo")); 98 | test5c; 99 | 100 | type Test5d = IntersectArr< 101 | Arr>, 102 | Tuple<[Primitive], true, Enum<"foo" | 42>> 103 | >; 104 | const test5d: Test5d = mTuple([mPrimitive("string")], true, mEnum("foo")); 105 | test5d; 106 | 107 | type Test5e = IntersectArr< 108 | Arr>, 109 | Tuple<[Primitive], true, Primitive> 110 | >; 111 | const test5e: Test5e = mTuple([mPrimitive("string")], false, mNever()); 112 | test5e; 113 | 114 | type Test5f = IntersectArr< 115 | Arr>, 116 | Tuple<[Primitive, Primitive], true, Primitive> 117 | >; 118 | const test5f: Test5f = mNever(); 119 | test5f; 120 | 121 | // --- OBJECT --- 122 | 123 | type Test6a = IntersectArr< 124 | Arr>, 125 | Object<{ foo: Primitive }, "foo", true, Primitive> 126 | >; 127 | const test6a: Test6a = mNever(); 128 | test6a; 129 | 130 | // --- UNION --- 131 | 132 | type Test7a = IntersectArr< 133 | Arr>, 134 | Union> | Arr>> 135 | >; 136 | const test7a: Test7a = mUnion(mArr(mPrimitive("string"))); 137 | test7a; 138 | // @ts-expect-error 139 | const test7a2: Test7a = mUnion(mArr(mPrimitive(42))); 140 | test7a2; 141 | 142 | type Test7b = IntersectArr< 143 | Arr>, 144 | Union | Arr>> 145 | >; 146 | const test7b: Test7b = mUnion(mConst(["foo"])); 147 | test7b; 148 | // @ts-expect-error 149 | const test7b2: Test7b = mUnion(mConst([42])); 150 | test7b2; 151 | 152 | type Test7c = IntersectArr< 153 | Arr>, 154 | Union> | Tuple<[Primitive]>> 155 | >; 156 | const test7c: Test7c = mUnion(mNever()); 157 | test7c; 158 | 159 | // --- INTERSECTION --- 160 | 161 | type Test8a = IntersectArr< 162 | Arr>, 163 | Intersection, Primitive> 164 | >; 165 | const test8a: Test8a = mError("Cannot intersect intersection"); 166 | test8a; 167 | 168 | // --- ERROR --- 169 | 170 | type Err = Error<"Any">; 171 | type Test9a = IntersectArr>, Err>; 172 | const test9a: Test9a = mError("Any"); 173 | test9a; 174 | -------------------------------------------------------------------------------- /tests/meta-types/intersection/const.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Intersection, 3 | Const, 4 | Enum, 5 | Union, 6 | Primitive, 7 | Arr, 8 | Tuple, 9 | Object, 10 | Error, 11 | } from "meta-types"; 12 | import { IntersectConst } from "meta-types/intersection/const"; 13 | 14 | import { mNever, mConst, mError, mUnion } from "./helpers"; 15 | 16 | // --- CONSTS --- 17 | 18 | type Test1a = IntersectConst, Const<"foo">>; 19 | const test1a: Test1a = mConst("foo"); 20 | test1a; 21 | 22 | type Test1b = IntersectConst, Const<"bar">>; 23 | const test1b: Test1b = mNever(); 24 | test1b; 25 | 26 | // --- ENUM --- 27 | 28 | type Test2a = IntersectConst, Enum<"foo" | "bar" | "baz">>; 29 | const test2a: Test2a = mConst("foo"); 30 | test2a; 31 | 32 | type Test2b = IntersectConst, Enum<["bar", "baz"]>>; 33 | const test2b: Test2b = mNever(); 34 | test2b; 35 | 36 | // --- PRIMITIVES --- 37 | 38 | type Test3a = IntersectConst, Primitive>; 39 | const test3a: Test3a = mConst("foo"); 40 | test3a; 41 | 42 | type Test3b = IntersectConst, Primitive>; 43 | const test3b: Test3b = mNever(); 44 | test3b; 45 | 46 | // --- ARRAY --- 47 | 48 | type Test4a = IntersectConst, Arr>>; 49 | const test4a: Test4a = mConst(["foo", "bar"]); 50 | test4a; 51 | 52 | type Test4b = IntersectConst, Arr>>; 53 | const test4b: Test4b = mNever(); 54 | test4b; 55 | 56 | // --- TUPLE --- 57 | 58 | type Test5a = IntersectConst< 59 | Const<["foo", "bar"]>, 60 | Tuple<[Primitive], true, Primitive> 61 | >; 62 | const test5a: Test5a = mConst(["foo", "bar"]); 63 | test5a; 64 | 65 | type Test5b = IntersectConst< 66 | Const<["foo", 42, "bar"]>, 67 | Tuple<[Primitive, Primitive], true, Primitive> 68 | >; 69 | const test5b: Test5b = mConst(["foo", 42, "bar"]); 70 | test5b; 71 | 72 | type Test5c = IntersectConst< 73 | Const<["foo", 42]>, 74 | Tuple<[Primitive], true, Primitive> 75 | >; 76 | const test5c: Test5c = mNever(); 77 | test5c; 78 | 79 | type Test5d = IntersectConst< 80 | Const<"foo">, 81 | Tuple<[Primitive], true, Primitive> 82 | >; 83 | const test5d: Test5d = mNever(); 84 | test5d; 85 | 86 | // --- OBJECT --- 87 | 88 | type Test6a = IntersectConst< 89 | Const<{ foo: "bar" }>, 90 | Object<{ foo: Primitive }, "foo", true, Primitive> 91 | >; 92 | const test6a: Test6a = mConst({ foo: "bar" }); 93 | test6a; 94 | 95 | type Test6b = IntersectConst< 96 | Const<"foo">, 97 | Object<{ foo: Primitive }, "foo", true, Primitive> 98 | >; 99 | const test6b: Test6b = mNever(); 100 | test6b; 101 | 102 | // --- UNION --- 103 | 104 | type Test7a = IntersectConst< 105 | Const<"foo">, 106 | Union | Primitive> 107 | >; 108 | const test7a: Test7a = mUnion(mConst("foo")); 109 | test7a; 110 | 111 | type Test7b = IntersectConst< 112 | Const<"foo">, 113 | Union | Primitive> 114 | >; 115 | const test7b: Test7b = mUnion(mConst("foo")); 116 | test7b; 117 | 118 | type Test7c = IntersectConst< 119 | Const<"foo">, 120 | Union | Arr>> 121 | >; 122 | const test7c: Test7c = mUnion(mNever()); 123 | test7c; 124 | 125 | // --- INTERSECTION --- 126 | 127 | type Test8a = IntersectConst< 128 | Const<"foo">, 129 | Intersection, Primitive> 130 | >; 131 | const test8a: Test8a = mError("Cannot intersect intersection"); 132 | test8a; 133 | 134 | // --- ERROR --- 135 | 136 | type Err = Error<"Any">; 137 | type Test9a = IntersectConst, Err>; 138 | const test9a: Test9a = mError("Any"); 139 | test9a; 140 | -------------------------------------------------------------------------------- /tests/meta-types/intersection/enum.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Const, 3 | Enum, 4 | Primitive, 5 | Arr, 6 | Tuple, 7 | Object, 8 | Union, 9 | Intersection, 10 | Error, 11 | } from "meta-types"; 12 | import { IntersectEnum } from "meta-types/intersection/enum"; 13 | 14 | import { mNever, mConst, mEnum, mError, mUnion } from "./helpers"; 15 | 16 | // --- CONSTS --- 17 | 18 | type Test1a = IntersectEnum, Const<"foo">>; 19 | const test1a: Test1a = mConst("foo"); 20 | test1a; 21 | 22 | type Test1b = IntersectEnum, Const>; 23 | const test1b: Test1b = mNever(); 24 | test1b; 25 | 26 | // --- ENUM --- 27 | 28 | type Test2a = IntersectEnum, Enum<"foo" | 42>>; 29 | let test2a: Test2a = mEnum("foo"); 30 | test2a = mEnum(42 as 42); 31 | // @ts-expect-error 32 | test2a = mEnum("bar"); 33 | test2a; 34 | 35 | type Test2b = IntersectEnum, Enum<43 | true>>; 36 | // @ts-expect-error 37 | let test2b: Test2b = mEnum("foo"); 38 | // @ts-expect-error 39 | test2b = mEnum("bar"); 40 | // @ts-expect-error 41 | test2b = mEnum(42); 42 | // @ts-expect-error 43 | test2b = mEnum(43); 44 | // @ts-expect-error 45 | test2b = mEnum(true); 46 | test2b; 47 | 48 | // --- PRIMITIVES --- 49 | 50 | type Test4a = IntersectEnum, Primitive>; 51 | let test4a: Test4a = mEnum("foo"); 52 | test4a = mEnum("bar"); 53 | // @ts-expect-error 54 | test4a = mEnum(42 as 42); 55 | test4a; 56 | 57 | enum Food { 58 | Pizza = "pizza", 59 | Tacos = "tacos", 60 | Fries = "fries", 61 | } 62 | 63 | type Test4b = IntersectEnum, Primitive>; 64 | let test4b: Test4b = mEnum(Food.Pizza); 65 | test4b = mEnum(Food.Tacos); 66 | test4b = mEnum(Food.Fries); 67 | test4b; 68 | 69 | type Test4c = IntersectEnum, Primitive>; 70 | // @ts-expect-error 71 | let test4c: Test4c = mEnum("foo"); 72 | // @ts-expect-error 73 | test4c = mEnum("bar"); 74 | // @ts-expect-error 75 | test4c = mEnum(42 as 42); 76 | test4c; 77 | 78 | // --- ARRAY --- 79 | 80 | type Test5a = IntersectEnum< 81 | Enum<["foo", "bar"] | [42]>, 82 | Arr> 83 | >; 84 | let test5a: Test5a = mEnum(["foo", "bar"]); 85 | // @ts-expect-error 86 | test5a = mEnum([42 as 42]); 87 | test5a; 88 | 89 | type Test5b = IntersectEnum, Arr>>; 90 | // @ts-expect-error 91 | let test5b: Test5b = mEnum("foo"); 92 | // @ts-expect-error 93 | let test5b: Test5b = mEnum(42 as 42); 94 | test5b; 95 | 96 | // --- TUPLE --- 97 | 98 | type Test6a = IntersectEnum< 99 | Enum<["foo", "bar"] | ["foo", 42]>, 100 | Tuple<[Primitive], true, Primitive> 101 | >; 102 | let test6a: Test6a = mEnum(["foo", "bar"]); 103 | // @ts-expect-error 104 | test6a = mEnum(["foo", 42]); 105 | test6a; 106 | 107 | type Test6b = IntersectEnum< 108 | Enum<"foo" | "bar" | 42>, 109 | Tuple<[Primitive], true, Primitive> 110 | >; 111 | // @ts-expect-error 112 | let test6b: Test6b = mEnum("foo"); 113 | // @ts-expect-error 114 | test6b = mEnum("bar"); 115 | // @ts-expect-error 116 | test6b = mEnum(42); 117 | test6b; 118 | 119 | // --- OBJECT --- 120 | 121 | type Test7a = IntersectEnum< 122 | Enum<{ foo: "str"; bar: "str" } | { foo: "str"; bar: 42 }>, 123 | Object<{ foo: Primitive }, "foo", true, Primitive> 124 | >; 125 | let test7a: Test7a = mEnum({ foo: "str", bar: "str" }); 126 | // @ts-expect-error 127 | test7a = mEnum({ foo: "str", bar: 42 as 42 }); 128 | test7a; 129 | 130 | type Test7b = IntersectEnum< 131 | Enum<"foo" | "bar" | 42>, 132 | Object<{ foo: Primitive }, "foo", true, Primitive> 133 | >; 134 | // @ts-expect-error 135 | let test7b: Test7b = mEnum("foo"); 136 | // @ts-expect-error 137 | let test7b: Test7b = mEnum("bar"); 138 | // @ts-expect-error 139 | let test7b: Test7b = mEnum(42 as 42); 140 | test7b; 141 | 142 | // --- UNION --- 143 | 144 | type Test3a = IntersectEnum, Union>>; 145 | let test3a: Test3a = mUnion(mEnum("foo")); 146 | test3a = mUnion(mEnum("bar")); 147 | // @ts-expect-error 148 | test3a = mUnion(mEnum(42 as 42)); 149 | test3a; 150 | 151 | type Test3b = IntersectEnum< 152 | Enum<"foo" | "bar" | 42>, 153 | Union | Primitive> 154 | >; 155 | const test3b: Test3b = mUnion(mConst("foo")); 156 | test3b; 157 | 158 | type Test3c = IntersectEnum< 159 | Enum<"foo" | "bar" | 42>, 160 | Union> 161 | >; 162 | // @ts-expect-error 163 | let test3c: Test3c = mUnion(mEnum("foo")); 164 | // @ts-expect-error 165 | test3c = mUnion(mEnum("bar")); 166 | // @ts-expect-error 167 | test3c = mUnion(mEnum(42 as 42)); 168 | test3c; 169 | 170 | // --- INTERSECTION --- 171 | 172 | type Test8a = IntersectEnum< 173 | Enum<"foo" | "bar" | 42>, 174 | Intersection, Primitive> 175 | >; 176 | const test8a: Test8a = mError("Cannot intersect intersection"); 177 | test8a; 178 | 179 | // --- ERROR --- 180 | 181 | type Test9a = IntersectEnum, Error<"Any">>; 182 | const test9a: Test9a = mError("Any"); 183 | test9a; 184 | -------------------------------------------------------------------------------- /tests/meta-types/intersection/helpers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Any, 3 | Never, 4 | Const, 5 | Primitive, 6 | Arr, 7 | Tuple, 8 | Object, 9 | Union, 10 | Intersection, 11 | Error, 12 | } from "meta-types"; 13 | 14 | export const mAny = (): Any => ({ type: "any" }); 15 | 16 | export const mNever = (): Never => ({ type: "never" }); 17 | 18 | export const mConst = (value: T): Const => ({ type: "const", value }); 19 | 20 | export const mEnum = (values: T): { type: "enum"; values: T } => ({ 21 | type: "enum", 22 | values, 23 | }); 24 | 25 | export const mPrimitive = (value: T): Primitive => ({ 26 | type: "primitive", 27 | value, 28 | }); 29 | 30 | export const mArr = (values: T): Arr => ({ type: "array", values }); 31 | 32 | export const mTuple = ( 33 | values: T, 34 | isOpen: O, 35 | openProps: V 36 | ): Tuple => ({ 37 | type: "tuple", 38 | values, 39 | isOpen, 40 | openProps, 41 | }); 42 | 43 | export const mObject = ( 44 | values: T, 45 | required: R, 46 | isOpen: O, 47 | openProps: V 48 | ): Object => ({ 49 | type: "object", 50 | values, 51 | required, 52 | isOpen, 53 | openProps, 54 | }); 55 | 56 | export const mUnion = (value: T): Union => ({ 57 | type: "union", 58 | values: value, 59 | }); 60 | 61 | export const mIntersection = (left: L, right: R): Intersection => ({ 62 | type: "intersection", 63 | left, 64 | right, 65 | }); 66 | 67 | export const mError = (message: M): Error => ({ type: "error", message }); 68 | -------------------------------------------------------------------------------- /tests/meta-types/intersection/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Resolve, 3 | Const, 4 | Enum, 5 | Primitive, 6 | Arr, 7 | Tuple, 8 | Union, 9 | Intersection, 10 | } from "meta-types"; 11 | 12 | type Test1 = Resolve< 13 | Intersection | Primitive>, Const<"foo">> 14 | >; 15 | const test1a: Test1 = "foo"; 16 | test1a; 17 | // @ts-expect-error 18 | const test1b: Test1 = "other string"; 19 | // @ts-expect-error 20 | const test1c: Test1 = { not: "a string" }; 21 | 22 | type Test2 = Resolve< 23 | Intersection>, Tuple<[Const<"foo">]>> 24 | >; 25 | const test2a: Test2 = ["foo"]; 26 | test2a; 27 | const test2b: Test2 = ["foo", "str", "otherStr"]; 28 | test2b; 29 | // @ts-expect-error 30 | const test2c: Test2 = []; 31 | test2c; 32 | // @ts-expect-error 33 | const test2d: Test2 = ["foo", { not: "a string" }]; 34 | test2d; 35 | 36 | type Test3 = Resolve< 37 | Intersection< 38 | Intersection | Primitive>, Const<"foo">>, 39 | Intersection, Enum<"foo" | 42>> 40 | > 41 | >; 42 | const test3a: Test3 = "foo"; 43 | test3a; 44 | // @ts-expect-error 45 | const test3b: Test3 = "other string"; 46 | // @ts-expect-error 47 | const test3c: Test3 = { not: "a string" }; 48 | 49 | type Test4 = Resolve< 50 | Intersection< 51 | Union< 52 | | Intersection | Primitive>, Const<"foo">> 53 | | Const<42> 54 | >, 55 | Intersection, Enum<"foo" | 42>> 56 | > 57 | >; 58 | const test4a: Test4 = "foo"; 59 | test4a; 60 | // @ts-expect-error 61 | const test4b: Test4 = "other string"; 62 | // @ts-expect-error 63 | const test4c: Test4 = { not: "a string" }; 64 | -------------------------------------------------------------------------------- /tests/meta-types/intersection/object.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Const, 3 | Enum, 4 | Primitive, 5 | Arr, 6 | Tuple, 7 | Object, 8 | Union, 9 | Intersection, 10 | Error, 11 | } from "meta-types"; 12 | import { IntersectObject } from "meta-types/intersection/object"; 13 | 14 | import { 15 | mAny, 16 | mNever, 17 | mConst, 18 | mPrimitive, 19 | mEnum, 20 | mError, 21 | mObject, 22 | mUnion, 23 | } from "./helpers"; 24 | 25 | // --- CONSTS --- 26 | 27 | type Test1a = IntersectObject< 28 | Object<{ str: Primitive }, "str">, 29 | Const<{ str: "str"; bar: "str" }> 30 | >; 31 | const test1a: Test1a = mConst({ str: "str", bar: "str" }); 32 | test1a; 33 | 34 | type Test1b = IntersectObject< 35 | Object<{ str: Primitive }, "str", false>, 36 | Const<{ str: "str"; bar: "str" }> 37 | >; 38 | const test1b: Test1b = mNever(); 39 | test1b; 40 | 41 | type Test1c = IntersectObject< 42 | Object<{ str: Primitive }, "str">, 43 | Const<{ num: 42 }> 44 | >; 45 | const test1c: Test1c = mNever(); 46 | test1c; 47 | 48 | type Test1d = IntersectObject< 49 | Object<{ str: Primitive }, "str">, 50 | Const<{ num: 42; str: "string" }> 51 | >; 52 | const test1d: Test1d = mConst({ num: 42, str: "string" }); 53 | test1d; 54 | 55 | // --- ENUM --- 56 | 57 | type Test2a = IntersectObject< 58 | Object<{ str: Primitive }, "str">, 59 | Enum<{ str: "string" } | 42> 60 | >; 61 | const test2a: Test2a = mEnum({ str: "string" }); 62 | test2a; 63 | 64 | type Test2b = IntersectObject< 65 | Object<{ str: Primitive }, "str", false>, 66 | Enum<"bar" | { str: "string"; bar: 42 }> 67 | >; 68 | // @ts-expect-error 69 | let test2b: Test2b = mEnum("bar"); 70 | // @ts-expect-error 71 | test2b = mEnum({ str: "string", bar: 42 as 42 }); 72 | test2b; 73 | 74 | type Test2c = IntersectObject< 75 | Object<{ str: Primitive }, "str">, 76 | Enum<"bar" | true | { num: 42 }> 77 | >; 78 | // @ts-expect-error 79 | let test2c: Test2c = mEnum("bar"); 80 | // @ts-expect-error 81 | test2c = mEnum(true); 82 | // @ts-expect-error 83 | test2c = mEnum({ num: 42 as 42 }); 84 | test2c; 85 | 86 | // --- UNION --- 87 | 88 | type Test3a = IntersectObject< 89 | Object<{ str: Primitive }, "str">, 90 | Union | Const<{ str: "str" }>> 91 | >; 92 | const test3a: Test3a = mUnion(mConst({ str: "str" })); 93 | test3a; 94 | // @ts-expect-error: String primitive doesn't match object 95 | const test3a2: Test3a = mUnion(mPrimitive("str")); 96 | test3a2; 97 | 98 | type Test3b = IntersectObject< 99 | Object<{ str: Primitive }, "str">, 100 | Union | Object<{ foo: Primitive }, "foo", false>> 101 | >; 102 | // Doesn't match string, neither object because it is closed 103 | const test3b: Test3b = mUnion(mNever()); 104 | test3b; 105 | // @ts-expect-error: Doesn't match object because B is closed and "str" is required in A 106 | const test3b2: Test3b = mUnion( 107 | mObject({ foo: mPrimitive("string"), str: mNever() }, "foo", false, mAny()) 108 | ); 109 | test3b2; 110 | 111 | type Test3c = IntersectObject< 112 | Object<{ str: Primitive }, "str">, 113 | Union>> 114 | >; 115 | const test3c: Test3c = mUnion(mNever()); 116 | test3c; 117 | 118 | // --- PRIMITIVES --- 119 | 120 | type Test4a = IntersectObject< 121 | Object<{ str: Primitive }, "str">, 122 | Primitive 123 | >; 124 | const test4a: Test4a = mNever(); 125 | test4a; 126 | 127 | type Test4b = IntersectObject< 128 | Object<{ str: Primitive }, "str">, 129 | Primitive 130 | >; 131 | const test4b: Test4b = mNever(); 132 | test4b; 133 | 134 | // --- ARRAY --- 135 | 136 | type Test5a = IntersectObject< 137 | Object<{ str: Primitive }, "str">, 138 | Arr> 139 | >; 140 | const test5a: Test5a = mNever(); 141 | test5a; 142 | 143 | // --- TUPLE --- 144 | 145 | type Test6a = IntersectObject< 146 | Object<{ str: Primitive }, "str">, 147 | Tuple<[Primitive], true, Primitive> 148 | >; 149 | const test6a: Test6a = mNever(); 150 | test6a; 151 | 152 | // --- OBJECT --- 153 | 154 | type Test7a = IntersectObject< 155 | Object<{ str: Primitive }, "str">, 156 | Object<{ foo: Primitive }, "foo", true, Primitive> 157 | >; 158 | const test7a: Test7a = mObject( 159 | { str: mPrimitive("string"), foo: mPrimitive("string") }, 160 | "str", 161 | true, 162 | mPrimitive("string") 163 | ); 164 | test7a; 165 | const test7a2: Test7a = mObject( 166 | { str: mPrimitive("string"), foo: mPrimitive("string") }, 167 | "foo", 168 | true, 169 | mPrimitive("string") 170 | ); 171 | test7a2; 172 | 173 | type Test7b = IntersectObject< 174 | Object<{ str: Primitive }, "str">, 175 | Object<{ str: Primitive }, "str", false> 176 | >; 177 | const test7b: Test7b = mObject( 178 | { str: mPrimitive("string") }, 179 | "str", 180 | false, 181 | mAny() 182 | ); 183 | test7b; 184 | 185 | type Test7c = IntersectObject< 186 | Object<{ str: Primitive }, "str", true>, 187 | Object<{ str: Primitive }, "str", false> 188 | >; 189 | const test7c: Test7c = mObject( 190 | { str: mPrimitive("string") }, 191 | "str", 192 | false, 193 | mAny() 194 | ); 195 | test7c; 196 | 197 | type Test7d = IntersectObject< 198 | Object<{ str: Primitive }, "str">, 199 | Object<{ otherStr: Primitive }, ["otherStr"], false> 200 | >; 201 | // Rejects "str" property because B is closed 202 | const test7d: Test7d = mNever(); 203 | test7d; 204 | 205 | type Test7e = IntersectObject< 206 | Object<{ str: Primitive }, "str">, 207 | Object<{ bool: Primitive }, ["bool"], true, Primitive> 208 | >; 209 | // Rejects "str" property because it should be bool AND str 210 | const test7e: Test7e = mNever(); 211 | test7e; 212 | 213 | // --- INTERSECTION --- 214 | 215 | type Test8a = IntersectObject< 216 | Object<{ str: Primitive }, "str">, 217 | Intersection, Primitive> 218 | >; 219 | const test8a: Test8a = mError("Cannot intersect intersection"); 220 | test8a; 221 | 222 | // --- ERROR --- 223 | 224 | type Test9a = IntersectObject< 225 | Object<{ str: Primitive }, "str">, 226 | Error<"Any"> 227 | >; 228 | const test9a: Test9a = mError("Any"); 229 | test9a; 230 | -------------------------------------------------------------------------------- /tests/meta-types/intersection/primitive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Intersection, 3 | Const, 4 | Enum, 5 | Union, 6 | Primitive, 7 | Arr, 8 | Tuple, 9 | Object, 10 | Error, 11 | } from "meta-types"; 12 | import { IntersectPrimitive } from "meta-types/intersection/primitive"; 13 | 14 | import { mNever, mConst, mEnum, mPrimitive, mError, mUnion } from "./helpers"; 15 | 16 | // --- CONSTS --- 17 | 18 | type Test1a = IntersectPrimitive, Const<"foo">>; 19 | const test1a: Test1a = mConst("foo"); 20 | test1a; 21 | 22 | type Test1b = IntersectPrimitive, Const<42>>; 23 | const test1b: Test1b = mNever(); 24 | test1b; 25 | 26 | // --- ENUM --- 27 | 28 | type Test2a = IntersectPrimitive, Enum<"foo" | "bar" | 42>>; 29 | let test2a: Test2a = mEnum("foo"); 30 | test2a = mEnum("bar"); 31 | // @ts-expect-error 32 | test2a = mEnum(42 as 42); 33 | test2a; 34 | 35 | type Test2b = IntersectPrimitive, Enum<"bar" | "baz" | 42>>; 36 | const test2b: Test2b = mEnum(42 as 42); 37 | test2b; 38 | 39 | type Test2c = IntersectPrimitive, Enum<"bar" | "baz">>; 40 | // @ts-expect-error 41 | let test2c: Test2c = mEnum("bar"); 42 | // @ts-expect-error 43 | test2c = mEnum("baz"); 44 | test2c; 45 | 46 | // --- PRIMITIVES --- 47 | 48 | type Test3a = IntersectPrimitive, Primitive>; 49 | const test3a: Test3a = mPrimitive("string"); 50 | test3a; 51 | 52 | type Test3b = IntersectPrimitive, Primitive>; 53 | const test3b: Test3b = mNever(); 54 | test3b; 55 | 56 | // --- ARRAY --- 57 | 58 | type Test4a = IntersectPrimitive, Arr>>; 59 | const test4a: Test4a = mNever(); 60 | test4a; 61 | 62 | // --- TUPLE --- 63 | 64 | type Test5a = IntersectPrimitive< 65 | Primitive, 66 | Tuple<[Primitive], true, Primitive> 67 | >; 68 | const test5a: Test5a = mNever(); 69 | test5a; 70 | 71 | // --- OBJECT --- 72 | 73 | type Test6a = IntersectPrimitive< 74 | Primitive, 75 | Object<{ foo: Primitive }, "foo", true, Primitive> 76 | >; 77 | const test6a: Test6a = mNever(); 78 | test6a; 79 | 80 | // --- UNION --- 81 | 82 | type Test7a = IntersectPrimitive< 83 | Primitive, 84 | Union | Primitive> 85 | >; 86 | const test7a: Test7a = mUnion(mPrimitive("string")); 87 | test7a; 88 | // @ts-expect-error 89 | const test7a2: Test7a = mUnion(mPrimitive(42)); 90 | test7a2; 91 | 92 | type Test7b = IntersectPrimitive< 93 | Primitive, 94 | Union | Primitive> 95 | >; 96 | const test7b: Test7b = mUnion(mConst("foo")); 97 | test7b; 98 | 99 | type Test7c = IntersectPrimitive< 100 | Primitive, 101 | Union | Arr>> 102 | >; 103 | const test7c: Test7c = mUnion(mNever()); 104 | test7c; 105 | 106 | // --- INTERSECTION --- 107 | 108 | type Test8a = IntersectPrimitive< 109 | Primitive, 110 | Intersection, Primitive> 111 | >; 112 | const test8a: Test8a = mError("Cannot intersect intersection"); 113 | test8a; 114 | 115 | // --- ERROR --- 116 | 117 | type Err = Error<"Any">; 118 | type Test9a = IntersectPrimitive, Err>; 119 | const test9a: Test9a = mError("Any"); 120 | test9a; 121 | -------------------------------------------------------------------------------- /tests/meta-types/intersection/tuple.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Intersection, 3 | Const, 4 | Enum, 5 | Union, 6 | Primitive, 7 | Arr, 8 | Tuple, 9 | Object, 10 | Error, 11 | } from "meta-types"; 12 | import { IntersectTuple } from "meta-types/intersection/tuple"; 13 | 14 | import { 15 | mNever, 16 | mConst, 17 | mEnum, 18 | mPrimitive, 19 | mTuple, 20 | mUnion, 21 | mError, 22 | mAny, 23 | } from "./helpers"; 24 | 25 | // --- CONSTS --- 26 | 27 | type Test1a = IntersectTuple< 28 | Tuple<[Primitive, Primitive]>, 29 | Const<["foo", 42, { any: "value" }]> 30 | >; 31 | const test1a: Test1a = mConst(["foo", 42, { any: "value" }]); 32 | test1a; 33 | 34 | type Test1b = IntersectTuple< 35 | Tuple<[Primitive, Primitive], true, Primitive>, 36 | Const<["foo", 42, true]> 37 | >; 38 | const test1b: Test1b = mConst(["foo", 42, true]); 39 | test1b; 40 | 41 | type Test1c = IntersectTuple< 42 | Tuple<[Primitive, Primitive], true, Primitive>, 43 | Const<["foo", 42, "bar"]> 44 | >; 45 | const test1c: Test1c = mNever(); 46 | test1c; 47 | 48 | type Test1d = IntersectTuple< 49 | Tuple<[Primitive, Primitive]>, 50 | Const<[42, "foo"]> 51 | >; 52 | const test1d: Test1d = mNever(); 53 | test1d; 54 | 55 | // --- ENUM --- 56 | 57 | type Test2a = IntersectTuple< 58 | Tuple<[Primitive, Primitive], true, Primitive>, 59 | Enum< 60 | ["foo"] | ["foo", 42] | ["foo", 42, true] | ["foo", 42, { any: "value" }] 61 | > 62 | >; 63 | let test2a: Test2a = mEnum(["foo", 42]); 64 | test2a = mEnum(["foo", 42, true]); 65 | test2a; 66 | 67 | type Test2b = IntersectTuple< 68 | Tuple<[Primitive, Primitive], false>, 69 | Enum< 70 | ["foo"] | ["foo", 42] | ["foo", 42, true] | ["foo", 42, { any: "value" }] 71 | > 72 | >; 73 | const test2b: Test2b = mEnum(["foo", 42]); 74 | test2b; 75 | 76 | type Test2c = IntersectTuple< 77 | Tuple<[Primitive, Primitive], true, Primitive>, 78 | Enum<["bar", "baz"]> 79 | >; 80 | // @ts-expect-error 81 | const test2c: Test2c = mEnum(["bar", "baz"]); 82 | test2c; 83 | 84 | // --- PRIMITIVES --- 85 | 86 | type Test3a = IntersectTuple]>, Primitive>; 87 | const test3a: Test3a = mNever(); 88 | test3a; 89 | 90 | // --- ARRAY --- 91 | 92 | type Test4a = IntersectTuple< 93 | Tuple<[Primitive]>, 94 | Arr> 95 | >; 96 | const test4a: Test4a = mTuple( 97 | [mPrimitive("string")], 98 | true, 99 | mPrimitive("string") 100 | ); 101 | test4a; 102 | 103 | type Test4b = IntersectTuple< 104 | Tuple<[Primitive], true, Primitive>, 105 | Arr> 106 | >; 107 | const test4b: Test4b = mTuple( 108 | [mPrimitive("string")], 109 | true, 110 | mPrimitive("string") 111 | ); 112 | test4b; 113 | 114 | type Test4c = IntersectTuple< 115 | Tuple<[Primitive], true, Const<"foo">>, 116 | Arr> 117 | >; 118 | const test4c: Test4c = mTuple([mPrimitive("string")], true, mConst("foo")); 119 | test4c; 120 | 121 | type Test4d = IntersectTuple< 122 | Tuple<[Primitive], true, Enum<"foo" | 42>>, 123 | Arr> 124 | >; 125 | const test4d: Test4d = mTuple([mPrimitive("string")], true, mEnum("foo")); 126 | test4d; 127 | 128 | type Test4e = IntersectTuple< 129 | Tuple<[Primitive], true, Primitive>, 130 | Arr> 131 | >; 132 | const test4e: Test4e = mTuple([mPrimitive("string")], false, mNever()); 133 | test4e; 134 | 135 | type Test4f = IntersectTuple< 136 | Tuple<[Primitive, Primitive], true, Primitive>, 137 | Arr> 138 | >; 139 | const test4f: Test4f = mNever(); 140 | test4f; 141 | 142 | // --- TUPLE --- 143 | 144 | type Test5a = IntersectTuple< 145 | Tuple<[Primitive]>, 146 | Tuple<[Primitive]> 147 | >; 148 | const test5a: Test5a = mTuple([mPrimitive("string")], true, mAny()); 149 | test5a; 150 | 151 | type Test5b = IntersectTuple< 152 | Tuple<[Primitive]>, 153 | Tuple<[Primitive], true, Primitive> 154 | >; 155 | const test5b: Test5b = mTuple( 156 | [mPrimitive("string")], 157 | true, 158 | mPrimitive("string") 159 | ); 160 | test5b; 161 | 162 | type Test5c = IntersectTuple< 163 | Tuple<[Primitive]>, 164 | Tuple<[Primitive], true, Const<"foo">> 165 | >; 166 | const test5c: Test5c = mTuple([mPrimitive("string")], true, mConst("foo")); 167 | test5c; 168 | 169 | type Test5d = IntersectTuple< 170 | Tuple<[Primitive], true, Primitive>, 171 | Tuple<[Primitive], true, Enum<"foo" | 42>> 172 | >; 173 | const test5d: Test5d = mTuple([mPrimitive("string")], true, mEnum("foo")); 174 | test5d; 175 | 176 | type Test5e = IntersectTuple< 177 | Tuple<[Primitive], true, Primitive>, 178 | Tuple<[Primitive], true, Primitive> 179 | >; 180 | const test5e: Test5e = mTuple([mPrimitive("string")], false, mNever()); 181 | test5e; 182 | 183 | type Test5f = IntersectTuple< 184 | Tuple<[Primitive]>, 185 | Tuple<[Primitive, Primitive], true, Primitive> 186 | >; 187 | const test5f: Test5f = mTuple( 188 | [mPrimitive("string"), mPrimitive(true)], 189 | true, 190 | mPrimitive("string") 191 | ); 192 | test5f; 193 | 194 | type Test5g = IntersectTuple< 195 | Tuple<[Primitive], false>, 196 | Tuple<[Primitive], true> 197 | >; 198 | const test5g: Test5g = mTuple([mPrimitive("string")], false, mAny()); 199 | test5g; 200 | 201 | type Test5h = IntersectTuple< 202 | Tuple<[Primitive], false>, 203 | Tuple<[Primitive, Primitive], true> 204 | >; 205 | const test5h: Test5h = mNever(); 206 | test5h; 207 | 208 | type Test5i = IntersectTuple< 209 | Tuple<[Primitive, Primitive]>, 210 | Tuple<[Primitive, Primitive], true> 211 | >; 212 | const test5i: Test5i = mNever(); 213 | test5i; 214 | 215 | // --- OBJECT --- 216 | 217 | type Test6a = IntersectTuple< 218 | Tuple<[Primitive]>, 219 | Object<{ foo: Primitive }, "foo", true, Primitive> 220 | >; 221 | const test6a: Test6a = mNever(); 222 | test6a; 223 | 224 | // --- UNION --- 225 | 226 | type Test7a = IntersectTuple< 227 | Tuple<[Primitive]>, 228 | Union]> | Tuple<[Primitive]>> 229 | >; 230 | const test7a: Test7a = mUnion(mTuple([mPrimitive("string")], true, mAny())); 231 | test7a; 232 | // @ts-expect-error 233 | const test7a2: Test7a = mUnion(mTuple([mPrimitive(42)], true, mAny())); 234 | test7a2; 235 | 236 | type Test7b = IntersectTuple< 237 | Tuple<[Primitive]>, 238 | Union | Tuple<[Primitive]>> 239 | >; 240 | const test7b: Test7b = mUnion(mConst(["foo"])); 241 | test7b; 242 | // @ts-expect-error 243 | const test7b2: Test7b = mUnion(mTuple([mPrimitive(42)], true, mAny())); 244 | test7b2; 245 | 246 | type Test7c = IntersectTuple< 247 | Tuple<[Primitive]>, 248 | Union> | Tuple<[Primitive]>> 249 | >; 250 | const test7c: Test7c = mUnion(mNever()); 251 | test7c; 252 | 253 | // --- INTERSECTION --- 254 | 255 | type Test8a = IntersectTuple< 256 | Tuple<[Primitive]>, 257 | Intersection, Primitive> 258 | >; 259 | const test8a: Test8a = mError("Cannot intersect intersection"); 260 | test8a; 261 | 262 | // --- ERROR --- 263 | 264 | type Err = Error<"Any">; 265 | type Test9a = IntersectTuple]>, Err>; 266 | const test9a: Test9a = mError("Any"); 267 | test9a; 268 | -------------------------------------------------------------------------------- /tests/meta-types/intersection/union.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Const, 3 | Enum, 4 | Primitive, 5 | Arr, 6 | Tuple, 7 | Object, 8 | Union, 9 | Intersection, 10 | Error, 11 | } from "meta-types"; 12 | import { IntersectUnion } from "meta-types/intersection/union"; 13 | 14 | import { mNever, mConst, mUnion, mEnum, mPrimitive, mError } from "./helpers"; 15 | 16 | // --- CONSTS --- 17 | 18 | type Test1a = IntersectUnion< 19 | Union | Primitive>, 20 | Const<"foo"> 21 | >; 22 | const test1a: Test1a = mUnion(mConst("foo")); 23 | test1a; 24 | 25 | type Test1b = IntersectUnion< 26 | Union | Primitive>, 27 | Const<422> 28 | >; 29 | const test1b: Test1b = mUnion(mConst(422)); 30 | test1b; 31 | 32 | type Test1c = IntersectUnion< 33 | Union | Primitive>, 34 | Const 35 | >; 36 | const test1c: Test1c = mUnion(mNever()); 37 | test1c; 38 | 39 | // --- ENUM --- 40 | 41 | type Test2a = IntersectUnion< 42 | Union | Primitive>, 43 | Enum<"foo" | 42> 44 | >; 45 | const test2a: Test2a = mUnion(mConst("foo")); 46 | test2a; 47 | const test2b: Test2a = mUnion(mEnum(42 as 42)); 48 | test2b; 49 | 50 | type Test2c = IntersectUnion< 51 | Union | Primitive>, 52 | Enum<["bar", true]> 53 | >; 54 | const test2c: Test2c = mUnion(mNever()); 55 | test2c; 56 | 57 | // --- PRIMITIVES --- 58 | 59 | type Test4a = IntersectUnion< 60 | Union | Primitive>, 61 | Primitive 62 | >; 63 | const test4a: Test4a = mUnion(mConst("foo")); 64 | test4a; 65 | // @ts-expect-error 66 | const test4b: Test4a = mUnion(mConst(42)); 67 | test4b; 68 | 69 | type Test4c = IntersectUnion< 70 | Union | Primitive>, 71 | Primitive 72 | >; 73 | const test4c: Test4c = mUnion(mPrimitive(42)); 74 | test4c; 75 | // @ts-expect-error 76 | const test4d: Test4c = mUnion(mPrimitive("foo")); 77 | test4d; 78 | 79 | type Test4e = IntersectUnion< 80 | Union | Primitive>, 81 | Primitive 82 | >; 83 | const test4e: Test4e = mUnion(mNever()); 84 | test4e; 85 | 86 | // --- ARRAY --- 87 | 88 | type Test5a = IntersectUnion< 89 | Union | Primitive>, 90 | Arr> 91 | >; 92 | const test5a: Test5a = mUnion(mNever()); 93 | test5a; 94 | 95 | // --- TUPLE --- 96 | 97 | type Test6a = IntersectUnion< 98 | Union | Primitive>, 99 | Tuple<[Primitive], true, Primitive> 100 | >; 101 | const test6a: Test6a = mUnion(mNever()); 102 | test6a; 103 | 104 | // --- OBJECT --- 105 | 106 | type Test7a = IntersectUnion< 107 | Union | Primitive>, 108 | Object<{ foo: Primitive }, ["foo"], true, Primitive> 109 | >; 110 | const test7a: Test7a = mUnion(mNever()); 111 | test7a; 112 | 113 | // --- UNION --- 114 | 115 | type Test3a = IntersectUnion< 116 | Union | Primitive>, 117 | Union> 118 | >; 119 | const test3a: Test3a = mUnion(mUnion(mConst("foo" as "foo"))); 120 | test3a; 121 | // @ts-expect-error 122 | const test3a2: Test3a = mUnion(mUnion(mConst(42 as 42))); 123 | test3a2; 124 | 125 | type Test3b = IntersectUnion< 126 | Union | Primitive>, 127 | Union | Primitive> 128 | >; 129 | const test3b: Test3b = mUnion(mUnion(mConst("foo"))); 130 | test3b; 131 | 132 | type Test3c = IntersectUnion< 133 | Union | Primitive>, 134 | Union | Primitive> 135 | >; 136 | const test3c: Test3c = mUnion(mUnion(mPrimitive(42))); 137 | test3c; 138 | 139 | type Test3d = IntersectUnion< 140 | Union | Primitive>, 141 | Union>> 142 | >; 143 | const test3d: Test3d = mUnion(mUnion(mNever())); 144 | test3d; 145 | 146 | // --- INTERSECTION --- 147 | 148 | type Test8a = IntersectUnion< 149 | Union | Primitive>, 150 | Intersection, Primitive> 151 | >; 152 | const test8a: Test8a = mError("Cannot intersect intersection"); 153 | test8a; 154 | 155 | // --- ERROR --- 156 | 157 | type Test9a = IntersectUnion< 158 | Union | Primitive>, 159 | Error<"Any"> 160 | >; 161 | const test9a: Test9a = mError("Any"); 162 | test9a; 163 | -------------------------------------------------------------------------------- /tests/meta-types/never.ts: -------------------------------------------------------------------------------- 1 | import { Resolve, Never } from "meta-types"; 2 | 3 | type Test1 = Resolve; 4 | // @ts-expect-error 5 | const test1a: Test1 = null; 6 | test1a; 7 | // @ts-expect-error 8 | const test1b: Test1 = true; 9 | test1b; 10 | // @ts-expect-error 11 | const test1c: Test1 = "string"; 12 | test1c; 13 | // @ts-expect-error 14 | const test1d: Test1 = 42; 15 | test1d; 16 | // @ts-expect-error 17 | const test1e: Test1 = { foo: "bar" }; 18 | test1e; 19 | // @ts-expect-error 20 | const test1f: Test1 = ["foo", "bar"]; 21 | test1f; 22 | -------------------------------------------------------------------------------- /tests/meta-types/object.ts: -------------------------------------------------------------------------------- 1 | import { Resolve, Object, Primitive } from "meta-types"; 2 | 3 | // --- OPEN --- 4 | 5 | type Test1 = Resolve< 6 | Object< 7 | { str: Primitive; num: Primitive }, 8 | "str", 9 | true, 10 | Primitive 11 | > 12 | >; 13 | 14 | // @ts-expect-error: Requires 'str' property 15 | const test1a: Test1 = {}; 16 | test1a; 17 | 18 | const test1b: Test1 = { str: "str" }; 19 | test1b; 20 | 21 | // @ts-expect-error: 'num' should be number 22 | const test1c: Test1 = { str: "str", num: "not a number" }; 23 | test1c; 24 | 25 | const test1d: Test1 = { str: "str", num: 42 }; 26 | test1d; 27 | 28 | // Impossible to raise error at the moment... 29 | const test1e: Test1 = { str: "str", num: 42, openProp: { foo: "bar" } }; 30 | test1e; 31 | 32 | type Test2 = Resolve>>; 33 | 34 | const test2a: Test2 = {}; 35 | test2a; 36 | 37 | const test2b: Test2 = { str: "str" }; 38 | test2b; 39 | 40 | // @ts-expect-error: 'num' should be string 41 | const test2c: Test2 = { str: "str", num: 42 }; 42 | test2c; 43 | 44 | // --- CLOSED --- 45 | 46 | type Test3 = Resolve< 47 | Object<{ str: Primitive; num: Primitive }, "str", false> 48 | >; 49 | 50 | // @ts-expect-error: Requires 'str' property 51 | const test3a: Test3 = {}; 52 | test3a; 53 | 54 | const test3b: Test3 = { str: "str" }; 55 | test3b; 56 | 57 | // @ts-expect-error: 'num' should be number 58 | const test3c: Test3 = { str: "str", num: "not a number" }; 59 | test3c; 60 | 61 | const test3d: Test3 = { str: "str", num: 43 }; 62 | test3d; 63 | 64 | const test3e: Test3 = { 65 | str: "str", 66 | num: 43, 67 | // @ts-expect-error 68 | openProp: { foo: "bar" }, 69 | }; 70 | test3e; 71 | 72 | type Test4 = Resolve }, "str" | "num", false>>; 73 | 74 | // @ts-expect-error: Requires 'num' property 75 | const test4a: Test4 = { str: "str" }; 76 | test4a; 77 | // @ts-expect-error: Rejects additional 'num' property 78 | const test4b: Test4 = { str: "str", num: 42 }; 79 | test4b; 80 | -------------------------------------------------------------------------------- /tests/meta-types/tuple.ts: -------------------------------------------------------------------------------- 1 | import { Resolve, Tuple, Primitive } from "meta-types"; 2 | 3 | // --- OPEN --- 4 | 5 | type Test1 = Resolve, Primitive]>>; 6 | const test1a: Test1 = ["string", 42]; 7 | test1a; 8 | const test1b: Test1 = ["string", 42, { any: "value" }]; 9 | test1b; 10 | // @ts-expect-error 11 | const test1c: Test1 = []; 12 | test1c; 13 | // @ts-expect-error 14 | const test1d: Test1 = ["string"]; 15 | test1d; 16 | // @ts-expect-error 17 | const test1e: Test1 = [42, "string"]; 18 | test1e; 19 | 20 | type Test2 = Resolve< 21 | Tuple<[Primitive, Primitive], true, Primitive> 22 | >; 23 | const test2a: Test2 = ["string", 42]; 24 | test2a; 25 | const test2b: Test2 = ["string", 42, true, false]; 26 | test2b; 27 | // @ts-expect-error 28 | const test2c: Test2 = ["string", 42, { any: "value" }]; 29 | test2c; 30 | // @ts-expect-error 31 | const test2d: Test2 = []; 32 | test2d; 33 | // @ts-expect-error 34 | const test2e: Test2 = ["string"]; 35 | test2e; 36 | // @ts-expect-error 37 | const test2f: Test2 = [42, "string"]; 38 | test2f; 39 | 40 | // --- CLOSED --- 41 | 42 | type Test3 = Resolve, Primitive], false>>; 43 | const test3a: Test3 = ["string", 42]; 44 | test3a; 45 | // @ts-expect-error 46 | const test3b: Test3 = ["string", 42, { any: "value" }]; 47 | test3b; 48 | // @ts-expect-error 49 | const test3c: Test3 = []; 50 | test3c; 51 | // @ts-expect-error 52 | const test3d: Test3 = ["string"]; 53 | test3d; 54 | // @ts-expect-error 55 | const test3e: Test3 = [42, "string"]; 56 | test3e; 57 | -------------------------------------------------------------------------------- /tests/meta-types/union.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Resolve, 3 | Any, 4 | Never, 5 | Const, 6 | Enum, 7 | Primitive, 8 | Arr, 9 | Tuple, 10 | Object, 11 | Union, 12 | Intersection, 13 | Error, 14 | } from "meta-types"; 15 | 16 | // --- ANY --- 17 | 18 | type Test1 = Resolve>>; 19 | const test1a: Test1 = null; 20 | test1a; 21 | const test1b: Test1 = true; 22 | test1b; 23 | const test1c: Test1 = "string"; 24 | test1c; 25 | const test1d: Test1 = 42; 26 | test1d; 27 | const test1e: Test1 = { foo: "bar" }; 28 | test1e; 29 | const test1f: Test1 = ["foo", "bar"]; 30 | test1f; 31 | 32 | // --- NEVER --- 33 | 34 | type Test2a = Resolve>>; 35 | const test2a: Test2a = "string"; 36 | test2a; 37 | // @ts-expect-error 38 | const test2a2: Test2a = 42; 39 | test2a2; 40 | 41 | type Test2b = Resolve>; 42 | // @ts-expect-error 43 | const test2b: Test2b = "any thing"; 44 | test2a; 45 | 46 | // --- CONSTS --- 47 | 48 | type Test3 = Resolve | Const<"bar"> | Const<42>>>; 49 | const test3a: Test3 = "foo"; 50 | test3a; 51 | const test3b: Test3 = "bar"; 52 | test3b; 53 | const test3c: Test3 = 42; 54 | test3c; 55 | // @ts-expect-error 56 | const test3d: Test3 = "baz"; 57 | test3d; 58 | // @ts-expect-error 59 | const test3e: Test3 = 43; 60 | test3e; 61 | 62 | // --- ENUMS --- 63 | 64 | type Test4 = Resolve | Enum<"baz" | 43>>>; 65 | const test4a: Test4 = "foo"; 66 | test4a; 67 | const test4b: Test4 = "bar"; 68 | test4b; 69 | const test4c: Test4 = 42; 70 | test4c; 71 | const test4d: Test4 = "baz"; 72 | test4d; 73 | const test4e: Test4 = 43; 74 | test4e; 75 | // @ts-expect-error 76 | const test4f: Test4 = "bazz"; 77 | test4f; 78 | // @ts-expect-error 79 | const test4g: Test4 = 44; 80 | test4g; 81 | 82 | // --- PRIMITIVES --- 83 | 84 | type Test5 = Resolve | Primitive>>; 85 | const test5a: Test5 = "string"; 86 | test5a; 87 | const test5b: Test5 = 42; 88 | test5b; 89 | // @ts-expect-error: Requires string or number 90 | const test5c: Test5 = ["not", "a", "string", "or", "number", 42]; 91 | test5c; 92 | 93 | // --- ARRAYS --- 94 | 95 | type Test6 = Resolve> | Arr>>>; 96 | 97 | const test6a: Test6 = ["string", "array"]; 98 | test6a; 99 | const test6b: Test6 = [42, 13]; 100 | test6b; 101 | // @ts-expect-error: Rejects mixed array 102 | const test6c: Test6 = ["not", "a", "string", "or", "number", 42]; 103 | test6c; 104 | // @ts-expect-error: Rejects other values 105 | const test6d: Test6 = { not: "a string or number array" }; 106 | test6d; 107 | 108 | // --- TUPLES --- 109 | 110 | type Test7 = Resolve< 111 | Union< 112 | | Tuple<[Primitive, Primitive]> 113 | | Tuple<[Primitive, Primitive], false> 114 | > 115 | >; 116 | 117 | const test7a: Test7 = ["string", 42]; 118 | test7a; 119 | const test7b: Test7 = ["string", 42, true]; 120 | test7b; 121 | const test7c: Test7 = ["string", true]; 122 | test7c; 123 | // @ts-expect-error: Tuple B is closed 124 | const test7d: Test7 = ["string", true, "any"]; 125 | test7d; 126 | 127 | // --- OBJECTS --- 128 | 129 | type Test8 = Resolve< 130 | Union< 131 | | Object<{ bar: Primitive }, "bar"> 132 | | Object<{ foo: Primitive }, "foo", false> 133 | > 134 | >; 135 | 136 | const test8a: Test8 = { bar: 42 }; 137 | test8a; 138 | const test8b: Test8 = { bar: 42, any: "value" }; 139 | test8b; 140 | const test8c: Test8 = { foo: "str", any: "value" }; 141 | test8c; 142 | // Impossible to raise error at the moment 143 | const test8d: Test8 = { foo: "str", any: "value" }; 144 | test8d; 145 | 146 | // --- UNIONS --- 147 | 148 | type Test9 = Resolve< 149 | Union< 150 | | Union | Primitive> 151 | | Union | Const<42>> 152 | > 153 | >; 154 | 155 | const test9a: Test9 = "string"; 156 | test9a; 157 | const test9b: Test9 = true; 158 | test9b; 159 | const test9c: Test9 = "foo"; 160 | test9c; 161 | const test9d: Test9 = 42; 162 | test9d; 163 | // @ts-expect-error 164 | const test9e: Test9 = 43; 165 | test9e; 166 | 167 | // --- INTERSECTIONS --- 168 | 169 | type Test10 = Resolve< 170 | Union< 171 | | Intersection, Const<"foo">> 172 | | Intersection, Const<42>> 173 | > 174 | >; 175 | 176 | const test10a: Test10 = "foo"; 177 | test10a; 178 | const test10b: Test10 = 42; 179 | test10b; 180 | // @ts-expect-error 181 | const test10c: Test10 = "bar"; 182 | test10c; 183 | // @ts-expect-error 184 | const test10d: Test10 = 43; 185 | test10d; 186 | 187 | // --- ERROR --- 188 | 189 | type Test11 = Resolve | Error<"Other value">>>; 190 | 191 | const test11a: Test11 = "foo"; 192 | test11a; 193 | // @ts-expect-error 194 | const test11b: Test11 = "Other value"; 195 | test11b; 196 | -------------------------------------------------------------------------------- /tests/utils/concat.ts: -------------------------------------------------------------------------------- 1 | import { ConcatReversed, Concat } from "utils"; 2 | 3 | type TupleA = ["a", "b", "c"]; 4 | type TupleB = ["d", "e", "f"]; 5 | 6 | type AssertConcatReversed = ConcatReversed; 7 | let assertConcatReverse: AssertConcatReversed; 8 | assertConcatReverse = ["c", "b", "a", "d", "e", "f"]; 9 | assertConcatReverse; 10 | 11 | type AssertConcat = Concat; 12 | let assertConcat: AssertConcat; 13 | assertConcat = ["a", "b", "c", "d", "e", "f"]; 14 | assertConcat; 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "baseUrl": "src", 5 | "declaration": true, 6 | "lib": ["es2017"], 7 | "moduleResolution": "node", 8 | "noUnusedLocals": true, 9 | "noUnusedParameters": true, 10 | "outDir": "lib", 11 | "paths": { "*": ["./*"] }, 12 | "removeComments": true, 13 | "resolveJsonModule": true, 14 | "sourceMap": true, 15 | "strictNullChecks": true, 16 | "target": "es2017" 17 | } 18 | } 19 | --------------------------------------------------------------------------------