route.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import {Errorf} from '@e22m4u/js-format';
  2. import {Debuggable} from '@e22m4u/js-debug';
  3. import {HookRegistry, RouterHookType} from './hooks/index.js';
  4. import {MODULE_DEBUG_NAMESPACE} from './debuggable-service.js';
  5. import {cloneDeep, getRequestPathname} from './utils/index.js';
  6. /**
  7. * @typedef {import('./request-context.js').RequestContext} RequestContext
  8. * @typedef {(ctx: RequestContext) => *} RoutePreHandler
  9. * @typedef {(ctx: RequestContext) => *} RouteHandler
  10. * @typedef {(ctx: RequestContext, data: *) => *} RoutePostHandler
  11. * @typedef {{
  12. * method: string,
  13. * path: string,
  14. * handler: RouteHandler,
  15. * preHandler?: RoutePreHandler|(RoutePreHandler[]),
  16. * postHandler?: RoutePostHandler|(RoutePostHandler[]),
  17. * meta?: object,
  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. * Meta.
  74. *
  75. * @type {object}
  76. */
  77. _meta = {};
  78. /**
  79. * Getter of the meta.
  80. *
  81. * @returns {object}
  82. */
  83. get meta() {
  84. return this._meta;
  85. }
  86. /**
  87. * Handler.
  88. *
  89. * @type {RouteHandler}
  90. * @private
  91. */
  92. _handler;
  93. /**
  94. * Getter of the handler.
  95. *
  96. * @returns {*}
  97. */
  98. get handler() {
  99. return this._handler;
  100. }
  101. /**
  102. * Hook registry.
  103. *
  104. * @type {HookRegistry}
  105. * @private
  106. */
  107. _hookRegistry = new HookRegistry();
  108. /**
  109. * Getter of the hook registry.
  110. *
  111. * @returns {HookRegistry}
  112. */
  113. get hookRegistry() {
  114. return this._hookRegistry;
  115. }
  116. /**
  117. * Constructor.
  118. *
  119. * @param {RouteDefinition} routeDef
  120. */
  121. constructor(routeDef) {
  122. super({
  123. namespace: MODULE_DEBUG_NAMESPACE,
  124. noEnvironmentNamespace: true,
  125. noInstantiationMessage: true,
  126. });
  127. if (!routeDef || typeof routeDef !== 'object' || Array.isArray(routeDef))
  128. throw new Errorf(
  129. 'The first parameter of Route.constructor ' +
  130. 'should be an Object, but %v was given.',
  131. routeDef,
  132. );
  133. if (!routeDef.method || typeof routeDef.method !== 'string')
  134. throw new Errorf(
  135. 'The option "method" of the Route should be ' +
  136. 'a non-empty String, but %v was given.',
  137. routeDef.method,
  138. );
  139. this._method = routeDef.method.toUpperCase();
  140. if (typeof routeDef.path !== 'string')
  141. throw new Errorf(
  142. 'The option "path" of the Route should be ' +
  143. 'a String, but %v was given.',
  144. routeDef.path,
  145. );
  146. this._path = routeDef.path;
  147. if (typeof routeDef.handler !== 'function')
  148. throw new Errorf(
  149. 'The option "handler" of the Route should be ' +
  150. 'a Function, but %v was given.',
  151. routeDef.handler,
  152. );
  153. if (routeDef.meta != null) {
  154. if (typeof routeDef.meta !== 'object' || Array.isArray(routeDef.meta))
  155. throw new Errorf(
  156. 'The option "meta" of the Route should be ' +
  157. 'a plain Object, but %v was given.',
  158. routeDef.meta,
  159. );
  160. this._meta = cloneDeep(routeDef.meta);
  161. }
  162. this._handler = routeDef.handler;
  163. if (routeDef.preHandler != null) {
  164. const preHandlerHooks = Array.isArray(routeDef.preHandler)
  165. ? routeDef.preHandler
  166. : [routeDef.preHandler];
  167. preHandlerHooks.forEach(hook => {
  168. this._hookRegistry.addHook(RouterHookType.PRE_HANDLER, hook);
  169. });
  170. }
  171. if (routeDef.postHandler != null) {
  172. const postHandlerHooks = Array.isArray(routeDef.postHandler)
  173. ? routeDef.postHandler
  174. : [routeDef.postHandler];
  175. postHandlerHooks.forEach(hook => {
  176. this._hookRegistry.addHook(RouterHookType.POST_HANDLER, hook);
  177. });
  178. }
  179. this.ctorDebug('A new route %s %v was created.', this._method, this._path);
  180. }
  181. /**
  182. * Handle request.
  183. *
  184. * @param {RequestContext} context
  185. * @returns {*}
  186. */
  187. handle(context) {
  188. const debug = this.getDebuggerFor(this.handle);
  189. const requestPath = getRequestPathname(context.request);
  190. debug(
  191. 'Invoking the Route handler for the request %s %v.',
  192. this.method.toUpperCase(),
  193. requestPath,
  194. );
  195. return this._handler(context);
  196. }
  197. }