route-registry.js 2.4 KB

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