-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
rest.application.ts
406 lines (380 loc) · 11.8 KB
/
rest.application.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
// Copyright IBM Corp. 2018,2020. All Rights Reserved.
// Node module: @loopback/rest
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
import {
Application,
ApplicationConfig,
Binding,
BindingAddress,
Constructor,
Context,
Provider,
Server,
} from '@loopback/core';
import {
ExpressMiddlewareFactory,
ExpressRequestHandler,
Middleware,
MiddlewareBindingOptions,
} from '@loopback/express';
import {OpenApiSpec, OperationObject} from '@loopback/openapi-v3';
import {PathParams} from 'express-serve-static-core';
import {ServeStaticOptions} from 'serve-static';
import {format} from 'util';
import {BodyParser} from './body-parsers';
import {RestBindings} from './keys';
import {RestComponent} from './rest.component';
import {HttpRequestListener, HttpServerLike, RestServer} from './rest.server';
import {ControllerClass, ControllerFactory, RouteEntry} from './router';
import {RouterSpec} from './router/router-spec';
import {SequenceFunction, SequenceHandler} from './sequence';
export const ERR_NO_MULTI_SERVER = format(
'RestApplication does not support multiple servers!',
'To create your own server bindings, please extend the Application class.',
);
// To help cut down on verbosity!
export const SequenceActions = RestBindings.SequenceActions;
/**
* An implementation of the Application class that automatically provides
* an instance of a REST server. This application class is intended to be
* a single-server implementation. Any attempt to bind additional servers
* will throw an error.
*
*/
export class RestApplication extends Application implements HttpServerLike {
/**
* The main REST server instance providing REST API for this application.
*/
get restServer(): RestServer {
// FIXME(kjdelisle): I attempted to mimic the pattern found in RestServer
// with no success, so until I've got a better way, this is functional.
return this.getSync<RestServer>('servers.RestServer');
}
/**
* Handle incoming HTTP(S) request by invoking the corresponding
* Controller method via the configured Sequence.
*
* @example
*
* ```ts
* const app = new RestApplication();
* // setup controllers, etc.
*
* const server = http.createServer(app.requestHandler);
* server.listen(3000);
* ```
*
* @param req - The request.
* @param res - The response.
*/
get requestHandler(): HttpRequestListener {
return this.restServer.requestHandler;
}
/**
* Create a REST application with the given parent context
* @param parent - Parent context
*/
constructor(parent: Context);
/**
* Create a REST application with the given configuration and parent context
* @param config - Application configuration
* @param parent - Parent context
*/
constructor(config?: ApplicationConfig, parent?: Context);
constructor(configOrParent?: ApplicationConfig | Context, parent?: Context) {
super(configOrParent, parent);
this.component(RestComponent);
}
server(server: Constructor<Server>, name?: string): Binding {
if (this.findByTag('server').length > 0) {
throw new Error(ERR_NO_MULTI_SERVER);
}
return super.server(server, name);
}
sequence(sequence: Constructor<SequenceHandler>): Binding {
return this.restServer.sequence(sequence);
}
handler(handlerFn: SequenceFunction) {
this.restServer.handler(handlerFn);
}
/**
* Mount static assets to the REST server.
* See https://expressjs.com/en/4x/api.html#express.static
* @param path - The path(s) to serve the asset.
* See examples at https://expressjs.com/en/4x/api.html#path-examples
* To avoid performance penalty, `/` is not allowed for now.
* @param rootDir - The root directory from which to serve static assets
* @param options - Options for serve-static
*/
static(path: PathParams, rootDir: string, options?: ServeStaticOptions) {
this.restServer.static(path, rootDir, options);
}
/**
* Bind a body parser to the server context
* @param parserClass - Body parser class
* @param address - Optional binding address
*/
bodyParser(
bodyParserClass: Constructor<BodyParser>,
address?: BindingAddress<BodyParser>,
): Binding<BodyParser> {
return this.restServer.bodyParser(bodyParserClass, address);
}
/**
* Configure the `basePath` for the rest server
* @param path - Base path
*/
basePath(path = '') {
this.restServer.basePath(path);
}
/**
* Bind an Express middleware to this server context
*
* @example
* ```ts
* import myExpressMiddlewareFactory from 'my-express-middleware';
* const myExpressMiddlewareConfig= {};
* const myExpressMiddleware = myExpressMiddlewareFactory(myExpressMiddlewareConfig);
* server.expressMiddleware('middleware.express.my', myExpressMiddleware);
* ```
* @param key - Middleware binding key
* @param middleware - Express middleware handler function(s)
*
*/
expressMiddleware(
key: BindingAddress,
middleware: ExpressRequestHandler | ExpressRequestHandler[],
options?: MiddlewareBindingOptions,
): Binding<Middleware>;
/**
* Bind an Express middleware to this server context
*
* @example
* ```ts
* import myExpressMiddlewareFactory from 'my-express-middleware';
* const myExpressMiddlewareConfig= {};
* server.expressMiddleware(myExpressMiddlewareFactory, myExpressMiddlewareConfig);
* ```
* @param middlewareFactory - Middleware module name or factory function
* @param middlewareConfig - Middleware config
* @param options - Options for registration
*
* @typeParam CFG - Configuration type
*/
expressMiddleware<CFG>(
middlewareFactory: ExpressMiddlewareFactory<CFG>,
middlewareConfig?: CFG,
options?: MiddlewareBindingOptions,
): Binding<Middleware>;
expressMiddleware<CFG>(
factoryOrKey: ExpressMiddlewareFactory<CFG> | BindingAddress<Middleware>,
configOrHandlers: CFG | ExpressRequestHandler | ExpressRequestHandler[],
options: MiddlewareBindingOptions = {},
): Binding<Middleware> {
return this.restServer.expressMiddleware(
factoryOrKey,
configOrHandlers,
options,
);
}
/**
* Register a middleware function or provider class
*
* @example
* ```ts
* const log: Middleware = async (requestCtx, next) {
* // ...
* }
* server.middleware(log);
* ```
*
* @param middleware - Middleware function or provider class
* @param options - Middleware binding options
*/
middleware(
middleware: Middleware | Constructor<Provider<Middleware>>,
options: MiddlewareBindingOptions = {},
): Binding<Middleware> {
return this.restServer.middleware(middleware, options);
}
/**
* Register a new Controller-based route.
*
* @example
* ```ts
* class MyController {
* greet(name: string) {
* return `hello ${name}`;
* }
* }
* app.route('get', '/greet', operationSpec, MyController, 'greet');
* ```
*
* @param verb - HTTP verb of the endpoint
* @param path - URL path of the endpoint
* @param spec - The OpenAPI spec describing the endpoint (operation)
* @param controllerCtor - Controller constructor
* @param controllerFactory - A factory function to create controller instance
* @param methodName - The name of the controller method
*/
route<T>(
verb: string,
path: string,
spec: OperationObject,
controllerCtor: ControllerClass<T>,
controllerFactory: ControllerFactory<T>,
methodName: string,
): Binding;
/**
* Register a new route invoking a handler function.
*
* @example
* ```ts
* function greet(name: string) {
* return `hello ${name}`;
* }
* app.route('get', '/', operationSpec, greet);
* ```
*
* @param verb - HTTP verb of the endpoint
* @param path - URL path of the endpoint
* @param spec - The OpenAPI spec describing the endpoint (operation)
* @param handler - The function to invoke with the request parameters
* described in the spec.
*/
route(
verb: string,
path: string,
spec: OperationObject,
handler: Function,
): Binding;
/**
* Register a new route.
*
* @example
* ```ts
* function greet(name: string) {
* return `hello ${name}`;
* }
* const route = new Route('get', '/', operationSpec, greet);
* app.route(route);
* ```
*
* @param route - The route to add.
*/
route(route: RouteEntry): Binding;
/**
* Register a new route.
*
* @example
* ```ts
* function greet(name: string) {
* return `hello ${name}`;
* }
* app.route('get', '/', operationSpec, greet);
* ```
*/
route(
verb: string,
path: string,
spec: OperationObject,
handler: Function,
): Binding;
route<T>(
routeOrVerb: RouteEntry | string,
path?: string,
spec?: OperationObject,
controllerCtorOrHandler?: ControllerClass<T> | Function,
controllerFactory?: ControllerFactory<T>,
methodName?: string,
): Binding {
const server = this.restServer;
if (typeof routeOrVerb === 'object') {
return server.route(routeOrVerb);
} else if (arguments.length === 4) {
return server.route(
routeOrVerb,
path!,
spec!,
controllerCtorOrHandler as Function,
);
} else {
return server.route(
routeOrVerb,
path!,
spec!,
controllerCtorOrHandler as ControllerClass<T>,
controllerFactory!,
methodName!,
);
}
}
/**
* Register a route redirecting callers to a different URL.
*
* @example
* ```ts
* app.redirect('/explorer', '/explorer/');
* ```
*
* @param fromPath - URL path of the redirect endpoint
* @param toPathOrUrl - Location (URL path or full URL) where to redirect to.
* If your server is configured with a custom `basePath`, then the base path
* is prepended to the target location.
* @param statusCode - HTTP status code to respond with,
* defaults to 303 (See Other).
*/
redirect(
fromPath: string,
toPathOrUrl: string,
statusCode?: number,
): Binding {
return this.restServer.redirect(fromPath, toPathOrUrl, statusCode);
}
/**
* Set the OpenAPI specification that defines the REST API schema for this
* application. All routes, parameter definitions and return types will be
* defined in this way.
*
* Note that this will override any routes defined via decorators at the
* controller level (this function takes precedent).
*
* @param spec - The OpenAPI specification, as an object.
* @returns Binding for the api spec
*/
api(spec: OpenApiSpec): Binding {
return this.restServer.bind(RestBindings.API_SPEC).to(spec);
}
/**
* Mount an Express router to expose additional REST endpoints handled
* via legacy Express-based stack.
*
* @param basePath - Path where to mount the router at, e.g. `/` or `/api`.
* @param router - The Express router to handle the requests.
* @param spec - A partial OpenAPI spec describing endpoints provided by the
* router. LoopBack will prepend `basePath` to all endpoints automatically.
* This argument is optional. You can leave it out if you don't want to
* document the routes.
*/
mountExpressRouter(
basePath: string,
router: ExpressRequestHandler,
spec?: RouterSpec,
): void {
this.restServer.mountExpressRouter(basePath, router, spec);
}
/**
* Export the OpenAPI spec to the given json or yaml file
* @param outFile - File name for the spec. The extension of the file
* determines the format of the file.
* - `yaml` or `yml`: YAML
* - `json` or other: JSON
* If the outFile is not provided or its value is `''` or `'-'`, the spec is
* written to the console using the `log` function.
* @param log - Log function, default to `console.log`
*/
async exportOpenApiSpec(outFile = '', log = console.log): Promise<void> {
return this.restServer.exportOpenApiSpec(outFile, log);
}
}