@loopback/rest 5.1.0 → 5.2.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 (107) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/body-parsers/body-parser.d.ts +1 -1
  3. package/dist/body-parsers/body-parser.js +112 -115
  4. package/dist/body-parsers/body-parser.js.map +1 -1
  5. package/dist/body-parsers/body-parser.json.js +24 -27
  6. package/dist/body-parsers/body-parser.json.js.map +1 -1
  7. package/dist/body-parsers/body-parser.raw.js +19 -22
  8. package/dist/body-parsers/body-parser.raw.js.map +1 -1
  9. package/dist/body-parsers/body-parser.text.js +21 -24
  10. package/dist/body-parsers/body-parser.text.js.map +1 -1
  11. package/dist/body-parsers/body-parser.urlencoded.js +19 -22
  12. package/dist/body-parsers/body-parser.urlencoded.js.map +1 -1
  13. package/dist/coercion/coerce-parameter.d.ts +3 -1
  14. package/dist/coercion/coerce-parameter.js +28 -12
  15. package/dist/coercion/coerce-parameter.js.map +1 -1
  16. package/dist/http-handler.d.ts +1 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +2 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/keys.d.ts +1 -1
  21. package/dist/keys.js +33 -34
  22. package/dist/keys.js.map +1 -1
  23. package/dist/parser.js +1 -1
  24. package/dist/parser.js.map +1 -1
  25. package/dist/providers/find-route.provider.d.ts +1 -1
  26. package/dist/providers/find-route.provider.js +21 -24
  27. package/dist/providers/find-route.provider.js.map +1 -1
  28. package/dist/providers/invoke-method.provider.d.ts +1 -1
  29. package/dist/providers/invoke-method.provider.js +16 -19
  30. package/dist/providers/invoke-method.provider.js.map +1 -1
  31. package/dist/providers/log-error.provider.d.ts +1 -1
  32. package/dist/providers/parse-params.provider.d.ts +1 -1
  33. package/dist/providers/parse-params.provider.js +20 -23
  34. package/dist/providers/parse-params.provider.js.map +1 -1
  35. package/dist/providers/reject.provider.d.ts +1 -1
  36. package/dist/providers/reject.provider.js +25 -28
  37. package/dist/providers/reject.provider.js.map +1 -1
  38. package/dist/providers/send.provider.d.ts +1 -1
  39. package/dist/request-context.d.ts +1 -1
  40. package/dist/rest.application.d.ts +1 -2
  41. package/dist/rest.application.js.map +1 -1
  42. package/dist/rest.component.d.ts +1 -1
  43. package/dist/rest.component.js +46 -49
  44. package/dist/rest.component.js.map +1 -1
  45. package/dist/rest.server.d.ts +1 -2
  46. package/dist/rest.server.js +618 -621
  47. package/dist/rest.server.js.map +1 -1
  48. package/dist/router/base-route.d.ts +1 -1
  49. package/dist/router/controller-route.d.ts +1 -1
  50. package/dist/router/controller-route.js +3 -4
  51. package/dist/router/controller-route.js.map +1 -1
  52. package/dist/router/handler-route.d.ts +1 -1
  53. package/dist/router/handler-route.js +2 -2
  54. package/dist/router/handler-route.js.map +1 -1
  55. package/dist/router/redirect-route.js +2 -2
  56. package/dist/router/redirect-route.js.map +1 -1
  57. package/dist/router/regexp-router.js +55 -58
  58. package/dist/router/regexp-router.js.map +1 -1
  59. package/dist/router/route-entry.d.ts +1 -1
  60. package/dist/router/trie-router.js +31 -34
  61. package/dist/router/trie-router.js.map +1 -1
  62. package/dist/sequence.d.ts +1 -1
  63. package/dist/sequence.js +74 -77
  64. package/dist/sequence.js.map +1 -1
  65. package/dist/spec-enhancers/consolidate.spec-enhancer.js +89 -92
  66. package/dist/spec-enhancers/consolidate.spec-enhancer.js.map +1 -1
  67. package/dist/spec-enhancers/info.spec-enhancer.js +63 -67
  68. package/dist/spec-enhancers/info.spec-enhancer.js.map +1 -1
  69. package/dist/types.d.ts +10 -0
  70. package/dist/validation/ajv-factory.provider.js +63 -66
  71. package/dist/validation/ajv-factory.provider.js.map +1 -1
  72. package/dist/validation/request-body.validator.d.ts +10 -2
  73. package/dist/validation/request-body.validator.js +25 -9
  74. package/dist/validation/request-body.validator.js.map +1 -1
  75. package/package.json +29 -27
  76. package/src/body-parsers/body-parser.json.ts +1 -1
  77. package/src/body-parsers/body-parser.raw.ts +1 -1
  78. package/src/body-parsers/body-parser.text.ts +1 -1
  79. package/src/body-parsers/body-parser.ts +1 -1
  80. package/src/body-parsers/body-parser.urlencoded.ts +1 -1
  81. package/src/coercion/coerce-parameter.ts +55 -15
  82. package/src/http-handler.ts +1 -1
  83. package/src/index.ts +6 -0
  84. package/src/keys.ts +1 -2
  85. package/src/parser.ts +1 -1
  86. package/src/providers/find-route.provider.ts +1 -1
  87. package/src/providers/invoke-method.provider.ts +1 -1
  88. package/src/providers/log-error.provider.ts +1 -1
  89. package/src/providers/parse-params.provider.ts +1 -1
  90. package/src/providers/reject.provider.ts +1 -1
  91. package/src/providers/send.provider.ts +1 -1
  92. package/src/request-context.ts +1 -1
  93. package/src/rest.application.ts +4 -2
  94. package/src/rest.component.ts +1 -1
  95. package/src/rest.server.ts +5 -3
  96. package/src/router/base-route.ts +1 -1
  97. package/src/router/controller-route.ts +2 -2
  98. package/src/router/external-express-routes.ts +1 -1
  99. package/src/router/handler-route.ts +1 -1
  100. package/src/router/redirect-route.ts +1 -2
  101. package/src/router/regexp-router.ts +1 -1
  102. package/src/router/route-entry.ts +1 -1
  103. package/src/router/trie-router.ts +1 -1
  104. package/src/sequence.ts +1 -1
  105. package/src/spec-enhancers/info.spec-enhancer.ts +3 -2
  106. package/src/types.ts +11 -0
  107. package/src/validation/request-body.validator.ts +35 -12
@@ -6,7 +6,6 @@
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.createBodyParserBinding = exports.RestServer = void 0;
8
8
  const tslib_1 = require("tslib");
9
- const context_1 = require("@loopback/context");
10
9
  const core_1 = require("@loopback/core");
11
10
  const express_1 = require("@loopback/express");
12
11
  const http_server_1 = require("@loopback/http-server");
@@ -55,653 +54,651 @@ const SequenceActions = keys_1.RestBindings.SequenceActions;
55
54
  * const server = await app.get('servers.foo');
56
55
  * ```
57
56
  */
58
- let RestServer = /** @class */ (() => {
59
- let RestServer = class RestServer extends express_1.BaseMiddlewareRegistry {
60
- /**
61
- *
62
- * Creates an instance of RestServer.
63
- *
64
- * @param app - The application instance (injected via
65
- * CoreBindings.APPLICATION_INSTANCE).
66
- * @param config - The configuration options (injected via
67
- * RestBindings.CONFIG).
68
- *
57
+ let RestServer = class RestServer extends express_1.BaseMiddlewareRegistry {
58
+ /**
59
+ *
60
+ * Creates an instance of RestServer.
61
+ *
62
+ * @param app - The application instance (injected via
63
+ * CoreBindings.APPLICATION_INSTANCE).
64
+ * @param config - The configuration options (injected via
65
+ * RestBindings.CONFIG).
66
+ *
67
+ */
68
+ constructor(app, config = {}) {
69
+ var _a;
70
+ super(app);
71
+ /*
72
+ * Registry of external routes & static assets
69
73
  */
70
- constructor(app, config = {}) {
71
- var _a;
72
- super(app);
73
- /*
74
- * Registry of external routes & static assets
75
- */
76
- this._externalRoutes = new router_1.ExternalExpressRoutes();
77
- this.config = resolveRestServerConfig(config);
78
- this.bind(keys_1.RestBindings.PORT).to(this.config.port);
79
- this.bind(keys_1.RestBindings.HOST).to(config.host);
80
- this.bind(keys_1.RestBindings.PATH).to(config.path);
81
- this.bind(keys_1.RestBindings.PROTOCOL).to((_a = config.protocol) !== null && _a !== void 0 ? _a : 'http');
82
- this.bind(keys_1.RestBindings.HTTPS_OPTIONS).to(config);
83
- if (config.requestBodyParser) {
84
- this.bind(keys_1.RestBindings.REQUEST_BODY_PARSER_OPTIONS).to(config.requestBodyParser);
85
- }
86
- if (config.sequence) {
87
- this.sequence(config.sequence);
88
- }
89
- if (config.router) {
90
- this.bind(keys_1.RestBindings.ROUTER_OPTIONS).to(config.router);
91
- }
92
- this.basePath(config.basePath);
93
- this.bind(keys_1.RestBindings.BASE_PATH).toDynamicValue(() => this._basePath);
94
- this.bind(keys_1.RestBindings.HANDLER).toDynamicValue(() => this.httpHandler);
95
- }
96
- // eslint-disable-next-line @typescript-eslint/naming-convention
97
- get OASEnhancer() {
98
- this._setupOASEnhancerIfNeeded();
99
- return this._OASEnhancer;
100
- }
101
- get requestHandler() {
102
- if (this._requestHandler == null) {
103
- this._setupRequestHandlerIfNeeded();
104
- }
105
- return this._requestHandler;
106
- }
107
- get httpHandler() {
108
- this._setupHandlerIfNeeded();
109
- return this._httpHandler;
110
- }
111
- get listening() {
112
- return this._httpServer ? this._httpServer.listening : false;
74
+ this._externalRoutes = new router_1.ExternalExpressRoutes();
75
+ this.config = resolveRestServerConfig(config);
76
+ this.bind(keys_1.RestBindings.PORT).to(this.config.port);
77
+ this.bind(keys_1.RestBindings.HOST).to(config.host);
78
+ this.bind(keys_1.RestBindings.PATH).to(config.path);
79
+ this.bind(keys_1.RestBindings.PROTOCOL).to((_a = config.protocol) !== null && _a !== void 0 ? _a : 'http');
80
+ this.bind(keys_1.RestBindings.HTTPS_OPTIONS).to(config);
81
+ if (config.requestBodyParser) {
82
+ this.bind(keys_1.RestBindings.REQUEST_BODY_PARSER_OPTIONS).to(config.requestBodyParser);
83
+ }
84
+ if (config.sequence) {
85
+ this.sequence(config.sequence);
86
+ }
87
+ if (config.router) {
88
+ this.bind(keys_1.RestBindings.ROUTER_OPTIONS).to(config.router);
89
+ }
90
+ this.basePath(config.basePath);
91
+ this.bind(keys_1.RestBindings.BASE_PATH).toDynamicValue(() => this._basePath);
92
+ this.bind(keys_1.RestBindings.HANDLER).toDynamicValue(() => this.httpHandler);
93
+ }
94
+ // eslint-disable-next-line @typescript-eslint/naming-convention
95
+ get OASEnhancer() {
96
+ this._setupOASEnhancerIfNeeded();
97
+ return this._OASEnhancer;
98
+ }
99
+ get requestHandler() {
100
+ if (this._requestHandler == null) {
101
+ this._setupRequestHandlerIfNeeded();
113
102
  }
114
- /**
115
- * The base url for the server, including the basePath if set. For example,
116
- * the value will be 'http://localhost:3000/api' if `basePath` is set to
117
- * '/api'.
118
- */
119
- get url() {
120
- let serverUrl = this.rootUrl;
121
- if (!serverUrl)
122
- return serverUrl;
123
- serverUrl = serverUrl + (this._basePath || '');
103
+ return this._requestHandler;
104
+ }
105
+ get httpHandler() {
106
+ this._setupHandlerIfNeeded();
107
+ return this._httpHandler;
108
+ }
109
+ get listening() {
110
+ return this._httpServer ? this._httpServer.listening : false;
111
+ }
112
+ /**
113
+ * The base url for the server, including the basePath if set. For example,
114
+ * the value will be 'http://localhost:3000/api' if `basePath` is set to
115
+ * '/api'.
116
+ */
117
+ get url() {
118
+ let serverUrl = this.rootUrl;
119
+ if (!serverUrl)
124
120
  return serverUrl;
121
+ serverUrl = serverUrl + (this._basePath || '');
122
+ return serverUrl;
123
+ }
124
+ /**
125
+ * The root url for the server without the basePath. For example, the value
126
+ * will be 'http://localhost:3000' regardless of the `basePath`.
127
+ */
128
+ get rootUrl() {
129
+ var _a;
130
+ return (_a = this._httpServer) === null || _a === void 0 ? void 0 : _a.url;
131
+ }
132
+ _setupOASEnhancerIfNeeded() {
133
+ if (this._OASEnhancer != null)
134
+ return;
135
+ this.add(core_1.createBindingFromClass(openapi_v3_1.OASEnhancerService, {
136
+ key: openapi_v3_1.OASEnhancerBindings.OAS_ENHANCER_SERVICE,
137
+ }));
138
+ this._OASEnhancer = this.getSync(openapi_v3_1.OASEnhancerBindings.OAS_ENHANCER_SERVICE);
139
+ }
140
+ _setupRequestHandlerIfNeeded() {
141
+ if (this._expressApp != null)
142
+ return;
143
+ this._expressApp = express_2.default();
144
+ this._applyExpressSettings();
145
+ this._requestHandler = this._expressApp;
146
+ // Allow CORS support for all endpoints so that users
147
+ // can test with online SwaggerUI instance
148
+ this.expressMiddleware(cors_1.default, this.config.cors, {
149
+ injectConfiguration: false,
150
+ key: 'middleware.cors',
151
+ group: 'cors',
152
+ });
153
+ // Set up endpoints for OpenAPI spec/ui
154
+ this._setupOpenApiSpecEndpoints();
155
+ // Mount our router & request handler
156
+ this._expressApp.use(this._basePath, (req, res, next) => {
157
+ this._handleHttpRequest(req, res).catch(next);
158
+ });
159
+ // Mount our error handler
160
+ this._expressApp.use(this._unexpectedErrorHandler());
161
+ }
162
+ /**
163
+ * Get an Express handler for unexpected errors
164
+ */
165
+ _unexpectedErrorHandler() {
166
+ const handleUnExpectedError = (err, req, res, next) => {
167
+ // Handle errors reported by Express middleware such as CORS
168
+ // First try to use the `REJECT` action
169
+ this.get(SequenceActions.REJECT, { optional: true })
170
+ .then(reject => {
171
+ if (reject) {
172
+ // TODO(rfeng): There is a possibility that the error is thrown
173
+ // from the `REJECT` action in the sequence
174
+ return reject({ request: req, response: res }, err);
175
+ }
176
+ // Use strong-error handler directly
177
+ strong_error_handler_1.writeErrorToResponse(err, req, res);
178
+ })
179
+ .catch(unexpectedErr => next(unexpectedErr));
180
+ };
181
+ return handleUnExpectedError;
182
+ }
183
+ /**
184
+ * Apply express settings.
185
+ */
186
+ _applyExpressSettings() {
187
+ assertExists(this._expressApp, 'this._expressApp');
188
+ const settings = this.config.expressSettings;
189
+ for (const key in settings) {
190
+ this._expressApp.set(key, settings[key]);
191
+ }
192
+ if (this.config.router && typeof this.config.router.strict === 'boolean') {
193
+ this._expressApp.set('strict routing', this.config.router.strict);
125
194
  }
126
- /**
127
- * The root url for the server without the basePath. For example, the value
128
- * will be 'http://localhost:3000' regardless of the `basePath`.
129
- */
130
- get rootUrl() {
131
- return this._httpServer && this._httpServer.url;
132
- }
133
- _setupOASEnhancerIfNeeded() {
134
- if (this._OASEnhancer != null)
135
- return;
136
- this.add(context_1.createBindingFromClass(openapi_v3_1.OASEnhancerService, {
137
- key: openapi_v3_1.OASEnhancerBindings.OAS_ENHANCER_SERVICE,
138
- }));
139
- this._OASEnhancer = this.getSync(openapi_v3_1.OASEnhancerBindings.OAS_ENHANCER_SERVICE);
140
- }
141
- _setupRequestHandlerIfNeeded() {
142
- if (this._expressApp != null)
143
- return;
144
- this._expressApp = express_2.default();
145
- this._applyExpressSettings();
146
- this._requestHandler = this._expressApp;
147
- // Allow CORS support for all endpoints so that users
148
- // can test with online SwaggerUI instance
149
- this.expressMiddleware(cors_1.default, this.config.cors, {
195
+ }
196
+ /**
197
+ * Mount /openapi.json, /openapi.yaml for specs and /swagger-ui, /explorer
198
+ * to redirect to externally hosted API explorer
199
+ */
200
+ _setupOpenApiSpecEndpoints() {
201
+ assertExists(this._expressApp, 'this._expressApp');
202
+ if (this.config.openApiSpec.disabled)
203
+ return;
204
+ const router = express_2.default.Router();
205
+ const mapping = this.config.openApiSpec.endpointMapping;
206
+ // Serving OpenAPI spec
207
+ for (const p in mapping) {
208
+ this.addOpenApiSpecEndpoint(p, mapping[p], router);
209
+ }
210
+ const explorerPaths = ['/swagger-ui', '/explorer'];
211
+ router.get(explorerPaths, (req, res, next) => this._redirectToSwaggerUI(req, res, next));
212
+ this.expressMiddleware('middleware.apiSpec.defaults', router, {
213
+ group: 'apiSpec',
214
+ });
215
+ }
216
+ /**
217
+ * Add a new non-controller endpoint hosting a form of the OpenAPI spec.
218
+ *
219
+ * @param path Path at which to host the copy of the OpenAPI
220
+ * @param form Form that should be rendered from that path
221
+ */
222
+ addOpenApiSpecEndpoint(path, form, router) {
223
+ if (router == null) {
224
+ const key = `middleware.apiSpec.${path}.${form}`;
225
+ if (this.contains(key)) {
226
+ throw new Error(`The path ${path} is already configured for OpenApi hosting`);
227
+ }
228
+ const newRouter = express_2.default.Router();
229
+ newRouter.get(path, (req, res) => this._serveOpenApiSpec(req, res, form));
230
+ this.expressMiddleware(() => newRouter, {}, {
150
231
  injectConfiguration: false,
151
- key: 'middleware.cors',
152
- group: 'cors',
153
- });
154
- // Set up endpoints for OpenAPI spec/ui
155
- this._setupOpenApiSpecEndpoints();
156
- // Mount our router & request handler
157
- this._expressApp.use(this._basePath, (req, res, next) => {
158
- this._handleHttpRequest(req, res).catch(next);
232
+ key: `middleware.apiSpec.${path}.${form}`,
233
+ group: 'apiSpec',
159
234
  });
160
- // Mount our error handler
161
- this._expressApp.use(this._unexpectedErrorHandler());
162
235
  }
163
- /**
164
- * Get an Express handler for unexpected errors
165
- */
166
- _unexpectedErrorHandler() {
167
- const handleUnExpectedError = (err, req, res, next) => {
168
- // Handle errors reported by Express middleware such as CORS
169
- // First try to use the `REJECT` action
170
- this.get(SequenceActions.REJECT, { optional: true })
171
- .then(reject => {
172
- if (reject) {
173
- // TODO(rfeng): There is a possibility that the error is thrown
174
- // from the `REJECT` action in the sequence
175
- return reject({ request: req, response: res }, err);
176
- }
177
- // Use strong-error handler directly
178
- strong_error_handler_1.writeErrorToResponse(err, req, res);
179
- })
180
- .catch(unexpectedErr => next(unexpectedErr));
181
- };
182
- return handleUnExpectedError;
236
+ else {
237
+ router.get(path, (req, res) => this._serveOpenApiSpec(req, res, form));
183
238
  }
239
+ }
240
+ _handleHttpRequest(request, response) {
241
+ return this.httpHandler.handleRequest(request, response);
242
+ }
243
+ _setupHandlerIfNeeded() {
244
+ if (this._httpHandler)
245
+ return;
246
+ // Watch for binding events
247
+ // See https://github.com/strongloop/loopback-next/issues/433
248
+ const routesObserver = {
249
+ filter: binding => core_1.filterByKey(keys_1.RestBindings.API_SPEC.key)(binding) ||
250
+ (core_1.filterByKey(/^(controllers|routes)\..+/)(binding) &&
251
+ // Exclude controller routes to avoid circular events
252
+ !core_1.filterByTag(keys_1.RestTags.CONTROLLER_ROUTE)(binding)),
253
+ observe: () => {
254
+ // Rebuild the HttpHandler instance whenever a controller/route was
255
+ // added/deleted.
256
+ this._createHttpHandler();
257
+ },
258
+ };
259
+ this._routesEventSubscription = this.subscribe(routesObserver);
260
+ this._createHttpHandler();
261
+ }
262
+ /**
263
+ * Create an instance of HttpHandler and populates it with routes
264
+ */
265
+ _createHttpHandler() {
184
266
  /**
185
- * Apply express settings.
267
+ * Check if there is custom router in the context
186
268
  */
187
- _applyExpressSettings() {
188
- assertExists(this._expressApp, 'this._expressApp');
189
- const settings = this.config.expressSettings;
190
- for (const key in settings) {
191
- this._expressApp.set(key, settings[key]);
192
- }
193
- if (this.config.router && typeof this.config.router.strict === 'boolean') {
194
- this._expressApp.set('strict routing', this.config.router.strict);
269
+ const router = this.getSync(keys_1.RestBindings.ROUTER, { optional: true });
270
+ const routingTable = new router_1.RoutingTable(router, this._externalRoutes);
271
+ this._httpHandler = new http_handler_1.HttpHandler(this, this.config, routingTable);
272
+ // Remove controller routes
273
+ for (const b of this.findByTag(keys_1.RestTags.CONTROLLER_ROUTE)) {
274
+ this.unbind(b.key);
275
+ }
276
+ for (const b of this.find(`${core_1.CoreBindings.CONTROLLERS}.*`)) {
277
+ const controllerName = b.key.replace(/^controllers\./, '');
278
+ const ctor = b.valueConstructor;
279
+ if (!ctor) {
280
+ throw new Error(`The controller ${controllerName} was not bound via .toClass()`);
281
+ }
282
+ const apiSpec = openapi_v3_1.getControllerSpec(ctor);
283
+ if (!apiSpec) {
284
+ // controller methods are specified through app.api() spec
285
+ debug('Skipping controller %s - no API spec provided', controllerName);
286
+ continue;
287
+ }
288
+ debug('Registering controller %s', controllerName);
289
+ if (apiSpec.components) {
290
+ this._httpHandler.registerApiComponents(apiSpec.components);
291
+ }
292
+ const controllerFactory = router_1.createControllerFactoryForBinding(b.key);
293
+ const routes = router_1.createRoutesForController(apiSpec, ctor, controllerFactory);
294
+ for (const route of routes) {
295
+ const binding = this.bindRoute(route);
296
+ binding
297
+ .tag(keys_1.RestTags.CONTROLLER_ROUTE)
298
+ .tag({ [keys_1.RestTags.CONTROLLER_BINDING]: b.key });
299
+ }
300
+ }
301
+ for (const b of this.findByTag(keys_1.RestTags.REST_ROUTE)) {
302
+ // TODO(bajtos) should we support routes defined asynchronously?
303
+ const route = this.getSync(b.key);
304
+ this._httpHandler.registerRoute(route);
305
+ }
306
+ // TODO(bajtos) should we support API spec defined asynchronously?
307
+ const spec = this.getSync(keys_1.RestBindings.API_SPEC);
308
+ for (const path in spec.paths) {
309
+ for (const verb in spec.paths[path]) {
310
+ const routeSpec = spec.paths[path][verb];
311
+ this._setupOperation(verb, path, routeSpec);
195
312
  }
196
313
  }
197
- /**
198
- * Mount /openapi.json, /openapi.yaml for specs and /swagger-ui, /explorer
199
- * to redirect to externally hosted API explorer
200
- */
201
- _setupOpenApiSpecEndpoints() {
202
- assertExists(this._expressApp, 'this._expressApp');
203
- if (this.config.openApiSpec.disabled)
204
- return;
205
- const router = express_2.default.Router();
206
- const mapping = this.config.openApiSpec.endpointMapping;
207
- // Serving OpenAPI spec
208
- for (const p in mapping) {
209
- this.addOpenApiSpecEndpoint(p, mapping[p], router);
210
- }
211
- const explorerPaths = ['/swagger-ui', '/explorer'];
212
- router.get(explorerPaths, (req, res, next) => this._redirectToSwaggerUI(req, res, next));
213
- this.expressMiddleware('middleware.apiSpec.defaults', router, {
214
- group: 'apiSpec',
314
+ }
315
+ _setupOperation(verb, path, spec) {
316
+ const handler = spec['x-operation'];
317
+ if (typeof handler === 'function') {
318
+ // Remove a field value that cannot be represented in JSON.
319
+ // Start by creating a shallow-copy of the spec, so that we don't
320
+ // modify the original spec object provided by user.
321
+ spec = Object.assign({}, spec);
322
+ delete spec['x-operation'];
323
+ const route = new router_1.Route(verb, path, spec, handler);
324
+ this._httpHandler.registerRoute(route);
325
+ return;
326
+ }
327
+ const controllerName = spec['x-controller-name'];
328
+ if (typeof controllerName === 'string') {
329
+ const b = this.getBinding(`controllers.${controllerName}`, {
330
+ optional: true,
215
331
  });
216
- }
217
- /**
218
- * Add a new non-controller endpoint hosting a form of the OpenAPI spec.
219
- *
220
- * @param path Path at which to host the copy of the OpenAPI
221
- * @param form Form that should be rendered from that path
222
- */
223
- addOpenApiSpecEndpoint(path, form, router) {
224
- if (router == null) {
225
- const key = `middleware.apiSpec.${path}.${form}`;
226
- if (this.contains(key)) {
227
- throw new Error(`The path ${path} is already configured for OpenApi hosting`);
228
- }
229
- const newRouter = express_2.default.Router();
230
- newRouter.get(path, (req, res) => this._serveOpenApiSpec(req, res, form));
231
- this.expressMiddleware(() => newRouter, {}, {
232
- injectConfiguration: false,
233
- key: `middleware.apiSpec.${path}.${form}`,
234
- group: 'apiSpec',
235
- });
332
+ if (!b) {
333
+ throw new Error(`Unknown controller ${controllerName} used by "${verb} ${path}"`);
236
334
  }
237
- else {
238
- router.get(path, (req, res) => this._serveOpenApiSpec(req, res, form));
335
+ const ctor = b.valueConstructor;
336
+ if (!ctor) {
337
+ throw new Error(`The controller ${controllerName} was not bound via .toClass()`);
239
338
  }
339
+ const controllerFactory = router_1.createControllerFactoryForBinding(b.key);
340
+ const route = new router_1.ControllerRoute(verb, path, spec, ctor, controllerFactory);
341
+ this._httpHandler.registerRoute(route);
342
+ return;
240
343
  }
241
- _handleHttpRequest(request, response) {
242
- return this.httpHandler.handleRequest(request, response);
243
- }
244
- _setupHandlerIfNeeded() {
245
- if (this._httpHandler)
246
- return;
247
- // Watch for binding events
248
- // See https://github.com/strongloop/loopback-next/issues/433
249
- const routesObserver = {
250
- filter: binding => context_1.filterByKey(keys_1.RestBindings.API_SPEC.key)(binding) ||
251
- (context_1.filterByKey(/^(controllers|routes)\..+/)(binding) &&
252
- // Exclude controller routes to avoid circular events
253
- !context_1.filterByTag(keys_1.RestTags.CONTROLLER_ROUTE)(binding)),
254
- observe: () => {
255
- // Rebuild the HttpHandler instance whenever a controller/route was
256
- // added/deleted.
257
- this._createHttpHandler();
258
- },
259
- };
260
- this._routesEventSubscription = this.subscribe(routesObserver);
261
- this._createHttpHandler();
262
- }
263
- /**
264
- * Create an instance of HttpHandler and populates it with routes
265
- */
266
- _createHttpHandler() {
267
- /**
268
- * Check if there is custom router in the context
269
- */
270
- const router = this.getSync(keys_1.RestBindings.ROUTER, { optional: true });
271
- const routingTable = new router_1.RoutingTable(router, this._externalRoutes);
272
- this._httpHandler = new http_handler_1.HttpHandler(this, this.config, routingTable);
273
- // Remove controller routes
274
- for (const b of this.findByTag(keys_1.RestTags.CONTROLLER_ROUTE)) {
275
- this.unbind(b.key);
276
- }
277
- for (const b of this.find(`${core_1.CoreBindings.CONTROLLERS}.*`)) {
278
- const controllerName = b.key.replace(/^controllers\./, '');
279
- const ctor = b.valueConstructor;
280
- if (!ctor) {
281
- throw new Error(`The controller ${controllerName} was not bound via .toClass()`);
282
- }
283
- const apiSpec = openapi_v3_1.getControllerSpec(ctor);
284
- if (!apiSpec) {
285
- // controller methods are specified through app.api() spec
286
- debug('Skipping controller %s - no API spec provided', controllerName);
287
- continue;
288
- }
289
- debug('Registering controller %s', controllerName);
290
- if (apiSpec.components) {
291
- this._httpHandler.registerApiComponents(apiSpec.components);
292
- }
293
- const controllerFactory = router_1.createControllerFactoryForBinding(b.key);
294
- const routes = router_1.createRoutesForController(apiSpec, ctor, controllerFactory);
295
- for (const route of routes) {
296
- const binding = this.bindRoute(route);
297
- binding
298
- .tag(keys_1.RestTags.CONTROLLER_ROUTE)
299
- .tag({ [keys_1.RestTags.CONTROLLER_BINDING]: b.key });
300
- }
301
- }
302
- for (const b of this.findByTag(keys_1.RestTags.REST_ROUTE)) {
303
- // TODO(bajtos) should we support routes defined asynchronously?
304
- const route = this.getSync(b.key);
305
- this._httpHandler.registerRoute(route);
306
- }
307
- // TODO(bajtos) should we support API spec defined asynchronously?
308
- const spec = this.getSync(keys_1.RestBindings.API_SPEC);
309
- for (const path in spec.paths) {
310
- for (const verb in spec.paths[path]) {
311
- const routeSpec = spec.paths[path][verb];
312
- this._setupOperation(verb, path, routeSpec);
313
- }
314
- }
344
+ throw new Error(`There is no handler configured for operation "${verb} ${path}`);
345
+ }
346
+ async _serveOpenApiSpec(request, response, specForm) {
347
+ const requestContext = new request_context_1.RequestContext(request, response, this, this.config);
348
+ specForm = specForm !== null && specForm !== void 0 ? specForm : { version: '3.0.0', format: 'json' };
349
+ const specObj = await this.getApiSpec(requestContext);
350
+ if (specForm.format === 'json') {
351
+ const spec = JSON.stringify(specObj, null, 2);
352
+ response.setHeader('content-type', 'application/json; charset=utf-8');
353
+ response.end(spec, 'utf-8');
354
+ }
355
+ else {
356
+ const yaml = js_yaml_1.safeDump(specObj, {});
357
+ response.setHeader('content-type', 'text/yaml; charset=utf-8');
358
+ response.end(yaml, 'utf-8');
315
359
  }
316
- _setupOperation(verb, path, spec) {
317
- const handler = spec['x-operation'];
318
- if (typeof handler === 'function') {
319
- // Remove a field value that cannot be represented in JSON.
320
- // Start by creating a shallow-copy of the spec, so that we don't
321
- // modify the original spec object provided by user.
322
- spec = Object.assign({}, spec);
323
- delete spec['x-operation'];
324
- const route = new router_1.Route(verb, path, spec, handler);
325
- this._httpHandler.registerRoute(route);
326
- return;
327
- }
328
- const controllerName = spec['x-controller-name'];
329
- if (typeof controllerName === 'string') {
330
- const b = this.getBinding(`controllers.${controllerName}`, {
331
- optional: true,
332
- });
333
- if (!b) {
334
- throw new Error(`Unknown controller ${controllerName} used by "${verb} ${path}"`);
335
- }
336
- const ctor = b.valueConstructor;
337
- if (!ctor) {
338
- throw new Error(`The controller ${controllerName} was not bound via .toClass()`);
339
- }
340
- const controllerFactory = router_1.createControllerFactoryForBinding(b.key);
341
- const route = new router_1.ControllerRoute(verb, path, spec, ctor, controllerFactory);
342
- this._httpHandler.registerRoute(route);
343
- return;
344
- }
345
- throw new Error(`There is no handler configured for operation "${verb} ${path}`);
346
- }
347
- async _serveOpenApiSpec(request, response, specForm) {
348
- const requestContext = new request_context_1.RequestContext(request, response, this, this.config);
349
- specForm = specForm !== null && specForm !== void 0 ? specForm : { version: '3.0.0', format: 'json' };
350
- const specObj = await this.getApiSpec(requestContext);
351
- if (specForm.format === 'json') {
352
- const spec = JSON.stringify(specObj, null, 2);
353
- response.setHeader('content-type', 'application/json; charset=utf-8');
354
- response.end(spec, 'utf-8');
355
- }
356
- else {
357
- const yaml = js_yaml_1.safeDump(specObj, {});
358
- response.setHeader('content-type', 'text/yaml; charset=utf-8');
359
- response.end(yaml, 'utf-8');
360
- }
360
+ }
361
+ async _redirectToSwaggerUI(request, response, next) {
362
+ const config = this.config.apiExplorer;
363
+ if (config.disabled) {
364
+ debug('Redirect to swagger-ui was disabled by configuration.');
365
+ next();
366
+ return;
367
+ }
368
+ debug('Redirecting to swagger-ui from %j.', request.originalUrl);
369
+ const requestContext = new request_context_1.RequestContext(request, response, this, this.config);
370
+ const protocol = requestContext.requestedProtocol;
371
+ const baseUrl = protocol === 'http' ? config.httpUrl : config.url;
372
+ const openApiUrl = `${requestContext.requestedBaseUrl}/openapi.json`;
373
+ const fullUrl = `${baseUrl}?url=${openApiUrl}`;
374
+ response.redirect(302, fullUrl);
375
+ }
376
+ /**
377
+ * Register a controller class with this server.
378
+ *
379
+ * @param controllerCtor - The controller class
380
+ * (constructor function).
381
+ * @returns The newly created binding, you can use the reference to
382
+ * further modify the binding, e.g. lock the value to prevent further
383
+ * modifications.
384
+ *
385
+ * @example
386
+ * ```ts
387
+ * class MyController {
388
+ * }
389
+ * app.controller(MyController).lock();
390
+ * ```
391
+ *
392
+ */
393
+ controller(controllerCtor) {
394
+ return this.bind('controllers.' + controllerCtor.name).toClass(controllerCtor);
395
+ }
396
+ route(routeOrVerb, path, spec, controllerCtorOrHandler, controllerFactory, methodName) {
397
+ if (typeof routeOrVerb === 'object') {
398
+ const r = routeOrVerb;
399
+ // Encode the path to escape special chars
400
+ return this.bindRoute(r);
401
+ }
402
+ if (!path) {
403
+ throw new assert_1.AssertionError({
404
+ message: 'path is required for a controller-based route',
405
+ });
361
406
  }
362
- async _redirectToSwaggerUI(request, response, next) {
363
- const config = this.config.apiExplorer;
364
- if (config.disabled) {
365
- debug('Redirect to swagger-ui was disabled by configuration.');
366
- next();
367
- return;
368
- }
369
- debug('Redirecting to swagger-ui from %j.', request.originalUrl);
370
- const requestContext = new request_context_1.RequestContext(request, response, this, this.config);
371
- const protocol = requestContext.requestedProtocol;
372
- const baseUrl = protocol === 'http' ? config.httpUrl : config.url;
373
- const openApiUrl = `${requestContext.requestedBaseUrl}/openapi.json`;
374
- const fullUrl = `${baseUrl}?url=${openApiUrl}`;
375
- response.redirect(302, fullUrl);
407
+ if (!spec) {
408
+ throw new assert_1.AssertionError({
409
+ message: 'spec is required for a controller-based route',
410
+ });
376
411
  }
377
- /**
378
- * Register a controller class with this server.
379
- *
380
- * @param controllerCtor - The controller class
381
- * (constructor function).
382
- * @returns The newly created binding, you can use the reference to
383
- * further modify the binding, e.g. lock the value to prevent further
384
- * modifications.
385
- *
386
- * @example
387
- * ```ts
388
- * class MyController {
389
- * }
390
- * app.controller(MyController).lock();
391
- * ```
392
- *
393
- */
394
- controller(controllerCtor) {
395
- return this.bind('controllers.' + controllerCtor.name).toClass(controllerCtor);
396
- }
397
- route(routeOrVerb, path, spec, controllerCtorOrHandler, controllerFactory, methodName) {
398
- if (typeof routeOrVerb === 'object') {
399
- const r = routeOrVerb;
400
- // Encode the path to escape special chars
401
- return this.bindRoute(r);
402
- }
403
- if (!path) {
404
- throw new assert_1.AssertionError({
405
- message: 'path is required for a controller-based route',
406
- });
407
- }
408
- if (!spec) {
409
- throw new assert_1.AssertionError({
410
- message: 'spec is required for a controller-based route',
411
- });
412
- }
413
- if (arguments.length === 4) {
414
- if (!controllerCtorOrHandler) {
415
- throw new assert_1.AssertionError({
416
- message: 'handler function is required for a handler-based route',
417
- });
418
- }
419
- return this.route(new router_1.Route(routeOrVerb, path, spec, controllerCtorOrHandler));
420
- }
412
+ if (arguments.length === 4) {
421
413
  if (!controllerCtorOrHandler) {
422
414
  throw new assert_1.AssertionError({
423
- message: 'controller is required for a controller-based route',
415
+ message: 'handler function is required for a handler-based route',
424
416
  });
425
417
  }
426
- if (!methodName) {
427
- throw new assert_1.AssertionError({
428
- message: 'methodName is required for a controller-based route',
429
- });
430
- }
431
- return this.route(new router_1.ControllerRoute(routeOrVerb, path, spec, controllerCtorOrHandler, controllerFactory, methodName));
432
- }
433
- bindRoute(r) {
434
- const namespace = keys_1.RestBindings.ROUTES;
435
- const encodedPath = encodeURIComponent(r.path).replace(/\./g, '%2E');
436
- return this.bind(`${namespace}.${r.verb} ${encodedPath}`)
437
- .to(r)
438
- .tag(keys_1.RestTags.REST_ROUTE)
439
- .tag({ [keys_1.RestTags.ROUTE_VERB]: r.verb, [keys_1.RestTags.ROUTE_PATH]: r.path });
440
- }
441
- /**
442
- * Register a route redirecting callers to a different URL.
443
- *
444
- * @example
445
- * ```ts
446
- * server.redirect('/explorer', '/explorer/');
447
- * ```
448
- *
449
- * @param fromPath - URL path of the redirect endpoint
450
- * @param toPathOrUrl - Location (URL path or full URL) where to redirect to.
451
- * If your server is configured with a custom `basePath`, then the base path
452
- * is prepended to the target location.
453
- * @param statusCode - HTTP status code to respond with,
454
- * defaults to 303 (See Other).
455
- */
456
- redirect(fromPath, toPathOrUrl, statusCode) {
457
- return this.route(new router_1.RedirectRoute(fromPath, this._basePath + toPathOrUrl, statusCode));
458
- }
459
- /**
460
- * Mount static assets to the REST server.
461
- * See https://expressjs.com/en/4x/api.html#express.static
462
- * @param path - The path(s) to serve the asset.
463
- * See examples at https://expressjs.com/en/4x/api.html#path-examples
464
- * @param rootDir - The root directory from which to serve static assets
465
- * @param options - Options for serve-static
466
- */
467
- static(path, rootDir, options) {
468
- this._externalRoutes.registerAssets(path, rootDir, options);
469
- }
470
- /**
471
- * Set the OpenAPI specification that defines the REST API schema for this
472
- * server. All routes, parameter definitions and return types will be defined
473
- * in this way.
474
- *
475
- * Note that this will override any routes defined via decorators at the
476
- * controller level (this function takes precedent).
477
- *
478
- * @param spec - The OpenAPI specification, as an object.
479
- * @returns Binding for the spec
480
- *
481
- */
482
- api(spec) {
483
- return this.bind(keys_1.RestBindings.API_SPEC).to(spec);
418
+ return this.route(new router_1.Route(routeOrVerb, path, spec, controllerCtorOrHandler));
484
419
  }
485
- /**
486
- * Get the OpenAPI specification describing the REST API provided by
487
- * this application.
488
- *
489
- * This method merges operations (HTTP endpoints) from the following sources:
490
- * - `app.api(spec)`
491
- * - `app.controller(MyController)`
492
- * - `app.route(route)`
493
- * - `app.route('get', '/greet', operationSpec, MyController, 'greet')`
494
- *
495
- * If the optional `requestContext` is provided, then the `servers` list
496
- * in the returned spec will be updated to work in that context.
497
- * Specifically:
498
- * 1. if `config.openApi.setServersFromRequest` is enabled, the servers
499
- * list will be replaced with the context base url
500
- * 2. Any `servers` entries with a path of `/` will have that path
501
- * replaced with `requestContext.basePath`
502
- *
503
- * @param requestContext - Optional context to update the `servers` list
504
- * in the returned spec
505
- */
506
- async getApiSpec(requestContext) {
507
- let spec = await this.get(keys_1.RestBindings.API_SPEC);
508
- const components = this.httpHandler.getApiComponents();
509
- // Apply deep clone to prevent getApiSpec() callers from
510
- // accidentally modifying our internal routing data
511
- spec.paths = lodash_1.cloneDeep(this.httpHandler.describeApiPaths());
512
- if (components) {
513
- const defs = lodash_1.cloneDeep(components);
514
- spec.components = { ...spec.components, ...defs };
515
- }
516
- router_spec_1.assignRouterSpec(spec, this._externalRoutes.routerSpec);
517
- if (requestContext) {
518
- spec = this.updateSpecFromRequest(spec, requestContext);
519
- }
520
- // Apply OAS enhancers to the OpenAPI specification
521
- this.OASEnhancer.spec = spec;
522
- spec = await this.OASEnhancer.applyAllEnhancers();
523
- return spec;
524
- }
525
- /**
526
- * Update or rebuild OpenAPI Spec object to be appropriate for the context of
527
- * a specific request for the spec, leveraging both app config and request
528
- * path information.
529
- *
530
- * @param spec base spec object from which to start
531
- * @param requestContext request to use to infer path information
532
- * @returns Updated or rebuilt spec object to use in the context of the request
533
- */
534
- updateSpecFromRequest(spec, requestContext) {
535
- if (this.config.openApiSpec.setServersFromRequest) {
536
- spec = Object.assign({}, spec);
537
- spec.servers = [{ url: requestContext.requestedBaseUrl }];
538
- }
539
- const basePath = requestContext.basePath;
540
- if (spec.servers && basePath) {
541
- for (const s of spec.servers) {
542
- // Update the default server url to honor `basePath`
543
- if (s.url === '/') {
544
- s.url = basePath;
545
- }
546
- }
547
- }
548
- return spec;
420
+ if (!controllerCtorOrHandler) {
421
+ throw new assert_1.AssertionError({
422
+ message: 'controller is required for a controller-based route',
423
+ });
549
424
  }
550
- /**
551
- * Configure a custom sequence class for handling incoming requests.
552
- *
553
- * @example
554
- * ```ts
555
- * class MySequence implements SequenceHandler {
556
- * constructor(
557
- * @inject('send) public send: Send)) {
558
- * }
559
- *
560
- * public async handle({response}: RequestContext) {
561
- * send(response, 'hello world');
562
- * }
563
- * }
564
- * ```
565
- *
566
- * @param value - The sequence to invoke for each incoming request.
567
- */
568
- sequence(value) {
569
- this.bind(keys_1.RestBindings.SEQUENCE).toClass(value);
425
+ if (!methodName) {
426
+ throw new assert_1.AssertionError({
427
+ message: 'methodName is required for a controller-based route',
428
+ });
570
429
  }
571
- /**
572
- * Configure a custom sequence function for handling incoming requests.
573
- *
574
- * @example
575
- * ```ts
576
- * app.handler(({request, response}, sequence) => {
577
- * sequence.send(response, 'hello world');
578
- * });
579
- * ```
580
- *
581
- * @param handlerFn - The handler to invoke for each incoming request.
582
- */
583
- handler(handlerFn) {
584
- class SequenceFromFunction extends sequence_1.DefaultSequence {
585
- async handle(context) {
586
- return handlerFn(context, this);
430
+ return this.route(new router_1.ControllerRoute(routeOrVerb, path, spec, controllerCtorOrHandler, controllerFactory, methodName));
431
+ }
432
+ bindRoute(r) {
433
+ const namespace = keys_1.RestBindings.ROUTES;
434
+ const encodedPath = encodeURIComponent(r.path).replace(/\./g, '%2E');
435
+ return this.bind(`${namespace}.${r.verb} ${encodedPath}`)
436
+ .to(r)
437
+ .tag(keys_1.RestTags.REST_ROUTE)
438
+ .tag({ [keys_1.RestTags.ROUTE_VERB]: r.verb, [keys_1.RestTags.ROUTE_PATH]: r.path });
439
+ }
440
+ /**
441
+ * Register a route redirecting callers to a different URL.
442
+ *
443
+ * @example
444
+ * ```ts
445
+ * server.redirect('/explorer', '/explorer/');
446
+ * ```
447
+ *
448
+ * @param fromPath - URL path of the redirect endpoint
449
+ * @param toPathOrUrl - Location (URL path or full URL) where to redirect to.
450
+ * If your server is configured with a custom `basePath`, then the base path
451
+ * is prepended to the target location.
452
+ * @param statusCode - HTTP status code to respond with,
453
+ * defaults to 303 (See Other).
454
+ */
455
+ redirect(fromPath, toPathOrUrl, statusCode) {
456
+ return this.route(new router_1.RedirectRoute(fromPath, this._basePath + toPathOrUrl, statusCode));
457
+ }
458
+ /**
459
+ * Mount static assets to the REST server.
460
+ * See https://expressjs.com/en/4x/api.html#express.static
461
+ * @param path - The path(s) to serve the asset.
462
+ * See examples at https://expressjs.com/en/4x/api.html#path-examples
463
+ * @param rootDir - The root directory from which to serve static assets
464
+ * @param options - Options for serve-static
465
+ */
466
+ static(path, rootDir, options) {
467
+ this._externalRoutes.registerAssets(path, rootDir, options);
468
+ }
469
+ /**
470
+ * Set the OpenAPI specification that defines the REST API schema for this
471
+ * server. All routes, parameter definitions and return types will be defined
472
+ * in this way.
473
+ *
474
+ * Note that this will override any routes defined via decorators at the
475
+ * controller level (this function takes precedent).
476
+ *
477
+ * @param spec - The OpenAPI specification, as an object.
478
+ * @returns Binding for the spec
479
+ *
480
+ */
481
+ api(spec) {
482
+ return this.bind(keys_1.RestBindings.API_SPEC).to(spec);
483
+ }
484
+ /**
485
+ * Get the OpenAPI specification describing the REST API provided by
486
+ * this application.
487
+ *
488
+ * This method merges operations (HTTP endpoints) from the following sources:
489
+ * - `app.api(spec)`
490
+ * - `app.controller(MyController)`
491
+ * - `app.route(route)`
492
+ * - `app.route('get', '/greet', operationSpec, MyController, 'greet')`
493
+ *
494
+ * If the optional `requestContext` is provided, then the `servers` list
495
+ * in the returned spec will be updated to work in that context.
496
+ * Specifically:
497
+ * 1. if `config.openApi.setServersFromRequest` is enabled, the servers
498
+ * list will be replaced with the context base url
499
+ * 2. Any `servers` entries with a path of `/` will have that path
500
+ * replaced with `requestContext.basePath`
501
+ *
502
+ * @param requestContext - Optional context to update the `servers` list
503
+ * in the returned spec
504
+ */
505
+ async getApiSpec(requestContext) {
506
+ let spec = await this.get(keys_1.RestBindings.API_SPEC);
507
+ const components = this.httpHandler.getApiComponents();
508
+ // Apply deep clone to prevent getApiSpec() callers from
509
+ // accidentally modifying our internal routing data
510
+ spec.paths = lodash_1.cloneDeep(this.httpHandler.describeApiPaths());
511
+ if (components) {
512
+ const defs = lodash_1.cloneDeep(components);
513
+ spec.components = { ...spec.components, ...defs };
514
+ }
515
+ router_spec_1.assignRouterSpec(spec, this._externalRoutes.routerSpec);
516
+ if (requestContext) {
517
+ spec = this.updateSpecFromRequest(spec, requestContext);
518
+ }
519
+ // Apply OAS enhancers to the OpenAPI specification
520
+ this.OASEnhancer.spec = spec;
521
+ spec = await this.OASEnhancer.applyAllEnhancers();
522
+ return spec;
523
+ }
524
+ /**
525
+ * Update or rebuild OpenAPI Spec object to be appropriate for the context of
526
+ * a specific request for the spec, leveraging both app config and request
527
+ * path information.
528
+ *
529
+ * @param spec base spec object from which to start
530
+ * @param requestContext request to use to infer path information
531
+ * @returns Updated or rebuilt spec object to use in the context of the request
532
+ */
533
+ updateSpecFromRequest(spec, requestContext) {
534
+ if (this.config.openApiSpec.setServersFromRequest) {
535
+ spec = Object.assign({}, spec);
536
+ spec.servers = [{ url: requestContext.requestedBaseUrl }];
537
+ }
538
+ const basePath = requestContext.basePath;
539
+ if (spec.servers && basePath) {
540
+ for (const s of spec.servers) {
541
+ // Update the default server url to honor `basePath`
542
+ if (s.url === '/') {
543
+ s.url = basePath;
587
544
  }
588
545
  }
589
- this.sequence(SequenceFromFunction);
590
- }
591
- /**
592
- * Bind a body parser to the server context
593
- * @param parserClass - Body parser class
594
- * @param address - Optional binding address
595
- */
596
- bodyParser(bodyParserClass, address) {
597
- const binding = createBodyParserBinding(bodyParserClass, address);
598
- this.add(binding);
599
- return binding;
600
- }
601
- /**
602
- * Configure the `basePath` for the rest server
603
- * @param path - Base path
604
- */
605
- basePath(path = '') {
606
- if (this._requestHandler != null) {
607
- throw new Error('Base path cannot be set as the request handler has been created');
608
- }
609
- // Trim leading and trailing `/`
610
- path = path.replace(/(^\/)|(\/$)/, '');
611
- if (path)
612
- path = '/' + path;
613
- this._basePath = path;
614
- this.config.basePath = path;
615
- }
616
- /**
617
- * Start this REST API's HTTP/HTTPS server.
618
- */
619
- async start() {
620
- // Set up the Express app if not done yet
621
- this._setupRequestHandlerIfNeeded();
622
- // Setup the HTTP handler so that we can verify the configuration
623
- // of API spec, controllers and routes at startup time.
624
- this._setupHandlerIfNeeded();
625
- const port = await this.get(keys_1.RestBindings.PORT);
626
- const host = await this.get(keys_1.RestBindings.HOST);
627
- const path = await this.get(keys_1.RestBindings.PATH);
628
- const protocol = await this.get(keys_1.RestBindings.PROTOCOL);
629
- const httpsOptions = await this.get(keys_1.RestBindings.HTTPS_OPTIONS);
630
- if (this.config.listenOnStart === false) {
631
- debug('RestServer is not listening as listenOnStart flag is set to false.');
632
- return;
633
- }
634
- const serverOptions = {};
635
- if (protocol === 'https')
636
- Object.assign(serverOptions, httpsOptions);
637
- Object.assign(serverOptions, { port, host, protocol, path });
638
- this._httpServer = new http_server_1.HttpServer(this.requestHandler, serverOptions);
639
- await this._httpServer.start();
640
- this.bind(keys_1.RestBindings.PORT).to(this._httpServer.port);
641
- this.bind(keys_1.RestBindings.HOST).to(this._httpServer.host);
642
- this.bind(keys_1.RestBindings.URL).to(this._httpServer.url);
643
- debug('RestServer listening at %s', this._httpServer.url);
644
- }
645
- /**
646
- * Stop this REST API's HTTP/HTTPS server.
647
- */
648
- async stop() {
649
- // Kill the server instance.
650
- if (!this._httpServer)
651
- return;
652
- await this._httpServer.stop();
653
- this._httpServer = undefined;
654
546
  }
655
- /**
656
- * Mount an Express router to expose additional REST endpoints handled
657
- * via legacy Express-based stack.
658
- *
659
- * @param basePath - Path where to mount the router at, e.g. `/` or `/api`.
660
- * @param router - The Express router to handle the requests.
661
- * @param spec - A partial OpenAPI spec describing endpoints provided by the
662
- * router. LoopBack will prepend `basePath` to all endpoints automatically.
663
- * This argument is optional. You can leave it out if you don't want to
664
- * document the routes.
665
- */
666
- mountExpressRouter(basePath, router, spec) {
667
- this._externalRoutes.mountRouter(basePath, router, spec);
668
- }
669
- /**
670
- * Export the OpenAPI spec to the given json or yaml file
671
- * @param outFile - File name for the spec. The extension of the file
672
- * determines the format of the file.
673
- * - `yaml` or `yml`: YAML
674
- * - `json` or other: JSON
675
- * If the outFile is not provided or its value is `''` or `'-'`, the spec is
676
- * written to the console using the `log` function.
677
- * @param log - Log function, default to `console.log`
678
- */
679
- async exportOpenApiSpec(outFile = '', log = console.log) {
680
- const spec = await this.getApiSpec();
681
- if (outFile === '-' || outFile === '') {
682
- const json = JSON.stringify(spec, null, 2);
683
- log('%s', json);
684
- return;
685
- }
686
- const fileName = outFile.toLowerCase();
687
- if (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) {
688
- const yaml = js_yaml_1.safeDump(spec);
689
- fs_1.default.writeFileSync(outFile, yaml, 'utf-8');
690
- }
691
- else {
692
- const json = JSON.stringify(spec, null, 2);
693
- fs_1.default.writeFileSync(outFile, json, 'utf-8');
694
- }
695
- log('The OpenAPI spec has been saved to %s.', outFile);
696
- }
697
- };
698
- RestServer = tslib_1.__decorate([
699
- tslib_1.__param(0, context_1.inject(core_1.CoreBindings.APPLICATION_INSTANCE)),
700
- tslib_1.__param(1, context_1.inject(keys_1.RestBindings.CONFIG, { optional: true })),
701
- tslib_1.__metadata("design:paramtypes", [core_1.Application, Object])
702
- ], RestServer);
703
- return RestServer;
704
- })();
547
+ return spec;
548
+ }
549
+ /**
550
+ * Configure a custom sequence class for handling incoming requests.
551
+ *
552
+ * @example
553
+ * ```ts
554
+ * class MySequence implements SequenceHandler {
555
+ * constructor(
556
+ * @inject('send) public send: Send)) {
557
+ * }
558
+ *
559
+ * public async handle({response}: RequestContext) {
560
+ * send(response, 'hello world');
561
+ * }
562
+ * }
563
+ * ```
564
+ *
565
+ * @param value - The sequence to invoke for each incoming request.
566
+ */
567
+ sequence(value) {
568
+ this.bind(keys_1.RestBindings.SEQUENCE).toClass(value);
569
+ }
570
+ /**
571
+ * Configure a custom sequence function for handling incoming requests.
572
+ *
573
+ * @example
574
+ * ```ts
575
+ * app.handler(({request, response}, sequence) => {
576
+ * sequence.send(response, 'hello world');
577
+ * });
578
+ * ```
579
+ *
580
+ * @param handlerFn - The handler to invoke for each incoming request.
581
+ */
582
+ handler(handlerFn) {
583
+ class SequenceFromFunction extends sequence_1.DefaultSequence {
584
+ async handle(context) {
585
+ return handlerFn(context, this);
586
+ }
587
+ }
588
+ this.sequence(SequenceFromFunction);
589
+ }
590
+ /**
591
+ * Bind a body parser to the server context
592
+ * @param parserClass - Body parser class
593
+ * @param address - Optional binding address
594
+ */
595
+ bodyParser(bodyParserClass, address) {
596
+ const binding = createBodyParserBinding(bodyParserClass, address);
597
+ this.add(binding);
598
+ return binding;
599
+ }
600
+ /**
601
+ * Configure the `basePath` for the rest server
602
+ * @param path - Base path
603
+ */
604
+ basePath(path = '') {
605
+ if (this._requestHandler != null) {
606
+ throw new Error('Base path cannot be set as the request handler has been created');
607
+ }
608
+ // Trim leading and trailing `/`
609
+ path = path.replace(/(^\/)|(\/$)/, '');
610
+ if (path)
611
+ path = '/' + path;
612
+ this._basePath = path;
613
+ this.config.basePath = path;
614
+ }
615
+ /**
616
+ * Start this REST API's HTTP/HTTPS server.
617
+ */
618
+ async start() {
619
+ // Set up the Express app if not done yet
620
+ this._setupRequestHandlerIfNeeded();
621
+ // Setup the HTTP handler so that we can verify the configuration
622
+ // of API spec, controllers and routes at startup time.
623
+ this._setupHandlerIfNeeded();
624
+ const port = await this.get(keys_1.RestBindings.PORT);
625
+ const host = await this.get(keys_1.RestBindings.HOST);
626
+ const path = await this.get(keys_1.RestBindings.PATH);
627
+ const protocol = await this.get(keys_1.RestBindings.PROTOCOL);
628
+ const httpsOptions = await this.get(keys_1.RestBindings.HTTPS_OPTIONS);
629
+ if (this.config.listenOnStart === false) {
630
+ debug('RestServer is not listening as listenOnStart flag is set to false.');
631
+ return;
632
+ }
633
+ const serverOptions = {};
634
+ if (protocol === 'https')
635
+ Object.assign(serverOptions, httpsOptions);
636
+ Object.assign(serverOptions, { port, host, protocol, path });
637
+ this._httpServer = new http_server_1.HttpServer(this.requestHandler, serverOptions);
638
+ await this._httpServer.start();
639
+ this.bind(keys_1.RestBindings.PORT).to(this._httpServer.port);
640
+ this.bind(keys_1.RestBindings.HOST).to(this._httpServer.host);
641
+ this.bind(keys_1.RestBindings.URL).to(this._httpServer.url);
642
+ debug('RestServer listening at %s', this._httpServer.url);
643
+ }
644
+ /**
645
+ * Stop this REST API's HTTP/HTTPS server.
646
+ */
647
+ async stop() {
648
+ // Kill the server instance.
649
+ if (!this._httpServer)
650
+ return;
651
+ await this._httpServer.stop();
652
+ this._httpServer = undefined;
653
+ }
654
+ /**
655
+ * Mount an Express router to expose additional REST endpoints handled
656
+ * via legacy Express-based stack.
657
+ *
658
+ * @param basePath - Path where to mount the router at, e.g. `/` or `/api`.
659
+ * @param router - The Express router to handle the requests.
660
+ * @param spec - A partial OpenAPI spec describing endpoints provided by the
661
+ * router. LoopBack will prepend `basePath` to all endpoints automatically.
662
+ * This argument is optional. You can leave it out if you don't want to
663
+ * document the routes.
664
+ */
665
+ mountExpressRouter(basePath, router, spec) {
666
+ this._externalRoutes.mountRouter(basePath, router, spec);
667
+ }
668
+ /**
669
+ * Export the OpenAPI spec to the given json or yaml file
670
+ * @param outFile - File name for the spec. The extension of the file
671
+ * determines the format of the file.
672
+ * - `yaml` or `yml`: YAML
673
+ * - `json` or other: JSON
674
+ * If the outFile is not provided or its value is `''` or `'-'`, the spec is
675
+ * written to the console using the `log` function.
676
+ * @param log - Log function, default to `console.log`
677
+ */
678
+ async exportOpenApiSpec(outFile = '', log = console.log) {
679
+ const spec = await this.getApiSpec();
680
+ if (outFile === '-' || outFile === '') {
681
+ const json = JSON.stringify(spec, null, 2);
682
+ log('%s', json);
683
+ return;
684
+ }
685
+ const fileName = outFile.toLowerCase();
686
+ if (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) {
687
+ const yaml = js_yaml_1.safeDump(spec);
688
+ fs_1.default.writeFileSync(outFile, yaml, 'utf-8');
689
+ }
690
+ else {
691
+ const json = JSON.stringify(spec, null, 2);
692
+ fs_1.default.writeFileSync(outFile, json, 'utf-8');
693
+ }
694
+ log('The OpenAPI spec has been saved to %s.', outFile);
695
+ }
696
+ };
697
+ RestServer = tslib_1.__decorate([
698
+ tslib_1.__param(0, core_1.inject(core_1.CoreBindings.APPLICATION_INSTANCE)),
699
+ tslib_1.__param(1, core_1.inject(keys_1.RestBindings.CONFIG, { optional: true })),
700
+ tslib_1.__metadata("design:paramtypes", [core_1.Application, Object])
701
+ ], RestServer);
705
702
  exports.RestServer = RestServer;
706
703
  /**
707
704
  * An assertion type guard for TypeScript to instruct the compiler that the
@@ -719,9 +716,9 @@ function assertExists(val, name) {
719
716
  */
720
717
  function createBodyParserBinding(parserClass, key) {
721
718
  const address = key !== null && key !== void 0 ? key : `${keys_1.RestBindings.REQUEST_BODY_PARSER}.${parserClass.name}`;
722
- return context_1.Binding.bind(address)
719
+ return core_1.Binding.bind(address)
723
720
  .toClass(parserClass)
724
- .inScope(context_1.BindingScope.TRANSIENT)
721
+ .inScope(core_1.BindingScope.TRANSIENT)
725
722
  .tag(body_parsers_1.REQUEST_BODY_PARSER_TAG);
726
723
  }
727
724
  exports.createBodyParserBinding = createBodyParserBinding;