@loopback/rest
Version:
Expose controllers as REST endpoints and route REST API requests to controller methods
142 lines • 5.59 kB
JavaScript
// Copyright IBM Corp. and LoopBack contributors 2020. All Rights Reserved.
// Node module: @loopback/rest
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConsolidationEnhancer = void 0;
const tslib_1 = require("tslib");
const core_1 = require("@loopback/core");
const openapi_v3_1 = require("@loopback/openapi-v3");
const debug_1 = tslib_1.__importDefault(require("debug"));
const json_schema_compare_1 = tslib_1.__importDefault(require("json-schema-compare"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const debug = (0, debug_1.default)('loopback:openapi:spec-enhancer:consolidate');
/**
* This enhancer consolidates schemas into `/components/schemas` and replaces
* instances of said schema with a $ref pointer.
*
* Please note that the title property must be set on a schema in order to be
* considered for consolidation.
*
* For example, with the following schema instance:
*
* ```json
* schema: {
* title: 'loopback.example',
* properties: {
* test: {
* type: 'string',
* },
* },
* }
* ```
*
* The consolidator will copy the schema body to
* `/components/schemas/loopback.example` and replace any instance of the schema
* with a reference to the component schema as follows:
*
* ```json
* schema: {
* $ref: '#/components/schemas/loopback.example',
* }
* ```
*
* When comparing schemas to avoid naming collisions, the description field
* is ignored.
*/
let ConsolidationEnhancer = class ConsolidationEnhancer {
constructor(config) {
var _a, _b, _c;
this.config = config;
this.name = 'consolidate';
this.disabled = ((_c = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.rest) === null || _b === void 0 ? void 0 : _b.openApiSpec) === null || _c === void 0 ? void 0 : _c.consolidate) === false;
}
modifySpec(spec) {
return !this.disabled ? this.consolidateSchemaObjects(spec) : spec;
}
/**
* Recursively search OpenApiSpec PathsObject for SchemaObjects with title
* property. Moves reusable schema bodies to #/components/schemas and replace
* with json pointer. It handles title collisions with schema body comparision.
*/
consolidateSchemaObjects(spec) {
// use 'paths' as crawl root
this.recursiveWalk(spec.paths, ['paths'], spec);
return spec;
}
recursiveWalk(rootSchema, parentPath, spec) {
if (this.isTraversable(rootSchema)) {
Object.entries(rootSchema).forEach(([key, subSchema]) => {
if (subSchema) {
this.recursiveWalk(subSchema, parentPath.concat(key), spec);
this.processSchema(subSchema, parentPath.concat(key), spec);
}
});
}
}
/**
* Carry out schema consolidation after tree traversal. If 'title' property
* set then we consider current schema for consolidation. SchemaObjects with
* properties (and title set) are moved to #/components/schemas/<title> and
* replaced with ReferenceObject.
*
* Features:
* - name collision protection
*
* @param schema - current schema element to process
* @param parentPath - path object to parent
* @param spec - subject OpenApi specification
*/
processSchema(schema, parentPath, spec) {
const schemaObj = this.ifConsolidationCandidate(schema);
if (schemaObj) {
// name collison protection
let instanceNo = 1;
let title = schemaObj.title;
let refSchema = this.getRefSchema(title, spec);
while (refSchema &&
!(0, json_schema_compare_1.default)(schemaObj, refSchema, {
ignore: ['description'],
})) {
title = `${schemaObj.title}${instanceNo++}`;
refSchema = this.getRefSchema(title, spec);
}
if (!refSchema) {
debug('Creating new component $ref with schema %j', schema);
this.patchRef(title, schema, spec);
}
debug('Creating link to $ref %j', title);
this.patchPath(title, parentPath, spec);
}
}
getRefSchema(name, spec) {
const schema = lodash_1.default.get(spec, ['components', 'schemas', name]);
return schema;
}
patchRef(name, value, spec) {
lodash_1.default.set(spec, ['components', 'schemas', name], value);
}
patchPath(name, path, spec) {
const patch = {
$ref: `#/components/schemas/${name}`,
};
lodash_1.default.set(spec, path, patch);
}
ifConsolidationCandidate(schema) {
// use title to discriminate references
return (0, openapi_v3_1.isSchemaObject)(schema) && schema.properties && schema.title
? schema
: undefined;
}
isTraversable(schema) {
return schema && typeof schema === 'object' ? true : false;
}
};
exports.ConsolidationEnhancer = ConsolidationEnhancer;
exports.ConsolidationEnhancer = ConsolidationEnhancer = tslib_1.__decorate([
(0, core_1.injectable)(openapi_v3_1.asSpecEnhancer, { scope: core_1.BindingScope.SINGLETON }),
tslib_1.__param(0, (0, core_1.inject)(core_1.CoreBindings.APPLICATION_CONFIG, { optional: true })),
tslib_1.__metadata("design:paramtypes", [Object])
], ConsolidationEnhancer);
//# sourceMappingURL=consolidate.spec-enhancer.js.map
;