Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: mafintosh/is-my-json-valid
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 01db30a6c968bfa87f2b6e16a905e73172bc6bea
Choose a base ref
...
head repository: mafintosh/is-my-json-valid
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 9df4acbb3077bce07f74e2f788cc9bfc11e0a5b3
Choose a head ref

Commits on Feb 21, 2017

  1. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    19eeaf9 View commit details

Commits on Aug 23, 2017

  1. Merge pull request #139 from mafintosh/disallow-nan-and-infinity

    Disallow NaN and Infinity for Number type
    LinusU authored Aug 23, 2017

    Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    906738a View commit details
  2. 2.16.1

    LinusU committed Aug 23, 2017

    Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    2190955 View commit details

Commits on Dec 5, 2017

  1. Add schemaPath to verbose output

    Julian de Bhal committed Dec 5, 2017
    3
    Copy the full SHA
    ddb520e View commit details

Commits on Dec 6, 2017

  1. Add schemaPath to README

    Julian de Bhal committed Dec 6, 2017
    Copy the full SHA
    1edfc55 View commit details

Commits on Dec 18, 2017

  1. Merge pull request #148 from deBhal/add/schemaPath

    Add "schemaPath" to verbose output, showing which subschema triggered each error.
    LinusU authored Dec 18, 2017

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    928417d View commit details
  2. 2.17.0

    LinusU committed Dec 18, 2017

    Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    030467a View commit details
  3. default schemaPath in visit anyOf or oneOf

    Shalev Shalit authored Dec 18, 2017

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b5f46cd View commit details
  4. Merge pull request #1 from shalevshalit/fix-visit

    default schemaPath in visit anyOf or oneOf
    Shalev Shalit authored Dec 18, 2017

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    8621356 View commit details
  5. Merge pull request #151 from shalevshalit/master

    default schemaPath in visit anyOf or oneOf
    LinusU authored Dec 18, 2017

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ee8f26a View commit details
  6. 2.17.1

    LinusU committed Dec 18, 2017

    Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    3bebc8d View commit details
  7. Add test for nested selectors

    Julian de Bhal committed Dec 18, 2017
    Copy the full SHA
    8ace642 View commit details

Commits on Dec 19, 2017

  1. Merge pull request #158 from deBhal/add/test/visit-inside-anyof-and-o…

    …neof
    
    Add test for nested selectors
    LinusU authored Dec 19, 2017

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5c6a43d View commit details

Commits on Feb 14, 2018

  1. Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    767c6c0 View commit details
  2. Merge pull request #159 from mafintosh/safe-regex

    Avoid catastrophic backtracking
    LinusU authored Feb 14, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b3051b2 View commit details
  3. 2.17.2

    LinusU committed Feb 14, 2018

    Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    10ddd9d View commit details

Commits on Aug 9, 2018

  1. Add TypeScript typings

    LinusU committed Aug 9, 2018

    Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    7103fd3 View commit details
  2. Merge pull request #164 from mafintosh/typescript

    Add TypeScript typings
    LinusU authored Aug 9, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5247819 View commit details
  3. 2.18.0

    LinusU committed Aug 9, 2018

    Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    8ef04cc View commit details
  4. Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    7c19e8c View commit details
  5. Merge pull request #165 from mafintosh/ts-enum

    Add TypeScript support for enum types
    LinusU authored Aug 9, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    c206054 View commit details
  6. Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    9d4ac46 View commit details
  7. Refactor Factory definition

    LinusU committed Aug 9, 2018

    Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    8ecbe38 View commit details
  8. Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    3da57a4 View commit details
  9. Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    c634f4b View commit details
  10. Improve type names

    LinusU committed Aug 9, 2018

    Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    feed73c View commit details
  11. Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    4c39294 View commit details

Commits on Aug 10, 2018

  1. Merge pull request #166 from mafintosh/ts-tojson

    Add toJSON to TypeScript typings
    LinusU authored Aug 10, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    20b299d View commit details
  2. Fix assertions of union types

    LinusU committed Aug 10, 2018

    Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    45b9708 View commit details
  3. Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    88dad89 View commit details
  4. Merge pull request #167 from mafintosh/ts-required

    Make "required" optional in TypeScript typings
    LinusU authored Aug 10, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    7160756 View commit details
  5. Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    e8c30d5 View commit details
  6. Merge pull request #168 from mafintosh/ts-oneof

    Add support for "oneOf" to TypeScript typings
    LinusU authored Aug 10, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4bec868 View commit details

Commits on Aug 13, 2018

  1. Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    484197f View commit details
  2. Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    fad4c91 View commit details
  3. Merge pull request #171 from mafintosh/ts-nullable

    Add nullable types to TypeScript typings
    LinusU authored Aug 13, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    1712811 View commit details
  4. 2.19.0

    LinusU committed Aug 13, 2018

    Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    dcea5be View commit details

Commits on Feb 13, 2019

  1. Cleanup package metadata

    LinusU committed Feb 13, 2019
    1

    Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    b6d9b3f View commit details

Commits on May 7, 2019

  1. Merge pull request #175 from LinusU/meta

    Cleanup package metadata
    LinusU authored May 7, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    8c11f77 View commit details
  2. 2.20.0

    LinusU committed May 7, 2019

    Verified

    This commit was signed with the committer’s verified signature.
    LinusU Linus Unnebäck
    Copy the full SHA
    60111f4 View commit details

Commits on Jun 26, 2020

  1. Copy the full SHA
    0fb366a View commit details
  2. 2.20.1

    mafintosh committed Jun 26, 2020
    Copy the full SHA
    2684bd0 View commit details
  3. test on 12

    mafintosh committed Jun 26, 2020
    Copy the full SHA
    314a36f View commit details

Commits on Jun 29, 2020

  1. Copy the full SHA
    c3fc04f View commit details
  2. 2.20.2

    mafintosh committed Jun 29, 2020
    Copy the full SHA
    adf40bd View commit details
  3. only inline allocated vars

    mafintosh committed Jun 29, 2020
    Copy the full SHA
    3419563 View commit details
  4. 2.20.3

    mafintosh committed Jun 29, 2020
    Copy the full SHA
    9df4acb View commit details
Showing with 951 additions and 102 deletions.
  1. +1 −1 .travis.yml
  2. +111 −48 README.md
  3. +32 −6 formats.js
  4. +127 −0 index.d.ts
  5. +61 −29 index.js
  6. +17 −18 package.json
  7. +15 −0 test/safe-regex.js
  8. +167 −0 test/schema-path.js
  9. +410 −0 test/typings.ts
  10. +10 −0 tsconfig.json
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
language: node_js
node_js:
- "0.10"
- "12"
159 changes: 111 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
# is-my-json-valid

A [JSONSchema](http://json-schema.org/) validator that uses code generation
to be extremely fast

```
npm install is-my-json-valid
```
A [JSONSchema](https://json-schema.org/) validator that uses code generation to be extremely fast.

It passes the entire JSONSchema v4 test suite except for `remoteRefs` and `maxLength`/`minLength` when using unicode surrogate pairs.

[![build status](http://img.shields.io/travis/mafintosh/is-my-json-valid.svg?style=flat)](http://travis-ci.org/mafintosh/is-my-json-valid)
[![build status](https://img.shields.io/travis/mafintosh/is-my-json-valid.svg?style=flat)](https://travis-ci.org/mafintosh/is-my-json-valid)

## Installation

```sh
npm install --save is-my-json-valid
```

## Usage

Simply pass a schema to compile it

``` js
```js
var validator = require('is-my-json-valid')

var validate = validator({
@@ -39,13 +40,13 @@ console.log(validate.errors)

You can also pass the schema as a string

``` js
```js
var validate = validator('{"type": ... }')
```

Optionally you can use the require submodule to load a schema from `__dirname`

``` js
```js
var validator = require('is-my-json-valid/require')
var validate = validator('my-schema.json')
```
@@ -55,7 +56,7 @@ var validate = validator('my-schema.json')
is-my-json-valid supports the formats specified in JSON schema v4 (such as date-time).
If you want to add your own custom formats pass them as the formats options to the validator

``` js
```js
var validate = validator({
type: 'string',
required: true,
@@ -74,7 +75,7 @@ console.log(validate('ab')) // false

You can pass in external schemas that you reference using the `$ref` attribute as the `schemas` option

``` js
```js
var ext = {
required: true,
type: 'string'
@@ -95,7 +96,7 @@ validate(42) // return false

is-my-json-valid supports filtering away properties not in the schema

``` js
```js
var filter = validator.filter({
required: true,
type: 'object',
@@ -109,12 +110,15 @@ var doc = {hello: 'world', notInSchema: true}
console.log(filter(doc)) // {hello: 'world'}
```

## Verbose mode outputs the value on errors
## Verbose mode shows more information about the source of the error

is-my-json-valid outputs the value causing an error when verbose is set to true
When the `verbose` options is set to `true`, `is-my-json-valid` also outputs:

``` js
var validate = validator({
- `value`: The data value that caused the error
- `schemaPath`: an array of keys indicating which sub-schema failed

```js
var schema = {
required: true,
type: 'object',
properties: {
@@ -123,20 +127,42 @@ var validate = validator({
type: 'string'
}
}
}, {
}
var validate = validator(schema, {
verbose: true
})

validate({hello: 100});
console.log(validate.errors) // {field: 'data.hello', message: 'is the wrong type', value: 100, type: 'string'}
console.log(validate.errors)
// [ { field: 'data.hello',
// message: 'is the wrong type',
// value: 100,
// type: 'string',
// schemaPath: [ 'properties', 'hello' ] } ]
```

Many popular libraries make it easy to retrieve the failing rule with the `schemaPath`:

```js
var schemaPath = validate.errors[0].schemaPath
var R = require('ramda')

console.log( 'All evaluate to the same thing: ', R.equals(
schema.properties.hello,
{ required: true, type: 'string' },
R.path(schemaPath, schema),
require('lodash').get(schema, schemaPath),
require('jsonpointer').get(schema, [""].concat(schemaPath))
))
// All evaluate to the same thing: true
```

## Greedy mode tries to validate as much as possible

By default is-my-json-valid bails on first validation error but when greedy is
set to true it tries to validate as much as possible:

``` js
```js
var validate = validator({
type: 'object',
properties: {
@@ -158,43 +184,80 @@ console.log(validate.errors) // [{field: 'data.y', message: 'is required'},

Here is a list of possible `message` values for errors:

* `is required`
* `is the wrong type`
* `has additional items`
* `must be FORMAT format` (FORMAT is the `format` property from the schema)
* `must be unique`
* `must be an enum value`
* `dependencies not set`
* `has additional properties`
* `referenced schema does not match`
* `negative schema matches`
* `pattern mismatch`
* `no schemas match`
* `no (or more than one) schemas match`
* `has a remainder`
* `has more properties than allowed`
* `has less properties than allowed`
* `has more items than allowed`
* `has less items than allowed`
* `has longer length than allowed`
* `has less length than allowed`
* `is less than minimum`
* `is more than maximum`
- `is required`
- `is the wrong type`
- `has additional items`
- `must be FORMAT format` (FORMAT is the `format` property from the schema)
- `must be unique`
- `must be an enum value`
- `dependencies not set`
- `has additional properties`
- `referenced schema does not match`
- `negative schema matches`
- `pattern mismatch`
- `no schemas match`
- `no (or more than one) schemas match`
- `has a remainder`
- `has more properties than allowed`
- `has less properties than allowed`
- `has more items than allowed`
- `has less items than allowed`
- `has longer length than allowed`
- `has less length than allowed`
- `is less than minimum`
- `is more than maximum`

## Performance

is-my-json-valid uses code generation to turn your JSON schema into basic javascript code that is easily optimizeable by v8.

At the time of writing, is-my-json-valid is the __fastest validator__ when running
At the time of writing, is-my-json-valid is the **fastest validator** when running

* [json-schema-benchmark](https://github.com/Muscula/json-schema-benchmark)
* [cosmicreals.com benchmark](http://cosmicrealms.com/blog/2014/08/29/benchmark-of-node-dot-js-json-validation-modules-part-3/)
* [jsck benchmark](https://github.com/pandastrike/jsck/issues/72#issuecomment-70992684)
* [themis benchmark](https://cdn.rawgit.com/playlyfe/themis/master/benchmark/results.html)
* [z-schema benchmark](https://rawgit.com/zaggino/z-schema/master/benchmark/results.html)
- [json-schema-benchmark](https://github.com/Muscula/json-schema-benchmark)
- [cosmicreals.com benchmark](http://cosmicrealms.com/blog/2014/08/29/benchmark-of-node-dot-js-json-validation-modules-part-3/)
- [jsck benchmark](https://github.com/pandastrike/jsck/issues/72#issuecomment-70992684)
- [themis benchmark](https://cdn.rawgit.com/playlyfe/themis/master/benchmark/results.html)
- [z-schema benchmark](https://rawgit.com/zaggino/z-schema/master/benchmark/results.html)

If you know any other relevant benchmarks open a PR and I'll add them.

## TypeScript support

This library ships with TypeScript typings. They are still early on and not perfect at the moment, but should hopefully handle the most common cases. If you find anything that doesn't work, please open an issue and we'll try to solve it.

The typings are using `unknown` and thus require TypeScript 3.0 or later.

Here is a quick sample of usage together with express:

```typescript
import createError = require('http-errors')
import createValidator = require('is-my-json-valid')
import { Request, Response, NextFunction } from 'express'

const personValidator = createValidator({
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' },
},
required: [
'name'
]
})

export function post (req: Request, res: Response, next: NextFunction) {
// Here req.body is typed as: any

if (!personValidator(req.body)) {
throw createError(400, { errors: personValidator.errors })
}

// Here req.body is typed as: { name: string, age: number | undefined }
}
```

As you can see, the typings for is-my-json-valid will contruct an interface from the schema passed in. This allows you to work with your incoming json body in a type safe way.

## License

MIT
38 changes: 32 additions & 6 deletions formats.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,40 @@
exports['date-time'] = /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}[tT ]\d{2}:\d{2}:\d{2}(\.\d+)?([zZ]|[+-]\d{2}:\d{2})$/
var createIpValidator = require('is-my-ip-valid')

var reEmailWhitespace = /\s/
var reHostnameFirstPass = /^[a-zA-Z0-9.-]+$/
var reHostnamePart = /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/
var rePhoneFirstPass = /^\+[0-9][0-9 ]{5,27}[0-9]$/
var rePhoneDoubleSpace = / {2}/
var rePhoneGlobalSpace = / /g

exports['date-time'] = /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}[tT ]\d{2}:\d{2}:\d{2}(?:\.\d+|)([zZ]|[+-]\d{2}:\d{2})$/
exports['date'] = /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$/
exports['time'] = /^\d{2}:\d{2}:\d{2}$/
exports['email'] = /^\S+@\S+$/
exports['ip-address'] = exports['ipv4'] = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
exports['ipv6'] = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/
exports['email'] = function (input) { return (input.indexOf('@') !== -1) && (!reEmailWhitespace.test(input)) }
exports['ip-address'] = exports['ipv4'] = createIpValidator({ version: 4 })
exports['ipv6'] = createIpValidator({ version: 6 })
exports['uri'] = /^[a-zA-Z][a-zA-Z0-9+-.]*:[^\s]*$/
exports['color'] = /(#?([0-9A-Fa-f]{3,6})\b)|(aqua)|(black)|(blue)|(fuchsia)|(gray)|(green)|(lime)|(maroon)|(navy)|(olive)|(orange)|(purple)|(red)|(silver)|(teal)|(white)|(yellow)|(rgb\(\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*\))|(rgb\(\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*\))/
exports['hostname'] = /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$/
exports['hostname'] = function (input) {
if (!(reHostnameFirstPass.test(input))) return false

var parts = input.split('.')

for (var i = 0; i < parts.length; i++) {
if (!(reHostnamePart.test(parts[i]))) return false
}

return true
}
exports['alpha'] = /^[a-zA-Z]+$/
exports['alphanumeric'] = /^[a-zA-Z0-9]+$/
exports['style'] = /\s*(.+?):\s*([^;]+);?/g
exports['phone'] = /^\+(?:[0-9] ?){6,14}[0-9]$/
exports['phone'] = function (input) {
if (!(rePhoneFirstPass.test(input))) return false
if (rePhoneDoubleSpace.test(input)) return false

var digits = input.substring(1).replace(rePhoneGlobalSpace, '').length

return (digits >= 7 && digits <= 15)
}
exports['utc-millisec'] = /^[0-9]{1,15}\.?[0-9]{0,15}$/
127 changes: 127 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
type AnySchema = NullSchema | BooleanSchema | NullableBooleanSchema | NumberSchema | NullableNumberSchema | StringSchema | NullableStringSchema | AnyEnumSchema | AnyArraySchema | AnyNullableArraySchema | AnyObjectSchema | AnyNullableObjectSchema | AnyAllOptionalObjectSchema | AnyNullableAllOptionalObjectSchema | AnyOneOfSchema
type StringKeys<T> = (keyof T) & string

interface NullSchema { type: 'null' }

interface BooleanSchema { type: 'boolean' }
interface NullableBooleanSchema { type: ('boolean' | 'null')[] }

interface NumberSchema { type: 'number' }
interface NullableNumberSchema { type: ('number' | 'null')[] }

interface StringSchema { type: 'string' }
interface NullableStringSchema { type: ('string' | 'null')[] }

interface AnyEnumSchema extends EnumSchema<any> {}
interface EnumSchema<Enum> { enum: Enum[] }

interface AnyArraySchema extends ArraySchema<AnySchema> {}
interface ArraySchema<ItemSchema extends AnySchema> { type: 'array', items: ItemSchema }

interface AnyNullableArraySchema extends NullableArraySchema<AnySchema> {}
interface NullableArraySchema<ItemSchema extends AnySchema> { type: ('array' | 'null')[], items: ItemSchema }

interface AnyObjectSchema extends ObjectSchema<Record<string, AnySchema>, string> {}
interface ObjectSchema<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> {
additionalProperties?: boolean
type: 'object'
properties: Properties
required: Required[]
}

interface AnyNullableObjectSchema extends NullableObjectSchema<Record<string, AnySchema>, string> {}
interface NullableObjectSchema<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> {
additionalProperties?: boolean
type: ('object' | 'null')[]
properties: Properties
required: Required[]
}

interface AnyAllOptionalObjectSchema extends AllOptionalObjectSchema<Record<string, AnySchema>> {}
interface AllOptionalObjectSchema<Properties extends Record<string, AnySchema>> {
additionalProperties?: boolean
type: 'object'
properties: Properties
}

interface AnyNullableAllOptionalObjectSchema extends NullableAllOptionalObjectSchema<Record<string, AnySchema>> {}
interface NullableAllOptionalObjectSchema<Properties extends Record<string, AnySchema>> {
additionalProperties?: boolean
type: ('object' | 'null')[]
properties: Properties
}

interface AnyOneOfSchema { oneOf: AnySchema[] }

interface ArrayFromSchema<ItemSchema extends AnySchema> extends Array<TypeFromSchema<ItemSchema>> {}

type ObjectFromSchema<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> = {
[Key in keyof Properties]: (Key extends Required ? TypeFromSchema<Properties[Key]> : TypeFromSchema<Properties[Key]> | undefined)
}

type TypeFromSchema<Schema extends AnySchema> = (
Schema extends EnumSchema<infer Enum> ? Enum
: Schema extends NullSchema ? null
: Schema extends BooleanSchema ? boolean
: Schema extends NullableBooleanSchema ? (boolean | null)
: Schema extends NumberSchema ? number
: Schema extends NullableNumberSchema ? (number | null)
: Schema extends StringSchema ? string
: Schema extends NullableStringSchema ? (string | null)
: Schema extends ArraySchema<infer ItemSchema> ? ArrayFromSchema<ItemSchema>
: Schema extends NullableArraySchema<infer ItemSchema> ? (ArrayFromSchema<ItemSchema> | null)
: Schema extends ObjectSchema<infer Properties, infer Required> ? ObjectFromSchema<Properties, Required>
: Schema extends NullableObjectSchema<infer Properties, infer Required> ? (ObjectFromSchema<Properties, Required> | null)
: Schema extends AllOptionalObjectSchema<infer Properties> ? ObjectFromSchema<Properties, never>
: Schema extends NullableAllOptionalObjectSchema<infer Properties> ? (ObjectFromSchema<Properties, never> | null)
: never
)

declare namespace factory {
interface ValidationError {
field: string
message: string
value: unknown
type: string
}
}

interface Validator<Schema extends AnySchema, Output = TypeFromSchema<Schema>> {
(input: unknown, options?: any): input is Output
errors: factory.ValidationError[]
toJSON(): Schema
}

interface Filter<Schema extends AnySchema, Output = TypeFromSchema<Schema>> {
(input: Output, options?: any): Output
}

interface Factory {
/* One of object schema */
<Properties1 extends Record<string, AnySchema>, Required1 extends StringKeys<Properties1>, Properties2 extends Record<string, AnySchema>, Required2 extends StringKeys<Properties2>> (schema: { oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>] }, options?: any): Validator<{ oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>] }, ObjectFromSchema<Properties1, Required1> | ObjectFromSchema<Properties2, Required2>>
createFilter<Properties1 extends Record<string, AnySchema>, Required1 extends StringKeys<Properties1>, Properties2 extends Record<string, AnySchema>, Required2 extends StringKeys<Properties2>> (schema: { oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>] }, options?: any): Filter<{ oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>] }, ObjectFromSchema<Properties1, Required1> | ObjectFromSchema<Properties2, Required2>>
<Properties1 extends Record<string, AnySchema>, Required1 extends StringKeys<Properties1>, Properties2 extends Record<string, AnySchema>, Required2 extends StringKeys<Properties2>, Properties3 extends Record<string, AnySchema>, Required3 extends StringKeys<Properties3>> (schema: { oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>] }, options?: any): Validator<{ oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>] }, ObjectFromSchema<Properties1, Required1> | ObjectFromSchema<Properties2, Required2> | ObjectFromSchema<Properties3, Required3>>
createFilter<Properties1 extends Record<string, AnySchema>, Required1 extends StringKeys<Properties1>, Properties2 extends Record<string, AnySchema>, Required2 extends StringKeys<Properties2>, Properties3 extends Record<string, AnySchema>, Required3 extends StringKeys<Properties3>> (schema: { oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>] }, options?: any): Filter<{ oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>] }, ObjectFromSchema<Properties1, Required1> | ObjectFromSchema<Properties2, Required2> | ObjectFromSchema<Properties3, Required3>>
<Properties1 extends Record<string, AnySchema>, Required1 extends StringKeys<Properties1>, Properties2 extends Record<string, AnySchema>, Required2 extends StringKeys<Properties2>, Properties3 extends Record<string, AnySchema>, Required3 extends StringKeys<Properties3>, Properties4 extends Record<string, AnySchema>, Required4 extends StringKeys<Properties4>> (schema: { oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>, ObjectSchema<Properties4, Required4>] }, options?: any): Validator<{ oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>, ObjectSchema<Properties4, Required4>] }, ObjectFromSchema<Properties1, Required1> | ObjectFromSchema<Properties2, Required2> | ObjectFromSchema<Properties3, Required3> | ObjectFromSchema<Properties4, Required4>>
createFilter<Properties1 extends Record<string, AnySchema>, Required1 extends StringKeys<Properties1>, Properties2 extends Record<string, AnySchema>, Required2 extends StringKeys<Properties2>, Properties3 extends Record<string, AnySchema>, Required3 extends StringKeys<Properties3>, Properties4 extends Record<string, AnySchema>, Required4 extends StringKeys<Properties4>> (schema: { oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>, ObjectSchema<Properties4, Required4>] }, options?: any): Filter<{ oneOf: [ObjectSchema<Properties1, Required1>, ObjectSchema<Properties2, Required2>, ObjectSchema<Properties3, Required3>, ObjectSchema<Properties4, Required4>] }, ObjectFromSchema<Properties1, Required1> | ObjectFromSchema<Properties2, Required2> | ObjectFromSchema<Properties3, Required3> | ObjectFromSchema<Properties4, Required4>>

/* One of plain schema */
<Schema1 extends AnySchema, Schema2 extends AnySchema> (schema: { oneOf: [Schema1, Schema2] }, options?: any): Validator<{ oneOf: [Schema1, Schema2] }, TypeFromSchema<Schema1> | TypeFromSchema<Schema2>>
createFilter<Schema1 extends AnySchema, Schema2 extends AnySchema> (schema: { oneOf: [Schema1, Schema2] }, options?: any): Filter<{ oneOf: [Schema1, Schema2] }, TypeFromSchema<Schema1> | TypeFromSchema<Schema2>>
<Schema1 extends AnySchema, Schema2 extends AnySchema, Schema3 extends AnySchema> (schema: { oneOf: [Schema1, Schema2, Schema3] }, options?: any): Validator<{ oneOf: [Schema1, Schema2, Schema3] }, TypeFromSchema<Schema1> | TypeFromSchema<Schema2> | TypeFromSchema<Schema3>>
createFilter<Schema1 extends AnySchema, Schema2 extends AnySchema, Schema3 extends AnySchema> (schema: { oneOf: [Schema1, Schema2, Schema3] }, options?: any): Filter<{ oneOf: [Schema1, Schema2, Schema3] }, TypeFromSchema<Schema1> | TypeFromSchema<Schema2> | TypeFromSchema<Schema3>>
<Schema1 extends AnySchema, Schema2 extends AnySchema, Schema3 extends AnySchema, Schema4 extends AnySchema> (schema: { oneOf: [Schema1, Schema2, Schema3, Schema4] }, options?: any): Validator<{ oneOf: [Schema1, Schema2, Schema3, Schema4] }, TypeFromSchema<Schema1> | TypeFromSchema<Schema2> | TypeFromSchema<Schema3> | TypeFromSchema<Schema4>>
createFilter<Schema1 extends AnySchema, Schema2 extends AnySchema, Schema3 extends AnySchema, Schema4 extends AnySchema> (schema: { oneOf: [Schema1, Schema2, Schema3, Schema4] }, options?: any): Filter<{ oneOf: [Schema1, Schema2, Schema3, Schema4] }, TypeFromSchema<Schema1> | TypeFromSchema<Schema2> | TypeFromSchema<Schema3> | TypeFromSchema<Schema4>>

/* Object schema */
<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> (schema: ObjectSchema<Properties, Required>, options?: any): Validator<ObjectSchema<Properties, Required>>
createFilter<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> (schema: ObjectSchema<Properties, Required>, options?: any): Filter<ObjectSchema<Properties, Required>>

/* Plain schema */
<Schema extends AnySchema> (schema: Schema, options?: any): Validator<Schema>
createFilter<Schema extends AnySchema> (schema: Schema, options?: any): Filter<Schema>
}

declare const factory: Factory

export = factory
90 changes: 61 additions & 29 deletions index.js
Original file line number Diff line number Diff line change
@@ -45,13 +45,6 @@ var get = function(obj, additionalSchemas, ptr) {
}
}

var formatName = function(field) {
field = JSON.stringify(field)
var pattern = /\[([^\[\]"]+)\]/
while (pattern.test(field)) field = field.replace(pattern, '."+$1+"')
return field
}

var types = {}

types.any = function() {
@@ -75,7 +68,7 @@ types.object = function(name) {
}

types.number = function(name) {
return 'typeof '+name+' === "number"'
return 'typeof '+name+' === "number" && isFinite('+name+')'
}

types.integer = function(name) {
@@ -86,9 +79,10 @@ types.string = function(name) {
return 'typeof '+name+' === "string"'
}

var unique = function(array) {
var unique = function(array, len) {
len = Math.min(len === -1 ? array.length : len, array.length)
var list = []
for (var i = 0; i < array.length; i++) {
for (var i = 0; i < len; i++) {
list.push(typeof array[i] === 'object' ? JSON.stringify(array[i]) : array[i])
}
for (var i = 1; i < list.length; i++) {
@@ -109,16 +103,40 @@ var isMultipleOf = function(name, multipleOf) {
return !res;
}

var testLimitedRegex = function (r, s, maxLength) {
if (maxLength > -1 && s.length > maxLength) return true
return r.test(s)
}

var compile = function(schema, cache, root, reporter, opts) {
var fmts = opts ? xtend(formats, opts.formats) : formats
var scope = {unique:unique, formats:fmts, isMultipleOf:isMultipleOf}
var scope = {unique:unique, formats:fmts, isMultipleOf:isMultipleOf, testLimitedRegex:testLimitedRegex}
var verbose = opts ? !!opts.verbose : false;
var greedy = opts && opts.greedy !== undefined ?
opts.greedy : false;

var syms = {}
var allocated = []
var gensym = function(name) {
return name+(syms[name] = (syms[name] || 0)+1)
var res = name+(syms[name] = (syms[name] || 0)+1)
allocated.push(res)
return res
}

var formatName = function(field) {
var s = JSON.stringify(field)
try {
var pattern = /\[([^\[\]"]+)\]/
while (pattern.test(s)) s = s.replace(pattern, replacer)
return s
} catch (_) {
return JSON.stringify(field)
}

function replacer (match, v) {
if (allocated.indexOf(v) === -1) throw new Error('Unreplaceable')
return '." + ' + v + ' + "'
}
}

var reversePatterns = {}
@@ -134,10 +152,11 @@ var compile = function(schema, cache, root, reporter, opts) {
var genloop = function() {
var v = vars.shift()
vars.push(v+v[0])
allocated.push(v)
return v
}

var visit = function(name, node, reporter, filter) {
var visit = function(name, node, reporter, filter, schemaPath) {
var properties = node.properties
var type = node.type
var tuple = false
@@ -157,7 +176,14 @@ var compile = function(schema, cache, root, reporter, opts) {
if (reporter === true) {
validate('if (validate.errors === null) validate.errors = []')
if (verbose) {
validate('validate.errors.push({field:%s,message:%s,value:%s,type:%s})', formatName(prop || name), JSON.stringify(msg), value || name, JSON.stringify(type))
validate(
'validate.errors.push({field:%s,message:%s,value:%s,type:%s,schemaPath:%s})',
formatName(prop || name),
JSON.stringify(msg),
value || name,
JSON.stringify(type),
JSON.stringify(schemaPath)
)
} else {
validate('validate.errors.push({field:%s,message:%s})', formatName(prop || name), JSON.stringify(msg))
}
@@ -199,7 +225,7 @@ var compile = function(schema, cache, root, reporter, opts) {
} else if (node.additionalItems) {
var i = genloop()
validate('for (var %s = %d; %s < %s.length; %s++) {', i, node.items.length, i, name, i)
visit(name+'['+i+']', node.additionalItems, reporter, filter)
visit(name+'['+i+']', node.additionalItems, reporter, filter, schemaPath.concat('additionalItems'))
validate('}')
}
}
@@ -210,7 +236,7 @@ var compile = function(schema, cache, root, reporter, opts) {
scope[n] = fmts[node.format]

if (typeof scope[n] === 'function') validate('if (!%s(%s)) {', n, name)
else validate('if (!%s.test(%s)) {', n, name)
else validate('if (!testLimitedRegex(%s, %s, %d)) {', n, name, typeof node.maxLength === 'undefined' ? -1 : node.maxLength)
error('must be '+node.format+' format')
validate('}')
if (type !== 'string' && formats[node.format]) validate('}')
@@ -236,7 +262,7 @@ var compile = function(schema, cache, root, reporter, opts) {

if (node.uniqueItems) {
if (type !== 'array') validate('if (%s) {', types.array(name))
validate('if (!(unique(%s))) {', name)
validate('if (!(unique(%s, %d))) {', name, node.maxItems || -1)
error('must be unique')
validate('}')
if (type !== 'array') validate('}')
@@ -278,7 +304,7 @@ var compile = function(schema, cache, root, reporter, opts) {
}
if (typeof deps === 'object') {
validate('if (%s !== undefined) {', genobj(name, key))
visit(name, deps, reporter, filter)
visit(name, deps, reporter, filter, schemaPath.concat(['dependencies', key]))
validate('}')
}
})
@@ -312,7 +338,7 @@ var compile = function(schema, cache, root, reporter, opts) {
if (filter) validate('delete %s', name+'['+keys+'['+i+']]')
error('has additional properties', null, JSON.stringify(name+'.') + ' + ' + keys + '['+i+']')
} else {
visit(name+'['+keys+'['+i+']]', node.additionalProperties, reporter, filter)
visit(name+'['+keys+'['+i+']]', node.additionalProperties, reporter, filter, schemaPath.concat(['additionalProperties']))
}

validate
@@ -343,7 +369,7 @@ var compile = function(schema, cache, root, reporter, opts) {
if (node.not) {
var prev = gensym('prev')
validate('var %s = errors', prev)
visit(name, node.not, false, filter)
visit(name, node.not, false, filter, schemaPath.concat('not'))
validate('if (%s === errors) {', prev)
error('negative schema matches')
validate('} else {')
@@ -356,7 +382,7 @@ var compile = function(schema, cache, root, reporter, opts) {

var i = genloop()
validate('for (var %s = 0; %s < %s.length; %s++) {', i, i, name, i)
visit(name+'['+i+']', node.items, reporter, filter)
visit(name+'['+i+']', node.items, reporter, filter, schemaPath.concat('items'))
validate('}')

if (type !== 'array') validate('}')
@@ -373,7 +399,7 @@ var compile = function(schema, cache, root, reporter, opts) {
Object.keys(node.patternProperties).forEach(function(key) {
var p = patterns(key)
validate('if (%s.test(%s)) {', p, keys+'['+i+']')
visit(name+'['+keys+'['+i+']]', node.patternProperties[key], reporter, filter)
visit(name+'['+keys+'['+i+']]', node.patternProperties[key], reporter, filter, schemaPath.concat(['patternProperties', key]))
validate('}')
})

@@ -384,15 +410,15 @@ var compile = function(schema, cache, root, reporter, opts) {
if (node.pattern) {
var p = patterns(node.pattern)
if (type !== 'string') validate('if (%s) {', types.string(name))
validate('if (!(%s.test(%s))) {', p, name)
validate('if (!(testLimitedRegex(%s, %s, %d))) {', p, name, typeof node.maxLength === 'undefined' ? -1 : node.maxLength)
error('pattern mismatch')
validate('}')
if (type !== 'string') validate('}')
}

if (node.allOf) {
node.allOf.forEach(function(sch) {
visit(name, sch, reporter, filter)
node.allOf.forEach(function(sch, key) {
visit(name, sch, reporter, filter, schemaPath.concat(['allOf', key]))
})
}

@@ -406,7 +432,7 @@ var compile = function(schema, cache, root, reporter, opts) {
validate('if (errors !== %s) {', prev)
('errors = %s', prev)
}
visit(name, sch, false, false)
visit(name, sch, false, false, schemaPath)
})
node.anyOf.forEach(function(sch, i) {
if (i) validate('}')
@@ -425,7 +451,7 @@ var compile = function(schema, cache, root, reporter, opts) {
('var %s = 0', passes)

node.oneOf.forEach(function(sch, i) {
visit(name, sch, false, false)
visit(name, sch, false, false, schemaPath)
validate('if (%s === errors) {', prev)
('%s++', passes)
('} else {')
@@ -533,7 +559,13 @@ var compile = function(schema, cache, root, reporter, opts) {
Object.keys(properties).forEach(function(p) {
if (Array.isArray(type) && type.indexOf('null') !== -1) validate('if (%s !== null) {', name)

visit(genobj(name, p), properties[p], reporter, filter)
visit(
genobj(name, p),
properties[p],
reporter,
filter,
schemaPath.concat(tuple ? p : ['properties', p])
)

if (Array.isArray(type) && type.indexOf('null') !== -1) validate('}')
})
@@ -549,7 +581,7 @@ var compile = function(schema, cache, root, reporter, opts) {
('validate.errors = null')
('var errors = 0')

visit('data', schema, reporter, opts && opts.filter)
visit('data', schema, reporter, opts && opts.filter, [])

validate
('return errors === 0')
35 changes: 17 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,34 +1,33 @@
{
"name": "is-my-json-valid",
"version": "2.16.0",
"description": "A JSONSchema validator that uses code generation to be extremely fast",
"main": "index.js",
"version": "2.20.3",
"license": "MIT",
"repository": "mafintosh/is-my-json-valid",
"files": [
"formats.js",
"index.d.ts",
"index.js",
"require.js"
],
"scripts": {
"test": "tape test/*.js && tsc"
},
"dependencies": {
"generate-function": "^2.0.0",
"generate-object-property": "^1.1.0",
"is-my-ip-valid": "^1.0.0",
"jsonpointer": "^4.0.0",
"xtend": "^4.0.0"
},
"devDependencies": {
"tape": "^2.13.4"
},
"scripts": {
"test": "tape test/*.js"
},
"repository": {
"type": "git",
"url": "https://github.com/mafintosh/is-my-json-valid"
"safe-regex": "^1.1.0",
"tape": "^2.13.4",
"typescript": "^3.0.1"
},
"keywords": [
"json",
"schema",
"orderly",
"jsonschema"
],
"author": "Mathias Buus",
"license": "MIT",
"bugs": {
"url": "https://github.com/mafintosh/is-my-json-valid/issues"
},
"homepage": "https://github.com/mafintosh/is-my-json-valid"
]
}
15 changes: 15 additions & 0 deletions test/safe-regex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
var tape = require('tape')
var safeRegex = require('safe-regex')

var formats = require('../formats')

tape('safe-regex', function (t) {
var key
for (key in formats) {
if (formats[key] instanceof RegExp) {
t.ok(safeRegex(formats[key]), key + ' should be a safe regex')
}
}

t.end()
})
167 changes: 167 additions & 0 deletions test/schema-path.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
var tape = require('tape')
var validator = require('../')
var get = require('jsonpointer').get;

function toPointer( path ) {
if ( ! ( path && path.length && path.join ) ){
return '';
}
return '/' + path.join('/');
}

function lookup(schema, err){
return get(schema, toPointer(err.schemaPath));
}

tape('schemaPath', function(t) {
var schema = {
type: 'object',
target: 'top level',
properties: {
target: 'inside properties',
hello: {
target: 'inside hello',
type:'string'
},
someItems: {
target: 'in someItems',
type: 'array',
items: [
{
type: 'string'
},
{
type: 'array'
},
],
additionalItems: {
target: 'inside additionalItems',
type: 'boolean',
}
},
nestedOuter: {
type: 'object',
target: 'in nestedOuter',
properties: {
nestedInner: {
type: 'object',
target: 'in nestedInner',
properties: {
deeplyNestedProperty: {
target: 'in deeplyNestedProperty',
type: "boolean"
}
}
},
},
required: ['nestedInner']
},
aggregate: {
allOf: [
{ pattern: 'z$' },
{ pattern: '^a' },
{ pattern: '-' },
{ pattern: '^...$' }
]
},
negate: {
target: "in negate",
not: {
type: "boolean"
}
},
selection: {
target: 'in selection',
anyOf: [
{ 'pattern': '^[a-z]{3}$' },
{ 'pattern': '^[0-9]$' }
],
},
exclusiveSelection: {
target: 'There can be only one',
oneOf: [
{ pattern: 'a' },
{ pattern: 'e' },
{ pattern: 'i' },
{ pattern: 'o' },
{ pattern: 'u' }
]
}
},
patternProperties: {
".*String": { type: 'string' },
'^[01]+$': { type: 'number' }
},
additionalProperties: false
}
var validate = validator(schema, { verbose: true, greedy: true } );

function notOkAt(data, path, message) {
if(validate(data)) {
return t.fail('should have failed: ' + message)
}
t.deepEqual(validate.errors[0].schemaPath, path, message)
}

function notOkWithTarget(data, target, message) {
if(validate(data)) {
return t.fail('should have failed: ' + message)
}
t.deepEqual(lookup(schema, validate.errors[0]).target, target, message)
}

// Top level errors
notOkAt(null, [], 'should target parent of failed type error')
notOkAt(undefined, [], 'should target parent of failed type error')
notOkWithTarget({invalidAdditionalProp: '*whistles innocently*'}, 'top level', 'additionalProperties should be associated with containing schema')

// Errors in properties
notOkAt({hello: 42}, ['properties', 'hello'], 'should target property with type error')
notOkAt({someItems: [42]}, ['properties','someItems','0'], 'should target specific someItems rule(0)')
notOkAt({someItems: ['astring', 42]}, ['properties','someItems','1'], 'should target specific someItems rule(1)')
notOkAt({someItems: ['astring', 42, 'not a boolean']}, ['properties','someItems', 'additionalItems'], 'should target additionalItems')
notOkWithTarget({someItems: ['astring', 42, true, false, 42]}, 'inside additionalItems', 'should sitll target additionalProperties after valid additional items')

notOkWithTarget({nestedOuter: {}}, 'in nestedOuter', 'should target container of missing required property')
notOkWithTarget({nestedOuter: {nestedInner: 'not an object'}}, 'in nestedInner', 'should target property with type error (inner)')
notOkWithTarget({nestedOuter: {nestedInner: {deeplyNestedProperty: 'not a boolean'}}}, 'in deeplyNestedProperty', 'should target property with type error (deep)')

notOkAt({aggregate: 'a-a'}, ['properties', 'aggregate', 'allOf', 0], 'should target specific rule in allOf (0)')
notOkAt({aggregate: 'z-z'}, ['properties', 'aggregate', 'allOf', 1], 'should target specific rule in allOf (1)')
notOkAt({aggregate: 'a:z'}, ['properties', 'aggregate', 'allOf', 2], 'should target specific rule in allOf (2)')
notOkAt({aggregate: 'a--z'}, ['properties', 'aggregate', 'allOf', 3], 'should target specific rule in allOf (3)')

notOkAt({'notAString': 42}, ['patternProperties', '.*String'], 'should target the specific pattern in patternProperties (wildcards)')
notOkAt({
'I am a String': 'I really am',
'001100111011000111100': "Don't stand around jabbering when you're in mortal danger"
}, ['patternProperties', '^[01]+$'], 'should target the specific pattern in patternProperties ("binary" keys)')

notOkWithTarget({negate: false}, 'in negate', 'should target container of not')

notOkWithTarget(({selection: 'grit'}), 'in selection', 'should target container for anyOf (no matches)');
notOkWithTarget(({exclusiveSelection: 'fly'}), 'There can be only one', 'should target container for oneOf (no match)');
notOkWithTarget(({exclusiveSelection: 'ice'}), 'There can be only one', 'should target container for oneOf (multiple matches)');

t.end()
})

tape('schemaPath - nested selectors', function(t) {
var schema = {
anyOf: [
{ oneOf:[
{ allOf: [
{
properties: {
nestedSelectors: {type: "integer"}
}
}
]}
]}
]
}
var validate = validator(schema, { verbose: true, greedy: true } );
t.notOk(validate({nestedSelectors: "nope"}), 'should not crash on visit inside *Of');

t.end()
})
410 changes: 410 additions & 0 deletions test/typings.ts

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"strict": true,
"noEmit": true
},
"files": [
"index.d.ts",
"test/typings.ts"
]
}