@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
@@ -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
+ ];
@@ -12,7 +12,6 @@ import {
12
12
  } from '@loopback/openapi-v3';
13
13
  import ajv, {Ajv} from 'ajv';
14
14
  import debugModule from 'debug';
15
- import _ from 'lodash';
16
15
  import util from 'util';
17
16
  import {HttpErrors, RequestBody, RestHttpErrors} from '..';
18
17
  import {
@@ -119,11 +118,11 @@ function getKeyForOptions(
119
118
  }
120
119
 
121
120
  /**
122
- * Validate the request body data against JSON schema.
123
- * @param body - The request body data.
121
+ * Validate the value against JSON schema.
122
+ * @param value - The data value.
124
123
  * @param schema - The JSON schema used to perform the validation.
125
124
  * @param globalSchemas - Schema references.
126
- * @param options - Request body validation options.
125
+ * @param options - Value validation options.
127
126
  */
128
127
  export async function validateValueAgainstSchema(
129
128
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -156,11 +155,11 @@ export async function validateValueAgainstSchema(
156
155
  let validationErrors: ajv.ErrorObject[] = [];
157
156
  try {
158
157
  const validationResult = await validate(value);
159
- // When value is optional & values is empty / null, ajv returns null
160
- if (validationResult || validationResult === null) {
161
- debug(`Value from ${options.source} passed AJV validation.`);
162
- return;
163
- }
158
+ debug(
159
+ `Value from ${options.source} passed AJV validation.`,
160
+ validationResult,
161
+ );
162
+ return validationResult;
164
163
  } catch (error) {
165
164
  validationErrors = error.errors;
166
165
  }
@@ -180,30 +179,32 @@ export async function validateValueAgainstSchema(
180
179
 
181
180
  // Throw invalid request body error
182
181
  if (options.source === 'body') {
183
- const error = RestHttpErrors.invalidRequestBody();
184
- addErrorDetails(error, validationErrors);
182
+ const error = RestHttpErrors.invalidRequestBody(
183
+ buildErrorDetails(validationErrors),
184
+ );
185
185
  throw error;
186
186
  }
187
187
 
188
188
  // Throw invalid value error
189
- const error = new HttpErrors.BadRequest('Invalid value.');
190
- addErrorDetails(error, validationErrors);
189
+ const error = RestHttpErrors.invalidData(value, options.name ?? '(unknown)', {
190
+ details: buildErrorDetails(validationErrors),
191
+ });
191
192
  throw error;
192
193
  }
193
194
 
194
- function addErrorDetails(
195
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
196
- error: any,
195
+ function buildErrorDetails(
197
196
  validationErrors: ajv.ErrorObject[],
198
- ) {
199
- error.details = _.map(validationErrors, e => {
200
- return {
201
- path: e.dataPath,
202
- code: e.keyword,
203
- message: e.message,
204
- info: e.params,
205
- };
206
- });
197
+ ): RestHttpErrors.ValidationErrorDetails[] {
198
+ return validationErrors.map(
199
+ (e: ajv.ErrorObject): RestHttpErrors.ValidationErrorDetails => {
200
+ return {
201
+ path: e.dataPath,
202
+ code: e.keyword,
203
+ message: e.message ?? `must pass validation rule ${e.keyword}`,
204
+ info: e.params,
205
+ };
206
+ },
207
+ );
207
208
  }
208
209
 
209
210
  /**