UNPKG

11.7 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
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}
213exports.SwaggerExplorer = SwaggerExplorer;