npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

approved

v2.1.2

Published

Schema-based data filtering, validation and error handling.

Downloads

12

Readme

Build Status NPM Version Dependency Status

approved.js

Schema-based data filtering, validation and error handling.

Approved.js allows for filtering and validating complex javascript objects and error handling. It has a simple, unified and beautiful API which fully supports asynchronous processing.

It is an open source package. The source code is available on GitHub where you can also find our issue tracker.

Motivation

We often write code which validates data and handles errors. We do it when we write API controllers, GraphQL mutations, before we try to write something into a database and in many other cases where we manipulate and mutate data.

Every time you start writing validations, you ask yourself the same questions. You try hard finding the best way to get a clean and beautiful solution. At the end you desperately start googling for answers, searching best practices and possible conventions.

We need to write a clean and beautiful code to keep our projects long-term sustainable. Validation happens before actual action and error handling happens afterwords - validation and error handling go hand in hand. Approved.js has been written with that in mind.

Installation

Install the package by running the command below.

$ npm install --save approved

Continue reading to see how this package is supposed to be used.

Usage

Below, we create a simple example to show the benefit of using Approved.js in your Node.js projects. In this tutorial we filter and validate data and then create a new document in a database. If something goes wrong, an error is handled and parsed into a user-friendly format.

To make things as clean as possible, we use Babel with ES7 features thus we can wrap our code into the async block.

(async function() {
  // code here
})().catch(console.error);

For the purpose of this tutorial let's first define an imaginary data object which we will later validate and save to the database.

const data = {
  firstName: 'John',
  lastName: 'Smith',
  email: '[email protected]'
}

Next, let's create a simple schema for the data above.

import {Approval} from 'approved';

let schema = new Approval();

We can filter data and extract only the paths defined by filters. Filters also cast data to the right data type.

schema.addFilter({
  path: 'email',
  type: 'string'
});

data = await schema.filter(data); // check the API for method options

We define a filter and then we call the filter method to extract, cast and modify the data object.

Let's continue by adding validations. We need to verify the email before we store it to the database.

schema.addValidation({
  path: 'name',
  validator: 'isPresent',
  message: 'must be present'
});

try {
  await schema.validate(data); // check the API for method options
} catch(e) {
  // validation failed
}

Similar to filters, we first add the validation, then we execute the validate method to verify the data object. If validation fails the method throws a ValidationError.

Let's update the try/catch block above with an error handler.

let errors = null;
try {
  await schema.validate();
} catch(e) {
  errors = await schema.handle(e); // check the API for method options
  if (!errors) throw e; // unhandled error
}

To show the real benefit of the handle method let's use the MongoDB driver to store data, with a unique index on the email field, named uniqueEmail. This triggers the E11000 error when the code is executed for the second time. How to use the MongoDB driver and how to create a unique index is out of scope for this tutorial and you'll have to figure that yourself.

import {MongoClient} from 'mongodb';

const mongo = await MongoClient.connect('mongodb://localhost:27017/approved');

The handle method handles the ValidationError by default. To handle a MongoError, we need to define a handler. Here we also use the mongo-error-parser helper for parsing MongoError message (don't forget to install it).

import mongoParser from 'mongo-error-parser';

schema.addHandler({
  path: 'email',
  error: 'MongoError',
  block: async (err) => err.code === 11000 && mongoParser(err).index === 'uniqueEmail',
  message: 'is already taken'
});

We can now update the try/catch block defined earlier with the logic for storing data to the database.

let errors = null;
try {
  await schema.validate(data);
  data = await mongo.collection('users').insertOne(data);
} catch(e) {
  errors = await schema.handle(e);
  if (!errors) throw e; // unhandled error
}

That's it. We can now show a well structured data object to a user.

console.log({data, errors}); // cool for GraphQL

Note that it's a good practice to store schema classes into the ./approvals directory. See the included example (npm run example) and the source code for more.

API

The core of this package represents the Approval class.

import {Approval} from 'approved';

const schema = new Approval(config);

| Param | Type | Required | Default | Description |-------|------|----------|---------|------------ | config | Object | No | - | A configuration object accepting keys types={}, validators={}, filters=[], validations=[] and handlers=[].

Instance Variables

schema.filters

Holds a list of defined inout filters.

schema.handlers

Holds a list of defined error handlers.

schema.types

Holds a list of defined data types.

schema.validations

Holds a list of defined validations.

schema.validators

Holds a list of defined validators.

Instance Methods

schema.addFilter(filter)

Adds a filter to the list of filters.

schema.addHandler(handler)

Adds a handler to the list of handlers.

schema.addValidation(validation)

Adds a validation to the list of validations.

schema.filter(data, context, {strict});

Filters a data object against the filters provided by the addFilter method.

| Param | Type | Required | Default | Description |-------|------|----------|---------|------------ | data | Object | Yes | - | A data object. | context | Object | No | {} | A context object which is passed into filters. | strict | Boolean | No | false | Removes data object keys which are not defined by the filters when true.

schema.handle(error, context);

Handles the provided Error object based on the provided handlers.

| Param | Type | Required | Description |-------|------|----------|------------ | error | Object | Yes | Error class instance or an error object. | context | Object | No | {} | A context object which is passed into handlers.

schema.removeFilterAtIndex(index)

Removes a filter from the list of filters.

schema.removeHandlerAtIndex(index)

Removes a handler from the list of handlers.

schema.removeValidationAtIndex(index)

Removes a validation from the list of validations.

schema.setType(name, fn)

Registers a data type method.

| Param | Type | Required | Description |-------|------|----------|------------ | name | String | Yes | Type name. | fn | Function | Yes | Type method.

schema.setValidator(name, fn)

Registers a validator.

| Param | Type | Required | Description |-------|------|----------|------------ | name | String | Yes | Validator name. | fn | Function | Yes | Validator method.

schema.unsetType(name)

Unregisters a data type method.

| Param | Type | Required | Description |-------|------|----------|------------ | name | String | Yes | Type name.

schema.unsetValidator(name)

Unregisters a validator.

| Param | Type | Required | Description |-------|------|----------|------------ | name | String | Yes | Validator name.

schema.validate(data, context);

| Param | Type | Required | Default | Description |-------|------|----------|---------|------------ | data | Object | Yes | - | A data object. | context | Object | No | {} | A context object which is passed into validators.

Validates the data object against the validations provided by the addValidation method. It throws a ValidationError if an object is not valid.

Filters

Filter object defines how a value of an data object key is casted and filtered by the filter method.

| Key | Type | Required | Description |-----|------|----------|------------ | path | String | Yes | Key name of a data object (e.g. firstName). Nested object paths are also supported (e.g. users.name.first) | type | String | Yes | Data type name (possible values are boolean, date, float, integer or string). | block | Function | No | Synchronous or asynchronous resolver for modifying key value (e.g. async (s) => s).

const filter = {
  path: 'email',
  type: 'string',
  block: (s, o) => s.toLowerCase()
};

Validations

Validation object defines how a value of a data object key is validated by the validate method.

| Key | Type | Required | Description |-----|------|----------|------------ | path | String | Yes | Key name of a data object (e.g. firstName). Nested object paths are also supported (e.g. users.name.first) | validator | String | Yes | Validator name (see the Built-in Validators section for a list of available names). | options | Object | No | Validator settings. | message | String | Yes | Output error message explaining what went wrong.

let validation = {
  path: 'name',
  validator: 'isPresent',
  message: 'must be present'
};

Built-in Validators

contains

Checks if the string contains the seed.

| Option | Type | Required | Default | Description |--------|------|---------|----------|------------ | seed | String | Yes | - | The seed which should exist in the string.

isAbsent

Validates that the specified attribute is blank.

isBase64

Validates that the specified attribute is base64 encoded string.

isByteLength

Validates that the specified attribute is a string where length (in bytes) falls in a range.

| Option | Type | Required | Default | Description |--------|------|----------|---------|------------ | min | Integer | Yes | 0 | Minimum number in bytes. | max | Integer | Yes | - | Maximum number in bytes.

isCreditCard

Validates that the specified attribute is a credit card number.

isDate

Validates that the specified attribute is a date string.

| Option | Type | Required | Default | Description |--------|------|----------|---------|------------ | format | String | No | - | Date format (possible value is iso8601).

isEmail

Validates that the specified attribute is an email.

| Option | Type | Required | Default | Description |--------|------|----------|----------|------------ | allowDisplayName | Boolean | No | false | When set to true, the validator will also match name <address>. | allowUtf8LocalPart | Boolean | No | false | When set to false, the validator will not allow any non-English UTF8 character in email address' local part. | requireTld | Boolean | No | true | When set to false, email addresses without having TLD in their domain will also be matched.

isExcluded

Validates that the specified attribute is not in an array of restricted values.

| Option | Type | Required | Description |--------|------|----------|------------ | values | Array | Yes | Array of restricted values.

isFQDN

Validates that the specified attribute is a fully qualified domain name (e.g. domain.com).

| Option | Type | Required | Description |--------|------|----------|------------ | requireTld | Boolean | No | Require top-level domain name. | allowUnderscores | Boolean | No | Allow string to include underscores. | allowTrailingDot | Boolean | No | Allow string to include a trailing dot.

isHexadecimal

Validates that the specified attribute is a hexadecimal number.

isHexColor

Validates that the specified attribute is a hexadecimal color string.

isIncluded

Validates that the specified attribute is in an array of allowed values.

| Option | Type | Required | Description |--------|------|----------|------------ | values | Array | Yes | Array of allowed values.

isIP

Validates that the specified attribute is an IP.

| Option | Type | Required | Default | Description |--------|------|----------|---------|------------- | version | Integer | No | - | IP version (4 or 6).

isISBN

Validates that the specified attribute is an International Standard Book Number.

| Option | Type | Required | Default | Description |--------|------|----------|---------|------------ | version | Integer | No | - | IP version (10 or 13).

isISIN

Validates that the specified attribute is an International Securities Identification.

isJSON

Validates that the specified attribute is a stringified JSON string.

isLength

Validates the length of the specified attribute.

| Option | Type | Required | Default | Description |--------|------|----------|----------|------------ | min | Number | No | 0 | Minimum number of characters. | max | Number | No | - | Maximum number of characters.

isLowercase

Validates that the specified attribute is lowercase.

isMACAddress

Validates that the specified attribute is a MAC address.

isMongoId

Validates that the specified attribute is a valid hex-encoded representation of a MongoDB ObjectId.

isPresent

Validates that the specified attribute is not blank.

isUppercase

Validates that the specified attribute is uppercase.

isURL

Validates that the specified attribute is an URL.

| Option | Type | Required | Default | Description |--------|------|----------|---------|------------ | protocols | Array | No | ['http', 'https', 'ftp'] | List of known protocols (e.g. http, https, ftp). | requireTld | Boolean | No | true | Require top-level domain name. | requireProtocol | Boolean | No | true | Require URL protocol. | requireValidProtocol | Boolean | No | true | Require a valid protocol. | allowUnderscores | Boolean | No | false | Allow using underscores. | allowTrailingDot | Boolean | No | false | Allow trailing dot. | allowProtocolRelativeUrls | Boolean | No | false | Allow protocol relative urls (e.g. //foobar.com)

isUUID

Validates that the specified attribute is a UUID.

| Option | Type | Required | Description |--------|------|----------|------------ | version | Integer | No | UUID version (3, 4 or 5).

isValid

Validates the specified attribute against the provided block function. If the function returns true then the attribute is treated as valid.

| Option | Type | Required | Description |--------|------|----------|------------ | block | Function | Yes | Synchronous or asynchronous function (e.g. async () => true)

let validation = {
  path: 'name',
  validator: 'isValid',
  options: {block: async (value, options) => true},
  message: 'must be present'
};
matches

Validates that the specified attribute matches the pattern.

| Key | Type | Required | Description |-----|------|----------|------------ | pattern | String | Yes | Regular expression pattern. | modifiers | String | No | Regular expression modifiers.

Handlers

Handler object defines how an error is handled by the handle method.

| Key | Type | Required | Description |-----|------|----------|------------ | path | String | Yes | The output key name or a key name of a data object to which the error refers to. | error | Object | Yes | Error class instance. | block | Function | No | Synchronous or asynchronous helper to additionally check if the handler applies to the provided error. | message | String | Yes | Output error message explaining what went wrong.

let handler = {
  path: 'request',
  error: 'Error',
  block: async (err) => err.code === 400,
  message: 'bad request'
};

Advanced Usage

You can customize how this module behaves by extending the Schema class or by defining your custom types and validators (check the source code for more).

Custom Type

Use the setType method to define a custom data type.

let schema = new Schema();

schema.setType('cooltype', (value, context) => {
  return `cool-${value}`; // not a very smart example :)
});

Custom Validator

Use the setValidator method to define a custom validator.

let schema = new Schema();

schema.setValidator('isCool', async (value, context) => {
  return str === 'cool'; // not a very smart example :)
});

Contribute

Let's make this package even better. Please contribute!

License (MIT)

Copyright (c) 2016 Kristijan Sedlak <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.