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
|
64 | .scanFromPrototype(instance, prototype, (name) => {
|
65 | const targetCallback = prototype[name];
|
66 | const excludeEndpoint = api_exclude_endpoint_explorer_1.exploreApiExcludeEndpointMetadata(instance, prototype, targetCallback);
|
67 | if (excludeEndpoint && excludeEndpoint.disable) {
|
68 | return;
|
69 | }
|
70 | const ctrlExtraModels = api_extra_models_explorer_1.exploreApiExtraModelsMetadata(instance, prototype, targetCallback);
|
71 | this.registerExtraModels(ctrlExtraModels);
|
72 | const methodMetadata = lodash_1.mapValues(documentResolvers, (explorers) => explorers.reduce((metadata, fn) => {
|
73 | const exploredMetadata = fn.call(self, instance, prototype, targetCallback, metatype, globalPrefix, modulePath, applicationConfig);
|
74 | if (!exploredMetadata) {
|
75 | return metadata;
|
76 | }
|
77 | if (!lodash_1.isArray(exploredMetadata)) {
|
78 | return Object.assign(Object.assign({}, metadata), exploredMetadata);
|
79 | }
|
80 | return lodash_1.isArray(metadata)
|
81 | ? [...metadata, ...exploredMetadata]
|
82 | : exploredMetadata;
|
83 | }, {}));
|
84 | const mergedMethodMetadata = this.mergeMetadata(globalMetadata, lodash_1.omitBy(methodMetadata, lodash_1.isEmpty));
|
85 | return this.migrateOperationSchema(Object.assign(Object.assign({ responses: {} }, lodash_1.omit(globalMetadata, 'chunks')), mergedMethodMetadata), prototype, targetCallback);
|
86 | })
|
87 | .filter((path) => { var _a; return (_a = path.root) === null || _a === void 0 ? void 0 : _a.path; });
|
88 | return denormalizedPaths;
|
89 | }
|
90 | exploreGlobalMetadata(metatype) {
|
91 | const globalExplorers = [
|
92 | api_use_tags_explorer_1.exploreGlobalApiTagsMetadata,
|
93 | api_security_explorer_1.exploreGlobalApiSecurityMetadata,
|
94 | api_response_explorer_1.exploreGlobalApiResponseMetadata.bind(null, this.schemas),
|
95 | api_headers_explorer_1.exploreGlobalApiHeaderMetadata
|
96 | ];
|
97 | const globalMetadata = globalExplorers
|
98 | .map((explorer) => explorer.call(explorer, metatype))
|
99 | .filter((val) => !shared_utils_1.isUndefined(val))
|
100 | .reduce((curr, next) => {
|
101 | if (next.depth) {
|
102 | return Object.assign(Object.assign({}, curr), { chunks: (curr.chunks || []).concat(next) });
|
103 | }
|
104 | return Object.assign(Object.assign({}, curr), next);
|
105 | }, {});
|
106 | return globalMetadata;
|
107 | }
|
108 | exploreRoutePathAndMethod(instance, prototype, method, metatype, globalPrefix, modulePath, applicationConfig) {
|
109 | const methodPath = Reflect.getMetadata(constants_1.PATH_METADATA, method);
|
110 | if (shared_utils_1.isUndefined(methodPath)) {
|
111 | return undefined;
|
112 | }
|
113 | const requestMethod = Reflect.getMetadata(constants_1.METHOD_METADATA, method);
|
114 | const methodVersion = Reflect.getMetadata(constants_1.VERSION_METADATA, method);
|
115 | const controllerVersion = this.getVersionMetadata(metatype, applicationConfig.getVersioning());
|
116 | const allRoutePaths = this.routePathFactory.create({
|
117 | methodPath,
|
118 | methodVersion,
|
119 | modulePath,
|
120 | globalPrefix,
|
121 | controllerVersion,
|
122 | ctrlPath: this.reflectControllerPath(metatype),
|
123 | versioningOptions: applicationConfig.getVersioning()
|
124 | }, requestMethod);
|
125 | const fullPath = this.validateRoutePath(lodash_1.head(allRoutePaths));
|
126 | const apiExtension = Reflect.getMetadata(constants_2.DECORATORS.API_EXTENSION, method);
|
127 | return Object.assign({ method: common_1.RequestMethod[requestMethod].toLowerCase(), path: fullPath === '' ? '/' : fullPath, operationId: this.getOperationId(instance, method) }, apiExtension);
|
128 | }
|
129 | getOperationId(instance, method) {
|
130 | var _a;
|
131 | return this.operationIdFactory(((_a = instance.constructor) === null || _a === void 0 ? void 0 : _a.name) || '', method.name);
|
132 | }
|
133 | reflectControllerPath(metatype) {
|
134 | return Reflect.getMetadata(constants_1.PATH_METADATA, metatype);
|
135 | }
|
136 | validateRoutePath(path) {
|
137 | if (shared_utils_1.isUndefined(path)) {
|
138 | return '';
|
139 | }
|
140 | if (Array.isArray(path)) {
|
141 | path = lodash_1.head(path);
|
142 | }
|
143 | let pathWithParams = '';
|
144 | for (const item of pathToRegexp.parse(path)) {
|
145 | pathWithParams += shared_utils_1.isString(item) ? item : `${item.prefix}{${item.name}}`;
|
146 | }
|
147 | return pathWithParams === '/' ? '' : shared_utils_1.addLeadingSlash(pathWithParams);
|
148 | }
|
149 | mergeMetadata(globalMetadata, methodMetadata) {
|
150 | if (methodMetadata.root && !methodMetadata.root.parameters) {
|
151 | methodMetadata.root.parameters = [];
|
152 | }
|
153 | const deepMerge = (metadata) => (value, key) => {
|
154 | if (!metadata[key]) {
|
155 | return value;
|
156 | }
|
157 | const globalValue = metadata[key];
|
158 | if (metadata.depth) {
|
159 | return this.deepMergeMetadata(globalValue, value, metadata.depth);
|
160 | }
|
161 | return this.mergeValues(globalValue, value);
|
162 | };
|
163 | if (globalMetadata.chunks) {
|
164 | const { chunks } = globalMetadata;
|
165 | chunks.forEach((chunk) => {
|
166 | methodMetadata = lodash_1.mapValues(methodMetadata, deepMerge(chunk));
|
167 | });
|
168 | }
|
169 | return lodash_1.mapValues(methodMetadata, deepMerge(globalMetadata));
|
170 | }
|
171 | deepMergeMetadata(globalValue, methodValue, maxDepth, currentDepthLevel = 0) {
|
172 | if (currentDepthLevel === maxDepth) {
|
173 | return this.mergeValues(globalValue, methodValue);
|
174 | }
|
175 | return lodash_1.mapValues(methodValue, (value, key) => {
|
176 | if (key in globalValue) {
|
177 | return this.deepMergeMetadata(globalValue[key], methodValue[key], maxDepth, currentDepthLevel + 1);
|
178 | }
|
179 | return value;
|
180 | });
|
181 | }
|
182 | mergeValues(globalValue, methodValue) {
|
183 | if (!lodash_1.isArray(globalValue)) {
|
184 | return Object.assign(Object.assign({}, globalValue), methodValue);
|
185 | }
|
186 | return [...globalValue, ...methodValue];
|
187 | }
|
188 | migrateOperationSchema(document, prototype, method) {
|
189 | const parametersObject = lodash_1.get(document, 'root.parameters');
|
190 | const requestBodyIndex = (parametersObject || []).findIndex(is_body_parameter_util_1.isBodyParameter);
|
191 | if (requestBodyIndex < 0) {
|
192 | return document;
|
193 | }
|
194 | const requestBody = parametersObject[requestBodyIndex];
|
195 | parametersObject.splice(requestBodyIndex, 1);
|
196 | const classConsumes = Reflect.getMetadata(constants_2.DECORATORS.API_CONSUMES, prototype);
|
197 | const methodConsumes = Reflect.getMetadata(constants_2.DECORATORS.API_CONSUMES, method);
|
198 | let consumes = merge_and_uniq_util_1.mergeAndUniq(classConsumes, methodConsumes);
|
199 | consumes = lodash_1.isEmpty(consumes) ? ['application/json'] : consumes;
|
200 | const keysToRemove = ['schema', 'in', 'name', 'examples'];
|
201 | document.root.requestBody = Object.assign(Object.assign({}, lodash_1.omit(requestBody, keysToRemove)), this.mimetypeContentWrapper.wrap(consumes, lodash_1.pick(requestBody, ['schema', 'examples'])));
|
202 | return document;
|
203 | }
|
204 | registerExtraModels(extraModels) {
|
205 | extraModels.forEach((item) => this.schemaObjectFactory.exploreModelSchema(item, this.schemas));
|
206 | }
|
207 | getVersionMetadata(metatype, versioningOptions) {
|
208 | return (versioningOptions === null || versioningOptions === void 0 ? void 0 : versioningOptions.type) === common_1.VersioningType.URI
|
209 | ? Reflect.getMetadata(constants_1.VERSION_METADATA, metatype)
|
210 | : undefined;
|
211 | }
|
212 | }
|
213 | exports.SwaggerExplorer = SwaggerExplorer;
|