UNPKG

12.6 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.SwaggerExplorer = void 0;
4const common_1 = require("@nestjs/common");
5const constants_1 = require("@nestjs/common/constants");
6const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
7const metadata_scanner_1 = require("@nestjs/core/metadata-scanner");
8const route_path_factory_1 = require("@nestjs/core/router/route-path-factory");
9const lodash_1 = require("lodash");
10const pathToRegexp = require("path-to-regexp");
11const constants_2 = require("./constants");
12const api_exclude_controller_explorer_1 = require("./explorers/api-exclude-controller.explorer");
13const api_exclude_endpoint_explorer_1 = require("./explorers/api-exclude-endpoint.explorer");
14const api_extra_models_explorer_1 = require("./explorers/api-extra-models.explorer");
15const api_headers_explorer_1 = require("./explorers/api-headers.explorer");
16const api_operation_explorer_1 = require("./explorers/api-operation.explorer");
17const api_parameters_explorer_1 = require("./explorers/api-parameters.explorer");
18const api_response_explorer_1 = require("./explorers/api-response.explorer");
19const api_security_explorer_1 = require("./explorers/api-security.explorer");
20const api_use_tags_explorer_1 = require("./explorers/api-use-tags.explorer");
21const mimetype_content_wrapper_1 = require("./services/mimetype-content-wrapper");
22const is_body_parameter_util_1 = require("./utils/is-body-parameter.util");
23const merge_and_uniq_util_1 = require("./utils/merge-and-uniq.util");
24class 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}
226exports.SwaggerExplorer = SwaggerExplorer;