@loopback/rest 6.0.0 → 7.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 (88) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/body-parsers/body-parser.js +3 -0
  3. package/dist/body-parsers/body-parser.js.map +1 -1
  4. package/dist/coercion/coerce-parameter.d.ts +1 -1
  5. package/dist/coercion/coerce-parameter.js +36 -13
  6. package/dist/coercion/coerce-parameter.js.map +1 -1
  7. package/dist/coercion/utils.d.ts +1 -1
  8. package/dist/http-handler.js +5 -0
  9. package/dist/http-handler.js.map +1 -1
  10. package/dist/index.js +1 -1
  11. package/dist/providers/find-route.provider.d.ts +0 -2
  12. package/dist/providers/find-route.provider.js +11 -8
  13. package/dist/providers/find-route.provider.js.map +1 -1
  14. package/dist/providers/invoke-method.provider.d.ts +0 -2
  15. package/dist/providers/invoke-method.provider.js +21 -10
  16. package/dist/providers/invoke-method.provider.js.map +1 -1
  17. package/dist/providers/log-error.provider.js +7 -2
  18. package/dist/providers/log-error.provider.js.map +1 -1
  19. package/dist/providers/parse-params.provider.d.ts +0 -2
  20. package/dist/providers/parse-params.provider.js +16 -8
  21. package/dist/providers/parse-params.provider.js.map +1 -1
  22. package/dist/providers/reject.provider.d.ts +1 -1
  23. package/dist/providers/reject.provider.js +2 -1
  24. package/dist/providers/reject.provider.js.map +1 -1
  25. package/dist/providers/send.provider.d.ts +1 -4
  26. package/dist/providers/send.provider.js +11 -13
  27. package/dist/providers/send.provider.js.map +1 -1
  28. package/dist/request-context.js.map +1 -1
  29. package/dist/rest-http-error.d.ts +3 -1
  30. package/dist/rest-http-error.js +3 -2
  31. package/dist/rest-http-error.js.map +1 -1
  32. package/dist/rest.application.js +2 -2
  33. package/dist/rest.application.js.map +1 -1
  34. package/dist/rest.component.js +0 -2
  35. package/dist/rest.component.js.map +1 -1
  36. package/dist/rest.server.d.ts +3 -2
  37. package/dist/rest.server.js +14 -7
  38. package/dist/rest.server.js.map +1 -1
  39. package/dist/router/base-route.js +3 -3
  40. package/dist/router/base-route.js.map +1 -1
  41. package/dist/router/controller-route.js +1 -1
  42. package/dist/router/controller-route.js.map +1 -1
  43. package/dist/router/handler-route.js +1 -1
  44. package/dist/router/handler-route.js.map +1 -1
  45. package/dist/router/redirect-route.js +1 -1
  46. package/dist/router/redirect-route.js.map +1 -1
  47. package/dist/sequence.d.ts +3 -2
  48. package/dist/sequence.js +30 -7
  49. package/dist/sequence.js.map +1 -1
  50. package/dist/spec-enhancers/consolidate.spec-enhancer.js +1 -1
  51. package/dist/spec-enhancers/consolidate.spec-enhancer.js.map +1 -1
  52. package/dist/spec-enhancers/info.spec-enhancer.js +13 -5
  53. package/dist/spec-enhancers/info.spec-enhancer.js.map +1 -1
  54. package/dist/types.d.ts +4 -0
  55. package/dist/validation/ajv-factory.provider.js +5 -3
  56. package/dist/validation/ajv-factory.provider.js.map +1 -1
  57. package/dist/validation/openapi-formats.d.ts +26 -0
  58. package/dist/validation/openapi-formats.js +85 -0
  59. package/dist/validation/openapi-formats.js.map +1 -0
  60. package/dist/validation/request-body.validator.d.ts +4 -4
  61. package/dist/validation/request-body.validator.js +14 -19
  62. package/dist/validation/request-body.validator.js.map +1 -1
  63. package/package.json +24 -21
  64. package/src/body-parsers/body-parser.ts +3 -0
  65. package/src/coercion/coerce-parameter.ts +44 -20
  66. package/src/http-handler.ts +6 -0
  67. package/src/providers/find-route.provider.ts +24 -8
  68. package/src/providers/invoke-method.provider.ts +28 -10
  69. package/src/providers/log-error.provider.ts +2 -1
  70. package/src/providers/parse-params.provider.ts +28 -8
  71. package/src/providers/reject.provider.ts +4 -3
  72. package/src/providers/send.provider.ts +9 -12
  73. package/src/request-context.ts +2 -1
  74. package/src/rest-http-error.ts +6 -2
  75. package/src/rest.application.ts +2 -2
  76. package/src/rest.component.ts +0 -2
  77. package/src/rest.server.ts +17 -8
  78. package/src/router/base-route.ts +3 -3
  79. package/src/router/controller-route.ts +1 -1
  80. package/src/router/handler-route.ts +3 -1
  81. package/src/router/redirect-route.ts +1 -1
  82. package/src/sequence.ts +45 -3
  83. package/src/spec-enhancers/consolidate.spec-enhancer.ts +2 -2
  84. package/src/spec-enhancers/info.spec-enhancer.ts +15 -6
  85. package/src/types.ts +5 -0
  86. package/src/validation/ajv-factory.provider.ts +8 -4
  87. package/src/validation/openapi-formats.ts +92 -0
  88. package/src/validation/request-body.validator.ts +26 -25
@@ -3,14 +3,23 @@
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
- import {bind, Context, inject, Provider} from '@loopback/core';
6
+ import {
7
+ BindingScope,
8
+ Context,
9
+ inject,
10
+ injectable,
11
+ Provider,
12
+ } from '@loopback/core';
7
13
  import {asMiddleware, Middleware} from '@loopback/express';
14
+ import debugFactory from 'debug';
8
15
  import {HttpHandler} from '../http-handler';
9
16
  import {RestBindings, RestTags} from '../keys';
10
17
  import {ResolvedRoute} from '../router';
11
18
  import {RestMiddlewareGroups} from '../sequence';
12
19
  import {FindRoute, Request} from '../types';
13
20
 
21
+ const debug = debugFactory('loopback:rest:find-route');
22
+
14
23
  export class FindRouteProvider implements Provider<FindRoute> {
15
24
  constructor(
16
25
  @inject(RestBindings.Http.CONTEXT) protected context: Context,
@@ -23,26 +32,33 @@ export class FindRouteProvider implements Provider<FindRoute> {
23
32
 
24
33
  action(request: Request): ResolvedRoute {
25
34
  const found = this.handler.findRoute(request);
35
+ debug('Route found for %s %s', request.method, request.originalUrl, found);
26
36
  found.updateBindings(this.context);
27
37
  return found;
28
38
  }
29
39
  }
30
40
 
31
- @bind(
41
+ @injectable(
32
42
  asMiddleware({
33
43
  group: RestMiddlewareGroups.FIND_ROUTE,
34
44
  chain: RestTags.REST_MIDDLEWARE_CHAIN,
35
45
  }),
46
+ {scope: BindingScope.SINGLETON},
36
47
  )
37
48
  export class FindRouteMiddlewareProvider implements Provider<Middleware> {
38
- constructor(
39
- @inject(RestBindings.SequenceActions.FIND_ROUTE)
40
- protected findRoute: FindRoute,
41
- ) {}
42
-
43
49
  value(): Middleware {
44
50
  return async (ctx, next) => {
45
- const route = this.findRoute(ctx.request);
51
+ const request = ctx.request;
52
+ debug('Finding route for %s %s', request.method, request.originalUrl);
53
+ const handler = await ctx.get(RestBindings.HANDLER);
54
+ const route = handler.findRoute(request);
55
+ debug(
56
+ 'Route found for %s %s',
57
+ request.method,
58
+ request.originalUrl,
59
+ route,
60
+ );
61
+ route.updateBindings(ctx);
46
62
  ctx.bind(RestBindings.Operation.ROUTE).to(route);
47
63
  return next();
48
64
  };
@@ -3,13 +3,22 @@
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
- import {bind, Context, inject, Provider} from '@loopback/core';
6
+ import {
7
+ BindingScope,
8
+ Context,
9
+ inject,
10
+ injectable,
11
+ Provider,
12
+ } from '@loopback/core';
7
13
  import {asMiddleware, Middleware} from '@loopback/express';
14
+ import debugFactory from 'debug';
8
15
  import {RestBindings, RestTags} from '../keys';
9
16
  import {RouteEntry} from '../router';
10
17
  import {RestMiddlewareGroups} from '../sequence';
11
18
  import {InvokeMethod, OperationArgs, OperationRetval} from '../types';
12
19
 
20
+ const debug = debugFactory('loopback:rest:invoke-method');
21
+
13
22
  export class InvokeMethodProvider implements Provider<InvokeMethod> {
14
23
  constructor(@inject(RestBindings.Http.CONTEXT) protected context: Context) {}
15
24
 
@@ -22,28 +31,37 @@ export class InvokeMethodProvider implements Provider<InvokeMethod> {
22
31
  }
23
32
  }
24
33
 
25
- @bind(
34
+ @injectable(
26
35
  asMiddleware({
27
36
  group: RestMiddlewareGroups.INVOKE_METHOD,
28
37
  upstreamGroups: RestMiddlewareGroups.PARSE_PARAMS,
29
38
  chain: RestTags.REST_MIDDLEWARE_CHAIN,
30
39
  }),
40
+ {scope: BindingScope.SINGLETON},
31
41
  )
32
42
  export class InvokeMethodMiddlewareProvider implements Provider<Middleware> {
33
- constructor(
34
- @inject(RestBindings.SequenceActions.INVOKE_METHOD)
35
- protected invokeMethod: InvokeMethod,
36
- ) {}
37
-
38
43
  value(): Middleware {
39
44
  return async (ctx, next) => {
40
45
  const route: RouteEntry = await ctx.get(RestBindings.Operation.ROUTE);
41
46
  const params: OperationArgs = await ctx.get(
42
47
  RestBindings.Operation.PARAMS,
43
48
  );
44
- const retVal = await this.invokeMethod(route, params);
45
- ctx.bind(RestBindings.Operation.RETURN_VALUE).to(retVal);
46
- return retVal;
49
+ if (debug.enabled) {
50
+ debug('Invoking method %s with', route.describe(), params);
51
+ }
52
+ try {
53
+ const retVal = await route.invokeHandler(ctx, params);
54
+ ctx.bind(RestBindings.Operation.RETURN_VALUE).to(retVal);
55
+ if (debug.enabled) {
56
+ debug('Return value from %s', route.describe(), retVal);
57
+ }
58
+ return retVal;
59
+ } catch (err) {
60
+ if (debug.enabled) {
61
+ debug('Error thrown from %s', route.describe(), err);
62
+ }
63
+ throw err;
64
+ }
47
65
  };
48
66
  }
49
67
  }
@@ -3,9 +3,10 @@
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
- import {Provider} from '@loopback/core';
6
+ import {BindingScope, injectable, Provider} from '@loopback/core';
7
7
  import {LogError, Request} from '../types';
8
8
 
9
+ @injectable({scope: BindingScope.SINGLETON})
9
10
  export class LogErrorProvider implements Provider<LogError> {
10
11
  value(): LogError {
11
12
  return (err, statusCode, req) => this.action(err, statusCode, req);
@@ -3,8 +3,9 @@
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
- import {bind, inject, Provider} from '@loopback/core';
6
+ import {BindingScope, inject, injectable, Provider} from '@loopback/core';
7
7
  import {asMiddleware, Middleware} from '@loopback/express';
8
+ import debugFactory from 'debug';
8
9
  import {RequestBodyParser} from '../body-parsers';
9
10
  import {RestBindings, RestTags} from '../keys';
10
11
  import {parseOperationArgs} from '../parser';
@@ -12,6 +13,9 @@ import {ResolvedRoute} from '../router';
12
13
  import {RestMiddlewareGroups} from '../sequence';
13
14
  import {AjvFactory, ParseParams, Request, ValidationOptions} from '../types';
14
15
  import {DEFAULT_AJV_VALIDATION_OPTIONS} from '../validation/ajv-factory.provider';
16
+
17
+ const debug = debugFactory('loopback:rest:parse-param');
18
+
15
19
  /**
16
20
  * Provides the function for parsing args in requests at runtime.
17
21
  *
@@ -39,24 +43,40 @@ export class ParseParamsProvider implements Provider<ParseParams> {
39
43
  }
40
44
  }
41
45
 
42
- @bind(
46
+ @injectable(
43
47
  asMiddleware({
44
48
  group: RestMiddlewareGroups.PARSE_PARAMS,
45
49
  upstreamGroups: RestMiddlewareGroups.FIND_ROUTE,
46
50
  chain: RestTags.REST_MIDDLEWARE_CHAIN,
47
51
  }),
52
+ {scope: BindingScope.SINGLETON},
48
53
  )
49
54
  export class ParseParamsMiddlewareProvider implements Provider<Middleware> {
50
- constructor(
51
- @inject(RestBindings.SequenceActions.PARSE_PARAMS)
52
- protected parseParams: ParseParams,
53
- ) {}
54
-
55
55
  value(): Middleware {
56
56
  return async (ctx, next) => {
57
+ const requestBodyParser = await ctx.get(RestBindings.REQUEST_BODY_PARSER);
58
+ const validationOptions: ValidationOptions =
59
+ (await ctx.get(
60
+ RestBindings.REQUEST_BODY_PARSER_OPTIONS.deepProperty('validation'),
61
+ {optional: true},
62
+ )) ?? DEFAULT_AJV_VALIDATION_OPTIONS;
63
+ const ajvFactory = await ctx.get(RestBindings.AJV_FACTORY, {
64
+ optional: true,
65
+ });
66
+
57
67
  const route: ResolvedRoute = await ctx.get(RestBindings.Operation.ROUTE);
58
- const params = await this.parseParams(ctx.request, route);
68
+ debug('Parsing parameters for %s %s', route.verb, route.path);
69
+ const params = await parseOperationArgs(
70
+ ctx.request,
71
+ route,
72
+ requestBodyParser,
73
+ {
74
+ ajvFactory: ajvFactory,
75
+ ...validationOptions,
76
+ },
77
+ );
59
78
  ctx.bind(RestBindings.Operation.PARAMS).to(params);
79
+ debug('Parameters', params);
60
80
  return next();
61
81
  };
62
82
  }
@@ -3,11 +3,11 @@
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
- import {LogError, Reject, HandlerContext} from '../types';
7
- import {inject, Provider} from '@loopback/core';
6
+ import {BindingScope, inject, injectable, Provider} from '@loopback/core';
8
7
  import {HttpError} from 'http-errors';
8
+ import {ErrorWriterOptions, writeErrorToResponse} from 'strong-error-handler';
9
9
  import {RestBindings} from '../keys';
10
- import {writeErrorToResponse, ErrorWriterOptions} from 'strong-error-handler';
10
+ import {HandlerContext, LogError, Reject} from '../types';
11
11
 
12
12
  // TODO(bajtos) Make this mapping configurable at RestServer level,
13
13
  // allow apps and extensions to contribute additional mappings.
@@ -15,6 +15,7 @@ const codeToStatusCodeMap: {[key: string]: number} = {
15
15
  ENTITY_NOT_FOUND: 404,
16
16
  };
17
17
 
18
+ @injectable({scope: BindingScope.SINGLETON})
18
19
  export class RejectProvider implements Provider<Reject> {
19
20
  constructor(
20
21
  @inject(RestBindings.SequenceActions.LOG_ERROR)
@@ -3,11 +3,11 @@
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
- import {bind, inject, Provider} from '@loopback/core';
6
+ import {BindingScope, injectable, Provider} from '@loopback/core';
7
7
  import {asMiddleware, Middleware} from '@loopback/express';
8
8
  import {RestBindings, RestTags} from '../keys';
9
9
  import {RestMiddlewareGroups} from '../sequence';
10
- import {Reject, Send} from '../types';
10
+ import {Send} from '../types';
11
11
  import {writeResultToResponse} from '../writer';
12
12
  /**
13
13
  * Provides the function that populates the response object with
@@ -16,13 +16,14 @@ import {writeResultToResponse} from '../writer';
16
16
  * @returns The handler function that will populate the
17
17
  * response with operation results.
18
18
  */
19
+ @injectable({scope: BindingScope.SINGLETON})
19
20
  export class SendProvider implements Provider<Send> {
20
21
  value() {
21
22
  return writeResultToResponse;
22
23
  }
23
24
  }
24
25
 
25
- @bind(
26
+ @injectable(
26
27
  asMiddleware({
27
28
  group: RestMiddlewareGroups.SEND_RESPONSE,
28
29
  downstreamGroups: [
@@ -31,17 +32,13 @@ export class SendProvider implements Provider<Send> {
31
32
  ],
32
33
  chain: RestTags.REST_MIDDLEWARE_CHAIN,
33
34
  }),
35
+ {scope: BindingScope.SINGLETON},
34
36
  )
35
37
  export class SendResponseMiddlewareProvider implements Provider<Middleware> {
36
- constructor(
37
- @inject(RestBindings.SequenceActions.SEND)
38
- protected send: Send,
39
- @inject(RestBindings.SequenceActions.REJECT)
40
- protected reject: Reject,
41
- ) {}
42
-
43
38
  value(): Middleware {
44
39
  return async (ctx, next) => {
40
+ const send = await ctx.get(RestBindings.SequenceActions.SEND);
41
+ const reject = await ctx.get(RestBindings.SequenceActions.REJECT);
45
42
  try {
46
43
  /**
47
44
  * Invoke downstream middleware to produce the result
@@ -50,12 +47,12 @@ export class SendResponseMiddlewareProvider implements Provider<Middleware> {
50
47
  /**
51
48
  * Write the result to HTTP response
52
49
  */
53
- this.send(ctx.response, result);
50
+ send(ctx.response, result);
54
51
  } catch (err) {
55
52
  /**
56
53
  * Write the error to HTTP response
57
54
  */
58
- this.reject(ctx, err);
55
+ reject(ctx, err);
59
56
  }
60
57
  };
61
58
  }
@@ -17,7 +17,8 @@ import {RestServerResolvedConfig} from './rest.server';
17
17
  * A per-request Context combining an IoC container with handler context
18
18
  * (request, response, etc.).
19
19
  */
20
- export class RequestContext extends MiddlewareContext
20
+ export class RequestContext
21
+ extends MiddlewareContext
21
22
  implements HandlerContext {
22
23
  /**
23
24
  * Get the protocol used by the client to make the request.
@@ -11,7 +11,7 @@ export namespace RestHttpErrors {
11
11
  name: string,
12
12
  extraProperties?: Props,
13
13
  ): HttpErrors.HttpError & Props {
14
- const msg = `Invalid data ${JSON.stringify(data)} for parameter ${name}!`;
14
+ const msg = `Invalid data ${JSON.stringify(data)} for parameter "${name}".`;
15
15
  return Object.assign(
16
16
  new HttpErrors.BadRequest(msg),
17
17
  {
@@ -51,11 +51,15 @@ export namespace RestHttpErrors {
51
51
 
52
52
  export const INVALID_REQUEST_BODY_MESSAGE =
53
53
  'The request body is invalid. See error object `details` property for more info.';
54
- export function invalidRequestBody(): HttpErrors.HttpError {
54
+
55
+ export function invalidRequestBody(
56
+ details: ValidationErrorDetails[],
57
+ ): HttpErrors.HttpError & {details: ValidationErrorDetails[]} {
55
58
  return Object.assign(
56
59
  new HttpErrors.UnprocessableEntity(INVALID_REQUEST_BODY_MESSAGE),
57
60
  {
58
61
  code: 'VALIDATION_FAILED',
62
+ details,
59
63
  },
60
64
  );
61
65
  }
@@ -102,7 +102,7 @@ export class RestApplication extends Application implements HttpServerLike {
102
102
  }
103
103
 
104
104
  sequence(sequence: Constructor<SequenceHandler>): Binding {
105
- return this.bind(RestBindings.SEQUENCE).toClass(sequence);
105
+ return this.restServer.sequence(sequence);
106
106
  }
107
107
 
108
108
  handler(handlerFn: SequenceFunction) {
@@ -368,7 +368,7 @@ export class RestApplication extends Application implements HttpServerLike {
368
368
  * @returns Binding for the api spec
369
369
  */
370
370
  api(spec: OpenApiSpec): Binding {
371
- return this.bind(RestBindings.API_SPEC).to(spec);
371
+ return this.restServer.bind(RestBindings.API_SPEC).to(spec);
372
372
  }
373
373
 
374
374
  /**
@@ -43,7 +43,6 @@ import {
43
43
  RestServer,
44
44
  RestServerConfig,
45
45
  } from './rest.server';
46
- import {MiddlewareSequence} from './sequence';
47
46
  import {ConsolidationEnhancer} from './spec-enhancers/consolidate.spec-enhancer';
48
47
  import {InfoSpecEnhancer} from './spec-enhancers/info.spec-enhancer';
49
48
  import {AjvFactoryProvider} from './validation/ajv-factory.provider';
@@ -120,7 +119,6 @@ export class RestComponent implements Component {
120
119
  ).tag({[CoreTags.EXTENSION_POINT]: RestTags.REST_MIDDLEWARE_CHAIN});
121
120
  app.add(invokeMiddlewareServiceBinding);
122
121
 
123
- app.bind(RestBindings.SEQUENCE).toClass(MiddlewareSequence);
124
122
  const apiSpec = createEmptyApiSpec();
125
123
  // Merge the OpenAPI `servers` spec from the config into the empty one
126
124
  if (config?.openApiSpec?.servers) {
@@ -64,6 +64,7 @@ import {
64
64
  import {assignRouterSpec} from './router/router-spec';
65
65
  import {
66
66
  DefaultSequence,
67
+ MiddlewareSequence,
67
68
  RestMiddlewareGroups,
68
69
  SequenceFunction,
69
70
  SequenceHandler,
@@ -110,7 +111,8 @@ const SequenceActions = RestBindings.SequenceActions;
110
111
  * const server = await app.get('servers.foo');
111
112
  * ```
112
113
  */
113
- export class RestServer extends BaseMiddlewareRegistry
114
+ export class RestServer
115
+ extends BaseMiddlewareRegistry
114
116
  implements Server, HttpServerLike {
115
117
  /**
116
118
  * Handle incoming HTTP(S) request by invoking the corresponding
@@ -169,6 +171,10 @@ export class RestServer extends BaseMiddlewareRegistry
169
171
  return this._httpServer ? this._httpServer.listening : false;
170
172
  }
171
173
 
174
+ get httpServer(): HttpServer | undefined {
175
+ return this._httpServer;
176
+ }
177
+
172
178
  /**
173
179
  * The base url for the server, including the basePath if set. For example,
174
180
  * the value will be 'http://localhost:3000/api' if `basePath` is set to
@@ -222,6 +228,8 @@ export class RestServer extends BaseMiddlewareRegistry
222
228
 
223
229
  if (config.sequence) {
224
230
  this.sequence(config.sequence);
231
+ } else {
232
+ this.sequence(MiddlewareSequence);
225
233
  }
226
234
 
227
235
  if (config.router) {
@@ -889,10 +897,14 @@ export class RestServer extends BaseMiddlewareRegistry
889
897
  * }
890
898
  * ```
891
899
  *
892
- * @param value - The sequence to invoke for each incoming request.
900
+ * @param sequenceClass - The sequence class to invoke for each incoming request.
893
901
  */
894
- public sequence(value: Constructor<SequenceHandler>) {
895
- this.bind(RestBindings.SEQUENCE).toClass(value);
902
+ public sequence(sequenceClass: Constructor<SequenceHandler>) {
903
+ const sequenceBinding = createBindingFromClass(sequenceClass, {
904
+ key: RestBindings.SEQUENCE,
905
+ });
906
+ this.add(sequenceBinding);
907
+ return sequenceBinding;
896
908
  }
897
909
 
898
910
  /**
@@ -971,10 +983,7 @@ export class RestServer extends BaseMiddlewareRegistry
971
983
  return;
972
984
  }
973
985
 
974
- const serverOptions = {};
975
- if (protocol === 'https') Object.assign(serverOptions, httpsOptions);
976
- Object.assign(serverOptions, {port, host, protocol, path});
977
-
986
+ const serverOptions = {...httpsOptions, port, host, protocol, path};
978
987
  this._httpServer = new HttpServer(this.requestHandler, serverOptions);
979
988
 
980
989
  await this._httpServer.start();
@@ -36,11 +36,11 @@ export abstract class BaseRoute implements RouteEntry {
36
36
  ): Promise<OperationRetval>;
37
37
 
38
38
  describe(): string {
39
- return `"${this.verb} ${this.path}"`;
39
+ return `${this.verb} ${this.path}`;
40
40
  }
41
41
 
42
42
  toString() {
43
- return `${this.constructor.name} - ${this.verb} ${this.path}`;
43
+ return `${this.constructor.name} - ${this.describe()}`;
44
44
  }
45
45
  }
46
46
 
@@ -48,6 +48,6 @@ export class RouteSource implements InvocationSource<RouteEntry> {
48
48
  type = 'route';
49
49
  constructor(readonly value: RouteEntry) {}
50
50
  toString() {
51
- return `${this.value.verb} ${this.value.path}`;
51
+ return this.value.toString();
52
52
  }
53
53
  }
@@ -101,7 +101,7 @@ export class ControllerRoute<T> extends BaseRoute {
101
101
  }
102
102
 
103
103
  describe(): string {
104
- return `${this._controllerName}.${this._methodName}`;
104
+ return `${super.describe()} => ${this._controllerName}.${this._methodName}`;
105
105
  }
106
106
 
107
107
  updateBindings(requestContext: Context) {
@@ -20,7 +20,9 @@ export class Route extends BaseRoute {
20
20
  }
21
21
 
22
22
  describe(): string {
23
- return this._handler.name || super.describe();
23
+ return `${super.describe()} => ${
24
+ this._handler.name || this._handler.toString()
25
+ }`;
24
26
  }
25
27
 
26
28
  updateBindings(requestContext: Context) {
@@ -42,7 +42,7 @@ export class RedirectRoute implements RouteEntry, ResolvedRoute {
42
42
  }
43
43
 
44
44
  describe(): string {
45
- return `RedirectRoute from "${this.sourcePath}" to "${this.targetLocation}"`;
45
+ return `Redirect: "${this.sourcePath}" => "${this.targetLocation}"`;
46
46
  }
47
47
 
48
48
  /**
package/src/sequence.ts CHANGED
@@ -3,11 +3,19 @@
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
- import {config, inject, ValueOrPromise} from '@loopback/core';
6
+ import {
7
+ BindingScope,
8
+ config,
9
+ Context,
10
+ inject,
11
+ injectable,
12
+ ValueOrPromise,
13
+ } from '@loopback/core';
7
14
  import {
8
15
  InvokeMiddleware,
9
16
  InvokeMiddlewareOptions,
10
17
  MiddlewareGroups,
18
+ MiddlewareView,
11
19
  } from '@loopback/express';
12
20
  import debugFactory from 'debug';
13
21
  import {RestBindings, RestTags} from './keys';
@@ -177,7 +185,10 @@ export namespace RestMiddlewareGroups {
177
185
  /**
178
186
  * A sequence implementation using middleware chains
179
187
  */
188
+ @injectable({scope: BindingScope.SINGLETON})
180
189
  export class MiddlewareSequence implements SequenceHandler {
190
+ private middlewareView: MiddlewareView;
191
+
181
192
  static defaultOptions: InvokeMiddlewareOptions = {
182
193
  chain: RestTags.REST_MIDDLEWARE_CHAIN,
183
194
  orderedGroups: [
@@ -199,6 +210,26 @@ export class MiddlewareSequence implements SequenceHandler {
199
210
 
200
211
  RestMiddlewareGroups.INVOKE_METHOD,
201
212
  ],
213
+
214
+ /**
215
+ * Reports an error if there are middleware groups are unreachable as they
216
+ * are ordered after the `invokeMethod` group.
217
+ */
218
+ validate: groups => {
219
+ const index = groups.indexOf(RestMiddlewareGroups.INVOKE_METHOD);
220
+ if (index !== -1) {
221
+ const unreachableGroups = groups.slice(index + 1);
222
+ if (unreachableGroups.length > 0) {
223
+ throw new Error(
224
+ `Middleware groups "${unreachableGroups.join(
225
+ ',',
226
+ )}" are not invoked as they are ordered after "${
227
+ RestMiddlewareGroups.INVOKE_METHOD
228
+ }"`,
229
+ );
230
+ }
231
+ }
232
+ },
202
233
  };
203
234
 
204
235
  /**
@@ -208,11 +239,17 @@ export class MiddlewareSequence implements SequenceHandler {
208
239
  * To be injected via RestBindings.INVOKE_MIDDLEWARE_SERVICE.
209
240
  */
210
241
  constructor(
242
+ @inject.context()
243
+ context: Context,
244
+
211
245
  @inject(RestBindings.INVOKE_MIDDLEWARE_SERVICE)
212
246
  readonly invokeMiddleware: InvokeMiddleware,
213
247
  @config()
214
248
  readonly options: InvokeMiddlewareOptions = MiddlewareSequence.defaultOptions,
215
- ) {}
249
+ ) {
250
+ this.middlewareView = new MiddlewareView(context, options);
251
+ debug('Discovered middleware', this.middlewareView.middlewareBindingKeys);
252
+ }
216
253
 
217
254
  /**
218
255
  * Runs the default sequence. Given a handler context (request and response),
@@ -246,6 +283,11 @@ export class MiddlewareSequence implements SequenceHandler {
246
283
  this.options.chain,
247
284
  this.options.orderedGroups,
248
285
  );
249
- await this.invokeMiddleware(context, this.options);
286
+ const options: InvokeMiddlewareOptions = {
287
+ middlewareList: this.middlewareView.middlewareBindingKeys,
288
+ validate: MiddlewareSequence.defaultOptions.validate,
289
+ ...this.options,
290
+ };
291
+ await this.invokeMiddleware(context, options);
250
292
  }
251
293
  }
@@ -5,10 +5,10 @@
5
5
 
6
6
  import {
7
7
  ApplicationConfig,
8
- bind,
9
8
  BindingScope,
10
9
  CoreBindings,
11
10
  inject,
11
+ injectable,
12
12
  } from '@loopback/core';
13
13
  import {
14
14
  asSpecEnhancer,
@@ -58,7 +58,7 @@ const debug = debugFactory('loopback:openapi:spec-enhancer:consolidate');
58
58
  * When comparing schemas to avoid naming collisions, the description field
59
59
  * is ignored.
60
60
  */
61
- @bind(asSpecEnhancer, {scope: BindingScope.SINGLETON})
61
+ @injectable(asSpecEnhancer, {scope: BindingScope.SINGLETON})
62
62
  export class ConsolidationEnhancer implements OASEnhancer {
63
63
  name = 'consolidate';
64
64
  disabled: boolean;
@@ -5,16 +5,17 @@
5
5
 
6
6
  import {
7
7
  ApplicationMetadata,
8
- bind,
9
8
  BindingScope,
10
9
  CoreBindings,
11
10
  inject,
11
+ injectable,
12
12
  JSONObject,
13
13
  JSONValue,
14
14
  } from '@loopback/core';
15
15
  import {
16
16
  asSpecEnhancer,
17
17
  ContactObject,
18
+ DEFAULT_OPENAPI_SPEC_INFO,
18
19
  mergeOpenAPISpec,
19
20
  OASEnhancer,
20
21
  OpenApiSpec,
@@ -27,7 +28,7 @@ const debug = debugFactory('loopback:openapi:spec-enhancer:info');
27
28
  * An OpenAPI spec enhancer to populate `info` with application metadata
28
29
  * (package.json).
29
30
  */
30
- @bind(asSpecEnhancer, {scope: BindingScope.SINGLETON})
31
+ @injectable(asSpecEnhancer, {scope: BindingScope.SINGLETON})
31
32
  export class InfoSpecEnhancer implements OASEnhancer {
32
33
  name = 'info';
33
34
 
@@ -44,12 +45,20 @@ export class InfoSpecEnhancer implements OASEnhancer {
44
45
  const contact: ContactObject = InfoSpecEnhancer.parseAuthor(
45
46
  this.pkg.author,
46
47
  );
48
+ // Only override `info` if the `spec.info` is not customized
49
+ const overrideInfo =
50
+ spec.info.title === DEFAULT_OPENAPI_SPEC_INFO.title &&
51
+ spec.info.version === DEFAULT_OPENAPI_SPEC_INFO.version;
47
52
  const patchSpec = {
48
53
  info: {
49
- title: this.pkg.name,
50
- description: this.pkg.description,
51
- version: this.pkg.version,
52
- contact,
54
+ title: overrideInfo ? this.pkg.name : spec.info.title ?? this.pkg.name,
55
+ description: overrideInfo
56
+ ? this.pkg.description
57
+ : spec.info.description ?? this.pkg.description,
58
+ version: overrideInfo
59
+ ? this.pkg.version
60
+ : spec.info.version ?? this.pkg.version,
61
+ contact: overrideInfo ? contact : spec.info.contact ?? contact,
53
62
  },
54
63
  };
55
64
  debug('Enhancing OpenAPI spec with %j', patchSpec);
package/src/types.ts CHANGED
@@ -117,6 +117,11 @@ export interface ValueValidationOptions extends ValidationOptions {
117
117
  * 'query', 'cookie', etc...
118
118
  */
119
119
  source?: string;
120
+
121
+ /**
122
+ * Parameter name, as provided in `ParameterObject#name` property.
123
+ */
124
+ name?: string;
120
125
  }
121
126
 
122
127
  /**