server/services/api_service/service.js

  1. 'use strict';
  2. /* eslint-disable security/detect-object-injection */
  3. /**
  4. * Module dependencies, required for ALL Twyr' modules
  5. * @ignore
  6. */
  7. /**
  8. * Module dependencies, required for this module
  9. * @ignore
  10. */
  11. const TwyrBaseService = require('twyr-base-service').TwyrBaseService;
  12. const TwyrSrvcError = require('twyr-service-error').TwyrServiceError;
  13. /**
  14. * @class ApiService
  15. * @extends {TwyrBaseService}
  16. * @classdesc The Twyr Web Application Server API Service.
  17. *
  18. * @description
  19. * Allows the rest of the Twyr Modules to communicate with each other by allowing registration / invocation of API.
  20. *
  21. */
  22. class ApiService extends TwyrBaseService {
  23. // #region Constructor
  24. constructor(parent, loader) {
  25. super(parent, loader);
  26. }
  27. // #endregion
  28. // #region startup/teardown code
  29. /**
  30. * @async
  31. * @function
  32. * @override
  33. * @instance
  34. * @memberof ApiService
  35. * @name _setup
  36. *
  37. * @returns {null} Nothing.
  38. *
  39. * @summary Sets up the broker to manage API exposed by other modules.
  40. */
  41. async _setup() {
  42. try {
  43. await super._setup();
  44. const customMatch = function(pattern, data) {
  45. const items = this.find(pattern, true) || [];
  46. items.push(data);
  47. return {
  48. 'find': function() {
  49. return items.length ? items : [];
  50. },
  51. 'remove': function(search, api) {
  52. const apiIdx = items.indexOf(api);
  53. if(apiIdx < 0) return false;
  54. items.splice(apiIdx, 1);
  55. return true;
  56. }
  57. };
  58. };
  59. this.$patrun = require('patrun')(customMatch);
  60. return null;
  61. }
  62. catch(err) {
  63. throw new TwyrSrvcError(`${this.name}::_setup error`, err);
  64. }
  65. }
  66. /**
  67. * @async
  68. * @function
  69. * @override
  70. * @instance
  71. * @memberof ApiService
  72. * @name _teardown
  73. *
  74. * @returns {undefined} Nothing.
  75. *
  76. * @summary Deletes the broker that manages API.
  77. */
  78. async _teardown() {
  79. try {
  80. if(this.$patrun) delete this.$patrun;
  81. await super._teardown();
  82. return null;
  83. }
  84. catch(err) {
  85. throw new TwyrSrvcError(`${this.name}::_teardown error`, err);
  86. }
  87. }
  88. // #endregion
  89. // #region API
  90. /**
  91. * @async
  92. * @function
  93. * @instance
  94. * @memberof ApiService
  95. * @name add
  96. *
  97. * @param {string} pattern - The pattern to which this api will respond.
  98. * @param {Function} api - The api to be invoked against the pattern.
  99. *
  100. * @returns {boolean} Boolean true/false - depending on whether the registration succeeded.
  101. *
  102. * @summary Registers the api function as a handler for the pattern.
  103. */
  104. async add(pattern, api) {
  105. try {
  106. // eslint-disable-next-line curly
  107. if(typeof api !== 'function') {
  108. throw new Error(`${this.name}::add expects a function for the pattern: ${pattern}`);
  109. }
  110. pattern = pattern.split('::').reduce((obj, value) => {
  111. obj[value] = value;
  112. return obj;
  113. }, {});
  114. this.$patrun.add(pattern, api);
  115. return true;
  116. }
  117. catch(err) {
  118. throw new TwyrSrvcError(`${this.name}::add error`, err);
  119. }
  120. }
  121. /**
  122. * @async
  123. * @function
  124. * @instance
  125. * @memberof ApiService
  126. * @name execute
  127. *
  128. * @param {string} pattern - The pattern to be executed.
  129. * @param {Object} data - The data to be passed in as arguments to each of the api registered against the pattern.
  130. *
  131. * @returns {Array} The results of the execution.
  132. *
  133. * @summary Executes all the apis registered as handlers for the pattern.
  134. */
  135. async execute(pattern, data) {
  136. try {
  137. pattern = pattern.split('::').reduce((obj, value) => {
  138. obj[value] = value;
  139. return obj;
  140. }, {});
  141. if(!Array.isArray(data))
  142. data = [data];
  143. const apis = this.$patrun.find(pattern);
  144. const results = [];
  145. let errors = null;
  146. for(const api of apis) { // eslint-disable-line curly
  147. try {
  148. const result = await api(...data);
  149. results.push(result);
  150. }
  151. catch(execErr) {
  152. if(!errors)
  153. errors = new TwyrSrvcError(execErr);
  154. else
  155. errors = new TwyrSrvcError(execErr, errors);
  156. }
  157. }
  158. if(!errors)
  159. return results;
  160. else
  161. throw errors;
  162. }
  163. catch(err) {
  164. throw new TwyrSrvcError(`${this.name}::execute error`, err);
  165. }
  166. }
  167. /**
  168. * @async
  169. * @function
  170. * @instance
  171. * @memberof ApiService
  172. * @name remove
  173. *
  174. * @param {string} pattern - The pattern to which this api will respond.
  175. * @param {Function} api - The api to be de-registered against the pattern.
  176. *
  177. * @returns {boolean} Boolean true/false - depending on whether the de-registration succeeded.
  178. *
  179. * @summary De-registers the api function as a handler for the pattern.
  180. */
  181. async remove(pattern, api) {
  182. try {
  183. // eslint-disable-next-line curly
  184. if(typeof api !== 'function') {
  185. throw new Error(`${this.name}::remove expects a function for the pattern: ${pattern}`);
  186. }
  187. pattern = pattern.split('::').reduce((obj, value) => {
  188. obj[value] = value;
  189. return obj;
  190. }, {});
  191. this.$patrun.remove(pattern, api);
  192. return true;
  193. }
  194. catch(err) {
  195. throw new TwyrSrvcError(`${this.name}::remove error`, err);
  196. }
  197. }
  198. // #endregion
  199. // #region Properties
  200. /**
  201. * @override
  202. */
  203. get Interface() {
  204. return {
  205. 'add': this.add.bind(this),
  206. 'execute': this.execute.bind(this),
  207. 'remove': this.remove.bind(this)
  208. };
  209. }
  210. /**
  211. * @override
  212. */
  213. get dependencies() {
  214. return ['ConfigurationService', 'LoggerService'].concat(super.dependencies);
  215. }
  216. /**
  217. * @override
  218. */
  219. get basePath() {
  220. return __dirname;
  221. }
  222. // #endregion
  223. }
  224. exports.service = ApiService;