@loopback/rest 5.0.1 → 5.2.0

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 (118) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/body-parsers/body-parser.d.ts +1 -1
  3. package/dist/body-parsers/body-parser.helpers.js +1 -1
  4. package/dist/body-parsers/body-parser.js +112 -115
  5. package/dist/body-parsers/body-parser.js.map +1 -1
  6. package/dist/body-parsers/body-parser.json.js +24 -27
  7. package/dist/body-parsers/body-parser.json.js.map +1 -1
  8. package/dist/body-parsers/body-parser.raw.js +19 -22
  9. package/dist/body-parsers/body-parser.raw.js.map +1 -1
  10. package/dist/body-parsers/body-parser.text.js +21 -24
  11. package/dist/body-parsers/body-parser.text.js.map +1 -1
  12. package/dist/body-parsers/body-parser.urlencoded.js +19 -22
  13. package/dist/body-parsers/body-parser.urlencoded.js.map +1 -1
  14. package/dist/body-parsers/index.js +1 -1
  15. package/dist/body-parsers/index.js.map +1 -1
  16. package/dist/coercion/coerce-parameter.d.ts +3 -1
  17. package/dist/coercion/coerce-parameter.js +28 -12
  18. package/dist/coercion/coerce-parameter.js.map +1 -1
  19. package/dist/coercion/validator.js +1 -1
  20. package/dist/http-handler.d.ts +1 -1
  21. package/dist/http-handler.js +1 -1
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.js +2 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/keys.d.ts +1 -1
  26. package/dist/keys.js +33 -34
  27. package/dist/keys.js.map +1 -1
  28. package/dist/parser.js +1 -1
  29. package/dist/parser.js.map +1 -1
  30. package/dist/providers/find-route.provider.d.ts +1 -1
  31. package/dist/providers/find-route.provider.js +21 -24
  32. package/dist/providers/find-route.provider.js.map +1 -1
  33. package/dist/providers/invoke-method.provider.d.ts +1 -1
  34. package/dist/providers/invoke-method.provider.js +16 -19
  35. package/dist/providers/invoke-method.provider.js.map +1 -1
  36. package/dist/providers/log-error.provider.d.ts +1 -1
  37. package/dist/providers/parse-params.provider.d.ts +1 -1
  38. package/dist/providers/parse-params.provider.js +20 -23
  39. package/dist/providers/parse-params.provider.js.map +1 -1
  40. package/dist/providers/reject.provider.d.ts +1 -1
  41. package/dist/providers/reject.provider.js +25 -28
  42. package/dist/providers/reject.provider.js.map +1 -1
  43. package/dist/providers/send.provider.d.ts +1 -1
  44. package/dist/request-context.d.ts +1 -1
  45. package/dist/rest.application.d.ts +15 -2
  46. package/dist/rest.application.js +14 -1
  47. package/dist/rest.application.js.map +1 -1
  48. package/dist/rest.component.d.ts +1 -1
  49. package/dist/rest.component.js +46 -49
  50. package/dist/rest.component.js.map +1 -1
  51. package/dist/rest.server.d.ts +15 -2
  52. package/dist/rest.server.js +615 -588
  53. package/dist/rest.server.js.map +1 -1
  54. package/dist/router/base-route.d.ts +1 -1
  55. package/dist/router/controller-route.d.ts +1 -1
  56. package/dist/router/controller-route.js +3 -4
  57. package/dist/router/controller-route.js.map +1 -1
  58. package/dist/router/external-express-routes.js +1 -1
  59. package/dist/router/external-express-routes.js.map +1 -1
  60. package/dist/router/handler-route.d.ts +1 -1
  61. package/dist/router/handler-route.js +2 -2
  62. package/dist/router/handler-route.js.map +1 -1
  63. package/dist/router/redirect-route.js +2 -2
  64. package/dist/router/redirect-route.js.map +1 -1
  65. package/dist/router/regexp-router.js +55 -58
  66. package/dist/router/regexp-router.js.map +1 -1
  67. package/dist/router/route-entry.d.ts +1 -1
  68. package/dist/router/trie-router.js +32 -35
  69. package/dist/router/trie-router.js.map +1 -1
  70. package/dist/sequence.d.ts +1 -1
  71. package/dist/sequence.js +74 -77
  72. package/dist/sequence.js.map +1 -1
  73. package/dist/spec-enhancers/consolidate.spec-enhancer.js +89 -92
  74. package/dist/spec-enhancers/consolidate.spec-enhancer.js.map +1 -1
  75. package/dist/spec-enhancers/info.spec-enhancer.js +63 -67
  76. package/dist/spec-enhancers/info.spec-enhancer.js.map +1 -1
  77. package/dist/types.d.ts +10 -0
  78. package/dist/validation/ajv-factory.provider.js +63 -66
  79. package/dist/validation/ajv-factory.provider.js.map +1 -1
  80. package/dist/validation/request-body.validator.d.ts +10 -2
  81. package/dist/validation/request-body.validator.js +25 -9
  82. package/dist/validation/request-body.validator.js.map +1 -1
  83. package/package.json +25 -23
  84. package/src/body-parsers/body-parser.helpers.ts +1 -1
  85. package/src/body-parsers/body-parser.json.ts +1 -1
  86. package/src/body-parsers/body-parser.raw.ts +1 -1
  87. package/src/body-parsers/body-parser.text.ts +1 -1
  88. package/src/body-parsers/body-parser.ts +1 -1
  89. package/src/body-parsers/body-parser.urlencoded.ts +1 -1
  90. package/src/body-parsers/index.ts +1 -1
  91. package/src/coercion/coerce-parameter.ts +55 -15
  92. package/src/coercion/validator.ts +1 -1
  93. package/src/http-handler.ts +2 -2
  94. package/src/index.ts +6 -0
  95. package/src/keys.ts +1 -2
  96. package/src/parser.ts +1 -1
  97. package/src/providers/find-route.provider.ts +1 -1
  98. package/src/providers/invoke-method.provider.ts +1 -1
  99. package/src/providers/log-error.provider.ts +1 -1
  100. package/src/providers/parse-params.provider.ts +1 -1
  101. package/src/providers/reject.provider.ts +1 -1
  102. package/src/providers/send.provider.ts +1 -1
  103. package/src/request-context.ts +1 -1
  104. package/src/rest.application.ts +19 -3
  105. package/src/rest.component.ts +1 -1
  106. package/src/rest.server.ts +35 -3
  107. package/src/router/base-route.ts +1 -1
  108. package/src/router/controller-route.ts +2 -2
  109. package/src/router/external-express-routes.ts +2 -2
  110. package/src/router/handler-route.ts +1 -1
  111. package/src/router/redirect-route.ts +1 -2
  112. package/src/router/regexp-router.ts +1 -1
  113. package/src/router/route-entry.ts +1 -1
  114. package/src/router/trie-router.ts +2 -2
  115. package/src/sequence.ts +1 -1
  116. package/src/spec-enhancers/info.spec-enhancer.ts +3 -2
  117. package/src/types.ts +11 -0
  118. package/src/validation/request-body.validator.ts +35 -12
@@ -3,7 +3,7 @@
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 {inject, Provider} from '@loopback/context';
6
+ import {inject, Provider} from '@loopback/core';
7
7
  import {RequestBodyParser} from '../body-parsers';
8
8
  import {RestBindings} from '../keys';
9
9
  import {parseOperationArgs} from '../parser';
@@ -4,7 +4,7 @@
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
6
  import {LogError, Reject, HandlerContext} from '../types';
7
- import {inject, Provider} from '@loopback/context';
7
+ import {inject, Provider} from '@loopback/core';
8
8
  import {HttpError} from 'http-errors';
9
9
  import {RestBindings} from '../keys';
10
10
  import {writeErrorToResponse, ErrorWriterOptions} from 'strong-error-handler';
@@ -3,7 +3,7 @@
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, BoundValue} from '@loopback/context';
6
+ import {Provider, BoundValue} from '@loopback/core';
7
7
  import {writeResultToResponse} from '../writer';
8
8
  /**
9
9
  * Provides the function that populates the response object with
@@ -3,7 +3,7 @@
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 {Context} from '@loopback/context';
6
+ import {Context} from '@loopback/core';
7
7
  import {
8
8
  HandlerContext,
9
9
  MiddlewareContext,
@@ -1,16 +1,18 @@
1
- // Copyright IBM Corp. 2018,2019. All Rights Reserved.
1
+ // Copyright IBM Corp. 2018,2020. All Rights Reserved.
2
2
  // Node module: @loopback/rest
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
6
  import {
7
+ Application,
8
+ ApplicationConfig,
7
9
  Binding,
8
10
  BindingAddress,
9
11
  Constructor,
10
12
  Context,
11
13
  Provider,
12
- } from '@loopback/context';
13
- import {Application, ApplicationConfig, Server} from '@loopback/core';
14
+ Server,
15
+ } from '@loopback/core';
14
16
  import {
15
17
  ExpressMiddlewareFactory,
16
18
  ExpressRequestHandler,
@@ -387,4 +389,18 @@ export class RestApplication extends Application implements HttpServerLike {
387
389
  ): void {
388
390
  this.restServer.mountExpressRouter(basePath, router, spec);
389
391
  }
392
+
393
+ /**
394
+ * Export the OpenAPI spec to the given json or yaml file
395
+ * @param outFile - File name for the spec. The extension of the file
396
+ * determines the format of the file.
397
+ * - `yaml` or `yml`: YAML
398
+ * - `json` or other: JSON
399
+ * If the outFile is not provided or its value is `''` or `'-'`, the spec is
400
+ * written to the console using the `log` function.
401
+ * @param log - Log function, default to `console.log`
402
+ */
403
+ async exportOpenApiSpec(outFile = '', log = console.log): Promise<void> {
404
+ return this.restServer.exportOpenApiSpec(outFile, log);
405
+ }
390
406
  }
@@ -8,7 +8,7 @@ import {
8
8
  Constructor,
9
9
  createBindingFromClass,
10
10
  inject,
11
- } from '@loopback/context';
11
+ } from '@loopback/core';
12
12
  import {
13
13
  Application,
14
14
  Component,
@@ -4,18 +4,20 @@
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
6
  import {
7
+ Application,
7
8
  Binding,
8
9
  BindingAddress,
9
10
  BindingScope,
10
11
  Constructor,
11
12
  ContextObserver,
13
+ CoreBindings,
12
14
  createBindingFromClass,
13
15
  filterByKey,
14
16
  filterByTag,
15
17
  inject,
18
+ Server,
16
19
  Subscription,
17
- } from '@loopback/context';
18
- import {Application, CoreBindings, Server} from '@loopback/core';
20
+ } from '@loopback/core';
19
21
  import {BaseMiddlewareRegistry, ExpressRequestHandler} from '@loopback/express';
20
22
  import {HttpServer, HttpServerOptions} from '@loopback/http-server';
21
23
  import {
@@ -32,6 +34,7 @@ import cors from 'cors';
32
34
  import debugFactory from 'debug';
33
35
  import express, {ErrorRequestHandler} from 'express';
34
36
  import {PathParams} from 'express-serve-static-core';
37
+ import fs from 'fs';
35
38
  import {IncomingMessage, ServerResponse} from 'http';
36
39
  import {ServerOptions} from 'https';
37
40
  import {safeDump} from 'js-yaml';
@@ -124,6 +127,7 @@ export class RestServer extends BaseMiddlewareRegistry
124
127
  */
125
128
 
126
129
  protected _OASEnhancer: OASEnhancerService;
130
+ // eslint-disable-next-line @typescript-eslint/naming-convention
127
131
  public get OASEnhancer(): OASEnhancerService {
128
132
  this._setupOASEnhancerIfNeeded();
129
133
  return this._OASEnhancer;
@@ -176,7 +180,7 @@ export class RestServer extends BaseMiddlewareRegistry
176
180
  * will be 'http://localhost:3000' regardless of the `basePath`.
177
181
  */
178
182
  get rootUrl(): string | undefined {
179
- return this._httpServer && this._httpServer.url;
183
+ return this._httpServer?.url;
180
184
  }
181
185
 
182
186
  /**
@@ -987,6 +991,34 @@ export class RestServer extends BaseMiddlewareRegistry
987
991
  ): void {
988
992
  this._externalRoutes.mountRouter(basePath, router, spec);
989
993
  }
994
+
995
+ /**
996
+ * Export the OpenAPI spec to the given json or yaml file
997
+ * @param outFile - File name for the spec. The extension of the file
998
+ * determines the format of the file.
999
+ * - `yaml` or `yml`: YAML
1000
+ * - `json` or other: JSON
1001
+ * If the outFile is not provided or its value is `''` or `'-'`, the spec is
1002
+ * written to the console using the `log` function.
1003
+ * @param log - Log function, default to `console.log`
1004
+ */
1005
+ async exportOpenApiSpec(outFile = '', log = console.log): Promise<void> {
1006
+ const spec = await this.getApiSpec();
1007
+ if (outFile === '-' || outFile === '') {
1008
+ const json = JSON.stringify(spec, null, 2);
1009
+ log('%s', json);
1010
+ return;
1011
+ }
1012
+ const fileName = outFile.toLowerCase();
1013
+ if (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) {
1014
+ const yaml = safeDump(spec);
1015
+ fs.writeFileSync(outFile, yaml, 'utf-8');
1016
+ } else {
1017
+ const json = JSON.stringify(spec, null, 2);
1018
+ fs.writeFileSync(outFile, json, 'utf-8');
1019
+ }
1020
+ log('The OpenAPI spec has been saved to %s.', outFile);
1021
+ }
990
1022
  }
991
1023
 
992
1024
  /**
@@ -3,7 +3,7 @@
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 {Context, InvocationSource} from '@loopback/context';
6
+ import {Context, InvocationSource} from '@loopback/core';
7
7
  import {OperationObject} from '@loopback/openapi-v3';
8
8
  import {OperationArgs, OperationRetval} from '../types';
9
9
  import {RouteEntry} from './route-entry';
@@ -7,11 +7,11 @@ import {
7
7
  BindingScope,
8
8
  Constructor,
9
9
  Context,
10
+ CoreBindings,
10
11
  instantiateClass,
11
12
  invokeMethod,
12
13
  ValueOrPromise,
13
- } from '@loopback/context';
14
- import {CoreBindings} from '@loopback/core';
14
+ } from '@loopback/core';
15
15
  import {ControllerSpec, OperationObject} from '@loopback/openapi-v3';
16
16
  import assert from 'assert';
17
17
  import debugFactory from 'debug';
@@ -1,9 +1,9 @@
1
- // Copyright IBM Corp. 2019. All Rights Reserved.
1
+ // Copyright IBM Corp. 2019,2020. All Rights Reserved.
2
2
  // Node module: @loopback/rest
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 {Context} from '@loopback/context';
6
+ import {Context} from '@loopback/core';
7
7
  import {
8
8
  executeExpressRequestHandler,
9
9
  ExpressRequestHandler,
@@ -3,7 +3,7 @@
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 {Context, invokeMethodWithInterceptors} from '@loopback/context';
6
+ import {Context, invokeMethodWithInterceptors} from '@loopback/core';
7
7
  import {OperationObject} from '@loopback/openapi-v3';
8
8
  import {RestBindings} from '../keys';
9
9
  import {OperationArgs, OperationRetval} from '../types';
@@ -54,8 +54,7 @@ export class RedirectRoute implements RouteEntry, ResolvedRoute {
54
54
  const redirectOptions = obj as RedirectRoute;
55
55
  if (
56
56
  redirectOptions?.targetLocation &&
57
- redirectOptions.spec &&
58
- redirectOptions.spec.description === 'LoopBack Redirect route'
57
+ redirectOptions.spec?.description === 'LoopBack Redirect route'
59
58
  ) {
60
59
  return true;
61
60
  }
@@ -3,7 +3,7 @@
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 {inject} from '@loopback/context';
6
+ import {inject} from '@loopback/core';
7
7
  import {inspect} from 'util';
8
8
  import {RestBindings} from '../keys';
9
9
  import {PathParameterValues} from '../types';
@@ -3,7 +3,7 @@
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 {Context} from '@loopback/context';
6
+ import {Context} from '@loopback/core';
7
7
  import {OperationObject, SchemasObject} from '@loopback/openapi-v3';
8
8
  import {OperationArgs, OperationRetval, PathParameterValues} from '../types';
9
9
 
@@ -1,9 +1,9 @@
1
- // Copyright IBM Corp. 2018,2019. All Rights Reserved.
1
+ // Copyright IBM Corp. 2018,2020. All Rights Reserved.
2
2
  // Node module: @loopback/rest
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 {inject} from '@loopback/context';
6
+ import {inject} from '@loopback/core';
7
7
  import {inspect} from 'util';
8
8
  import {RestBindings} from '../keys';
9
9
  import {RestRouterOptions} from './rest-router';
package/src/sequence.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
6
  const debug = require('debug')('loopback:rest:sequence');
7
- import {inject, ValueOrPromise} from '@loopback/context';
7
+ import {inject, ValueOrPromise} from '@loopback/core';
8
8
  import {InvokeMiddleware} from '@loopback/express';
9
9
  import {RestBindings} from './keys';
10
10
  import {RequestContext} from './request-context';
@@ -4,13 +4,14 @@
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
6
  import {
7
+ ApplicationMetadata,
7
8
  bind,
8
9
  BindingScope,
10
+ CoreBindings,
9
11
  inject,
10
12
  JSONObject,
11
13
  JSONValue,
12
- } from '@loopback/context';
13
- import {ApplicationMetadata, CoreBindings} from '@loopback/core';
14
+ } from '@loopback/core';
14
15
  import {
15
16
  asSpecEnhancer,
16
17
  ContactObject,
package/src/types.ts CHANGED
@@ -108,6 +108,17 @@ export type AjvKeyword = KeywordDefinition & {name: string};
108
108
  */
109
109
  export type AjvFormat = FormatDefinition & {name: string};
110
110
 
111
+ /**
112
+ * Options for any value validation using AJV
113
+ */
114
+ export interface ValueValidationOptions extends RequestBodyValidationOptions {
115
+ /**
116
+ * Where the data comes from. It can be 'body', 'path', 'header',
117
+ * 'query', 'cookie', etc...
118
+ */
119
+ source?: string;
120
+ }
121
+
111
122
  /**
112
123
  * Options for request body validation using AJV
113
124
  */
@@ -15,7 +15,11 @@ import debugModule from 'debug';
15
15
  import _ from 'lodash';
16
16
  import util from 'util';
17
17
  import {HttpErrors, RequestBody, RestHttpErrors} from '..';
18
- import {RequestBodyValidationOptions, SchemaValidatorCache} from '../types';
18
+ import {
19
+ RequestBodyValidationOptions,
20
+ SchemaValidatorCache,
21
+ ValueValidationOptions,
22
+ } from '../types';
19
23
  import {AjvFactoryProvider} from './ajv-factory.provider';
20
24
 
21
25
  const toJsonSchema = require('@openapi-contrib/openapi-schema-to-json-schema');
@@ -66,7 +70,10 @@ export async function validateRequestBody(
66
70
  if (!schema) return;
67
71
 
68
72
  options = {coerceTypes: !!body.coercionRequired, ...options};
69
- await validateValueAgainstSchema(body.value, schema, globalSchemas, options);
73
+ await validateValueAgainstSchema(body.value, schema, globalSchemas, {
74
+ ...options,
75
+ source: 'body',
76
+ });
70
77
  }
71
78
 
72
79
  /**
@@ -115,12 +122,12 @@ function getKeyForOptions(options: RequestBodyValidationOptions) {
115
122
  * @param globalSchemas - Schema references.
116
123
  * @param options - Request body validation options.
117
124
  */
118
- async function validateValueAgainstSchema(
125
+ export async function validateValueAgainstSchema(
119
126
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
- body: any,
127
+ value: any,
121
128
  schema: SchemaObject | ReferenceObject,
122
129
  globalSchemas: SchemasObject = {},
123
- options: RequestBodyValidationOptions = {},
130
+ options: ValueValidationOptions = {},
124
131
  ) {
125
132
  let validate: ajv.ValidateFunction | undefined;
126
133
 
@@ -145,10 +152,10 @@ async function validateValueAgainstSchema(
145
152
 
146
153
  let validationErrors: ajv.ErrorObject[] = [];
147
154
  try {
148
- const validationResult = await validate(body);
149
- // When body is optional & values is empty / null, ajv returns null
155
+ const validationResult = await validate(value);
156
+ // When value is optional & values is empty / null, ajv returns null
150
157
  if (validationResult || validationResult === null) {
151
- debug('Request body passed AJV validation.');
158
+ debug(`Value from ${options.source} passed AJV validation.`);
152
159
  return;
153
160
  }
154
161
  } catch (error) {
@@ -158,8 +165,8 @@ async function validateValueAgainstSchema(
158
165
  /* istanbul ignore if */
159
166
  if (debug.enabled) {
160
167
  debug(
161
- 'Invalid request body: %s. Errors: %s',
162
- util.inspect(body, {depth: null}),
168
+ 'Invalid value: %s. Errors: %s',
169
+ util.inspect(value, {depth: null}),
163
170
  util.inspect(validationErrors),
164
171
  );
165
172
  }
@@ -168,7 +175,24 @@ async function validateValueAgainstSchema(
168
175
  validationErrors = options.ajvErrorTransformer(validationErrors);
169
176
  }
170
177
 
171
- const error = RestHttpErrors.invalidRequestBody();
178
+ // Throw invalid request body error
179
+ if (options.source === 'body') {
180
+ const error = RestHttpErrors.invalidRequestBody();
181
+ addErrorDetails(error, validationErrors);
182
+ throw error;
183
+ }
184
+
185
+ // Throw invalid value error
186
+ const error = new HttpErrors.BadRequest('Invalid value.');
187
+ addErrorDetails(error, validationErrors);
188
+ throw error;
189
+ }
190
+
191
+ function addErrorDetails(
192
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
193
+ error: any,
194
+ validationErrors: ajv.ErrorObject[],
195
+ ) {
172
196
  error.details = _.map(validationErrors, e => {
173
197
  return {
174
198
  path: e.dataPath,
@@ -177,7 +201,6 @@ async function validateValueAgainstSchema(
177
201
  info: e.params,
178
202
  };
179
203
  });
180
- throw error;
181
204
  }
182
205
 
183
206
  /**