From c8fc00e17863f6c2c33e5cd57473365f52986e7f Mon Sep 17 00:00:00 2001 From: shimks Date: Thu, 26 Apr 2018 14:01:16 -0400 Subject: [PATCH] chore(repository-json-schema): replace JsonDefinition with JSONSchema6 --- packages/openapi-v3/src/json-to-schema.ts | 47 ++++++------ .../test/unit/json-to-schema.unit.ts | 75 ++++++++----------- packages/repository-json-schema/package.json | 4 +- .../src/build-schema.ts | 41 ++++------ packages/repository-json-schema/src/index.ts | 3 + .../integration/build-schema.integration.ts | 10 ++- .../test/unit/build-schema.unit.ts | 6 +- 7 files changed, 82 insertions(+), 104 deletions(-) diff --git a/packages/openapi-v3/src/json-to-schema.ts b/packages/openapi-v3/src/json-to-schema.ts index 2fafad3bbb77..8b16f01051cf 100644 --- a/packages/openapi-v3/src/json-to-schema.ts +++ b/packages/openapi-v3/src/json-to-schema.ts @@ -3,11 +3,15 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {JsonDefinition} from '@loopback/repository-json-schema'; +import {JSONSchema} from '@loopback/repository-json-schema'; import {SchemaObject} from '@loopback/openapi-v3-types'; import * as _ from 'lodash'; -export function jsonToSchemaObject(json: JsonDefinition): SchemaObject { +/** + * Converts JSON Schemas into a SchemaObject + * @param json JSON Schema to convert from + */ +export function jsonToSchemaObject(json: JSONSchema): SchemaObject { const result: SchemaObject = {}; const propsToIgnore = [ 'anyOf', @@ -36,42 +40,27 @@ export function jsonToSchemaObject(json: JsonDefinition): SchemaObject { } case 'definitions': { result.definitions = _.mapValues(json.definitions, def => - jsonToSchemaObject(def), + jsonToSchemaObject(jsonOrBooleanToJSON(def)), ); break; } case 'properties': { result.properties = _.mapValues(json.properties, item => - jsonToSchemaObject(item), + jsonToSchemaObject(jsonOrBooleanToJSON(item)), ); break; } case 'additionalProperties': { if (typeof json.additionalProperties !== 'boolean') { result.additionalProperties = jsonToSchemaObject( - json.additionalProperties as JsonDefinition, + json.additionalProperties!, ); } break; } case 'items': { const items = Array.isArray(json.items) ? json.items[0] : json.items; - result.items = jsonToSchemaObject(items as JsonDefinition); - break; - } - case 'enum': { - const newEnum = []; - const primitives = ['string', 'number', 'boolean']; - for (const element of json.enum!) { - if (primitives.includes(typeof element) || element === null) { - newEnum.push(element); - } else { - // if element is JsonDefinition, convert to SchemaObject - newEnum.push(jsonToSchemaObject(element as JsonDefinition)); - } - } - result.enum = newEnum; - + result.items = jsonToSchemaObject(jsonOrBooleanToJSON(items!)); break; } case '$ref': { @@ -82,7 +71,7 @@ export function jsonToSchemaObject(json: JsonDefinition): SchemaObject { break; } default: { - result[property] = json[property as keyof JsonDefinition]; + result[property] = json[property as keyof JSONSchema]; break; } } @@ -90,3 +79,17 @@ export function jsonToSchemaObject(json: JsonDefinition): SchemaObject { return result; } + +/** + * Helper function used to interpret boolean values as JSON Schemas. + * See http://json-schema.org/draft-06/json-schema-release-notes.html + * @param jsonOrBool converts boolean values into their representative JSON Schemas + * @returns JSONSchema + */ +export function jsonOrBooleanToJSON(jsonOrBool: boolean | JSONSchema) { + if (typeof jsonOrBool === 'object') { + return jsonOrBool; + } else { + return jsonOrBool ? {} : {not: {}}; + } +} diff --git a/packages/openapi-v3/test/unit/json-to-schema.unit.ts b/packages/openapi-v3/test/unit/json-to-schema.unit.ts index 99240459ac9c..0ae57c1faab6 100644 --- a/packages/openapi-v3/test/unit/json-to-schema.unit.ts +++ b/packages/openapi-v3/test/unit/json-to-schema.unit.ts @@ -4,23 +4,23 @@ // License text available at https://opensource.org/licenses/MIT import {expect} from '@loopback/testlab'; -import {JsonDefinition} from '@loopback/repository-json-schema'; import {SchemaObject} from '@loopback/openapi-v3-types'; -import {jsonToSchemaObject} from '../..'; +import {jsonToSchemaObject, jsonOrBooleanToJSON} from '../..'; +import {JSONSchema} from '@loopback/repository-json-schema'; describe('jsonToSchemaObject', () => { it('does nothing when given an empty object', () => { expect({}).to.eql({}); }); - const typeDef: JsonDefinition = {type: ['string', 'number']}; + const typeDef: JSONSchema = {type: ['string', 'number']}; const expectedType: SchemaObject = {type: 'string'}; it('converts type', () => { propertyConversionTest(typeDef, expectedType); }); it('ignores non-compatible JSON schema properties', () => { - const nonCompatibleDef: JsonDefinition = { + const nonCompatibleDef = { anyOf: [], oneOf: [], additionalItems: { @@ -34,7 +34,7 @@ describe('jsonToSchemaObject', () => { }); it('converts allOf', () => { - const allOfDef: JsonDefinition = { + const allOfDef: JSONSchema = { allOf: [typeDef, typeDef], }; const expectedAllOf: SchemaObject = { @@ -44,7 +44,7 @@ describe('jsonToSchemaObject', () => { }); it('converts definitions', () => { - const definitionsDef: JsonDefinition = { + const definitionsDef: JSONSchema = { definitions: {foo: typeDef, bar: typeDef}, }; const expectedDef: SchemaObject = { @@ -54,7 +54,7 @@ describe('jsonToSchemaObject', () => { }); it('converts properties', () => { - const propertyDef: JsonDefinition = { + const propertyDef: JSONSchema = { properties: { foo: typeDef, }, @@ -68,8 +68,8 @@ describe('jsonToSchemaObject', () => { }); context('additionalProperties', () => { - it('is converted properly when the type is JsonDefinition', () => { - const additionalDef: JsonDefinition = { + it('is converted properly when the type is JSONSchema', () => { + const additionalDef: JSONSchema = { additionalProperties: typeDef, }; const expectedAdditional: SchemaObject = { @@ -79,7 +79,7 @@ describe('jsonToSchemaObject', () => { }); it('is converted properly when it is "false"', () => { - const noAdditionalDef: JsonDefinition = { + const noAdditionalDef: JSONSchema = { additionalProperties: false, }; const expectedDef: SchemaObject = {}; @@ -88,7 +88,7 @@ describe('jsonToSchemaObject', () => { }); it('converts items', () => { - const itemsDef: JsonDefinition = { + const itemsDef: JSONSchema = { type: 'array', items: typeDef, }; @@ -99,40 +99,8 @@ describe('jsonToSchemaObject', () => { propertyConversionTest(itemsDef, expectedItems); }); - context('enum', () => { - it('is converted properly when the type is primitive', () => { - const enumStringDef: JsonDefinition = { - enum: ['foo', 'bar'], - }; - const expectedStringDef: SchemaObject = { - enum: ['foo', 'bar'], - }; - propertyConversionTest(enumStringDef, expectedStringDef); - }); - - it('is converted properly when it is null', () => { - const enumNullDef: JsonDefinition = { - enum: [null, null], - }; - const expectedNullDef: JsonDefinition = { - enum: [null, null], - }; - propertyConversionTest(enumNullDef, expectedNullDef); - }); - - it('is converted properly when the type is complex', () => { - const enumCustomDef: JsonDefinition = { - enum: [typeDef, typeDef], - }; - const expectedCustomDef: SchemaObject = { - enum: [expectedType, expectedType], - }; - propertyConversionTest(enumCustomDef, expectedCustomDef); - }); - }); - it('retains given properties in the conversion', () => { - const inputDef: JsonDefinition = { + const inputDef: JSONSchema = { title: 'foo', type: 'object', properties: { @@ -171,3 +139,22 @@ describe('jsonToSchemaObject', () => { expect(jsonToSchemaObject(property)).to.deepEqual(expected); } }); + +describe('jsonOrBooleanToJson', () => { + it('converts true to {}', () => { + expect(jsonOrBooleanToJSON(true)).to.eql({}); + }); + + it('converts false to {}', () => { + expect(jsonOrBooleanToJSON(false)).to.eql({not: {}}); + }); + + it('makes no changes to JSON Schema', () => { + const jsonSchema: JSONSchema = { + properties: { + number: {type: 'number'}, + }, + }; + expect(jsonOrBooleanToJSON(jsonSchema)).to.eql(jsonSchema); + }); +}); diff --git a/packages/repository-json-schema/package.json b/packages/repository-json-schema/package.json index b3086b2b3819..8e8fa19945e4 100644 --- a/packages/repository-json-schema/package.json +++ b/packages/repository-json-schema/package.json @@ -26,13 +26,11 @@ "dependencies": { "@loopback/context": "^0.8.1", "@loopback/repository": "^0.8.1", - "lodash": "^4.17.5", - "typescript-json-schema": "^0.22.0" + "@types/json-schema": "^6.0.1" }, "devDependencies": { "@loopback/build": "^0.6.0", "@loopback/testlab": "^0.8.0", - "@types/lodash": "^4.14.106", "@types/node": "^8.10.4" }, "files": [ diff --git a/packages/repository-json-schema/src/build-schema.ts b/packages/repository-json-schema/src/build-schema.ts index eb3d890f9fa1..e3a859d98d5e 100644 --- a/packages/repository-json-schema/src/build-schema.ts +++ b/packages/repository-json-schema/src/build-schema.ts @@ -8,37 +8,22 @@ import { PropertyDefinition, ModelDefinition, } from '@loopback/repository'; -import {includes} from 'lodash'; -import {Definition, PrimitiveType} from 'typescript-json-schema'; import {MetadataInspector, MetadataAccessor} from '@loopback/context'; +import { + JSONSchema6 as JSONSchema, + JSONSchema6TypeName as JSONSchemaTypeName, +} from 'json-schema'; -export const JSON_SCHEMA_KEY = MetadataAccessor.create( +export const JSON_SCHEMA_KEY = MetadataAccessor.create( 'loopback:json-schema', ); -/** - * Type definition for JSON Schema - */ -export interface JsonDefinition extends Definition { - allOf?: JsonDefinition[]; - oneOf?: JsonDefinition[]; - anyOf?: JsonDefinition[]; - items?: JsonDefinition | JsonDefinition[]; - additionalItems?: { - anyOf: JsonDefinition[]; - }; - enum?: PrimitiveType[] | JsonDefinition[]; - additionalProperties?: JsonDefinition | boolean; - definitions?: {[definition: string]: JsonDefinition}; - properties?: {[property: string]: JsonDefinition}; -} - /** * Gets the JSON Schema of a TypeScript model/class by seeing if one exists * in a cache. If not, one is generated and then cached. * @param ctor Contructor of class to get JSON Schema from */ -export function getJsonSchema(ctor: Function): JsonDefinition { +export function getJsonSchema(ctor: Function): JSONSchema { // NOTE(shimks) currently impossible to dynamically update const jsonSchema = MetadataInspector.getClassMetadata(JSON_SCHEMA_KEY, ctor); if (jsonSchema) { @@ -82,16 +67,18 @@ export function stringTypeToWrapper(type: string): Function { * @param ctor Constructor */ export function isComplexType(ctor: Function) { - return !includes([String, Number, Boolean, Object, Function], ctor); + return !([String, Number, Boolean, Object, Function] as Function[]).includes( + ctor, + ); } /** * Converts property metadata into a JSON property definition * @param meta */ -export function metaToJsonProperty(meta: PropertyDefinition): JsonDefinition { +export function metaToJsonProperty(meta: PropertyDefinition): JSONSchema { let ctor = meta.type as string | Function; - let def: JsonDefinition = {}; + let def: JSONSchema = {}; // errors out if @property.array() is not used on a property of array if (ctor === Array) { @@ -104,7 +91,7 @@ export function metaToJsonProperty(meta: PropertyDefinition): JsonDefinition { const propDef = isComplexType(ctor) ? {$ref: `#/definitions/${ctor.name}`} - : {type: ctor.name.toLowerCase()}; + : {type: ctor.name.toLowerCase()}; if (meta.array) { def.type = 'array'; @@ -124,9 +111,9 @@ export function metaToJsonProperty(meta: PropertyDefinition): JsonDefinition { * reflection API * @param ctor Constructor of class to convert from */ -export function modelToJsonSchema(ctor: Function): JsonDefinition { +export function modelToJsonSchema(ctor: Function): JSONSchema { const meta: ModelDefinition | {} = ModelMetadataHelper.getModelMetadata(ctor); - const result: JsonDefinition = {}; + const result: JSONSchema = {}; // returns an empty object if metadata is an empty object if (!(meta instanceof ModelDefinition)) { diff --git a/packages/repository-json-schema/src/index.ts b/packages/repository-json-schema/src/index.ts index 96e9407c4901..de7f7b83fc86 100644 --- a/packages/repository-json-schema/src/index.ts +++ b/packages/repository-json-schema/src/index.ts @@ -4,3 +4,6 @@ // License text available at https://opensource.org/licenses/MIT export * from './build-schema'; + +import {JSONSchema6 as JSONSchema} from 'json-schema'; +export {JSONSchema}; diff --git a/packages/repository-json-schema/test/integration/build-schema.integration.ts b/packages/repository-json-schema/test/integration/build-schema.integration.ts index 67d29de505e2..e1dc069e62fb 100644 --- a/packages/repository-json-schema/test/integration/build-schema.integration.ts +++ b/packages/repository-json-schema/test/integration/build-schema.integration.ts @@ -4,10 +4,14 @@ // License text available at https://opensource.org/licenses/MIT import {model, property} from '@loopback/repository'; -import {modelToJsonSchema} from '../../src/build-schema'; +import { + modelToJsonSchema, + JSON_SCHEMA_KEY, + getJsonSchema, + JSONSchema, +} from '../..'; import {expect} from '@loopback/testlab'; import {MetadataInspector} from '@loopback/context'; -import {JSON_SCHEMA_KEY, getJsonSchema} from '../../index'; describe('build-schema', () => { describe('modelToJsonSchema', () => { @@ -339,7 +343,7 @@ describe('build-schema', () => { class TestModel { @property() foo: number; } - const cachedSchema = { + const cachedSchema: JSONSchema = { properties: { cachedProperty: { type: 'string', diff --git a/packages/repository-json-schema/test/unit/build-schema.unit.ts b/packages/repository-json-schema/test/unit/build-schema.unit.ts index 1c9740b0df28..0a2ada413ebe 100644 --- a/packages/repository-json-schema/test/unit/build-schema.unit.ts +++ b/packages/repository-json-schema/test/unit/build-schema.unit.ts @@ -1,9 +1,5 @@ import {expect} from '@loopback/testlab'; -import { - isComplexType, - stringTypeToWrapper, - metaToJsonProperty, -} from '../../index'; +import {isComplexType, stringTypeToWrapper, metaToJsonProperty} from '../..'; describe('build-schema', () => { describe('stringTypeToWrapper', () => {