npm version Build Status davidDm Dependencies Known Vulnerabilities Coverage Status Inline docs npm downloads Licence Stability: Stable contributions welcome PRs Welcome

flow-validator

Object validation with proper flow types and more.

Installation

npm install flow-validator

Usage

import { arrayOf, string, number, object, instanceOf, Type, Vobject, asyncArrayOf, tuple, takes, match } from 'flow-validator';
import { express } from 'flow-validator/express';

// { name: string, age: ?number, toys: Array<string> }
const Person = object({ name: string, age: number.optional(), toys: arrayOf(string) });
const fred = Person.parse({ name: 'Fred', age: 89, toys: ['teddy bear', 'shotgun'] });
console.log(fred); // eslint-disable-line no-console

// Array<string> validated asynchronously
const InventoryObjects = asyncArrayOf(string.async().refine(checkInventory));
const shoppingCart = InventoryObjects.parse(['AK47', 'stuffed bunny']);
shoppingCart.then(items => console.log(items)); // eslint-disable-line no-console

async function checkInventory(item: string, error): Promise<string> {
  if (~['AK47', 'stuffed bunny'].indexOf(item)) return item;
  return Promise.reject(error('no supplies'));
}

// pattern matching
const x = match(1,
  number, n => new Date(n*2),
  Person, ({ name }) => [name, name]
);
(x: Date);

// express middleware example
const middleware = express.middleware(object({ headers: object({ 'my-custom-header': string }) }), (req, res, next) => next());

// express endpoint matching middleware (inspired to Spring RequestMapping)
const requestMap1 = express.requestMapping(object({ body: Person }), (req, res) => res.json(req.body.age));
const requestMap2 = express.requestMapping(object({ body: object({ username: string, password: string }) }), (req, res) => {/* authenticate */});
// app.use('/user', requestMap1);
// app.use('/user', requestMap2);

const Contact = object({ name: string, birth: string.toDate(), email: string.isEmail().optional() });
console.log(Contact.parse({ name: 'fred', birth: String(new Date), email: 'gobi301@gmail.com' })); // eslint-disable-line no-console

// ensure functions params, useful on user input functions
const signUpUser = takes(string.isEmail(), number)((email, secretCoupon) => `user ${email} added with coupon: ${secretCoupon}`);
signUpUser('gobi301@gmail.com', 666);

// Don't Repeat Yourself
// you can use a type of a defined schema, instead of
// var yogi: { name: string, age: ?number, toys: Array<string> }
var yogi: typeof Person.type; // eslint-disable-line no-unused-vars

// runtime introspection
const Name: Type<string> = Person.schema.name; // eslint-disable-line no-unused-vars
const Age: Type<?number> = Person.schema.age; // eslint-disable-line no-unused-vars

// const tup: [string, number, Date] = ...
const tup = tuple([string, number, instanceOf(Date)]).parse(['hello', 4, new Date]);  // eslint-disable-line no-unused-vars

// { a: string, b: number, c: Array<string | number | Date>, d: string, e: Date }
const Schema = object({
  a: string,
  b: number.optional(),
  c: arrayOf(string.or(number).or(instanceOf(Date))),
  d: string.refine((s, error) => { // refinements must return the same type
    if (/el/.test(s)) return s;
    throw error(String(/el/)); // this throws proper error
  }).revalidate(), // add a revalidate if want to be sure not changed type during refinement
  e: string.to(s => new Date(s)) // with .to() you can convert types
});

const toBeValidated = {
  a: 'hi',
  c: [1, new Date, '2017'],
  d: 'hello',
  e: 'Mon Feb 27 2017 10:00:15 GMT-0800 (PST)'
};

// validate input object, returns original object if valid, throws otherwise
// VType object has .validate() method that returns original object
// arrayOf, tuple, mapping, object, objectExact ha V prefixed variants,
// and can't contain validators that change input
Vobject({ a: string }).validate({ a: 'hello' }) === toBeValidated; // = true

// same as validate, but it make a copy in case of: arrayOf, tuple, mapping, object, objectExact
// it can be used when using refinemnts that return not the original value
// and with .to() for conversions
Schema.parse(toBeValidated) === toBeValidated; // = false
// deepEqual(Schema.parse(toBeValidated), toBeValidated); // = true

// shortcuts
Vobject({ a: string }).isValid({ a: 'hello' }); // : boolean
Vobject({ a: string }).validateResult({ a: 'hello' }); // : { value: ... } | { error: ... }
Schema.parseResult(toBeValidated); // : { value: ... } | { error: ... }

// you can chain validators, to reuse them or check if your custom type converter works
string.to(s => new Date(s)).chain(instanceOf(Date));

// to get JSON serializable error report
try { Schema.parse(); } catch (e) { console.log(e.toJSON()); } // eslint-disable-line no-console

// sometimes flow will not remember types, ex:
object({ x: number.to(n => new Date(n)) }).parse({ x: 4 }); // unknown type

// solution
const x2: Type<Date> = number.to(n => new Date(n));
object({ x2 }).parse({ x2: 4 }); // : { x: Date }

// type-safe composition
const str2num = (s: string) => Number(s);
const div = (n: number) => n / 2;
const num2str = (n: number) => String(n);
const str2arr = (s: string) => s.split('1');
const nonSense = string.compose(str2num).compose(div).compose(num2str).compose(str2arr);
nonSense.parseResult('1234567890'); // : Array<string>

// you can convert sync type to async one
string.async();

if you do not want import the entire library, you can find single validators in import { object } from 'flow-validator/sync/object'; or import { asyncArrayOf } from 'flow-validator/async/asyncArrayOf';

Implemented types / combinators

Type Flow syntax Runtime type
string string string
number number number
boolean boolean boolean
generic object Object objectType
generic function Function Function
instance of C C instanceOf(C)
class of C Class<C> classOf(C)
array Array<A> arrayOf(A)
intersection A & B intersection(A, B)
union A | B union(A, B)
literal 's' literal('s')
optional ?A optional(A)
map { [key: A]: B } mapping(A, B)
refinement number.refine(n => { if (n > 10) return n; throw new Error(); })
object { name: string } object({ name: string })
exact object {| name: string |} objectExact({ name: string })
null null isNull
undefined void isUndefined
not checked any isAny
all types mixed isMixed
function (a: A) => B takes(A)(returns(B)(...))

Included refinements

Type Refinements Transformations
string .isEmail() .isValidDate() .minLength() .maxLength() .regexp() .toDate()

Technical documentation

for older versions:

git clone https://github.com/freddi301/flow-validator.git
cd flow-validator
yarn
npm run doc:serve

Feature Requests Wanted

(open issue, include examples or links)

Inspiration

flow-io (checkout io-ts too for typescript)

Alternatives

Other Alternatives - not flow typed

Planned Features

Experimental

Other


Code Climate Test Coverage Issue Count

bitHound Overall Score bitHound Code bitHound Dependencies bitHound Dev Dependencies

NSP Status repo size

NPM