diff --git a/.eslintrc b/.eslintrc index 4d490618171211f2175e73a2f1e093e8f5a1a78b..2753bb351e4e3109b78192336abe90b733943bf3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,13 +4,31 @@ "node": true, "browser": true }, - "rules": { - "semi": [2, "always"], - "no-unused-vars": 2, - "no-extra-semi": 2, - "no-undef": 2 - }, + "extends": "airbnb-base", "parserOptions": { "sourceType": "module" + }, + "rules" : { + "max-len": ["error", { + "code": 150 + }], + "arrow-parens": ["error", "as-needed"], + "no-multi-assign": 0, + "strict": 0, + "no-console": 0, + "prefer-destructuring": 0, + "function-paren-newline": 0, + "global-require": 0, + "prefer-spread": 0, + "prefer-rest-params": 0, + "prefer-arrow-callback": 0, + "arrow-body-style": 0, + "no-restricted-globals": 0, + "consistent-return": 0, + "no-param-reassign": 0, + "no-underscore-dangle": 0, + "import/no-unresolved": 0, + "import/no-dynamic-require": 0, + "import/no-extraneous-dependencies": 0 } } diff --git a/.gitignore b/.gitignore index 0178658220c4107d15766b0a9e7a80963a491090..6b7e9586d4a9017cdce94a43976f072762c1e5db 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *~ *.log *.tgz +.nyc_output .DS_Store /lib /package diff --git a/.travis.yml b/.travis.yml index 852ace9fe4f281605adcfb4dec9d63421b9b0739..742339dfb21a66166640d9f42048ed039a6b1864 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,15 +5,15 @@ branches: except: - gh-pages node_js: - - 4 - 6 - 8 - 9 + - 10 before_script: - npm run build script: - - npm run cover:schema + - npm run test:ci after_success: - - npm run cover:up + - npm run codecov notifications: email: false diff --git a/README.md b/README.md index 31876bcc9f257da59b804d87d4fbd21bf12fe717..09072b6173756464a6fda2aef42613fbc9a3750e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ + + [](https://github.com/json-schema-faker/json-schema-faker) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8MXLRJ7QQXGYY) @@ -13,615 +15,38 @@ [](http://inch-ci.org/github/json-schema-faker/json-schema-faker) [](http://json-schema-faker.github.io/json-schema-faker/) -Use [JSON Schema](http://json-schema.org/draft-04/json-schema-core.html) along with fake generators to provide consistent and meaningful fake data for your system. - -We are looking for **contributors**! If you wanna help us make `jsf` more awesome, simply write us so! - -## Join us! - -We've recently setup a [gitter room](https://gitter.im/json-schema-faker) for this project, if you want contribute, talk about specific issues from the library, or you need help on json-schema topics just reach us! - -Have some ❤ for JSON-Schema-Faker? You can support the project via: - -- [Open Collective](https://opencollective.com/json-schema-faker/donate) -- [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8MXLRJ7QQXGYY) - -## What's new? - -A release candidate for `v0.5.x` series was released in order to support local/remote reference downloading thanks to `json-schema-ref-parser`, this change forced `jsf` to be completely async. - -```js -var jsf = require('json-schema-faker'); - -var REMOTE_REF = 'https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json'; - -var schema = { - $ref: REMOTE_REF -}; - -jsf.resolve(schema).then(function(result) { - console.log(JSON.stringify(result, null, 2)); -}); -``` - -**WIP: BREAKING CHANGES** - -- All code examples from previous versions (`v0.4.x` and below) will not work, however the fix is easy -- All dependent tools are of course outdated and they may not work as expected, please take care of this: If you're a maintainer please upgrade your API. - -Until a polished `v0.5.0` version is released we encourage you to use and test around this RC, the main API will remain intact but probably option names, or subtle behaviors can be introduced. - -Examples, new ideas, tips and any kind of kindly feedback is greatly appreciated. - -**NEW: BACKWARD COMPATIBILITY** - -- Since `0.5.0-rc3` we introduced a `jsf.resolve()` method for full-async results. -- Since `0.5.0-rc3` the methods `jsf.sync()` is REMOVED and the API for `jsf()` will remain sync. - -Thanks for all your feedback in advance to everyone! - -# Table of contents - -- Basics - - [JSON-schema-faker](#fake-your-schemas) - - [Online demo](#online-demo) - - [Install](#install) - - [npm](#npm) - - [bower](#bower) - - [cdnjs](#cdnjs) - - [Overview](#overview) - - [Example usage](#example-usage) - - [More examples](#more-examples) - - [Gist demos](#gist-demos) - - [Automation](#automation) - - [Angular-jsf (AngularJS plugin)](#angular-jsf) - - [Grunt plugin](#grunt-plugin) - - [CLI](#cli) - - [Webpack loader](#webpack-loader) -- Advanced - - [JSON Schema specification support](#json-schema-specification-support) - - [Supported keywords](#supported-keywords) - - [Using references](#using-references) - - [Faking values](#faking-values) - - [Advanced usage of faker.js and Chance.js](#user-content-advanced-usage-of-fakerjs-and-chancejs) - - [Custom formats](#custom-formats) - - [Custom options](#custom-options) - - [Extending dependencies](#extending-dependencies) - - [Inferred Types](#inferred-types) - - [Swagger extensions](#swagger-extensions) - - [Bundling](#bundling) -- Misc - - [Contribution](#contribution) - - [Technical Documentation](#technical-documentation) - - [Resources](#resources) - - [Motivation](#motivation) - -## Online demo - -See [online demo](http://json-schema-faker.js.org/). You can save your schemas online and share the link with your collaborators. - -## Install - -`jsf` is installable through 3 different channels: - -### npm - -Install `json-schema-faker` with npm: - - npm install json-schema-faker --save - -### bower - -Install `json-schema-faker` with bower: - - bower install json-schema-faker --save - -### cdnjs - -JSON-Schema-faker is also available at [cdnjs.com](https://www.cdnjs.com/libraries/json-schema-faker). This means you can just include the script file into your HTML: - - # remember to update the version number! - <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/json-schema-faker/0.5.0-rc1/json-schema-faker.min.js"></script> - -It will be fetched from the [Content Delivery Network](https://en.wikipedia.org/wiki/Content_delivery_network) without installing any node.js package. - -You can see [an example JS fiddle based on `jsf` loaded from cdnjs](https://jsfiddle.net/ftzhnmzq/4/). - -## Overview - -JSON-Schema-faker (or `jsf` for short) combines two things: - - * The [JSON-schema specification](http://json-schema.org/draft-04/json-schema-core.html), that defines what is the allowed content of a JSON document - * Fake data generators, that are used to generate basic or complex data, conforming to the schema. - -Since `v0.5.x` external generators are not longer bundled with jsf, however built-in defaults are shipped for all basic types and formats. - -## Example usage - -```javascript -var jsf = require('json-schema-faker'); - -var schema = { - type: 'object', - properties: { - user: { - type: 'object', - properties: { - id: { - $ref: '#/definitions/positiveInt' - }, - name: { - type: 'string', - faker: 'name.findName' - }, - email: { - type: 'string', - format: 'email', - faker: 'internet.email' - } - }, - required: ['id', 'name', 'email'] - } - }, - required: ['user'], - definitions: { - positiveInt: { - type: 'integer', - minimum: 0, - exclusiveMinimum: true - } - } -}; - -jsf.resolve(schema).then(function(sample) { - console.log(sample); - // "[object Object]" - - console.log(sample.user.name); - // "John Doe" -}); -``` -([demo »](http://json-schema-faker.js.org/#gist/927cf888cbc250a2b8e60eb5834cdfbd)) - -`jsf.version` attribute is available to check which version you're using: - -```javascript -var jsf = require('json-schema-faker'); -console.log(jsf.version); -// "0.5.0-rc1" -``` - -### More examples - - * [json-schema.org/example1.html](http://json-schema.org/example1.html): - [warehouse location](http://json-schema-faker.js.org/#gist/bb4774bf26167360e7c5cf2a29db3e56), - [Product from Acme catalog](http://json-schema-faker.js.org/#gist/c7a398c537cf7befce0df67fe7feeea8) - * [json-schema.org/example2.html](http://json-schema.org/example2.html): - [_diskDevice_ storage type](http://json-schema-faker.js.org/#gist/0c0d676023ea505c97eef9af0b4d95da), - [_diskUUID_ storage type](http://json-schema-faker.js.org/#gist/0ac23aa547acfdb2897a7afec3042534), - [_nfs_ storage type](http://json-schema-faker.js.org/#gist/473ac2bc364b2610f7fc703e59cfe1c9), - [_tmpfs_ storage type](http://json-schema-faker.js.org/#gist/de1c5f18f0d231557ce25e44f581cadf) - -### Gist demos - -Clone these gists and execute them locally (each gist has its own readme with instructions): - - * [jsf console](https://gist.github.com/ducin/9f2364ccde2e9248fbcd) - minimal example of jsf working directly under command line - * [jsf grunt](https://gist.github.com/ducin/87e0b55bddd1801d3d99) - example of jsf working under grunt.js - -## Automation - -### angular-jsf - -Use [`angular-jsf`](https://github.com/json-schema-faker/angular-jsf) module (installable via `npm` and `bower`) to get **`jsf` working in your angular app out of the box**! And check out [angular-jsf demo](http://angular-jsf.js.org/). - -### Grunt plugin - -Use [grunt-jsonschema-faker](https://github.com/json-schema-faker/grunt-jsonschema-faker) -to automate running `json-schema-faker` against your JSON schemas. - -### CLI - -Use [json-schema-faker-cli](https://github.com/oprogramador/json-schema-faker-cli) -to run `jsf` from your command line. - -### Webpack loader - -Use [json-schema-faker-loader](https://github.com/jeffcatania/json-schema-faker-loader) -to execute `jsf` as a [webpack](https://webpack.github.io/) loader. - -## JSON Schema specification support - -Currently `jsf` supports the JSON-Schema specification **draft-04** only. - -If you want to use **draft-03**, you may find useful information [here](https://github.com/json-schema-faker/json-schema-faker/issues/66). - -## Supported keywords - -Below is the list of supported keywords: - -- `$ref` — Resolve internal references only, and/or external if provided. -- `required` — All required properties are guaranteed, if not can be omitted. -- `pattern` — Generate samples based on RegExp values. -- `format` — Core formats **v4-draft only**: - [`date-time`](http://json-schema.org/draft-04/json-schema-validation.html#anchor108), - [`email`](http://json-schema.org/draft-04/json-schema-validation.html#anchor111), - [`hostname`](http://json-schema.org/draft-04/json-schema-validation.html#anchor114), - [`ipv4`](http://json-schema.org/draft-04/json-schema-validation.html#anchor117), - [`ipv6`](http://json-schema.org/draft-04/json-schema-validation.html#anchor120) - and [`uri`](http://json-schema.org/draft-04/json-schema-validation.html#anchor123) - -- [demo »](http://json-schema-faker.js.org/#gist/f58db80cbf52c12c623166090240d964) -- `enum` — Returns any of these enumerated values. -- `minLength`, `maxLength` — Applies length constraints to string values. -- `minimum`, `maximum` — Applies constraints to numeric values. -- `exclusiveMinimum`, `exclusiveMaximum` — Adds exclusivity for numeric values. -- `multipleOf` — Multiply constraints for numeric values. -- `items` — Support for subschema and fixed item values. -- `minItems`, `maxItems` — Adds length constraints for array items. -- `uniqueItems` — Applies uniqueness constraints for array items. -- `additionalItems` — Partially supported (?) -- `allOf`, `oneOf`, `anyOf` — Subschema combinators. -- `properties` — Object properties to be generated. -- `minProperties`, `maxProperties` — Adds length constraints for object properties. -- `patternProperties` — RegExp-based object properties. -- `additionalProperties` — Partially supported (?) -- `dependencies` — Not supported yet (?) -- `not` — Not supported yet (?) - -## Using references - -Inline references are fully supported (json-pointers) but external can't be resolved by `jsf`. - -Remote en local references are automatically resolved thanks to `json-schema-ref-parser`. - -```javascript -var schema = { - type: 'object', - properties: { - someValue: { - $ref: 'otherSchema' - } - } -}; - -var refs = [ - { - id: 'otherSchema', - type: 'string' - } -]; +> Use [JSON Schema](http://json-schema.org/draft-04/json-schema-core.html) along with fake generators to provide consistent and meaningful fake data for your system. -jsf.resolve(schema, refs).then(function(sample) { - console.log(sample.someValue); - // "voluptatem" -}); -``` +## What's next? -Local references are always resolved from the `process.cwd()`, of course you can specify a custom folder to look-up: `jsf(schema, refs, cwd)` +The latest `RC16` comes with some deprecations and breaking-changes towards `v0.5.x` API: -## Faking values +- **deprecated** — You will not longer be able to call `jsf()` and get a fully-dereferenced result. It will just generate given refs and inline ones, nothing else. + - `jsf.generate()` is the sync-version, with partial dereferencing through given refs, etc. + - `jsf.resolve()` is the async-version, with full dereferencing, given refs are also supported. +- **deprecated** — TypeScript sources are not longer used, however `d.ts` definitions will be updated on time. -`jsf` has built-in generators for core-formats, [Faker.js](https://github.com/marak/Faker.js/) and [Chance.js](http://chancejs.com/) (and others) are also supported but they require setup: +Review here: https://github.com/json-schema-faker/json-schema-faker/pull/464 -```js -jsf.extend('faker', function() { - return require('faker'); -}); -``` +> Most _documentation_ from README.md was moved to `docs/` and it should have its own place there, on the website, or wiki pages, etc. This part is unclear yet, so any suggestion is welcome on the PR above. -```json -{ - "type": "string", - "faker": "internet.email" -} -``` -([demo »](http://json-schema-faker.js.org/#gist/89659ebf28be89d3f860c3f80cbffe4b)) - -The above schema will invoke [`faker.internet.email()`](https://github.com/Marak/faker.js/blob/1f47f09e25ad43db41ea4187c3cd3f7e113d4cb4/lib/internet.js#L32). - -Note that both generators has higher precedence than **format**. - -You can also use standard JSON Schema keywords, e.g. `pattern`: - -```json -{ - "type": "string", - "pattern": "yes|no|maybe|i don't know" -} -``` -([demo »](http://json-schema-faker.js.org/#gist/8ee282679da5a31cd7edc4cf35f37081)) - -### Advanced usage of faker.js and Chance.js - -In following inline code examples the `faker` and `chance` variables are assumed to be created with, respectively: - -```javascript -var faker = require('faker'); - -var Chance = require('chance'), - chance = new Chance(); -``` - -Another example of faking values is passing arguments to the generator: - -```json -{ - "type": "string", - "chance": { - "email": { - "domain": "fake.com" - } - } -} -``` -([demo »](http://json-schema-faker.js.org/#gist/c6ab6a0325e53fd3b38ee0293a9aeea3)) - -which will invoke [`chance.email({ "domain": "fake.com" })`](https://github.com/chancejs/chancejs/blob/b4c143bf53f516dfd77a8376d0f631462458c062/chance.js#L1118). -This example works for single-parameter generator function. - -However, if you pass multiple arguments to the generator function, just pass them wrapped in an array. -In the example below we use the [`faker.finance.amount(min, max, dec, symbol)`](https://github.com/Marak/faker.js/blob/1f47f09e25ad43db41ea4187c3cd3f7e113d4cb4/lib/finance.js#L85) -generator which has 4 parameters. We just wrap them with an array and it's equivalent to `faker.finance.amount(100, 10000, 2, "$")`: - -```json -{ - "type": "object", - "properties": { - "cash": { - "type": "string", - "faker": { - "finance.amount": [100, 10000, 2, "$"] - } - } - }, - "required": [ - "cash" - ] -} -``` -([demo »](http://json-schema-faker.js.org/#gist/3a15a11d706e5b145c30f943d55c42b2)) - -However, if you want to pass a single parameter that is an array itself, e.g. -[`chance.pickone(["banana", "apple", "orange"])`](https://github.com/chancejs/chancejs/blob/b4c143bf53f516dfd77a8376d0f631462458c062/chance.js#L382), -just like [described here](https://github.com/json-schema-faker/json-schema-faker/issues/171), -then you need to wrap it with an array once more (twice in total). The outer brackets determine that the content is gonna be a list of params injected into the generator. The inner brackets are just the value itself - the array we pass: - -```json -{ - "type": "object", - "properties": { - "food": { - "type": "string", - "chance": { - "pickone": [ - [ - "banana", - "apple", - "orange" - ] - ] - } - } - }, - "required": [ - "food" - ] -} -``` -([demo »](http://json-schema-faker.js.org/#gist/792d626e7d92841ded5be59b8ed001eb)) - -## Custom formats - -Additionally, you can add custom generators for those: - -```javascript -jsf.format('semver', function() { - return jsf.random.randexp('\\d\\.\\d\\.[1-9]\\d?'); -}); -``` - -Now that format can be generated: - -```json -{ - "type": "string", - "format": "semver" -} -``` - -Usage: - -- **format()** — Return all registered formats (custom only) -- **format(obj)** — Register formats by key/value → name/callback -- **format(name)** — Returns that format generator (undefined if not exists) -- **format(name, callback)** — Register a custom format by name/callback - -Callback: - -- **schema** (object) — The schema for input - -Note that custom generators has lower precedence than core ones. - -## Custom Options - -You may define following options for `jsf` that alter its behavior: - -- `failOnInvalidTypes`: boolean - don't throw exception when invalid type passed -- `defaultInvalidTypeProduct`: - default value generated for a schema with invalid type (works only if `failOnInvalidTypes` is set to `false`) -- `failOnInvalidFormat`: boolean - don't throw exception when invalid format passed -- `maxItems`: number - Configure a maximum amount of items to generate in an array. This will override the maximum items found inside a JSON Schema. -- `maxLength`: number - Configure a maximum length to allow generating strings for. This will override the maximum length found inside a JSON Schema. -- `random`: Function - a replacement for `Math.random` to support pseudorandom number generation. -- `alwaysFakeOptionals`: boolean - When true, all object-properties will be generated regardless they're `required` or not. -- `optionalsProbability`: number - A decimal number from 0 to 1 that indicates the probability to fake a non-required object property (default: 0). When `0.0`, only `required` properties will be generated; when `1.0`, all properties are generated. This option is overwritten to 1 when `alwaysFakeOptionals = true`. - -Set options just as below: - -```javascript -jsf.option({ - failOnInvalidTypes: false -}); -``` - -## Extending dependencies - -You may extend [Faker.js](http://marak.com/faker.js/): - -```javascript -var jsf = require('json-schema-faker'); - -jsf.extend('faker', function(){ - var faker = require('faker'); - - faker.locale = "de"; // or any other language - faker.custom = { - statement: function(length) { - return faker.name.firstName() + " has " + faker.finance.amount() + " on " + faker.finance.account(length) + "."; - } - }; - return faker; -}); - -var schema = { - "type": "string", - "faker": { - "custom.statement": [19] - } -} - -jsf.resolve(schema).then(...); -``` - -or if you want to use [faker's *individual localization packages*](https://github.com/Marak/faker.js#individual-localization-packages), simply do the following: - -```js -jsf.extend('faker', function() { - // just ignore the passed faker instance - var faker = require('faker/locale/de'); - // do other stuff - return faker; -}); -``` - -You can also extend [Chance.js](http://chancejs.com/), using built-in [chance.mixin](http://chancejs.com/#mixin) function: - -```javascript -var jsf = require('json-schema-faker'); - -jsf.extend('chance', function(){ - var Chance = require('chance'); - var chance = new Chance(); - - chance.mixin({ - 'user': function() { - return { - first: chance.first(), - last: chance.last(), - email: chance.email() - }; - } - }); - - return chance; -}); - -var schema = { - "type": "string", - "chance": "user" -} - -jsf.resolve(schema).then(...); -``` - -The first parameter of `extend` function is the generator name (`faker`, `chance`, etc.). The second one is the function that **must return** the dependency library. - -## Inferred Types - -JSON Schema does not require you to provide the `type` property for your JSON Schema documents and document fragments. - -But since `jsf` uses the `type` property to create the proper fake data, we attempt to infer the type whenever it is not provided. We do this based on the JSON Schema validation properties you use. - -> Now this means that if you do not use any of the JSON Schema validation properties, jsf will not be able to infer the type for you and you will need to **explicitly** set your `type` manually.) - -Below is the list of JSON Schema validation properties and the inferred type based on the property: - -**array** - -* `additionalItems` -* `items` -* `maxItems` -* `minItems` -* `uniqueItems` - -**integer** *(Number uses the same properties so if you need `number`, set your `type` explicitly)* - -* `exclusiveMaximum` -* `exclusiveMinimum` -* `maximum` -* `minimum` -* `multipleOf` - -**object** - -* `additionalProperties` -* `dependencies` -* `maxProperties` -* `minProperties` -* `patternProperties` -* `properties` -* `required` - -**string** - -* `maxLength` -* `minLength` -* `pattern` - -## Swagger extensions - -`jsf` supports [OpenAPI Specification *vendor extensions*](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#vendorExtensions), i.e. - -* `x-faker` property that stands for `faker` property ([demo »](http://json-schema-faker.js.org/#gist/7cdf200c27eceb6163a79fbc50813fcb)) -* `x-chance` property that stands for `chance` property ([demo »](http://json-schema-faker.js.org/#gist/c0084695b4ca1c4cd015ded1f5c6dc33)) - -Thanks to it, you can use valid swagger definitions for `jsf` data generation. - -## Bundling - -JSON-Schema-faker might be used in Node.js as well as in the browser. In order to execute `jsf` in a browser, you should include the distribution file from [`dist`](dist) directory. Each new version of `jsf` is bundled using [Rollup.js](http://rollupjs.org/) and stored by the library maintainers. The bundle includes full versions of all dependencies. - -From `v0.5.x` and beyond we'll not longer bundle external generators, locales and such due the unnecessary waste of time and space. - -## Contribution +## Contributors * [Alvaro Cabrera](https://twitter.com/pateketrueke) * [Tomasz Ducin](https://twitter.com/tomasz_ducin) * artwork by [Ajay Karat](http://www.devilsgarage.com/) -We are more than happy to welcome new contributors, our project is heavily developed, but we need more power :) -Please see [contribution guide](.github/CONTRIBUTING.md), you can always contact us to ask how you can help. - -### Technical Documentation - -If you want to contribute, take a look at [the technical documentation page](docs/). You may find some important information there making it easier to start. +> We are more than happy to welcome new contributors, our project is still being developed, but we need more feedback! +> +> Please see our [contribution guide](.github/CONTRIBUTING.md) to learn how. -Moreover, if you find something unclear (e.g. how does something work) or would like to suggest improving the docs, please submit an issue, we'll gladly provide more info for future contributors. +### We are looking for your help! -## Resources +We have a [gitter room](https://gitter.im/json-schema-faker) for this project, if you want contribute, talk about specific issues from the library, or you need help on json-schema topics just reach us! -* [JSON, JSON Schema & JSON-schema-faker](https://www.youtube.com/watch?v=TkqiUG3j_Xw) - WarsawJS meetup presentation recording, a step-by-step guide to JSON-related tools, including `jsf` +Have some ♥ for JSON-Schema-Faker? You can support the project via: -## Motivation - -There were some existing projects or services trying to achieve similar goals as `jsf`: - -- http://www.json-generator.com/ -- https://github.com/unindented/fake-json -- https://github.com/jonahkagan/schematic-ipsum -- https://www.npmjs.org/package/json-schema-mock -- https://github.com/thaume/json-schema-processor -- https://github.com/andreineculau/json-schema-random -- https://github.com/murgatroid99/json-schema-random-instance -- https://github.com/tomarad/JSON-Schema-Instantiator +- [Open Collective](https://opencollective.com/json-schema-faker/donate) +- [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8MXLRJ7QQXGYY) -but they were either incomplete, outdated, broken or non-standard. That's why `jsf` was created. +Take a look at [the technical documentation page](docs/). diff --git a/bower.json b/bower.json deleted file mode 100644 index 930d8b254fa09edaa1b0075068005c6151175a06..0000000000000000000000000000000000000000 --- a/bower.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "json-schema-faker", - "version": "0.5.0-rc15", - "description": "JSON-Schema + fake data generators", - "homepage": "http://json-schema-faker.js.org", - "main": "dist/json-schema-faker.js", - "authors": [ - "Alvaro Cabrera <pateketrueke@gmail.com>", - "Tomasz Ducin <tomasz@ducin.it>" - ], - "moduleType": [ - "globals", - "node" - ], - "keywords": [ - "json", - "jsonschema", - "fake", - "mocks" - ], - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "coverage", - "build", - "lib", - "ts", - "logo", - "spec", - "docs", - "package.json", - "CONTRIBUTING.md" - ] -} diff --git a/build/.banner.txt b/build/.banner.txt deleted file mode 100644 index 17c789f6a905119c9f27efb302bb6d1a43898fd1..0000000000000000000000000000000000000000 --- a/build/.banner.txt +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * <%= pkg.name %> library v<%= pkg.version %> - * <%= pkg.homepage %> - * - * Copyright (c) 2014-<%= now.substr(0, 4) %> Alvaro Cabrera & Tomasz Ducin - * Released under the <%= pkg.license %> license - * - * Date: <%= now %> - */ diff --git a/build/VERSION b/build/VERSION deleted file mode 100755 index f382c264402ea3621b2a6ecfb675ddb3dc186b45..0000000000000000000000000000000000000000 --- a/build/VERSION +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -VERSION="$(cat package.json | grep '"version":')" -VERSION=${VERSION##*:} -VERSION=${VERSION%,*} -VERSION=$(echo $VERSION | sed 's/"//g') - -SEARCH='("version":[[:space:]]*").+(")' -REPLACE="\1${VERSION}\2" - -sed -i ".tmp" -E "s/${SEARCH}/${REPLACE}/g" bower.json -rm *.tmp - -echo $VERSION diff --git a/build/dist.js b/build/dist.js deleted file mode 100644 index 7407ee2801330de569a30b4ba9c0da2978ca8bab..0000000000000000000000000000000000000000 --- a/build/dist.js +++ /dev/null @@ -1,115 +0,0 @@ -// discuss -var bundleName = 'JSONSchemaFaker'; - -// boilerplate... -var fs = require('fs-extra'), - path = require('path'), - rollup = require('rollup'), - commonJs = require('rollup-plugin-commonjs'), - nodeResolve = require('rollup-plugin-node-resolve'), - template = require('lodash.template'); - -var buildDir = __dirname, - projectDir = path.join(__dirname, '..'); - -var BANNER_TEXT = fs.readFileSync(path.join(buildDir, '.banner.txt')).toString(); - -var pkg = require(path.join(projectDir, 'package.json')), - bannerTemplate = template(BANNER_TEXT); - -var banner = bannerTemplate({ pkg: pkg, now: (new Date()).toISOString().replace('T', ' ') }); - -// custom bundler -function bundle(options, next) { - var destFile = path.join(projectDir, 'dist', options.dest || '', options.id + '.js'); - - rollup.rollup({ - input: options.src, - plugins: [ - { - resolveId(importee, importer) { - if (!importer) { - return importee; - } - - switch (importee) { - case 'jsonpath': - return require.resolve('jsonpath/jsonpath'); - - case 'json-schema-ref-parser': - return importee; - } - }, - load(importee) { - switch (importee) { - case 'json-schema-ref-parser': - return 'export default __DEREQ__("' + importee + '");'; - } - }, - }, - commonJs(), - nodeResolve({ - module: true, - jsnext: true, - main: true, - browser: true, - }), - ], - }).then(function(_bundle) { - console.log('Bundling...'); - - return _bundle.generate({ - banner, - format: 'umd', - name: bundleName, - }); - }).then(function(result) { - function dereq(file) { - return 'createCommonjsModule(function(module, exports) {' - + fs.readFileSync(file).toString().replace(/\brequire\b/g, '_dereq_') - + '});'; - } - - var _bundle = result.code.replace(/__DEREQ__\("(.+?)"\);/g, (_, src) => { - if (src === 'json-schema-ref-parser') { - return dereq(require.resolve('json-schema-ref-parser/dist/ref-parser.js')); - } - }); - - var gcc = require('google-closure-compiler-js').compile; - - var min = gcc({ - jsCode: [{ src: _bundle }], - languageIn: 'ECMASCRIPT6', - languageOut: 'ECMASCRIPT5', - compilationLevel: 'ADVANCED', - warningLevel: 'VERBOSE', - env: 'CUSTOM', - createSourceMap: false, - applyInputSourceMaps: false, - }).compiledCode; - - // minified output - fs.outputFileSync(destFile.replace(/\.js$/, '.min.js'), min); - - // regular output - fs.outputFileSync(destFile, _bundle); - - // OK - console.log('Bundle: ' + destFile); - - next(); - }) - .catch(function(error) { - console.log(error.stack); - }); -} - -console.log('Building...'); - -bundle({ id: pkg.name, src: path.join(projectDir, 'lib/index.js') }, function(err) { - if (err) { - throw err; - return; - } -}); diff --git a/dist/json-schema-faker.bundle.min.js b/dist/json-schema-faker.bundle.min.js new file mode 100644 index 0000000000000000000000000000000000000000..4a203c6c873f69fb1298088356a062af19c29811 Binary files /dev/null and b/dist/json-schema-faker.bundle.min.js differ diff --git a/dist/json-schema-faker.cjs.js b/dist/json-schema-faker.cjs.js new file mode 100644 index 0000000000000000000000000000000000000000..a05bc5692efbe5f084582fe45dc7f14ac992fd87 Binary files /dev/null and b/dist/json-schema-faker.cjs.js differ diff --git a/dist/json-schema-faker.es.js b/dist/json-schema-faker.es.js new file mode 100644 index 0000000000000000000000000000000000000000..5c218913375c1498b739f558f270aa396583a981 Binary files /dev/null and b/dist/json-schema-faker.es.js differ diff --git a/dist/json-schema-faker.js b/dist/json-schema-faker.js index 200a5ac237a6eb9fb4a14a4a4c798370140046cf..a0fa45bf05d73c57420a77fda81d9b719b2cdde5 100644 Binary files a/dist/json-schema-faker.js and b/dist/json-schema-faker.js differ diff --git a/dist/json-schema-faker.min.js b/dist/json-schema-faker.min.js index 11b4f654c2a5621bb14e89ac0f67b12441836d4d..0338c713c9621922fb9dc4a538ec1302dcff60b7 100644 Binary files a/dist/json-schema-faker.min.js and b/dist/json-schema-faker.min.js differ diff --git a/dist/json-schema-faker.min.js.map b/dist/json-schema-faker.min.js.map new file mode 100644 index 0000000000000000000000000000000000000000..02cdb9fc5008109434f3b653d63b3d1dddb1e905 Binary files /dev/null and b/dist/json-schema-faker.min.js.map differ diff --git a/docs/README.md b/docs/README.md index 7bc9675557fafccb7140448f6b0cecbe9e2bc91c..b40a0ceff0d79c61d8c65bf2529d339ba7781111 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,57 +1,151 @@ -## Table of Contents +# Table of Contents * [Architecture](#architecture) -* [Sources](#sources) * [Building](#building) -* [Testing](#testing) +* [Testing](TESTING.md) +* [Usage](USAGE.md) -# Architecture +## Architecture - +The source code is intended to be available through `src` (for testing), but bundled versions are provided to ensure portabilty: -JSON Schema Faker is a JavaScript tool that can be executed both in the browser and the server. +- Both `cjs` and `es` modules are exported for NodeJS and modern Javascript environments respectively +- Standard `.js` and `.min.js` are emitted for browser usage, they're exported as `umd` without dependencies (`json-schema-ref-parser` and `jsonpath`) +- Also a single `.bundle.min.js` is generated containing both dependencies from above, also as `umd` -JSF has been migrated to [TypeScript](typescriptlang.org). The structure is still being refactored. +Generated sources are available as an [NPM dependency](https://www.npmjs.com/package/json-schema-faker) and through [UNPKG](https://unpkg.com/json-schema-faker@0.5.0-rc15/dist/). -Currently JSF consists of: +```js +import jsf from 'json-schema-faker'; +``` + +Let's examine how the library works: + +```js +// 1.0 - first, we need a working (and valid) `schema` +const schema = { + type: 'string', +}; + +// 1.1 - optionally, you can provide used `refs` +const refs = [ + { + id: 'Test', + type: 'boolean', + } +]; -* `types` - each module represents one basic JSON Schema structure - * there is also the `external` module which executes `chance` and `faker` generators -* `generators` - very small modules which generate some low-level stuff -* `core` - the engine, various files -* `api` - here are additional methods you can call on jsf object: - * `format()` - to register/get/call your own formats - * `extend()` - extending jsf dependencies with user custom logic -* `class` - encapsulated containers; you might consider them as part of core - * `format` - for custom regular expressions - * `container` - for dependencies (`faker`, `chance`, `randexp`); this is needed for two reasons: - * end user might want to extend `faker` or `chance` with his/her custom generator functions and we need to remember that - * we don't want to include both faker and chance by default - we wanna make them optional +// 1.2 - additionally, you can provide a `cwd` for local references +const cwd = `${__dirname}/schema`; +``` -# Sources +Note that: -Currently we've got both: +- 1.0 — All input MUST be valid JSON-Schema +- 1.1 — Given `refs` are also valid JSON-Schema +- 1.2 — Given `cwd` is needed only if relative paths are used -* new TypeScript source files (`ts` directory) -* old JavaScript output (`lib` directory). +Now we can produce values from our previous settings: -This is a temporary state, of course, it will be refactored :). -Once we find a suitable solution for bundling TypeScript directly into final js build files - we might remove `lib` directory then. -TypeScript is fired with ``--module commonjs` option - javascript output uses standard CommonJS `require` calls and then -it's processed via browserify to create bundles (as it used to before introducing TypeScript). +```js +// 2.0 - generate a sample from the given `schema` +const syncValue = jsf.generate(schema, refs); -The codebase is quite complex and so the whole process had to be cplit into parts. THe migration/refactoring is still a work-in-progress. +// 2.1 - resolve and generate complex schemas with remote references +const asyncValue = await jsf.resolve(schema, refs, cwd); +``` -# Building +The core provides typed generators for all basic types and well-known formats. -Compile typescript to javascript: +- 2.0 — Built-in generators can be resolved synchronously +- 2.1 — Local and remote references are resolved asynchronously - npm run tsc +For more specific values `jsf` offers a rich menu of options and methods: -Build package files: +```js +// 3.0 - custom formats are supported +jsf.format('name', callback); +jsf.format('name', null); // unregister `name` format - ./build/dist.js +// 3.1 - define `jsf` settings +jsf.option('optionName', 'value'); +jsf.option({ optionName: 'value' }); -# Testing +// 3.2 - the `version` is also exported +jsf.version; // 0.5.0-rc16 -Detailed description of test [can be found here](../spec). +// 3.3 - internal `random` generators +jsf.random; // { pick, date, shuffle, number, randexp } + +// 3.4 - extend keywords with external generators +jsf.extend('chance', () => require('chance')); +jsf.extend('faker', () => require('faker')); + +// 3.5 - extend keywords with custom generators +jsf.define('myProp', (value, schema) => schema); + +// 3.6 - unregister extensions by keyword +jsf.reset('myProp'); +jsf.reset(); // clear extensions + +// 3.7 - retrieve registered extensions by keyword +jsf.locate('faker'); +``` + +- 3.0 — This method should register non supported formats +- 3.1 — You should be able to setup custom behavior or defaults, etc. +- 3.2 — As convenience the package `version` should be exported +- 3.3 — Helpers should be shared too, to be used outside the API +- 3.4 — Third-party generators should be setup through this method (dependencies) +- 3.5 — Custom keywords like `autoIncrement` and `pattern` should be setup through this method (extensions) and stored in a shared container +- 3.6 — Added dependencies and extensions should be cleared from the container through this method, if no name is given the the entire container should be reset +- 3.7 — Any registered third-party generator should be returned through this method + +### Available options + +- `defaultInvalidTypeProduct` — If `failOnInvalidTypes` is disabled this value will be returned on any invalid `type` given (default: `null`) +- `defaultRandExpMax` — Setup default value directly as `RandExp.prototype.max` (default: `10`) +- `ignoreProperties` — Skip given properties from being generated (default: `[]`) +- `ignoreMissingRefs` — If enabled, it will resolve to `{}` for unknown references (default: `false`) +- `failOnInvalidTypes` — If enabled, it will throw an `Error` for unknown types (default: `true`) +- `failOnInvalidFormat` — If enabled, it will throw an `Error` for unknown formats (default: `true`) +- `alwaysFakeOptionals` — When enabled, it will set `optionalsProbability` as `1.0` (default: `false`) +- `optionalsProbability` — A value from `0.0` to `1.0` to generate values in a consistent way, e.g. `0.5` will generate from `0%` to `50%` of values. Using arrays it means items, on objects they're properties, etc. (default: `false`) +- `fixedProbabilities` — If enabled, then `optionalsProbability: 0.5` will always generate the half of values (default: `false`) +- `useExamplesValue` — If enabled, it will return a random value from `examples` if they're present (default: `false`) +- `useDefaultValue` — If enabled, it will return the `default` value if present (default: `false`) +- `requiredOnly` — If enabled, only `required` properties will be generated (default: `false`) +- `minItems` — Override `minItems` if it's less than this value (default: `0`) +- `maxItems` — Override `maxItems` if it's greater than this value (default: `null`) +- `minLength` — Override `minLength` if it's less than this value (default: `0`) +- `maxLength` — Override `maxLength` if it's greater than this value (default: `null`) +- `resolveJsonPath` — If enabled, it will expand `jsonPath` keywords on all generated objects (default: `false`) +- `reuseProperties` — If enabled, it will try to generate missing properties from existing ones. Only when `fillProperties` is enabled too (default: `false`) +- `fillProperties` — If enabled, it will try to generate missing properties to fulfill the schema definition (default: `true`) +- `random` — Setup a custom _randonmess_ generator, useful for getting deterministic results (default: `Math.random`) + +## Building + +**JSON-Schema-Faker** is a JavaScript tool that can be executed both in the browser and the server. + +It's built with [bili](https://github.com/egoist/bili), as we're using ES6 syntax for the source code and modules. + +To generate `dist/` sources run: + +```bash +$ npm run build +``` + +## Testing + +Unit tests are run with `mocha -r esm` in order to use ES6 modules syntax, allowing to import and test code directly from the `src` folder. + +Also we include "schema tests" to ensure generated data is also valid. + +See our [reference guide](TESTING.md) to learn how. + +## Usage + +Use the [website](http://json-schema-faker.js.org/) tool and generate some values right now. + +Please read our [guide](USAGE.md) for further usage instructions. diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 0000000000000000000000000000000000000000..1bb24ce34e57ee795bb63ba004079ea39e553d1b --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,63 @@ +## Testing + +**JSON-Schema-Faker** has performs two types of testing: unit and schema. + +### Unit tests + +- They just validate the building blocks from the entire library +- We're using `mocha` and `chai` so the worflow would be very familiar + +---- + +### Schema tests + +- Kind of functional tests, high-level - executing the whole `jsf` engine to generate fake data against given JSON Schema and checking the quality of the results +- We are using `.json` files to describe the entire testing suite + +Those `.json` files look like the following: + +```json +[ + { + "description": "Feature or issue description", + "tests": [ + { + "description": "Single test description", + "schema": { ... }, + ... + } + ] + }, + ... +] +``` + +Basically it will execute this for you: + +```js +describe('Feature or issue description', () => { + it('Single test description', () => { + // ... + }); +}); +``` + +The properties below are used to setup the test and execute the assertions: + +- `require` — a relative to the spec directory +- `schema` — the main used schema to fake and test +- `refs` — are for resolving used `$ref`'s on the faked schema +- `throws` — test if the an error was expected, can be a boolean or string +- `hasNot` — used for primitives, it will perform a `not.toContain()` test +- `hasProps` — +- `onlyProps` — +- `type` — used for primitives, it will perform a `toHaveType()` test +- `valid` — will test the generated json against the original schema +- `equal` — will test equality for the given schema and the generated one +- `repeat` — will execute the same test many times as given +- `seed` — +- `set` — +- `skip` — +- `only` — +- `count` — +- `length` — diff --git a/docs/USAGE.md b/docs/USAGE.md new file mode 100644 index 0000000000000000000000000000000000000000..4587bf7b81b58832dcbf38a8b83163fd0aaee904 --- /dev/null +++ b/docs/USAGE.md @@ -0,0 +1,550 @@ +# Table of contents + +- Basics + - [JSON-schema-faker](#fake-your-schemas) + - [Online demo](#online-demo) + - [Install](#install) + - [npm](#npm) + - [bower](#bower) + - [cdnjs](#cdnjs) + - [Overview](#overview) + - [Example usage](#example-usage) + - [More examples](#more-examples) + - [Gist demos](#gist-demos) + - [Automation](#automation) + - [Angular-jsf (AngularJS plugin)](#angular-jsf) + - [Grunt plugin](#grunt-plugin) + - [CLI](#cli) + - [Webpack loader](#webpack-loader) +- Advanced + - [JSON Schema specification support](#json-schema-specification-support) + - [Supported keywords](#supported-keywords) + - [Using references](#using-references) + - [Faking values](#faking-values) + - [Advanced usage of faker.js and Chance.js](#user-content-advanced-usage-of-fakerjs-and-chancejs) + - [Custom formats](#custom-formats) + - [Custom options](#custom-options) + - [Extending dependencies](#extending-dependencies) + - [Inferred Types](#inferred-types) + - [Swagger extensions](#swagger-extensions) + - [Bundling](#bundling) +- Misc + - [Contribution](#contribution) + - [Technical Documentation](#technical-documentation) + - [Resources](#resources) + - [Motivation](#motivation) + +## Online demo + +See [online demo](http://json-schema-faker.js.org/). You can save your schemas online and share the link with your collaborators. + +## Install + +`jsf` is installable through 3 different channels: + +### npm + +Install `json-schema-faker` with npm: + + npm install json-schema-faker --save + +### bower + +Install `json-schema-faker` with bower: + + bower install json-schema-faker --save + +### cdnjs + +JSON-Schema-faker is also available at [cdnjs.com](https://www.cdnjs.com/libraries/json-schema-faker). This means you can just include the script file into your HTML: + + # remember to update the version number! + <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/json-schema-faker/0.5.0-rc1/json-schema-faker.min.js"></script> + +It will be fetched from the [Content Delivery Network](https://en.wikipedia.org/wiki/Content_delivery_network) without installing any node.js package. + +You can see [an example JS fiddle based on `jsf` loaded from cdnjs](https://jsfiddle.net/ftzhnmzq/4/). + +## Overview + +JSON-Schema-faker (or `jsf` for short) combines two things: + + * The [JSON-schema specification](http://json-schema.org/draft-04/json-schema-core.html), that defines what is the allowed content of a JSON document + * Fake data generators, that are used to generate basic or complex data, conforming to the schema. + +Since `v0.5.x` external generators are not longer bundled with jsf, however built-in defaults are shipped for all basic types and formats. + +## Example usage + +```javascript +var jsf = require('json-schema-faker'); + +var schema = { + type: 'object', + properties: { + user: { + type: 'object', + properties: { + id: { + $ref: '#/definitions/positiveInt' + }, + name: { + type: 'string', + faker: 'name.findName' + }, + email: { + type: 'string', + format: 'email', + faker: 'internet.email' + } + }, + required: ['id', 'name', 'email'] + } + }, + required: ['user'], + definitions: { + positiveInt: { + type: 'integer', + minimum: 0, + exclusiveMinimum: true + } + } +}; + +jsf.resolve(schema).then(function(sample) { + console.log(sample); + // "[object Object]" + + console.log(sample.user.name); + // "John Doe" +}); +``` +([demo »](http://json-schema-faker.js.org/#gist/927cf888cbc250a2b8e60eb5834cdfbd)) + +`jsf.version` attribute is available to check which version you're using: + +```javascript +var jsf = require('json-schema-faker'); +console.log(jsf.version); +// "0.5.0-rc1" +``` + +### More examples + + * [json-schema.org/example1.html](http://json-schema.org/example1.html): + [warehouse location](http://json-schema-faker.js.org/#gist/bb4774bf26167360e7c5cf2a29db3e56), + [Product from Acme catalog](http://json-schema-faker.js.org/#gist/c7a398c537cf7befce0df67fe7feeea8) + * [json-schema.org/example2.html](http://json-schema.org/example2.html): + [_diskDevice_ storage type](http://json-schema-faker.js.org/#gist/0c0d676023ea505c97eef9af0b4d95da), + [_diskUUID_ storage type](http://json-schema-faker.js.org/#gist/0ac23aa547acfdb2897a7afec3042534), + [_nfs_ storage type](http://json-schema-faker.js.org/#gist/473ac2bc364b2610f7fc703e59cfe1c9), + [_tmpfs_ storage type](http://json-schema-faker.js.org/#gist/de1c5f18f0d231557ce25e44f581cadf) + +### Gist demos + +Clone these gists and execute them locally (each gist has its own readme with instructions): + + * [jsf console](https://gist.github.com/ducin/9f2364ccde2e9248fbcd) - minimal example of jsf working directly under command line + * [jsf grunt](https://gist.github.com/ducin/87e0b55bddd1801d3d99) - example of jsf working under grunt.js + +## Automation + +### angular-jsf + +Use [`angular-jsf`](https://github.com/json-schema-faker/angular-jsf) module (installable via `npm` and `bower`) to get **`jsf` working in your angular app out of the box**! And check out [angular-jsf demo](http://angular-jsf.js.org/). + +### Grunt plugin + +Use [grunt-jsonschema-faker](https://github.com/json-schema-faker/grunt-jsonschema-faker) +to automate running `json-schema-faker` against your JSON schemas. + +### CLI + +Use [json-schema-faker-cli](https://github.com/oprogramador/json-schema-faker-cli) +to run `jsf` from your command line. + +### Webpack loader + +Use [json-schema-faker-loader](https://github.com/jeffcatania/json-schema-faker-loader) +to execute `jsf` as a [webpack](https://webpack.github.io/) loader. + +## JSON Schema specification support + +Currently `jsf` supports the JSON-Schema specification **draft-04** only. + +If you want to use **draft-03**, you may find useful information [here](https://github.com/json-schema-faker/json-schema-faker/issues/66). + +## Supported keywords + +Below is the list of supported keywords: + +- `$ref` — Resolve internal references only, and/or external if provided. +- `required` — All required properties are guaranteed, if not can be omitted. +- `pattern` — Generate samples based on RegExp values. +- `format` — Core formats **v4-draft only**: + [`date-time`](http://json-schema.org/draft-04/json-schema-validation.html#anchor108), + [`email`](http://json-schema.org/draft-04/json-schema-validation.html#anchor111), + [`hostname`](http://json-schema.org/draft-04/json-schema-validation.html#anchor114), + [`ipv4`](http://json-schema.org/draft-04/json-schema-validation.html#anchor117), + [`ipv6`](http://json-schema.org/draft-04/json-schema-validation.html#anchor120) + and [`uri`](http://json-schema.org/draft-04/json-schema-validation.html#anchor123) + -- [demo »](http://json-schema-faker.js.org/#gist/f58db80cbf52c12c623166090240d964) +- `enum` — Returns any of these enumerated values. +- `minLength`, `maxLength` — Applies length constraints to string values. +- `minimum`, `maximum` — Applies constraints to numeric values. +- `exclusiveMinimum`, `exclusiveMaximum` — Adds exclusivity for numeric values. +- `multipleOf` — Multiply constraints for numeric values. +- `items` — Support for subschema and fixed item values. +- `minItems`, `maxItems` — Adds length constraints for array items. +- `uniqueItems` — Applies uniqueness constraints for array items. +- `additionalItems` — Partially supported (?) +- `allOf`, `oneOf`, `anyOf` — Subschema combinators. +- `properties` — Object properties to be generated. +- `minProperties`, `maxProperties` — Adds length constraints for object properties. +- `patternProperties` — RegExp-based object properties. +- `additionalProperties` — Partially supported (?) +- `dependencies` — Not supported yet (?) +- `not` — Not supported yet (?) + +## Using references + +Inline references are fully supported (json-pointers) but external can't be resolved by `jsf`. + +Remote en local references are automatically resolved thanks to `json-schema-ref-parser`. + +```javascript +var schema = { + type: 'object', + properties: { + someValue: { + $ref: 'otherSchema' + } + } +}; + +var refs = [ + { + id: 'otherSchema', + type: 'string' + } +]; + +jsf.resolve(schema, refs).then(function(sample) { + console.log(sample.someValue); + // "voluptatem" +}); +``` + +Local references are always resolved from the `process.cwd()`, of course you can specify a custom folder to look-up: `jsf(schema, refs, cwd)` + +## Faking values + +`jsf` has built-in generators for core-formats, [Faker.js](https://github.com/marak/Faker.js/) and [Chance.js](http://chancejs.com/) (and others) are also supported but they require setup: + +```js +jsf.extend('faker', function() { + return require('faker'); +}); +``` + +```json +{ + "type": "string", + "faker": "internet.email" +} +``` +([demo »](http://json-schema-faker.js.org/#gist/89659ebf28be89d3f860c3f80cbffe4b)) + +The above schema will invoke [`faker.internet.email()`](https://github.com/Marak/faker.js/blob/1f47f09e25ad43db41ea4187c3cd3f7e113d4cb4/lib/internet.js#L32). + +Note that both generators has higher precedence than **format**. + +You can also use standard JSON Schema keywords, e.g. `pattern`: + +```json +{ + "type": "string", + "pattern": "yes|no|maybe|i don't know" +} +``` +([demo »](http://json-schema-faker.js.org/#gist/8ee282679da5a31cd7edc4cf35f37081)) + +### Advanced usage of faker.js and Chance.js + +In following inline code examples the `faker` and `chance` variables are assumed to be created with, respectively: + +```javascript +var faker = require('faker'); + +var Chance = require('chance'), + chance = new Chance(); +``` + +Another example of faking values is passing arguments to the generator: + +```json +{ + "type": "string", + "chance": { + "email": { + "domain": "fake.com" + } + } +} +``` +([demo »](http://json-schema-faker.js.org/#gist/c6ab6a0325e53fd3b38ee0293a9aeea3)) + +which will invoke [`chance.email({ "domain": "fake.com" })`](https://github.com/chancejs/chancejs/blob/b4c143bf53f516dfd77a8376d0f631462458c062/chance.js#L1118). +This example works for single-parameter generator function. + +However, if you pass multiple arguments to the generator function, just pass them wrapped in an array. +In the example below we use the [`faker.finance.amount(min, max, dec, symbol)`](https://github.com/Marak/faker.js/blob/1f47f09e25ad43db41ea4187c3cd3f7e113d4cb4/lib/finance.js#L85) +generator which has 4 parameters. We just wrap them with an array and it's equivalent to `faker.finance.amount(100, 10000, 2, "$")`: + +```json +{ + "type": "object", + "properties": { + "cash": { + "type": "string", + "faker": { + "finance.amount": [100, 10000, 2, "$"] + } + } + }, + "required": [ + "cash" + ] +} +``` +([demo »](http://json-schema-faker.js.org/#gist/3a15a11d706e5b145c30f943d55c42b2)) + +However, if you want to pass a single parameter that is an array itself, e.g. +[`chance.pickone(["banana", "apple", "orange"])`](https://github.com/chancejs/chancejs/blob/b4c143bf53f516dfd77a8376d0f631462458c062/chance.js#L382), +just like [described here](https://github.com/json-schema-faker/json-schema-faker/issues/171), +then you need to wrap it with an array once more (twice in total). The outer brackets determine that the content is gonna be a list of params injected into the generator. The inner brackets are just the value itself - the array we pass: + +```json +{ + "type": "object", + "properties": { + "food": { + "type": "string", + "chance": { + "pickone": [ + [ + "banana", + "apple", + "orange" + ] + ] + } + } + }, + "required": [ + "food" + ] +} +``` +([demo »](http://json-schema-faker.js.org/#gist/792d626e7d92841ded5be59b8ed001eb)) + +## Custom formats + +Additionally, you can add custom generators for those: + +```javascript +jsf.format('semver', function() { + return jsf.random.randexp('\\d\\.\\d\\.[1-9]\\d?'); +}); +``` + +Now that format can be generated: + +```json +{ + "type": "string", + "format": "semver" +} +``` + +Usage: + +- **format()** — Return all registered formats (custom only) +- **format(obj)** — Register formats by key/value → name/callback +- **format(name)** — Returns that format generator (undefined if not exists) +- **format(name, callback)** — Register a custom format by name/callback + +Callback: + +- **schema** (object) — The schema for input + +Note that custom generators has lower precedence than core ones. + +## Custom Options + +You may define following options for `jsf` that alter its behavior: + +- `failOnInvalidTypes`: boolean - don't throw exception when invalid type passed +- `defaultInvalidTypeProduct`: - default value generated for a schema with invalid type (works only if `failOnInvalidTypes` is set to `false`) +- `failOnInvalidFormat`: boolean - don't throw exception when invalid format passed +- `maxItems`: number - Configure a maximum amount of items to generate in an array. This will override the maximum items found inside a JSON Schema. +- `maxLength`: number - Configure a maximum length to allow generating strings for. This will override the maximum length found inside a JSON Schema. +- `random`: Function - a replacement for `Math.random` to support pseudorandom number generation. +- `alwaysFakeOptionals`: boolean - When true, all object-properties will be generated regardless they're `required` or not. +- `optionalsProbability`: number - A decimal number from 0 to 1 that indicates the probability to fake a non-required object property (default: 0). When `0.0`, only `required` properties will be generated; when `1.0`, all properties are generated. This option is overwritten to 1 when `alwaysFakeOptionals = true`. + +Set options just as below: + +```javascript +jsf.option({ + failOnInvalidTypes: false +}); +``` + +## Extending dependencies + +You may extend [Faker.js](http://marak.com/faker.js/): + +```javascript +var jsf = require('json-schema-faker'); + +jsf.extend('faker', function(){ + var faker = require('faker'); + + faker.locale = "de"; // or any other language + faker.custom = { + statement: function(length) { + return faker.name.firstName() + " has " + faker.finance.amount() + " on " + faker.finance.account(length) + "."; + } + }; + return faker; +}); + +var schema = { + "type": "string", + "faker": { + "custom.statement": [19] + } +} + +jsf.resolve(schema).then(...); +``` + +or if you want to use [faker's *individual localization packages*](https://github.com/Marak/faker.js#individual-localization-packages), simply do the following: + +```js +jsf.extend('faker', function() { + // just ignore the passed faker instance + var faker = require('faker/locale/de'); + // do other stuff + return faker; +}); +``` + +You can also extend [Chance.js](http://chancejs.com/), using built-in [chance.mixin](http://chancejs.com/#mixin) function: + +```javascript +var jsf = require('json-schema-faker'); + +jsf.extend('chance', function(){ + var Chance = require('chance'); + var chance = new Chance(); + + chance.mixin({ + 'user': function() { + return { + first: chance.first(), + last: chance.last(), + email: chance.email() + }; + } + }); + + return chance; +}); + +var schema = { + "type": "string", + "chance": "user" +} + +jsf.resolve(schema).then(...); +``` + +The first parameter of `extend` function is the generator name (`faker`, `chance`, etc.). The second one is the function that **must return** the dependency library. + +## Inferred Types + +JSON Schema does not require you to provide the `type` property for your JSON Schema documents and document fragments. + +But since `jsf` uses the `type` property to create the proper fake data, we attempt to infer the type whenever it is not provided. We do this based on the JSON Schema validation properties you use. + +> Now this means that if you do not use any of the JSON Schema validation properties, jsf will not be able to infer the type for you and you will need to **explicitly** set your `type` manually.) + +Below is the list of JSON Schema validation properties and the inferred type based on the property: + +**array** + +* `additionalItems` +* `items` +* `maxItems` +* `minItems` +* `uniqueItems` + +**integer** *(Number uses the same properties so if you need `number`, set your `type` explicitly)* + +* `exclusiveMaximum` +* `exclusiveMinimum` +* `maximum` +* `minimum` +* `multipleOf` + +**object** + +* `additionalProperties` +* `dependencies` +* `maxProperties` +* `minProperties` +* `patternProperties` +* `properties` +* `required` + +**string** + +* `maxLength` +* `minLength` +* `pattern` + +## Swagger extensions + +`jsf` supports [OpenAPI Specification *vendor extensions*](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#vendorExtensions), i.e. + +* `x-faker` property that stands for `faker` property ([demo »](http://json-schema-faker.js.org/#gist/7cdf200c27eceb6163a79fbc50813fcb)) +* `x-chance` property that stands for `chance` property ([demo »](http://json-schema-faker.js.org/#gist/c0084695b4ca1c4cd015ded1f5c6dc33)) + +Thanks to it, you can use valid swagger definitions for `jsf` data generation. + +## Bundling + +JSON-Schema-faker might be used in Node.js as well as in the browser. In order to execute `jsf` in a browser, you should include the distribution file from [`dist`](dist) directory. Each new version of `jsf` is bundled using [Rollup.js](http://rollupjs.org/) and stored by the library maintainers. The bundle includes full versions of all dependencies. + +> From `v0.5.x` and beyond we'll not longer bundle external generators, locales and such due the unnecessary waste of time and space. + +## Resources + +* [JSON, JSON Schema & JSON-schema-faker](https://www.youtube.com/watch?v=TkqiUG3j_Xw) - WarsawJS meetup presentation recording, a step-by-step guide to JSON-related tools, including `jsf` + +## Motivation + +There were some existing projects or services trying to achieve similar goals as `jsf`: + +- http://www.json-generator.com/ +- https://github.com/unindented/fake-json +- https://github.com/jonahkagan/schematic-ipsum +- https://www.npmjs.org/package/json-schema-mock +- https://github.com/thaume/json-schema-processor +- https://github.com/andreineculau/json-schema-random +- https://github.com/murgatroid99/json-schema-random-instance +- https://github.com/tomarad/JSON-Schema-Instantiator + +...but they were either incomplete, outdated, broken or non-standard. That's why `jsf` was created. diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index d17abfac0d6dd99f00554cf7b1732aded7a9d5dd..0000000000000000000000000000000000000000 Binary files a/lib/index.js and /dev/null differ diff --git a/package-lock.json b/package-lock.json index 08671be8963a80f0f43119c6ccdd5135c5bc88de..b0bdedf89918d41397f592dd798a7ffc20b3dfcd 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index 92097c6b43689c1e8281e8693ab7ea6935a2aca1..e236176c4642b9cb9727859113317d0643eda379 100644 --- a/package.json +++ b/package.json @@ -3,35 +3,35 @@ "version": "0.5.0-rc15", "description": "JSON-Schema + fake data generators", "homepage": "http://json-schema-faker.js.org", - "main": "lib/index.js", + "main": "dist/json-schema-faker.cjs.js", + "module": "dist/json-schema-faker.es.js", + "browser": "dist/json-schema-faker.min.js", "scripts": { - "dist": "npm run build:dist", - "watch": "tarima watch -d VERSION=`./build/VERSION`", - "build": "tarima -qf VERSION=`./build/VERSION`", - "build:dist": "npm run build && node build/dist.js", - "test": "npm run test:lint && npm run build && npm run test:unit && npm run test:schema", - "test:lint": "tslint ts/**/*.ts", - "test:unit": "jasmine-node spec/unit --noStackTrace --captureExceptions", - "test:schema": "jasmine-node spec/schema --coffee --noStackTrace --captureExceptions", - "dev": "jasmine-node spec/schema --coffee --verbose --autoTest --watchFolders lib", - "dev:unit": "jasmine-node spec/unit --coffee --verbose --autoTest --watchFolders lib", - "cover:up": "codecov --file=coverage/lcov.info --disable=gcov -e TRAVIS_NODE_VERSION", - "cover:unit": "istanbul cover --root lib --x '**/spec/**' -- jasmine-node --coffee spec/unit", - "cover:schema": "istanbul cover --root lib --x '**/spec/**' -- jasmine-node --coffee spec/schema", - "typedoc": "typedoc --out docs/html ts/ --module commonjs", - "graphviz": "madge lib --dot > structure.gv" + "build:concat:dist": "concat -o dist/json-schema-faker.bundle.min.js node_modules/json-schema-ref-parser/dist/ref-parser.min.js node_modules/jsonpath/jsonpath.js dist/json-schema-faker.min.js", + "build:browser": "bili --banner --target browser --format umd,umd-min --moduleName JSONSchemaFaker --name json-schema-faker --js buble && npm run build:concat:dist", + "build:node": "bili src/index.js --target node --js buble --format es,cjs", + "build": "npm run build:browser && npm run build:node", + "dev": "npm test -- -w", + "test": "npm run test:unit --", + "test:ci": "npm run coverage:all && npm run report -- -r lcov", + "test:all": "npm run test:run tests && npm run report -- -r html", + "test:run": "NODE_ENV=test _mocha --exit --recursive --watch-extensions js,json -r esm -bR spec", + "test:unit": "npm run test:run tests/unit", + "test:schema": "npm run test:run tests/schema", + "coverage": "nyc -x '**/tests/**' -x '**/*.spec.js'", + "coverage:all": "npm run coverage -- npm run test:all", + "coverage:unit": "npm run coverage -- npm run test:unit", + "codecov": "codecov --file=coverage/lcov.info -e TRAVIS_NODE_VERSION", + "report": "nyc report", + "lint": "eslint src tests", + "pretest": "npm run lint", + "prepublish": "npm run build", + "graphviz": "madge src --dot > structure.gv" }, + "author": "Alvaro Cabrera <pateketrueke@gmail.com> (https://soypache.co)", "contributors": [ - { - "name": "Alvaro Cabrera", - "email": "pateketrueke@gmail.com", - "url": "https://soypache.co" - }, - { - "name": "Tomasz Ducin", - "email": "tomasz@ducin.it", - "url": "http://ducin.it" - } + "Ajay Karat <info@devilsgarage.com> (http://devilsgarage.com/)", + "Tomasz Ducin <tomasz@ducin.it> (http://ducin.it)" ], "repository": { "type": "git", @@ -39,66 +39,44 @@ }, "bugs": "https://github.com/json-schema-faker/json-schema-faker/issues", "license": "MIT", - "tarima": { - "from": "ts", - "output": "lib", - "watching": [ - "spec" - ], - "bundle": true, - "ignore": [ - "**/*.d.ts" - ], - "filter": [ - "!*/*/**" - ], - "rename": [ - "ts/**:{filepath/1}/{filename}.{extname}" - ], - "bundleOptions": { - "rollup": { - "bundle": "JSONSchemaFaker", - "format": "cjs", - "interop": false - } - } - }, "keywords": [ "json", "jsonschema", "fake", "mocks" ], + "bili": { + "external": [ + "json-schema-ref-parser", + "jsonpath" + ] + }, "devDependencies": { + "ajv": "^6.5.3", + "bili": "^3.1.2", + "chai": "^4.1.2", "chance": "^1.0.9", - "clone": "^2.1.1", + "clone": "^2.1.2", "codecov": "^3.0.0", + "concat": "^1.0.3", + "esm": "^3.0.82", "faker": "^4.1.0", - "fs-extra": "^5.0.0", + "fs-extra": "^7.0.0", "glob": "^7.1.2", - "google-closure-compiler-js": "^20180402.0.0", - "istanbul": "^0.4.5", - "jasmine-node": "2.0.0-beta4", - "jayschema": "^0.3.2", - "lodash.template": "^4.4.0", - "rollup": "^0.57.1", + "is-my-json-valid": "^2.19.0", + "mocha": "^5.2.0", + "nyc": "^13.0.1", + "rollup": "^0.65.0", "rollup-plugin-commonjs": "^9.1.0", "rollup-plugin-node-resolve": "^3.0.0", "seedrandom": "^2.4.3", "semver": "^5.3.0", - "tarima": "^4.0.3", - "ts-node": "^5.0.0", - "tslint": "^5.3.2", "tv4": "^1.3.0", - "typedoc": "^0.11.1", - "typescript": "^2.3.4", "z-schema": "^3.18.4" }, "dependencies": { - "deref": "^0.7.1", "json-schema-ref-parser": "^5.0.0", "jsonpath": "^1.0.0", - "randexp": "^0.4.5", - "tslib": "^1.7.1" + "randexp": "^0.5.3" } } diff --git a/spec/README.md b/spec/README.md deleted file mode 100644 index 36994b4a08710852e46cec586cdfe77f88c8b865..0000000000000000000000000000000000000000 --- a/spec/README.md +++ /dev/null @@ -1,59 +0,0 @@ -`jsf` has two types of tests: - -* [unit tests](#unit-tests) -* [schema tests](#schema-tests) - ----- - -# unit tests - -location: [`spec/unit`](unit) - -Typical unit tests, low-level. Using `jasmine` under `jasmine-node`. Each *spec* file loads a js unit using node.js `require` function and fires assertions against it. That's all. - ----- - -# schema tests - -location: [`spec/schema`](schema) - -Kind of functional tests, high-level - executing the whole `jsf` engine to generate fake data against given JSON Schema and checking the quality of the results. - -The *specs* are based on our custom test layer (that wraps `jasmine`). We are using json files to describe the entire testing suite. However, the low-level assertions are run using `jasmine-node` beneath. - -The json files look like the following: - -```json -[ - { - "description": "Feature or issue description", - "tests": [ - { - "description": "Single test description", - "schema": { ... }, - ... - } - ] - }, - ... -] -``` - -Basically it will execute this for you: - -```coffeescript -describe 'Feature or issue description', -> - it 'Single test description', -> - ... -``` - -The properties below are used to setup the test and execute the assertions: - -- `require` a relative to the spec directory -- `schema` the main used schema to fake and test -- `refs` are for resolving used `$ref`'s on the faked schema -- `throws` test if the an error was expected, can be a boolean or string -- `hasNot` used for primitives, it will perform a `not.toContain()` test -- `type` used for primitives, it will perform a `toHaveType()` test -- `valid` will test the generated json against the original schema -- `equal` will test equality for the given schema and the generated one diff --git a/spec/schema/core/extend/chance-extend.js b/spec/schema/core/extend/chance-extend.js deleted file mode 100644 index b826ce31d0b70653ea2f1839a4a299b544d032e0..0000000000000000000000000000000000000000 --- a/spec/schema/core/extend/chance-extend.js +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = { - extend: function () { - var Chance = require('chance'); - var chance = new Chance(); - - chance.mixin({ - 'user': function() { - return { - first: chance.first(), - last: chance.last(), - email: chance.email() - }; - } - }); - - return chance; - }, - register: function(jsf) { - return jsf.extend('chance', this.extend); - } -}; diff --git a/spec/schema/core/extend/faker-extend.js b/spec/schema/core/extend/faker-extend.js deleted file mode 100644 index fe219846d26c6a3cce2aeb1f389b6d068a5f9815..0000000000000000000000000000000000000000 --- a/spec/schema/core/extend/faker-extend.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - extend: function () { - var faker = require('faker/locale/de'); - - faker.mixin = function (namespace, fnObject) { - faker[namespace] = fnObject; - }; - - faker.mixin('custom', { - statement: function (length) { - return faker.name.firstName() + " has " + faker.finance.amount() + " on " + faker.finance.account(length) + "."; - } - }); - - return faker; - }, - register: function(jsf) { - return jsf.extend('faker', this.extend); - } -}; diff --git a/spec/schema/core/formats/semver.js b/spec/schema/core/formats/semver.js deleted file mode 100644 index ed5e13ba7faec4616dd62d9b20b4e058851f2dbd..0000000000000000000000000000000000000000 --- a/spec/schema/core/formats/semver.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - register: function (jsf) { - return jsf.format({ - semver: function () { - return jsf.random.randexp('\\d\\.\\d\\.[1-9]\\d?'); - } - }); - } -}; diff --git a/spec/schema/core/option/alwaysFakeOptionals.js b/spec/schema/core/option/alwaysFakeOptionals.js deleted file mode 100644 index 035bd0f9879963ff2cd20bf7c79e527e50958e97..0000000000000000000000000000000000000000 --- a/spec/schema/core/option/alwaysFakeOptionals.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - register: function(jsf) { - return jsf.option({ - 'useDefaultValue': true, - 'alwaysFakeOptionals': true - }); - } -}; diff --git a/spec/schema/core/option/enable-jsonpath.js b/spec/schema/core/option/enable-jsonpath.js deleted file mode 100644 index f680543bbf117e440ab6ab60b380ec89b61941a4..0000000000000000000000000000000000000000 --- a/spec/schema/core/option/enable-jsonpath.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - register: function(jsf) { - return jsf.option({ - resolveJsonPath: true, - }); - } -}; diff --git a/spec/schema/core/option/failOnInvalidType.js b/spec/schema/core/option/failOnInvalidType.js deleted file mode 100644 index 8662c91b18bad26bacff6a7bae46cc44309aacd3..0000000000000000000000000000000000000000 --- a/spec/schema/core/option/failOnInvalidType.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - register: function(jsf) { - return jsf.option({ - 'failOnInvalidTypes': false - }); - } -}; - diff --git a/spec/schema/core/option/ignoreProperties.js b/spec/schema/core/option/ignoreProperties.js deleted file mode 100644 index 3abe31b2c5095ed30efced2a0c2251a7e4d06704..0000000000000000000000000000000000000000 --- a/spec/schema/core/option/ignoreProperties.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - register: function(jsf) { - return jsf.option({ - fillProperties: false, - ignoreProperties: ['foo', /^b/, x => x.default === 42] - }); - } -}; diff --git a/spec/schema/core/option/optionalsProbabilityEquals1.js b/spec/schema/core/option/optionalsProbabilityEquals1.js deleted file mode 100644 index c269a57e63ccfad948f098071ffb9ae2a3868d1a..0000000000000000000000000000000000000000 --- a/spec/schema/core/option/optionalsProbabilityEquals1.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - register: function(jsf) { - return jsf.option({ - 'useDefaultValue': true, - 'optionalsProbability': 1.0 - }); - } -}; diff --git a/spec/schema/core/option/optionalsProbabilityOverwritten.js b/spec/schema/core/option/optionalsProbabilityOverwritten.js deleted file mode 100644 index d8aabc2010d2cad95fee694f40fe502b06f8af26..0000000000000000000000000000000000000000 --- a/spec/schema/core/option/optionalsProbabilityOverwritten.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - register: function(jsf) { - return jsf.option({ - 'useDefaultValue': true, - 'alwaysFakeOptionals': true, - 'optionalsProbability': 0.0 - }); - } -}; diff --git a/spec/schema/core/option/random.js b/spec/schema/core/option/random.js deleted file mode 100644 index d53558ac0f372d382957bcfec7e68358f079f1c2..0000000000000000000000000000000000000000 --- a/spec/schema/core/option/random.js +++ /dev/null @@ -1,9 +0,0 @@ -const seedrandom = require('seedrandom'); - -module.exports = { - register: function(jsf) { - return jsf.option({ - random: seedrandom('some seed') - }); - } -}; diff --git a/spec/schema/core/option/useDefaultValue.js b/spec/schema/core/option/useDefaultValue.js deleted file mode 100644 index 7d9b208a80cbee297040c8759cac8e241e47ee75..0000000000000000000000000000000000000000 --- a/spec/schema/core/option/useDefaultValue.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - register: function(jsf) { - return jsf.option({ - 'useDefaultValue': true - }); - } -}; - diff --git a/spec/schema/helpers.coffee b/spec/schema/helpers.coffee deleted file mode 100644 index eb91535c411a816b7690eda39e3356fbb5a42315..0000000000000000000000000000000000000000 --- a/spec/schema/helpers.coffee +++ /dev/null @@ -1,88 +0,0 @@ -tv4 = require('tv4') -clone = require('clone') -ZSchema = require('z-schema') -JaySchema = require('jayschema') - -formatValidators = require('./validator').validate - -[tv4, ZSchema].map formatValidators - -global.customMatchers = - toHaveType: -> - compare: (actual, expected) -> - test = Object::toString.call(actual).match(/object (\w+)/) - - if test[1].toLowerCase() isnt expected - pass: false - message: "Expected #{JSON.stringify actual} to have #{expected} type" - else - pass: true - - toHaveSchema: -> - compare: (actual, expected) -> - [ expected, refs ] = expected if Array.isArray(expected) - - fail = [] - fixed = {} - - if refs - fixed[s.id.split('#')[0]] = clone(s) for s in refs - - # z-schema - validator = new ZSchema - ignoreUnresolvableReferences: false - - validator.setRemoteReference(k, v) for k, v of fixed - - try - valid = validator.validate clone(actual), clone(expected) - catch e - fail.push e.message - - if errors = validator.getLastErrors() or not valid - fail.push errors.map((e) -> - if e.code is 'PARENT_SCHEMA_VALIDATION_FAILED' - e.inner.map((e) -> e.message).join '\n' - else - e.message - ).join('\n') or "Invalid schema #{JSON.stringify actual}" - - # tv4 - api = tv4.freshApi() - - api.banUnknown = false - api.cyclicCheck = false - - api.addSchema(id, json) for id, json of fixed - - result = api.validateResult actual, - clone(expected), api.cyclicCheck, api.banUnknown - - if result.missing.length - fail.push 'Missing ' + result.missing.join(', ') - - fail.push(result.error) if result.error - - # jayschema - jay = new JaySchema - - formatValidators jay - - jay.register(clone(json)) for id, json of fixed - - result = jay.validate actual, clone(expected) - - if result.length - fail.push result.map((e) -> e.desc or e.message).join('\n') or - "Invalid schema #{JSON.stringify actual}" - - pass: !fail.length - message: fail.join('\n') if fail.length - message: fail.length and """ - #{fail.join('\n')} - --- - #{JSON.stringify(actual, null, 2)} - --- - #{JSON.stringify(expected, null, 2)} - --- - """ diff --git a/spec/schema/main-spec.coffee b/spec/schema/main-spec.coffee deleted file mode 100644 index 2e948886c08e47bea44748d79e602f158023d6c3..0000000000000000000000000000000000000000 --- a/spec/schema/main-spec.coffee +++ /dev/null @@ -1,120 +0,0 @@ -fs = require('fs') -glob = require('glob') -jsf = require('../../') - -pick = (obj, key) -> - parts = key.split('.') - obj = obj[parts.shift()] while parts.length - obj - -tryTest = (test, refs, schema) -> - return if test.skip - - (if test.async - jsf.resolve(schema, refs) - else - Promise.resolve().then -> - jsf(schema, refs)) - .catch (error) -> - if typeof test.throws is 'string' - expect(error).toMatch new RegExp(test.throws, 'im') - - if typeof test.throws is 'boolean' - throw error if test.throws isnt true - .then (sample) -> - if test.dump - console.log 'IN', JSON.stringify(schema, null, 2) - console.log 'OUT', JSON.stringify(sample, null, 2) - return - - if test.hasProps - test.hasProps.forEach (prop) -> - if Array.isArray(sample) - sample.forEach (s) -> - expect(s[prop]).not.toBeUndefined() - else - expect(sample[prop]).not.toBeUndefined() - - if test.onlyProps - expect(Object.keys(sample)).toEqual test.onlyProps - - if test.count - expect((if Array.isArray(sample) then sample - else Object.keys(sample)).length).toEqual test.count - - if test.hasNot - expect(JSON.stringify sample).not.toContain test.hasNot - - if test.type - expect(sample).toHaveType test.type - - if test.valid - expect(sample).toHaveSchema [schema, refs] - - if "equal" of test - expect(sample).toEqual test.equal - -only = [] -all = [] - -glob.sync("#{__dirname}/**/*.json").forEach (file) -> - suite = try - JSON.parse(fs.readFileSync(file)) - catch e - console.log "Invalid JSON: #{file}" - console.log e.message - process.exit 1 - null - - (if Array.isArray(suite) then suite else [suite]).forEach (x) -> - return if x.xdescription? - _only = false - suite = { file } - suite[k] = v for k, v of x - suite.tests = suite.tests.sort (a, b) -> - return -1 if a.only? - return 1 if b.only? - return 0 - .filter (y) -> - if (_only and !y.only?) or y.xdescription? - return false - _only = true if y.only - true - only.push(suite) if x.only? or _only - all.push(suite) - -(if only.length then only else all).forEach (suite) -> - describe "#{suite.description} (#{suite.file.replace(__dirname + '/', '')})", -> - beforeEach -> - jasmine.addMatchers(customMatchers) - - suite.tests.forEach (test) -> - it test.description, (done) -> - jsf.option(jsf.option.getDefaults()) - - if test.set - jsf.option(test.set) - - if test.require - wrapper = require('./' + test.require) - wrapper.register(jsf) - - schema = if typeof test.schema is 'string' - pick(suite, test.schema) - else - test.schema - - refs = test.refs?.map (ref) -> - if typeof ref is 'string' - pick(suite, ref) - else - ref - - # support for "exhaustive" testing, increase or set in .json spec - # for detecting more bugs quickly by executing the same test N-times - nth = test.repeat or (if process.CI then 100 else 10) - - tasks = [] - tasks.push(tryTest(test, refs, schema)) while nth-- - - Promise.all(tasks).then(done) diff --git a/spec/schema/validator.coffee b/spec/schema/validator.coffee deleted file mode 100644 index 51fbe716ef2384db078b914827a05df69d114f1d..0000000000000000000000000000000000000000 --- a/spec/schema/validator.coffee +++ /dev/null @@ -1,20 +0,0 @@ -semver = require('semver') - -module.exports = - validate: (v) -> - registry = v.addFormat or v.registerFormat - msgOnFail = not v.registerFormat - - registry.call v, 'semver', (value) -> - err = try - pass = semver.valid(value) is value - catch e - e.message - - if msgOnFail - # tv4, Jayschema - return null if pass - err - else - # ZSchema - pass diff --git a/spec/unit/core/infer.spec.js b/spec/unit/core/infer.spec.js deleted file mode 100644 index 82836c80b86ecebaed006d82d03bf1f765dc520c..0000000000000000000000000000000000000000 --- a/spec/unit/core/infer.spec.js +++ /dev/null @@ -1,12 +0,0 @@ -// var infer = require('../../../ts/core/infer').default; - -// describe("Infer", function () { - -// it("should infer `array` type when `additionalItems` property exists on top-level schema", function () { -// var schema = { -// "additionalItems": true -// }; - -// expect(infer(schema, "")).toEqual("array"); -// }); -// }); diff --git a/spec/unit/core/utils.spec.js b/spec/unit/core/utils.spec.js deleted file mode 100644 index 10f55e61400dde508a74d87279780646e5489e21..0000000000000000000000000000000000000000 --- a/spec/unit/core/utils.spec.js +++ /dev/null @@ -1,93 +0,0 @@ -var utils = require('../../../ts/core/utils').default; -var optionAPI = require('../../../ts/api/option').default; - -describe("Utils", function () { - - describe("hasProperties function", function () { - var bigObject = { - "some": "keys", - "existing": "on", - "the": "object" - }; - - var smallObject = { - "some": "keys" - }; - - it("should return true when one key being checked", function () { - expect(utils.hasProperties(bigObject, "some")).toBe(true); - expect(utils.hasProperties(bigObject, "existing")).toBe(true); - expect(utils.hasProperties(bigObject, "the")).toBe(true); - expect(utils.hasProperties(smallObject, "some")).toBe(true); - }); - - it("should return true when all keys being checked", function () { - expect(utils.hasProperties(bigObject, "some", "existing", "the")).toBe(true); - expect(utils.hasProperties(smallObject, "some", "existing", "the")).toBe(true); - }); - - it("should return false when no keys exist on object", function () { - expect(utils.hasProperties(bigObject, "different")).toBe(false); - expect(utils.hasProperties(smallObject, "different")).toBe(false); - }); - }); - - describe("getSubAttribute function", function () { - var object = { - "outer": { - "inner": { - "key": "value" - } - } - }; - - it("should return a leaf if chain is long enough", function () { - expect(utils.getSubAttribute(object, "outer.inner.key")).toEqual("value"); - expect(utils.getSubAttribute(object, "outer.inner.key.help.me")).toEqual("value"); - }); - - it("should return a subobject if the chain doesn't reach a leaf (is shorter)", function () { - expect(utils.getSubAttribute(object, "outer.inner")).toEqual({"key": "value"}); - }); - - it("should return a subobject of the valid chain part (and ignore the invalid chain part)", function () { - expect(utils.getSubAttribute(object, "outer.help.me")).toEqual({"inner": {"key": "value"}}); - expect(utils.getSubAttribute(object, "help.me")).toEqual(object); - }); - }); - - describe("typecast function", function() { - var typecast = utils.typecast; - - it('should normalize constraints and format final values', () => { - typecast({}, opts => { - expect(opts).toEqual({}); - }); - - var schema = { - type: 'integer', - enum: [1, 2, 3], - minimum: 2, - }; - - typecast(schema, opts => { - expect(schema.enum).toEqual([2, 3]); - expect(opts).toEqual({ minimum: 2 }); - }); - }); - - it('should normalize constraints with global options', () => { - optionAPI({ - maxLength: 4, - }); - - typecast({ - type: 'string', - maxLength: 10, - }, opts => { - expect(opts).toEqual({ maxLength: 4 }); - }); - }); - - }); -}); diff --git a/spec/unit/generators/boolean.spec.js b/spec/unit/generators/boolean.spec.js deleted file mode 100644 index 23d2418e8c01fe7549b2d8269201eb4061c18e04..0000000000000000000000000000000000000000 --- a/spec/unit/generators/boolean.spec.js +++ /dev/null @@ -1,7 +0,0 @@ -var booleanGenerator = require('../../../ts/generators/boolean').default; - -describe("Boolean Generator", function() { - it("should always return a boolean type", function() { - expect(booleanGenerator()).toEqual(jasmine.any(Boolean)); - }); -}); diff --git a/spec/unit/generators/ipv4.spec.js b/spec/unit/generators/ipv4.spec.js deleted file mode 100644 index b3e02b15ffb4987b922edc1f475cc2881ef70014..0000000000000000000000000000000000000000 --- a/spec/unit/generators/ipv4.spec.js +++ /dev/null @@ -1,7 +0,0 @@ -var ipv4Generator = require('../../../ts/generators/ipv4').default; - -describe("IPv4 Generator", function() { - it("should always match the IPv4 regex", function() { - expect(ipv4Generator()).toMatch(/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/); - }); -}); diff --git a/spec/unit/generators/null.spec.js b/spec/unit/generators/null.spec.js deleted file mode 100644 index e5fdba736a330b63fae2dff409e4a3733c33a8f6..0000000000000000000000000000000000000000 --- a/spec/unit/generators/null.spec.js +++ /dev/null @@ -1,7 +0,0 @@ -var nullGenerator = require('../../../ts/generators/null').default; - -describe("Null Generator", function() { - it("should always return `null` value", function() { - expect(nullGenerator()).toEqual(null); - }); -}); diff --git a/spec/unit/generators/number.spec.js b/spec/unit/generators/number.spec.js deleted file mode 100644 index 68bb181de387f33b5abcc4beeff98314cfebe665..0000000000000000000000000000000000000000 --- a/spec/unit/generators/number.spec.js +++ /dev/null @@ -1,8 +0,0 @@ -var numberType = require('../../../ts/types/number').default; - -describe("Number Generator", function() { - it("should return number with a fractional part", function() { - var n = numberType({}); - expect(n).not.toEqual(Math.floor(n)); - }); -}); diff --git a/spec/unit/helpers.js b/spec/unit/helpers.js deleted file mode 100644 index 3d91a2527cf8e253406929f397d5413d9b9a506c..0000000000000000000000000000000000000000 --- a/spec/unit/helpers.js +++ /dev/null @@ -1 +0,0 @@ -require('ts-node').register({ fast: true }); diff --git a/ts/api/format.ts b/src/api/format.js similarity index 76% rename from ts/api/format.ts rename to src/api/format.js index 9a0670cf586ca411c223029ab20eb30a4420a0bf..8d752c0a1ed95530da211a0a73ae311a012c6e89 100644 --- a/ts/api/format.ts +++ b/src/api/format.js @@ -1,9 +1,7 @@ import Registry from '../class/Registry'; -type Format = Function; - // instantiate -var registry = new Registry<Format>(); +const registry = new Registry(); /** * Custom format API @@ -13,12 +11,14 @@ var registry = new Registry<Format>(); * @param callback * @returns {any} */ -function formatAPI(nameOrFormatMap?: string|Object, callback?: Format): any { +function formatAPI(nameOrFormatMap, callback) { if (typeof nameOrFormatMap === 'undefined') { return registry.list(); } else if (typeof nameOrFormatMap === 'string') { if (typeof callback === 'function') { registry.register(nameOrFormatMap, callback); + } else if (callback === null || callback === false) { + registry.unregister(nameOrFormatMap); } else { return registry.get(nameOrFormatMap); } diff --git a/ts/api/option.ts b/src/api/option.js similarity index 66% rename from ts/api/option.ts rename to src/api/option.js index d10343143345116f85dddcff8bd04414c62e0501..e014d608c0d361b87188796f956e02c29f6550c2 100644 --- a/ts/api/option.ts +++ b/src/api/option.js @@ -1,7 +1,7 @@ import OptionRegistry from '../class/OptionRegistry'; // instantiate -var registry = new OptionRegistry(); +const registry = new OptionRegistry(); /** * Custom option API @@ -9,12 +9,12 @@ var registry = new OptionRegistry(); * @param nameOrOptionMap * @returns {any} */ -function optionAPI(nameOrOptionMap?: string|Object): any { +function optionAPI(nameOrOptionMap) { if (typeof nameOrOptionMap === 'string') { return registry.get(nameOrOptionMap); - } else { - return registry.registerMany(nameOrOptionMap); } + + return registry.registerMany(nameOrOptionMap); } optionAPI.getDefaults = () => registry.defaults; diff --git a/ts/class/Container.ts b/src/class/Container.js similarity index 67% rename from ts/class/Container.ts rename to src/class/Container.js index eba92a249e88a691c8f334a53f102e35d3889cdb..530a548760bd055746d88fdcd260a581eede3ae3 100644 --- a/ts/class/Container.ts +++ b/src/class/Container.js @@ -1,20 +1,10 @@ -function template(value, schema) { - if (Array.isArray(value)) { - return value.map(x => template(x, schema)); - } - - if (typeof value === 'string') { - value = value.replace(/#\{([\w.-]+)\}/g, (_, $1) => schema[$1]); - } - - return value; -} +import util from '../core/utils'; // dynamic proxy for custom generators function proxy(gen) { return (value, schema, property, rootSchema) => { - var fn = value; - var args = []; + let fn = value; + let args = []; // support for nested object, first-key is the generator if (typeof value === 'object') { @@ -30,10 +20,10 @@ function proxy(gen) { } // support for keypaths, e.g. "internet.email" - var props = fn.split('.'); + const props = fn.split('.'); // retrieve a fresh dependency - var ctx = gen(); + let ctx = gen(); while (props.length > 1) { ctx = ctx[props.shift()]; @@ -44,31 +34,22 @@ function proxy(gen) { // invoke dynamic generators if (typeof value === 'function') { - value = value.apply(ctx, args.map(x => template(x, rootSchema))); + value = value.apply(ctx, args.map(x => util.template(x, rootSchema))); } // test for pending callbacks if (Object.prototype.toString.call(value) === '[object Object]') { - for (var key in value) { + Object.keys(value).forEach(key => { if (typeof value[key] === 'function') { - throw new Error('Cannot resolve value for "' + property + ': ' + fn + '", given: ' + value); + throw new Error(`Cannot resolve value for '${property}: ${fn}', given: ${value}`); } - } + }); } return value; }; } -type Dependency = any; - -/** - * string => extension map object - */ -type Registry = { - [s: string]: Dependency; -}; - /** * Container is used to wrap external generators (faker, chance, casual, etc.) and its dependencies. * @@ -78,9 +59,6 @@ type Registry = { * RandExp is not longer considered an "extension". */ class Container { - private registry: Registry; - private support: Registry; - constructor() { // dynamic requires - handle all dependencies // they will NOT be included on the bundle @@ -88,12 +66,26 @@ class Container { this.support = {}; } + /** + * Unregister extensions + * @param name + */ + reset(name) { + if (!name) { + this.registry = {}; + this.support = {}; + } else { + delete this.registry[name]; + delete this.support[name]; + } + } + /** * Override dependency given by name * @param name * @param callback */ - public extend(name: string, callback: Function): void { + extend(name, callback) { this.registry[name] = callback(this.registry[name]); // built-in proxy (can be overridden) @@ -107,7 +99,7 @@ class Container { * @param name * @param callback */ - public define(name: string, callback: Function): void { + define(name, callback) { this.support[name] = callback; } @@ -116,9 +108,9 @@ class Container { * @param name * @returns {Dependency} */ - public get(name: string): Dependency { + get(name) { if (typeof this.registry[name] === 'undefined') { - throw new ReferenceError('"' + name + '" dependency doesn\'t exist.'); + throw new ReferenceError(`'${name}' dependency doesn't exist.`); } return this.registry[name]; } @@ -127,21 +119,22 @@ class Container { * Apply a custom keyword * @param schema */ - public wrap(schema: JsonSchema): any { - var keys = Object.keys(schema); - var length = keys.length; - var context = {}; + wrap(schema) { + const keys = Object.keys(schema); + const context = {}; + + let length = keys.length; - while (length--) { - var fn = keys[length].replace(/^x-/, ''); - var gen = this.support[fn]; + while (length--) { // eslint-disable-line + const fn = keys[length].replace(/^x-/, ''); + const gen = this.support[fn]; if (typeof gen === 'function') { Object.defineProperty(schema, 'generate', { configurable: false, enumerable: false, writable: false, - value: rootSchema => gen.call(context, schema[keys[length]], schema, keys[length], rootSchema), + value: rootSchema => gen.call(context, schema[keys[length]], schema, keys[length], rootSchema), // eslint-disable-line }); break; } diff --git a/src/class/OptionRegistry.js b/src/class/OptionRegistry.js new file mode 100644 index 0000000000000000000000000000000000000000..7ed7c203a8e232be28f087b4dc7e58c7d2746714 --- /dev/null +++ b/src/class/OptionRegistry.js @@ -0,0 +1,46 @@ +import Registry from './Registry'; + +const defaults = {}; + +defaults.defaultInvalidTypeProduct = null; +defaults.defaultRandExpMax = 10; + +defaults.ignoreProperties = []; +defaults.ignoreMissingRefs = false; +defaults.failOnInvalidTypes = true; +defaults.failOnInvalidFormat = true; + +defaults.alwaysFakeOptionals = false; +defaults.optionalsProbability = false; +defaults.fixedProbabilities = false; +defaults.useExamplesValue = false; +defaults.useDefaultValue = false; +defaults.requiredOnly = false; + +defaults.minItems = 0; +defaults.maxItems = null; +defaults.minLength = 0; +defaults.maxLength = null; + +defaults.resolveJsonPath = false; +defaults.reuseProperties = false; +defaults.fillProperties = true; + +defaults.random = Math.random; + +/** + * This class defines a registry for custom settings used within JSF. + */ +class OptionRegistry extends Registry { + constructor() { + super(); + this.data = Object.assign({}, defaults); + this._defaults = defaults; + } + + get defaults() { + return Object.assign({}, this._defaults); + } +} + +export default OptionRegistry; diff --git a/ts/class/Registry.ts b/src/class/Registry.js similarity index 58% rename from ts/class/Registry.ts rename to src/class/Registry.js index 9a3fa6cf30a0e72e89bf3b9f6fe380efec0fbdfa..0eece05a067708c3f2226ea80ae9a4d05c4d6193 100644 --- a/ts/class/Registry.ts +++ b/src/class/Registry.js @@ -1,52 +1,55 @@ -/** - * string => T map object - */ -type DataMap<T> = { - [s: string]: T; -}; - /** * This class defines a registry for custom formats used within JSF. */ -class Registry<T> { - protected data: DataMap<T>; - +class Registry { constructor() { // empty by default this.data = {}; } + /** + * Unregisters custom format(s) + * @param name + */ + unregister(name) { + if (!name) { + this.data = {}; + } else { + delete this.data[name]; + } + } + /** * Registers custom format */ - public register(name: string, callback: T): void { + register(name, callback) { this.data[name] = callback; } /** * Register many formats at one shot */ - public registerMany(formats: Object): void { - for (var name in formats) { + registerMany(formats) { + Object.keys(formats).forEach(name => { this.data[name] = formats[name]; - } + }); } /** * Returns element by registry key */ - public get(name: string): T { - var format: T = this.data[name]; + get(name) { + const format = this.data[name]; + return format; } /** * Returns the whole registry content */ - public list(): DataMap<T> { + list() { return this.data; } - } export default Registry; diff --git a/ts/core/constants.ts b/src/core/constants.js similarity index 100% rename from ts/core/constants.ts rename to src/core/constants.js diff --git a/src/core/error.js b/src/core/error.js new file mode 100644 index 0000000000000000000000000000000000000000..89fecaa2c4bdca4f9f220d335790cd06205373a4 --- /dev/null +++ b/src/core/error.js @@ -0,0 +1,13 @@ +class ParseError extends Error { + constructor(message, path) { + super(); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + this.name = 'ParseError'; + this.message = message; + this.path = path; + } +} + +export default ParseError; diff --git a/ts/core/infer.ts b/src/core/infer.js similarity index 63% rename from ts/core/infer.ts rename to src/core/infer.js index c173f2cd6d34f3963f03f302884d377d98f35e35..313d14daf98b7c67a12fa8a3d0e98ec00539f34f 100644 --- a/ts/core/infer.ts +++ b/src/core/infer.js @@ -1,28 +1,17 @@ -type propertyList = string[]; - -interface propertyTypeMap { - [s: string]: propertyList; - array: propertyList; - integer: propertyList; - number?: propertyList; - object: propertyList; - string: propertyList; -} - -var inferredProperties: propertyTypeMap = { +const inferredProperties = { array: [ 'additionalItems', 'items', 'maxItems', 'minItems', - 'uniqueItems' + 'uniqueItems', ], integer: [ 'exclusiveMaximum', 'exclusiveMinimum', 'maximum', 'minimum', - 'multipleOf' + 'multipleOf', ], object: [ 'additionalProperties', @@ -31,24 +20,24 @@ var inferredProperties: propertyTypeMap = { 'minProperties', 'patternProperties', 'properties', - 'required' + 'required', ], string: [ 'maxLength', 'minLength', - 'pattern' - ] + 'pattern', + ], }; inferredProperties.number = inferredProperties.integer; -var subschemaProperties: propertyList = [ +const subschemaProperties = [ 'additionalItems', 'items', 'additionalProperties', 'dependencies', 'patternProperties', - 'properties' + 'properties', ]; /** @@ -60,13 +49,16 @@ var subschemaProperties: propertyList = [ * * @returns {boolean} */ -function matchesType(obj: Object, lastElementInPath: string, inferredTypeProperties: propertyList): boolean { - return Object.keys(obj).filter(function(prop: string) { - var isSubschema: boolean = subschemaProperties.indexOf(lastElementInPath) > -1, - inferredPropertyFound: boolean = inferredTypeProperties.indexOf(prop) > -1; +function matchesType(obj, lastElementInPath, inferredTypeProperties) { + return Object.keys(obj).filter(prop => { + const isSubschema = subschemaProperties.indexOf(lastElementInPath) > -1; + const inferredPropertyFound = inferredTypeProperties.indexOf(prop) > -1; + if (inferredPropertyFound && !isSubschema) { return true; } + + return false; }).length > 0; } @@ -76,9 +68,13 @@ function matchesType(obj: Object, lastElementInPath: string, inferredTypePropert * * @returns {string|null} */ -function inferType(obj: Object, schemaPath: SchemaPath): string { - for (var typeName in inferredProperties) { - var lastElementInPath: string = schemaPath[schemaPath.length - 1]; +function inferType(obj, schemaPath) { + const keys = Object.keys(inferredProperties); + + for (let i = 0; i < keys.length; i += 1) { + const typeName = keys[i]; + const lastElementInPath = schemaPath[schemaPath.length - 1]; + if (matchesType(obj, lastElementInPath, inferredProperties[typeName])) { return typeName; } diff --git a/ts/core/random.ts b/src/core/random.js similarity index 68% rename from ts/core/random.ts rename to src/core/random.js index 9dd9fb8a6d4e49fd4b542208b72d6a3911aca061..0ea6faffdafdc36e28b277314ed56454e6aadbbe 100644 --- a/ts/core/random.ts +++ b/src/core/random.js @@ -1,19 +1,24 @@ -/// <reference path="../index.d.ts" /> +import RandExp from 'randexp'; import optionAPI from '../api/option'; import env from '../core/constants'; -import RandExp from 'randexp'; +function getRandomInteger(min, max) { + min = typeof min === 'undefined' ? env.MIN_INTEGER : min; + max = typeof max === 'undefined' ? env.MAX_INTEGER : max; + + return Math.floor(optionAPI('random')() * ((max - min) + 1)) + min; +} -function _randexp(value: string) { +function _randexp(value) { // set maximum default, see #193 RandExp.prototype.max = optionAPI('defaultRandExpMax'); // same implementation as the original except using our random RandExp.prototype.randInt = (a, b) => - a + Math.floor(optionAPI('random')() * (1 + b - a)); + a + Math.floor(optionAPI('random')() * (1 + (b - a))); - var re = new RandExp(value); + const re = new RandExp(value); return re.gen(); } @@ -24,7 +29,7 @@ function _randexp(value: string) { * @param collection * @returns {T} */ -function pick<T>(collection: T[]): T { +function pick(collection) { return collection[Math.floor(optionAPI('random')() * collection.length)]; } @@ -34,16 +39,18 @@ function pick<T>(collection: T[]): T { * @param collection * @returns {T[]} */ -function shuffle<T>(collection: T[]): T[] { - var tmp: T, - key: number, - copy: T[] = collection.slice(), - length: number = collection.length; +function shuffle(collection) { + let tmp; + let key; + let length = collection.length; + + const copy = collection.slice(); for (; length > 0;) { key = Math.floor(optionAPI('random')() * length); // swap - tmp = copy[--length]; + length -= 1; + tmp = copy[length]; copy[length] = copy[key]; copy[key] = tmp; } @@ -57,8 +64,8 @@ function shuffle<T>(collection: T[]): T[] { * Using Math.round() will give you a non-uniform distribution! * @see http://stackoverflow.com/a/1527820/769384 */ -function getRandom(min: number, max: number): number { - return optionAPI('random')() * (max - min) + min; +function getRandom(min, max) { + return (optionAPI('random')() * (max - min)) + min; } /** @@ -71,7 +78,7 @@ function getRandom(min: number, max: number): number { * @param hasPrecision * @returns {number} */ -function number(min?: number, max?: number, defMin?: number, defMax?: number, hasPrecision: boolean = false): number { +function number(min, max, defMin, defMax, hasPrecision = false) { defMin = typeof defMin === 'undefined' ? env.MIN_NUMBER : defMin; defMax = typeof defMax === 'undefined' ? env.MAX_NUMBER : defMax; @@ -82,13 +89,11 @@ function number(min?: number, max?: number, defMin?: number, defMax?: number, ha max += min; } - var result: number = getRandom(min, max); - - if (!hasPrecision) { - return Math.round(result); + if (hasPrecision) { + return getRandom(min, max); } - return result; + return getRandomInteger(min, max); } function by(type) { @@ -113,6 +118,8 @@ function by(type) { case 'years': return number(1, 20) * 31104012345; + + default: break; } } @@ -121,8 +128,8 @@ function date(step) { return by(step); } - var now = new Date(); - var days = number(-1000, env.MOST_NEAR_DATETIME); + const now = new Date(); + const days = number(-1000, env.MOST_NEAR_DATETIME); now.setTime(now.getTime() - days); @@ -130,9 +137,9 @@ function date(step) { } export default { - pick: pick, - date: date, + pick, + date, + shuffle, + number, randexp: _randexp, - shuffle: shuffle, - number: number, }; diff --git a/ts/core/run.ts b/src/core/run.js similarity index 72% rename from ts/core/run.ts rename to src/core/run.js index 10ca9fa4660f8b0f39f8733cc567b9dbaa35aa91..46dde8ad29549fcc6943d1b141c6eb201afabaea 100644 --- a/ts/core/run.ts +++ b/src/core/run.js @@ -1,11 +1,10 @@ +import jsonpath from 'jsonpath'; + import optionAPI from '../api/option'; import traverse from './traverse'; import random from './random'; import utils from './utils'; -import deref from 'deref'; -import jsonpath from 'jsonpath'; - function pick(data) { return Array.isArray(data) ? random.pick(data) @@ -63,7 +62,7 @@ function resolve(obj, data, values, property) { if (params.count > 1) { values[key] = jsonpath.query(data, params.path, params.count); } else { - values[key] = jsonpath.value(data, params.path); + values[key] = jsonpath.query(data, params.path); } } @@ -82,9 +81,9 @@ function resolve(obj, data, values, property) { } // TODO provide types -function run(refs: any, schema: JsonSchema, container: Container) { +function run(refs, schema, container) { try { - const result = traverse(schema, [], function reduce(sub, maxReduceDepth) { + const result = traverse(schema, [], function reduce(sub, maxReduceDepth, parentSchemaPath) { if (typeof maxReduceDepth === 'undefined') { maxReduceDepth = random.number(1, 3); } @@ -109,14 +108,22 @@ function run(refs: any, schema: JsonSchema, container: Container) { return sub; } + let ref; + if (sub.$ref.indexOf('#/') === -1) { - var ref = deref.util.findByRef(sub.$ref, refs); + ref = refs[sub.$ref] || null; + } - if (!ref) { - throw new Error('Reference not found: ' + sub.$ref); + if (sub.$ref.indexOf('#/definitions/') === 0) { + ref = schema.definitions[sub.$ref.split('#/definitions/')[1]] || null; + } + + if (typeof ref !== 'undefined') { + if (!ref && optionAPI('ignoreMissingRefs') !== true) { + throw new Error(`Reference not found: ${sub.$ref}`); } - return ref; + utils.merge(sub, ref || {}); } // just remove the reference @@ -125,14 +132,14 @@ function run(refs: any, schema: JsonSchema, container: Container) { } if (Array.isArray(sub.allOf)) { - var schemas: JsonSchema[] = sub.allOf; + const schemas = sub.allOf; delete sub.allOf; // this is the only case where all sub-schemas // must be resolved before any merge - schemas.forEach(function(subSchema: JsonSchema) { - var _sub = reduce(subSchema, maxReduceDepth + 1); + schemas.forEach(subSchema => { + const _sub = reduce(subSchema, maxReduceDepth + 1, parentSchemaPath); // call given thunks if present utils.merge(sub, typeof _sub.thunk === 'function' @@ -142,7 +149,7 @@ function run(refs: any, schema: JsonSchema, container: Container) { } if (Array.isArray(sub.oneOf || sub.anyOf)) { - var mix = sub.oneOf || sub.anyOf; + const mix = sub.oneOf || sub.anyOf; // test every value from the enum against each-oneOf // schema, only values that validate once are kept @@ -150,12 +157,9 @@ function run(refs: any, schema: JsonSchema, container: Container) { sub.enum = sub.enum.filter(x => utils.validate(x, mix)); } - delete sub.anyOf; - delete sub.oneOf; - return { thunk() { - var copy = utils.merge({}, sub); + const copy = utils.omitProps(sub, ['anyOf', 'oneOf']); utils.merge(copy, random.pick(mix)); @@ -164,9 +168,18 @@ function run(refs: any, schema: JsonSchema, container: Container) { }; } - for (var prop in sub) { + Object.keys(sub).forEach(prop => { if ((Array.isArray(sub[prop]) || typeof sub[prop] === 'object') && !utils.isKey(prop)) { - sub[prop] = reduce(sub[prop], maxReduceDepth); + sub[prop] = reduce(sub[prop], maxReduceDepth, parentSchemaPath.concat(prop)); + } + }); + + // avoid extra calls on sub-schemas, fixes #458 + if (parentSchemaPath) { + const lastProp = parentSchemaPath[parentSchemaPath.length - 1]; + + if (lastProp === 'properties' || lastProp === 'items') { + return sub; } } @@ -180,7 +193,7 @@ function run(refs: any, schema: JsonSchema, container: Container) { return result; } catch (e) { if (e.path) { - throw new Error(e.message + ' in ' + '/' + e.path.join('/')); + throw new Error(`${e.message} in /${e.path.join('/')}`); } else { throw e; } diff --git a/ts/core/traverse.ts b/src/core/traverse.js similarity index 67% rename from ts/core/traverse.ts rename to src/core/traverse.js index db002f4c0043a468555aedc8faf253b8fe58aaa5..05ace4ebbca83126144fc423728a61833657132b 100644 --- a/ts/core/traverse.ts +++ b/src/core/traverse.js @@ -6,16 +6,27 @@ import types from '../types/index'; import optionAPI from '../api/option'; // TODO provide types -function traverse(schema: JsonSchema, path: SchemaPath, resolve: Function, rootSchema?: JsonSchema) { - schema = resolve(schema); +function traverse(schema, path, resolve, rootSchema) { + schema = resolve(schema, undefined, path); if (!schema) { return; } // default values has higher precedence - if (optionAPI('useDefaultValue') && 'default' in schema) { - return schema.default; + if (path[path.length - 1] !== 'properties') { + // example values have highest precedence + if (optionAPI('useExamplesValue') && Array.isArray(schema.examples)) { + return utils.typecast(null, schema, () => random.pick(schema.examples)); + } + + if (optionAPI('useDefaultValue') && 'default' in schema) { + return schema.default; + } + + if ('template' in schema) { + return utils.template(schema.template, rootSchema); + } } if (schema.not && typeof schema.not === 'object') { @@ -23,7 +34,7 @@ function traverse(schema: JsonSchema, path: SchemaPath, resolve: Function, rootS } if (Array.isArray(schema.enum)) { - return utils.typecast(schema, () => random.pick(schema.enum)); + return utils.typecast(null, schema, () => random.pick(schema.enum)); } // thunks can return sub-schemas @@ -32,11 +43,11 @@ function traverse(schema: JsonSchema, path: SchemaPath, resolve: Function, rootS } if (typeof schema.generate === 'function') { - return utils.typecast(schema, () => schema.generate(rootSchema)); + return utils.typecast(null, schema, () => schema.generate(rootSchema)); } // TODO remove the ugly overcome - var type: any = schema.type; + let type = schema.type; if (Array.isArray(type)) { type = random.pick(type); @@ -52,7 +63,7 @@ function traverse(schema: JsonSchema, path: SchemaPath, resolve: Function, rootS if (typeof type === 'string') { if (!types[type]) { if (optionAPI('failOnInvalidTypes')) { - throw new ParseError('unknown primitive ' + utils.short(type), path.concat(['type'])); + throw new ParseError(`unknown primitive ${utils.short(type)}`, path.concat(['type'])); } else { return optionAPI('defaultInvalidTypeProduct'); } @@ -74,19 +85,19 @@ function traverse(schema: JsonSchema, path: SchemaPath, resolve: Function, rootS } } - var copy = {}; + let copy = {}; if (Array.isArray(schema)) { copy = []; } - for (var prop in schema) { + Object.keys(schema).forEach(prop => { if (typeof schema[prop] === 'object' && prop !== 'definitions') { copy[prop] = traverse(schema[prop], path.concat([prop]), resolve, copy); } else { copy[prop] = schema[prop]; } - } + }); return copy; } diff --git a/ts/core/utils.ts b/src/core/utils.js similarity index 73% rename from ts/core/utils.ts rename to src/core/utils.js index a160aab4c523b5e65a251244244f48be9685a677..90d2b39df8a8ab0ec4dc4e8c0094782528675dfc 100644 --- a/ts/core/utils.ts +++ b/src/core/utils.js @@ -2,11 +2,11 @@ import optionAPI from '../api/option'; import env from '../core/constants'; import random from './random'; -function getSubAttribute(obj: any, dotSeparatedKey: string): any { - var keyElements: string[] = dotSeparatedKey.split('.'); +function getSubAttribute(obj, dotSeparatedKey) { + const keyElements = dotSeparatedKey.split('.'); while (keyElements.length) { - var prop = keyElements.shift(); + const prop = keyElements.shift(); if (!obj[prop]) { break; @@ -24,8 +24,8 @@ function getSubAttribute(obj: any, dotSeparatedKey: string): any { * @param properties * @returns {boolean} */ -function hasProperties(obj: Object, ...properties: string[]): boolean { - return properties.filter(function(key: string): boolean { +function hasProperties(obj, ...properties) { + return properties.filter(key => { return typeof obj[key] !== 'undefined'; }).length > 0; } @@ -35,15 +35,16 @@ function hasProperties(obj: Object, ...properties: string[]): boolean { * External generators (faker, chance, casual) may return data in non-expected formats, such as string, when you might expect an * integer. This function is used to force the typecast. This is the base formatter for all result values. * + * @param type * @param schema * @param callback * @returns {any} */ -function typecast(schema: JsonSchema, callback: Function): any { +function typecast(type, schema, callback) { const params = {}; // normalize constraints - switch (schema.type) { + switch (type || schema.type) { case 'integer': case 'number': if (typeof schema.minimum !== 'undefined') { @@ -55,8 +56,8 @@ function typecast(schema: JsonSchema, callback: Function): any { } if (schema.enum) { - var min = Math.max(params.minimum || 0, 0); - var max = Math.min(params.maximum || Infinity, Infinity); + let min = Math.max(params.minimum || 0, 0); + let max = Math.min(params.maximum || Infinity, Infinity); if (schema.exclusiveMinimum && min === schema.minimum) { min += schema.multipleOf || 1; @@ -67,17 +68,20 @@ function typecast(schema: JsonSchema, callback: Function): any { } // discard out-of-bounds enumerations - schema.enum = schema.enum.filter(x => { - if (x >= min && x <= max) { - return true; - } - - return false; - }); + if (min || max !== Infinity) { + schema.enum = schema.enum.filter(x => { + if (x >= min && x <= max) { + return true; + } + + return false; + }); + } } + break; - case 'string': + case 'string': { if (typeof schema.minLength !== 'undefined') { params.minLength = schema.minLength; } @@ -98,14 +102,18 @@ function typecast(schema: JsonSchema, callback: Function): any { if (_minLength && params.minLength < _minLength) { params.minLength = _minLength; } + break; + } + + default: break; } // execute generator - var value = callback(params); + let value = callback(params); // normalize output value - switch (schema.type) { + switch (type || schema.type) { case 'number': value = parseFloat(value); break; @@ -118,33 +126,36 @@ function typecast(schema: JsonSchema, callback: Function): any { value = !!value; break; - case 'string': + case 'string': { value = String(value); - var min = Math.max(params.minLength || 0, 0); - var max = Math.min(params.maxLength || Infinity, Infinity); + const min = Math.max(params.minLength || 0, 0); + const max = Math.min(params.maxLength || Infinity, Infinity); while (value.length < min) { - value += ' ' + value; + value += ` ${value}`; } if (value.length > max) { value = value.substr(0, max); } break; + } + + default: break; } return value; } -function merge(a: Object, b: Object): Object { - for (var key in b) { +function merge(a, b) { + Object.keys(b).forEach(key => { if (typeof b[key] !== 'object' || b[key] === null) { a[key] = b[key]; } else if (Array.isArray(b[key])) { a[key] = a[key] || []; // fix #292 - skip duplicated values from merge object (b) - b[key].forEach(function(value) { + b[key].forEach(value => { if (a[key].indexOf(value) === -1) { a[key].push(value); } @@ -154,22 +165,25 @@ function merge(a: Object, b: Object): Object { } else { a[key] = merge(a[key], b[key]); } - } + }); + return a; } function clean(obj, isArray, requiredProps) { if (!obj || typeof obj !== 'object') { - return obj; + return obj; } + if (Array.isArray(obj)) { obj = obj - .map(function (value) { return clean(value, true, requiredProps); }) - .filter(function (value) { return typeof value !== 'undefined'; }); + .map(value => clean(value, true, requiredProps)) + .filter(value => typeof value !== 'undefined'); return obj; } - Object.keys(obj).forEach(function(k) { + + Object.keys(obj).forEach(k => { if (!requiredProps || requiredProps.indexOf(k) === -1) { if (Array.isArray(obj[k]) && !obj[k].length) { delete obj[k]; @@ -178,17 +192,19 @@ function clean(obj, isArray, requiredProps) { obj[k] = clean(obj[k]); } }); + if (!Object.keys(obj).length && isArray) { return undefined; } + return obj; } function short(schema) { - var s = JSON.stringify(schema); - var l = JSON.stringify(schema, null, 2); + const s = JSON.stringify(schema); + const l = JSON.stringify(schema, null, 2); - return s.length > 400 ? l.substr(0, 400) + '...' : l; + return s.length > 400 ? `${l.substr(0, 400)}...` : l; } function anyValue() { @@ -205,10 +221,10 @@ function anyValue() { {}, Math.random(), Math.random().toString(36).substr(2), - ]); + ]); } -function notValue(schema: JsonSchema, parent: Function) { +function notValue(schema, parent) { const copy = merge({}, parent); if (typeof schema.minimum !== 'undefined') { @@ -230,19 +246,23 @@ function notValue(schema: JsonSchema, parent: Function) { } if (schema.type) { - copy.type = random.pick(env.ALL_TYPES.filter(function (x) { + copy.type = random.pick(env.ALL_TYPES.filter(x => { const types = Array.isArray(schema.type) ? schema.type : [schema.type]; + return types.every(type => { // treat both types as _similar enough_ to be skipped equal if (x === 'number' || x === 'integer') { - return type !== 'number' && type !== 'integer'; + return type !== 'number' && type !== 'integer'; } + return x !== type; }); })); } else if (schema.enum) { + let value; + do { - var value = anyValue(); + value = anyValue(); } while (schema.enum.indexOf(value) !== -1); copy.enum = [value]; @@ -269,10 +289,12 @@ function validate(value, schemas) { if (typeof x.maximum !== 'undefined' && value <= x.maximum) { return true; } + + return false; }); } -function isKey(prop: string): boolean { +function isKey(prop) { return prop === 'enum' || prop === 'default' || prop === 'required' || prop === 'definitions'; } @@ -294,16 +316,29 @@ function omitProps(obj, props) { return copy; } +function template(value, schema) { + if (Array.isArray(value)) { + return value.map(x => template(x, schema)); + } + + if (typeof value === 'string') { + value = value.replace(/#\{([\w.-]+)\}/g, (_, $1) => schema[$1]); + } + + return value; +} + export default { - getSubAttribute: getSubAttribute, - hasProperties: hasProperties, - omitProps: omitProps, - typecast: typecast, - merge: merge, - clean: clean, - short: short, - notValue: notValue, - anyValue: anyValue, - validate: validate, - isKey: isKey, + getSubAttribute, + hasProperties, + omitProps, + typecast, + merge, + clean, + short, + notValue, + anyValue, + validate, + isKey, + template, }; diff --git a/ts/generators/boolean.ts b/src/generators/boolean.js similarity index 82% rename from ts/generators/boolean.ts rename to src/generators/boolean.js index fab429db2654b2986c5ce380824a799b4421c112..328dace6792a4976b8b4c1e812576c468b2b91a7 100644 --- a/ts/generators/boolean.ts +++ b/src/generators/boolean.js @@ -5,7 +5,7 @@ import optionAPI from '../api/option'; * * @returns {boolean} */ -function booleanGenerator(): boolean { +function booleanGenerator() { return optionAPI('random')() > 0.5; } diff --git a/ts/generators/coreFormat.ts b/src/generators/coreFormat.js similarity index 83% rename from ts/generators/coreFormat.ts rename to src/generators/coreFormat.js index 2c2b1f5b90e934ef5b79f2c629fb0cf7caaa3544..c49336a713191b70342f19aba5795f7d2b876d68 100644 --- a/ts/generators/coreFormat.ts +++ b/src/generators/coreFormat.js @@ -4,7 +4,7 @@ import random from '../core/random'; * Predefined core formats * @type {[key: string]: string} */ -var regexps: IStringMap = { +const regexps = { email: '[a-zA-Z\\d][a-zA-Z\\d-]{1,13}[a-zA-Z\\d]@{hostname}', hostname: '[a-zA-Z]{1,33}\\.[a-z]{2,4}', ipv6: '[a-f\\d]{4}(:[a-f\\d]{4}){7}', @@ -18,8 +18,8 @@ var regexps: IStringMap = { * @param coreFormat * @returns {string} */ -function coreFormatGenerator(coreFormat: string): string { - return random.randexp(regexps[coreFormat]).replace(/\{(\w+)\}/, function(match: string, key: string) { +function coreFormatGenerator(coreFormat) { + return random.randexp(regexps[coreFormat]).replace(/\{(\w+)\}/, (match, key) => { return random.randexp(regexps[key]); }); } diff --git a/ts/generators/date.ts b/src/generators/date.js similarity index 84% rename from ts/generators/date.ts rename to src/generators/date.js index 2c1830ff9f0c16c6438de14c34ad3051c24edc3a..731756e42e64b181985a26cfd20785de01e39139 100644 --- a/ts/generators/date.ts +++ b/src/generators/date.js @@ -5,7 +5,7 @@ import dateTimeGenerator from './dateTime'; * * @returns {string} */ -function dateGenerator(): string { +function dateGenerator() { return dateTimeGenerator().slice(0, 10); } diff --git a/ts/generators/dateTime.ts b/src/generators/dateTime.js similarity index 83% rename from ts/generators/dateTime.ts rename to src/generators/dateTime.js index edb576b649c361dc50cceff197276164e5cf909b..1690138ee58772d9f200b2c440fc1dbb1a470a45 100644 --- a/ts/generators/dateTime.ts +++ b/src/generators/dateTime.js @@ -5,7 +5,7 @@ import random from '../core/random'; * * @returns {string} */ -function dateTimeGenerator(): string { +function dateTimeGenerator() { return random.date().toISOString(); } diff --git a/ts/generators/ipv4.ts b/src/generators/ipv4.js similarity index 69% rename from ts/generators/ipv4.ts rename to src/generators/ipv4.js index 5401afc61653a475c58ead3e3b53c4c075aa8c4a..b41b57bd25b392a6b83b8cd1e8e9e5681dbd11d3 100644 --- a/ts/generators/ipv4.ts +++ b/src/generators/ipv4.js @@ -5,8 +5,8 @@ import random from '../core/random'; * * @returns {string} */ -function ipv4Generator(): string { - return [0, 0, 0, 0].map(function(): number { +function ipv4Generator() { + return [0, 0, 0, 0].map(() => { return random.number(0, 255); }).join('.'); } diff --git a/ts/generators/null.ts b/src/generators/null.js similarity index 76% rename from ts/generators/null.ts rename to src/generators/null.js index a3c7aa71ba46cfb85bc5f687e1e24c59927507f4..f47a59679155d91f25ca0459a4932f2ae2675b77 100644 --- a/ts/generators/null.ts +++ b/src/generators/null.js @@ -3,7 +3,7 @@ * * @returns {null} */ -function nullGenerator(): any { +function nullGenerator() { return null; } diff --git a/ts/generators/thunk.ts b/src/generators/thunk.js similarity index 57% rename from ts/generators/thunk.ts rename to src/generators/thunk.js index 44f48deb722e6f7b381c209125612a1f7c83111d..99523c8b88a93ba478301d2880defe48d595487a 100644 --- a/ts/generators/thunk.ts +++ b/src/generators/thunk.js @@ -6,8 +6,9 @@ import random from '../core/random'; * * @returns {string} */ -function produce(): string { - var length: number = random.number(1, 5); +function produce() { + const length = random.number(1, 5); + return words(length).join(' '); } @@ -16,19 +17,20 @@ function produce(): string { * * @returns {string} */ -function thunkGenerator(min: number = 0, max: number = 140): string { - var min: number = Math.max(0, min), - max: number = random.number(min, max), - result: string = produce(); +function thunkGenerator(min = 0, max = 140) { + const _min = Math.max(0, min); + const _max = random.number(_min, max); + + let result = produce(); // append until length is reached - while (result.length < min) { + while (result.length < _min) { result += produce(); } // cut if needed - if (result.length > max) { - result = result.substr(0, max); + if (result.length > _max) { + result = result.substr(0, _max); } return result; diff --git a/ts/generators/time.ts b/src/generators/time.js similarity index 84% rename from ts/generators/time.ts rename to src/generators/time.js index bd6e8cc0dcf2a7bd2cee1746160c5df8339d45fa..a1d1dc89d136d78cf038be43e4b8e73fd137713a 100644 --- a/ts/generators/time.ts +++ b/src/generators/time.js @@ -5,7 +5,7 @@ import dateTimeGenerator from './dateTime'; * * @returns {string} */ -function timeGenerator(): string { +function timeGenerator() { return dateTimeGenerator().slice(11); } diff --git a/src/generators/words.js b/src/generators/words.js new file mode 100644 index 0000000000000000000000000000000000000000..eddf6ee77bfbe2b2c6137d538c0a7b98618d890d --- /dev/null +++ b/src/generators/words.js @@ -0,0 +1,21 @@ +import random from '../core/random'; + +const LIPSUM_WORDS = `Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod tempor incididunt ut labore +et dolore magna aliqua Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea +commodo consequat Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla +pariatur Excepteur sint occaecat cupidatat non proident sunt in culpa qui officia deserunt mollit anim id est +laborum`.split(/\W/); + +/** + * Generates randomized array of single lorem ipsum words. + * + * @param length + * @returns {Array.<string>} + */ +function wordsGenerator(length) { + const words = random.shuffle(LIPSUM_WORDS); + + return words.slice(0, length); +} + +export default wordsGenerator; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000000000000000000000000000000000000..7fd07fe6625ee5f9e1e5fcf552e84694f9d14dda --- /dev/null +++ b/src/index.js @@ -0,0 +1,154 @@ +import $RefParser from 'json-schema-ref-parser'; + +import Container from './class/Container'; +import format from './api/format'; +import option from './api/option'; +import env from './core/constants'; +import random from './core/random'; +import utils from './core/utils'; +import run from './core/run'; + +const container = new Container(); + +function setupKeywords() { + // built-in support + container.define('pattern', random.randexp); + + // safe auto-increment values + container.define('autoIncrement', function autoIncrement(value, schema) { + if (!this.offset) { + const min = schema.minimum || 1; + const max = min + env.MAX_NUMBER; + const offset = value.initialOffset || schema.initialOffset; + + this.offset = offset || random.number(min, max); + } + + if (value === true) { + return this.offset++; // eslint-disable-line + } + + return schema; + }); + + // safe-and-sequential dates + container.define('sequentialDate', function sequentialDate(value, schema) { + if (!this.now) { + this.now = random.date(); + } + + if (value) { + schema = this.now.toISOString(); + value = value === true + ? 'days' + : value; + + if (['seconds', 'minutes', 'hours', 'days', 'weeks', 'months', 'years'].indexOf(value) === -1) { + throw new Error(`Unsupported increment by ${utils.short(value)}`); + } + + this.now.setTime(this.now.getTime() + random.date(value)); + } + + return schema; + }); +} + +function getRefs(refs) { + let $refs = {}; + + if (Array.isArray(refs)) { + refs.forEach(schema => { + $refs[schema.id] = schema; + }); + } else { + $refs = refs || {}; + } + + return $refs; +} + +const jsf = (schema, refs, cwd) => { + console.log('[json-schema-faker] calling JsonSchemaFaker() is deprecated, call either .generate() or .resolve()'); + + if (cwd) { + console.log('[json-schema-faker] references are only supported by calling .resolve()'); + } + + return jsf.generate(schema, refs); +}; + +jsf.generate = (schema, refs) => { + const $refs = getRefs(refs); + + return run($refs, schema, container); +}; + +jsf.resolve = (schema, refs, cwd) => { + if (typeof refs === 'string') { + cwd = refs; + refs = {}; + } + + // normalize basedir (browser aware) + cwd = cwd || (typeof process !== 'undefined' ? process.cwd() : ''); + cwd = `${cwd.replace(/\/+$/, '')}/`; + + const $refs = getRefs(refs); + + // identical setup as json-schema-sequelizer + const fixedRefs = { + order: 300, + canRead: true, + read(file, callback) { + try { + callback(null, $refs[file.url] || $refs[file.url.split('/').pop()]); + } catch (e) { + callback(e); + } + }, + }; + + return $RefParser + .dereference(cwd, schema, { + resolve: { + file: { order: 100 }, + http: { order: 200 }, + fixedRefs, + }, + dereference: { + circular: 'ignore', + }, + }).then(sub => run($refs, sub, container)); +}; + +setupKeywords(); + +jsf.format = format; +jsf.option = option; +jsf.random = random; + +// returns itself for chaining +jsf.extend = (name, cb) => { + container.extend(name, cb); + return jsf; +}; + +jsf.define = (name, cb) => { + container.define(name, cb); + return jsf; +}; + +jsf.reset = name => { + container.reset(name); + setupKeywords(); + return jsf; +}; + +jsf.locate = name => { + return container.get(name); +}; + +jsf.version = '0.5.0-rc16'; + +export default jsf; diff --git a/src/types/array.js b/src/types/array.js new file mode 100644 index 0000000000000000000000000000000000000000..6b94baf7191ccbcf4b1d75ad1121c1a1e958e051 --- /dev/null +++ b/src/types/array.js @@ -0,0 +1,106 @@ +import random from '../core/random'; +import utils from '../core/utils'; +import ParseError from '../core/error'; +import optionAPI from '../api/option'; + +// TODO provide types +function unique(path, items, value, sample, resolve, traverseCallback) { + const tmp = []; + const seen = []; + + function walk(obj) { + const json = JSON.stringify(obj); + + if (seen.indexOf(json) === -1) { + seen.push(json); + tmp.push(obj); + } + } + + items.forEach(walk); + + // TODO: find a better solution? + let limit = 100; + + while (tmp.length !== items.length) { + walk(traverseCallback(value.items || sample, path, resolve)); + + if (!limit) { + limit -= 1; + break; + } + } + + return tmp; +} + +// TODO provide types +function arrayType(value, path, resolve, traverseCallback) { + const items = []; + + if (!(value.items || value.additionalItems)) { + if (utils.hasProperties(value, 'minItems', 'maxItems', 'uniqueItems')) { + throw new ParseError(`missing items for ${utils.short(value)}`, path); + } + return items; + } + + if (Array.isArray(value.items)) { + return value.items.map((item, key) => { + const itemSubpath = path.concat(['items', key]); + + return traverseCallback(item, itemSubpath, resolve); + }); + } + + let minItems = value.minItems; + let maxItems = value.maxItems; + + if (optionAPI('minItems')) { + // fix boundaries + minItems = !maxItems + ? optionAPI('minItems') + : Math.min(optionAPI('minItems'), maxItems); + } + + if (optionAPI('maxItems')) { + // Don't allow user to set max items above our maximum + if (maxItems && maxItems > optionAPI('maxItems')) { + maxItems = optionAPI('maxItems'); + } + + // Don't allow user to set min items above our maximum + if (minItems && minItems > optionAPI('maxItems')) { + minItems = maxItems; + } + } + + const optionalsProbability = optionAPI('alwaysFakeOptionals') === true ? 1.0 : optionAPI('optionalsProbability'); + const fixedProbabilities = optionAPI('fixedProbabilities') || false; + + let length = random.number(minItems, maxItems, 1, 5); + + if (optionalsProbability !== false) { + length = fixedProbabilities + ? Math.round(maxItems * optionalsProbability) + : random.number(minItems, maxItems * optionalsProbability); + } + + // TODO below looks bad. Should additionalItems be copied as-is? + const sample = typeof value.additionalItems === 'object' ? value.additionalItems : {}; + + for (let current = items.length; current < length; current += 1) { + const itemSubpath = path.concat(['items', current]); + const element = traverseCallback(value.items || sample, itemSubpath, resolve); + + items.push(element); + } + + if (value.uniqueItems) { + return unique(path.concat(['items']), items, value, sample, resolve, traverseCallback); + } + + return items; +} + +export default arrayType; diff --git a/ts/types/boolean.ts b/src/types/boolean.js similarity index 61% rename from ts/types/boolean.ts rename to src/types/boolean.js index beb9e2bd30e565b93d26244c9b0ce5a3df1e96a6..be7636c90f8fe54358494630ab175a824e94606a 100644 --- a/ts/types/boolean.ts +++ b/src/types/boolean.js @@ -1,5 +1,5 @@ import booleanGenerator from '../generators/boolean'; -var booleanType: FTypeGenerator = booleanGenerator; +const booleanType = booleanGenerator; export default booleanType; diff --git a/ts/types/index.ts b/src/types/index.js similarity index 83% rename from ts/types/index.ts rename to src/types/index.js index dbdd3389573f5beffec25b5622ae320efe046009..6b8b8a0eaf8eb7d825610e0684fb6e68bd362319 100644 --- a/ts/types/index.ts +++ b/src/types/index.js @@ -6,16 +6,14 @@ import _number from './number'; import _object from './object'; import _string from './string'; -var typeMap: { - [type: string]: FTypeGenerator; -} = { +const typeMap = { boolean: _boolean, null: _null, array: _array, integer: _integer, number: _number, object: _object, - string: _string + string: _string, }; export default typeMap; diff --git a/src/types/integer.js b/src/types/integer.js new file mode 100644 index 0000000000000000000000000000000000000000..3857b6ca06714977d6d3fbb01e8fd05078e5bcfd --- /dev/null +++ b/src/types/integer.js @@ -0,0 +1,11 @@ +import number from './number'; + +// The `integer` type is just a wrapper for the `number` type. The `number` type +// returns floating point numbers, and `integer` type truncates the fraction +// part, leaving the result as an integer. + +function integerType(value) { + return number(Object.assign({ multipleOf: 1 }, value)); +} + +export default integerType; diff --git a/ts/types/null.ts b/src/types/null.js similarity index 62% rename from ts/types/null.ts rename to src/types/null.js index b39e5083a7c5dcbdde33494fc14f60fd2a88aa90..9362f837fb5b88674dae34bbf792ec2a24c72c16 100644 --- a/ts/types/null.ts +++ b/src/types/null.js @@ -1,6 +1,6 @@ import nullGenerator from '../generators/null'; -var nullType: FTypeGenerator = nullGenerator; +const nullType = nullGenerator; export default nullType; diff --git a/ts/types/number.ts b/src/types/number.js similarity index 58% rename from ts/types/number.ts rename to src/types/number.js index 92f80027cfc93159783b930dcdd1e0dd490e36fd..ea5e31550878142e6d68e4f56fcba85ceb2aedb8 100644 --- a/ts/types/number.ts +++ b/src/types/number.js @@ -1,10 +1,11 @@ import random from '../core/random'; import env from '../core/constants'; -var numberType: FTypeGenerator = function numberType(value: INumberSchema): number { - var min = typeof value.minimum === 'undefined' ? env.MIN_INTEGER : value.minimum, - max = typeof value.maximum === 'undefined' ? env.MAX_INTEGER : value.maximum, - multipleOf = value.multipleOf; +function numberType(value) { + let min = typeof value.minimum === 'undefined' ? env.MIN_INTEGER : value.minimum; + let max = typeof value.maximum === 'undefined' ? env.MAX_INTEGER : value.maximum; + + const multipleOf = value.multipleOf; if (multipleOf) { max = Math.floor(max / multipleOf) * multipleOf; @@ -25,7 +26,7 @@ var numberType: FTypeGenerator = function numberType(value: INumberSchema): numb if (multipleOf) { if (String(multipleOf).indexOf('.') === -1) { - var base = random.number(Math.floor(min / multipleOf), Math.floor(max / multipleOf)) * multipleOf; + let base = random.number(Math.floor(min / multipleOf), Math.floor(max / multipleOf)) * multipleOf; while (base < min) { base += value.multipleOf; @@ -34,17 +35,23 @@ var numberType: FTypeGenerator = function numberType(value: INumberSchema): numb return base; } - var boundary = (max - min) / multipleOf; + const boundary = (max - min) / multipleOf; + + let num; + let fix; do { - var num = random.number(0, boundary) * multipleOf; - var fix = (num / multipleOf) % 1; + num = random.number(0, boundary) * multipleOf; + fix = (num / multipleOf) % 1; } while (fix !== 0); + + // FIXME: https://github.com/json-schema-faker/json-schema-faker/issues/379 + return num; } return random.number(min, max, undefined, undefined, true); -}; +} export default numberType; diff --git a/src/types/object.js b/src/types/object.js new file mode 100644 index 0000000000000000000000000000000000000000..c71faf5ee7e6402ecc3abef703bfe545083d8651 --- /dev/null +++ b/src/types/object.js @@ -0,0 +1,213 @@ +import random from '../core/random'; +import words from '../generators/words'; +import utils from '../core/utils'; +import optionAPI from '../api/option'; +import ParseError from '../core/error'; + +// fallback generator +const anyType = { type: ['string', 'number', 'integer', 'boolean'] }; + +// TODO provide types +function objectType(value, path, resolve, traverseCallback) { + const props = {}; + + const properties = value.properties || {}; + const patternProperties = value.patternProperties || {}; + const requiredProperties = (value.required || []).slice(); + const allowsAdditional = value.additionalProperties !== false; + + const propertyKeys = Object.keys(properties); + const patternPropertyKeys = Object.keys(patternProperties); + const optionalProperties = propertyKeys.concat(patternPropertyKeys).reduce((_response, _key) => { + if (requiredProperties.indexOf(_key) === -1) _response.push(_key); + return _response; + }, []); + const allProperties = requiredProperties.concat(optionalProperties); + + const additionalProperties = allowsAdditional // eslint-disable-line + ? (value.additionalProperties === true ? anyType : value.additionalProperties) + : null; + + if (!allowsAdditional && + propertyKeys.length === 0 && + patternPropertyKeys.length === 0 && + utils.hasProperties(value, 'minProperties', 'maxProperties', 'dependencies', 'required') + ) { + // just nothing + return {}; + } + + if (optionAPI('requiredOnly') === true) { + requiredProperties.forEach(key => { + if (properties[key]) { + props[key] = properties[key]; + } + }); + + return traverseCallback(props, path.concat(['properties']), resolve); + } + + const optionalsProbability = optionAPI('alwaysFakeOptionals') === true ? 1.0 : optionAPI('optionalsProbability'); + const fixedProbabilities = optionAPI('fixedProbabilities') || false; + const ignoreProperties = optionAPI('ignoreProperties') || []; + + const min = Math.max(value.minProperties || 0, requiredProperties.length); + const max = Math.min(value.maxProperties || allProperties.length, allProperties.length); + + let neededExtras = Math.max(0, min - requiredProperties.length); + + if (allProperties.length === 1 && !requiredProperties.length) { + neededExtras = random.number(neededExtras, allProperties.length + (max - min)); + } + + if (optionalsProbability !== false) { + if (fixedProbabilities === true) { + neededExtras = Math.round((min - requiredProperties.length) + (optionalsProbability * (max - min))); + } else { + neededExtras = random.number(min - requiredProperties.length, optionalsProbability * (max - min)); + } + } + + const extraPropertiesRandomOrder = random.shuffle(optionalProperties).slice(0, neededExtras); + const extraProperties = optionalProperties.filter(_item => { + return extraPropertiesRandomOrder.indexOf(_item) !== -1; + }); + + // properties are read from right-to-left + const _props = requiredProperties.concat(extraProperties).slice(0, max); + + const skipped = []; + const missing = []; + + _props.forEach(key => { + for (let i = 0; i < ignoreProperties.length; i += 1) { + if ((ignoreProperties[i] instanceof RegExp && ignoreProperties[i].test(key)) + || (typeof ignoreProperties[i] === 'string' && ignoreProperties[i] === key) + || (typeof ignoreProperties[i] === 'function' && ignoreProperties[i](properties[key], key))) { + skipped.push(key); + return; + } + } + + // first ones are the required properies + if (properties[key]) { + props[key] = properties[key]; + } else { + let found; + + // then try patternProperties + patternPropertyKeys.forEach(_key => { + if (key.match(new RegExp(_key))) { + found = true; + props[random.randexp(key)] = patternProperties[_key]; + } + }); + + if (!found) { + // try patternProperties again, + const subschema = patternProperties[key] || additionalProperties; + + // FIXME: allow anyType as fallback when no subschema is given? + + if (subschema) { + // otherwise we can use additionalProperties? + props[patternProperties[key] ? random.randexp(key) : key] = subschema; + } else { + missing.push(key); + } + } + } + }); + + const fillProps = optionAPI('fillProperties'); + const reuseProps = optionAPI('reuseProperties'); + + // discard already ignored props if they're not required to be filled... + let current = Object.keys(props).length + (fillProps ? 0 : skipped.length); + + function get() { + let one; + + do { + one = requiredProperties.shift(); + } while (props[one]); + + return one; + } + + while (fillProps) { + if (!(patternPropertyKeys.length || allowsAdditional)) { + break; + } + + if (current >= min) { + break; + } + + if (allowsAdditional) { + if (reuseProps && ((propertyKeys.length - current) > min)) { + let count = 0; + let key; + + do { + count += 1; + + // skip large objects + if (count > 1000) { + break; + } + + key = get() || random.pick(propertyKeys); + } while (typeof props[key] !== 'undefined'); + + if (typeof props[key] === 'undefined') { + props[key] = properties[key]; + current += 1; + } + } else if (patternPropertyKeys.length && !additionalProperties) { + const prop = random.pick(patternPropertyKeys); + const word = random.randexp(prop); + + if (!props[word]) { + props[word] = patternProperties[prop]; + current += 1; + } + } else { + const word = get() || (words(1) + random.randexp('[a-f\\d]{1,3}')); + + if (!props[word]) { + props[word] = additionalProperties || anyType; + current += 1; + } + } + } + + for (let i = 0; current < min && i < patternPropertyKeys.length; i += 1) { + const _key = patternPropertyKeys[i]; + const word = random.randexp(_key); + + if (!props[word]) { + props[word] = patternProperties[_key]; + current += 1; + } + } + } + + if (!allowsAdditional && current < min) { + if (missing.length) { + throw new ParseError(`properties '${ + missing.join(', ') + }' were not found while additionalProperties is false:\n${ + utils.short(value) + }`, path); + } + + throw new ParseError(`properties constraints were too strong to successfully generate a valid object for:\n${ + utils.short(value) + }`, path); + } + + return traverseCallback(props, path.concat(['properties']), resolve); +} + +export default objectType; diff --git a/ts/types/string.ts b/src/types/string.js similarity index 74% rename from ts/types/string.ts rename to src/types/string.js index 68c173b042faaa7685062c9974504d3b048b5e4b..acee26aa7aa0dda2e8758653679619214937deee 100644 --- a/ts/types/string.ts +++ b/src/types/string.js @@ -9,8 +9,8 @@ import format from '../api/format'; import random from '../core/random'; import utils from '../core/utils'; -function generateFormat(value: IStringSchema, invalid: () => string): string { - var callback: Function = format(value.format); +function generateFormat(value, invalid) { + const callback = format(value.format); if (typeof callback === 'function') { return callback(value); @@ -38,22 +38,21 @@ function generateFormat(value: IStringSchema, invalid: () => string): string { default: if (typeof callback === 'undefined') { if (optionAPI('failOnInvalidFormat')) { - throw new Error('unknown registry key ' + utils.short(value.format)); + throw new Error(`unknown registry key ${utils.short(value.format)}`); } else { return invalid(); } } - throw new Error('unsupported format "' + value.format + '"'); + throw new Error(`unsupported format '${value.format}'`); } } -var stringType: FTypeGenerator = function stringType(value: IStringSchema): string { - var output: string; - - output = utils.typecast(value, opts => { +function stringType(value) { + // here we need to force type to fix #467 + const output = utils.typecast('string', value, opts => { if (value.format) { - return generateFormat(value, () => thunk(opts.minLength, opts.maxLength) ); + return generateFormat(value, () => thunk(opts.minLength, opts.maxLength)); } if (value.pattern) { @@ -64,6 +63,6 @@ var stringType: FTypeGenerator = function stringType(value: IStringSchema): stri }); return output; -}; +} export default stringType; diff --git a/spec/schema/core/define/autoIncrement.json b/tests/schema/core/define/autoIncrement.json similarity index 96% rename from spec/schema/core/define/autoIncrement.json rename to tests/schema/core/define/autoIncrement.json index 5fa7de862e5d5841acb00a99c0997cb71c881de0..bec2e658535a208d010d6e702c5f9348884a67ee 100644 --- a/spec/schema/core/define/autoIncrement.json +++ b/tests/schema/core/define/autoIncrement.json @@ -15,6 +15,7 @@ "minItems": 3, "maxItems": 3 }, + "repeat": 1, "equal": [ { "id": 100001 diff --git a/tests/schema/core/extend/chance-extend.js b/tests/schema/core/extend/chance-extend.js new file mode 100644 index 0000000000000000000000000000000000000000..e607c7d77a1d00bd475a37873d682cd186006f4e --- /dev/null +++ b/tests/schema/core/extend/chance-extend.js @@ -0,0 +1,21 @@ +module.exports = { + extend() { + const Chance = require('chance'); + const chance = new Chance(); + + chance.mixin({ + user() { + return { + first: chance.first(), + last: chance.last(), + email: chance.email(), + }; + }, + }); + + return chance; + }, + register(jsf) { + return jsf.extend('chance', this.extend); + }, +}; diff --git a/spec/schema/core/extend/chance.json b/tests/schema/core/extend/chance.json similarity index 100% rename from spec/schema/core/extend/chance.json rename to tests/schema/core/extend/chance.json diff --git a/tests/schema/core/extend/faker-extend.js b/tests/schema/core/extend/faker-extend.js new file mode 100644 index 0000000000000000000000000000000000000000..21c93c9e4a142edf0edebd86a0bcf641ddd03d14 --- /dev/null +++ b/tests/schema/core/extend/faker-extend.js @@ -0,0 +1,20 @@ +module.exports = { + extend() { + const faker = require('faker/locale/de'); + + faker.mixin = (namespace, fnObject) => { + faker[namespace] = fnObject; + }; + + faker.mixin('custom', { + statement(length) { + return `${faker.name.firstName()} has ${faker.finance.amount()} on ${faker.finance.account(length)}.`; + }, + }); + + return faker; + }, + register(jsf) { + return jsf.extend('faker', this.extend); + }, +}; diff --git a/spec/schema/core/extend/faker.json b/tests/schema/core/extend/faker.json similarity index 100% rename from spec/schema/core/extend/faker.json rename to tests/schema/core/extend/faker.json diff --git a/spec/schema/core/formats.json b/tests/schema/core/formats.json similarity index 100% rename from spec/schema/core/formats.json rename to tests/schema/core/formats.json diff --git a/tests/schema/core/formats/semver.js b/tests/schema/core/formats/semver.js new file mode 100644 index 0000000000000000000000000000000000000000..932d896023bedf706603c3a78709f5312149b9f1 --- /dev/null +++ b/tests/schema/core/formats/semver.js @@ -0,0 +1,9 @@ +module.exports = { + register(jsf) { + return jsf.format({ + semver() { + return jsf.random.randexp('\\d\\.\\d\\.[1-9]\\d?'); + }, + }); + }, +}; diff --git a/spec/schema/core/issues/allOf.json b/tests/schema/core/issues/allOf.json similarity index 100% rename from spec/schema/core/issues/allOf.json rename to tests/schema/core/issues/allOf.json diff --git a/spec/schema/core/issues/anonymous.json b/tests/schema/core/issues/anonymous.json similarity index 100% rename from spec/schema/core/issues/anonymous.json rename to tests/schema/core/issues/anonymous.json diff --git a/spec/schema/core/issues/chance-and-faker.json b/tests/schema/core/issues/chance-and-faker.json similarity index 100% rename from spec/schema/core/issues/chance-and-faker.json rename to tests/schema/core/issues/chance-and-faker.json diff --git a/spec/schema/core/issues/chance.json b/tests/schema/core/issues/chance.json similarity index 100% rename from spec/schema/core/issues/chance.json rename to tests/schema/core/issues/chance.json diff --git a/spec/schema/core/issues/cleanup.json b/tests/schema/core/issues/cleanup.json similarity index 100% rename from spec/schema/core/issues/cleanup.json rename to tests/schema/core/issues/cleanup.json diff --git a/spec/schema/core/issues/errors.json b/tests/schema/core/issues/errors.json similarity index 100% rename from spec/schema/core/issues/errors.json rename to tests/schema/core/issues/errors.json diff --git a/spec/schema/core/issues/format.json b/tests/schema/core/issues/format.json similarity index 100% rename from spec/schema/core/issues/format.json rename to tests/schema/core/issues/format.json diff --git a/spec/schema/core/issues/inferred.json b/tests/schema/core/issues/inferred.json similarity index 100% rename from spec/schema/core/issues/inferred.json rename to tests/schema/core/issues/inferred.json diff --git a/spec/schema/core/issues/issue-115.json b/tests/schema/core/issues/issue-115.json similarity index 100% rename from spec/schema/core/issues/issue-115.json rename to tests/schema/core/issues/issue-115.json diff --git a/spec/schema/core/issues/issue-158.json b/tests/schema/core/issues/issue-158.json similarity index 100% rename from spec/schema/core/issues/issue-158.json rename to tests/schema/core/issues/issue-158.json diff --git a/spec/schema/core/issues/issue-171.json b/tests/schema/core/issues/issue-171.json similarity index 100% rename from spec/schema/core/issues/issue-171.json rename to tests/schema/core/issues/issue-171.json diff --git a/spec/schema/core/issues/issue-193.json b/tests/schema/core/issues/issue-193.json similarity index 100% rename from spec/schema/core/issues/issue-193.json rename to tests/schema/core/issues/issue-193.json diff --git a/spec/schema/core/issues/issue-224.json b/tests/schema/core/issues/issue-224.json similarity index 100% rename from spec/schema/core/issues/issue-224.json rename to tests/schema/core/issues/issue-224.json diff --git a/spec/schema/core/issues/issue-238.json b/tests/schema/core/issues/issue-238.json similarity index 100% rename from spec/schema/core/issues/issue-238.json rename to tests/schema/core/issues/issue-238.json diff --git a/spec/schema/core/issues/issue-252.json b/tests/schema/core/issues/issue-252.json similarity index 100% rename from spec/schema/core/issues/issue-252.json rename to tests/schema/core/issues/issue-252.json diff --git a/spec/schema/core/issues/issue-258.json b/tests/schema/core/issues/issue-258.json similarity index 85% rename from spec/schema/core/issues/issue-258.json rename to tests/schema/core/issues/issue-258.json index 7e735221c1811625cdc6de1b26b866ccd0b78e6e..c54c40796ef35dd59ecc1a17b3e25c7444bac263 100644 --- a/spec/schema/core/issues/issue-258.json +++ b/tests/schema/core/issues/issue-258.json @@ -51,15 +51,9 @@ ], "tests": [ { - "description": "should work as expected (sync)", + "description": "should work as expected", "schema": "schemas.0", "valid": true - }, - { - "description": "should work as expected (async)", - "schema": "schemas.0", - "async": true, - "valid": true } ] } diff --git a/spec/schema/core/issues/issue-274.json b/tests/schema/core/issues/issue-274.json similarity index 100% rename from spec/schema/core/issues/issue-274.json rename to tests/schema/core/issues/issue-274.json diff --git a/spec/schema/core/issues/issue-304.json b/tests/schema/core/issues/issue-304.json similarity index 100% rename from spec/schema/core/issues/issue-304.json rename to tests/schema/core/issues/issue-304.json diff --git a/spec/schema/core/issues/issue-329.json b/tests/schema/core/issues/issue-329.json similarity index 99% rename from spec/schema/core/issues/issue-329.json rename to tests/schema/core/issues/issue-329.json index 74702b28750935530906c69e52300c89e23f6886..88e7e444f79cac147471d1e16573200142c5da79 100644 --- a/spec/schema/core/issues/issue-329.json +++ b/tests/schema/core/issues/issue-329.json @@ -191,8 +191,6 @@ }, "copyright": "Copyright 2014-2017 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright" }, - "skip": true, - "async": true, "hasProps": ["Id", "Name"] } ] diff --git a/spec/schema/core/issues/issue-331.json b/tests/schema/core/issues/issue-331.json similarity index 100% rename from spec/schema/core/issues/issue-331.json rename to tests/schema/core/issues/issue-331.json diff --git a/spec/schema/core/issues/issue-336.json b/tests/schema/core/issues/issue-336.json similarity index 100% rename from spec/schema/core/issues/issue-336.json rename to tests/schema/core/issues/issue-336.json diff --git a/spec/schema/core/issues/issue-338.json b/tests/schema/core/issues/issue-338.json similarity index 100% rename from spec/schema/core/issues/issue-338.json rename to tests/schema/core/issues/issue-338.json diff --git a/tests/schema/core/issues/issue-345.json b/tests/schema/core/issues/issue-345.json new file mode 100644 index 0000000000000000000000000000000000000000..0fb5f615236a0f3a5ad6b51b2742d8e57be30f8a --- /dev/null +++ b/tests/schema/core/issues/issue-345.json @@ -0,0 +1,46 @@ +{ + "description": "Invalid JSON generated with cyclic reference", + "tests": [ + { + "description": "it will work as expected", + "schema": { + "type": "object", + "definitions": { + "DefaultCyclic_106": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "me": { + "type": "object", + "$ref": "#/definitions/DefaultCyclic_106" + } + }, + "additionalProperties": false, + "required": [ + "id" + ] + } + }, + "properties": { + "id": { + "type": "integer" + }, + "me": { + "type": "object", + "$ref": "#/definitions/DefaultCyclic_106" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "set": { + "alwaysFakeOptionals": true + }, + "valid": true + } + ] +} diff --git a/tests/schema/core/issues/issue-369.json b/tests/schema/core/issues/issue-369.json new file mode 100644 index 0000000000000000000000000000000000000000..9b9a152bdd19a6811baacfee0025470519f7070c --- /dev/null +++ b/tests/schema/core/issues/issue-369.json @@ -0,0 +1,48 @@ +{ + "description": "Creates unnecessary fields that not described in schema but exists in required section", + "tests": [ + { + "description": "should take missing props from required ones", + "schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Customer", + "description": "Customer", + "type": "object", + "properties": { + "emails": { + "type": "array", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "typeCode": { + "type": "string" + }, + "isPrimary": { + "type": "boolean" + } + }, + "required": [ + "address", + "typeCode", + "isPrimary" + ] + }, + "minItems": 1, + "maxItems": 1 + } + }, + "required": [ + "name", + "birthDate", + "addresses", + "emails" + ] + }, + "count": 4, + "valid": true + } + ] +} diff --git a/tests/schema/core/issues/issue-379.json b/tests/schema/core/issues/issue-379.json new file mode 100644 index 0000000000000000000000000000000000000000..cd3051d9ccbdbcca7d55f446171286efef638707 --- /dev/null +++ b/tests/schema/core/issues/issue-379.json @@ -0,0 +1,26 @@ +{ + "description": "Floating point when using multipleOf", + "tests": [ + { + "description": "should truncate up to given decimals", + "schema": { + "type": "number", + "multipleOf": 0.01 + }, + "seed": 0.25546874766962246, + "equal": 51093749.53, + "valid": true + }, + { + "description": "should truncate up to given decimals (large decimals)", + "schema": { + "type": "number", + "multipleOf": 0.01 + }, + "seed": 0.06559633646612273, + "equal": 13119267.29, + "skip": true, + "valid": true + } + ] +} diff --git a/tests/schema/core/issues/issue-386.json b/tests/schema/core/issues/issue-386.json new file mode 100644 index 0000000000000000000000000000000000000000..65acbe24bff58f1428ad797c5da2d8a923666f4d --- /dev/null +++ b/tests/schema/core/issues/issue-386.json @@ -0,0 +1,70 @@ +[ + { + "description": "OneOf/AnyOf Json Schema Validation not working", + "tests": [ + { + "description": "should eventually generate a valid value", + "schema": { + "title": "timesync", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "sntp": { + "type": "boolean", + "default": false, + "description": "Update the system clock using SNTP." + }, + "timep": { + "type": "boolean", + "default": false, + "description": "Update the system clock using TIMEP." + }, + "timep-or-sntp": { + "type": "boolean", + "default": true, + "description": "Update the system clock using TIMEP or SNTP." + }, + "ntp": { + "type": "boolean", + "default": false, + "description": "Update the system clock using NTP." + }, + "uri": { + "type": "string", + "description": "The URI of the configuration entity", + "readonly": true + } + }, + "oneOf": [ + { + "required": [ + "sntp" + ] + }, + { + "required": [ + "timep" + ] + }, + { + "required": [ + "timep-or-sntp" + ] + }, + { + "required": [ + "ntp" + ] + } + ], + "additionalProperties": false + }, + "set": { + "optionalsProbability": 1 + }, + "skip": true, + "valid": true + } + ] + } +] diff --git a/tests/schema/core/issues/issue-400.json b/tests/schema/core/issues/issue-400.json new file mode 100644 index 0000000000000000000000000000000000000000..e19db7102f01ab8172b96efceb4273c7bccde229 --- /dev/null +++ b/tests/schema/core/issues/issue-400.json @@ -0,0 +1,115 @@ +{ + "description": "jsonPath only returns the first item in data set", + "tests": [ + { + "description": "should randomize values from json-path references", + "schema": { + "type": "object", + "properties": { + "users": { + "type": "array", + "minItems": 5, + "maxItems": 10, + "items": { + "type": "object", + "properties": { + "emplId": { + "type": "integer", + "minimum": 1000000, + "maximum": 1500000 + }, + "empDeptId": { + "type": "integer", + "jsonPath": "$..departments[*].id" + } + }, + "required": [ + "emplId", + "empDeptId" + ] + } + }, + "departments": { + "type": "array", + "minItems": 3, + "maxItems": 5, + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "minimum": 1000, + "maximum": 9999 + }, + "name": { + "type": "string", + "faker": "name.jobArea" + } + }, + "required": [ + "id", + "name" + ] + } + } + }, + "required": [ + "users", + "departments" + ] + }, + "require": "core/extend/faker-extend", + "set": { + "resolveJsonPath": true + }, + "valid": true + }, + { + "description": "should randomize values from json-path references (example 2)", + "schema": { + "type": "object", + "properties": { + "hunches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "faker": "random.uuid" + } + }, + "required": ["id"] + } + }, + "boxes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "faker": "random.uuid" + }, + "hunchIds": { + "type": "array", + "items": { + "type": "string", + "jsonPath": "$..hunches[*].id" + } + } + }, + "required": ["id", "hunchIds"] + } + } + }, + "required": ["hunches", "boxes"] + }, + "require": "core/extend/faker-extend", + "set": { + "resolveJsonPath": true + }, + "valid": true + } + ] +} diff --git a/tests/schema/core/issues/issue-405.json b/tests/schema/core/issues/issue-405.json new file mode 100644 index 0000000000000000000000000000000000000000..630ebe5cab2e82b934c02106593780f01d09925a --- /dev/null +++ b/tests/schema/core/issues/issue-405.json @@ -0,0 +1,16 @@ +{ + "description": "Enum with negative number is not generated", + "tests": [ + { + "description": "should pick anything from enum values", + "schema": { + "type": "integer", + "enum": [ + -4 + ] + }, + "equal": -4, + "valid": true + } + ] +} diff --git a/tests/schema/core/issues/issue-416.json b/tests/schema/core/issues/issue-416.json new file mode 100644 index 0000000000000000000000000000000000000000..9679a47c81151751287227ba6a09cd9293b1b0c9 --- /dev/null +++ b/tests/schema/core/issues/issue-416.json @@ -0,0 +1,28 @@ +{ + "description": "Weird output using patternProperties and minProperties", + "tests": [ + { + "description": "it will generate only given props when additionalProperties is false", + "schema": { + "type": "object", + "patternProperties": { + "[a-z0-9]": { + "type": "object", + "properties": { + "example": { + "type": "string" + } + }, + "required": [ + "example" + ] + } + }, + "minProperties": 10, + "maxProperties": 10 + }, + "count": 10, + "valid": true + } + ] +} diff --git a/tests/schema/core/issues/issue-419.json b/tests/schema/core/issues/issue-419.json new file mode 100644 index 0000000000000000000000000000000000000000..9590fe8863394988169c5d1894ca562ac68d6299 --- /dev/null +++ b/tests/schema/core/issues/issue-419.json @@ -0,0 +1,77 @@ +{ + "description": "Having two properties that have the same value", + "tests": [ + { + "description": "should resolve values from siblings", + "schema": { + "type": "object", + "properties": { + "cards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "default": "osom" + }, + "slug": { + "type": "string", + "template": "#{id}" + } + }, + "required": [ + "id", + "slug" + ] + }, + "minItems": 1, + "maxItems": 1 + } + }, + "required": [ + "cards" + ] + }, + "set": { + "useDefaultValue": true + }, + "equal": { + "cards": [ + { + "id": "osom", + "slug": "osom" + } + ] + }, + "valid": true + }, + { + "description": "should access generated values if they're sorted", + "schema": { + "type": "object", + "properties": { + "z": { + "type": "string", + "faker": "name.findName" + }, + "b": { + "type": "string", + "faker": "name.lastName" + }, + "c": { + "template": "#{b} II" + }, + "y": { + "faker": { + "internet.email": ["#{z}", "#{c}", "#{b}.com"] + } + } + }, + "required": ["z", "b", "c", "y"] + }, + "require": "core/extend/faker-extend", + "valid": true + } + ] +} diff --git a/tests/schema/core/issues/issue-425.json b/tests/schema/core/issues/issue-425.json new file mode 100644 index 0000000000000000000000000000000000000000..ba64ac56fddbdd8449f0c3b65f6c599275aa3983 --- /dev/null +++ b/tests/schema/core/issues/issue-425.json @@ -0,0 +1,46 @@ +{ + "description": "optionalsProbability not recalculated for each record in an array?", + "tests": [ + { + "description": "it will eventually generate phone property", + "schema": { + "type": "object", + "properties": { + "records": { + "type": "array", + "minItems": 5, + "maxItems": 20, + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "faker": "name.findName" + }, + "phone": { + "type": "string", + "faker": "phone.phoneNumber" + }, + "email": { + "type": "string", + "faker": "internet.email" + } + }, + "required": [ + "name", + "email" + ] + } + } + }, + "required": [ + "records" + ] + }, + "set": { + "optionalsProbability": 0.6 + }, + "valid": true + } + ] +} diff --git a/tests/schema/core/issues/issue-427.json b/tests/schema/core/issues/issue-427.json new file mode 100644 index 0000000000000000000000000000000000000000..d75b79078e561ed882769ce583d1a3ec1dcc1558 --- /dev/null +++ b/tests/schema/core/issues/issue-427.json @@ -0,0 +1,100 @@ +{ + "description": "Circular referencies not working?", + "tests": [ + { + "description": "should eventually generate nested values", + "schema": { + "additionalProperties": false, + "type": "object", + "properties": { + "a": { + "type": "object", + "additionalProperties": false, + "properties": { + "prop1": { + "type": "string" + }, + "prop2": { + "type": "number" + } + }, + "required": [ + "prop1" + ] + }, + "b": { + "$ref": "#/definitions/B" + } + }, + "required": [ + "a", + "b" + ], + "definitions": { + "B": { + "type": "object", + "additionalProperties": false, + "properties": { + "b1": { + "type": "number" + }, + "b2": { + "type": "boolean" + }, + "b3": { + "anyOf": [ + { + "$ref": "#/definitions/C" + }, + { + "$ref": "#/definitions/D" + } + ] + } + }, + "required": [ + "b1", + "b2", + "b3" + ] + }, + "C": { + "type": "object", + "additionalProperties": false, + "properties": { + "c1": { + "type": "string" + }, + "c2": { + "type": "boolean" + } + }, + "required": [ + "c1" + ] + }, + "D": { + "type": "object", + "additionalProperties": false, + "properties": { + "d1": { + "type": "string" + }, + "d2": { + "$ref": "#/definitions/B" + } + }, + "required": [ + "d1", + "d2" + ] + } + } + }, + "set": { + "optionalsProbability": 0.6 + }, + "valid": true + } + ] +} diff --git a/tests/schema/core/issues/issue-442.json b/tests/schema/core/issues/issue-442.json new file mode 100644 index 0000000000000000000000000000000000000000..3b8666b521e9056ed683e475817227046990f86d --- /dev/null +++ b/tests/schema/core/issues/issue-442.json @@ -0,0 +1,30 @@ +{ + "description": "useDefaultValue should use default, not properties.default", + "tests": [ + { + "description": "Setting useDefaultValue should not pick from properties.default", + "schema": { + "type": "object", + "required": ["default", "other"], + "properties": { + "default": { + "type": "boolean", + "default": false + }, + "other": { + "type": "string", + "enum": ["value"] + } + } + }, + "set": { + "useDefaultValue": true + }, + "equal": { + "default": false, + "other": "value" + }, + "valid": true + } + ] +} diff --git a/tests/schema/core/issues/issue-443.json b/tests/schema/core/issues/issue-443.json new file mode 100644 index 0000000000000000000000000000000000000000..c1fb0bd259a86e13e05ccb1198cc61570218bd4f --- /dev/null +++ b/tests/schema/core/issues/issue-443.json @@ -0,0 +1,441 @@ +[ + { + "description": "OptionalsProbability not working as expected #443", + "schemas": [ + { + "title": "name", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "name": { + "type": "object", + "required": [ + "name_value" + ], + "properties": { + "name_value": { + "type": "string", + "pattern": "^(\"){1}[a-zA-Z0-9]{1,}(\"){1}$" + }, + "name_reference": { + "$ref": "#/definitions/name_ref_1" + } + } + }, + "uri": { + "type": "string", + "description": "The URI of the configuration entity", + "readonly": true + } + }, + "required": [ + "name" + ], + "definitions": { + "portal-profile_ref_3": { + "type": "object", + "required": [ + "profile" + ], + "properties": { + "profile": { + "type": "string", + "pattern": "^(\"){1}[a-zA-Z0-9]{1,}(\"){1}$" + } + } + }, + "policy_ref_4": { + "type": "object", + "required": [ + "policy" + ], + "properties": { + "policy": { + "type": "string" + } + } + }, + "reauth-period_ref_5": { + "type": "object", + "required": [ + "reauth-period" + ], + "properties": { + "reauth-period": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 999999999 + } + } + }, + "user-id_ref_6": { + "type": "object", + "required": [ + "user-id" + ], + "properties": { + "user-id": { + "type": "integer", + "minimum": 1, + "maximum": 4094 + } + } + }, + "user-name_ref_7": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "string" + } + } + }, + "user-id-tagged_ref_8": { + "type": "object", + "required": [ + "user-id-tagged" + ], + "properties": { + "user-id-tagged": { + "type": "integer", + "minimum": 1, + "maximum": 4094 + } + } + }, + "user-name-tagged_ref_9": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "string" + } + } + }, + "secondary-role_ref_11": { + "type": "object", + "required": [ + "role" + ], + "properties": { + "role": { + "type": "string", + "pattern": "^(\"){1}[a-zA-Z0-9]{1,}(\"){1}$" + } + } + }, + "server-redirect_ref_10": { + "type": "object", + "anyOf": [ + { + "required": [ + "secondary-role" + ] + }, + { + "required": [ + "vsa" + ] + } + ], + "properties": { + "secondary-role": { + "$ref": "#/definitions/secondary-role_ref_11" + }, + "vsa": { + "type": "boolean" + } + } + }, + "name_ref_1": { + "type": "object", + "properties": { + "portal-profile": { + "type": "object", + "properties": { + "cmd_no_form": { + "type": "boolean", + "enum": [ + false + ] + }, + "portal-profile_reference": { + "$ref": "#/definitions/portal-profile_ref_3" + } + }, + "oneOf": [ + { + "required": [ + "portal-profile_reference" + ] + }, + { + "required": [ + "cmd_no_form" + ] + } + ] + }, + "user-id-tagged": { + "type": "object", + "properties": { + "cmd_no_form": { + "type": "boolean", + "enum": [ + false + ] + }, + "user-id-tagged_reference": { + "$ref": "#/definitions/user-id-tagged_ref_8" + } + }, + "oneOf": [ + { + "required": [ + "user-id-tagged_reference" + ] + }, + { + "required": [ + "cmd_no_form" + ] + } + ] + }, + "server-redirect": { + "type": "object", + "properties": { + "cmd_no_form": { + "type": "boolean", + "enum": [ + false + ] + }, + "server-redirect_reference": { + "$ref": "#/definitions/server-redirect_ref_10" + } + }, + "anyOf": [ + { + "required": [ + "server-redirect_reference" + ], + "not": { + "required": [ + "cmd_no_form" + ] + } + }, + { + "required": [ + "cmd_no_form" + ], + "not": { + "required": [ + "server-redirect_reference" + ] + } + } + ] + }, + "policy": { + "type": "object", + "properties": { + "cmd_no_form": { + "type": "boolean", + "enum": [ + false + ] + }, + "policy_reference": { + "$ref": "#/definitions/policy_ref_4" + } + }, + "oneOf": [ + { + "required": [ + "policy_reference" + ] + }, + { + "required": [ + "cmd_no_form" + ] + } + ] + }, + "reauth-period": { + "$ref": "#/definitions/reauth-period_ref_5" + }, + "user-id": { + "type": "object", + "properties": { + "cmd_no_form": { + "type": "boolean", + "enum": [ + false + ] + }, + "user-id_reference": { + "$ref": "#/definitions/user-id_ref_6" + } + }, + "oneOf": [ + { + "required": [ + "user-id_reference" + ] + }, + { + "required": [ + "cmd_no_form" + ] + } + ] + }, + "user-name-tagged": { + "type": "object", + "properties": { + "cmd_no_form": { + "type": "boolean", + "enum": [ + false + ] + }, + "user-name-tagged_reference": { + "$ref": "#/definitions/user-name-tagged_ref_9" + } + }, + "oneOf": [ + { + "required": [ + "user-name-tagged_reference" + ] + }, + { + "required": [ + "cmd_no_form" + ] + } + ] + }, + "user-name": { + "type": "object", + "properties": { + "cmd_no_form": { + "type": "boolean", + "enum": [ + false + ] + }, + "user-name_reference": { + "$ref": "#/definitions/user-name_ref_7" + } + }, + "oneOf": [ + { + "required": [ + "user-name_reference" + ] + }, + { + "required": [ + "cmd_no_form" + ] + } + ] + } + }, + "anyOf": [ + { + "required": [ + "portal-profile" + ], + "not": { + "required": [ + "server-redirect" + ] + } + }, + { + "required": [ + "policy" + ] + }, + { + "required": [ + "reauth-period" + ] + }, + { + "required": [ + "user-id" + ], + "not": { + "required": [ + "user-name" + ] + } + }, + { + "required": [ + "user-name" + ], + "not": { + "required": [ + "user-id" + ] + } + }, + { + "required": [ + "user-id-tagged" + ], + "not": { + "required": [ + "user-name-tagged" + ] + } + }, + { + "required": [ + "user-name-tagged" + ], + "not": { + "required": [ + "user-id-tagged" + ] + } + }, + { + "required": [ + "server-redirect" + ], + "not": { + "required": [ + "portal-profile" + ] + } + } + ] + } + } + } + ], + "tests": [ + { + "description": "it will eventually generate more values", + "schema": "schemas.0", + "set": { + "optionalsProbability": 0.1 + }, + "valid": true + } + ] + } +] diff --git a/tests/schema/core/issues/issue-446.json b/tests/schema/core/issues/issue-446.json new file mode 100644 index 0000000000000000000000000000000000000000..953e94ce4f4d80decc2f420c56c38f435b34ddd6 --- /dev/null +++ b/tests/schema/core/issues/issue-446.json @@ -0,0 +1,41 @@ +[ + { + "description": "oneOf/anyOf/allOf is deleted after one item generation", + "schemas": [ + { + "definitions": { + "Element": { + "anyOf": [ + { + "enum": [ + "123" + ] + }, + { + "enum": [ + "456" + ] + } + ] + } + }, + "items": [ + { + "$ref": "#/definitions/Element" + }, + { + "$ref": "#/definitions/Element" + } + ] + } + ], + "tests": [ + { + "description": "it will always generate two items", + "schema": "schemas.0", + "length": 2, + "valid": true + } + ] + } +] diff --git a/tests/schema/core/issues/issue-453.json b/tests/schema/core/issues/issue-453.json new file mode 100644 index 0000000000000000000000000000000000000000..f75335b1f715a29dbbee43945ba2b8dce7df9686 --- /dev/null +++ b/tests/schema/core/issues/issue-453.json @@ -0,0 +1,26 @@ +[ + { + "description": "Example using $ref not working", + "schemas": [ + { + "id": "otherSchema", + "type": "string" + } + ], + "tests": [ + { + "description": "it will eventually generate a value", + "schema": { + "type": "object", + "properties": { + "someValue": { + "$ref": "otherSchema" + } + } + }, + "refs": ["schemas.0"], + "valid": true + } + ] + } +] diff --git a/tests/schema/core/issues/issue-455.json b/tests/schema/core/issues/issue-455.json new file mode 100644 index 0000000000000000000000000000000000000000..34290c6f1a717a19c4230542226c1ea4eef92e1b --- /dev/null +++ b/tests/schema/core/issues/issue-455.json @@ -0,0 +1,23 @@ +[ + { + "description": "use faker or chance as object properties", + "tests": [ + { + "description": "should allow both keywords", + "schema": { + "type": "object", + "properties": { + "faker": { + "type": "string", + "format": "email" + } + }, + "required": [ + "faker" + ] + }, + "valid": true + } + ] + } +] diff --git a/tests/schema/core/issues/issue-456.json b/tests/schema/core/issues/issue-456.json new file mode 100644 index 0000000000000000000000000000000000000000..1f341c79fb42c71a7d83fc12cd53d58fd9d9c6ce --- /dev/null +++ b/tests/schema/core/issues/issue-456.json @@ -0,0 +1,37 @@ +[ + { + "description": "Optional maxItems, minItems and maxLength doesn't work", + "tests": [ + { + "description": "minItems/maxItems will override schema", + "schema": { + "type": "array", + "minItems": 100, + "maxItems": 200, + "items": { + "type": "integer" + } + }, + "set": { + "minItems": 1, + "maxItems": 1 + }, + "length": 1 + }, + { + "description": "setting maxLength should truncate schema, not override", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "faker": "name.findName" + }, + "require": "core/extend/faker-extend", + "set": { + "maxLength": 20 + }, + "length": 1 + } + ] + } +] diff --git a/tests/schema/core/issues/issue-458.json b/tests/schema/core/issues/issue-458.json new file mode 100644 index 0000000000000000000000000000000000000000..f36c79ee323cd299919a84737b37ae9adfb313a0 --- /dev/null +++ b/tests/schema/core/issues/issue-458.json @@ -0,0 +1,22 @@ +[ + { + "description": "when defining object with property called 'pattern'", + "tests": [ + { + "description": "should fake all properties as expected", + "schema": { + "type": "object", + "properties": { + "pattern": { + "type": "string" + } + }, + "required": [ + "pattern" + ] + }, + "valid": true + } + ] + } +] diff --git a/tests/schema/core/issues/issue-467.json b/tests/schema/core/issues/issue-467.json new file mode 100644 index 0000000000000000000000000000000000000000..21be4bf4193c474145a4fbb67cf45bb7a9895ad2 --- /dev/null +++ b/tests/schema/core/issues/issue-467.json @@ -0,0 +1,32 @@ +{ + "description": "String maxLength is ignored after 0.5.0-rc12", + "tests": [ + { + "description": "generated strings must not exceed 10 chars", + "schema": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "maxLength": 10 + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "user" + ] + }, + "valid": true + } + ] +} diff --git a/spec/schema/core/issues/issue-57.json b/tests/schema/core/issues/issue-57.json similarity index 100% rename from spec/schema/core/issues/issue-57.json rename to tests/schema/core/issues/issue-57.json diff --git a/spec/schema/core/issues/number-min-greater-than-max.json b/tests/schema/core/issues/number-min-greater-than-max.json similarity index 100% rename from spec/schema/core/issues/number-min-greater-than-max.json rename to tests/schema/core/issues/number-min-greater-than-max.json diff --git a/spec/schema/core/issues/number-min-max.json b/tests/schema/core/issues/number-min-max.json similarity index 100% rename from spec/schema/core/issues/number-min-max.json rename to tests/schema/core/issues/number-min-max.json diff --git a/spec/schema/core/issues/reserved.json b/tests/schema/core/issues/reserved.json similarity index 100% rename from spec/schema/core/issues/reserved.json rename to tests/schema/core/issues/reserved.json diff --git a/spec/schema/core/jsonpath.json b/tests/schema/core/jsonpath.json similarity index 100% rename from spec/schema/core/jsonpath.json rename to tests/schema/core/jsonpath.json diff --git a/spec/schema/core/keywords.json b/tests/schema/core/keywords.json similarity index 100% rename from spec/schema/core/keywords.json rename to tests/schema/core/keywords.json diff --git a/tests/schema/core/option/alwaysFakeOptionals.js b/tests/schema/core/option/alwaysFakeOptionals.js new file mode 100644 index 0000000000000000000000000000000000000000..258db1219526b5a72807079cde76c3d40501978f --- /dev/null +++ b/tests/schema/core/option/alwaysFakeOptionals.js @@ -0,0 +1,9 @@ +module.exports = { + register(jsf) { + return jsf.option({ + useDefaultValue: true, + fixedProbabilities: true, + alwaysFakeOptionals: true, + }); + }, +}; diff --git a/spec/schema/core/option/alwaysFakeOptionals.json b/tests/schema/core/option/alwaysFakeOptionals.json similarity index 100% rename from spec/schema/core/option/alwaysFakeOptionals.json rename to tests/schema/core/option/alwaysFakeOptionals.json diff --git a/tests/schema/core/option/enable-jsonpath.js b/tests/schema/core/option/enable-jsonpath.js new file mode 100644 index 0000000000000000000000000000000000000000..228dcfdb5163b44db8a163427e80ad5e521257e0 --- /dev/null +++ b/tests/schema/core/option/enable-jsonpath.js @@ -0,0 +1,7 @@ +module.exports = { + register(jsf) { + return jsf.option({ + resolveJsonPath: true, + }); + }, +}; diff --git a/tests/schema/core/option/failOnInvalidType.js b/tests/schema/core/option/failOnInvalidType.js new file mode 100644 index 0000000000000000000000000000000000000000..c1d45d76a9da596c9b927f6aec891540a830f31d --- /dev/null +++ b/tests/schema/core/option/failOnInvalidType.js @@ -0,0 +1,8 @@ +module.exports = { + register(jsf) { + return jsf.option({ + failOnInvalidTypes: false, + }); + }, +}; + diff --git a/spec/schema/core/option/failOnInvalidType.json b/tests/schema/core/option/failOnInvalidType.json similarity index 100% rename from spec/schema/core/option/failOnInvalidType.json rename to tests/schema/core/option/failOnInvalidType.json diff --git a/spec/schema/core/option/falsy-defaults.json b/tests/schema/core/option/falsy-defaults.json similarity index 100% rename from spec/schema/core/option/falsy-defaults.json rename to tests/schema/core/option/falsy-defaults.json diff --git a/tests/schema/core/option/ignoreProperties.js b/tests/schema/core/option/ignoreProperties.js new file mode 100644 index 0000000000000000000000000000000000000000..86c38d561a2636a5169adea1b703243bab804140 --- /dev/null +++ b/tests/schema/core/option/ignoreProperties.js @@ -0,0 +1,8 @@ +module.exports = { + register(jsf) { + return jsf.option({ + fillProperties: false, + ignoreProperties: ['foo', /^b/, x => x.default === 42], + }); + }, +}; diff --git a/spec/schema/core/option/ignoreProperties.json b/tests/schema/core/option/ignoreProperties.json similarity index 100% rename from spec/schema/core/option/ignoreProperties.json rename to tests/schema/core/option/ignoreProperties.json diff --git a/spec/schema/core/option/optionalsProbability.json b/tests/schema/core/option/optionalsProbability.json similarity index 94% rename from spec/schema/core/option/optionalsProbability.json rename to tests/schema/core/option/optionalsProbability.json index ab6753f3720d352a3e574af61bce6064b1e86db9..3a513b4384aa5ee998ead9d83c49b77d319794ad 100644 --- a/spec/schema/core/option/optionalsProbability.json +++ b/tests/schema/core/option/optionalsProbability.json @@ -83,6 +83,7 @@ "count": 0, "set": { "useDefaultValue": true, + "fixedProbabilities": true, "optionalsProbability": 0.0 } }, @@ -93,6 +94,7 @@ "count": 1, "set": { "useDefaultValue": true, + "fixedProbabilities": true, "optionalsProbability": 0.2 } }, @@ -103,6 +105,7 @@ "count": 2, "set": { "useDefaultValue": true, + "fixedProbabilities": true, "optionalsProbability": 0.4 } }, @@ -113,6 +116,7 @@ "count": 3, "set": { "useDefaultValue": true, + "fixedProbabilities": true, "optionalsProbability": 0.6 } }, @@ -123,6 +127,7 @@ "count": 4, "set": { "useDefaultValue": true, + "fixedProbabilities": true, "optionalsProbability": 0.8 } }, @@ -133,6 +138,7 @@ "count": 5, "set": { "useDefaultValue": true, + "fixedProbabilities": true, "optionalsProbability": 1.0 } } diff --git a/tests/schema/core/option/optionalsProbabilityEquals1.js b/tests/schema/core/option/optionalsProbabilityEquals1.js new file mode 100644 index 0000000000000000000000000000000000000000..81fc7484da7931fd50c05544636ca8716903610e --- /dev/null +++ b/tests/schema/core/option/optionalsProbabilityEquals1.js @@ -0,0 +1,9 @@ +module.exports = { + register(jsf) { + return jsf.option({ + useDefaultValue: true, + fixedProbabilities: true, + optionalsProbability: 1.0, + }); + }, +}; diff --git a/tests/schema/core/option/optionalsProbabilityOverwritten.js b/tests/schema/core/option/optionalsProbabilityOverwritten.js new file mode 100644 index 0000000000000000000000000000000000000000..e7d1f3ffa21e785df8606a3f970aef23432f51f6 --- /dev/null +++ b/tests/schema/core/option/optionalsProbabilityOverwritten.js @@ -0,0 +1,10 @@ +module.exports = { + register(jsf) { + return jsf.option({ + useDefaultValue: true, + fixedProbabilities: true, + alwaysFakeOptionals: true, + optionalsProbability: 0.0, + }); + }, +}; diff --git a/tests/schema/core/option/random.js b/tests/schema/core/option/random.js new file mode 100644 index 0000000000000000000000000000000000000000..51de6ffedfe1322b38584d1241cd39320b253fc2 --- /dev/null +++ b/tests/schema/core/option/random.js @@ -0,0 +1,9 @@ +const seedrandom = require('seedrandom'); + +module.exports = { + register(jsf) { + return jsf.option({ + random: seedrandom('some seed'), + }); + }, +}; diff --git a/spec/schema/core/option/random.json b/tests/schema/core/option/random.json similarity index 97% rename from spec/schema/core/option/random.json rename to tests/schema/core/option/random.json index 2af07c2e4593660219b9b579b003f200bd8e632d..cce07cdfae7cfbb782c7e0b704b1dcac312a18ec 100644 --- a/spec/schema/core/option/random.json +++ b/tests/schema/core/option/random.json @@ -23,7 +23,7 @@ }, "valid": true, "equal": { - "a": 92, + "a": 93, "b": "enim veniam", "c": "ff353" }, diff --git a/tests/schema/core/option/useDefaultValue.js b/tests/schema/core/option/useDefaultValue.js new file mode 100644 index 0000000000000000000000000000000000000000..9150c32ca6d1a4032f331e54586f08412eab7b8d --- /dev/null +++ b/tests/schema/core/option/useDefaultValue.js @@ -0,0 +1,8 @@ +module.exports = { + register(jsf) { + return jsf.option({ + useDefaultValue: true, + }); + }, +}; + diff --git a/spec/schema/core/option/useDefaultValue.json b/tests/schema/core/option/useDefaultValue.json similarity index 100% rename from spec/schema/core/option/useDefaultValue.json rename to tests/schema/core/option/useDefaultValue.json diff --git a/tests/schema/core/option/useExamplesValue.js b/tests/schema/core/option/useExamplesValue.js new file mode 100644 index 0000000000000000000000000000000000000000..52964df691f8b89bd190427efbeb15295b2e17c0 --- /dev/null +++ b/tests/schema/core/option/useExamplesValue.js @@ -0,0 +1,8 @@ +module.exports = { + register(jsf) { + return jsf.option({ + useExamplesValue: true, + }); + }, +}; + diff --git a/tests/schema/core/option/useExamplesValue.json b/tests/schema/core/option/useExamplesValue.json new file mode 100644 index 0000000000000000000000000000000000000000..24debe563e2d28be1d1a3ae998b8b3c9e227cee8 --- /dev/null +++ b/tests/schema/core/option/useExamplesValue.json @@ -0,0 +1,18 @@ +[ + { + "description": "useExamplesValue option", + "tests": [ + { + "description": "should handle useExamplesValue option", + "schema": { + "type": "string", + "examples": [ + "World" + ] + }, + "equal": "World", + "require": "core/option/useExamplesValue" + } + ] + } +] diff --git a/spec/schema/core/primitives.json b/tests/schema/core/primitives.json similarity index 100% rename from spec/schema/core/primitives.json rename to tests/schema/core/primitives.json diff --git a/spec/schema/core/references.json b/tests/schema/core/references.json similarity index 100% rename from spec/schema/core/references.json rename to tests/schema/core/references.json diff --git a/spec/schema/core/refs/sync.json b/tests/schema/core/refs/sync.json similarity index 85% rename from spec/schema/core/refs/sync.json rename to tests/schema/core/refs/sync.json index 9985d10d16acec9595396cc0e8b05ea8962315f0..42b8596430aaf4c13df933e2f7426b94a51c1d7e 100644 --- a/spec/schema/core/refs/sync.json +++ b/tests/schema/core/refs/sync.json @@ -1,9 +1,9 @@ [ { - "description": "should work on sync/async mode", + "description": "should work on async mode (by default)", "tests": [ { - "description": "should resolve all references in async mode", + "description": "should resolve all references", "schema": { "type": "object", "required": ["value", "other"], @@ -24,11 +24,10 @@ } } }, - "async": true, "valid": true }, { - "description": "should resolve only local references on sync mode", + "description": "should resolve only local references", "schema": { "type": "object", "properties": { diff --git a/spec/schema/core/typecast/chance/bool.json b/tests/schema/core/typecast/chance/bool.json similarity index 100% rename from spec/schema/core/typecast/chance/bool.json rename to tests/schema/core/typecast/chance/bool.json diff --git a/spec/schema/core/typecast/chance/year.json b/tests/schema/core/typecast/chance/year.json similarity index 100% rename from spec/schema/core/typecast/chance/year.json rename to tests/schema/core/typecast/chance/year.json diff --git a/spec/schema/core/typecast/faker/address-latitude-longitude.json b/tests/schema/core/typecast/faker/address-latitude-longitude.json similarity index 100% rename from spec/schema/core/typecast/faker/address-latitude-longitude.json rename to tests/schema/core/typecast/faker/address-latitude-longitude.json diff --git a/spec/schema/core/typecast/faker/finance-amount.json b/tests/schema/core/typecast/faker/finance-amount.json similarity index 100% rename from spec/schema/core/typecast/faker/finance-amount.json rename to tests/schema/core/typecast/faker/finance-amount.json diff --git a/spec/schema/core/types/array.json b/tests/schema/core/types/array.json similarity index 100% rename from spec/schema/core/types/array.json rename to tests/schema/core/types/array.json diff --git a/spec/schema/core/types/integer.json b/tests/schema/core/types/integer.json similarity index 100% rename from spec/schema/core/types/integer.json rename to tests/schema/core/types/integer.json diff --git a/spec/schema/core/types/not.json b/tests/schema/core/types/not.json similarity index 100% rename from spec/schema/core/types/not.json rename to tests/schema/core/types/not.json diff --git a/spec/schema/core/types/object.json b/tests/schema/core/types/object.json similarity index 100% rename from spec/schema/core/types/object.json rename to tests/schema/core/types/object.json diff --git a/spec/schema/core/types/string.json b/tests/schema/core/types/string.json similarity index 100% rename from spec/schema/core/types/string.json rename to tests/schema/core/types/string.json diff --git a/tests/schema/helpers.js b/tests/schema/helpers.js new file mode 100644 index 0000000000000000000000000000000000000000..137c0164cc4ba726cb61770c4e3d6b88a8eba72c --- /dev/null +++ b/tests/schema/helpers.js @@ -0,0 +1,119 @@ +import fs from 'fs'; +import glob from 'glob'; +import { expect } from 'chai'; +import _jsf from '../../src'; +import { checkType, checkSchema } from './validator'; + +export const jsf = _jsf; + +export function pick(obj, key) { + const parts = key.split('.'); + + let out = obj; + + while (parts.length) { + out = out[parts.shift()]; + } + + return out; +} + +export function getTests(srcDir) { + const only = []; + const all = []; + + glob.sync(`${srcDir}/**/*.json`).forEach(file => { + let suite; + + try { + suite = JSON.parse(fs.readFileSync(file)); + } catch (e) { + console.log(`Invalid JSON: ${file}`); + console.log(e.message); + process.exit(1); + } + + (Array.isArray(suite) ? suite : [suite]).forEach(x => { + if (x.xdescription) return; + + let _only = false; + + suite = Object.assign({ file }, x); + + suite.tests = suite.tests.sort((a, b) => { + if (a.only) return -1; + if (b.only) return 1; + return 0; + }).filter(y => { + if ((_only && !y.only) || y.xdescription) return false; + if (y.only) _only = true; + return true; + }); + + if (x.only || _only) only.push(suite); + + all.push(suite); + }); + }); + + return { only, all }; +} + +export function tryTest(test, refs, schema) { + return _jsf.resolve(schema, refs).then(sample => { + if (test.dump) { + console.log(JSON.stringify(sample, null, 2)); + return; + } + + if (test.type) { + checkType(sample, test.type); + } + + if (test.valid) { + checkSchema(sample, schema, refs); + } + + if (test.length) { + expect(sample.length).to.eql(test.length); + } + + if (test.hasProps) { + test.hasProps.forEach(prop => { + if (Array.isArray(sample)) { + sample.forEach(s => { + expect(s[prop]).not.to.eql(undefined); + }); + } else { + expect(sample[prop]).not.to.eql(undefined); + } + }); + } + + if (test.onlyProps) { + expect(Object.keys(sample)).to.eql(test.onlyProps); + } + + if (test.count) { + expect((Array.isArray(sample) ? sample : Object.keys(sample)).length).to.eql(test.count); + } + + if (test.hasNot) { + expect(JSON.stringify(sample)).not.to.contain(test.hasNot); + } + + if ('equal' in test) { + expect(sample).to.eql(test.equal); + } + }).catch(error => { + if (typeof test.throws === 'string') { + expect(error).to.match(new RegExp(test.throws, 'im')); + } + + if (typeof test.throws === 'boolean') { + if (test.throws !== true) { + throw error; + } + } + }); +} diff --git a/tests/schema/main.spec.js b/tests/schema/main.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..c959318019ce102d99447ba2494d39068954ccc9 --- /dev/null +++ b/tests/schema/main.spec.js @@ -0,0 +1,55 @@ +import { jsf, pick, tryTest, getTests } from './helpers'; + +const { only, all } = getTests(__dirname); + +/* global describe, it */ + +(only.length ? only : all).forEach(suite => { + describe(`${suite.description} (${suite.file.replace(`${__dirname}/`, '')})`, () => { + suite.tests.forEach(test => { + it(test.description, () => { + jsf.option(jsf.option.getDefaults()); + + if (test.set) { + jsf.option(test.set); + } + + if (test.seed) { + jsf.option({ + random: () => test.seed, + }); + } + + if (test.require) { + require(`./${test.require}`).register(jsf); + } + + const schema = typeof test.schema === 'string' + ? pick(suite, test.schema) + : test.schema; + + const refs = (test.refs || []).map(ref => { + return typeof ref === 'string' + ? pick(suite, ref) + : ref; + }); + + // support for "exhaustive" testing, increase or set in .json spec + // for detecting more bugs quickly by executing the same test N-times + let nth = test.repeat || (process.CI ? 100 : 10); + + const tasks = []; + + while (nth) { + if (!test.skip) { + tasks.push(tryTest(test, refs, schema)); + } + + nth -= 1; + } + + return Promise.all(tasks); + }).timeout(process.CI ? 30000 : 10000); + }); + }); +}); diff --git a/tests/schema/validator.js b/tests/schema/validator.js new file mode 100644 index 0000000000000000000000000000000000000000..a0e032938804c3fb4447b9d8e02e17fa6006d13a --- /dev/null +++ b/tests/schema/validator.js @@ -0,0 +1,145 @@ +import is from 'is-my-json-valid'; +import Ajv from 'ajv'; +import tv4 from 'tv4'; +import clone from 'clone'; +import semver from 'semver'; +import ZSchema from 'z-schema'; + +function addValidators(v) { + const registry = v.addFormat || v.registerFormat; + const msgOnFail = !v.registerFormat; + + registry.call(v, 'semver', value => { + let pass; + let err; + + try { + pass = semver.valid(value) === value; + } catch (e) { + err = e.message; + } + + if (msgOnFail) { + // tv4, Jayschema + if (pass) return null; + return err; + } + + // ZSchema + return pass; + }); +} + +export function checkType(sample, type) { + const test = Object.prototype.toString.call(sample).match(/object (\w+)/); + + if (test[1].toLowerCase() !== type) { + throw new Error(`Expected ${JSON.stringify(sample)} to be ${type}`); + } +} + +export function checkSchema(sample, schema, refs) { + const fail = []; + const fixed = {}; + + if (refs) { + refs.forEach(s => { + fixed[s.id ? s.id.split('#')[0] : ''] = clone(s); + }); + } + + // is-my-json-valid + const v = is(schema, { + formats: { + semver: semver.valid, + }, + schemas: fixed, + }); + + if (!v(sample)) { + // FIXME: https://github.com/mafintosh/is-my-json-valid/issues/172 + if (v.errors[0].field !== 'data.num') { + v.errors.forEach(e => { + fail.push(`${e.field.replace('data.', '')} ${e.message}`); + }); + } + } + + // z-schema + const validator = new ZSchema({ + ignoreUnresolvableReferences: false, + }); + + Object.keys(fixed).forEach(k => { + validator.setRemoteReference(k, fixed[k]); + }); + + let valid; + + try { + valid = validator.validate(clone(sample), clone(schema)); + } catch (e) { + fail.push(`[z-schema] ${e.message}`); + } + + const errors = validator.getLastErrors(); + + if (errors || !valid) { + fail.push(errors.map(e => { + if (e.code === 'PARENT_SCHEMA_VALIDATION_FAILED') { + return e.inner.map(x => `[z-schema] ${x.message}`).join('\n'); + } + + return `[z-schema] ${e.message}`; + }).join('\n') || `[z-schema] Invalid schema ${JSON.stringify(sample)}`); + } + + // tv4 + const api = tv4.freshApi(); + + api.banUnknown = false; + api.cyclicCheck = false; + + Object.keys(fixed).forEach(k => { + api.addSchema(k, fixed[k]); + }); + + const result = api.validateResult(sample, clone(schema), api.cyclicCheck, api.banUnknown); + + if (result.missing.length) { + fail.push(`[tv4] Missing ${result.missing.join(', ')}`); + } + + if (result.error) { + fail.push(`[tv4] ${result.error}`); + } + + // ajv + const ajv = new Ajv({ + validateSchema: false, + jsonPointers: true, + logger: false, + formats: { + semver: semver.valid, + }, + }); + + Object.keys(fixed).forEach(id => { + ajv.addSchema(fixed[id], id); + }); + + if (!ajv.validate(schema, sample)) { + ajv.errors.forEach(x => { + fail.push(`[ajv] ${x.message}`); + }); + } + + if (fail.length) { + const a = JSON.stringify(sample, null, 2); + const b = JSON.stringify(schema, null, 2); + + throw new Error(`Given sample does not match schema.\n${fail.join('\n')}\n---\n${a}\n---\n${b}\n---\n`); + } +} + +[tv4, ZSchema].map(addValidators); diff --git a/tests/unit/core/infer.spec.js b/tests/unit/core/infer.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..0dcb49c32cb97a75bfd331a02df2ed0ea5cef483 --- /dev/null +++ b/tests/unit/core/infer.spec.js @@ -0,0 +1,14 @@ +import { expect } from 'chai'; +import infer from '../../../src/core/infer'; + +/* global describe, it */ + +describe('Infer', () => { + it('should infer `array` type when `additionalItems` property exists on top-level schema', () => { + const schema = { + additionalItems: true, + }; + + expect(infer(schema, '')).to.eql('array'); + }); +}); diff --git a/tests/unit/core/utils.spec.js b/tests/unit/core/utils.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ac59813e9bebe4a10c4c6479120fb46c478308e2 --- /dev/null +++ b/tests/unit/core/utils.spec.js @@ -0,0 +1,104 @@ +import { expect } from 'chai'; +import utils from '../../../src/core/utils'; +import optionAPI from '../../../src/api/option'; + +/* global describe, it */ + +describe('Utils', () => { + describe('hasProperties function', () => { + const bigObject = { + some: 'keys', + existing: 'on', + the: 'object', + }; + + const smallObject = { + some: 'keys', + }; + + it('should return true when one key being checked', () => { + expect(utils.hasProperties(bigObject, 'some')).to.eql(true); + expect(utils.hasProperties(bigObject, 'existing')).to.eql(true); + expect(utils.hasProperties(bigObject, 'the')).to.eql(true); + expect(utils.hasProperties(smallObject, 'some')).to.eql(true); + }); + + it('should return true when all keys being checked', () => { + expect(utils.hasProperties(bigObject, 'some', 'existing', 'the')).to.eql(true); + expect(utils.hasProperties(smallObject, 'some', 'existing', 'the')).to.eql(true); + }); + + it('should return false when no keys exist on object', () => { + expect(utils.hasProperties(bigObject, 'different')).to.eql(false); + expect(utils.hasProperties(smallObject, 'different')).to.eql(false); + }); + }); + + describe('getSubAttribute function', () => { + const object = { + outer: { + inner: { + key: 'value', + }, + }, + }; + + it('should return a leaf if chain is long enough', () => { + expect(utils.getSubAttribute(object, 'outer.inner.key')).to.eql('value'); + expect(utils.getSubAttribute(object, 'outer.inner.key.help.me')).to.eql('value'); + }); + + it('should return a subobject if the chain doesn\'t reach a leaf (is shorter)', () => { + expect(utils.getSubAttribute(object, 'outer.inner')).to.eql({ key: 'value' }); + }); + + it('should return a subobject of the valid chain part (and ignore the invalid chain part)', () => { + expect(utils.getSubAttribute(object, 'outer.help.me')).to.eql({ inner: { key: 'value' } }); + expect(utils.getSubAttribute(object, 'help.me')).to.eql(object); + }); + }); + + describe('typecast function', () => { + it('should normalize constraints and format final values', () => { + utils.typecast(null, {}, opts => { + expect(opts).to.eql({}); + }); + + const schema = { + type: 'integer', + enum: [1, 2, 3], + minimum: 2, + }; + + utils.typecast(null, schema, opts => { + expect(schema.enum).to.eql([2, 3]); + expect(opts).to.eql({ minimum: 2 }); + }); + }); + + it('should normalize constraints with global options', () => { + optionAPI({ + maxLength: 4, + }); + + utils.typecast(null, { + type: 'string', + maxLength: 10, + }, opts => { + expect(opts).to.eql({ maxLength: 4 }); + }); + }); + + it('should accept custom types for typecasting', () => { + optionAPI({ + maxLength: 5, + }); + + utils.typecast('string', { + maxLength: 10, + }, opts => { + expect(opts).to.eql({ maxLength: 5 }); + }); + }); + }); +}); diff --git a/tests/unit/generators/boolean.spec.js b/tests/unit/generators/boolean.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..03b5b10f3dad1f7b94c7f5023d40df854bc1a691 --- /dev/null +++ b/tests/unit/generators/boolean.spec.js @@ -0,0 +1,10 @@ +import { expect } from 'chai'; +import booleanGenerator from '../../../src/generators/boolean'; + +/* global describe, it */ + +describe('Boolean Generator', () => { + it('should always return a boolean type', () => { + expect(typeof booleanGenerator()).to.eql('boolean'); + }); +}); diff --git a/tests/unit/generators/ipv4.spec.js b/tests/unit/generators/ipv4.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..cab8ed10b8b0cdb9dfe69f87f43157e91de397e9 --- /dev/null +++ b/tests/unit/generators/ipv4.spec.js @@ -0,0 +1,10 @@ +import { expect } from 'chai'; +import ipv4Generator from '../../../src/generators/ipv4'; + +/* global describe, it */ + +describe('IPv4 Generator', () => { + it('should always match the IPv4 regex', () => { + expect(ipv4Generator()).to.match(/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/); + }); +}); diff --git a/tests/unit/generators/null.spec.js b/tests/unit/generators/null.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..24f656db0d942a59a304d98585559516859eeb1d --- /dev/null +++ b/tests/unit/generators/null.spec.js @@ -0,0 +1,10 @@ +import { expect } from 'chai'; +import nullGenerator from '../../../src/generators/null'; + +/* global describe, it */ + +describe('Null Generator', () => { + it('should always return `null` value', () => { + expect(nullGenerator()).to.eql(null); + }); +}); diff --git a/tests/unit/generators/number.spec.js b/tests/unit/generators/number.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..e54af465864f4e9decfcda43975dcb502a2639a2 --- /dev/null +++ b/tests/unit/generators/number.spec.js @@ -0,0 +1,13 @@ +import { expect } from 'chai'; +import numberType from '../../../src/types/number'; + +/* global describe, it */ + +describe('Number Generator', () => { + it('should return number with a fractional part', () => { + const n = numberType({}); + const m = Math.floor(n); + + expect(n).not.to.eql(m); + }); +}); diff --git a/ts/class/OptionRegistry.ts b/ts/class/OptionRegistry.ts deleted file mode 100644 index bc52f0bc3d40dd65d82080bc4efa93155985f950..0000000000000000000000000000000000000000 --- a/ts/class/OptionRegistry.ts +++ /dev/null @@ -1,44 +0,0 @@ -import Registry from './Registry'; - -type Option = boolean|number|Function; - -/** - * This class defines a registry for custom settings used within JSF. - */ -class OptionRegistry extends Registry<Option> { - constructor() { - super(); - this.data = this.defaults; - } - - get defaults() { - const data = {}; - - data['defaultInvalidTypeProduct'] = null; - data['defaultRandExpMax'] = 10; - - data['ignoreProperties'] = []; - data['ignoreMissingRefs'] = false; - data['failOnInvalidTypes'] = true; - data['failOnInvalidFormat'] = true; - - data['alwaysFakeOptionals'] = false; - data['optionalsProbability'] = 0.0; - data['useDefaultValue'] = false; - data['requiredOnly'] = false; - - data['minItems'] = 0; - data['maxItems'] = null; - data['maxLength'] = null; - - data['resolveJsonPath'] = false; - data['reuseProperties'] = false; - data['fillProperties'] = true; - - data['random'] = Math.random; - - return data; - } -} - -export default OptionRegistry; diff --git a/ts/core/error.ts b/ts/core/error.ts deleted file mode 100644 index b2155f85201cdda613799bc45d3393363b8251a7..0000000000000000000000000000000000000000 --- a/ts/core/error.ts +++ /dev/null @@ -1,21 +0,0 @@ -declare type ErrorInterface = Error; - -declare class Error implements ErrorInterface { - name: string; - message: string; - static captureStackTrace(object: Object, objectConstructor?: Function): void; -} - -class ParseError extends Error { - constructor(message: string, public path: StackTrace) { - super(); - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor); - } - this.name = 'ParseError'; - this.message = message; - this.path = path; - } -} - -export default ParseError; diff --git a/ts/generators/words.ts b/ts/generators/words.ts deleted file mode 100644 index eb1111d193db7356b8a1b3a2b6b235dae12ab55c..0000000000000000000000000000000000000000 --- a/ts/generators/words.ts +++ /dev/null @@ -1,20 +0,0 @@ -import random from '../core/random'; - -var LIPSUM_WORDS = ('Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod tempor incididunt ut labore' - + ' et dolore magna aliqua Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea' - + ' commodo consequat Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla' - + ' pariatur Excepteur sint occaecat cupidatat non proident sunt in culpa qui officia deserunt mollit anim id est' - + ' laborum').split(' '); - -/** - * Generates randomized array of single lorem ipsum words. - * - * @param length - * @returns {Array.<string>} - */ -function wordsGenerator(length: number): string[] { - var words = random.shuffle(LIPSUM_WORDS); - return words.slice(0, length); -} - -export default wordsGenerator; diff --git a/ts/index.d.ts b/ts/index.d.ts deleted file mode 100644 index bbe3516f6e6469d7a7772d69865c137c872affc6..0000000000000000000000000000000000000000 --- a/ts/index.d.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * JSF basic schema extension - */ -interface IGeneratorSchema { - faker?: any; - 'x-faker'?: any; - chance?: any; - 'x-chance'?: any; - casual?: any; - 'x-casual'?: any; -} - -interface IStringSchema extends IGeneratorSchema { - format?: string; - pattern?: RegExp; - minLength?: number; - maxLength?: number; -} - -interface INumberSchema extends IGeneratorSchema { - multipleOf?: number; - minimum?: number; - maximum?: number; - exclusiveMinimum?: boolean; - exclusiveMaximum?: boolean; -} - -interface IArraySchema extends IGeneratorSchema { - items?: IObjectSchema|IObjectSchema[]; - additionalItems?: boolean|IObjectSchema; - minItems?: number; - maxItems?: number; - uniqueItems?: boolean; -} - -interface IPropertySchema { - [property: string]: IObjectSchema; -} - -interface IObjectSchema extends IGeneratorSchema { - additionalProperties?: boolean; - required?: string[]; - properties?: IPropertySchema; - patternProperties?: IPropertySchema; // RegExp should be the index of this structure (see "5.4.4.1. Valid values" in http://json-schema.org/latest/json-schema-validation.html), but RegExp-key-based maps are unsupported in TypeScript - minProperties?: number; - maxProperties?: number; -} - -type ISchemaInternalType = 'string' | 'integer' | 'number' | 'object' | 'array' | 'boolean'; - -/** - * JSON Schema TypeScript interface. - * - * fetched from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/tv4/tv4.d.ts - */ -interface JsonSchema extends IGeneratorSchema { - [key: string]: any; - title?: string; - description?: string; - id?: string; - enum?: any[]; - $schema?: string; - type?: ISchemaInternalType; // | string[] // see http://json-schema.org/latest/json-schema-validation.html "5.5.2.1 valid values" - items?: any; - properties?: any; - patternProperties?: any; - additionalProperties?: boolean; - required?: string[]; - definitions?: any; - default?: any; -} - -declare type SchemaPath = string[]; - -declare type StackTrace = string[]; - -/** - * This interface is used to check consistency between type generators (string, boolean, array, etc.) - */ -interface FTypeGenerator { - (value?: IGeneratorSchema, path?: SchemaPath, resolve?: Function, traverseCallback?: Function): any; -} - -interface IStringMap { - [format: string]: string; -} - -/** - * This interface represents outer JSF object that is accessible by the end users. - * It is a stateful function (combined of additional functionalities) and needs a separate type. - */ -interface jsfAPI { - (schema: JsonSchema, refs?: any): any; - format: Function; - option: Function; - extend: Function; - version: string; -} - -// quick and dirty overcome -// TODO provide proper definitions -declare module 'randexp' { var randexp: any; export default randexp; } -declare module 'json-schema-ref-parser' { var $RefParser: any; export default $RefParser; } diff --git a/ts/index.ts b/ts/index.ts deleted file mode 100644 index 90375f6689fe3b0943e7e9e85689393ab5671bc3..0000000000000000000000000000000000000000 --- a/ts/index.ts +++ /dev/null @@ -1,181 +0,0 @@ -import $RefParser from 'json-schema-ref-parser'; -import deref from 'deref'; - -import Container from './class/Container'; -import format from './api/format'; -import option from './api/option'; -import env from './core/constants'; -import random from './core/random'; -import utils from './core/utils'; -import run from './core/run'; - -var container = new Container(); - -function getRefs(refs?: any) { - var $refs = {}; - - if (Array.isArray(refs)) { - refs.map(deref.util.normalizeSchema).forEach(function(schema) { - $refs[schema.id] = schema; - }); - } else { - $refs = refs || {}; - } - - return $refs; -} - -function walk(obj, cb) { - var keys = Object.keys(obj); - - var retval; - - for (var i = 0; i < keys.length; i += 1) { - retval = cb(obj[keys[i]], keys[i], obj); - - if (!retval && obj[keys[i]] && !Array.isArray(obj[keys[i]]) && typeof obj[keys[i]] === 'object') { - retval = walk(obj[keys[i]], cb); - } - - if (typeof retval !== 'undefined') { - return retval; - } - } -} - -var jsf = function(schema: JsonSchema, refs?: any) { - var ignore = option('ignoreMissingRefs'); - - const $ = deref((id, refs) => { - // FIXME: allow custom callback? - - if (ignore) { - return {}; - } - }); - - var $refs = getRefs(refs); - - return run($refs, $(schema, $refs, true), container); -}; - -jsf.resolve = <jsfAPI>function(schema: JsonSchema, refs?: any, cwd?: string) { - if (typeof refs === 'string') { - cwd = refs; - refs = {}; - } - - // normalize basedir (browser aware) - cwd = cwd || (typeof process !== 'undefined' ? process.cwd() : ''); - cwd = cwd.replace(/\/+$/, '') + '/'; - - var $refs = getRefs(refs); - - // identical setup as json-schema-sequelizer - const fixedRefs = { - order: 300, - canRead: true, - read(file, callback) { - const id = cwd !== '/' - ? file.url.replace(cwd, '') - : file.url; - - try { - callback(null, deref.util.findByRef(id, $refs)); - } catch (e) { - const result = walk(schema, (v, k, sub) => { - if (k === 'id' && v === id) { - return sub; - } - }); - - if (!result) { - return callback(e); - } - - callback(null, result); - } - }, - }; - - return $RefParser - .dereference(cwd, schema, { - resolve: { fixedRefs }, - dereference: { - circular: 'ignore', - }, - }).then((sub) => run($refs, sub, container)); -}; - -jsf.format = format; -jsf.option = option; -jsf.random = random; - -// built-in support -container.define('pattern', random.randexp); - -// skip default generators -container.define('jsonPath', (value, schema) => { - delete schema.type; - return schema; -}); - -// safe auto-increment values -container.define('autoIncrement', function(value, schema) { - if (!this.offset) { - const min = schema.minimum || 1; - const max = min + env.MAX_NUMBER; - const offset = value.initialOffset || schema.initialOffset; - - this.offset = offset || random.number(min, max); - } - - if (value === true) { - return this.offset++; - } - - return schema; -}); - -// safe-and-sequential dates -container.define('sequentialDate', function(value, schema) { - if (!this.now) { - this.now = random.date(); - } - - if (value) { - schema = this.now.toISOString(); - value = value === true - ? 'days' - : value; - - if (['seconds', 'minutes', 'hours', 'days', 'weeks', 'months', 'years'].indexOf(value) === -1) { - throw new Error(`Unsupported increment by ${utils.short(value)}`); - } - - this.now.setTime(this.now.getTime() + random.date(value)); - } - - return schema; -}); - -// returns itself for chaining -jsf.extend = function(name: string, cb: Function) { - container.extend(name, cb); - return jsf; -}; - -jsf.define = function(name: string, cb: Function) { - container.define(name, cb); - return jsf; -}; - -jsf.locate = function(name: string) { - return container.get(name); -}; - -/* global VERSION */ - -jsf.version = VERSION; - -export default jsf; diff --git a/ts/types/array.ts b/ts/types/array.ts deleted file mode 100644 index ee38168626ab0afde1824c0803533096791f79fc..0000000000000000000000000000000000000000 --- a/ts/types/array.ts +++ /dev/null @@ -1,102 +0,0 @@ -import random from '../core/random'; -import utils from '../core/utils'; -import ParseError from '../core/error'; -import optionAPI from '../api/option'; - -// TODO provide types -function unique(path: SchemaPath, items, value, sample, resolve, traverseCallback: Function) { - var tmp = [], - seen = []; - - function walk(obj) { - var json = JSON.stringify(obj); - - if (seen.indexOf(json) === -1) { - seen.push(json); - tmp.push(obj); - } - } - - items.forEach(walk); - - // TODO: find a better solution? - var limit: number = 100; - - while (tmp.length !== items.length) { - walk(traverseCallback(value.items || sample, path, resolve)); - - if (!limit--) { - break; - } - } - - return tmp; -} - -type Result = any; - -// TODO provide types -var arrayType: FTypeGenerator = function arrayType(value: IArraySchema, path: SchemaPath, resolve: Function, traverseCallback: Function): Result[] { - var items: Result[] = []; - - if (!(value.items || value.additionalItems)) { - if (utils.hasProperties(value, 'minItems', 'maxItems', 'uniqueItems')) { - throw new ParseError('missing items for ' + utils.short(value), path); - } - return items; - } - - // see http://stackoverflow.com/a/38355228/769384 - // after type guards support subproperties (in TS 2.0) we can simplify below to (value.items instanceof Array) - // so that value.items.map becomes recognized for typescript compiler - var tmpItems = value.items; - if (tmpItems instanceof Array) { - return Array.prototype.concat.call(items, tmpItems.map(function(item, key) { - var itemSubpath: SchemaPath = path.concat(['items', key + '']); - return traverseCallback(item, itemSubpath, resolve); - })); - } - - var minItems = value.minItems; - var maxItems = value.maxItems; - - if (optionAPI('minItems') && minItems === undefined) { - // fix boundaries - minItems = !maxItems - ? optionAPI('minItems') - : Math.min(optionAPI('minItems'), maxItems); - } - - if (optionAPI('maxItems')) { - // Don't allow user to set max items above our maximum - if (maxItems && maxItems > optionAPI('maxItems')) { - maxItems = optionAPI('maxItems'); - } - - // Don't allow user to set min items above our maximum - if (minItems && minItems > optionAPI('maxItems')) { - minItems = maxItems; - } - } - - var optionalsProbability = optionAPI('alwaysFakeOptionals') === true ? 1.0 : optionAPI('optionalsProbability'); - var length: number = (maxItems != null && optionalsProbability) - ? Math.round(maxItems * optionalsProbability) - : random.number(minItems, maxItems, 1, 5), - // TODO below looks bad. Should additionalItems be copied as-is? - sample: Object = typeof value.additionalItems === 'object' ? value.additionalItems : {}; - - for (var current: number = items.length; current < length; current++) { - var itemSubpath: SchemaPath = path.concat(['items', current + '']); - var element = traverseCallback(value.items || sample, itemSubpath, resolve); - items.push(element); - } - - if (value.uniqueItems) { - return unique(path.concat(['items']), items, value, sample, resolve, traverseCallback); - } - - return items; -}; - -export default arrayType; diff --git a/ts/types/integer.ts b/ts/types/integer.ts deleted file mode 100644 index 0bdddb6030226a70cb572869b34c1a4805a22d9f..0000000000000000000000000000000000000000 --- a/ts/types/integer.ts +++ /dev/null @@ -1,14 +0,0 @@ -import number from './number'; - -// The `integer` type is just a wrapper for the `number` type. The `number` type -// returns floating point numbers, and `integer` type truncates the fraction -// part, leaving the result as an integer. - -var integerType: FTypeGenerator = function integerType(value: INumberSchema): number { - var generated: number = number(value); - // whether the generated number is positive or negative, need to use either - // floor (positive) or ceil (negative) function to get rid of the fraction - return generated > 0 ? Math.floor(generated) : Math.ceil(generated); -}; - -export default integerType; diff --git a/ts/types/object.ts b/ts/types/object.ts deleted file mode 100644 index 137399338374d8e3cca652178b052e6ebf746a08..0000000000000000000000000000000000000000 --- a/ts/types/object.ts +++ /dev/null @@ -1,182 +0,0 @@ -import random from '../core/random'; -import words from '../generators/words'; -import utils from '../core/utils'; -import optionAPI from '../api/option'; -import ParseError from '../core/error'; - -// fallback generator -var anyType = { type: ['string', 'number', 'integer', 'boolean'] }; - -// TODO provide types -var objectType: FTypeGenerator = function objectType(value: IObjectSchema, path, resolve, traverseCallback: Function): Object { - var props = {}; - - var properties = value.properties || {}; - var patternProperties = value.patternProperties || {}; - var requiredProperties = (value.required || []).slice(); - var allowsAdditional = value.additionalProperties === false ? false : true; - - var propertyKeys = Object.keys(properties); - var patternPropertyKeys = Object.keys(patternProperties); - var optionalProperties = propertyKeys.concat(patternPropertyKeys).reduce(function(_response, _key) { - if (requiredProperties.indexOf(_key) === -1) _response.push(_key); - return _response; - }, []); - var allProperties = requiredProperties.concat(optionalProperties); - - var additionalProperties = allowsAdditional - ? (value.additionalProperties === true ? {} : value.additionalProperties) - : null; - - if ( - !allowsAdditional && - propertyKeys.length === 0 && - patternPropertyKeys.length === 0 && - utils.hasProperties(value, 'minProperties', 'maxProperties', 'dependencies', 'required') - ) { - // just nothing - return {}; - } - - if (optionAPI('requiredOnly') === true) { - requiredProperties.forEach(function(key) { - if (properties[key]) { - props[key] = properties[key]; - } - }); - - return traverseCallback(props, path.concat(['properties']), resolve); - } - - var optionalsProbability = optionAPI('alwaysFakeOptionals') === true ? 1.0 : optionAPI('optionalsProbability'); - var ignoreProperties = optionAPI('ignoreProperties') || []; - - var min = Math.max(value.minProperties || 0, requiredProperties.length); - var max = Math.max(value.maxProperties || allProperties.length); - - var neededExtras = Math.round((min - requiredProperties.length) + optionalsProbability * (max - min)); - var extraPropertiesRandomOrder = random.shuffle(optionalProperties).slice(0, neededExtras); - var extraProperties = optionalProperties.filter(function(_item) { - return extraPropertiesRandomOrder.indexOf(_item) !== -1; - }); - - // properties are read from right-to-left - var _props = requiredProperties.concat(extraProperties).slice(0, max); - - var skipped = []; - var missing = []; - - _props.forEach(function(key) { - for (let i = 0; i < ignoreProperties.length; i += 1) { - if ((ignoreProperties[i] instanceof RegExp && ignoreProperties[i].test(key)) - || (typeof ignoreProperties[i] === 'string' && ignoreProperties[i] === key) - || (typeof ignoreProperties[i] === 'function' && ignoreProperties[i](properties[key], key))) { - skipped.push(key); - return; - } - } - - // first ones are the required properies - if (properties[key]) { - props[key] = properties[key]; - } else { - var found; - - // then try patternProperties - patternPropertyKeys.forEach(function (_key) { - if (key.match(new RegExp(_key))) { - found = true; - props[random.randexp(key)] = patternProperties[_key]; - } - }); - - if (!found) { - // try patternProperties again, - var subschema = patternProperties[key] || additionalProperties; - - // FIXME: allow anyType as fallback when no subschema is given? - - if (subschema) { - // otherwise we can use additionalProperties? - props[patternProperties[key] ? random.randexp(key) : key] = subschema; - } else { - missing.push(key); - } - } - } - }); - - var fillProps = optionAPI('fillProperties'); - var reuseProps = optionAPI('reuseProperties'); - - // discard already ignored props if they're not required to be filled... - var current = Object.keys(props).length + (fillProps ? 0 : skipped.length); - - while (fillProps) { - if (!(patternPropertyKeys.length || allowsAdditional)) { - break; - } - - if (current >= min) { - break; - } - - if (allowsAdditional) { - if (reuseProps && ((propertyKeys.length - current) > min)) { - var count = 0; - - do { - count += 1; - - // skip large objects - if (count > 1000) { - break; - } - - var key = random.pick(propertyKeys); - } while (typeof props[key] !== 'undefined'); - - if (typeof props[key] === 'undefined') { - props[key] = properties[key]; - current += 1; - } - } else { - var word = words(1) + random.randexp('[a-f\\d]{1,3}'); - - if (!props[word]) { - props[word] = additionalProperties || anyType; - current += 1; - } - } - } - - patternPropertyKeys.forEach(function (_key) { - var word = random.randexp(_key); - - if (!props[word]) { - props[word] = patternProperties[_key]; - current += 1; - } - }); - } - - if (!allowsAdditional && current < min) { - if (missing.length) { - throw new ParseError( - 'properties "' + missing.join(', ') + '" were not found while additionalProperties is false:\n' + - utils.short(value), - path - ); - } - - throw new ParseError( - 'properties constraints were too strong to successfully generate a valid object for:\n' + - utils.short(value), - path - ); - } - - return traverseCallback(props, path.concat(['properties']), resolve); -}; - -export default objectType; diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 7d45328ff93a2bd16a62830d887c31411b07324f..0000000000000000000000000000000000000000 --- a/tslint.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "rules": { - "quotemark": [true, "single", "avoid-escape"], - "radix": true, - "semicolon": [true, "always"] - } -}