diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/README.md b/README.md index b73c514..9de4158 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,205 @@ -# microschema - -
+
+# microschema -Helper library to create JSON Schemas in a concise way. +Small library without dependencies to create JSON Schemas in a concise way. Example: ```js -const microschema = require('microschema') +const ms = require('microschema') -microschema.strictObj({ - identity_id: 'string:required', - client_id: 'number', - redirect_uri: 'string:uri', +ms.strictObj({ + identityId: 'string:required', + clientId: 'number', + redirectUri: 'string:uri', scope: 'string', - ip_address: 'string', - children: microschema.arrayOf(microschema.strictObj({ + ipAddress: ms.string({pattern: ''}), + children: ms.arrayOf(ms.strictObj({ scope: 'string' })) }) ``` + + +## Strings + +Using the `ms.string()` method: +```js +ms.string() +output = {type: 'string'}) +``` + +```js +ms.string({pattern: '[a-z]+'}) + +// Passing a javascript RegExp is equivalent to the above +ms.string({pattern: /[a-z]+/}) + +output = { + type: 'string', + pattern: '[a-z]+' +} +``` + +Setting the required flag (only possible within an object): +```js +ms.obj({ + foo: ms.required.string() +}) + +output = { + type: 'object', + required: ['foo'], + properties: { + foo: { + type: 'string' + } + } +} +``` + +Simplified usage within objects: +```js +ms.obj({ + foo: 'string' +}) + +output = { + type: 'object', + properties: { + foo: { + type: 'string' + } + } +} +``` + +```js +ms.obj({ + foo: 'string:required' +}) + +output = { + type: 'object', + required: ['foo'], + properties: { + foo: { + type: 'string' + } + } +} +``` + +## Numbers and Integers + +Simplified usage within objects: +```js +ms.obj({ + foo: 'string' +}) +``` + +Using the `ms.number()` method: +```js +ms.number() +output = {type: 'number'} +``` + +```js +ms.number({min: 0, max: 10}) + +output = { + type: 'number', + minimum: 0, + maximum: 10 +} +``` + +Using the `ms.integer()` method: +```js +ms.integer() +output = {type: 'integer'} +``` + +The `integer()` methods also accepts `min` and `max` params the same as `number()` does. + + +## Booleans + +```js +ms.boolean() +output = {type: 'boolean'}) +``` + +Simplified usage within objects: +```js +ms.obj({ + foo: 'boolean:required' +}) + +output = { + type: 'object', + required: ['foo'], + properties: { + foo: { + type: 'boolean' + } + } +} +``` + +## Objects + +```js +ms.obj() +output = {type: 'object'} +``` + +Don't allow additional properties with `strictObj()`: +```js +ms.strictObj({ + count: ms.integer() +}) + +output = { + type: 'object', + additionalProperties: false, + properties: { + count: {type: 'integer'} + } +} +``` + +## Arrays + +```js +ms.arrayOf(ms.string()) + +output = { + type: 'array', + items: {type: 'string'} +} +``` + + +## Enumerations + +```js +// All values in an enumeration must be of the same type. +ms.enum('foo', 'bar') + +output = { + type: 'string', + enum: ['foo', 'bar'] +} +``` diff --git a/index.js b/index.js index 94ace4f..3e7883f 100644 --- a/index.js +++ b/index.js @@ -102,6 +102,21 @@ module.exports = { }) }, + string ({pattern} = {}) { + const s = {type: 'string'} + if (pattern) { + if (pattern instanceof RegExp) { + if (pattern.flags) { + throw new Error(`JSON schema does not support regexp flags: ${pattern}`) + } + s.pattern = pattern.source + } else { + s.pattern = pattern + } + } + return this.decorate(s) + }, + number ({min, max, integer} = {}) { const type = integer ? 'integer' : 'number' const s = {type: type} @@ -115,6 +130,10 @@ module.exports = { return this.number(opts) }, + boolean () { + return this.decorate({type: 'boolean'}) + }, + decorate (obj) { if (this[isRequired]) obj[isRequired] = true return obj diff --git a/test.js b/test.js index 616a53d..421fc6c 100644 --- a/test.js +++ b/test.js @@ -1,9 +1,9 @@ -const microschema = require('./index') +const ms = require('./index') const test = require('ava').test const assert = require('assert') test('strictObj() creates an object with a single property', function (t) { - const schema = microschema.strictObj({foo: 'string'}) + const schema = ms.strictObj({foo: 'string'}) assert.deepEqual(schema, { type: 'object', @@ -15,7 +15,7 @@ test('strictObj() creates an object with a single property', function (t) { }) test('strictObj() creates an object with a uri string', function (t) { - const schema = microschema.strictObj({foo: 'string:uri'}) + const schema = ms.strictObj({foo: 'string:uri'}) assert.deepEqual(schema, { type: 'object', @@ -27,7 +27,7 @@ test('strictObj() creates an object with a uri string', function (t) { }) test('strictObj() creates an object with a required property', function (t) { - const schema = microschema.strictObj({ + const schema = ms.strictObj({ foo: 'string:required', bar: 'string' }) @@ -45,7 +45,7 @@ test('strictObj() creates an object with a required property', function (t) { test('enum() creates an enum of strings', function (t) { - const schema = microschema.enum('foo', 'bar') + const schema = ms.enum('foo', 'bar') assert.deepEqual(schema, { type: 'string', enum: ['foo', 'bar'] @@ -53,7 +53,7 @@ test('enum() creates an enum of strings', function (t) { }) test('enum() creates an enum from an array', function (t) { - const schema = microschema.enum(['foo', 'bar']) + const schema = ms.enum(['foo', 'bar']) assert.deepEqual(schema, { type: 'string', enum: ['foo', 'bar'] @@ -62,20 +62,75 @@ test('enum() creates an enum from an array', function (t) { test('arrayOf() creates an array with a type of its items', function (t) { - const schema = microschema.arrayOf('integer') + const schema = ms.arrayOf('integer') assert.deepEqual(schema, { type: 'array', items: {type: 'integer'} }) }) +test('string() creates a type string', function (t) { + const schema = ms.string() + assert.deepEqual(schema, {type: 'string'}) +}) + +test('required.string() creates a required string', function (t) { + const schema = ms.obj({ + foo: ms.required.string() + }) + assert.deepEqual(schema, { + type: 'object', + required: ['foo'], + properties: { + foo: { + type: 'string' + } + } + }) +}) + +test('string({pattern}) adds a regex pattern', function (t) { + const schema = ms.string({ + pattern: '[a-z]+' + }) + + assert.deepEqual(schema, { + type: 'string', + pattern: '[a-z]+' + }) +}) + +test('string({pattern}) accepts a javascript regex', function (t) { + const schema = ms.string({ + pattern: /[a-z]+/ + }) + + assert.deepEqual(schema, { + type: 'string', + pattern: '[a-z]+' + }) +}) + +test('string({pattern}) does not accept a javascript regex with flags', function (t) { + const method = () => { + ms.string({pattern: /[a-z]+/i}) + } + + assert.throws(method, (err) => { + assert.equal(err.message, + 'JSON schema does not support regexp flags: /[a-z]+/i') + + return true + }) +}) + test('number() creates a type number', function (t) { - const schema = microschema.number() + const schema = ms.number() assert.deepEqual(schema, {type: 'number'}) }) test('number() creates a type number with min and max', function (t) { - const schema = microschema.number({min: 0, max: 10}) + const schema = ms.number({min: 0, max: 10}) assert.deepEqual(schema, { type: 'number', minimum: 0, @@ -84,12 +139,12 @@ test('number() creates a type number with min and max', function (t) { }) test('integer() creates a type number', function (t) { - const schema = microschema.integer() + const schema = ms.integer() assert.deepEqual(schema, {type: 'integer'}) }) test('integer() creates a type integer with min and max', function (t) { - const schema = microschema.integer({min: 0, max: 10}) + const schema = ms.integer({min: 0, max: 10}) assert.deepEqual(schema, { type: 'integer', minimum: 0, @@ -97,14 +152,19 @@ test('integer() creates a type integer with min and max', function (t) { }) }) +test('boolean() creates a type boolean', function (t) { + const schema = ms.boolean() + assert.deepEqual(schema, {type: 'boolean'}) +}) + test('chaining creates a chain object', function (t) { - const chain = microschema.required + const chain = ms.required assert.ok(chain.strictObj instanceof Function) }) test('chaining creates a required obj', function (t) { - const schema = microschema.obj({ - myProperty: microschema.required.obj({foo: 'string'}) + const schema = ms.obj({ + myProperty: ms.required.obj({foo: 'string'}) }) assert.deepEqual(schema, { @@ -122,8 +182,8 @@ test('chaining creates a required obj', function (t) { }) test('chaining creates a required strict obj', function (t) { - const schema = microschema.obj({ - myProperty: microschema.required.strictObj({foo: 'string'}) + const schema = ms.obj({ + myProperty: ms.required.strictObj({foo: 'string'}) }) assert.deepEqual(schema, { @@ -142,8 +202,8 @@ test('chaining creates a required strict obj', function (t) { }) test('chaining creates a required enum', function (t) { - const schema = microschema.obj({ - myProperty: microschema.required.enum('foo', 'bar') + const schema = ms.obj({ + myProperty: ms.required.enum('foo', 'bar') }) assert.deepEqual(schema, {