Skip to content

sparkcentral/flow-validator

 
 

Repository files navigation

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';

describe("readme code", () => {
  it("works", () => {
    // { 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 -- MORE WORK NEEDED
    // 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(
      // eslint-disable-line no-console
      Contact.parse({
        name: "fred",
        birth: String(new Date()),
        email: "gobi301@gmail.com"
      })
    );

    // ensure functions params, useful on user input functions
    const #User = takes(string.isEmail(), number)(
      (email, secretCoupon) =>
        `user ${email} added with coupon: ${secretCoupon}`
    );
    #User("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;

    // runtime introspection
    const Name: Type<string> = Person.schema.name;
    const Age: Type<?number> = Person.schema.age;

    // const tup: [string, number, Date] = ...
    const tup = tuple([string, number, instanceOf(Date)]).parse([
      "hello",
      4,
      new Date()
    ]);

    // { 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`
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
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()

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

  • 0.7.0
  • 0.8.0
    • generate documentation from types (md, html, jsonschema, blueprint, mson, graphql-schema)
    • generate flow-validator validators from flow annotations, jsonschema and graphql-schema (cli, runtime, compiletime, startuptime)
    • json schema validation
  • 0.9.0
    • refactor validators to contain metadata
    • refactor errors to contain metadata
    • write visitors for validators and errors
    • write default interpreters for errors json and optionally localized text
  • 1.0.0
    • test 100%
    • doc examples for all validators
    • better flow coverage where possible
    • readme += new type example
  • 2.0.0

Experimental

  • readme += alternate use: json graphql alternative
  • rewrite match
  • Vmatch asyncMatch asyncVmatch
  • overloading
  • monad do notation using row polymorphism
  • auto row currying (aka builder)
  • literal values

Other

  • move documentation to surge.sh

Code Climate Test Coverage Issue Count

bitHound Overall Score bitHound Code bitHound Dependencies bitHound Dev Dependencies

NSP Status repo size

NPM

About

object validation with flow types and more

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 97.7%
  • Shell 1.8%
  • HTML 0.5%