GithubHelp home page GithubHelp logo

isabella232 / fargs Goto Github PK

View Code? Open in Web Editor NEW

This project forked from algolia/fargs

0.0 0.0 0.0 39 KB

Simple options manager for JavaScript libraries. Handles type checking, default values assignation and throws generated usage on errors.

License: MIT License

HTML 0.50% JavaScript 98.03% Shell 1.47%

fargs's Introduction

fargs

npm

Build Status Coverage Status

Dependency Status devDependency Status

GitHub license

Simple options manager for JavaScript libraries. It handles type checking, default values assignation and throws generated usage on errors.

Problem

Sometimes, you want to do some basic type checking for a public interface in your library. This always comes at a cost : incertainty or verbosity.

Incertainety

function test(myVar) {
  myVar = myVar || 'default value';

  // What if `myVar` is falsy?
  // What if `myVar` isn't a string?
}

Verbosity

function test(myVar) {
  if (typeof (myVar) === 'undefined') {
    myVar = 'default value';
  }
  if (!(typeof (myVar) === 'string' || myVar instanceof String))) {
    throw new Error('`myVar` should be a string');
  }
}

// 6 lines to do basic type checking and default assignment ?!

ES6 helps here:

function test(myVar = 'default value') {
  if (!(typeof (myVar) === 'string' || myVar instanceof String))) {
    throw new Error('`myVar` should be a string');
  }
}

but we can still improve on that.

Real-life example

Let's take a simple register function. It needs an email, an age and some extra fields.

With ES6, which gives us a cleaner parameters handling than ES5, here is how I would have written this function.

import isArray from 'lodash/isArray';
import isNumber from 'lodash/isNumber';
import isNull from 'lodash/isNull';
import isPlainObject from 'lodash/isPlainObject';
import isString from 'lodash/isString';

const AVAILABLE_PERMISSONS = ['read', 'write', 'update', 'delete'];
const USAGE = `
Usage:
  register(
*   email<string>,
*   age<number>,
    options: <Object>{
      name<string> = 'Default name',
      permissions<Array>: [<string>] = []
    } = {}
  )`;

function throwError(err) {
  throw new Error(`${USAGE}\n------\n${err}`)
}

function register(email, age, options = {}) {
  // Email should be present and match an email RegExp
  if (!isString(email) || !email.match(/^.*@.*\..*$/)) {
    throwError('`email` should be a string and match /^.*@.*\..*$/');
  }

  // Age is required and must be at least 13
  if (!isNumber(age)) {
    throwError('`age` should be a number');
  }
  if (age < 13) {
    throwError('`age` should be at least 13');
  }

  // Options should be a plain object
  if (!isPlainObject(options)) {
    throwError('`options` should be a plain object');
  }

  // Name and permissions get default values
  let {name = 'Default name', permissions = []} = options;

  // Name must be a string
  if (!isString(name)) {
    throwError('`options.name` should be a string');
  }

  // Permissions should be an array of strings which should
  // each be included in a constant permissions array
  if (!isArray(permissions) {
    throwError('`options.permissions` should be an Array');
  }
  permissions.forEach((permission, i) => {
    if (AVAILABLE_PERMISSIONS.indexOf(permission) === -1) {
      throwError(`\`options.permissions[${i}]\` should be part of AVAILABLE_PERMISSIONS`);
    }
  });
}

Notice that you'll lose the ability to use the nested default values in the parameters if you want to type-check options.

With this module, here is how you'd do it:

import fargs from 'fargs';

const AVAILABLE_PERMISSONS = ['read', 'write', 'update', 'delete'];

function register() {
  let [email, age, {name, permissions}] = fargs().check('register')
    .arg('email', {
      type: 'string',
      required: true,
      validators: [fargs.validators.email()]
    })
    .arg('age', {
      type: 'number',
      required: true,
      validators: [fargs.validators.greaterThan(12)]
    })
    .arg('options', {
      type: 'Object',
      value: {},
      children: {
        name: {type: 'string', value: 'default name'}
        permissions: {type: Array, element: {{
          type: 'string',
          validators: [fargs.validators.includedIn(AVAILABLE_PERMISSIONS)]
        }}}
      }
    })
    .values(arguments);
  // Here
  // - Generated usage was thrown if:
  //   - Any of the required fields was not provided
  //   - Any type mismatch
  //   - Any validator failure
  // - Else default values have been assigned in an nested way
}

Part of the logic can be precomputed. If you expect these calls to be run often, an easy way to optimize it is to extract the structure building from the function call, by using the .structure(name, object) method.

Structure

A structure can receive multiple parameters.

  • type: Only required attribute.
    String describing the type of the variable.
    Accepts multiple types with this syntax: number|string|Array.
    If you want to allow any type, just use the any type. If you want to pass a default value, in order to be able to print it correctly in the usage, use valueType|any, like string|any.

  • required: Is this element required? Defaults to false.

  • value: Default value.
    A clone of this is used to initialize the returned value if the value passed is undefined. Incompatible with required.

  • computeValue: Accepts a function with no argument.
    To use for the few cases where _.clone isn't what you want.
    lodash's clone() method doesn't work on functions, DOMElements, WeakMaps for instance. Incompatible with required.

  • element: Describe the structure each element of an array should have.
    Do not use required here if you want to allow undefined. Explicitly use the undefined type instead.

    {type: 'Array', element: {
      {type: 'string|undefined'}
    }}
    // Accepts an Array which should contain only strings or undefined.
  • children: Accepts the children structure. A structure with an Array type will expect children structures in an array:

    {type: 'Array', required: true, children: [
      {type: 'number', required: true},
      {type: 'string'}
    ]}
    // Accepts an array where:
    // - the first child should be a number and is required
    // - the second child should be a string and is not required

Types

This package comes with these built-in types:

  • *undefined: Only useful for element
  • *null
  • *boolean
  • *number
  • *string
  • *function
  • *Array
  • *Object
  • RegExp

* Embedded by default.

You can add non-default types by doing so:

// fargs.js
import fargs from 'fargs';

export default fargs().registerTypes([
  // Use one of the provided ones
  require('fargs/types/RegExp'),
  // Or use your own
  {
    name: 'MyCustomType',
    checker: (elt) => elt instanceof MyCustomType,
    printer: (elt) => `<#MyCustomType ${elt.id}>`
  }
]);

Validators

Embedded validators:

Validator Error
email ... should be an email
greaterThan(*n<number>) ... should be greater > than n
includedIn(*arr<Array[any]>, alias<string>) ... should be included in `alias
lowerThan(*n<number>) ... should be lower < than n
match(*r<RegExp>) ... should match r
maxLength(*n<number>)] ... should be at most n characters long
minLength(*n<number>) ... should be at least n characters long
strictlyEquals(*obj<any>, alias<string>) ... should be strictly equal === to `alias

There are no validators enabled by default. You need to add them that way:

// fargs.js
import fargs from 'fargs';

export default fargs().registerValidators([
  // Use one of the provided ones
  require('fargs/validators/strictlyEquals'),
  // Or use your own
  {
    name: 'inRange',
    validator: (min, max) =>
      (n) => (min <= n && n <= max) || `should be between \`${min}\` and \`${max}\``
  }
]);

License

This library is packaged under the MIT License

fargs's People

Contributors

jerska avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.