route-registry.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import {PathTrie} from '@e22m4u/js-path-trie';
  2. import {ROOT_PATH, Route} from './route/index.js';
  3. import {getRequestPathname} from './utils/index.js';
  4. import {ServiceContainer} from '@e22m4u/js-service';
  5. import {InvalidArgumentError} from '@e22m4u/js-format';
  6. import {DebuggableService} from './debuggable-service.js';
  7. /**
  8. * @typedef {{
  9. * route: Route,
  10. * params: object,
  11. * }} ResolvedRoute
  12. */
  13. /**
  14. * Route registry.
  15. */
  16. export class RouteRegistry extends DebuggableService {
  17. /**
  18. * Constructor.
  19. *
  20. * @param {ServiceContainer} container
  21. */
  22. constructor(container) {
  23. super(container);
  24. this._trie = new PathTrie();
  25. }
  26. /**
  27. * Define route.
  28. *
  29. * @param {import('./route/index.js').RouteDefinition} routeDef
  30. * @returns {Route}
  31. */
  32. defineRoute(routeDef) {
  33. const debug = this.getDebuggerFor(this.defineRoute);
  34. if (!routeDef || typeof routeDef !== 'object' || Array.isArray(routeDef)) {
  35. throw new InvalidArgumentError(
  36. 'The route definition must be an Object, but %v was given.',
  37. routeDef,
  38. );
  39. }
  40. const route = new Route(routeDef);
  41. const triePath = `${route.method}/${route.path}`;
  42. this._trie.add(triePath, route);
  43. debug(
  44. 'The route %s %v was registered.',
  45. route.method.toUpperCase(),
  46. route.path,
  47. );
  48. return route;
  49. }
  50. /**
  51. * Match route by request.
  52. *
  53. * @param {import('http').IncomingRequest} request
  54. * @returns {ResolvedRoute|undefined}
  55. */
  56. matchRouteByRequest(request) {
  57. const debug = this.getDebuggerFor(this.matchRouteByRequest);
  58. const requestPath = getRequestPathname(request);
  59. debug(
  60. 'Matching routes with the request %s %v.',
  61. request.method.toUpperCase(),
  62. requestPath,
  63. );
  64. const rawTriePath = `${request.method.toUpperCase()}/${requestPath}`;
  65. // маршрут формируется с удалением дубликатов косой черты
  66. // "OPTIONS//api/users/login" => "OPTIONS/api/users/login"
  67. const triePath = rawTriePath.replace(/\/+/g, ROOT_PATH);
  68. const resolved = this._trie.match(triePath);
  69. if (resolved) {
  70. const route = resolved.value;
  71. debug(
  72. 'The route %s %v was matched.',
  73. route.method.toUpperCase(),
  74. route.path,
  75. );
  76. const paramNames = Object.keys(resolved.params);
  77. if (paramNames.length) {
  78. paramNames.forEach(name => {
  79. debug(
  80. 'The path parameter %v had the value %v.',
  81. name,
  82. resolved.params[name],
  83. );
  84. });
  85. } else {
  86. debug('No path parameters found.');
  87. }
  88. return {route, params: resolved.params};
  89. }
  90. debug(
  91. 'No matched route for the request %s %v.',
  92. request.method.toUpperCase(),
  93. requestPath,
  94. );
  95. }
  96. }