Home

Brian Donovan edited this page Mar 10, 2018 · 23 revisions

Wiki

LGTM* is a JavaScript library for object validation. LGTM provides a convenient, expressive, and powerful builder syntax for declaring your validations. It serves as the core of your application's validation logic, leaving concerns about how and when to validate, how to display the results, and content localization entirely in your hands. LGTM supports asynchronous operation with promises and does its best to stay out of your way, letting you work the way you want to.

Resources

Installing

Browser

Use yarn (or npm) to install LGTM and include it in your build:

$ yarn add lgtm  # or `npm install --save lgtm`

Or, copy dist/lgtm.js file into your project and load it as normal. LGTM assumes Promise is available, but you can provide your own promise library if you want. See the Configuration section below for details.

LGTM is a UMD module, so LGTM will register itself with any CommonJS or AMD setup you have. If it doesn't find either of those it will export LGTM as a global with the module object.

LGTM should work in any modern browser, though it's been tested mostly in the latest versions of Chrome, Safari, and Firefox.

Node.js

$ yarn add lgtm
// using ES modules
import * as LGTM from 'lgtm';
// using CommonJS
const LGTM = require('lgtm');

Usage

LGTM can be used in either a browser or Node.js environment. These examples will use the LGTM export from the main file as if it were a global in the browser or you had done import * as LGTM from 'lgtm'; in your Node.js files.

Basic Validation

You need to make a validator and then tell it what attributes you want to validate. You can use the LGTM.validator() function which returns a builder to help you build a validator:

const person = {
  firstName: 'Brian',
  lastName: null,
  age: 30
};

const lastNameRequired = true;

const validator = LGTM.validator(
  LGTM.validates('firstName').required('You must enter a first name.'),
  LGTM.validates('lastName')
    .when(() => lastNameRequired)
    .required('You must enter a last name.'),
  LGTM.validates('age')
    .optional()
    .using(age => age > 18, 'You must be over 18.')
).build();

Once you have the validator you can use it on any object you want. All validation is done asynchronously, which means that validation errors may be returned in an unexpected order. Validation results are returned via a promise:

// Validate all attributes and return results with a promise.
const result = await validator.validate(person)
console.log(result); // { "valid": false, "errors": { "firstName": [ ], "lastName": ["You must enter a last name."], "age": [ ] } }

If you prefer the callback style, you can use that instead:

validator.validate(person, (err, result) => {
  // ...
});

When using the callback style, LGTM follows the node.js convention of passing any exception thrown or runtime error generated as the first argument. If there is no exception then err will be null, even if the validated object turned out to be invalid. When using the promise style runtime errors will be handled as a rejected promise.

LGTM supports async using promises. If the result of a validation function is a thenable (read: promise) then the result of that validation will be whatever the promise resolves to. This could allow you to check with your server for usernames being taken, for example:

const validator = LGTM.validator(
  LGTM.validates('username').using(username =>
    $.getJSON('/check-username', { username }).then(({ taken }) => !taken)
  )
).build();

Multiple Validations Per Attribute

Each attribute may have multiple validations run by chaining them together:

const validator = LGTM.validator(
  LGTM.validates('username')
    .required('Please enter a username')
    .using(
      username => !username || username[0] !== '$',
      "Usernames may not start with '$'"
    )
).build();

If you prefer, you can repeat the validates call to keep one validation each:

const validator = LGTM.validator(
  LGTM.validates('username').required('Please enter a username'),
  LGTM.validates('username').using(
    username => !username || username[0] !== '$',
    "Usernames may not start with '$'"
  )
).build();

Note that in both cases both validations will run, even if the first one fails. This may require that you guard against null or undefined values as in the examples above.

Custom Validations

If you find yourself using the same validations in several places you can register your custom validation and use it just like the built-in register() validation:

LGTM.helpers.register('isEven', function(message) {
  this.using(value => value % 2 === 0, message);
});

Then just go ahead and use it just like any other validation helper:

LGTM.validator(
  LGTM.validates('age').isEven('You must have an even age!')
).build();

To see more information about writing custom helpers see the Custom Helpers page.

Multi-Attribute Validations

Sometimes an attribute value will be valid or not based on the value of some other property on the object. In cases like that you can use the additional arguments passed to validations to access more information about the object being validated:

// businesses only need street addresses if they're not mobile
LGTM.validator(
  LGTM.validates('street1').using(
    'street1',
    'mobile',
    (street1, mobile) => street1 || mobile,
    'Please enter a street address.'
  )
).build();

The downside of this approach is that we're using street1 as a substitute for required(), though they aren't quite equivalent. Fortunately, we can still use required() with a little bit of help from when():

// businesses only need street addresses if they're not mobile
LGTM.validator(
  LGTM.validates('street1')
    .when('mobile', mobile => !mobile)
    .required('Please enter a street address.')
).build();

For more information on these methods, see the API reference for using() and when().

Configuration

If you have a specific promise library in your application and don't want LGTM to use the Promise implementation built into your JavaScript environment, then you can configure LGTM to use it by providing a Promise constructor:

// use LGTM with Q
LGTM.configure('Promise', Q.Promise);

// use LGTM with Ember's bundled RSVP
LGTM.configure('Promise', Ember.RSVP.Promise);

LGTM also needs to access properties on the object being validated. Currently, it will use a get method on the object if present to access properties. That is, instead of object[property] it will use object.get(property). This behavior is deprecated and will be removed in LGTM 2.0. To configure the get function for an Ember application, for example, do this: LGTM.configure('get', Ember.get);.

Compatibility

Some JavaScript frameworks such as Ember use a method for property access to support computed properties. LGTM supports the Ember approach and automatically calls object.get(attr) instead of object[attr] to get the property value if get() is present on the object being validated.

At this time LGTM does not have explicit integration with other libraries or frameworks, but if you make an integration layer for a framework such as Ember, Backbone, or Angular I'd be happy to list it here.

Contributing

Setup

First, install the development dependencies:

$ yarn

Then, try running the tests:

$ yarn test

Pull Requests

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Any contributors to the master lgtm repository must sign the Individual Contributor License Agreement (CLA). It's a short form that covers our bases and makes sure you're eligible to contribute.

When you have a change you'd like to see in the master repository, send a pull request. Before we merge your request, we'll make sure you're in the list of people who have signed a CLA.

* LGTM is an initialism meaning "looks good to me".