route.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import {Errorf} from '@e22m4u/js-format';
  2. import {HookType} from './hooks/index.js';
  3. import {Debuggable} from '@e22m4u/js-debug';
  4. import {HookRegistry} from './hooks/index.js';
  5. import {getRequestPathname} from './utils/index.js';
  6. import {MODULE_DEBUG_NAMESPACE} from './debuggable-service.js';
  7. /**
  8. * @typedef {import('./request-context.js').RequestContext} RequestContext
  9. * @typedef {(ctx: RequestContext) => *} RoutePreHandler
  10. * @typedef {(ctx: RequestContext) => *} RouteHandler
  11. * @typedef {(ctx: RequestContext, data: *) => *} RoutePostHandler
  12. * @typedef {{
  13. * method: string,
  14. * path: string,
  15. * handler: RouteHandler,
  16. * preHandler: RoutePreHandler|(RoutePreHandler[])|undefined,
  17. * postHandler: RoutePostHandler|(RoutePostHandler[])|undefined
  18. * }} RouteDefinition
  19. */
  20. /**
  21. * Http method.
  22. *
  23. * @type {{
  24. * GET: 'GET',
  25. * POST: 'POST',
  26. * PUT: 'PUT',
  27. * PATCH: 'PATCH',
  28. * DELETE: 'DELETE',
  29. * }}
  30. */
  31. export const HttpMethod = {
  32. GET: 'GET',
  33. POST: 'POST',
  34. PUT: 'PUT',
  35. PATCH: 'PATCH',
  36. DELETE: 'DELETE',
  37. };
  38. /**
  39. * Route.
  40. */
  41. export class Route extends Debuggable {
  42. /**
  43. * Method.
  44. *
  45. * @type {string}
  46. * @private
  47. */
  48. _method;
  49. /**
  50. * Getter of the method.
  51. *
  52. * @returns {string}
  53. */
  54. get method() {
  55. return this._method;
  56. }
  57. /**
  58. * Path template.
  59. *
  60. * @type {string}
  61. * @private
  62. */
  63. _path;
  64. /**
  65. * Getter of the path.
  66. *
  67. * @returns {string}
  68. */
  69. get path() {
  70. return this._path;
  71. }
  72. /**
  73. * Handler.
  74. *
  75. * @type {RouteHandler}
  76. * @private
  77. */
  78. _handler;
  79. /**
  80. * Getter of the handler.
  81. *
  82. * @returns {*}
  83. */
  84. get handler() {
  85. return this._handler;
  86. }
  87. /**
  88. * Hook registry.
  89. *
  90. * @type {HookRegistry}
  91. * @private
  92. */
  93. _hookRegistry = new HookRegistry();
  94. /**
  95. * Getter of the hook registry.
  96. *
  97. * @returns {HookRegistry}
  98. */
  99. get hookRegistry() {
  100. return this._hookRegistry;
  101. }
  102. /**
  103. * Constructor.
  104. *
  105. * @param {RouteDefinition} routeDef
  106. */
  107. constructor(routeDef) {
  108. super({
  109. namespace: MODULE_DEBUG_NAMESPACE,
  110. noEnvironmentNamespace: true,
  111. noInstantiationMessage: true,
  112. });
  113. if (!routeDef || typeof routeDef !== 'object' || Array.isArray(routeDef))
  114. throw new Errorf(
  115. 'The first parameter of Route.constructor ' +
  116. 'should be an Object, but %v was given.',
  117. routeDef,
  118. );
  119. if (!routeDef.method || typeof routeDef.method !== 'string')
  120. throw new Errorf(
  121. 'The option "method" of the Route should be ' +
  122. 'a non-empty String, but %v was given.',
  123. routeDef.method,
  124. );
  125. this._method = routeDef.method.toUpperCase();
  126. if (typeof routeDef.path !== 'string')
  127. throw new Errorf(
  128. 'The option "path" of the Route should be ' +
  129. 'a String, but %v was given.',
  130. routeDef.path,
  131. );
  132. this._path = routeDef.path;
  133. if (typeof routeDef.handler !== 'function')
  134. throw new Errorf(
  135. 'The option "handler" of the Route should be ' +
  136. 'a Function, but %v was given.',
  137. routeDef.handler,
  138. );
  139. this._handler = routeDef.handler;
  140. if (routeDef.preHandler != null) {
  141. const preHandlerHooks = Array.isArray(routeDef.preHandler)
  142. ? routeDef.preHandler
  143. : [routeDef.preHandler];
  144. preHandlerHooks.forEach(hook => {
  145. this._hookRegistry.addHook(HookType.PRE_HANDLER, hook);
  146. });
  147. }
  148. if (routeDef.postHandler != null) {
  149. const postHandlerHooks = Array.isArray(routeDef.postHandler)
  150. ? routeDef.postHandler
  151. : [routeDef.postHandler];
  152. postHandlerHooks.forEach(hook => {
  153. this._hookRegistry.addHook(HookType.POST_HANDLER, hook);
  154. });
  155. }
  156. this.ctorDebug('A new route %s %v was created.', this._method, this._path);
  157. }
  158. /**
  159. * Handle request.
  160. *
  161. * @param {RequestContext} context
  162. * @returns {*}
  163. */
  164. handle(context) {
  165. const debug = this.getDebuggerFor(this.handle);
  166. const requestPath = getRequestPathname(context.req);
  167. debug(
  168. 'Invoking the Route handler for the request %s %v.',
  169. this.method.toUpperCase(),
  170. requestPath,
  171. );
  172. return this._handler(context);
  173. }
  174. }