Skip to content

Commit

Permalink
feat(rest): add error codes for REST validation errors
Browse files Browse the repository at this point in the history
Add the following error codes:
 - VALIDATION_FAILED
 - INVALID_PARAMETER_VALUE
 - MISSING_REQUIRED_PARAMETER
  • Loading branch information
bajtos committed Sep 18, 2018
1 parent 3521b92 commit 1762765
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 15 deletions.
9 changes: 6 additions & 3 deletions docs/site/Error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ permalink: /doc/en/lb4/Error-handling.html
In order to allow clients to reliably detect individual error causes, LoopBack
sets the error `code` property to a machine-readable string.

| Error code | Description |
| :--------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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) |
| Error code | Description |
| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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) |
| VALIDATION_FAILED | The data provided by the client is not a valid entity. |
| 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. |
| MISSING_REQUIRED_PARAMETER | No value was provided for a required parameter. |

Besides LoopBack-specific error codes, your application can encounter low-level
error codes from Node.js and the underlying operating system. For example, when
Expand Down
21 changes: 18 additions & 3 deletions packages/rest/src/rest-http-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,22 @@ export namespace RestHttpErrors {
extraProperties?: Props,
): HttpErrors.HttpError & Props {
const msg = `Invalid data ${JSON.stringify(data)} for parameter ${name}!`;
return Object.assign(new HttpErrors.BadRequest(msg), extraProperties);
return Object.assign(
new HttpErrors.BadRequest(msg),
{
code: 'INVALID_PARAMETER_VALUE',
parameterName: name,
},
extraProperties,
);
}

export function missingRequired(name: string): HttpErrors.HttpError {
const msg = `Required parameter ${name} is missing!`;
return new HttpErrors.BadRequest(msg);
return Object.assign(new HttpErrors.BadRequest(msg), {
code: 'MISSING_REQUIRED_PARAMETER',
parameterName: name,
});
}

export function invalidParamLocation(location: string): HttpErrors.HttpError {
Expand All @@ -23,7 +33,12 @@ export namespace RestHttpErrors {
export const INVALID_REQUEST_BODY_MESSAGE =
'The request body is invalid. See error object `details` property for more info.';
export function invalidRequestBody(): HttpErrors.HttpError {
return new HttpErrors.UnprocessableEntity(INVALID_REQUEST_BODY_MESSAGE);
return Object.assign(
new HttpErrors.UnprocessableEntity(INVALID_REQUEST_BODY_MESSAGE),
{
code: 'VALIDATION_FAILED',
},
);
}

/**
Expand Down
18 changes: 13 additions & 5 deletions packages/rest/src/validation/request-body.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,18 @@ export function validateRequestBody(
requestBodySpec: RequestBodyObject | undefined,
globalSchemas?: SchemasObject,
) {
if (requestBodySpec && requestBodySpec.required && body == undefined)
throw new HttpErrors.BadRequest('Request body is required');
if (!requestBodySpec) return;

if (requestBodySpec.required && body == undefined) {
const err = Object.assign(
new HttpErrors.BadRequest('Request body is required'),
{
code: 'MISSING_REQUIRED_PARAMETER',
parameterName: 'request body',
},
);
throw err;
}

const schema = getRequestBodySchema(requestBodySpec);
debug('Request body schema: %j', util.inspect(schema, {depth: null}));
Expand All @@ -49,10 +59,8 @@ export function validateRequestBody(
* @param requestBodySpec The requestBody specification defined in `@requestBody()`.
*/
function getRequestBodySchema(
requestBodySpec: RequestBodyObject | undefined,
requestBodySpec: RequestBodyObject,
): SchemaObject | undefined {
if (!requestBodySpec) return;

const content = requestBodySpec.content;
// FIXME(bajtos) we need to find the entry matching the content-type
// header from the incoming request (e.g. "application/json").
Expand Down
20 changes: 16 additions & 4 deletions packages/rest/test/unit/request-body.validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ describe('validateRequestBody', () => {
];
verifyValidationRejectsInputWithError(
INVALID_MSG,
'VALIDATION_FAILED',
details,
{
description: 'missing required "title"',
Expand All @@ -86,6 +87,7 @@ describe('validateRequestBody', () => {
];
verifyValidationRejectsInputWithError(
INVALID_MSG,
'VALIDATION_FAILED',
details,
{
title: 'todo with a string value of "isComplete"',
Expand All @@ -112,6 +114,7 @@ describe('validateRequestBody', () => {
];
verifyValidationRejectsInputWithError(
INVALID_MSG,
'VALIDATION_FAILED',
details,
{
description: 'missing title and a string value of "isComplete"',
Expand Down Expand Up @@ -140,6 +143,7 @@ describe('validateRequestBody', () => {
];
verifyValidationRejectsInputWithError(
INVALID_MSG,
'VALIDATION_FAILED',
details,
{description: 'missing title'},
aBodySpec({$ref: '#/components/schemas/Todo'}),
Expand All @@ -150,6 +154,7 @@ describe('validateRequestBody', () => {
it('rejects empty values when body is required', () => {
verifyValidationRejectsInputWithError(
'Request body is required',
'MISSING_REQUIRED_PARAMETER',
undefined,
null,
aBodySpec(TODO_SCHEMA, {required: true}),
Expand All @@ -176,6 +181,7 @@ describe('validateRequestBody', () => {
};
verifyValidationRejectsInputWithError(
INVALID_MSG,
'VALIDATION_FAILED',
details,
{count: 'string value'},
aBodySpec(schema),
Expand Down Expand Up @@ -205,6 +211,7 @@ describe('validateRequestBody', () => {
};
verifyValidationRejectsInputWithError(
INVALID_MSG,
'VALIDATION_FAILED',
details,
{orders: ['order1', 1]},
aBodySpec(schema),
Expand All @@ -228,6 +235,7 @@ describe('validateRequestBody', () => {
};
verifyValidationRejectsInputWithError(
INVALID_MSG,
'VALIDATION_FAILED',
details,
[{title: 'a good todo'}, {description: 'a todo item missing title'}],
aBodySpec(schema),
Expand Down Expand Up @@ -263,6 +271,7 @@ describe('validateRequestBody', () => {
};
verifyValidationRejectsInputWithError(
INVALID_MSG,
'VALIDATION_FAILED',
details,
{
todos: [
Expand Down Expand Up @@ -298,6 +307,7 @@ describe('validateRequestBody', () => {
};
verifyValidationRejectsInputWithError(
INVALID_MSG,
'VALIDATION_FAILED',
details,
{
accounts: [
Expand All @@ -314,8 +324,9 @@ describe('validateRequestBody', () => {
// ----- HELPERS ----- /

function verifyValidationRejectsInputWithError(
errorMatcher: Error | RegExp | string,
details: RestHttpErrors.ValidationErrorDetails[] | undefined,
expectedMessage: string,
expectedCode: string,
expectedDetails: RestHttpErrors.ValidationErrorDetails[] | undefined,
body: object | null,
spec: RequestBodyObject | undefined,
schemas?: SchemasObject,
Expand All @@ -326,7 +337,8 @@ function verifyValidationRejectsInputWithError(
"expected Function { name: 'validateRequestBody' } to throw exception",
);
} catch (err) {
expect(err.message).to.equal(errorMatcher);
expect(err.details).to.deepEqual(details);
expect(err.message).to.equal(expectedMessage);
expect(err.code).to.equal(expectedCode);
expect(err.details).to.deepEqual(expectedDetails);
}
}

0 comments on commit 1762765

Please sign in to comment.