1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.RouterExplorer = void 0;
|
4 | const constants_1 = require("@nestjs/common/constants");
|
5 | const version_type_enum_1 = require("@nestjs/common/enums/version-type.enum");
|
6 | const exceptions_1 = require("@nestjs/common/exceptions");
|
7 | const version_options_interface_1 = require("@nestjs/common/interfaces/version-options.interface");
|
8 | const logger_service_1 = require("@nestjs/common/services/logger.service");
|
9 | const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
|
10 | const pathToRegexp = require("path-to-regexp");
|
11 | const unknown_request_mapping_exception_1 = require("../errors/exceptions/unknown-request-mapping.exception");
|
12 | const guards_consumer_1 = require("../guards/guards-consumer");
|
13 | const guards_context_creator_1 = require("../guards/guards-context-creator");
|
14 | const context_id_factory_1 = require("../helpers/context-id-factory");
|
15 | const execution_context_host_1 = require("../helpers/execution-context-host");
|
16 | const messages_1 = require("../helpers/messages");
|
17 | const router_method_factory_1 = require("../helpers/router-method-factory");
|
18 | const constants_2 = require("../injector/constants");
|
19 | const interceptors_consumer_1 = require("../interceptors/interceptors-consumer");
|
20 | const interceptors_context_creator_1 = require("../interceptors/interceptors-context-creator");
|
21 | const pipes_consumer_1 = require("../pipes/pipes-consumer");
|
22 | const pipes_context_creator_1 = require("../pipes/pipes-context-creator");
|
23 | const request_constants_1 = require("./request/request-constants");
|
24 | const route_params_factory_1 = require("./route-params-factory");
|
25 | const router_execution_context_1 = require("./router-execution-context");
|
26 | class RouterExplorer {
|
27 | constructor(metadataScanner, container, injector, routerProxy, exceptionsFilter, config, routePathFactory) {
|
28 | this.metadataScanner = metadataScanner;
|
29 | this.container = container;
|
30 | this.injector = injector;
|
31 | this.routerProxy = routerProxy;
|
32 | this.exceptionsFilter = exceptionsFilter;
|
33 | this.config = config;
|
34 | this.routePathFactory = routePathFactory;
|
35 | this.routerMethodFactory = new router_method_factory_1.RouterMethodFactory();
|
36 | this.logger = new logger_service_1.Logger(RouterExplorer.name, {
|
37 | timestamp: true,
|
38 | });
|
39 | this.exceptionFiltersCache = new WeakMap();
|
40 | const routeParamsFactory = new route_params_factory_1.RouteParamsFactory();
|
41 | const pipesContextCreator = new pipes_context_creator_1.PipesContextCreator(container, config);
|
42 | const pipesConsumer = new pipes_consumer_1.PipesConsumer();
|
43 | const guardsContextCreator = new guards_context_creator_1.GuardsContextCreator(container, config);
|
44 | const guardsConsumer = new guards_consumer_1.GuardsConsumer();
|
45 | const interceptorsContextCreator = new interceptors_context_creator_1.InterceptorsContextCreator(container, config);
|
46 | const interceptorsConsumer = new interceptors_consumer_1.InterceptorsConsumer();
|
47 | this.executionContextCreator = new router_execution_context_1.RouterExecutionContext(routeParamsFactory, pipesContextCreator, pipesConsumer, guardsContextCreator, guardsConsumer, interceptorsContextCreator, interceptorsConsumer, container.getHttpAdapterRef());
|
48 | }
|
49 | explore(instanceWrapper, moduleKey, applicationRef, host, routePathMetadata) {
|
50 | const { instance } = instanceWrapper;
|
51 | const routerPaths = this.scanForPaths(instance);
|
52 | this.applyPathsToRouterProxy(applicationRef, routerPaths, instanceWrapper, moduleKey, routePathMetadata, host);
|
53 | }
|
54 | extractRouterPath(metatype) {
|
55 | const path = Reflect.getMetadata(constants_1.PATH_METADATA, metatype);
|
56 | if ((0, shared_utils_1.isUndefined)(path)) {
|
57 | throw new unknown_request_mapping_exception_1.UnknownRequestMappingException();
|
58 | }
|
59 | if (Array.isArray(path)) {
|
60 | return path.map(p => (0, shared_utils_1.addLeadingSlash)(p));
|
61 | }
|
62 | return [(0, shared_utils_1.addLeadingSlash)(path)];
|
63 | }
|
64 | scanForPaths(instance, prototype) {
|
65 | const instancePrototype = (0, shared_utils_1.isUndefined)(prototype)
|
66 | ? Object.getPrototypeOf(instance)
|
67 | : prototype;
|
68 | return this.metadataScanner.scanFromPrototype(instance, instancePrototype, method => this.exploreMethodMetadata(instance, instancePrototype, method));
|
69 | }
|
70 | exploreMethodMetadata(instance, prototype, methodName) {
|
71 | const instanceCallback = instance[methodName];
|
72 | const prototypeCallback = prototype[methodName];
|
73 | const routePath = Reflect.getMetadata(constants_1.PATH_METADATA, prototypeCallback);
|
74 | if ((0, shared_utils_1.isUndefined)(routePath)) {
|
75 | return null;
|
76 | }
|
77 | const requestMethod = Reflect.getMetadata(constants_1.METHOD_METADATA, prototypeCallback);
|
78 | const version = Reflect.getMetadata(constants_1.VERSION_METADATA, prototypeCallback);
|
79 | const path = (0, shared_utils_1.isString)(routePath)
|
80 | ? [(0, shared_utils_1.addLeadingSlash)(routePath)]
|
81 | : routePath.map((p) => (0, shared_utils_1.addLeadingSlash)(p));
|
82 | return {
|
83 | path,
|
84 | requestMethod,
|
85 | targetCallback: instanceCallback,
|
86 | methodName,
|
87 | version,
|
88 | };
|
89 | }
|
90 | applyPathsToRouterProxy(router, routeDefinitions, instanceWrapper, moduleKey, routePathMetadata, host) {
|
91 | (routeDefinitions || []).forEach(routeDefinition => {
|
92 | const { version: methodVersion } = routeDefinition;
|
93 | routePathMetadata.methodVersion = methodVersion;
|
94 | this.applyCallbackToRouter(router, routeDefinition, instanceWrapper, moduleKey, routePathMetadata, host);
|
95 | });
|
96 | }
|
97 | applyCallbackToRouter(router, routeDefinition, instanceWrapper, moduleKey, routePathMetadata, host) {
|
98 | const { path: paths, requestMethod, targetCallback, methodName, } = routeDefinition;
|
99 | const { instance } = instanceWrapper;
|
100 | const routerMethodRef = this.routerMethodFactory
|
101 | .get(router, requestMethod)
|
102 | .bind(router);
|
103 | const isRequestScoped = !instanceWrapper.isDependencyTreeStatic();
|
104 | const proxy = isRequestScoped
|
105 | ? this.createRequestScopedHandler(instanceWrapper, requestMethod, this.container.getModuleByKey(moduleKey), moduleKey, methodName)
|
106 | : this.createCallbackProxy(instance, targetCallback, methodName, moduleKey, requestMethod);
|
107 | const isVersioned = (routePathMetadata.methodVersion ||
|
108 | routePathMetadata.controllerVersion) &&
|
109 | routePathMetadata.versioningOptions;
|
110 | let routeHandler = this.applyHostFilter(host, proxy);
|
111 | paths.forEach(path => {
|
112 | if (isVersioned &&
|
113 | routePathMetadata.versioningOptions.type !== version_type_enum_1.VersioningType.URI) {
|
114 |
|
115 | routeHandler = this.applyVersionFilter(router, routePathMetadata, routeHandler);
|
116 | }
|
117 | routePathMetadata.methodPath = path;
|
118 | const pathsToRegister = this.routePathFactory.create(routePathMetadata, requestMethod);
|
119 | pathsToRegister.forEach(path => routerMethodRef(path, routeHandler));
|
120 | const pathsToLog = this.routePathFactory.create(Object.assign(Object.assign({}, routePathMetadata), { versioningOptions: undefined }), requestMethod);
|
121 | pathsToLog.forEach(path => {
|
122 | if (isVersioned) {
|
123 | const version = this.routePathFactory.getVersion(routePathMetadata);
|
124 | this.logger.log((0, messages_1.VERSIONED_ROUTE_MAPPED_MESSAGE)(path, requestMethod, version));
|
125 | }
|
126 | else {
|
127 | this.logger.log((0, messages_1.ROUTE_MAPPED_MESSAGE)(path, requestMethod));
|
128 | }
|
129 | });
|
130 | });
|
131 | }
|
132 | applyHostFilter(host, handler) {
|
133 | if (!host) {
|
134 | return handler;
|
135 | }
|
136 | const httpAdapterRef = this.container.getHttpAdapterRef();
|
137 | const hosts = Array.isArray(host) ? host : [host];
|
138 | const hostRegExps = hosts.map((host) => {
|
139 | const keys = [];
|
140 | const regexp = pathToRegexp(host, keys);
|
141 | return { regexp, keys };
|
142 | });
|
143 | const unsupportedFilteringErrorMessage = Array.isArray(host)
|
144 | ? `HTTP adapter does not support filtering on hosts: ["${host.join('", "')}"]`
|
145 | : `HTTP adapter does not support filtering on host: "${host}"`;
|
146 | return (req, res, next) => {
|
147 | req.hosts = {};
|
148 | const hostname = httpAdapterRef.getRequestHostname(req) || '';
|
149 | for (const exp of hostRegExps) {
|
150 | const match = hostname.match(exp.regexp);
|
151 | if (match) {
|
152 | exp.keys.forEach((key, i) => (req.hosts[key.name] = match[i + 1]));
|
153 | return handler(req, res, next);
|
154 | }
|
155 | }
|
156 | if (!next) {
|
157 | throw new exceptions_1.InternalServerErrorException(unsupportedFilteringErrorMessage);
|
158 | }
|
159 | return next();
|
160 | };
|
161 | }
|
162 | applyVersionFilter(router, routePathMetadata, handler) {
|
163 | const { versioningOptions } = routePathMetadata;
|
164 | const version = this.routePathFactory.getVersion(routePathMetadata);
|
165 | if (router === null || router === void 0 ? void 0 : router.applyVersionFilter) {
|
166 | return router.applyVersionFilter(handler, version, versioningOptions);
|
167 | }
|
168 | |
169 |
|
170 |
|
171 | return (req, res, next) => {
|
172 | var _a, _b, _c, _d;
|
173 | if (version === version_options_interface_1.VERSION_NEUTRAL) {
|
174 | return handler(req, res, next);
|
175 | }
|
176 |
|
177 | if (versioningOptions.type === version_type_enum_1.VersioningType.URI) {
|
178 | return handler(req, res, next);
|
179 | }
|
180 |
|
181 | if (versioningOptions.type === version_type_enum_1.VersioningType.CUSTOM) {
|
182 | const extractedVersion = versioningOptions.extractor(req);
|
183 | if (Array.isArray(version)) {
|
184 | if (Array.isArray(extractedVersion) &&
|
185 | version.filter(extractedVersion.includes).length) {
|
186 | return handler(req, res, next);
|
187 | }
|
188 | else if ((0, shared_utils_1.isString)(extractedVersion) &&
|
189 | version.includes(extractedVersion)) {
|
190 | return handler(req, res, next);
|
191 | }
|
192 | }
|
193 | else {
|
194 | if (Array.isArray(extractedVersion) &&
|
195 | extractedVersion.includes(version)) {
|
196 | return handler(req, res, next);
|
197 | }
|
198 | else if ((0, shared_utils_1.isString)(extractedVersion) &&
|
199 | version === extractedVersion) {
|
200 | return handler(req, res, next);
|
201 | }
|
202 | }
|
203 | }
|
204 |
|
205 | if (versioningOptions.type === version_type_enum_1.VersioningType.MEDIA_TYPE) {
|
206 | const MEDIA_TYPE_HEADER = 'Accept';
|
207 | const acceptHeaderValue = ((_a = req.headers) === null || _a === void 0 ? void 0 : _a[MEDIA_TYPE_HEADER]) ||
|
208 | ((_b = req.headers) === null || _b === void 0 ? void 0 : _b[MEDIA_TYPE_HEADER.toLowerCase()]);
|
209 | const acceptHeaderVersionParameter = acceptHeaderValue
|
210 | ? acceptHeaderValue.split(';')[1]
|
211 | : undefined;
|
212 |
|
213 | if ((0, shared_utils_1.isUndefined)(acceptHeaderVersionParameter)) {
|
214 | if (Array.isArray(version)) {
|
215 | if (version.includes(version_options_interface_1.VERSION_NEUTRAL)) {
|
216 | return handler(req, res, next);
|
217 | }
|
218 | }
|
219 | }
|
220 | else {
|
221 | const headerVersion = acceptHeaderVersionParameter.split(versioningOptions.key)[1];
|
222 | if (Array.isArray(version)) {
|
223 | if (version.includes(headerVersion)) {
|
224 | return handler(req, res, next);
|
225 | }
|
226 | }
|
227 | else if ((0, shared_utils_1.isString)(version)) {
|
228 | if (version === headerVersion) {
|
229 | return handler(req, res, next);
|
230 | }
|
231 | }
|
232 | }
|
233 | }
|
234 |
|
235 | else if (versioningOptions.type === version_type_enum_1.VersioningType.HEADER) {
|
236 | const customHeaderVersionParameter = ((_c = req.headers) === null || _c === void 0 ? void 0 : _c[versioningOptions.header]) ||
|
237 | ((_d = req.headers) === null || _d === void 0 ? void 0 : _d[versioningOptions.header.toLowerCase()]);
|
238 |
|
239 | if ((0, shared_utils_1.isUndefined)(customHeaderVersionParameter)) {
|
240 | if (Array.isArray(version)) {
|
241 | if (version.includes(version_options_interface_1.VERSION_NEUTRAL)) {
|
242 | return handler(req, res, next);
|
243 | }
|
244 | }
|
245 | }
|
246 | else {
|
247 | if (Array.isArray(version)) {
|
248 | if (version.includes(customHeaderVersionParameter)) {
|
249 | return handler(req, res, next);
|
250 | }
|
251 | }
|
252 | else if ((0, shared_utils_1.isString)(version)) {
|
253 | if (version === customHeaderVersionParameter) {
|
254 | return handler(req, res, next);
|
255 | }
|
256 | }
|
257 | }
|
258 | }
|
259 | if (!next) {
|
260 | throw new exceptions_1.InternalServerErrorException('HTTP adapter does not support filtering on version');
|
261 | }
|
262 | return next();
|
263 | };
|
264 | }
|
265 | createCallbackProxy(instance, callback, methodName, moduleRef, requestMethod, contextId = constants_2.STATIC_CONTEXT, inquirerId) {
|
266 | const executionContext = this.executionContextCreator.create(instance, callback, methodName, moduleRef, requestMethod, contextId, inquirerId);
|
267 | const exceptionFilter = this.exceptionsFilter.create(instance, callback, moduleRef, contextId, inquirerId);
|
268 | return this.routerProxy.createProxy(executionContext, exceptionFilter);
|
269 | }
|
270 | createRequestScopedHandler(instanceWrapper, requestMethod, moduleRef, moduleKey, methodName) {
|
271 | const { instance } = instanceWrapper;
|
272 | const collection = moduleRef.controllers;
|
273 | return async (req, res, next) => {
|
274 | try {
|
275 | const contextId = this.getContextId(req);
|
276 | const contextInstance = await this.injector.loadPerContext(instance, moduleRef, collection, contextId);
|
277 | await this.createCallbackProxy(contextInstance, contextInstance[methodName], methodName, moduleKey, requestMethod, contextId, instanceWrapper.id)(req, res, next);
|
278 | }
|
279 | catch (err) {
|
280 | let exceptionFilter = this.exceptionFiltersCache.get(instance[methodName]);
|
281 | if (!exceptionFilter) {
|
282 | exceptionFilter = this.exceptionsFilter.create(instance, instance[methodName], moduleKey);
|
283 | this.exceptionFiltersCache.set(instance[methodName], exceptionFilter);
|
284 | }
|
285 | const host = new execution_context_host_1.ExecutionContextHost([req, res, next]);
|
286 | exceptionFilter.next(err, host);
|
287 | }
|
288 | };
|
289 | }
|
290 | getContextId(request) {
|
291 | const contextId = context_id_factory_1.ContextIdFactory.getByRequest(request);
|
292 | if (!request[request_constants_1.REQUEST_CONTEXT_ID]) {
|
293 | Object.defineProperty(request, request_constants_1.REQUEST_CONTEXT_ID, {
|
294 | value: contextId,
|
295 | enumerable: false,
|
296 | writable: false,
|
297 | configurable: false,
|
298 | });
|
299 | this.container.registerRequestProvider(request, contextId);
|
300 | }
|
301 | return contextId;
|
302 | }
|
303 | }
|
304 | exports.RouterExplorer = RouterExplorer;
|