UNPKG

5.59 kBJavaScriptView Raw
1"use strict";
2// Copyright IBM Corp. and LoopBack contributors 2020. All Rights Reserved.
3// Node module: @loopback/rest
4// This file is licensed under the MIT License.
5// License text available at https://opensource.org/licenses/MIT
6Object.defineProperty(exports, "__esModule", { value: true });
7exports.ConsolidationEnhancer = void 0;
8const tslib_1 = require("tslib");
9const core_1 = require("@loopback/core");
10const openapi_v3_1 = require("@loopback/openapi-v3");
11const debug_1 = tslib_1.__importDefault(require("debug"));
12const json_schema_compare_1 = tslib_1.__importDefault(require("json-schema-compare"));
13const lodash_1 = tslib_1.__importDefault(require("lodash"));
14const debug = (0, debug_1.default)('loopback:openapi:spec-enhancer:consolidate');
15/**
16 * This enhancer consolidates schemas into `/components/schemas` and replaces
17 * instances of said schema with a $ref pointer.
18 *
19 * Please note that the title property must be set on a schema in order to be
20 * considered for consolidation.
21 *
22 * For example, with the following schema instance:
23 *
24 * ```json
25 * schema: {
26 * title: 'loopback.example',
27 * properties: {
28 * test: {
29 * type: 'string',
30 * },
31 * },
32 * }
33 * ```
34 *
35 * The consolidator will copy the schema body to
36 * `/components/schemas/loopback.example` and replace any instance of the schema
37 * with a reference to the component schema as follows:
38 *
39 * ```json
40 * schema: {
41 * $ref: '#/components/schemas/loopback.example',
42 * }
43 * ```
44 *
45 * When comparing schemas to avoid naming collisions, the description field
46 * is ignored.
47 */
48let ConsolidationEnhancer = class ConsolidationEnhancer {
49 constructor(config) {
50 var _a, _b, _c;
51 this.config = config;
52 this.name = 'consolidate';
53 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;
54 }
55 modifySpec(spec) {
56 return !this.disabled ? this.consolidateSchemaObjects(spec) : spec;
57 }
58 /**
59 * Recursively search OpenApiSpec PathsObject for SchemaObjects with title
60 * property. Moves reusable schema bodies to #/components/schemas and replace
61 * with json pointer. It handles title collisions with schema body comparision.
62 */
63 consolidateSchemaObjects(spec) {
64 // use 'paths' as crawl root
65 this.recursiveWalk(spec.paths, ['paths'], spec);
66 return spec;
67 }
68 recursiveWalk(rootSchema, parentPath, spec) {
69 if (this.isTraversable(rootSchema)) {
70 Object.entries(rootSchema).forEach(([key, subSchema]) => {
71 if (subSchema) {
72 this.recursiveWalk(subSchema, parentPath.concat(key), spec);
73 this.processSchema(subSchema, parentPath.concat(key), spec);
74 }
75 });
76 }
77 }
78 /**
79 * Carry out schema consolidation after tree traversal. If 'title' property
80 * set then we consider current schema for consolidation. SchemaObjects with
81 * properties (and title set) are moved to #/components/schemas/<title> and
82 * replaced with ReferenceObject.
83 *
84 * Features:
85 * - name collision protection
86 *
87 * @param schema - current schema element to process
88 * @param parentPath - path object to parent
89 * @param spec - subject OpenApi specification
90 */
91 processSchema(schema, parentPath, spec) {
92 const schemaObj = this.ifConsolidationCandidate(schema);
93 if (schemaObj) {
94 // name collison protection
95 let instanceNo = 1;
96 let title = schemaObj.title;
97 let refSchema = this.getRefSchema(title, spec);
98 while (refSchema &&
99 !(0, json_schema_compare_1.default)(schemaObj, refSchema, {
100 ignore: ['description'],
101 })) {
102 title = `${schemaObj.title}${instanceNo++}`;
103 refSchema = this.getRefSchema(title, spec);
104 }
105 if (!refSchema) {
106 debug('Creating new component $ref with schema %j', schema);
107 this.patchRef(title, schema, spec);
108 }
109 debug('Creating link to $ref %j', title);
110 this.patchPath(title, parentPath, spec);
111 }
112 }
113 getRefSchema(name, spec) {
114 const schema = lodash_1.default.get(spec, ['components', 'schemas', name]);
115 return schema;
116 }
117 patchRef(name, value, spec) {
118 lodash_1.default.set(spec, ['components', 'schemas', name], value);
119 }
120 patchPath(name, path, spec) {
121 const patch = {
122 $ref: `#/components/schemas/${name}`,
123 };
124 lodash_1.default.set(spec, path, patch);
125 }
126 ifConsolidationCandidate(schema) {
127 // use title to discriminate references
128 return (0, openapi_v3_1.isSchemaObject)(schema) && schema.properties && schema.title
129 ? schema
130 : undefined;
131 }
132 isTraversable(schema) {
133 return schema && typeof schema === 'object' ? true : false;
134 }
135};
136exports.ConsolidationEnhancer = ConsolidationEnhancer;
137exports.ConsolidationEnhancer = ConsolidationEnhancer = tslib_1.__decorate([
138 (0, core_1.injectable)(openapi_v3_1.asSpecEnhancer, { scope: core_1.BindingScope.SINGLETON }),
139 tslib_1.__param(0, (0, core_1.inject)(core_1.CoreBindings.APPLICATION_CONFIG, { optional: true })),
140 tslib_1.__metadata("design:paramtypes", [Object])
141], ConsolidationEnhancer);
142//# sourceMappingURL=consolidate.spec-enhancer.js.map
\No newline at end of file