@flix-tech/fp-ts-type-check
Version:
Type validation in runtime
124 lines (86 loc) • 5.46 kB
Markdown
fp-ts-type-check is a library for runtime type validation of variables where you can't say their type for sure i.e. data you get via some API. It's somewhat similar to Typescript's [Type Guards](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards) except it uses type system to make sure this data is properly validated.
Features:
* **Type safe.** Typescript compiler makes sure your types and your type checkers are always in sync.
* **Detailed error reports.** If parsing of a big deep-nested structure failed - you'll know where exactly in that structure you have invalid data and why it was considered invalid
* **Composable.** Type checkers for big structures are created by composing checkers for simple structures.
* **Functional.** Each type checker is a pure function returning `Either` type from fp-ts. It's up for you to decide how to handle errors.
* **[Tree-shakeable](https://webpack.js.org/guides/tree-shaking/).** Bundle only functions you really use.
## Installation
To install the stable version:
```
npm install '-tech/fp-ts-type-check@~0.2.0'
```
While the major version number is 0 changes in minor version number bay break backward compatibility so you should stick to a fixed minor version.
## Usage
Meet `Parser[T]`, the main type of this library. `Parser[T]` is a function that takes any variable and checks if it's a variable of type `T`. It either returns the same value as `T` or `ParseError` object with parse error details. Parser's type definition looks somewhat like this:
```typescript
type Parser<A> = (x: unknown): Either<ParseError, A>;
interface ParseError {
path: string; // Path to the property causing error in deep nested structures
message: string; // Parsing error message
}
```
We're using the type [Either](https://gcanti.github.io/fp-ts/modules/Either.ts.html) from [fp-ts](https://github.com/gcanti/fp-ts) library.
Parsers for some complex structures you'd want to validate are composed from small parsers like here:
```typescript
import * as P from 'fp-ts-type-check';
interface ShoppingListItem {
name: string;
amount: { count: number; unit?: string };
}
const shoppingListItemParser: P.Parser<ShoppingListItem> = P.type({
name: P.string,
amount: P.type({
count: P.number,
unit: P.optional(P.string),
}),
});
shoppingListItem({name: "Apple", amount: {count: 5}}); // Right<ShoppingListItem>({name: "Apple", amount: {count: 5}})
shoppingListItem({name: "Apple", amount: {count: "some"}}); // Left<ParseError>({path: ".amount.count", message: "expected number, got string"})
```
As you can see, `ParseError` data is for your eyes only, user should get a generalized error message appropriate in this case.
You can reuse your parsers to construct parsers for even bigger structures:
```typescript
type ShoppingList = array<ShoppingListItem>;
const shoppingListParser: P.Parser<ShoppingList> = P.arrayOf(shoppingListItemParser);
const validShoppingList = [
{name: "Apple", amount: {count: 5}},
{name: "Milk", amount: {count: 500, unit: 'ml'}},
];
shoppingListParser(validShoppingList); // Right<ShoppingList>(...)
const invalidShoppingList = [
{name: "Apple", amount: {count: 5}},
{name: "Milk", amount: {unit: 'ml'}}, // No count here
];
shoppingListParser(invalidShoppingList); // Left<ParseError>({path: "[1].amount.count", message: "expected number, got undefined"})
```
# API documentation
### Simple type parsers
* `string(): Parser<string>` - checks that value is string.
* `boolean(): Parser<boolean>` - checks that value is boolean.
* `number(): Parser<number>` - checks that value is number.
* `object(): Parser<object>` - checks that value is any object.
* `any(): Parser<any>` - does not check anything.
* `exact<A>(expected: A): Parser<A>` - checks that value is same as expected one.
* `oneOf<A>(allowed: A[]): Parser<A>` - checks that value is one of allowed ones.
* `keyOf<A extends object>(allowed: A): Parser<keyof A>` - checks that value is string and also is a key of object `allowed`.
## Copositional parsers
* `type<A>(propertyParsers: { [K in keyof A]: Parser<A[K]> }): Parser<A>` - checks that values is an object and validates each it's property with corresponding parser.
* `arrayOf<A>(parseBody: Parser<A>): Parser<A[]>` - checks that value is an array and every item matches `parseBody` parser.
* `optional<A>(parseBody: Parser<A>): Parser<A | undefined>` - checks that value is either undefined or matches `parseBody` parser.
* `nullable<A>(parseBody: Parser<A>): Parser<A | null>` - checks that value is either null or matches `parseBody` parser.
* `discriminatedUnion<A extends {type: string;}>(parsers: {(type value): (type parser)}): Parser<A>` - checks that value is a [discriminated union](https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions) with discriminant in `type` property. Example usage:
```typescript
type Foo = { type: 'foo'; foo: number };
type Bar = { type: 'bar'; bar: number };
type FooBar = Foo | Bar;
const parser: P.Parser<FooBar> = P.discriminatedUnion({
foo: P.type({ foo: P.number }),
bar: P.type({ bar: P.number }),
});
```
### Logic combination parsers
* `and(parserA: Parser<A>, parserB<B>): Parser<A & B>` - checks that value matches both types A and B.
* `or(parserA: Parser<A>, parserB<B>): Parser<A | B>` - checks that value matches any of types A or B.
## License
The MIT License (MIT)