Passed
Pull Request — master (#406)
by
unknown
01:44
created

RouteNameResolver.resolve   B

Complexity

Conditions 6

Size

Total Lines 15
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 13
dl 0
loc 15
rs 8.6666
c 0
b 0
f 0
1
// Inspired by: https://github.com/nestjs/nest/blob/cb2af8a3723272bcbacde44dcaadab66a7ec8b7c/packages/core/router/route-alias-resolver.ts
2
import { Injectable } from '@nestjs/common';
3
import { PATH_METADATA } from '@nestjs/common/constants';
4
import { isObject } from '@nestjs/common/utils/shared.utils';
5
import { DiscoveryService } from '@nestjs/core';
6
import { ROUTE_NAME_METADATA } from './WithName';
7
8
@Injectable()
9
export class RouteNameResolver {
10
  private readonly nameMap: Map<string, string[]>;
11
12
  constructor(discoveryService: DiscoveryService) {
13
    this.nameMap = new Map();
14
    this.init(discoveryService);
15
  }
16
17
  private init(discoveryService: DiscoveryService) {
18
    const controllers = discoveryService.getControllers();
19
20
    controllers.forEach(controller => {
21
      const basePath: string = Reflect.getMetadata(
22
        PATH_METADATA,
23
        controller.metatype
24
      );
25
26
      const methodNames = Object.getOwnPropertyNames(
27
        controller.instance.__proto__
28
      );
29
30
      methodNames.forEach(methodName => {
31
        const method = controller.instance.__proto__[methodName];
32
        const name: string | undefined = Reflect.getMetadata(
33
          ROUTE_NAME_METADATA,
34
          method
35
        );
36
37
        if (name) {
38
          const path: string = Reflect.getMetadata(PATH_METADATA, method);
39
          this.register(name, basePath, [path]);
40
        }
41
      });
42
    });
43
  }
44
45
  public register(name: string, basePath: string, path: string[]) {
46
    if (this.nameMap.has(name)) {
47
      throw new Error(`Conflict ${name} already registered`);
48
    }
49
    this.nameMap.set(name, this.createPath(basePath, path));
50
  }
51
52
  public resolve(name: string, params?: object): string {
53
    if (!this.nameMap.has(name)) {
54
      throw new Error(`Not Found: ${name} not registered`);
55
    }
56
57
    const path = this.nameMap.get(name).reduce((path, part) => {
58
      if (part.startsWith(':') && isObject(params)) {
59
        const paramValue = params[part.replace(':', '')];
60
        return path + '/' + (paramValue !== undefined ? paramValue : part);
61
      }
62
      return part ? path + '/' + part : path;
63
    }, '');
64
65
    return path ? path : '/';
66
  }
67
68
  public getName(path: string): string | null {
69
    const pathWithoutQueryString = path.split('?')[0];
70
71
    const parts = this.splitPath([pathWithoutQueryString]);
72
73
    const entries = Array.from(this.nameMap.entries());
74
    entries.sort(
75
      ([, patternA], [, patternB]) =>
76
        this.splitPath(patternB).length - this.splitPath(patternA).length
77
    );
78
79
    const matches = entries.filter(([, pattern]) => {
80
      const routeParts = this.splitPath(pattern);
81
82
      if (routeParts.length !== parts.length) {
83
        return false;
84
      }
85
86
      for (let index = 0; index < parts.length; index++) {
87
        const partLeft = parts[index];
88
        const partRight = routeParts[index];
89
        if (partLeft !== partRight && !partRight.startsWith(':')) {
90
          return false;
91
        }
92
      }
93
94
      return true;
95
    });
96
97
    if (matches.length === 0) {
98
      return null;
99
    }
100
101
    return matches[0][0];
102
  }
103
104
  private createPath(basePath: string, path: string[]): string[] {
105
    const base = basePath ? [this.stripSlashes(basePath)] : [];
106
    return base.concat(this.splitPath(path));
107
  }
108
109
  private splitPath(path: string[]): string[] {
110
    const pathParts = [];
111
    path.forEach(part => {
112
      part.split('/').forEach(partial => {
113
        partial && pathParts.push(this.stripSlashes(partial));
114
      });
115
    });
116
    return pathParts;
117
  }
118
119
  private stripSlashes(str: string) {
120
    return str.replace(/^\/?(.*)\/?$/, '$1');
121
  }
122
}
123