• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nestjs / nest / e79cdc3c-762e-40cd-acb3-21e17deb1cd9

18 Aug 2024 03:11PM UTC coverage: 92.124% (-0.09%) from 92.213%
e79cdc3c-762e-40cd-acb3-21e17deb1cd9

Pull #13485

circleci

DylanVeldra
fix(core): merge req context with tenant payload in the request instance
Pull Request #13485: fix(core): merge request context with tenant context payload in the request singleton

2559 of 3078 branches covered (83.14%)

1 of 2 new or added lines in 2 files covered. (50.0%)

74 existing lines in 13 files now uncovered.

6737 of 7313 relevant lines covered (92.12%)

17.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.2
/packages/core/router/router-explorer.ts
1
import { HttpServer } from '@nestjs/common';
2
import { PATH_METADATA } from '@nestjs/common/constants';
1✔
3
import { RequestMethod, VersioningType } from '@nestjs/common/enums';
1✔
4
import { InternalServerErrorException } from '@nestjs/common/exceptions';
1✔
5
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
6
import { Type } from '@nestjs/common/interfaces/type.interface';
7
import { VersionValue } from '@nestjs/common/interfaces/version-options.interface';
8
import { Logger } from '@nestjs/common/services/logger.service';
1✔
9
import {
1✔
10
  addLeadingSlash,
11
  isUndefined,
12
} from '@nestjs/common/utils/shared.utils';
13
import * as pathToRegexp from 'path-to-regexp';
1✔
14
import { ApplicationConfig } from '../application-config';
15
import { UnknownRequestMappingException } from '../errors/exceptions/unknown-request-mapping.exception';
1✔
16
import { GuardsConsumer, GuardsContextCreator } from '../guards';
1✔
17
import { ContextIdFactory } from '../helpers/context-id-factory';
1✔
18
import { ExecutionContextHost } from '../helpers/execution-context-host';
1✔
19
import {
1✔
20
  ROUTE_MAPPED_MESSAGE,
21
  VERSIONED_ROUTE_MAPPED_MESSAGE,
22
} from '../helpers/messages';
23
import { RouterMethodFactory } from '../helpers/router-method-factory';
1✔
24
import { STATIC_CONTEXT } from '../injector/constants';
1✔
25
import { NestContainer } from '../injector/container';
26
import { Injector } from '../injector/injector';
27
import { ContextId, InstanceWrapper } from '../injector/instance-wrapper';
28
import { Module } from '../injector/module';
29
import {
1✔
30
  InterceptorsConsumer,
31
  InterceptorsContextCreator,
32
} from '../interceptors';
33
import { GraphInspector } from '../inspector/graph-inspector';
34
import {
35
  Entrypoint,
36
  HttpEntrypointMetadata,
37
} from '../inspector/interfaces/entrypoint.interface';
38
import { MetadataScanner } from '../metadata-scanner';
39
import { PipesConsumer, PipesContextCreator } from '../pipes';
1✔
40
import { ExceptionsFilter } from './interfaces/exceptions-filter.interface';
41
import { RoutePathMetadata } from './interfaces/route-path-metadata.interface';
42
import { PathsExplorer } from './paths-explorer';
1✔
43
import { REQUEST_CONTEXT_ID } from './request/request-constants';
1✔
44
import { RouteParamsFactory } from './route-params-factory';
1✔
45
import { RoutePathFactory } from './route-path-factory';
46
import { RouterExecutionContext } from './router-execution-context';
1✔
47
import { RouterProxy, RouterProxyCallback } from './router-proxy';
48

49
export interface RouteDefinition {
50
  path: string[];
51
  requestMethod: RequestMethod;
52
  targetCallback: RouterProxyCallback;
53
  methodName: string;
54
  version?: VersionValue;
55
}
56

57
export class RouterExplorer {
1✔
58
  private readonly executionContextCreator: RouterExecutionContext;
59
  private readonly pathsExplorer: PathsExplorer;
60
  private readonly routerMethodFactory = new RouterMethodFactory();
24✔
61
  private readonly logger = new Logger(RouterExplorer.name, {
24✔
62
    timestamp: true,
63
  });
64
  private readonly exceptionFiltersCache = new WeakMap();
24✔
65

66
  constructor(
67
    metadataScanner: MetadataScanner,
68
    private readonly container: NestContainer,
24✔
69
    private readonly injector: Injector,
24✔
70
    private readonly routerProxy: RouterProxy,
24✔
71
    private readonly exceptionsFilter: ExceptionsFilter,
24✔
72
    config: ApplicationConfig,
73
    private readonly routePathFactory: RoutePathFactory,
24✔
74
    private readonly graphInspector: GraphInspector,
24✔
75
  ) {
76
    this.pathsExplorer = new PathsExplorer(metadataScanner);
24✔
77

78
    const routeParamsFactory = new RouteParamsFactory();
24✔
79
    const pipesContextCreator = new PipesContextCreator(container, config);
24✔
80
    const pipesConsumer = new PipesConsumer();
24✔
81
    const guardsContextCreator = new GuardsContextCreator(container, config);
24✔
82
    const guardsConsumer = new GuardsConsumer();
24✔
83
    const interceptorsContextCreator = new InterceptorsContextCreator(
24✔
84
      container,
85
      config,
86
    );
87
    const interceptorsConsumer = new InterceptorsConsumer();
24✔
88

89
    this.executionContextCreator = new RouterExecutionContext(
24✔
90
      routeParamsFactory,
91
      pipesContextCreator,
92
      pipesConsumer,
93
      guardsContextCreator,
94
      guardsConsumer,
95
      interceptorsContextCreator,
96
      interceptorsConsumer,
97
      container.getHttpAdapterRef(),
98
    );
99
  }
100

101
  public explore<T extends HttpServer = any>(
102
    instanceWrapper: InstanceWrapper,
103
    moduleKey: string,
104
    applicationRef: T,
105
    host: string | RegExp | Array<string | RegExp>,
106
    routePathMetadata: RoutePathMetadata,
107
  ) {
108
    const { instance } = instanceWrapper;
3✔
109
    const routerPaths = this.pathsExplorer.scanForPaths(instance);
3✔
110
    this.applyPathsToRouterProxy(
3✔
111
      applicationRef,
112
      routerPaths,
113
      instanceWrapper,
114
      moduleKey,
115
      routePathMetadata,
116
      host,
117
    );
118
  }
119

120
  public extractRouterPath(metatype: Type<Controller>): string[] {
121
    const path = Reflect.getMetadata(PATH_METADATA, metatype);
3✔
122

123
    if (isUndefined(path)) {
3✔
124
      throw new UnknownRequestMappingException(metatype);
1✔
125
    }
126
    if (Array.isArray(path)) {
2✔
127
      return path.map(p => addLeadingSlash(p));
2✔
128
    }
129
    return [addLeadingSlash(path)];
1✔
130
  }
131

132
  public applyPathsToRouterProxy<T extends HttpServer>(
133
    router: T,
134
    routeDefinitions: RouteDefinition[],
135
    instanceWrapper: InstanceWrapper,
136
    moduleKey: string,
137
    routePathMetadata: RoutePathMetadata,
138
    host: string | RegExp | Array<string | RegExp>,
139
  ) {
140
    (routeDefinitions || []).forEach(routeDefinition => {
5!
141
      const { version: methodVersion } = routeDefinition;
10✔
142
      routePathMetadata.methodVersion = methodVersion;
10✔
143

144
      this.applyCallbackToRouter(
10✔
145
        router,
146
        routeDefinition,
147
        instanceWrapper,
148
        moduleKey,
149
        routePathMetadata,
150
        host,
151
      );
152
    });
153
  }
154

155
  private applyCallbackToRouter<T extends HttpServer>(
156
    router: T,
157
    routeDefinition: RouteDefinition,
158
    instanceWrapper: InstanceWrapper,
159
    moduleKey: string,
160
    routePathMetadata: RoutePathMetadata,
161
    host: string | RegExp | Array<string | RegExp>,
162
  ) {
163
    const {
164
      path: paths,
165
      requestMethod,
166
      targetCallback,
167
      methodName,
168
    } = routeDefinition;
4✔
169

170
    const { instance } = instanceWrapper;
4✔
171
    const routerMethodRef = this.routerMethodFactory
4✔
172
      .get(router, requestMethod)
173
      .bind(router);
174

175
    const isRequestScoped = !instanceWrapper.isDependencyTreeStatic();
4✔
176
    const proxy = isRequestScoped
4✔
177
      ? this.createRequestScopedHandler(
4!
178
          instanceWrapper,
179
          requestMethod,
180
          this.container.getModuleByKey(moduleKey),
181
          moduleKey,
182
          methodName,
183
        )
184
      : this.createCallbackProxy(
185
          instance,
186
          targetCallback,
187
          methodName,
188
          moduleKey,
189
          requestMethod,
190
        );
191

192
    const isVersioned =
193
      (routePathMetadata.methodVersion ||
4✔
194
        routePathMetadata.controllerVersion) &&
195
      routePathMetadata.versioningOptions;
196
    let routeHandler = this.applyHostFilter(host, proxy);
4✔
197

198
    paths.forEach(path => {
4✔
199
      if (
4!
200
        isVersioned &&
5✔
201
        routePathMetadata.versioningOptions.type !== VersioningType.URI
202
      ) {
203
        // All versioning (except for URI Versioning) is done via the "Version Filter"
204
        routeHandler = this.applyVersionFilter(
×
205
          router,
206
          routePathMetadata,
207
          routeHandler,
208
        );
209
      }
210

211
      routePathMetadata.methodPath = path;
4✔
212
      const pathsToRegister = this.routePathFactory.create(
4✔
213
        routePathMetadata,
214
        requestMethod,
215
      );
216
      pathsToRegister.forEach(path => {
4✔
217
        const entrypointDefinition: Entrypoint<HttpEntrypointMetadata> = {
4✔
218
          type: 'http-endpoint',
219
          methodName,
220
          className: instanceWrapper.name,
221
          classNodeId: instanceWrapper.id,
222
          metadata: {
223
            key: path,
224
            path,
225
            requestMethod: RequestMethod[
226
              requestMethod
227
            ] as keyof typeof RequestMethod,
228
            methodVersion: routePathMetadata.methodVersion as VersionValue,
229
            controllerVersion:
230
              routePathMetadata.controllerVersion as VersionValue,
231
          },
232
        };
233

234
        this.copyMetadataToCallback(targetCallback, routeHandler);
4✔
235
        routerMethodRef(path, routeHandler);
4✔
236

237
        this.graphInspector.insertEntrypointDefinition<HttpEntrypointMetadata>(
4✔
238
          entrypointDefinition,
239
          instanceWrapper.id,
240
        );
241
      });
242

243
      const pathsToLog = this.routePathFactory.create(
4✔
244
        {
245
          ...routePathMetadata,
246
          versioningOptions: undefined,
247
        },
248
        requestMethod,
249
      );
250
      pathsToLog.forEach(path => {
4✔
251
        if (isVersioned) {
4✔
252
          const version = this.routePathFactory.getVersion(routePathMetadata);
1✔
253
          this.logger.log(
1✔
254
            VERSIONED_ROUTE_MAPPED_MESSAGE(path, requestMethod, version),
255
          );
256
        } else {
257
          this.logger.log(ROUTE_MAPPED_MESSAGE(path, requestMethod));
3✔
258
        }
259
      });
260
    });
261
  }
262

263
  private applyHostFilter(
264
    host: string | RegExp | Array<string | RegExp>,
265
    handler: Function,
266
  ) {
267
    if (!host) {
4✔
268
      return handler;
3✔
269
    }
270

271
    const httpAdapterRef = this.container.getHttpAdapterRef();
1✔
272
    const hosts = Array.isArray(host) ? host : [host];
1!
273
    const hostRegExps = hosts.map((host: string | RegExp) => {
1✔
274
      const keys = [];
1✔
275
      const regexp = pathToRegexp(host, keys);
1✔
276
      return { regexp, keys };
1✔
277
    });
278

279
    const unsupportedFilteringErrorMessage = Array.isArray(host)
1✔
280
      ? `HTTP adapter does not support filtering on hosts: ["${host.join(
1!
281
          '", "',
282
        )}"]`
283
      : `HTTP adapter does not support filtering on host: "${host}"`;
284

285
    return <TRequest extends Record<string, any> = any, TResponse = any>(
1✔
286
      req: TRequest,
287
      res: TResponse,
288
      next: () => void,
289
    ) => {
290
      (req as Record<string, any>).hosts = {};
×
291
      const hostname = httpAdapterRef.getRequestHostname(req) || '';
×
292

293
      for (const exp of hostRegExps) {
×
294
        const match = hostname.match(exp.regexp);
×
295
        if (match) {
×
296
          if (exp.keys.length > 0) {
×
297
            exp.keys.forEach((key, i) => (req.hosts[key.name] = match[i + 1]));
×
298
          } else if (exp.regexp && match.groups) {
×
299
            for (const groupName in match.groups) {
×
300
              req.hosts[groupName] = match.groups[groupName];
×
301
            }
302
          }
303
          return handler(req, res, next);
×
304
        }
305
      }
306
      if (!next) {
×
307
        throw new InternalServerErrorException(
×
308
          unsupportedFilteringErrorMessage,
309
        );
310
      }
311
      return next();
×
312
    };
313
  }
314

315
  private applyVersionFilter<T extends HttpServer>(
316
    router: T,
317
    routePathMetadata: RoutePathMetadata,
318
    handler: Function,
319
  ) {
320
    const version = this.routePathFactory.getVersion(routePathMetadata);
1✔
321
    return router.applyVersionFilter(
1✔
322
      handler,
323
      version,
324
      routePathMetadata.versioningOptions,
325
    );
326
  }
327

328
  private createCallbackProxy(
329
    instance: Controller,
330
    callback: RouterProxyCallback,
331
    methodName: string,
332
    moduleRef: string,
333
    requestMethod: RequestMethod,
334
    contextId = STATIC_CONTEXT,
4✔
335
    inquirerId?: string,
336
  ) {
337
    const executionContext = this.executionContextCreator.create(
4✔
338
      instance,
339
      callback,
340
      methodName,
341
      moduleRef,
342
      requestMethod,
343
      contextId,
344
      inquirerId,
345
    );
346
    const exceptionFilter = this.exceptionsFilter.create(
4✔
347
      instance,
348
      callback,
349
      moduleRef,
350
      contextId,
351
      inquirerId,
352
    );
353
    return this.routerProxy.createProxy(executionContext, exceptionFilter);
4✔
354
  }
355

356
  public createRequestScopedHandler(
357
    instanceWrapper: InstanceWrapper,
358
    requestMethod: RequestMethod,
359
    moduleRef: Module,
360
    moduleKey: string,
361
    methodName: string,
362
  ) {
363
    const { instance } = instanceWrapper;
1✔
364
    const collection = moduleRef.controllers;
1✔
365

366
    const isTreeDurable = instanceWrapper.isDependencyTreeDurable();
1✔
367

368
    return async <TRequest extends Record<any, any>, TResponse>(
1✔
369
      req: TRequest,
370
      res: TResponse,
371
      next: () => void,
372
    ) => {
373
      try {
1✔
374
        const contextId = this.getContextId(req, isTreeDurable);
1✔
375
        const contextInstance = await this.injector.loadPerContext(
×
376
          instance,
377
          moduleRef,
378
          collection,
379
          contextId,
380
        );
381
        await this.createCallbackProxy(
×
382
          contextInstance,
383
          contextInstance[methodName],
384
          methodName,
385
          moduleKey,
386
          requestMethod,
387
          contextId,
388
          instanceWrapper.id,
389
        )(req, res, next);
390
      } catch (err) {
391
        let exceptionFilter = this.exceptionFiltersCache.get(
1✔
392
          instance[methodName],
393
        );
394
        if (!exceptionFilter) {
1!
395
          exceptionFilter = this.exceptionsFilter.create(
1✔
396
            instance,
397
            instance[methodName],
398
            moduleKey,
399
          );
400
          this.exceptionFiltersCache.set(instance[methodName], exceptionFilter);
1✔
401
        }
402
        const host = new ExecutionContextHost([req, res, next]);
1✔
403
        exceptionFilter.next(err, host);
1✔
404
      }
405
    };
406
  }
407

408
  private getContextId<T extends Record<any, unknown> = any>(
409
    request: T,
410
    isTreeDurable: boolean,
411
  ): ContextId {
412
    const contextId = ContextIdFactory.getByRequest(request);
1✔
413
    if (!request[REQUEST_CONTEXT_ID as any]) {
1!
414
      Object.defineProperty(request, REQUEST_CONTEXT_ID, {
×
415
        value: contextId,
416
        enumerable: false,
417
        writable: false,
418
        configurable: false,
419
      });
420

NEW
421
      const requestProviderValue = isTreeDurable
×
422
        ? contextId.payload
×
423
        : Object.assign(request, contextId.payload);
UNCOV
424
      this.container.registerRequestProvider(requestProviderValue, contextId);
×
425
    }
426
    return contextId;
×
427
  }
428

429
  private copyMetadataToCallback(
430
    originalCallback: RouterProxyCallback,
431
    targetCallback: Function,
432
  ) {
433
    for (const key of Reflect.getMetadataKeys(originalCallback)) {
5✔
434
      Reflect.defineMetadata(
9✔
435
        key,
436
        Reflect.getMetadata(key, originalCallback),
437
        targetCallback,
438
      );
439
    }
440
  }
441
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc