@loopback/rest 4.0.0-alpha.8 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (388) hide show
  1. package/CHANGELOG.md +1822 -0
  2. package/LICENSE +1 -1
  3. package/README.md +30 -58
  4. package/dist/body-parsers/body-parser.d.ts +25 -0
  5. package/dist/body-parsers/body-parser.helpers.d.ts +44 -0
  6. package/dist/body-parsers/body-parser.helpers.js +102 -0
  7. package/dist/body-parsers/body-parser.helpers.js.map +1 -0
  8. package/dist/body-parsers/body-parser.js +159 -0
  9. package/dist/body-parsers/body-parser.js.map +1 -0
  10. package/dist/body-parsers/body-parser.json.d.ts +9 -0
  11. package/dist/body-parsers/body-parser.json.js +43 -0
  12. package/dist/body-parsers/body-parser.json.js.map +1 -0
  13. package/dist/body-parsers/body-parser.raw.d.ts +12 -0
  14. package/dist/body-parsers/body-parser.raw.js +39 -0
  15. package/dist/body-parsers/body-parser.raw.js.map +1 -0
  16. package/dist/body-parsers/body-parser.stream.d.ts +12 -0
  17. package/dist/body-parsers/body-parser.stream.js +28 -0
  18. package/dist/body-parsers/body-parser.stream.js.map +1 -0
  19. package/dist/body-parsers/body-parser.text.d.ts +9 -0
  20. package/dist/body-parsers/body-parser.text.js +38 -0
  21. package/dist/body-parsers/body-parser.text.js.map +1 -0
  22. package/dist/body-parsers/body-parser.urlencoded.d.ts +9 -0
  23. package/dist/body-parsers/body-parser.urlencoded.js +36 -0
  24. package/dist/body-parsers/body-parser.urlencoded.js.map +1 -0
  25. package/dist/body-parsers/index.d.ts +8 -0
  26. package/dist/body-parsers/index.js +16 -0
  27. package/dist/body-parsers/index.js.map +1 -0
  28. package/dist/body-parsers/types.d.ts +51 -0
  29. package/dist/body-parsers/types.js +12 -0
  30. package/dist/body-parsers/types.js.map +1 -0
  31. package/dist/coercion/coerce-parameter.d.ts +9 -0
  32. package/dist/coercion/coerce-parameter.js +166 -0
  33. package/dist/coercion/coerce-parameter.js.map +1 -0
  34. package/dist/coercion/utils.d.ts +43 -0
  35. package/dist/coercion/utils.js +96 -0
  36. package/dist/coercion/utils.js.map +1 -0
  37. package/dist/coercion/validator.d.ts +49 -0
  38. package/dist/coercion/validator.js +85 -0
  39. package/dist/coercion/validator.js.map +1 -0
  40. package/dist/http-handler.d.ts +38 -0
  41. package/dist/http-handler.js +68 -0
  42. package/dist/http-handler.js.map +1 -0
  43. package/dist/index.d.ts +36 -1
  44. package/dist/index.js +40 -6
  45. package/dist/index.js.map +1 -0
  46. package/dist/keys.d.ts +198 -0
  47. package/dist/keys.js +202 -0
  48. package/dist/keys.js.map +1 -0
  49. package/dist/parse-json.d.ts +11 -0
  50. package/dist/parse-json.js +42 -0
  51. package/dist/parse-json.js.map +1 -0
  52. package/dist/parser.d.ts +11 -0
  53. package/dist/parser.js +76 -0
  54. package/dist/parser.js.map +1 -0
  55. package/{dist6/src/providers/find-route.d.ts → dist/providers/find-route.provider.d.ts} +3 -1
  56. package/dist/providers/find-route.provider.js +36 -0
  57. package/dist/providers/find-route.provider.js.map +1 -0
  58. package/dist/providers/index.d.ts +6 -0
  59. package/dist/providers/index.js +14 -0
  60. package/dist/providers/index.js.map +1 -0
  61. package/dist/{src/providers/invoke-method.d.ts → providers/invoke-method.provider.d.ts} +3 -1
  62. package/dist/providers/invoke-method.provider.js +30 -0
  63. package/dist/providers/invoke-method.provider.js.map +1 -0
  64. package/dist/providers/log-error.provider.d.ts +6 -0
  65. package/dist/providers/log-error.provider.js +21 -0
  66. package/dist/providers/log-error.provider.js.map +1 -0
  67. package/dist/providers/parse-params.provider.d.ts +15 -0
  68. package/dist/providers/parse-params.provider.js +41 -0
  69. package/dist/providers/parse-params.provider.js.map +1 -0
  70. package/dist/providers/reject.provider.d.ts +10 -0
  71. package/dist/providers/reject.provider.js +47 -0
  72. package/dist/providers/reject.provider.js.map +1 -0
  73. package/dist/{src/providers/send.d.ts → providers/send.provider.d.ts} +1 -4
  74. package/dist/{src/providers/send.js → providers/send.provider.js} +4 -6
  75. package/dist/providers/send.provider.js.map +1 -0
  76. package/dist/request-context.d.ts +36 -0
  77. package/dist/request-context.js +104 -0
  78. package/dist/request-context.js.map +1 -0
  79. package/dist/rest-http-error.d.ts +37 -0
  80. package/dist/rest-http-error.js +51 -0
  81. package/dist/rest-http-error.js.map +1 -0
  82. package/dist/rest.application.d.ts +232 -0
  83. package/dist/rest.application.js +174 -0
  84. package/dist/rest.application.js.map +1 -0
  85. package/dist/rest.component.d.ts +15 -0
  86. package/dist/rest.component.js +72 -0
  87. package/dist/rest.component.js.map +1 -0
  88. package/dist/rest.server.d.ts +443 -0
  89. package/dist/rest.server.js +748 -0
  90. package/dist/rest.server.js.map +1 -0
  91. package/dist/router/base-route.d.ts +29 -0
  92. package/dist/router/base-route.js +41 -0
  93. package/dist/router/base-route.js.map +1 -0
  94. package/dist/router/controller-route.d.ts +61 -0
  95. package/dist/router/controller-route.js +160 -0
  96. package/dist/router/controller-route.js.map +1 -0
  97. package/dist/router/external-express-routes.d.ts +24 -0
  98. package/dist/router/external-express-routes.js +90 -0
  99. package/dist/router/external-express-routes.js.map +1 -0
  100. package/dist/router/handler-route.d.ts +12 -0
  101. package/dist/router/handler-route.js +30 -0
  102. package/dist/router/handler-route.js.map +1 -0
  103. package/dist/router/index.d.ts +14 -0
  104. package/dist/router/index.js +25 -0
  105. package/dist/router/index.js.map +1 -0
  106. package/dist/router/openapi-path.d.ts +14 -0
  107. package/dist/router/openapi-path.js +64 -0
  108. package/dist/router/openapi-path.js.map +1 -0
  109. package/dist/router/redirect-route.d.ts +23 -0
  110. package/dist/router/redirect-route.js +50 -0
  111. package/dist/router/redirect-route.js.map +1 -0
  112. package/dist/router/regexp-router.d.ts +25 -0
  113. package/dist/router/regexp-router.js +84 -0
  114. package/dist/router/regexp-router.js.map +1 -0
  115. package/dist/router/rest-router.d.ts +35 -0
  116. package/dist/{src/internal-types.js → router/rest-router.js} +2 -2
  117. package/dist/router/rest-router.js.map +1 -0
  118. package/dist/router/route-entry.d.ts +46 -0
  119. package/dist/router/route-entry.js +20 -0
  120. package/dist/router/route-entry.js.map +1 -0
  121. package/dist/router/route-sort.d.ts +7 -0
  122. package/dist/router/route-sort.js +75 -0
  123. package/dist/router/route-sort.js.map +1 -0
  124. package/dist/router/router-base.d.ts +42 -0
  125. package/dist/router/router-base.js +101 -0
  126. package/dist/router/router-base.js.map +1 -0
  127. package/dist/router/router-spec.d.ts +3 -0
  128. package/dist/router/router-spec.js +40 -0
  129. package/dist/router/router-spec.js.map +1 -0
  130. package/dist/router/routing-table.d.ts +32 -0
  131. package/dist/router/routing-table.js +86 -0
  132. package/dist/router/routing-table.js.map +1 -0
  133. package/dist/router/trie-router.d.ts +13 -0
  134. package/dist/router/trie-router.js +55 -0
  135. package/dist/router/trie-router.js.map +1 -0
  136. package/dist/router/trie.d.ts +59 -0
  137. package/dist/router/trie.js +180 -0
  138. package/dist/router/trie.js.map +1 -0
  139. package/{dist6/src → dist}/sequence.d.ts +28 -23
  140. package/dist/sequence.js +112 -0
  141. package/dist/sequence.js.map +1 -0
  142. package/dist/spec-enhancers/consolidate.spec-enhancer.d.ts +68 -0
  143. package/dist/spec-enhancers/consolidate.spec-enhancer.js +145 -0
  144. package/dist/spec-enhancers/consolidate.spec-enhancer.js.map +1 -0
  145. package/dist/spec-enhancers/info.spec-enhancer.d.ts +19 -0
  146. package/dist/spec-enhancers/info.spec-enhancer.js +89 -0
  147. package/dist/spec-enhancers/info.spec-enhancer.js.map +1 -0
  148. package/dist/types.d.ts +178 -0
  149. package/dist/types.js +12 -0
  150. package/dist/types.js.map +1 -0
  151. package/dist/validation/ajv-factory.provider.d.ts +12 -0
  152. package/dist/validation/ajv-factory.provider.js +87 -0
  153. package/dist/validation/ajv-factory.provider.js.map +1 -0
  154. package/dist/validation/request-body.validator.d.ts +14 -0
  155. package/dist/validation/request-body.validator.js +161 -0
  156. package/dist/validation/request-body.validator.js.map +1 -0
  157. package/dist/writer.d.ts +9 -0
  158. package/dist/writer.js +62 -0
  159. package/dist/writer.js.map +1 -0
  160. package/package.json +66 -38
  161. package/src/body-parsers/body-parser.helpers.ts +148 -0
  162. package/src/body-parsers/body-parser.json.ts +46 -0
  163. package/src/body-parsers/body-parser.raw.ts +42 -0
  164. package/src/body-parsers/body-parser.stream.ts +27 -0
  165. package/src/body-parsers/body-parser.text.ts +44 -0
  166. package/src/body-parsers/body-parser.ts +208 -0
  167. package/src/body-parsers/body-parser.urlencoded.ts +42 -0
  168. package/src/body-parsers/index.ts +13 -0
  169. package/src/body-parsers/types.ts +60 -0
  170. package/src/coercion/coerce-parameter.ts +207 -0
  171. package/src/coercion/utils.ts +103 -0
  172. package/src/coercion/validator.ts +98 -0
  173. package/src/http-handler.ts +84 -41
  174. package/src/index.ts +37 -30
  175. package/src/keys.ts +273 -20
  176. package/src/parse-json.ts +42 -0
  177. package/src/parser.ts +89 -104
  178. package/src/providers/{find-route.ts → find-route.provider.ts} +10 -7
  179. package/src/providers/index.ts +7 -9
  180. package/src/providers/{invoke-method.ts → invoke-method.provider.ts} +8 -5
  181. package/src/providers/log-error.provider.ts +27 -0
  182. package/src/providers/parse-params.provider.ts +42 -0
  183. package/src/providers/reject.provider.ts +44 -0
  184. package/src/providers/{send.ts → send.provider.ts} +2 -5
  185. package/src/request-context.ts +123 -0
  186. package/src/rest-http-error.ts +87 -0
  187. package/src/rest.application.ts +390 -0
  188. package/src/rest.component.ts +111 -0
  189. package/src/rest.server.ts +1192 -0
  190. package/src/router/base-route.ts +53 -0
  191. package/src/router/controller-route.ts +241 -0
  192. package/src/router/external-express-routes.ts +139 -0
  193. package/src/router/handler-route.ts +44 -0
  194. package/src/router/index.ts +24 -0
  195. package/src/router/openapi-path.ts +67 -0
  196. package/src/router/redirect-route.ts +64 -0
  197. package/src/router/regexp-router.ts +104 -0
  198. package/src/router/rest-router.ts +48 -0
  199. package/src/router/route-entry.ts +74 -0
  200. package/src/router/route-sort.ts +74 -0
  201. package/src/router/router-base.ts +124 -0
  202. package/src/router/router-spec.ts +36 -0
  203. package/src/router/routing-table.ts +83 -279
  204. package/src/router/trie-router.ts +57 -0
  205. package/src/router/trie.ts +233 -0
  206. package/src/sequence.ts +44 -37
  207. package/src/spec-enhancers/consolidate.spec-enhancer.ts +182 -0
  208. package/src/spec-enhancers/info.spec-enhancer.ts +92 -0
  209. package/src/types.ts +216 -0
  210. package/src/validation/ajv-factory.provider.ts +94 -0
  211. package/src/validation/request-body.validator.ts +208 -0
  212. package/src/writer.ts +41 -68
  213. package/api-docs/.DS_Store +0 -0
  214. package/api-docs/apple-touch-icon-114x114-precomposed.png +0 -0
  215. package/api-docs/apple-touch-icon-144x144-precomposed.png +0 -0
  216. package/api-docs/apple-touch-icon-57x57-precomposed.png +0 -0
  217. package/api-docs/apple-touch-icon-72x72-precomposed.png +0 -0
  218. package/api-docs/apple-touch-icon-precomposed.png +0 -0
  219. package/api-docs/apple-touch-icon.png +0 -0
  220. package/api-docs/css/bootstrap.min.css +0 -9
  221. package/api-docs/css/code-themes/arta.css +0 -158
  222. package/api-docs/css/code-themes/ascetic.css +0 -50
  223. package/api-docs/css/code-themes/brown_paper.css +0 -104
  224. package/api-docs/css/code-themes/brown_papersq.png +0 -0
  225. package/api-docs/css/code-themes/dark.css +0 -103
  226. package/api-docs/css/code-themes/default.css +0 -135
  227. package/api-docs/css/code-themes/far.css +0 -111
  228. package/api-docs/css/code-themes/github.css +0 -127
  229. package/api-docs/css/code-themes/googlecode.css +0 -144
  230. package/api-docs/css/code-themes/idea.css +0 -121
  231. package/api-docs/css/code-themes/ir_black.css +0 -104
  232. package/api-docs/css/code-themes/magula.css +0 -121
  233. package/api-docs/css/code-themes/monokai.css +0 -114
  234. package/api-docs/css/code-themes/pojoaque.css +0 -104
  235. package/api-docs/css/code-themes/pojoaque.jpg +0 -0
  236. package/api-docs/css/code-themes/rainbow.css +0 -114
  237. package/api-docs/css/code-themes/school_book.css +0 -111
  238. package/api-docs/css/code-themes/school_book.png +0 -0
  239. package/api-docs/css/code-themes/sl-theme.css +0 -45
  240. package/api-docs/css/code-themes/solarized_dark.css +0 -88
  241. package/api-docs/css/code-themes/solarized_light.css +0 -88
  242. package/api-docs/css/code-themes/sunburst.css +0 -158
  243. package/api-docs/css/code-themes/tomorrow-night-blue.css +0 -52
  244. package/api-docs/css/code-themes/tomorrow-night-bright.css +0 -51
  245. package/api-docs/css/code-themes/tomorrow-night-eighties.css +0 -51
  246. package/api-docs/css/code-themes/tomorrow-night.css +0 -52
  247. package/api-docs/css/code-themes/tomorrow.css +0 -49
  248. package/api-docs/css/code-themes/vs.css +0 -86
  249. package/api-docs/css/code-themes/xcode.css +0 -154
  250. package/api-docs/css/code-themes/zenburn.css +0 -115
  251. package/api-docs/css/main.css +0 -139
  252. package/api-docs/favicon.ico +0 -0
  253. package/api-docs/fonts/0ihfXUL2emPh0ROJezvraLO3LdcAZYWl9Si6vvxL-qU.woff +0 -0
  254. package/api-docs/fonts/OsJ2DjdpjqFRVUSto6IffLO3LdcAZYWl9Si6vvxL-qU.woff +0 -0
  255. package/api-docs/fonts/_aijTyevf54tkVDLy-dlnLO3LdcAZYWl9Si6vvxL-qU.woff +0 -0
  256. package/api-docs/index.html +0 -7082
  257. package/api-docs/js/main.js +0 -19
  258. package/api-docs/js/vendor/bootstrap.min.js +0 -6
  259. package/api-docs/js/vendor/jquery-1.10.1.min.js +0 -6
  260. package/api-docs/js/vendor/jquery.scrollTo-1.4.3.1.js +0 -218
  261. package/api-docs/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js +0 -11
  262. package/dist/src/http-handler.d.ts +0 -19
  263. package/dist/src/http-handler.js +0 -43
  264. package/dist/src/http-handler.js.map +0 -1
  265. package/dist/src/index.d.ts +0 -14
  266. package/dist/src/index.js +0 -33
  267. package/dist/src/index.js.map +0 -1
  268. package/dist/src/internal-types.d.ts +0 -67
  269. package/dist/src/internal-types.js.map +0 -1
  270. package/dist/src/keys.d.ts +0 -22
  271. package/dist/src/keys.js +0 -35
  272. package/dist/src/keys.js.map +0 -1
  273. package/dist/src/parser.d.ts +0 -11
  274. package/dist/src/parser.js +0 -98
  275. package/dist/src/parser.js.map +0 -1
  276. package/dist/src/providers/bind-element.d.ts +0 -7
  277. package/dist/src/providers/bind-element.js +0 -34
  278. package/dist/src/providers/bind-element.js.map +0 -1
  279. package/dist/src/providers/find-route.d.ts +0 -9
  280. package/dist/src/providers/find-route.js +0 -42
  281. package/dist/src/providers/find-route.js.map +0 -1
  282. package/dist/src/providers/get-from-context.d.ts +0 -7
  283. package/dist/src/providers/get-from-context.js +0 -34
  284. package/dist/src/providers/get-from-context.js.map +0 -1
  285. package/dist/src/providers/index.d.ts +0 -8
  286. package/dist/src/providers/index.js +0 -18
  287. package/dist/src/providers/index.js.map +0 -1
  288. package/dist/src/providers/invoke-method.js +0 -36
  289. package/dist/src/providers/invoke-method.js.map +0 -1
  290. package/dist/src/providers/log-error-provider.d.ts +0 -6
  291. package/dist/src/providers/log-error-provider.js +0 -17
  292. package/dist/src/providers/log-error-provider.js.map +0 -1
  293. package/dist/src/providers/parse-params.d.ts +0 -13
  294. package/dist/src/providers/parse-params.js +0 -22
  295. package/dist/src/providers/parse-params.js.map +0 -1
  296. package/dist/src/providers/reject.d.ts +0 -6
  297. package/dist/src/providers/reject.js +0 -40
  298. package/dist/src/providers/reject.js.map +0 -1
  299. package/dist/src/providers/send.js.map +0 -1
  300. package/dist/src/rest-component.d.ts +0 -12
  301. package/dist/src/rest-component.js +0 -50
  302. package/dist/src/rest-component.js.map +0 -1
  303. package/dist/src/rest-server.d.ts +0 -211
  304. package/dist/src/rest-server.js +0 -426
  305. package/dist/src/rest-server.js.map +0 -1
  306. package/dist/src/router/metadata.d.ts +0 -150
  307. package/dist/src/router/metadata.js +0 -410
  308. package/dist/src/router/metadata.js.map +0 -1
  309. package/dist/src/router/routing-table.d.ts +0 -68
  310. package/dist/src/router/routing-table.js +0 -204
  311. package/dist/src/router/routing-table.js.map +0 -1
  312. package/dist/src/sequence.d.ts +0 -81
  313. package/dist/src/sequence.js +0 -104
  314. package/dist/src/sequence.js.map +0 -1
  315. package/dist/src/writer.d.ts +0 -17
  316. package/dist/src/writer.js +0 -87
  317. package/dist/src/writer.js.map +0 -1
  318. package/dist6/index.d.ts +0 -1
  319. package/dist6/index.js +0 -12
  320. package/dist6/src/http-handler.d.ts +0 -19
  321. package/dist6/src/http-handler.js +0 -53
  322. package/dist6/src/http-handler.js.map +0 -1
  323. package/dist6/src/index.d.ts +0 -14
  324. package/dist6/src/index.js +0 -33
  325. package/dist6/src/index.js.map +0 -1
  326. package/dist6/src/internal-types.d.ts +0 -67
  327. package/dist6/src/internal-types.js +0 -7
  328. package/dist6/src/internal-types.js.map +0 -1
  329. package/dist6/src/keys.d.ts +0 -22
  330. package/dist6/src/keys.js +0 -35
  331. package/dist6/src/keys.js.map +0 -1
  332. package/dist6/src/parser.d.ts +0 -11
  333. package/dist6/src/parser.js +0 -108
  334. package/dist6/src/parser.js.map +0 -1
  335. package/dist6/src/providers/bind-element.d.ts +0 -7
  336. package/dist6/src/providers/bind-element.js +0 -34
  337. package/dist6/src/providers/bind-element.js.map +0 -1
  338. package/dist6/src/providers/find-route.js +0 -42
  339. package/dist6/src/providers/find-route.js.map +0 -1
  340. package/dist6/src/providers/get-from-context.d.ts +0 -7
  341. package/dist6/src/providers/get-from-context.js +0 -34
  342. package/dist6/src/providers/get-from-context.js.map +0 -1
  343. package/dist6/src/providers/index.d.ts +0 -8
  344. package/dist6/src/providers/index.js +0 -18
  345. package/dist6/src/providers/index.js.map +0 -1
  346. package/dist6/src/providers/invoke-method.d.ts +0 -7
  347. package/dist6/src/providers/invoke-method.js +0 -44
  348. package/dist6/src/providers/invoke-method.js.map +0 -1
  349. package/dist6/src/providers/log-error-provider.d.ts +0 -6
  350. package/dist6/src/providers/log-error-provider.js +0 -17
  351. package/dist6/src/providers/log-error-provider.js.map +0 -1
  352. package/dist6/src/providers/parse-params.d.ts +0 -13
  353. package/dist6/src/providers/parse-params.js +0 -22
  354. package/dist6/src/providers/parse-params.js.map +0 -1
  355. package/dist6/src/providers/reject.d.ts +0 -6
  356. package/dist6/src/providers/reject.js +0 -40
  357. package/dist6/src/providers/reject.js.map +0 -1
  358. package/dist6/src/providers/send.d.ts +0 -15
  359. package/dist6/src/providers/send.js +0 -24
  360. package/dist6/src/providers/send.js.map +0 -1
  361. package/dist6/src/rest-component.d.ts +0 -12
  362. package/dist6/src/rest-component.js +0 -50
  363. package/dist6/src/rest-component.js.map +0 -1
  364. package/dist6/src/rest-server.d.ts +0 -211
  365. package/dist6/src/rest-server.js +0 -444
  366. package/dist6/src/rest-server.js.map +0 -1
  367. package/dist6/src/router/metadata.d.ts +0 -150
  368. package/dist6/src/router/metadata.js +0 -410
  369. package/dist6/src/router/metadata.js.map +0 -1
  370. package/dist6/src/router/routing-table.d.ts +0 -68
  371. package/dist6/src/router/routing-table.js +0 -218
  372. package/dist6/src/router/routing-table.js.map +0 -1
  373. package/dist6/src/sequence.js +0 -114
  374. package/dist6/src/sequence.js.map +0 -1
  375. package/dist6/src/writer.d.ts +0 -17
  376. package/dist6/src/writer.js +0 -87
  377. package/dist6/src/writer.js.map +0 -1
  378. package/index.d.ts +0 -6
  379. package/index.js +0 -9
  380. package/src/internal-types.ts +0 -96
  381. package/src/providers/bind-element.ts +0 -15
  382. package/src/providers/get-from-context.ts +0 -16
  383. package/src/providers/log-error-provider.ts +0 -23
  384. package/src/providers/parse-params.ts +0 -20
  385. package/src/providers/reject.ts +0 -27
  386. package/src/rest-component.ts +0 -54
  387. package/src/rest-server.ts +0 -584
  388. package/src/router/metadata.ts +0 -517
@@ -0,0 +1,1192 @@
1
+ // Copyright IBM Corp. 2018,2020. All Rights Reserved.
2
+ // Node module: @loopback/rest
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ import {
7
+ Binding,
8
+ BindingAddress,
9
+ BindingScope,
10
+ Constructor,
11
+ ContextObserver,
12
+ createBindingFromClass,
13
+ filterByKey,
14
+ filterByTag,
15
+ inject,
16
+ Subscription,
17
+ } from '@loopback/context';
18
+ import {Application, CoreBindings, Server} from '@loopback/core';
19
+ import {BaseMiddlewareRegistry, ExpressRequestHandler} from '@loopback/express';
20
+ import {HttpServer, HttpServerOptions} from '@loopback/http-server';
21
+ import {
22
+ getControllerSpec,
23
+ OASEnhancerBindings,
24
+ OASEnhancerService,
25
+ OpenAPIObject,
26
+ OpenApiSpec,
27
+ OperationObject,
28
+ ServerObject,
29
+ } from '@loopback/openapi-v3';
30
+ import assert, {AssertionError} from 'assert';
31
+ import cors from 'cors';
32
+ import debugFactory from 'debug';
33
+ import express, {ErrorRequestHandler} from 'express';
34
+ import {PathParams} from 'express-serve-static-core';
35
+ import {IncomingMessage, ServerResponse} from 'http';
36
+ import {ServerOptions} from 'https';
37
+ import {safeDump} from 'js-yaml';
38
+ import {cloneDeep} from 'lodash';
39
+ import {ServeStaticOptions} from 'serve-static';
40
+ import {writeErrorToResponse} from 'strong-error-handler';
41
+ import {BodyParser, REQUEST_BODY_PARSER_TAG} from './body-parsers';
42
+ import {HttpHandler} from './http-handler';
43
+ import {RestBindings, RestTags} from './keys';
44
+ import {RequestContext} from './request-context';
45
+ import {
46
+ ControllerClass,
47
+ ControllerFactory,
48
+ ControllerInstance,
49
+ ControllerRoute,
50
+ createControllerFactoryForBinding,
51
+ createRoutesForController,
52
+ ExternalExpressRoutes,
53
+ RedirectRoute,
54
+ RestRouterOptions,
55
+ Route,
56
+ RouteEntry,
57
+ RouterSpec,
58
+ RoutingTable,
59
+ } from './router';
60
+ import {assignRouterSpec} from './router/router-spec';
61
+ import {DefaultSequence, SequenceFunction, SequenceHandler} from './sequence';
62
+ import {Request, RequestBodyParserOptions, Response} from './types';
63
+
64
+ const debug = debugFactory('loopback:rest:server');
65
+
66
+ export type HttpRequestListener = (
67
+ req: IncomingMessage,
68
+ res: ServerResponse,
69
+ ) => void;
70
+
71
+ export interface HttpServerLike {
72
+ requestHandler: HttpRequestListener;
73
+ }
74
+
75
+ const SequenceActions = RestBindings.SequenceActions;
76
+
77
+ /**
78
+ * A REST API server for use with Loopback.
79
+ * Add this server to your application by importing the RestComponent.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * const app = new MyApplication();
84
+ * app.component(RestComponent);
85
+ * ```
86
+ *
87
+ * To add additional instances of RestServer to your application, use the
88
+ * `.server` function:
89
+ * ```ts
90
+ * app.server(RestServer, 'nameOfYourServer');
91
+ * ```
92
+ *
93
+ * By default, one instance of RestServer will be created when the RestComponent
94
+ * is bootstrapped. This instance can be retrieved with
95
+ * `app.getServer(RestServer)`, or by calling `app.get('servers.RestServer')`
96
+ * Note that retrieving other instances of RestServer must be done using the
97
+ * server's name:
98
+ * ```ts
99
+ * const server = await app.getServer('foo')
100
+ * // OR
101
+ * const server = await app.get('servers.foo');
102
+ * ```
103
+ */
104
+ export class RestServer extends BaseMiddlewareRegistry
105
+ implements Server, HttpServerLike {
106
+ /**
107
+ * Handle incoming HTTP(S) request by invoking the corresponding
108
+ * Controller method via the configured Sequence.
109
+ *
110
+ * @example
111
+ *
112
+ * ```ts
113
+ * const app = new Application();
114
+ * app.component(RestComponent);
115
+ * // setup controllers, etc.
116
+ *
117
+ * const restServer = await app.getServer(RestServer);
118
+ * const httpServer = http.createServer(restServer.requestHandler);
119
+ * httpServer.listen(3000);
120
+ * ```
121
+ *
122
+ * @param req - The request.
123
+ * @param res - The response.
124
+ */
125
+
126
+ protected _OASEnhancer: OASEnhancerService;
127
+ public get OASEnhancer(): OASEnhancerService {
128
+ this._setupOASEnhancerIfNeeded();
129
+ return this._OASEnhancer;
130
+ }
131
+
132
+ protected _requestHandler: HttpRequestListener;
133
+ public get requestHandler(): HttpRequestListener {
134
+ if (this._requestHandler == null) {
135
+ this._setupRequestHandlerIfNeeded();
136
+ }
137
+ return this._requestHandler;
138
+ }
139
+
140
+ public readonly config: RestServerResolvedConfig;
141
+ private _basePath: string;
142
+
143
+ protected _httpHandler: HttpHandler;
144
+ protected get httpHandler(): HttpHandler {
145
+ this._setupHandlerIfNeeded();
146
+ return this._httpHandler;
147
+ }
148
+
149
+ /**
150
+ * Context event subscriptions for route related changes
151
+ */
152
+ private _routesEventSubscription: Subscription;
153
+
154
+ protected _httpServer: HttpServer | undefined;
155
+
156
+ protected _expressApp?: express.Application;
157
+
158
+ get listening(): boolean {
159
+ return this._httpServer ? this._httpServer.listening : false;
160
+ }
161
+
162
+ /**
163
+ * The base url for the server, including the basePath if set. For example,
164
+ * the value will be 'http://localhost:3000/api' if `basePath` is set to
165
+ * '/api'.
166
+ */
167
+ get url(): string | undefined {
168
+ let serverUrl = this.rootUrl;
169
+ if (!serverUrl) return serverUrl;
170
+ serverUrl = serverUrl + (this._basePath || '');
171
+ return serverUrl;
172
+ }
173
+
174
+ /**
175
+ * The root url for the server without the basePath. For example, the value
176
+ * will be 'http://localhost:3000' regardless of the `basePath`.
177
+ */
178
+ get rootUrl(): string | undefined {
179
+ return this._httpServer && this._httpServer.url;
180
+ }
181
+
182
+ /**
183
+ *
184
+ * Creates an instance of RestServer.
185
+ *
186
+ * @param app - The application instance (injected via
187
+ * CoreBindings.APPLICATION_INSTANCE).
188
+ * @param config - The configuration options (injected via
189
+ * RestBindings.CONFIG).
190
+ *
191
+ */
192
+ constructor(
193
+ @inject(CoreBindings.APPLICATION_INSTANCE) app: Application,
194
+ @inject(RestBindings.CONFIG, {optional: true})
195
+ config: RestServerConfig = {},
196
+ ) {
197
+ super(app);
198
+
199
+ this.config = resolveRestServerConfig(config);
200
+
201
+ this.bind(RestBindings.PORT).to(this.config.port);
202
+ this.bind(RestBindings.HOST).to(config.host);
203
+ this.bind(RestBindings.PATH).to(config.path);
204
+ this.bind(RestBindings.PROTOCOL).to(config.protocol ?? 'http');
205
+ this.bind(RestBindings.HTTPS_OPTIONS).to(config as ServerOptions);
206
+
207
+ if (config.requestBodyParser) {
208
+ this.bind(RestBindings.REQUEST_BODY_PARSER_OPTIONS).to(
209
+ config.requestBodyParser,
210
+ );
211
+ }
212
+
213
+ if (config.sequence) {
214
+ this.sequence(config.sequence);
215
+ }
216
+
217
+ if (config.router) {
218
+ this.bind(RestBindings.ROUTER_OPTIONS).to(config.router);
219
+ }
220
+
221
+ this.basePath(config.basePath);
222
+
223
+ this.bind(RestBindings.BASE_PATH).toDynamicValue(() => this._basePath);
224
+ this.bind(RestBindings.HANDLER).toDynamicValue(() => this.httpHandler);
225
+ }
226
+
227
+ protected _setupOASEnhancerIfNeeded() {
228
+ if (this._OASEnhancer != null) return;
229
+ this.add(
230
+ createBindingFromClass(OASEnhancerService, {
231
+ key: OASEnhancerBindings.OAS_ENHANCER_SERVICE,
232
+ }),
233
+ );
234
+ this._OASEnhancer = this.getSync(OASEnhancerBindings.OAS_ENHANCER_SERVICE);
235
+ }
236
+
237
+ protected _setupRequestHandlerIfNeeded() {
238
+ if (this._expressApp != null) return;
239
+ this._expressApp = express();
240
+ this._applyExpressSettings();
241
+ this._requestHandler = this._expressApp;
242
+
243
+ // Allow CORS support for all endpoints so that users
244
+ // can test with online SwaggerUI instance
245
+ this.expressMiddleware(cors, this.config.cors, {
246
+ injectConfiguration: false,
247
+ key: 'middleware.cors',
248
+ group: 'cors',
249
+ });
250
+
251
+ // Set up endpoints for OpenAPI spec/ui
252
+ this._setupOpenApiSpecEndpoints();
253
+
254
+ // Mount our router & request handler
255
+ this._expressApp.use(this._basePath, (req, res, next) => {
256
+ this._handleHttpRequest(req, res).catch(next);
257
+ });
258
+
259
+ // Mount our error handler
260
+ this._expressApp.use(this._unexpectedErrorHandler());
261
+ }
262
+
263
+ /**
264
+ * Get an Express handler for unexpected errors
265
+ */
266
+ protected _unexpectedErrorHandler(): ErrorRequestHandler {
267
+ const handleUnExpectedError: ErrorRequestHandler = (
268
+ err,
269
+ req,
270
+ res,
271
+ next,
272
+ ) => {
273
+ // Handle errors reported by Express middleware such as CORS
274
+ // First try to use the `REJECT` action
275
+ this.get(SequenceActions.REJECT, {optional: true})
276
+ .then(reject => {
277
+ if (reject) {
278
+ // TODO(rfeng): There is a possibility that the error is thrown
279
+ // from the `REJECT` action in the sequence
280
+ return reject({request: req, response: res}, err);
281
+ }
282
+ // Use strong-error handler directly
283
+ writeErrorToResponse(err, req, res);
284
+ })
285
+ .catch(unexpectedErr => next(unexpectedErr));
286
+ };
287
+ return handleUnExpectedError;
288
+ }
289
+
290
+ /**
291
+ * Apply express settings.
292
+ */
293
+ protected _applyExpressSettings() {
294
+ assertExists(this._expressApp, 'this._expressApp');
295
+ const settings = this.config.expressSettings;
296
+ for (const key in settings) {
297
+ this._expressApp.set(key, settings[key]);
298
+ }
299
+ if (this.config.router && typeof this.config.router.strict === 'boolean') {
300
+ this._expressApp.set('strict routing', this.config.router.strict);
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Mount /openapi.json, /openapi.yaml for specs and /swagger-ui, /explorer
306
+ * to redirect to externally hosted API explorer
307
+ */
308
+ protected _setupOpenApiSpecEndpoints() {
309
+ assertExists(this._expressApp, 'this._expressApp');
310
+ if (this.config.openApiSpec.disabled) return;
311
+ const router = express.Router();
312
+ const mapping = this.config.openApiSpec.endpointMapping!;
313
+ // Serving OpenAPI spec
314
+ for (const p in mapping) {
315
+ this.addOpenApiSpecEndpoint(p, mapping[p], router);
316
+ }
317
+ const explorerPaths = ['/swagger-ui', '/explorer'];
318
+ router.get(explorerPaths, (req, res, next) =>
319
+ this._redirectToSwaggerUI(req, res, next),
320
+ );
321
+ this.expressMiddleware('middleware.apiSpec.defaults', router, {
322
+ group: 'apiSpec',
323
+ });
324
+ }
325
+
326
+ /**
327
+ * Add a new non-controller endpoint hosting a form of the OpenAPI spec.
328
+ *
329
+ * @param path Path at which to host the copy of the OpenAPI
330
+ * @param form Form that should be rendered from that path
331
+ */
332
+ addOpenApiSpecEndpoint(
333
+ path: string,
334
+ form: OpenApiSpecForm,
335
+ router?: express.Router,
336
+ ) {
337
+ if (router == null) {
338
+ const key = `middleware.apiSpec.${path}.${form}`;
339
+ if (this.contains(key)) {
340
+ throw new Error(
341
+ `The path ${path} is already configured for OpenApi hosting`,
342
+ );
343
+ }
344
+ const newRouter = express.Router();
345
+ newRouter.get(path, (req, res) => this._serveOpenApiSpec(req, res, form));
346
+ this.expressMiddleware(
347
+ () => newRouter,
348
+ {},
349
+ {
350
+ injectConfiguration: false,
351
+ key: `middleware.apiSpec.${path}.${form}`,
352
+ group: 'apiSpec',
353
+ },
354
+ );
355
+ } else {
356
+ router.get(path, (req, res) => this._serveOpenApiSpec(req, res, form));
357
+ }
358
+ }
359
+
360
+ protected _handleHttpRequest(request: Request, response: Response) {
361
+ return this.httpHandler.handleRequest(request, response);
362
+ }
363
+
364
+ protected _setupHandlerIfNeeded() {
365
+ if (this._httpHandler) return;
366
+
367
+ // Watch for binding events
368
+ // See https://github.com/strongloop/loopback-next/issues/433
369
+ const routesObserver: ContextObserver = {
370
+ filter: binding =>
371
+ filterByKey(RestBindings.API_SPEC.key)(binding) ||
372
+ (filterByKey(/^(controllers|routes)\..+/)(binding) &&
373
+ // Exclude controller routes to avoid circular events
374
+ !filterByTag(RestTags.CONTROLLER_ROUTE)(binding)),
375
+ observe: () => {
376
+ // Rebuild the HttpHandler instance whenever a controller/route was
377
+ // added/deleted.
378
+ this._createHttpHandler();
379
+ },
380
+ };
381
+ this._routesEventSubscription = this.subscribe(routesObserver);
382
+
383
+ this._createHttpHandler();
384
+ }
385
+
386
+ /**
387
+ * Create an instance of HttpHandler and populates it with routes
388
+ */
389
+ private _createHttpHandler() {
390
+ /**
391
+ * Check if there is custom router in the context
392
+ */
393
+ const router = this.getSync(RestBindings.ROUTER, {optional: true});
394
+ const routingTable = new RoutingTable(router, this._externalRoutes);
395
+
396
+ this._httpHandler = new HttpHandler(this, this.config, routingTable);
397
+
398
+ // Remove controller routes
399
+ for (const b of this.findByTag(RestTags.CONTROLLER_ROUTE)) {
400
+ this.unbind(b.key);
401
+ }
402
+
403
+ for (const b of this.find(`${CoreBindings.CONTROLLERS}.*`)) {
404
+ const controllerName = b.key.replace(/^controllers\./, '');
405
+ const ctor = b.valueConstructor;
406
+ if (!ctor) {
407
+ throw new Error(
408
+ `The controller ${controllerName} was not bound via .toClass()`,
409
+ );
410
+ }
411
+ const apiSpec = getControllerSpec(ctor);
412
+ if (!apiSpec) {
413
+ // controller methods are specified through app.api() spec
414
+ debug('Skipping controller %s - no API spec provided', controllerName);
415
+ continue;
416
+ }
417
+
418
+ debug('Registering controller %s', controllerName);
419
+ if (apiSpec.components) {
420
+ this._httpHandler.registerApiComponents(apiSpec.components);
421
+ }
422
+ const controllerFactory = createControllerFactoryForBinding(b.key);
423
+ const routes = createRoutesForController(
424
+ apiSpec,
425
+ ctor,
426
+ controllerFactory,
427
+ );
428
+ for (const route of routes) {
429
+ const binding = this.bindRoute(route);
430
+ binding
431
+ .tag(RestTags.CONTROLLER_ROUTE)
432
+ .tag({[RestTags.CONTROLLER_BINDING]: b.key});
433
+ }
434
+ }
435
+
436
+ for (const b of this.findByTag(RestTags.REST_ROUTE)) {
437
+ // TODO(bajtos) should we support routes defined asynchronously?
438
+ const route = this.getSync<RouteEntry>(b.key);
439
+ this._httpHandler.registerRoute(route);
440
+ }
441
+
442
+ // TODO(bajtos) should we support API spec defined asynchronously?
443
+ const spec: OpenApiSpec = this.getSync(RestBindings.API_SPEC);
444
+ for (const path in spec.paths) {
445
+ for (const verb in spec.paths[path]) {
446
+ const routeSpec: OperationObject = spec.paths[path][verb];
447
+ this._setupOperation(verb, path, routeSpec);
448
+ }
449
+ }
450
+ }
451
+
452
+ private _setupOperation(verb: string, path: string, spec: OperationObject) {
453
+ const handler = spec['x-operation'];
454
+ if (typeof handler === 'function') {
455
+ // Remove a field value that cannot be represented in JSON.
456
+ // Start by creating a shallow-copy of the spec, so that we don't
457
+ // modify the original spec object provided by user.
458
+ spec = Object.assign({}, spec);
459
+ delete spec['x-operation'];
460
+
461
+ const route = new Route(verb, path, spec, handler);
462
+ this._httpHandler.registerRoute(route);
463
+ return;
464
+ }
465
+
466
+ const controllerName = spec['x-controller-name'];
467
+ if (typeof controllerName === 'string') {
468
+ const b = this.getBinding(`controllers.${controllerName}`, {
469
+ optional: true,
470
+ });
471
+ if (!b) {
472
+ throw new Error(
473
+ `Unknown controller ${controllerName} used by "${verb} ${path}"`,
474
+ );
475
+ }
476
+
477
+ const ctor = b.valueConstructor;
478
+ if (!ctor) {
479
+ throw new Error(
480
+ `The controller ${controllerName} was not bound via .toClass()`,
481
+ );
482
+ }
483
+
484
+ const controllerFactory = createControllerFactoryForBinding(b.key);
485
+ const route = new ControllerRoute(
486
+ verb,
487
+ path,
488
+ spec,
489
+ ctor,
490
+ controllerFactory,
491
+ );
492
+ this._httpHandler.registerRoute(route);
493
+ return;
494
+ }
495
+
496
+ throw new Error(
497
+ `There is no handler configured for operation "${verb} ${path}`,
498
+ );
499
+ }
500
+
501
+ private async _serveOpenApiSpec(
502
+ request: Request,
503
+ response: Response,
504
+ specForm?: OpenApiSpecForm,
505
+ ) {
506
+ const requestContext = new RequestContext(
507
+ request,
508
+ response,
509
+ this,
510
+ this.config,
511
+ );
512
+
513
+ specForm = specForm ?? {version: '3.0.0', format: 'json'};
514
+ const specObj = await this.getApiSpec(requestContext);
515
+
516
+ if (specForm.format === 'json') {
517
+ const spec = JSON.stringify(specObj, null, 2);
518
+ response.setHeader('content-type', 'application/json; charset=utf-8');
519
+ response.end(spec, 'utf-8');
520
+ } else {
521
+ const yaml = safeDump(specObj, {});
522
+ response.setHeader('content-type', 'text/yaml; charset=utf-8');
523
+ response.end(yaml, 'utf-8');
524
+ }
525
+ }
526
+ private async _redirectToSwaggerUI(
527
+ request: Request,
528
+ response: Response,
529
+ next: express.NextFunction,
530
+ ) {
531
+ const config = this.config.apiExplorer;
532
+
533
+ if (config.disabled) {
534
+ debug('Redirect to swagger-ui was disabled by configuration.');
535
+ next();
536
+ return;
537
+ }
538
+
539
+ debug('Redirecting to swagger-ui from %j.', request.originalUrl);
540
+ const requestContext = new RequestContext(
541
+ request,
542
+ response,
543
+ this,
544
+ this.config,
545
+ );
546
+ const protocol = requestContext.requestedProtocol;
547
+ const baseUrl = protocol === 'http' ? config.httpUrl : config.url;
548
+ const openApiUrl = `${requestContext.requestedBaseUrl}/openapi.json`;
549
+ const fullUrl = `${baseUrl}?url=${openApiUrl}`;
550
+ response.redirect(302, fullUrl);
551
+ }
552
+
553
+ /**
554
+ * Register a controller class with this server.
555
+ *
556
+ * @param controllerCtor - The controller class
557
+ * (constructor function).
558
+ * @returns The newly created binding, you can use the reference to
559
+ * further modify the binding, e.g. lock the value to prevent further
560
+ * modifications.
561
+ *
562
+ * @example
563
+ * ```ts
564
+ * class MyController {
565
+ * }
566
+ * app.controller(MyController).lock();
567
+ * ```
568
+ *
569
+ */
570
+ controller(controllerCtor: ControllerClass<ControllerInstance>): Binding {
571
+ return this.bind('controllers.' + controllerCtor.name).toClass(
572
+ controllerCtor,
573
+ );
574
+ }
575
+
576
+ /**
577
+ * Register a new Controller-based route.
578
+ *
579
+ * @example
580
+ * ```ts
581
+ * class MyController {
582
+ * greet(name: string) {
583
+ * return `hello ${name}`;
584
+ * }
585
+ * }
586
+ * app.route('get', '/greet', operationSpec, MyController, 'greet');
587
+ * ```
588
+ *
589
+ * @param verb - HTTP verb of the endpoint
590
+ * @param path - URL path of the endpoint
591
+ * @param spec - The OpenAPI spec describing the endpoint (operation)
592
+ * @param controllerCtor - Controller constructor
593
+ * @param controllerFactory - A factory function to create controller instance
594
+ * @param methodName - The name of the controller method
595
+ */
596
+ route<I>(
597
+ verb: string,
598
+ path: string,
599
+ spec: OperationObject,
600
+ controllerCtor: ControllerClass<I>,
601
+ controllerFactory: ControllerFactory<I>,
602
+ methodName: string,
603
+ ): Binding;
604
+
605
+ /**
606
+ * Register a new route invoking a handler function.
607
+ *
608
+ * @example
609
+ * ```ts
610
+ * function greet(name: string) {
611
+ * return `hello ${name}`;
612
+ * }
613
+ * app.route('get', '/', operationSpec, greet);
614
+ * ```
615
+ *
616
+ * @param verb - HTTP verb of the endpoint
617
+ * @param path - URL path of the endpoint
618
+ * @param spec - The OpenAPI spec describing the endpoint (operation)
619
+ * @param handler - The function to invoke with the request parameters
620
+ * described in the spec.
621
+ */
622
+ route(
623
+ verb: string,
624
+ path: string,
625
+ spec: OperationObject,
626
+ handler: Function,
627
+ ): Binding;
628
+
629
+ /**
630
+ * Register a new generic route.
631
+ *
632
+ * @example
633
+ * ```ts
634
+ * function greet(name: string) {
635
+ * return `hello ${name}`;
636
+ * }
637
+ * const route = new Route('get', '/', operationSpec, greet);
638
+ * app.route(route);
639
+ * ```
640
+ *
641
+ * @param route - The route to add.
642
+ */
643
+ route(route: RouteEntry): Binding;
644
+
645
+ route<T>(
646
+ routeOrVerb: RouteEntry | string,
647
+ path?: string,
648
+ spec?: OperationObject,
649
+ controllerCtorOrHandler?: ControllerClass<T> | Function,
650
+ controllerFactory?: ControllerFactory<T>,
651
+ methodName?: string,
652
+ ): Binding {
653
+ if (typeof routeOrVerb === 'object') {
654
+ const r = routeOrVerb;
655
+ // Encode the path to escape special chars
656
+ return this.bindRoute(r);
657
+ }
658
+
659
+ if (!path) {
660
+ throw new AssertionError({
661
+ message: 'path is required for a controller-based route',
662
+ });
663
+ }
664
+
665
+ if (!spec) {
666
+ throw new AssertionError({
667
+ message: 'spec is required for a controller-based route',
668
+ });
669
+ }
670
+
671
+ if (arguments.length === 4) {
672
+ if (!controllerCtorOrHandler) {
673
+ throw new AssertionError({
674
+ message: 'handler function is required for a handler-based route',
675
+ });
676
+ }
677
+ return this.route(
678
+ new Route(routeOrVerb, path, spec, controllerCtorOrHandler as Function),
679
+ );
680
+ }
681
+
682
+ if (!controllerCtorOrHandler) {
683
+ throw new AssertionError({
684
+ message: 'controller is required for a controller-based route',
685
+ });
686
+ }
687
+
688
+ if (!methodName) {
689
+ throw new AssertionError({
690
+ message: 'methodName is required for a controller-based route',
691
+ });
692
+ }
693
+
694
+ return this.route(
695
+ new ControllerRoute(
696
+ routeOrVerb,
697
+ path,
698
+ spec,
699
+ controllerCtorOrHandler as ControllerClass<T>,
700
+ controllerFactory,
701
+ methodName,
702
+ ),
703
+ );
704
+ }
705
+
706
+ private bindRoute(r: RouteEntry) {
707
+ const namespace = RestBindings.ROUTES;
708
+ const encodedPath = encodeURIComponent(r.path).replace(/\./g, '%2E');
709
+ return this.bind(`${namespace}.${r.verb} ${encodedPath}`)
710
+ .to(r)
711
+ .tag(RestTags.REST_ROUTE)
712
+ .tag({[RestTags.ROUTE_VERB]: r.verb, [RestTags.ROUTE_PATH]: r.path});
713
+ }
714
+
715
+ /**
716
+ * Register a route redirecting callers to a different URL.
717
+ *
718
+ * @example
719
+ * ```ts
720
+ * server.redirect('/explorer', '/explorer/');
721
+ * ```
722
+ *
723
+ * @param fromPath - URL path of the redirect endpoint
724
+ * @param toPathOrUrl - Location (URL path or full URL) where to redirect to.
725
+ * If your server is configured with a custom `basePath`, then the base path
726
+ * is prepended to the target location.
727
+ * @param statusCode - HTTP status code to respond with,
728
+ * defaults to 303 (See Other).
729
+ */
730
+ redirect(
731
+ fromPath: string,
732
+ toPathOrUrl: string,
733
+ statusCode?: number,
734
+ ): Binding {
735
+ return this.route(
736
+ new RedirectRoute(fromPath, this._basePath + toPathOrUrl, statusCode),
737
+ );
738
+ }
739
+
740
+ /*
741
+ * Registry of external routes & static assets
742
+ */
743
+ private _externalRoutes = new ExternalExpressRoutes();
744
+
745
+ /**
746
+ * Mount static assets to the REST server.
747
+ * See https://expressjs.com/en/4x/api.html#express.static
748
+ * @param path - The path(s) to serve the asset.
749
+ * See examples at https://expressjs.com/en/4x/api.html#path-examples
750
+ * @param rootDir - The root directory from which to serve static assets
751
+ * @param options - Options for serve-static
752
+ */
753
+ static(path: PathParams, rootDir: string, options?: ServeStaticOptions) {
754
+ this._externalRoutes.registerAssets(path, rootDir, options);
755
+ }
756
+
757
+ /**
758
+ * Set the OpenAPI specification that defines the REST API schema for this
759
+ * server. All routes, parameter definitions and return types will be defined
760
+ * in this way.
761
+ *
762
+ * Note that this will override any routes defined via decorators at the
763
+ * controller level (this function takes precedent).
764
+ *
765
+ * @param spec - The OpenAPI specification, as an object.
766
+ * @returns Binding for the spec
767
+ *
768
+ */
769
+ api(spec: OpenApiSpec): Binding {
770
+ return this.bind(RestBindings.API_SPEC).to(spec);
771
+ }
772
+
773
+ /**
774
+ * Get the OpenAPI specification describing the REST API provided by
775
+ * this application.
776
+ *
777
+ * This method merges operations (HTTP endpoints) from the following sources:
778
+ * - `app.api(spec)`
779
+ * - `app.controller(MyController)`
780
+ * - `app.route(route)`
781
+ * - `app.route('get', '/greet', operationSpec, MyController, 'greet')`
782
+ *
783
+ * If the optional `requestContext` is provided, then the `servers` list
784
+ * in the returned spec will be updated to work in that context.
785
+ * Specifically:
786
+ * 1. if `config.openApi.setServersFromRequest` is enabled, the servers
787
+ * list will be replaced with the context base url
788
+ * 2. Any `servers` entries with a path of `/` will have that path
789
+ * replaced with `requestContext.basePath`
790
+ *
791
+ * @param requestContext - Optional context to update the `servers` list
792
+ * in the returned spec
793
+ */
794
+ async getApiSpec(requestContext?: RequestContext): Promise<OpenApiSpec> {
795
+ let spec = await this.get<OpenApiSpec>(RestBindings.API_SPEC);
796
+ const components = this.httpHandler.getApiComponents();
797
+
798
+ // Apply deep clone to prevent getApiSpec() callers from
799
+ // accidentally modifying our internal routing data
800
+ spec.paths = cloneDeep(this.httpHandler.describeApiPaths());
801
+ if (components) {
802
+ const defs = cloneDeep(components);
803
+ spec.components = {...spec.components, ...defs};
804
+ }
805
+
806
+ assignRouterSpec(spec, this._externalRoutes.routerSpec);
807
+
808
+ if (requestContext) {
809
+ spec = this.updateSpecFromRequest(spec, requestContext);
810
+ }
811
+
812
+ // Apply OAS enhancers to the OpenAPI specification
813
+ this.OASEnhancer.spec = spec;
814
+ spec = await this.OASEnhancer.applyAllEnhancers();
815
+
816
+ return spec;
817
+ }
818
+
819
+ /**
820
+ * Update or rebuild OpenAPI Spec object to be appropriate for the context of
821
+ * a specific request for the spec, leveraging both app config and request
822
+ * path information.
823
+ *
824
+ * @param spec base spec object from which to start
825
+ * @param requestContext request to use to infer path information
826
+ * @returns Updated or rebuilt spec object to use in the context of the request
827
+ */
828
+ private updateSpecFromRequest(
829
+ spec: OpenAPIObject,
830
+ requestContext: RequestContext,
831
+ ) {
832
+ if (this.config.openApiSpec.setServersFromRequest) {
833
+ spec = Object.assign({}, spec);
834
+ spec.servers = [{url: requestContext.requestedBaseUrl}];
835
+ }
836
+
837
+ const basePath = requestContext.basePath;
838
+ if (spec.servers && basePath) {
839
+ for (const s of spec.servers) {
840
+ // Update the default server url to honor `basePath`
841
+ if (s.url === '/') {
842
+ s.url = basePath;
843
+ }
844
+ }
845
+ }
846
+
847
+ return spec;
848
+ }
849
+
850
+ /**
851
+ * Configure a custom sequence class for handling incoming requests.
852
+ *
853
+ * @example
854
+ * ```ts
855
+ * class MySequence implements SequenceHandler {
856
+ * constructor(
857
+ * @inject('send) public send: Send)) {
858
+ * }
859
+ *
860
+ * public async handle({response}: RequestContext) {
861
+ * send(response, 'hello world');
862
+ * }
863
+ * }
864
+ * ```
865
+ *
866
+ * @param value - The sequence to invoke for each incoming request.
867
+ */
868
+ public sequence(value: Constructor<SequenceHandler>) {
869
+ this.bind(RestBindings.SEQUENCE).toClass(value);
870
+ }
871
+
872
+ /**
873
+ * Configure a custom sequence function for handling incoming requests.
874
+ *
875
+ * @example
876
+ * ```ts
877
+ * app.handler(({request, response}, sequence) => {
878
+ * sequence.send(response, 'hello world');
879
+ * });
880
+ * ```
881
+ *
882
+ * @param handlerFn - The handler to invoke for each incoming request.
883
+ */
884
+ public handler(handlerFn: SequenceFunction) {
885
+ class SequenceFromFunction extends DefaultSequence {
886
+ async handle(context: RequestContext): Promise<void> {
887
+ return handlerFn(context, this);
888
+ }
889
+ }
890
+
891
+ this.sequence(SequenceFromFunction);
892
+ }
893
+
894
+ /**
895
+ * Bind a body parser to the server context
896
+ * @param parserClass - Body parser class
897
+ * @param address - Optional binding address
898
+ */
899
+ bodyParser(
900
+ bodyParserClass: Constructor<BodyParser>,
901
+ address?: BindingAddress<BodyParser>,
902
+ ): Binding<BodyParser> {
903
+ const binding = createBodyParserBinding(bodyParserClass, address);
904
+ this.add(binding);
905
+ return binding;
906
+ }
907
+
908
+ /**
909
+ * Configure the `basePath` for the rest server
910
+ * @param path - Base path
911
+ */
912
+ basePath(path = '') {
913
+ if (this._requestHandler != null) {
914
+ throw new Error(
915
+ 'Base path cannot be set as the request handler has been created',
916
+ );
917
+ }
918
+ // Trim leading and trailing `/`
919
+ path = path.replace(/(^\/)|(\/$)/, '');
920
+ if (path) path = '/' + path;
921
+ this._basePath = path;
922
+ this.config.basePath = path;
923
+ }
924
+
925
+ /**
926
+ * Start this REST API's HTTP/HTTPS server.
927
+ */
928
+ async start(): Promise<void> {
929
+ // Set up the Express app if not done yet
930
+ this._setupRequestHandlerIfNeeded();
931
+ // Setup the HTTP handler so that we can verify the configuration
932
+ // of API spec, controllers and routes at startup time.
933
+ this._setupHandlerIfNeeded();
934
+
935
+ const port = await this.get(RestBindings.PORT);
936
+ const host = await this.get(RestBindings.HOST);
937
+ const path = await this.get(RestBindings.PATH);
938
+ const protocol = await this.get(RestBindings.PROTOCOL);
939
+ const httpsOptions = await this.get(RestBindings.HTTPS_OPTIONS);
940
+
941
+ if (this.config.listenOnStart === false) {
942
+ debug(
943
+ 'RestServer is not listening as listenOnStart flag is set to false.',
944
+ );
945
+ return;
946
+ }
947
+
948
+ const serverOptions = {};
949
+ if (protocol === 'https') Object.assign(serverOptions, httpsOptions);
950
+ Object.assign(serverOptions, {port, host, protocol, path});
951
+
952
+ this._httpServer = new HttpServer(this.requestHandler, serverOptions);
953
+
954
+ await this._httpServer.start();
955
+
956
+ this.bind(RestBindings.PORT).to(this._httpServer.port);
957
+ this.bind(RestBindings.HOST).to(this._httpServer.host);
958
+ this.bind(RestBindings.URL).to(this._httpServer.url);
959
+ debug('RestServer listening at %s', this._httpServer.url);
960
+ }
961
+
962
+ /**
963
+ * Stop this REST API's HTTP/HTTPS server.
964
+ */
965
+ async stop() {
966
+ // Kill the server instance.
967
+ if (!this._httpServer) return;
968
+ await this._httpServer.stop();
969
+ this._httpServer = undefined;
970
+ }
971
+
972
+ /**
973
+ * Mount an Express router to expose additional REST endpoints handled
974
+ * via legacy Express-based stack.
975
+ *
976
+ * @param basePath - Path where to mount the router at, e.g. `/` or `/api`.
977
+ * @param router - The Express router to handle the requests.
978
+ * @param spec - A partial OpenAPI spec describing endpoints provided by the
979
+ * router. LoopBack will prepend `basePath` to all endpoints automatically.
980
+ * This argument is optional. You can leave it out if you don't want to
981
+ * document the routes.
982
+ */
983
+ mountExpressRouter(
984
+ basePath: string,
985
+ router: ExpressRequestHandler,
986
+ spec?: RouterSpec,
987
+ ): void {
988
+ this._externalRoutes.mountRouter(basePath, router, spec);
989
+ }
990
+ }
991
+
992
+ /**
993
+ * An assertion type guard for TypeScript to instruct the compiler that the
994
+ * given value is not `null` or `undefined.
995
+ * @param val - A value can be `undefined` or `null`
996
+ * @param name - Name of the value
997
+ */
998
+ function assertExists<T>(val: T, name: string): asserts val is NonNullable<T> {
999
+ assert(val != null, `The value of ${name} cannot be null or undefined`);
1000
+ }
1001
+
1002
+ /**
1003
+ * Create a binding for the given body parser class
1004
+ * @param parserClass - Body parser class
1005
+ * @param key - Optional binding address
1006
+ */
1007
+ export function createBodyParserBinding(
1008
+ parserClass: Constructor<BodyParser>,
1009
+ key?: BindingAddress<BodyParser>,
1010
+ ): Binding<BodyParser> {
1011
+ const address =
1012
+ key ?? `${RestBindings.REQUEST_BODY_PARSER}.${parserClass.name}`;
1013
+ return Binding.bind<BodyParser>(address)
1014
+ .toClass(parserClass)
1015
+ .inScope(BindingScope.TRANSIENT)
1016
+ .tag(REQUEST_BODY_PARSER_TAG);
1017
+ }
1018
+
1019
+ /**
1020
+ * The form of OpenAPI specs to be served
1021
+ */
1022
+ export interface OpenApiSpecForm {
1023
+ version?: string;
1024
+ format?: string;
1025
+ }
1026
+
1027
+ const OPENAPI_SPEC_MAPPING: {[key: string]: OpenApiSpecForm} = {
1028
+ '/openapi.json': {version: '3.0.0', format: 'json'},
1029
+ '/openapi.yaml': {version: '3.0.0', format: 'yaml'},
1030
+ };
1031
+
1032
+ /**
1033
+ * Options to customize how OpenAPI specs are served
1034
+ */
1035
+ export interface OpenApiSpecOptions {
1036
+ /**
1037
+ * Mapping of urls to spec forms, by default:
1038
+ * ```
1039
+ * {
1040
+ * '/openapi.json': {version: '3.0.0', format: 'json'},
1041
+ * '/openapi.yaml': {version: '3.0.0', format: 'yaml'},
1042
+ * }
1043
+ * ```
1044
+ */
1045
+ endpointMapping?: {[key: string]: OpenApiSpecForm};
1046
+
1047
+ /**
1048
+ * A flag to force `servers` to be set from the http request for the OpenAPI
1049
+ * spec
1050
+ */
1051
+ setServersFromRequest?: boolean;
1052
+
1053
+ /**
1054
+ * Configure servers for OpenAPI spec
1055
+ */
1056
+ servers?: ServerObject[];
1057
+ /**
1058
+ * Set this flag to disable the endpoint for OpenAPI spec
1059
+ */
1060
+ disabled?: true;
1061
+
1062
+ /**
1063
+ * Set this flag to `false` to disable OAS schema consolidation. If not set,
1064
+ * the value defaults to `true`.
1065
+ */
1066
+ consolidate?: boolean;
1067
+ }
1068
+
1069
+ export interface ApiExplorerOptions {
1070
+ /**
1071
+ * URL for the hosted API explorer UI
1072
+ * default to https://loopback.io/api-explorer
1073
+ */
1074
+ url?: string;
1075
+
1076
+ /**
1077
+ * URL for the API explorer served over `http` protocol to deal with mixed
1078
+ * content security imposed by browsers as the spec is exposed over `http` by
1079
+ * default.
1080
+ * See https://github.com/strongloop/loopback-next/issues/1603
1081
+ */
1082
+ httpUrl?: string;
1083
+
1084
+ /**
1085
+ * Set this flag to disable the built-in redirect to externally
1086
+ * hosted API Explorer UI.
1087
+ */
1088
+ disabled?: true;
1089
+ }
1090
+
1091
+ /**
1092
+ * RestServer options
1093
+ */
1094
+ export type RestServerOptions = Partial<RestServerResolvedOptions>;
1095
+
1096
+ export interface RestServerResolvedOptions {
1097
+ port: number;
1098
+ path?: string;
1099
+
1100
+ /**
1101
+ * Base path for API/static routes
1102
+ */
1103
+ basePath?: string;
1104
+ cors: cors.CorsOptions;
1105
+ openApiSpec: OpenApiSpecOptions;
1106
+ apiExplorer: ApiExplorerOptions;
1107
+ requestBodyParser?: RequestBodyParserOptions;
1108
+ sequence?: Constructor<SequenceHandler>;
1109
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1110
+ expressSettings: {[name: string]: any};
1111
+ router: RestRouterOptions;
1112
+
1113
+ /**
1114
+ * Set this flag to `false` to not listen on connections when the REST server
1115
+ * is started. It's useful to mount a LoopBack REST server as a route to the
1116
+ * facade Express application. If not set, the value is default to `true`.
1117
+ */
1118
+ listenOnStart?: boolean;
1119
+ }
1120
+
1121
+ /**
1122
+ * Valid configuration for the RestServer constructor.
1123
+ */
1124
+ export type RestServerConfig = RestServerOptions & HttpServerOptions;
1125
+
1126
+ export type RestServerResolvedConfig = RestServerResolvedOptions &
1127
+ HttpServerOptions;
1128
+
1129
+ const DEFAULT_CONFIG: RestServerResolvedConfig = {
1130
+ port: 3000,
1131
+ openApiSpec: {},
1132
+ apiExplorer: {},
1133
+ cors: {
1134
+ origin: '*',
1135
+ methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
1136
+ preflightContinue: false,
1137
+ optionsSuccessStatus: 204,
1138
+ maxAge: 86400,
1139
+ credentials: true,
1140
+ },
1141
+ expressSettings: {},
1142
+ router: {},
1143
+ listenOnStart: true,
1144
+ };
1145
+
1146
+ function resolveRestServerConfig(
1147
+ config: RestServerConfig,
1148
+ ): RestServerResolvedConfig {
1149
+ const result: RestServerResolvedConfig = Object.assign(
1150
+ cloneDeep(DEFAULT_CONFIG),
1151
+ config,
1152
+ );
1153
+
1154
+ // Can't check falsiness, 0 is a valid port.
1155
+ if (result.port == null) {
1156
+ result.port = 3000;
1157
+ }
1158
+
1159
+ if (result.host == null) {
1160
+ // Set it to '' so that the http server will listen on all interfaces
1161
+ result.host = undefined;
1162
+ }
1163
+
1164
+ if (!result.openApiSpec.endpointMapping) {
1165
+ // mapping may be mutated by addOpenApiSpecEndpoint, be sure that doesn't
1166
+ // pollute the default mapping configuration
1167
+ result.openApiSpec.endpointMapping = cloneDeep(OPENAPI_SPEC_MAPPING);
1168
+ }
1169
+
1170
+ result.apiExplorer = normalizeApiExplorerConfig(config.apiExplorer);
1171
+
1172
+ if (result.openApiSpec.disabled) {
1173
+ // Disable apiExplorer if the OpenAPI spec endpoint is disabled
1174
+ result.apiExplorer.disabled = true;
1175
+ }
1176
+
1177
+ return result;
1178
+ }
1179
+
1180
+ function normalizeApiExplorerConfig(
1181
+ input: ApiExplorerOptions | undefined,
1182
+ ): ApiExplorerOptions {
1183
+ const config = input ?? {};
1184
+ const url = config.url ?? 'https://explorer.loopback.io';
1185
+
1186
+ config.httpUrl =
1187
+ config.httpUrl ?? config.url ?? 'http://explorer.loopback.io';
1188
+
1189
+ config.url = url;
1190
+
1191
+ return config;
1192
+ }