1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.SwaggerExplorer = void 0;
|
4 | const common_1 = require("@nestjs/common");
|
5 | const constants_1 = require("@nestjs/common/constants");
|
6 | const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
|
7 | const metadata_scanner_1 = require("@nestjs/core/metadata-scanner");
|
8 | const route_path_factory_1 = require("@nestjs/core/router/route-path-factory");
|
9 | const lodash_1 = require("lodash");
|
10 | const pathToRegexp = require("path-to-regexp");
|
11 | const constants_2 = require("./constants");
|
12 | const api_exclude_controller_explorer_1 = require("./explorers/api-exclude-controller.explorer");
|
13 | const api_exclude_endpoint_explorer_1 = require("./explorers/api-exclude-endpoint.explorer");
|
14 | const api_extra_models_explorer_1 = require("./explorers/api-extra-models.explorer");
|
15 | const api_headers_explorer_1 = require("./explorers/api-headers.explorer");
|
16 | const api_operation_explorer_1 = require("./explorers/api-operation.explorer");
|
17 | const api_parameters_explorer_1 = require("./explorers/api-parameters.explorer");
|
18 | const api_response_explorer_1 = require("./explorers/api-response.explorer");
|
19 | const api_security_explorer_1 = require("./explorers/api-security.explorer");
|
20 | const api_use_tags_explorer_1 = require("./explorers/api-use-tags.explorer");
|
21 | const mimetype_content_wrapper_1 = require("./services/mimetype-content-wrapper");
|
22 | const is_body_parameter_util_1 = require("./utils/is-body-parameter.util");
|
23 | const merge_and_uniq_util_1 = require("./utils/merge-and-uniq.util");
|
24 | class SwaggerExplorer {
|
25 | constructor(schemaObjectFactory) {
|
26 | this.schemaObjectFactory = schemaObjectFactory;
|
27 | this.mimetypeContentWrapper = new mimetype_content_wrapper_1.MimetypeContentWrapper();
|
28 | this.metadataScanner = new metadata_scanner_1.MetadataScanner();
|
29 | this.schemas = {};
|
30 | this.operationIdFactory = (controllerKey, methodKey) => controllerKey ? `${controllerKey}_${methodKey}` : methodKey;
|
31 | }
|
32 | exploreController(wrapper, applicationConfig, modulePath, globalPrefix, operationIdFactory) {
|
33 | this.routePathFactory = new route_path_factory_1.RoutePathFactory(applicationConfig);
|
34 | if (operationIdFactory) {
|
35 | this.operationIdFactory = operationIdFactory;
|
36 | }
|
37 | const { instance, metatype } = wrapper;
|
38 | const prototype = Object.getPrototypeOf(instance);
|
39 | const documentResolvers = {
|
40 | root: [
|
41 | this.exploreRoutePathAndMethod,
|
42 | api_operation_explorer_1.exploreApiOperationMetadata,
|
43 | api_parameters_explorer_1.exploreApiParametersMetadata.bind(null, this.schemas)
|
44 | ],
|
45 | security: [api_security_explorer_1.exploreApiSecurityMetadata],
|
46 | tags: [api_use_tags_explorer_1.exploreApiTagsMetadata],
|
47 | responses: [api_response_explorer_1.exploreApiResponseMetadata.bind(null, this.schemas)]
|
48 | };
|
49 | return this.generateDenormalizedDocument(metatype, prototype, instance, documentResolvers, applicationConfig, modulePath, globalPrefix);
|
50 | }
|
51 | getSchemas() {
|
52 | return this.schemas;
|
53 | }
|
54 | generateDenormalizedDocument(metatype, prototype, instance, documentResolvers, applicationConfig, modulePath, globalPrefix) {
|
55 | const self = this;
|
56 | const excludeController = api_exclude_controller_explorer_1.exploreApiExcludeControllerMetadata(metatype);
|
57 | if (excludeController) {
|
58 | return [];
|
59 | }
|
60 | const globalMetadata = this.exploreGlobalMetadata(metatype);
|
61 | const ctrlExtraModels = api_extra_models_explorer_1.exploreGlobalApiExtraModelsMetadata(metatype);
|
62 | this.registerExtraModels(ctrlExtraModels);
|
63 | const denormalizedPaths = this.metadataScanner.scanFromPrototype(instance, prototype, (name) => {
|
64 | const targetCallback = prototype[name];
|
65 | const excludeEndpoint = api_exclude_endpoint_explorer_1.exploreApiExcludeEndpointMetadata(instance, prototype, targetCallback);
|
66 | if (excludeEndpoint && excludeEndpoint.disable) {
|
67 | return;
|
68 | }
|
69 | const ctrlExtraModels = api_extra_models_explorer_1.exploreApiExtraModelsMetadata(instance, prototype, targetCallback);
|
70 | this.registerExtraModels(ctrlExtraModels);
|
71 | const methodMetadata = lodash_1.mapValues(documentResolvers, (explorers) => explorers.reduce((metadata, fn) => {
|
72 | const exploredMetadata = fn.call(self, instance, prototype, targetCallback, metatype, globalPrefix, modulePath, applicationConfig);
|
73 | if (!exploredMetadata) {
|
74 | return metadata;
|
75 | }
|
76 | if (!lodash_1.isArray(exploredMetadata)) {
|
77 | if (Array.isArray(metadata)) {
|
78 | return metadata.map((item) => (Object.assign(Object.assign({}, item), exploredMetadata)));
|
79 | }
|
80 | return Object.assign(Object.assign({}, metadata), exploredMetadata);
|
81 | }
|
82 | return lodash_1.isArray(metadata)
|
83 | ? [...metadata, ...exploredMetadata]
|
84 | : exploredMetadata;
|
85 | }, {}));
|
86 | if (Array.isArray(methodMetadata.root)) {
|
87 | return methodMetadata.root.map((endpointMetadata) => {
|
88 | endpointMetadata = Object.assign(Object.assign({}, methodMetadata), { root: endpointMetadata });
|
89 | const mergedMethodMetadata = this.mergeMetadata(globalMetadata, lodash_1.omitBy(endpointMetadata, lodash_1.isEmpty));
|
90 | return this.migrateOperationSchema(Object.assign(Object.assign({ responses: {} }, lodash_1.omit(globalMetadata, 'chunks')), mergedMethodMetadata), prototype, targetCallback);
|
91 | });
|
92 | }
|
93 | const mergedMethodMetadata = this.mergeMetadata(globalMetadata, lodash_1.omitBy(methodMetadata, lodash_1.isEmpty));
|
94 | return [
|
95 | this.migrateOperationSchema(Object.assign(Object.assign({ responses: {} }, lodash_1.omit(globalMetadata, 'chunks')), mergedMethodMetadata), prototype, targetCallback)
|
96 | ];
|
97 | });
|
98 | return lodash_1.flatten(denormalizedPaths).filter((path) => { var _a; return (_a = path.root) === null || _a === void 0 ? void 0 : _a.path; });
|
99 | }
|
100 | exploreGlobalMetadata(metatype) {
|
101 | const globalExplorers = [
|
102 | api_use_tags_explorer_1.exploreGlobalApiTagsMetadata,
|
103 | api_security_explorer_1.exploreGlobalApiSecurityMetadata,
|
104 | api_response_explorer_1.exploreGlobalApiResponseMetadata.bind(null, this.schemas),
|
105 | api_headers_explorer_1.exploreGlobalApiHeaderMetadata
|
106 | ];
|
107 | const globalMetadata = globalExplorers
|
108 | .map((explorer) => explorer.call(explorer, metatype))
|
109 | .filter((val) => !shared_utils_1.isUndefined(val))
|
110 | .reduce((curr, next) => {
|
111 | if (next.depth) {
|
112 | return Object.assign(Object.assign({}, curr), { chunks: (curr.chunks || []).concat(next) });
|
113 | }
|
114 | return Object.assign(Object.assign({}, curr), next);
|
115 | }, {});
|
116 | return globalMetadata;
|
117 | }
|
118 | exploreRoutePathAndMethod(instance, prototype, method, metatype, globalPrefix, modulePath, applicationConfig) {
|
119 | const methodPath = Reflect.getMetadata(constants_1.PATH_METADATA, method);
|
120 | if (shared_utils_1.isUndefined(methodPath)) {
|
121 | return undefined;
|
122 | }
|
123 | const requestMethod = Reflect.getMetadata(constants_1.METHOD_METADATA, method);
|
124 | const methodVersion = Reflect.getMetadata(constants_1.VERSION_METADATA, method);
|
125 | const controllerVersion = this.getVersionMetadata(metatype, applicationConfig.getVersioning());
|
126 | const allRoutePaths = this.routePathFactory.create({
|
127 | methodPath,
|
128 | methodVersion,
|
129 | modulePath,
|
130 | globalPrefix,
|
131 | controllerVersion,
|
132 | ctrlPath: this.reflectControllerPath(metatype),
|
133 | versioningOptions: applicationConfig.getVersioning()
|
134 | }, requestMethod);
|
135 | return allRoutePaths.map((routePath) => {
|
136 | const fullPath = this.validateRoutePath(routePath);
|
137 | const apiExtension = Reflect.getMetadata(constants_2.DECORATORS.API_EXTENSION, method);
|
138 | return Object.assign({ method: common_1.RequestMethod[requestMethod].toLowerCase(), path: fullPath === '' ? '/' : fullPath, operationId: this.getOperationId(instance, method) }, apiExtension);
|
139 | });
|
140 | }
|
141 | getOperationId(instance, method) {
|
142 | var _a;
|
143 | return this.operationIdFactory(((_a = instance.constructor) === null || _a === void 0 ? void 0 : _a.name) || '', method.name);
|
144 | }
|
145 | reflectControllerPath(metatype) {
|
146 | return Reflect.getMetadata(constants_1.PATH_METADATA, metatype);
|
147 | }
|
148 | validateRoutePath(path) {
|
149 | if (shared_utils_1.isUndefined(path)) {
|
150 | return '';
|
151 | }
|
152 | if (Array.isArray(path)) {
|
153 | path = lodash_1.head(path);
|
154 | }
|
155 | let pathWithParams = '';
|
156 | for (const item of pathToRegexp.parse(path)) {
|
157 | pathWithParams += shared_utils_1.isString(item) ? item : `${item.prefix}{${item.name}}`;
|
158 | }
|
159 | return pathWithParams === '/' ? '' : shared_utils_1.addLeadingSlash(pathWithParams);
|
160 | }
|
161 | mergeMetadata(globalMetadata, methodMetadata) {
|
162 | if (methodMetadata.root && !methodMetadata.root.parameters) {
|
163 | methodMetadata.root.parameters = [];
|
164 | }
|
165 | const deepMerge = (metadata) => (value, key) => {
|
166 | if (!metadata[key]) {
|
167 | return value;
|
168 | }
|
169 | const globalValue = metadata[key];
|
170 | if (metadata.depth) {
|
171 | return this.deepMergeMetadata(globalValue, value, metadata.depth);
|
172 | }
|
173 | return this.mergeValues(globalValue, value);
|
174 | };
|
175 | if (globalMetadata.chunks) {
|
176 | const { chunks } = globalMetadata;
|
177 | chunks.forEach((chunk) => {
|
178 | methodMetadata = lodash_1.mapValues(methodMetadata, deepMerge(chunk));
|
179 | });
|
180 | }
|
181 | return lodash_1.mapValues(methodMetadata, deepMerge(globalMetadata));
|
182 | }
|
183 | deepMergeMetadata(globalValue, methodValue, maxDepth, currentDepthLevel = 0) {
|
184 | if (currentDepthLevel === maxDepth) {
|
185 | return this.mergeValues(globalValue, methodValue);
|
186 | }
|
187 | return lodash_1.mapValues(methodValue, (value, key) => {
|
188 | if (key in globalValue) {
|
189 | return this.deepMergeMetadata(globalValue[key], methodValue[key], maxDepth, currentDepthLevel + 1);
|
190 | }
|
191 | return value;
|
192 | });
|
193 | }
|
194 | mergeValues(globalValue, methodValue) {
|
195 | if (!lodash_1.isArray(globalValue)) {
|
196 | return Object.assign(Object.assign({}, globalValue), methodValue);
|
197 | }
|
198 | return [...globalValue, ...methodValue];
|
199 | }
|
200 | migrateOperationSchema(document, prototype, method) {
|
201 | const parametersObject = lodash_1.get(document, 'root.parameters');
|
202 | const requestBodyIndex = (parametersObject || []).findIndex(is_body_parameter_util_1.isBodyParameter);
|
203 | if (requestBodyIndex < 0) {
|
204 | return document;
|
205 | }
|
206 | const requestBody = parametersObject[requestBodyIndex];
|
207 | parametersObject.splice(requestBodyIndex, 1);
|
208 | const classConsumes = Reflect.getMetadata(constants_2.DECORATORS.API_CONSUMES, prototype);
|
209 | const methodConsumes = Reflect.getMetadata(constants_2.DECORATORS.API_CONSUMES, method);
|
210 | let consumes = merge_and_uniq_util_1.mergeAndUniq(classConsumes, methodConsumes);
|
211 | consumes = lodash_1.isEmpty(consumes) ? ['application/json'] : consumes;
|
212 | const keysToRemove = ['schema', 'in', 'name', 'examples'];
|
213 | document.root.requestBody = Object.assign(Object.assign({}, lodash_1.omit(requestBody, keysToRemove)), this.mimetypeContentWrapper.wrap(consumes, lodash_1.pick(requestBody, ['schema', 'examples'])));
|
214 | return document;
|
215 | }
|
216 | registerExtraModels(extraModels) {
|
217 | extraModels.forEach((item) => this.schemaObjectFactory.exploreModelSchema(item, this.schemas));
|
218 | }
|
219 | getVersionMetadata(metatype, versioningOptions) {
|
220 | var _a;
|
221 | if ((versioningOptions === null || versioningOptions === void 0 ? void 0 : versioningOptions.type) === common_1.VersioningType.URI) {
|
222 | return ((_a = Reflect.getMetadata(constants_1.VERSION_METADATA, metatype)) !== null && _a !== void 0 ? _a : versioningOptions.defaultVersion);
|
223 | }
|
224 | }
|
225 | }
|
226 | exports.SwaggerExplorer = SwaggerExplorer;
|