Skip to content

Commit 1762765

Browse files
committed
feat(rest): add error codes for REST validation errors
Add the following error codes: - VALIDATION_FAILED - INVALID_PARAMETER_VALUE - MISSING_REQUIRED_PARAMETER
1 parent 3521b92 commit 1762765

File tree

4 files changed

+53
-15
lines changed

4 files changed

+53
-15
lines changed

docs/site/Error-handling.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ permalink: /doc/en/lb4/Error-handling.html
1111
In order to allow clients to reliably detect individual error causes, LoopBack
1212
sets the error `code` property to a machine-readable string.
1313

14-
| Error code | Description |
15-
| :--------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16-
| ENTITY_NOT_FOUND | The entity (model) was not found. This error is returned for example by [`EntityCrudRepository.prototype.findById`](http://apidocs.loopback.io/@loopback%2fdocs/repository.html#EntityCrudRepository.prototype.findById) |
14+
| Error code | Description |
15+
| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16+
| ENTITY_NOT_FOUND | The entity (model) was not found. This error is returned for example by [`EntityCrudRepository.prototype.findById`](http://apidocs.loopback.io/@loopback%2fdocs/repository.html#EntityCrudRepository.prototype.findById) |
17+
| VALIDATION_FAILED | The data provided by the client is not a valid entity. |
18+
| INVALID_PARAMETER_VALUE | The value provided for a parameter of a REST endpoint is not valid. For example, a string value was provided for a numeric parameter. |
19+
| MISSING_REQUIRED_PARAMETER | No value was provided for a required parameter. |
1720

1821
Besides LoopBack-specific error codes, your application can encounter low-level
1922
error codes from Node.js and the underlying operating system. For example, when

packages/rest/src/rest-http-error.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,22 @@ export namespace RestHttpErrors {
77
extraProperties?: Props,
88
): HttpErrors.HttpError & Props {
99
const msg = `Invalid data ${JSON.stringify(data)} for parameter ${name}!`;
10-
return Object.assign(new HttpErrors.BadRequest(msg), extraProperties);
10+
return Object.assign(
11+
new HttpErrors.BadRequest(msg),
12+
{
13+
code: 'INVALID_PARAMETER_VALUE',
14+
parameterName: name,
15+
},
16+
extraProperties,
17+
);
1118
}
1219

1320
export function missingRequired(name: string): HttpErrors.HttpError {
1421
const msg = `Required parameter ${name} is missing!`;
15-
return new HttpErrors.BadRequest(msg);
22+
return Object.assign(new HttpErrors.BadRequest(msg), {
23+
code: 'MISSING_REQUIRED_PARAMETER',
24+
parameterName: name,
25+
});
1626
}
1727

1828
export function invalidParamLocation(location: string): HttpErrors.HttpError {
@@ -23,7 +33,12 @@ export namespace RestHttpErrors {
2333
export const INVALID_REQUEST_BODY_MESSAGE =
2434
'The request body is invalid. See error object `details` property for more info.';
2535
export function invalidRequestBody(): HttpErrors.HttpError {
26-
return new HttpErrors.UnprocessableEntity(INVALID_REQUEST_BODY_MESSAGE);
36+
return Object.assign(
37+
new HttpErrors.UnprocessableEntity(INVALID_REQUEST_BODY_MESSAGE),
38+
{
39+
code: 'VALIDATION_FAILED',
40+
},
41+
);
2742
}
2843

2944
/**

packages/rest/src/validation/request-body.validator.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,18 @@ export function validateRequestBody(
3333
requestBodySpec: RequestBodyObject | undefined,
3434
globalSchemas?: SchemasObject,
3535
) {
36-
if (requestBodySpec && requestBodySpec.required && body == undefined)
37-
throw new HttpErrors.BadRequest('Request body is required');
36+
if (!requestBodySpec) return;
37+
38+
if (requestBodySpec.required && body == undefined) {
39+
const err = Object.assign(
40+
new HttpErrors.BadRequest('Request body is required'),
41+
{
42+
code: 'MISSING_REQUIRED_PARAMETER',
43+
parameterName: 'request body',
44+
},
45+
);
46+
throw err;
47+
}
3848

3949
const schema = getRequestBodySchema(requestBodySpec);
4050
debug('Request body schema: %j', util.inspect(schema, {depth: null}));
@@ -49,10 +59,8 @@ export function validateRequestBody(
4959
* @param requestBodySpec The requestBody specification defined in `@requestBody()`.
5060
*/
5161
function getRequestBodySchema(
52-
requestBodySpec: RequestBodyObject | undefined,
62+
requestBodySpec: RequestBodyObject,
5363
): SchemaObject | undefined {
54-
if (!requestBodySpec) return;
55-
5664
const content = requestBodySpec.content;
5765
// FIXME(bajtos) we need to find the entry matching the content-type
5866
// header from the incoming request (e.g. "application/json").

packages/rest/test/unit/request-body.validator.test.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ describe('validateRequestBody', () => {
6767
];
6868
verifyValidationRejectsInputWithError(
6969
INVALID_MSG,
70+
'VALIDATION_FAILED',
7071
details,
7172
{
7273
description: 'missing required "title"',
@@ -86,6 +87,7 @@ describe('validateRequestBody', () => {
8687
];
8788
verifyValidationRejectsInputWithError(
8889
INVALID_MSG,
90+
'VALIDATION_FAILED',
8991
details,
9092
{
9193
title: 'todo with a string value of "isComplete"',
@@ -112,6 +114,7 @@ describe('validateRequestBody', () => {
112114
];
113115
verifyValidationRejectsInputWithError(
114116
INVALID_MSG,
117+
'VALIDATION_FAILED',
115118
details,
116119
{
117120
description: 'missing title and a string value of "isComplete"',
@@ -140,6 +143,7 @@ describe('validateRequestBody', () => {
140143
];
141144
verifyValidationRejectsInputWithError(
142145
INVALID_MSG,
146+
'VALIDATION_FAILED',
143147
details,
144148
{description: 'missing title'},
145149
aBodySpec({$ref: '#/components/schemas/Todo'}),
@@ -150,6 +154,7 @@ describe('validateRequestBody', () => {
150154
it('rejects empty values when body is required', () => {
151155
verifyValidationRejectsInputWithError(
152156
'Request body is required',
157+
'MISSING_REQUIRED_PARAMETER',
153158
undefined,
154159
null,
155160
aBodySpec(TODO_SCHEMA, {required: true}),
@@ -176,6 +181,7 @@ describe('validateRequestBody', () => {
176181
};
177182
verifyValidationRejectsInputWithError(
178183
INVALID_MSG,
184+
'VALIDATION_FAILED',
179185
details,
180186
{count: 'string value'},
181187
aBodySpec(schema),
@@ -205,6 +211,7 @@ describe('validateRequestBody', () => {
205211
};
206212
verifyValidationRejectsInputWithError(
207213
INVALID_MSG,
214+
'VALIDATION_FAILED',
208215
details,
209216
{orders: ['order1', 1]},
210217
aBodySpec(schema),
@@ -228,6 +235,7 @@ describe('validateRequestBody', () => {
228235
};
229236
verifyValidationRejectsInputWithError(
230237
INVALID_MSG,
238+
'VALIDATION_FAILED',
231239
details,
232240
[{title: 'a good todo'}, {description: 'a todo item missing title'}],
233241
aBodySpec(schema),
@@ -263,6 +271,7 @@ describe('validateRequestBody', () => {
263271
};
264272
verifyValidationRejectsInputWithError(
265273
INVALID_MSG,
274+
'VALIDATION_FAILED',
266275
details,
267276
{
268277
todos: [
@@ -298,6 +307,7 @@ describe('validateRequestBody', () => {
298307
};
299308
verifyValidationRejectsInputWithError(
300309
INVALID_MSG,
310+
'VALIDATION_FAILED',
301311
details,
302312
{
303313
accounts: [
@@ -314,8 +324,9 @@ describe('validateRequestBody', () => {
314324
// ----- HELPERS ----- /
315325

316326
function verifyValidationRejectsInputWithError(
317-
errorMatcher: Error | RegExp | string,
318-
details: RestHttpErrors.ValidationErrorDetails[] | undefined,
327+
expectedMessage: string,
328+
expectedCode: string,
329+
expectedDetails: RestHttpErrors.ValidationErrorDetails[] | undefined,
319330
body: object | null,
320331
spec: RequestBodyObject | undefined,
321332
schemas?: SchemasObject,
@@ -326,7 +337,8 @@ function verifyValidationRejectsInputWithError(
326337
"expected Function { name: 'validateRequestBody' } to throw exception",
327338
);
328339
} catch (err) {
329-
expect(err.message).to.equal(errorMatcher);
330-
expect(err.details).to.deepEqual(details);
340+
expect(err.message).to.equal(expectedMessage);
341+
expect(err.code).to.equal(expectedCode);
342+
expect(err.details).to.deepEqual(expectedDetails);
331343
}
332344
}

0 commit comments

Comments
 (0)