Skip to content

Commit

Permalink
Merge branch 'release/0.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
charlespascoe committed Dec 27, 2017
2 parents 4931327 + cdf64a0 commit 842bda8
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 125 deletions.
148 changes: 73 additions & 75 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,7 @@

# Validate Objects Against TypeScript Interfaces #

- [Installation](#installation)
- [Basic Usage](#basic-usage)
- [Assertions](#assertions)
- [Handling Validation Errors](#handling-validation-errors)
- [Documentation](#documentation)
- [Validator](#validator)
- [extendValidator](#extendvalidator)
- [validate](#validate)
- [assertThat](#assertthat)
- [optional](#optional)
- [nullable](#nullable)
- [defaultsTo](#defaultsto)
- [onErrorDefaultsTo](#onerrordefaultsto)
- [isBoolean](#isboolean)
- [isNumber](#isnumber)
- [min](#min)
- [max](#max)
- [isString](#isstring)
- [matches](#matches)
- [minLength](#minLength)
- [maxLength](#maxLength)
- [lengthIs](#lengthis)
- [isArray](#isarray)
- [eachItem](#eachitem)
- [isObject](#isobject)
- [conformsTo](#conformsto)
- [equals](#equals)
Builds strongly-typed validators that can prove to the TypeScript compiler that a given object conforms to a TypeScript interface.

## Installation ##

Expand All @@ -49,10 +23,10 @@ interface Employee {

// 2) Define a schema
const employeeValidator: Validator<Employee> = {
name: assertThat('name', isString(minLength(1))),
roleCode: assertThat('roleCode', isNumber(min(1, max(10)))),
completedTraining: assertThat('completedTraining', optional(isBoolean())),
addressPostcode: assertThat('addressPostcode', isString(matches(/^[a-z]{2}\d{1,2}\s+\d{1,2}[a-z]{2}$/i)))
name: isString(minLength(1)),
roleCode: isNumber(min(1, max(10))),
completedTraining: optional(isBoolean()),
addressPostcode: isString(matches(/^[a-z]{2}\d{1,2}\s+\d{1,2}[a-z]{2}$/i))
};

// 3) Validate
Expand All @@ -68,36 +42,50 @@ try {
let wrong: Employee = validate({
name: 'Name',
roleCode: 4,
completedTraining: false,
completedTraining: 'false',
addressPostcode: 'WRONG'
}, employeeValidator);
} catch (err) {
console.log(err.toString());
}

// Outputs:
// Validation failed for $root.addressPostcode: Failed regular expression /^[a-z]{2}\d{1,2}\s+\d{1,2}[a-z]{2}$/i
// 2 validation errors:
// $root.completedTraining: Expected boolean, got string
// $root.addressPostcode: Failed regular expression /^[a-z]{2}\d{1,2}\s+\d{1,2}[a-z]{2}$/i

```

## Assertions ##
## Documentation ##
This library provides a number of strongly-typed assertions which can be combined to validate the type of each property.

An assertion may take another assertion as its last argument; if assertion check passes, it calls the next assertion. For example, `isString(minLength(1, maxLength(10)))` first checks if the value is a string, then checks if its length is at least 1, and then checks that its length is no more than 10. If `isString` fails, `minLength` isn't run. Chaining assertions in this way allows for complex values to be validated.

Some assertions require other assertions to come before it. For example, `minLength` can't be used by itself because it needs another assertion to check that the value has the `length` property - so something like `isString(minLength(1))` or `isArray(minLength(0))`.

## Handling Validation Errors ##

Errors will always be of the type `ValidationError`, which has a number of useful properties:

- `errorCode`: A string which is one of a set of error codes, e.g. `NOT_STRING`. Useful for producing custom error messages or triggering certain error logic.
- `message`: A human-readable error message, with more information as to why the validation failed
- `path`: An array of objects that describe the path to the value that caused the validation to fail. Each object is either an `ArrayIndexPathNode` (which has an `index` property) or `KeyPathNode` (which has a `key` property).

There's a `toString` method which prints this information in a human-readable format.

## Documentation ##
An assertion may take another assertion as its last argument; if assertion check passes, it calls the next assertion. For example, `isString(minLength(1, maxLength(10)))` first checks if the value is a string, then checks if its length is at least 1, and then checks that its length is no more than 10. If `isString` fails, `minLength` isn't run. Chaining assertions in this way allows for complex validation.

Some assertions require other assertions to come before it. For example, `minLength` can't be used by itself because it needs another assertion to check that the value has the `length` property - so something like `isString(minLength(1))` or `isArray(minLength(1))`.

Jump to section:
- [Validator](#validator)
- [extendValidator](#extendvalidator)
- [validate](#validate)
- [optional](#optional)
- [nullable](#nullable)
- [defaultsTo](#defaultsto)
- [onErrorDefaultsTo](#onerrordefaultsto)
- [isBoolean](#isboolean)
- [isNumber](#isnumber)
- [min](#min)
- [max](#max)
- [isString](#isstring)
- [matches](#matches)
- [minLength](#minLength)
- [maxLength](#maxLength)
- [lengthIs](#lengthis)
- [isArray](#isarray)
- [eachItem](#eachitem)
- [isObject](#isobject)
- [conformsTo](#conformsto)
- [equals](#equals)
- [Handling Validation Errors](#handling-validation-errors)

### Validator ###

Expand All @@ -111,25 +99,25 @@ interface IFoo {

// A valid validator
const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', isString()),
baz: assertThat('baz', isNumber())
bar: isString(),
baz: isNumber()
};

// All of these are invalid, and will result in an error from the TypeScript compiler

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', isString())
bar: isString()
}; // Missing 'baz'

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', isNumber()), // Wrong type
baz: assertThat('baz', isNumber())
bar: isNumber(), // Wrong type
baz: isNumber()
};

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', isString()),
baz: assertThat('baz', isNumber()),
blah: assertThat('blah', isBoolean()) // Unexpected property
bar: isString(),
baz: isNumber(),
blah: isBoolean() // Unexpected property
};

```
Expand All @@ -145,15 +133,15 @@ interface IFoo {
}

const fooValidator: Validator<IFoo> = {
abc: assertThat('abc', isNumber())
abc: isNumber()
};

interface IBar extends IFoo {
xyz: string;
}

const barValidator: Validator<IBar> = extendValidator(fooValidator, {
xyz: assertThat('xyz', isString())
xyz: isString()
});
```

Expand All @@ -162,9 +150,6 @@ const barValidator: Validator<IBar> = extendValidator(fooValidator, {

Checks that `arg` conforms to the type `T` using the given `validator`. Returns an object that conforms to `T` or throws an error.

### assertThat ###
Used to start an assertion chain.

### optional ###
Used when the property may not present on the object, or its value is undefined. Example:

Expand All @@ -174,7 +159,7 @@ interface IFoo {
}

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', optional(isString())),
bar: optional(isString()),
};

// Both of these are acceptable
Expand Down Expand Up @@ -202,7 +187,7 @@ interface IFoo {
}

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', nullable(isString())),
bar: nullable(isString()),
};
```

Expand All @@ -216,7 +201,7 @@ interface IFoo {
}

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', defaultsTo('baz', isString())),
bar: defaultsTo('baz', isString()),
};

const foo = validate({}, fooValidator);
Expand All @@ -236,14 +221,15 @@ interface IFoo {
}

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', onErrorDefaultsTo('baz', isString())),
bar: onErrorDefaultsTo('baz', isString()),
};

const foo = validate({bar: 123}, fooValidator);

console.log(foo);
// {bar: 'baz'}
```

### isBoolean ###
Throws an error if the value is not a boolean.

Expand All @@ -259,7 +245,7 @@ interface IFoo {
}

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', isNuber(min(0)))
bar: isNuber(min(0))
};

// This will throw an error
Expand All @@ -281,7 +267,7 @@ interface IFoo {
}

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', isStirng(matches(/^[a-z]+$/)))
bar: isStirng(matches(/^[a-z]+$/))
};

// This will throw an error
Expand All @@ -297,7 +283,7 @@ interface IFoo {
}

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', isStirng(minLength(1)))
bar: isStirng(minLength(1))
};

// This will throw an error
Expand All @@ -319,7 +305,7 @@ interface IFoo {
}

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', isArray())
bar: isArray()
};

// This is valid
Expand All @@ -342,7 +328,7 @@ interface IFoo {
}

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', isArray(eachItem(isNumber())))
bar: isArray(eachItem(isNumber()))
};

// This is valid
Expand All @@ -365,7 +351,7 @@ interface IFoo {
}

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', isObject())
bar: isObject()
};

// This is valid, but it wouldn't be safe to access properties on foo.bar
Expand All @@ -392,11 +378,11 @@ interface IFoo {
}

const barValidator: Validator<IBar> = {
baz: assertThat('baz', isNumber())
baz: isNumber()
};

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', conformsTo(barValidator))
bar: conformsTo(barValidator)
};

// This is valid
Expand Down Expand Up @@ -425,11 +411,23 @@ interface IFoo {
}

const fooValidator: Validator<IFoo> = {
bar: assertThat('bar', equals<Bar>('A', 'B', 'C'))
bar: equals<Bar>('A', 'B', 'C')
};

// Throws an error
validate({
bar: 'D'
}, fooValidator);
```

### Handling Validation Errors ###

Errors will always be of the type `ValidationErrorCollection`, which has a property `error: ValidationError[]`.

The `ValidationError` type has a number of useful properties:

- `errorCode`: A string which is one of a set of error codes, e.g. `NOT_STRING`. Useful for producing custom error messages or triggering certain error logic.
- `message`: A human-readable error message, with more information as to why the validation failed
- `path`: An array of objects that describe the path to the value that caused the validation to fail. Each object is either an `ArrayIndexPathNode` (which has an `index` property) or `KeyPathNode` (which has a `key` property).

The `ValidationErrorCollection.toString()` method prints this information in a human-readable format. The name of the root object defaults to `$root`, but this can be changed by passing a string, e.g. `err.toString('this')`.
Loading

0 comments on commit 842bda8

Please sign in to comment.