Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jcoreio/typescript-validators
API input validators with user-friendly error output and TypeScript to ensure you don't miss any properties
https://github.com/jcoreio/typescript-validators
api assert assertion defensive-programming js-schema json json-schema object-schema runtime-types schema type types typescript validate validation validation-schema validator
Last synced: 18 days ago
JSON representation
API input validators with user-friendly error output and TypeScript to ensure you don't miss any properties
- Host: GitHub
- URL: https://github.com/jcoreio/typescript-validators
- Owner: jcoreio
- License: mit
- Created: 2020-07-12T02:22:59.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2023-01-05T11:51:57.000Z (almost 2 years ago)
- Last Synced: 2024-04-14T22:48:01.910Z (8 months ago)
- Topics: api, assert, assertion, defensive-programming, js-schema, json, json-schema, object-schema, runtime-types, schema, type, types, typescript, validate, validation, validation-schema, validator
- Language: TypeScript
- Size: 928 KB
- Stars: 12
- Watchers: 3
- Forks: 1
- Open Issues: 21
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# typescript-validators
[![CircleCI](https://circleci.com/gh/jcoreio/typescript-validators.svg?style=svg)](https://circleci.com/gh/jcoreio/typescript-validators)
[![Coverage Status](https://codecov.io/gh/jcoreio/typescript-validators/branch/master/graph/badge.svg)](https://codecov.io/gh/jcoreio/typescript-validators)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
[![npm version](https://badge.fury.io/js/typescript-validators.svg)](https://badge.fury.io/js/typescript-validators)Complex type validators that generate TypeScript types for you.
The validation errors are detailed. Adapted from the brilliant work in `flow-runtime`.# Deprecated
I recreated this project as [`typed-validators`](https://github.com/jcoreio/typed-validators) and added Flow support!
A few breaking changes to the API were necessary for Flow support, but they also made it easier to declare objects with
optional properties.# Table of Contents
- [Introduction](#introduction)
- [What about generating validators from type defs?](#what-about-generating-validators-from-type-defs)
- [API](#api)
- [Type creators](#type-creators)
- [`t.boolean()`](#tboolean)
- [`t.boolean(true)`](#tbooleantrue)
- [`t.string()`](#tstring)
- [`t.string('foo')`](#tstringfoo)
- [`t.number()`](#tnumber)
- [`t.number(3)`](#tnumber3)
- [`t.symbol()`](#tsymbol)
- [`t.symbol(MySymbol)`](#tsymbolmysymbol)
- [`t.null()` / `t.nullLiteral()`](#tnull--tnullliteral)
- [`t.nullOr(t.string())`](#tnullortstring)
- [`t.undefined()` / `t.undefinedLiteral()`](#tundefined--tundefinedliteral)
- [`t.nullish()`](#tnullish)
- [`t.nullishOr(t.string())`](#tnullishortstring)
- [`t.array(t.number())`](#tarraytnumber)
- [`t.simpleObject({ foo: t.string() })`](#tsimpleobject-foo-tstring-)
- [`t.object`](#tobject)
- [`t.record(t.string(), t.number())`](#trecordtstring-tnumber)
- [`t.instanceOf(Date)`](#tinstanceofdate)
- [`t.tuple(t.string(), t.number())`](#ttupletstring-tnumber)
- [`t.allOf(A, B)`](#tallofa-b)
- [`t.oneOf(t.string(), t.number())`](#toneoftstring-tnumber)
- [`t.alias(name, type)`](#taliasname-type)
- [`t.ref(() => typeAlias)`](#tref--typealias)
- [`t.Type`](#ttypet)
- [`accepts(input: any): boolean`](#acceptsinput-any-boolean)
- [`assert(input: any, prefix = '', path?: (string | number | symbol)[]): V`](#assertv-extends-tinput-any-prefix---path-string--number--symbol-v)
- [`validate(input: any, prefix = '', path?: (string | number | symbol)[]): Validation`](#validateinput-any-prefix---path-string--number--symbol-validationt)
- [`toString(): string`](#tostring-string)
- [`t.ExtractType>`](#textracttypet-extends-typeany)
- [`t.TypeAlias`](#ttypealiast)
- [`readonly name: string`](#readonly-name-string)
- [`addConstraint(...constraints: TypeConstraint[]): this`](#addconstraintconstraints-typeconstraintt-this)
- [Custom Constraints](#custom-constraints)
- [Recursive Types](#recursive-types)# Introduction
When you need to validate the inputs to a TypeScript API, a problem arises. How do you ensure that a value that passes validation
matches your declared TypeScript type? Someone might modify one and forget to modify the other:```ts
type Post = {
author: {
name: string
username: string
}
content: string
// newly added by developer
tags: string[]
}// hypothetical syntax
const validator = requireObject({
author: requireObject({
name: requireString(),
username: requireString(),
}),
content: requireString(),
// uhoh!! developer forgot to add tags here
})
````typescript-validators` solves this by generating TypeScript types from your validators:
```ts
import * as t from 'typescript-validators'const PostValidator = t.simpleObject({
author: t.simpleObject({
name: t.string(),
username: t.string(),
}),
content: t.string(),
tags: t.array(t.string()),
})type Post = t.ExtractType
const example: Post = PostValidator.assert({
author: {
name: 'MC Hammer',
username: 'hammertime',
},
content: "Can't touch this",
tags: ['mc-hammer', 'hammertime'],
})
```Hover over `Post` in VSCode and you'll see, voilà:
```ts
type Post = {
author: {
name: string
username: string
}
content: string
tags: string[]
}
```# What about generating validators from type defs?
I'd like to be able to do this, because type defs are a lot more readable. In fact, for Flow, it's possible with
`babel-pluging-flow-runtime`, which I have a lot of experience with. That looks like this:```js
import {type Type, reify} from 'flow-runtime'type Post = {
author: {
name: string
username: string
}
content: string
tags: string[]
}const PostValidator = (reify: Type) // looooots of magic here
const example: Post = PostValidator.assert({
author: {
name: 'MC Hammer',
username: 'hammertime',
},
content: "Can't touch this",
tags: ['mc-hammer', 'hammertime'],
})
```This is sweet but there are some caveats:
- You have to add a Babel plugin to your toolchain (for TypeScript, not everyone wants to use Babel)
- There are issues with the Babel plugin. It aims to support all Flow types, with varying success.
- The original author of `flow-runtime` abandoned the project and I don't blame him. It was hugely ambitious and difficult to maintain.The author of `flow-runtime` himself told me in private conversations that he had moved on to an approach like
`typescript-validators` in his own projects, because generating types from the validator declarations is a lot
simpler and more maintainable in the long run.# API
I recommend importing like this:
```ts
import * as t from 'typescript-validators'
```## Type creators
All of the following methods return an instance of `t.Type`.
### `t.boolean()`
A validator that requires the value to be a `boolean`.
### `t.boolean(true)`
A validator that requires the value to be `true`.
### `t.string()`
A validator that requires the value to be a `string`.
### `t.string('foo')`
A validator that requires the value to be `'foo'`.
### `t.number()`
A validator that requires the value to be a `number`.
### `t.number(3)`
A validator that requires the value to be `3`.
### `t.symbol()`
A validator that requires the value to be a `symbol`.
### `t.symbol(MySymbol)`
A validator that requires the value to be `MySymbol`.
### `t.null()` / `t.nullLiteral()`
A validator that requires the value to be `null`.
### `t.nullOr(t.string())`
A validator that requires the value to be `string | null`
### `t.undefined()` / `t.undefinedLiteral()`
A validator that requires the value to be `undefined`.
### `t.nullish()`
A validator that requires the value to be `null | undefined`.
### `t.nullishOr(t.string())`
A validator that requires the value to be `string | null | undefined`.
### `t.array(t.number())`
A validator that requires the value to be `number[]`.
### `t.simpleObject({ foo: t.string() })`
A validator that requires the value to be an object with only a `foo` property that's a `string`.
### `t.object`
For dealing with optional properties, use the following.
The syntax is a bit awkward but it's the best way I could find to get a clean type output:```ts
const ThingValidator = t.object<{
name: any
comment?: any
}>()({
name: t.string(),
comment: t.optional(t.string()),
})type Thing = t.ExtractType
```The type of `Thing` will be `{ name: string, comment?: string }`. Note that the property types in the explicit type parameter
(`any`) are ignored. The type parameter just indicates which properties are required and which are optional, and also allows
you to mark properties readonly. These attributes will be reflected in `t.ExtractType`.You can also use the `t.optionalNullOr(t.string())` as a shorthand for
`t.optional(t.nullOr(t.string()))`.### `t.record(t.string(), t.number())`
A validator that requires the value to be `Record`.
### `t.instanceOf(Date)`
A validator that requires the value to be an instance of `Date`.
### `t.tuple(t.string(), t.number())`
A validator that requires the value to be `[string, number]`.
Accepts a variable number of arguments.### `t.allOf(A, B)`
A validator that requires the value to be `A & B`. Accepts a variable number of arguments, though type generation is only overloaded up to 8 arguments. For example:
```ts
const ThingType = t.simpleObject({ name: t.string() })
const CommentedType = t.simpleObject({ comment: t.string() })const CommentedThingType = t.allOf(ThingType, CommentedType)
CommentedThingType.assert({ name: 'foo', comment: 'sweet' })
```### `t.oneOf(t.string(), t.number())`
A validator that requires the value to be `string | number`. Accepts a variable number of arguments, though type generation is only overloaded up to 8 arguments.
### `t.alias(name, type)`
Creates a `TypeAlias` with the given `name` and `type`.
Type aliases serve two purposes:
- They allow you to [create recursive type validators with `t.ref()`](#recursive-types)
- You can [add custom constraints to them](#custom-constraints)### `t.ref(() => typeAlias)`
Creates a reference to the given `TypeAlias`. See [Recursive Types](#recursive-types) for examples.
## `t.Type`
The base class for all validator types.
`T` is the type of values it accepts.
### `accepts(input: any): boolean`
Returns `true` if and only if `input` is the correct type.
### `assert(input: any, prefix = '', path?: (string | number | symbol)[]): V`
Throws an error if `input` isn't the correct type.
`prefix` will be prepended to thrown error messages.
`path` will be prepended to validation error paths. If you are validating a function parameter named `foo`,
pass `['foo']` for `path` to get clear error messages.### `validate(input: any, prefix = '', path?: (string | number | symbol)[]): Validation`
Validates `input`, returning any errors in the `Validation`.
`prefix` and `path` are the same as in `assert`.
### `warn(input: any, prefix = '', path?: (string | number | symbol)[]): void`
Logs a warning to the console if `input` isn't the correct type.
### `toString(): string`
Returns a string representation of this type (using TS type syntax in most cases).
## `t.ExtractType>`
Gets the TypeScript type that a validator type accepts. For example:
```ts
import * as t from 'typescript-validators'const PostValidator = t.simpleObject({
author: t.simpleObject({
name: t.string(),
username: t.string(),
}),
content: t.string(),
tags: t.array(t.string()),
})type Post = t.ExtractType
```Hover over `Post` in the IDE and you'll see, voilà:
```ts
type Post = {
author: {
name: string
username: string
}
content: string
tags: string[]
}
```## `t.TypeAlias`
### `readonly name: string`
The name of the alias.
### `addConstraint(...constraints: TypeConstraint[]): this`
Adds custom constraints. `TypeConstraint` is a function `(value: T) => string | null | undefined` which
returns nullish if `value` is valid, or otherwise a `string` describing why `value` is invalid.## Custom Constraints
It's nice to be able to validate that something is a `number`, but what if we want to make sure it's positive?
We can do this by creating a type alias for `number` and adding a custom constraint to it:```ts
const PositiveNumberType = t
.alias('PositiveNumber', t.number())
.addConstraint((value: number) => (value > 0 ? undefined : 'must be > 0'))PositiveNumberType.assert(-1)
```The assertion will throw a `t.RuntimeTypeError` with the following message:
```
Value must be > 0Expected: PositiveNumber
Actual Value: -1
Actual Type: number
```## Recursive Types
Creating validators for recursive types takes a bit of extra effort. Naively, we would want to do this:
```ts
const NodeType = t.object<{
value: any
left?: any
right?: any
}>()({
value: t.any(),
left: t.optional(NodeType),
right: t.optional(NodeType),
})
```But `t.optional(NodeType)` causes the error `Block-scoped variable 'NodeType' referenced before its declaration`.
To work around this, we can create a `TypeAlias` and a reference to it:
```ts
const NodeType: t.TypeAlias<{
value: any
left?: Node
right?: Node
}> = t.alias(
'Node',
t.object<{
value: any
left?: any
right?: any
}>()({
value: t.any(),
left: t.optional(t.ref(() => NodeType)),
right: t.optional(t.ref(() => NodeType)),
})
)type Node = t.ExtractType
NodeType.assert({
value: 'foo',
left: {
value: 2,
right: {
value: 3,
},
},
right: {
value: 6,
},
})
```Notice how we use a thunk function in `t.ref(() => NodeType)` to avoid referencing `NodeType` before its declaration.