@mapbox/fusspot

    0.8.1 • Public • Published

    @mapbox/fusspot

    Build Status

    Fusspot is a tiny runtime type-assertion library.

    It can run in the browser as well as Node, and it's lightweight, flexible, and extensible.

    Table Of Contents

    Why does this exist?

    Many existing runtime type-assertion libraries solve a special problem, like form validation or component props, or aren't great for usage in the browser, because of size or syntax. We wanted something similar to React's prop-types but not attached to a specific use case. So we ended up creating Fusspot.

    Installation

    npm install @mapbox/fusspot

    Usage

    Basic

    In the example below we have a simple validation of an object and its properties. The outermost validator v.shape checks the shape of the object and then runs the inner validator v.arrayOf(v.string) to validate the value of names property.

    @mapbox/fusspot exports a single object for its API. In the examples below we name it v (for "validator").

    const v = require("@mapbox/fusspot");
    
    assertObj = v.assert(
      v.shape({
        names: v.arrayOf(v.string)
      })
    );
    
    assertObj({ names: ["ram", "harry"] }); // pass
    assertObj({ names: ["john", 987] }); // fail
    assertObj({ names: "john" }); // fail

    Required

    By default null and undefined are acceptable values for all validators. To not allow null/undefined as acceptable values, you can pass your validator to v.required to create a new validator which rejects undefined/null.

    // without v.required
    assertObj = v.assert(
      v.shape({
        name: v.string
      })
    );
    assertObj({}); // pass
    assertObj({ name: 'ram' }); // pass
    assertObj({ name: undefined }); // pass
    assertObj({ name: null }); // pass
    assertObj({ name: 9 }); // fail
    
    // with v.required
    strictAssertObj = v.assert(
      v.shape({
        name: v.required(v.string)
      })
    );
    
    strictAssertObj({}); // fail
    strictAssertObj({ name: 'ram' }); // pass
    strictAssertObj({ name: undefined }); // fail
    strictAssertObj({ name: null }); // fail
    strictAssertObj({ name: 9 }); // fail

    Composition

    You can compose any of the Higher-Order Validators to make complex validators.

    const personAssert = v.assert(
      v.shape({
        name: v.required(v.string),
        pets: v.arrayOf(
          v.shape({
            name: v.required(v.string),
            type: v.oneOf("dog", "pig", "cow", "bird")
          })
        )
      })
    );
    
    // assertion passes
    personAssert({
      name: "john",
      pets: [
        {
          name: "susy",
          type: "bird"
        }
      ]
    });
    
    // assertion fails
    personAssert({
      name: "harry",
      pets: [
        {
          name: "john",
          type: "mechanic"
        }
      ]
    });
    // Throws an error
    //   pets.0.type must be "dog", "pig", "cow", or "bird".
    const personAssert = v.assert(
      v.shape({
        prop: v.shape({
          person: v.shape({
            name: v.oneOfType(v.arrayOf(v.string), v.string)
          })
        })
      })
    );
    
    // assertion passes
    personAssert({ prop: { person: { name: ["j", "d"] } } });
    personAssert({ prop: { person: { name: ["jd"] } } });
    
    // assertion fails
    personAssert({ prop: { person: { name: 9 } } });
    // Throws an error
    //   prop.person.name must be an array or string.

    Assertions

    v.assert(rootValidator, options)

    Returns a function which accepts an input value to be validated. This function throws an error if validation fails else returns void.

    Parameters

    • rootValidator: The root validator to assert values with.
    • options: An options object or a string. If it is a string, it will be interpreted as options.description.
    • options.description: A string to prefix every error message with. For example, if description is myFunc and a string is invalid, the error message with be myFunc: value must be a string. (Formerly options.apiName, which works the same but is deprecated.)
    v.assert(v.equal(5))(5); // undefined
    v.assert(v.equal(5), { description: "myFunction" })(10); // Error: myFunction: value must be 5.
    v.assert(v.equal(5), 'Price')(10); // Error: Price: value must be 5.

    Primitive Validators

    v.any

    This is mostly useful in combination with a higher-order validator like v.arrayOf;

    const assert = v.assert(v.any);
    assert(false); // pass
    assert("str"); // pass
    assert(8); // pass
    assert([1, 2, 3]); // pass

    v.boolean

    const assert = v.assert(v.boolean);
    assert(false); // pass
    assert("true"); // fail

    v.number

    const assert = v.assert(v.number);
    assert(9); // pass
    assert("str"); // fail

    v.finite

    const assert = v.assert(v.finite);
    assert(9); // pass
    assert("str"); // fail
    assert(NaN); // fail

    v.plainArray

    const assert = v.assert(v.plainArray);
    assert([]); // pass
    assert({}); // fail

    v.plainObject

    const assert = v.assert(v.plainObject);
    assert({}); // pass
    assert(new Map()); // fail

    v.func

    const assert = v.assert(v.func);
    assert('foo'); // fail
    assert({}); // fail
    assert(() => {}); // pass

    v.date

    Passes if input is a Date that is valid (input.toString() !== 'Invalid Date').

    const assert = v.assert(v.date);
    assert('foo'); // fail
    assert(new Date('2019-99-99')); // fail
    assert(new Date()); // pass
    assert(new Date('2019-10-04')); // pass

    v.string

    const assert = v.assert(v.string);
    assert("str"); // pass
    assert(0x0); // fail

    v.nonEmptyString

    const assert = v.assert(v.nonEmptyString);
    assert("str"); // pass
    assert(""); // fail
    assert(7); // fail

    Higher-Order Validators

    Higher-Order Validators are functions that accept another validator or a value as their parameter and return a new validator.

    v.shape(validatorObj)

    Takes an object of validators and returns a validator that passes if the input shape matches.

    const assert = v.assert(
      v.shape({
        name: v.required(v.string),
        contact: v.number
      })
    );
    
    // pass
    assert({
      name: "john",
      contact: 8130325777
    });
    
    // fail
    assert({
      name: "john",
      contact: "8130325777"
    });

    v.strictShape(validatorObj)

    The same as v.shape, but rejects the object if it contains any properties that are not defined in the schema.

    const assert = v.assert(
      v.strictShape({
        name: v.required(v.string),
        contact: v.number
      })
    );
    
    // passes, just like v.shape
    assert({
      name: "john",
      contact: 8130325777
    });
    
    // fails, just like v.shape
    assert({
      name: "john",
      contact: "8130325777"
    });
    
    // fails where v.shape would pass, because birthday is not defined in the schema
    assert({
      name: "john",
      birthday: '06/06/66'
    });

    v.objectOf(validator)

    Takes a validator as an argument and returns a validator that passes if and only if every value in the input object passess the validator.

    const assert = v.assert(
      v.objectOf({ name: v.required(v.string) })
    );
    
    // pass
    assert({
      a: { name: 'foo' },
      b: { name: 'bar' }
    });
    
    // fail
    assert({
      a: { name: 'foo' },
      b: 77
    });

    v.arrayOf(validator)

    Takes a validator as an argument and returns a validator that passes if and only if every item of the input array passes the validator.

    const assert = v.assert(v.arrayOf(v.number));
    assert([90, 10]); // pass
    assert([90, "10"]); // fail
    assert(90); // fail

    v.tuple(...validators)

    Takes multiple validators that correspond to items in the input array and returns a validator that passes if and only if every item of the input array passes the corresponding validator.

    A "tuple" is an array with a fixed number of items, each item with its own specific type. One common example of a tuple is coordinates described by a two-item array, [longitude, latitude].

    const assert = v.assert(v.tuple(v.range(-180, 180), v.range(-90, 90)));
    assert([90, 10]); // pass
    assert([90, "10"]); // fail
    assert([90, 200]); // fail
    assert(90); // fail

    v.required(validator)

    Returns a strict validator which rejects null/undefined along with the validator.

    const assert = v.assert(v.arrayOf(v.required(v.number)));
    assert([90, 10]); // pass
    assert([90, 10, null]); // fail
    assert([90, 10, undefined]); // fail

    v.oneOfType(...validators)

    Takes multiple validators and returns a validator that passes if one or more of them pass.

    const assert = v.assert(v.oneOfType(v.string, v.number));
    assert(90); // pass
    assert("90"); // pass

    v.equal(value)

    Returns a validator that does a === comparison between value and input.

    const assert = v.assert(v.equal(985));
    assert(985); // pass
    assert(986); // fail

    v.oneOf(...values)

    Returns a validator that passes if input matches (===) with any one of the values.

    const assert = v.assert(v.oneOf(3.14, "3.1415", 3.1415));
    assert(3.14); // pass
    assert(986); // fail

    v.range([valueA, valueB])

    Returns a validator that passes if input inclusively lies between valueA & valueB.

    const assert = v.assert(v.range([-10, 10]));
    assert(4); // pass
    assert(-100); // fail

    v.instanceOf(Class)

    Returns a validator that passes if input is an instance of the provided Class, as determined by JavaScript's instanceof operator.

    class Foo {}
    class Bar {}
    class Baz extends Bar {}
    
    const assert = v.assert(v.instanceOf(Bar))
    assert(new Bar()); // pass
    assert(new Baz()); // pass
    assert(new Foo()); // fail

    Custom validators

    One of the primary goals of Fusspot is to be customizable out of the box. There are multiple ways to which one can create a custom validator. After creating a custom validator you can simply use it just like a regular validator i.e. pass it to v.assert() or use it with Higher-Order Validators.

    Simple

    A simple custom validator is a function which accepts the input value and returns a string if and only if the input value doesn't pass the test. This string should be a noun phrase describing the expected value type, which would be inserted into the error message like this value must be a(n) <returned_string>. Below is an example of a path validator for node environment.

    const path = require('path');
    
    function validateAbsolutePaths(value) {
      if (typeof value !== 'string' || !path.isAbsolute(value)) {
        return 'absolute path';
      }
    }
    
    const assert = v.assert(validateAbsolutePaths);
    assert('../Users'); // fail
    // Error: value must be an absolute path.
    assert('/Users'); // pass
    
    **For more examples look at the [src code](https://github.com/mapbox/fusspot/blob/master/lib/index.js#L238).**

    Customizing the entire error message

    If you need more control over the error message, your validator can return a function ({path}) => '<my_custom_error_message>' for custom messages, where path is an array containing the path (property name for objects and index for arrays) needed to traverse the input object to reach the value. The example below help illustrate this feature.

    function validateHexColour(value) {
      if (typeof value !== "string" || !/^#[0-9A-F]{6}$/i.test(value)) {
        return ({ path }) =>
          `The input value '${value}' at ${path.join(".")} is not a valid hex colour.`;
      }
    }
    
    const assert = v.assert(
      v.shape({
        colours: v.arrayOf(validateHexColour)
      })
    );
    
    assert({ colours: ["#dedede", "#eoz"] }); // fail
    // Error: The input value '#eoz' at colours.1 is not a valid hex colour.
    assert({ colours: ["#abcdef"] }); // pass

    Install

    npm i @mapbox/fusspot

    DownloadsWeekly Downloads

    94,989

    Version

    0.8.1

    License

    BSD-2-Clause

    Unpacked Size

    60.8 kB

    Total Files

    12

    Last publish

    Collaborators

    • mapbox-npm-01
    • mapbox-npm-02
    • mapbox-npm-07
    • mapbox-npm-03
    • mapbox-npm-04
    • mapbox-npm-09
    • mapbox-npm-05
    • mapbox-npm-06
    • mapbox-npm-08
    • mapbox-npm-advanced-actions
    • mapbox-npm-ci
    • mapbox-npm
    • mapbox-admin
    • mapbox-machine-user