Shapely
Runtime javascript type checker with support for records and tagged unions.
Use cases
I use Shapely in projects where:
- I can't use static type checkers.
- I can use static type checkers, but I still need to validate the shape of some data in runtime, because:
- I have a client/server setup and I need to ensure that the server API calls and their responses follow predefined protocols.
- I have a set of functions (possibly written by different team members) and I need to ensure that all of their return values follow a certain protocol.
Example
Here we have a simple client/server setup:
- The server implements three functions:
getCartItems
,getItemById
, andaddItemToCart
, and they are written by different team members. - Each of these functions is expected to:
- Return
{kind: 'Success', payload: any}
when invoked successfully. - Return
{kind: 'Failure', message: String}
when invoked unsuccessfully.
- Return
- We need to validate that each function actually follows this protocol every time it's invoked. We'll validate that both on the server and on the client.
// ServerResponse.js // This file defines the server's resopnse protocol,// and it'll be used both by the server and the client. ; // This is basically a tagged union that defines the two// possible shapes of the server's response data. The equivalent// flow type would look like this:// type ServerResponse =// {kind: 'Success', payload: mixed} |// {kind: 'Failure', message: string}const ServerResponse = ;
// server.js // The server imports the three functions, listens for requests from the client,// routes each request to the correct function, and then validates the returned value// of that function before sending it to the client. ;; ;;;const fns = getCartItems getItemById addItemToCart; ;
// client.js // The client simply sends requests to the server,// and returns a promise that either fulfills or rejects,// based on the server's response. ;; { return ;};
Usage
validate()
Takes a validator and a value:
- If valid, returns the same value.
- If invalid, throws.
Example:
; ; // returns 'a'; // throws
isValid()
Takes a validator and a value. Returns true/false.
Example:
; ; // returns true; // returns false
Strings, Numbers, Booleans
; ; // returns true; // returns true // returns true
Strict String Equality
; ; // returns true since they are equal; // returns false
Any
; ; // returns true for any value, including null
Unions
Unions are just a collection of other validators:
; const StringOrNumber = ; ; // returns true; // returns true; // returns false
Records
Records check if an object has certain keys and whether those keys match their respective validators.
; const LaunchVehicle = ; ; // returns true; // returns true, as only the expected keys are checked; // returns false
Tagged Unions
Tagged unions are just unions of tagged records:
; const Response = ; ; // returns true; // returns true; // returns false
Arrays
; const ArrayOfNumbersOrStrings = ; ; // returns true; // returns true; // returns true; // returns false
Maps
; const MapOfBooleans = ; ; // returns true; // returns false
Optional values
; const LaunchVehicle = ; ; // returns true ; // returns true
Nil
; ; // returns true; // returns true; // returns false
Recursive data types
; const BinaryTree = ; ; // returns true
Custom validators
You can make your own custom validators, provided they implement the Validator
interface:
type Validator = : boolean; : ValidationResult; type ValidationResult = isValid: 'true' | isValid: 'false' message: string score: number
Example:
; const Tuple = { return Array && vallength === 2; } { if !Array return isValid: 'false' message: `Array expected, given.` score: 0 // 0 means a complete type mismatch ; else if vallenth === 2 return isValid: 'true'; else return isValid: 'false' message: `A tuple 2 two elements. given` score: 1 // 1 means the general type matches, but the details don't. ; }; ; // returns true; // returns false; // returns [1, 2]; // throws Error: val.length is ecpected to be 2. 1 given
Development
Shapely is itself written in Flow, but like all flow code, it still compiles if you don't have flow on your system. We use Babel for compilation, Webpack for bundling, and LiveScript for writing the tests.
To watch the files and recompile:
$ cd path/to/shapely
$ npm run dev
To run the tests:
$ npm test
or:
$ npm run test:watch
License
MIT