UNPKG

7.19 kBJavaScriptView Raw
1"use strict";
2// Copyright IBM Corp. 2018,2020. All Rights Reserved.
3// Node module: @loopback/rest
4// This file is licensed under the MIT License.
5// License text available at https://opensource.org/licenses/MIT
6Object.defineProperty(exports, "__esModule", { value: true });
7exports.joinPath = exports.createRoutesForController = exports.createControllerFactoryForInstance = exports.createControllerFactoryForClass = exports.createControllerFactoryForBinding = exports.ControllerRoute = void 0;
8const tslib_1 = require("tslib");
9const core_1 = require("@loopback/core");
10const assert_1 = tslib_1.__importDefault(require("assert"));
11const debug_1 = tslib_1.__importDefault(require("debug"));
12const http_errors_1 = tslib_1.__importDefault(require("http-errors"));
13const util_1 = require("util");
14const keys_1 = require("../keys");
15const base_route_1 = require("./base-route");
16const debug = (0, debug_1.default)('loopback:rest:controller-route');
17/**
18 * A route backed by a controller
19 */
20class ControllerRoute extends base_route_1.BaseRoute {
21 /**
22 * Construct a controller based route
23 * @param verb - http verb
24 * @param path - http request path
25 * @param spec - OpenAPI operation spec
26 * @param controllerCtor - Controller class
27 * @param controllerFactory - A factory function to create a controller instance
28 * @param methodName - Controller method name, default to `x-operation-name`
29 */
30 constructor(verb, path, spec, controllerCtor, controllerFactory, methodName) {
31 const controllerName = spec['x-controller-name'] || controllerCtor.name;
32 methodName = methodName !== null && methodName !== void 0 ? methodName : spec['x-operation-name'];
33 if (!methodName) {
34 throw new Error('methodName must be provided either via the ControllerRoute argument ' +
35 'or via "x-operation-name" extension field in OpenAPI spec. ' +
36 `Operation: "${verb} ${path}" ` +
37 `Controller: ${controllerName}.`);
38 }
39 super(verb, path,
40 // Add x-controller-name and x-operation-name if not present
41 Object.assign({
42 'x-controller-name': controllerName,
43 'x-operation-name': methodName,
44 tags: [controllerName],
45 }, spec));
46 this._controllerFactory =
47 controllerFactory !== null && controllerFactory !== void 0 ? controllerFactory : createControllerFactoryForClass(controllerCtor);
48 this._controllerCtor = controllerCtor;
49 this._controllerName = controllerName || controllerCtor.name;
50 this._methodName = methodName;
51 }
52 describe() {
53 return `${super.describe()} => ${this._controllerName}.${this._methodName}`;
54 }
55 updateBindings(requestContext) {
56 /*
57 * Bind current controller to the request context in `SINGLETON` scope.
58 * Within the same request, we always get the same instance of the
59 * current controller when `requestContext.get(CoreBindings.CONTROLLER_CURRENT)`
60 * is invoked.
61 *
62 * Please note the controller class itself can be bound to other scopes,
63 * such as SINGLETON or TRANSIENT (default) in the application or server
64 * context.
65 *
66 * - SINGLETON: all requests share the same instance of a given controller
67 * - TRANSIENT: each request has its own instance of a given controller
68 */
69 requestContext
70 .bind(core_1.CoreBindings.CONTROLLER_CURRENT)
71 .toDynamicValue(() => this._controllerFactory(requestContext))
72 .inScope(core_1.BindingScope.SINGLETON);
73 requestContext.bind(core_1.CoreBindings.CONTROLLER_CLASS).to(this._controllerCtor);
74 requestContext
75 .bind(core_1.CoreBindings.CONTROLLER_METHOD_NAME)
76 .to(this._methodName);
77 requestContext.bind(keys_1.RestBindings.OPERATION_SPEC_CURRENT).to(this.spec);
78 }
79 async invokeHandler(requestContext, args) {
80 const controller = await requestContext.get('controller.current');
81 if (typeof controller[this._methodName] !== 'function') {
82 throw new http_errors_1.default.NotFound(`Controller method not found: ${this.describe()}`);
83 }
84 // Invoke the method with dependency injection
85 return (0, core_1.invokeMethod)(controller, this._methodName, requestContext, args, {
86 source: new base_route_1.RouteSource(this),
87 });
88 }
89}
90exports.ControllerRoute = ControllerRoute;
91/**
92 * Create a controller factory function for a given binding key
93 * @param key - Binding key
94 */
95function createControllerFactoryForBinding(key) {
96 return ctx => ctx.get(key);
97}
98exports.createControllerFactoryForBinding = createControllerFactoryForBinding;
99/**
100 * Create a controller factory function for a given class
101 * @param controllerCtor - Controller class
102 */
103function createControllerFactoryForClass(controllerCtor) {
104 return async (ctx) => {
105 // By default, we get an instance of the controller from the context
106 // using `controllers.<controllerName>` as the key
107 let inst = await ctx.get(`controllers.${controllerCtor.name}`, {
108 optional: true,
109 });
110 if (inst === undefined) {
111 inst = await (0, core_1.instantiateClass)(controllerCtor, ctx);
112 }
113 return inst;
114 };
115}
116exports.createControllerFactoryForClass = createControllerFactoryForClass;
117/**
118 * Create a controller factory function for a given instance
119 * @param controllerCtor - Controller instance
120 */
121function createControllerFactoryForInstance(controllerInst) {
122 return ctx => controllerInst;
123}
124exports.createControllerFactoryForInstance = createControllerFactoryForInstance;
125/**
126 * Create routes for a controller with the given spec
127 * @param spec - Controller spec
128 * @param controllerCtor - Controller class
129 * @param controllerFactory - Controller factory
130 */
131function createRoutesForController(spec, controllerCtor, controllerFactory) {
132 var _a;
133 const routes = [];
134 (0, assert_1.default)(typeof spec === 'object' && !!spec, 'API specification must be a non-null object');
135 if (!spec.paths || !Object.keys(spec.paths).length) {
136 return routes;
137 }
138 debug('Creating route for controller with API %s', (0, util_1.inspect)(spec, { depth: null }));
139 const basePath = (_a = spec.basePath) !== null && _a !== void 0 ? _a : '/';
140 for (const p in spec.paths) {
141 for (const verb in spec.paths[p]) {
142 const opSpec = spec.paths[p][verb];
143 const fullPath = joinPath(basePath, p);
144 const route = new ControllerRoute(verb, fullPath, opSpec, controllerCtor, controllerFactory);
145 routes.push(route);
146 }
147 }
148 return routes;
149}
150exports.createRoutesForController = createRoutesForController;
151function joinPath(basePath, path) {
152 const fullPath = [basePath, path]
153 .join('/') // Join by /
154 .replace(/(\/){2,}/g, '/') // Remove extra /
155 .replace(/\/$/, '') // Remove trailing /
156 .replace(/^(\/)?/, '/'); // Add leading /
157 return fullPath;
158}
159exports.joinPath = joinPath;
160//# sourceMappingURL=controller-route.js.map
\No newline at end of file