0.1.0 • Published 9 years ago

to.is v0.1.0

Weekly downloads
6
License
-
Repository
github
Last release
9 years ago

NPM version Build Status Coveralls Status Downloads

to.is

What is this?

Data filtering, sanitization and validation.

This lib is built on top of the excellent validator.js lib.

Requirements

  • Browsers: Modern browsers and IE 9+
  • Node: >= 0.10.0
  • validator.js >= 3.33.0

Installing (Browser)

$ bower install to.is --save
<script src="validator.min.js"></script>
<script src="to.is.min.js"></script>
<script>
to.lower().is.email("test@test.com");
</script>

NOTE: Not tested thoroughly in browsers currently, use at your own risk!

Installing (Node.js)

$ npm install to.is --save
var to = require('to.is').to,
    is = require('to.is').is;

Usage

Overview

to.filter1(options).filter2(options).is.constraint1().constraint2().type
// -or-
is.constraint1().constraint2().type

Examples

var GmailAddress = to.trim().normalizeEmail().is.endsWith("@gmail.com").email;
if (GmailAddress("test@gmail.com")){
    // it is true!
}

var results = GmailAddress.from("  test+extra@GMAIL.com  ");
var normalizedEmail = results.data; // === "test@gmail.com"
var errors = results.errors;

Only when you use to, trim() is automatically performed at the start and need not be specified. To disable, use trim(false).

var Profile = is.has({
    email: to.normalizeEmail().is.maxLen(100).email,
    username: to.lower().is.maxLen(100).str,
    age: is.within(13, 99).int,
}).obj;

var results = Profile.from({
    email: "tEsT@GMAIL.com",
    username: "  TEST  ",
    age: "99",
});
// === {email: "test@gmail.com", username: "test", age: 99}
var parsedProfile = results.data;

// Reusable
var results2 = Profile.from({
    email: "tEsT@GMAIL.com",
    age: 98.5,
});

There is no limit to nesting.

How It Works

to and is construct a chain of filters and constraints respectively. The chain is executed from left to right like a pipeline: processing the input according to the filters, parsing the processed input according to the type, and finally checking the constraints. The output of a filter is passed to the next as input.

The philosophy is that parsing is forgiving and tries to convert the input to the target type as much as possible.

At the end of the is chain, a type must be specified, after which a function is returned. Calling it sparks off the chain and returns a boolean indicating whether the raw input, after being processed by the filters and parsed, conforms to the constraints. You may also call from() to parse the input and retrieve any errors at the same time.

The input is never modified. If the input is an object, the parser makes a shallow clone first. Many of the types are immutable anyway, so there is no way to modify them.

undefined denotes "no value" and will not be processed nor parsed. The only way to process undefined is to specify a default value. For example,

to.def(0).is.int.from(undefined).data // === 0

// contrast with:
is.int.from(undefined).data           // === undefined

Types

Not to be confused with JS builtin types.

str

Coerced to a string.

The rules for coercion are the same as that of validator's, except that undefined remains as it is. In summary,

  1. null, NaN, functions and arrays are replaced with an empty string ''.
  2. toString is applied if available.
  3. concat with an empty string.

int

Parsed to a number (int).

If the input is a string, it is parsed using validator.toInt() (with trimming). Use is.radix() to specify the radix (default is 10).

If the input is a number, no parsing is performed.

All other types will fail parsing straightaway.

The resultant number must not be an NaN or an infinity. It will also be truncated. Example: -9.9 and 9.9 become -9 and 9 respectively.

float

Parsed to a number.

If the input is a string, it is parsed using validator.toFloat() (with trimming).

If the input is a number, no parsing is performed.

All other types will fail parsing straightaway.

The resultant number must not be an NaN (although infinities are accepted).

bool

Parsed to a boolean.

If the input is a string, only '0', 'false' and '' are parsed to false. Parsing is case-insensitive and ignores leading and trailing whitespaces.

For other types, the parser will use the ! operator like this !!input.

date

Parsed to a Date.

The input is parsed using validator.toDate() which in turn uses Date.parse().

If the input is a string, although it can be in a wide variety of formats, to be safe, you should only use the ISO 8601 format.

If the input is an object, it must be a Date object.

If the input is a number, it is treated as the UNIX epoch time with millisecond precision.

All other types will fail parsing straightaway.

obj

If the input is an object, it must not be null and not an Array. The parser makes a shallow clone.

All other types will fail parsing straightaway.

array

Coming soon.

email, url, ip4, ip6, ip

The input must be a string and is checked using the corresponding validator's methods.

ip accepts both IPv4 and IPv6.

url accepts both relative and absolute URLs (but not paths). Use is.absolute() to accept only absolute ones (with protocols).

Filters

All filters can handle any input, undefined, NaN, null and etc., you name it. But they only process the types they accept. For other types, they return the input untouched.

NameTypes AcceptedDescription
def(defVal)anySpecifies a default value. If the input is undefined or an empty string '', returns defVal. Note that there is an implicit trim() right after to, so to.def(x) is really to.trim().def(x), and all strings will be trimmed first.
lower(),upper()stringReturns a lowercase/uppercase version of the input.
trim(want)stringTrims the input. want defaults to true. Note that this is implicit. Use trim(false) to disable.
ltrim(),rtrim()stringTrims leading/trailing whitespaces. To use this, you have to disable trim() first.
normalizeEmail()stringNormalizes an email address using validator.normalizeEmail().

Do not specify the same filter more than once, if you do, only the last one has effect. For example, to.trim(false).trim(true) will still trim the input.

Custom Filters

Coming soon.

Constraints

NOTE: Unlike filters, constraints assume a valid type is passed in, so only use a constraint with a compatible type. For example, do not use absolute() with an int.

NameTypes AcceptedDescription
lt(n), lte(n), gt(n), gte(n)int,float,strSelf-explanatory.
radix(base)intParses the input according to the base specified (default is 10). This is not really a constraint but a way to specify the base for validator.toInt().
within(min, max)int,floatChecks the input int is within the range (inclusive).
contains(needle)strChecks the input contains the needle.
startsWith(prefix), endsWith(suffix)strSelf-explanatory.
matches(pattern, mods)strSame as validator.matches(). pattern can be an regex or a string.
len(min, max), len(exact), minLen(n), maxLen(n), byteLen(exact)strChecks the input has the required length.
upper(), lower(), hex()strChecks all chars are lowercase, uppercase or hex, respectively.
inside(s1, s2, ...)str,intChecks the input is strictly exactly one of specified. Example: inside('red', 'blue')
before(date), after(date)dateChecks the input is strictly before/after the specified date which could be a Date or string. If date is not specified, defaults to now.
within(min, max)dateChecks the input date is within the specified range (inclusive). min and max are optional defaulting to now.
has(fields)objParses the input object according to the fields specification. See example above. Extra fields not in the specs will be discarded.
absolute(protocol, ...)urlChecks the input URL starts with the specified protocols. Example: absolute('http', 'https'). If protocols are not specified, the defaults are 'http', 'https', and 'ftp'.

You may use the str constraints for string-based types such as url and email.

Like filters, do not specify the same constraint more than once, if you do, only the last one has effect.

Custom Constraints

Coming soon.

either

Usage:

// Required in node.js:
// var either = require('to.is').either;

var emailOrUsername = either(is.maxLen(100).email,
                             is.lower().maxLen(30).str);

var LoginData = is.has({
    login: emailOrUsername,
    password: is.len(6, 30).str,
}).obj;

// true
if (emailOrUsername("t@t.com"))

// true
if (emailOrUsername("john"))

var login = emailOrUsername.from("helloworld").data;

either returns a function as well that also has a from() method. It employs "short-circuit" logic.

Exact behavior

See the test cases in test/index.js for the exact behavior and accepted formats.

Error Handling

Example

Consider:

var errors = is.lower().minLen(100).absolute("http", "https").url.from("ftp://t.com").errors

The errors object will be

{
    absolute: ["http", "https"],
    minLen: [100],
}

Explanation: The input failed the absolute and minLen constraints, and their arguments are put in the errors object for easy diagnosis.

If there are no errors, errors will be undefined.

Invalid types

If the parser failed to parse the input, the errors object will be

{type: "typeName"}

Missing values

If the parser encountered an undefined input, the errors will be

{missing: true}

A more complex example

var SignupData = is.has({
    email: to.normalizeEmail().is.maxLen(100).email,
    password: to.trim().is.len(6, 30).str,
    name: to.trim().is.len(1, 30).str,
}).obj;

var result = SignupData.from({
    password: '  123  ',
    name: [],
});

In this case, result.errors will be nested like this:

{
    has: {
        email: {missing: true},   // the email field is missing and no default is specified
        password: {len: [6, 30]}, // the password is too short
        name: {type: "str"},      // name is an array, not a string.
    },
}

result.data will always be the raw, processed or parsed input, depending on when the error arises in the pipeline.

In this case, it is:

{
    password: '123',
    name: [],
}

either errors

Using emailOrUsername from above,

var result = emailOrUsername.from("JOHN");

// result will be
{
    data: ["JOHN", "JOHN"],
    errors: [{type: "email"}, {lower: []}],
}

result = emailOrUsername.from("john@test.com");

// result will be
{
    data: "john@test.com",
}

Testing

$ npm test

Browser

For now, use test/index.html until automated testing is setup.

Contributing

Contributions are welcome but please conform to the coding style. Create test cases and ensure your code passes the tests.

License

Copyright 2015 Lucas Tan. The MIT License.