From 0f9cd2ec0cf243a570aead6a26c302760b38ec98 Mon Sep 17 00:00:00 2001 From: Lukas Date: Wed, 12 Jun 2019 13:48:06 +0200 Subject: [PATCH] feat: add new methods --- README.md | 65 ++++++++++++++++++++ index.js | 77 ++++++++++++++++++------ test.js | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 299 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 73794a5..5c3c830 100644 --- a/README.md +++ b/README.md @@ -268,3 +268,68 @@ output = { const: 'foo' } ``` + +## anyOf / oneOf / allOf + +```js +ms.anyOf('number', ms.obj({foo: 'string'})) + +output = { + anyOf: [ + {type: 'number'}, + { + type: 'object', + properties: { + foo: {type: 'string'} + } + } + ] +} +``` +Note: you can also pass an array as the first argument + + +## $id / $ref + +```js +ms.$id('#user').obj({ + name: 'string', + friend: ms.$ref('#user') +}) + +output = { + $id: '#user', + type: 'object', + properties: { + name: {type: 'string'} + friend: {$ref: '#user'} + } +} +``` + +## definitions + +```js +ms.definitions({ + user: ms.obj({name: 'string'}) +}).obj({ + name: 'string', + friend: ms.$ref('#/definitions/user') +}) + +output = { + definitions: { + user: { + type: 'object', + properties: { + name: {type: 'string'} + } + } + } + type: 'object', + properties: { + name: {type: 'string'} + friend: {$ref: '#/definitions/user'} + } +} +``` diff --git a/index.js b/index.js index 5ed37d5..b816ba3 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ // Symbols -const isChain = Symbol('chainedMicroschema') const isRequired = Symbol('required') +const chained = Symbol('chained') // Helper library to create JsonSchemas // in a concise way. @@ -17,16 +17,16 @@ const isRequired = Symbol('required') // scope: 'string' // })) // }) + module.exports = { // Chaining Properties // ------------------- get required () { - const chain = Object.create(this) - chain[isChain] = true - chain[isRequired] = true - return chain + const self = chain(this) + self[isRequired] = true + return self }, @@ -66,7 +66,7 @@ module.exports = { jsonSchema.properties[propertyName] = propertySchema } - return this.decorate(jsonSchema) + return decorate(this, jsonSchema) }, // An object with no additional properties allowed @@ -84,7 +84,7 @@ module.exports = { enum (...enums) { if (Array.isArray(enums[0])) enums = enums[0] - return this.decorate({ + return decorate(this, { type: getJsonType(enums[0]), enum: enums }) @@ -92,7 +92,7 @@ module.exports = { const (value) { - return this.decorate({ + return decorate(this, { type: getJsonType(value), const: value }) @@ -105,10 +105,10 @@ module.exports = { // 2. {Object} JSON Schema // Example: microschema.arrayOf({type: 'object', properties: {...}}) arrayOf (schemaOrType, {minItems, maxItems, uniqueItems} = {}) { - const items = isString(schemaOrType) ? {type: schemaOrType} : schemaOrType - const s = this.decorate({ + const itemSchema = strToSchema(schemaOrType) + const s = decorate(this, { type: 'array', - items: items + items: itemSchema }) if (minItems) s.minItems = minItems @@ -135,7 +135,7 @@ module.exports = { if (minLength) s.minLength = minLength if (maxLength) s.maxLength = maxLength - return this.decorate(s) + return decorate(this, s) }, number ({min, max, integer} = {}) { @@ -143,7 +143,7 @@ module.exports = { const s = {type: type} if (min != null) s.minimum = min if (max != null) s.maximum = max - return this.decorate(s) + return decorate(this, s) }, integer (opts = {}) { @@ -152,15 +152,47 @@ module.exports = { }, boolean () { - return this.decorate({type: 'boolean'}) + return decorate(this, {type: 'boolean'}) + }, + + definitions (obj) { + const self = chain(this) + self[chained].definitions = obj + return self + }, + + $ref (reference) { + return decorate(this, {$ref: reference}) + }, + + $id (id) { + const self = chain(this) + self[chained].$id = id + return self + }, + + anyOf (...args) { + const defs = Array.isArray(args[0]) ? args[0] : args + return decorate(this, {anyOf: defs.map(strToSchema)}) }, - decorate (obj) { - if (this[isRequired]) obj[isRequired] = true - return obj + oneOf (...args) { + const defs = Array.isArray(args[0]) ? args[0] : args + return decorate(this, {oneOf: defs.map(strToSchema)}) + }, + + allOf (...args) { + const defs = Array.isArray(args[0]) ? args[0] : args + return decorate(this, {allOf: defs}) } } +function decorate (self, obj) { + if (self[isRequired]) obj[isRequired] = true + if (self[chained]) Object.assign(obj, self[chained]) + return obj +} + function parseTypeDescription (parentSchema, name, typeDesc) { const [type, ...options] = typeDesc.split(':') const propertySchema = {type: type} @@ -184,6 +216,17 @@ function parseTypeDescription (parentSchema, name, typeDesc) { return propertySchema } +function strToSchema (schemaOrType) { + return isString(schemaOrType) ? {type: schemaOrType} : schemaOrType +} + +function chain (self) { + if (self[chained]) return self + self = Object.create(self) + self[chained] = {} + return self +} + function getJsonType (obj) { if (isString(obj)) return 'string' if (Array.isArray(obj)) return 'array' diff --git a/test.js b/test.js index f8813ed..f859b0a 100644 --- a/test.js +++ b/test.js @@ -240,6 +240,180 @@ test('boolean() creates a type boolean', function (t) { assert.deepEqual(schema, {type: 'boolean'}) }) +test('$ref() creates a reference', function (t) { + const schema = ms.$ref('#/definitions/address') + assert.deepEqual(schema, {$ref: '#/definitions/address'}) +}) + +test('definitions() adds definitions', function (t) { + const schema = ms.definitions({ + check: ms.boolean() + }).strictObj({ + foo: ms.required.$ref('#/definitions/check') + }) + + assert.deepEqual(schema, { + definitions: { + check: {type: 'boolean'} + }, + type: 'object', + properties: { + foo: {$ref: '#/definitions/check'} + }, + required: [ 'foo' ], + additionalProperties: false + }) +}) + +test('$id() creates an id', function (t) { + const schema = ms.$id('#node').obj({ + children: ms.$ref('#node') + }) + assert.deepEqual(schema, { + $id: '#node', + type: 'object', + properties: { + children: {$ref: '#node'} + } + }) +}) + +test('anyOf() creates an anyOf object', function (t) { + const schema = ms.anyOf( + ms.obj({ + foo: 'string' + }), + ms.obj({ + bar: 'string' + }) + ) + + assert.deepEqual(schema, { + anyOf: [{ + type: 'object', + properties: {foo: {type: 'string'}} + }, { + type: 'object', + properties: {bar: {type: 'string'}} + }] + }) +}) + +test('anyOf() works with strings', function (t) { + const schema = ms.anyOf('string', 'boolean') + + assert.deepEqual(schema, { + anyOf: [ + {type: 'string'}, + {type: 'boolean'} + ] + }) +}) + +test('anyOf() works when passed an array', function (t) { + const schema = ms.anyOf([ + ms.obj({ + foo: 'string' + }), + ms.obj({ + bar: 'string' + }) + ]) + + assert.deepEqual(schema, { + anyOf: [{ + type: 'object', + properties: {foo: {type: 'string'}} + }, { + type: 'object', + properties: {bar: {type: 'string'}} + }] + }) +}) + +test('anyOf() works with required', function (t) { + const schema = ms.obj({ + any: ms.required.anyOf( + ms.obj({ + foo: ms.required.string() + }), + ms.obj({ + bar: 'string' + }) + ) + }) + + assert.deepEqual(schema, { + type: 'object', + required: ['any'], + properties: { + any: { + anyOf: [{ + type: 'object', + required: ['foo'], + properties: {foo: {type: 'string'}} + }, { + type: 'object', + properties: {bar: {type: 'string'}} + }] + } + } + }) +}) + +test('oneOf() creates a oneOf object', function (t) { + const schema = ms.oneOf( + ms.obj({ + foo: 'string' + }), + ms.obj({ + bar: 'string' + }) + ) + + assert.deepEqual(schema, { + oneOf: [{ + type: 'object', + properties: {foo: {type: 'string'}} + }, { + type: 'object', + properties: {bar: {type: 'string'}} + }] + }) +}) + +test('oneOf() works with strings', function (t) { + const schema = ms.oneOf('number', 'boolean') + + assert.deepEqual(schema, { + oneOf: [ + {type: 'number'}, + {type: 'boolean'} + ] + }) +}) + +test('allOf() creates a allOf object', function (t) { + const schema = ms.allOf( + ms.obj({ + foo: 'string' + }), + ms.obj({ + bar: 'string' + }) + ) + + assert.deepEqual(schema, { + allOf: [{ + type: 'object', + properties: {foo: {type: 'string'}} + }, { + type: 'object', + properties: {bar: {type: 'string'}} + }] + }) +}) + test('chaining creates a chain object', function (t) { const chain = ms.required assert.ok(chain.strictObj instanceof Function)