@loopback/rest 6.1.0 → 8.0.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 (78) hide show
  1. package/CHANGELOG.md +109 -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.application.js +1 -1
  30. package/dist/rest.application.js.map +1 -1
  31. package/dist/rest.server.d.ts +1 -0
  32. package/dist/rest.server.js +5 -4
  33. package/dist/rest.server.js.map +1 -1
  34. package/dist/router/base-route.js +3 -3
  35. package/dist/router/base-route.js.map +1 -1
  36. package/dist/router/controller-route.js +1 -1
  37. package/dist/router/controller-route.js.map +1 -1
  38. package/dist/router/handler-route.js +1 -1
  39. package/dist/router/handler-route.js.map +1 -1
  40. package/dist/router/redirect-route.js +1 -1
  41. package/dist/router/redirect-route.js.map +1 -1
  42. package/dist/sequence.js +1 -1
  43. package/dist/sequence.js.map +1 -1
  44. package/dist/spec-enhancers/consolidate.spec-enhancer.js +1 -1
  45. package/dist/spec-enhancers/consolidate.spec-enhancer.js.map +1 -1
  46. package/dist/spec-enhancers/info.spec-enhancer.js +13 -5
  47. package/dist/spec-enhancers/info.spec-enhancer.js.map +1 -1
  48. package/dist/validation/ajv-factory.provider.js +5 -3
  49. package/dist/validation/ajv-factory.provider.js.map +1 -1
  50. package/dist/validation/openapi-formats.d.ts +26 -0
  51. package/dist/validation/openapi-formats.js +85 -0
  52. package/dist/validation/openapi-formats.js.map +1 -0
  53. package/dist/validation/request-body.validator.d.ts +4 -4
  54. package/dist/validation/request-body.validator.js +5 -8
  55. package/dist/validation/request-body.validator.js.map +1 -1
  56. package/package.json +25 -22
  57. package/src/body-parsers/body-parser.ts +3 -0
  58. package/src/coercion/coerce-parameter.ts +43 -19
  59. package/src/http-handler.ts +6 -0
  60. package/src/providers/find-route.provider.ts +24 -8
  61. package/src/providers/invoke-method.provider.ts +28 -10
  62. package/src/providers/log-error.provider.ts +2 -1
  63. package/src/providers/parse-params.provider.ts +28 -8
  64. package/src/providers/reject.provider.ts +4 -3
  65. package/src/providers/send.provider.ts +9 -12
  66. package/src/request-context.ts +2 -1
  67. package/src/rest.application.ts +1 -1
  68. package/src/rest.server.ts +8 -5
  69. package/src/router/base-route.ts +3 -3
  70. package/src/router/controller-route.ts +1 -1
  71. package/src/router/handler-route.ts +3 -1
  72. package/src/router/redirect-route.ts +1 -1
  73. package/src/sequence.ts +2 -2
  74. package/src/spec-enhancers/consolidate.spec-enhancer.ts +2 -2
  75. package/src/spec-enhancers/info.spec-enhancer.ts +15 -6
  76. package/src/validation/ajv-factory.provider.ts +8 -4
  77. package/src/validation/openapi-formats.ts +92 -0
  78. package/src/validation/request-body.validator.ts +8 -8
@@ -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.
@@ -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
  /**
@@ -111,7 +111,8 @@ const SequenceActions = RestBindings.SequenceActions;
111
111
  * const server = await app.get('servers.foo');
112
112
  * ```
113
113
  */
114
- export class RestServer extends BaseMiddlewareRegistry
114
+ export class RestServer
115
+ extends BaseMiddlewareRegistry
115
116
  implements Server, HttpServerLike {
116
117
  /**
117
118
  * Handle incoming HTTP(S) request by invoking the corresponding
@@ -170,6 +171,10 @@ export class RestServer extends BaseMiddlewareRegistry
170
171
  return this._httpServer ? this._httpServer.listening : false;
171
172
  }
172
173
 
174
+ get httpServer(): HttpServer | undefined {
175
+ return this._httpServer;
176
+ }
177
+
173
178
  /**
174
179
  * The base url for the server, including the basePath if set. For example,
175
180
  * the value will be 'http://localhost:3000/api' if `basePath` is set to
@@ -206,6 +211,7 @@ export class RestServer extends BaseMiddlewareRegistry
206
211
  config: RestServerConfig = {},
207
212
  ) {
208
213
  super(app);
214
+ this.scope = BindingScope.SERVER;
209
215
 
210
216
  this.config = resolveRestServerConfig(config);
211
217
 
@@ -978,10 +984,7 @@ export class RestServer extends BaseMiddlewareRegistry
978
984
  return;
979
985
  }
980
986
 
981
- const serverOptions = {};
982
- if (protocol === 'https') Object.assign(serverOptions, httpsOptions);
983
- Object.assign(serverOptions, {port, host, protocol, path});
984
-
987
+ const serverOptions = {...httpsOptions, port, host, protocol, path};
985
988
  this._httpServer = new HttpServer(this.requestHandler, serverOptions);
986
989
 
987
990
  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
@@ -4,11 +4,11 @@
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
6
  import {
7
- bind,
8
7
  BindingScope,
9
8
  config,
10
9
  Context,
11
10
  inject,
11
+ injectable,
12
12
  ValueOrPromise,
13
13
  } from '@loopback/core';
14
14
  import {
@@ -185,7 +185,7 @@ export namespace RestMiddlewareGroups {
185
185
  /**
186
186
  * A sequence implementation using middleware chains
187
187
  */
188
- @bind({scope: BindingScope.SINGLETON})
188
+ @injectable({scope: BindingScope.SINGLETON})
189
189
  export class MiddlewareSequence implements SequenceHandler {
190
190
  private middlewareView: MiddlewareView;
191
191
 
@@ -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);
@@ -4,16 +4,17 @@
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
6
  import {
7
- bind,
8
7
  BindingScope,
9
8
  filterByTag,
10
9
  inject,
10
+ injectable,
11
11
  Provider,
12
12
  } from '@loopback/core';
13
13
  import AjvCtor from 'ajv';
14
14
  import debugModule from 'debug';
15
15
  import {RestBindings, RestTags} from '../keys';
16
16
  import {AjvFactory, AjvFormat, AjvKeyword, ValidationOptions} from '../types';
17
+ import {openapiFormats} from './openapi-formats';
17
18
 
18
19
  const debug = debugModule('loopback:rest:ajv');
19
20
 
@@ -29,7 +30,7 @@ export const DEFAULT_AJV_VALIDATION_OPTIONS: ValidationOptions = {
29
30
  /**
30
31
  * A provider class that instantiate an AJV instance
31
32
  */
32
- @bind({scope: BindingScope.SINGLETON})
33
+ @injectable({scope: BindingScope.SINGLETON})
33
34
  export class AjvFactoryProvider implements Provider<AjvFactory> {
34
35
  constructor(
35
36
  @inject(
@@ -57,8 +58,6 @@ export class AjvFactoryProvider implements Provider<AjvFactory> {
57
58
  jsonPointers: true,
58
59
  // nullable: support keyword "nullable" from Open API 3 specification.
59
60
  nullable: true,
60
- // Allow OpenAPI spec binary format
61
- unknownFormats: ['binary'],
62
61
  ...validationOptions,
63
62
  };
64
63
 
@@ -84,12 +83,17 @@ export class AjvFactoryProvider implements Provider<AjvFactory> {
84
83
  });
85
84
  }
86
85
 
86
+ for (const format of openapiFormats) {
87
+ ajvInst.addFormat(format.name, format);
88
+ }
89
+
87
90
  if (this.formats) {
88
91
  this.formats.forEach(format => {
89
92
  debug('Adding Ajv format %s', format.name);
90
93
  ajvInst.addFormat(format.name, format);
91
94
  });
92
95
  }
96
+
93
97
  return ajvInst;
94
98
  };
95
99
  }
@@ -0,0 +1,92 @@
1
+ // Copyright IBM Corp. 2020. All Rights Reserved.
2
+ // Node module: @loopback/rest
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ import {AjvFormat} from '../types';
7
+
8
+ /**
9
+ * int32: [-2147483648, 21474836 47]
10
+ */
11
+ export const int32Format: AjvFormat = {
12
+ name: 'int32',
13
+ type: 'number',
14
+ validate: (value: number) => {
15
+ return (
16
+ Number.isInteger(value) && value >= -2147483648 && value <= 2147483647
17
+ );
18
+ },
19
+ async: false,
20
+ };
21
+
22
+ /**
23
+ * int64: [-9223372036854775808, 9223372036854775807]
24
+ */
25
+ export const int64Format: AjvFormat = {
26
+ name: 'int64',
27
+ type: 'number',
28
+ validate: (value: number) => {
29
+ const max = Number.MAX_SAFE_INTEGER; // 9007199254740991
30
+ const min = Number.MIN_SAFE_INTEGER; // -9007199254740991
31
+ return Number.isInteger(value) && value >= min && value <= max;
32
+ },
33
+ async: false,
34
+ };
35
+
36
+ /**
37
+ * float: [-2^128, 2^128]
38
+ */
39
+ export const floatFormat: AjvFormat = {
40
+ name: 'float',
41
+ type: 'number',
42
+ validate: (value: number) => {
43
+ return value >= -Math.pow(2, 128) && value <= Math.pow(2, 128);
44
+ },
45
+ async: false,
46
+ };
47
+
48
+ /**
49
+ * double: [-2^1024, 2^1024]
50
+ */
51
+ export const doubleFormat: AjvFormat = {
52
+ name: 'double',
53
+ type: 'number',
54
+ validate: (value: number) => {
55
+ const max = Number.MAX_VALUE; // 1.7976931348623157e+308
56
+ const min = -Number.MAX_VALUE; // -1.7976931348623157e+308
57
+ return value >= min && value <= max;
58
+ },
59
+ async: false,
60
+ };
61
+
62
+ /**
63
+ * Base64 encoded string
64
+ */
65
+ export const byteFormat: AjvFormat = {
66
+ name: 'byte',
67
+ type: 'string',
68
+ validate: (value: string) => {
69
+ const base64 = Buffer.from(value, 'base64').toString('base64');
70
+ return value === base64;
71
+ },
72
+ async: false,
73
+ };
74
+
75
+ /**
76
+ * Binary string
77
+ */
78
+ export const binaryFormat: AjvFormat = {
79
+ name: 'binary',
80
+ type: 'string',
81
+ validate: (value: string) => true,
82
+ async: false,
83
+ };
84
+
85
+ export const openapiFormats: AjvFormat[] = [
86
+ int32Format,
87
+ int64Format,
88
+ floatFormat,
89
+ doubleFormat,
90
+ byteFormat,
91
+ binaryFormat,
92
+ ];
@@ -118,11 +118,11 @@ function getKeyForOptions(
118
118
  }
119
119
 
120
120
  /**
121
- * Validate the request body data against JSON schema.
122
- * @param body - The request body data.
121
+ * Validate the value against JSON schema.
122
+ * @param value - The data value.
123
123
  * @param schema - The JSON schema used to perform the validation.
124
124
  * @param globalSchemas - Schema references.
125
- * @param options - Request body validation options.
125
+ * @param options - Value validation options.
126
126
  */
127
127
  export async function validateValueAgainstSchema(
128
128
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -155,11 +155,11 @@ export async function validateValueAgainstSchema(
155
155
  let validationErrors: ajv.ErrorObject[] = [];
156
156
  try {
157
157
  const validationResult = await validate(value);
158
- // When value is optional & values is empty / null, ajv returns null
159
- if (validationResult || validationResult === null) {
160
- debug(`Value from ${options.source} passed AJV validation.`);
161
- return;
162
- }
158
+ debug(
159
+ `Value from ${options.source} passed AJV validation.`,
160
+ validationResult,
161
+ );
162
+ return validationResult;
163
163
  } catch (error) {
164
164
  validationErrors = error.errors;
165
165
  }